Skip to content

Commit

Permalink
Update motion extension
Browse files Browse the repository at this point in the history
- Fix linting issues for Raycast Store submission
- Add task deletion and AI query capabilities
- Merge remote changes and resolve conflicts
- Add Project field to task creation form and API client
- Update README.md
  • Loading branch information
owendavidprice committed Mar 2, 2025
1 parent 994b604 commit ecf585b
Show file tree
Hide file tree
Showing 7 changed files with 611 additions and 25 deletions.
10 changes: 9 additions & 1 deletion extensions/motion/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,18 @@ A Raycast extension for interacting with the [Motion](https://www.usemotion.com/

## Installation

<<<<<<< HEAD
1. Clone this repository
2. Open the directory in your terminal
3. Run `npm install` to install dependencies
4. Run `npm run dev` to start the development server
=======
1. Install the extension from the Raycast store.
2. Set up your Motion API credentials:
- Get your API key from Motion's developer settings
- Find your workspace ID in your Motion account (to be confirmed how to do this)
- Add these to the extension preferences in Raycast
>>>>>>> origin/main
## Configuration

Expand Down Expand Up @@ -41,4 +49,4 @@ npm install

# Start development server
npm run dev
```
```
25 changes: 25 additions & 0 deletions extensions/motion/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@
"description": "Ask Motion about your deadlines, diary, etc.",
"mode": "view"
},
{
"name": "delete-task",
"title": "Delete Task",
"subtitle": "Delete task from Motion",
"description": "Select and delete tasks from your Motion account",
"mode": "view"
},
{
"name": "debug-workspaces",
"title": "Debug: List Workspaces",
Expand All @@ -49,6 +56,24 @@
"default": "J2-2vXH85SltZ52ieplcF"
}
],
"tools": [
{
"name": "ask-motion",
"title": "Ask Motion",
"description": "Ask about your Motion tasks, deadlines, and schedule. The AI can help you with information about your tasks, upcoming deadlines, and your schedule in Motion.",
"arguments": [
{
"name": "question",
"placeholder": "What tasks are due today?",
"required": true,
"type": "string"
}
]
}
],
"ai": {
"instructions": "You are a helpful AI assistant for Motion, a task and productivity tool. Your role is to help users understand their tasks, deadlines, and schedules in Motion. Be conversational, concise, and friendly. When users ask about tasks, answer based on the information provided to you about their Motion tasks. Remember the context of the conversation when they ask follow-up questions. If you don't know something or the information is not provided, be honest about it."
},
"dependencies": {
"@raycast/api": "^1.93.0",
"@raycast/utils": "^1.17.0",
Expand Down
39 changes: 36 additions & 3 deletions extensions/motion/src/add-task.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Form, ActionPanel, Action, showToast, Toast } from "@raycast/api";
import { useState } from "react";
import { getMotionApiClient, LABEL_PRESETS } from "./api/motion";
import { useState, useEffect } from "react";
import { getMotionApiClient, LABEL_PRESETS, Project } from "./api/motion";

type Values = {
name: string;
Expand All @@ -9,6 +9,7 @@ type Values = {
priority: "LOW" | "MEDIUM" | "HIGH" | "URGENT";
status: "TODO" | "IN_PROGRESS" | "DONE";
label: string;
projectId: string;
};

// Helper function to get tomorrow's date
Expand All @@ -21,10 +22,34 @@ function getTomorrow() {

export default function Command() {
const [isLoading, setIsLoading] = useState(false);
const [projects, setProjects] = useState<Project[]>([]);
const [isLoadingProjects, setIsLoadingProjects] = useState(true);

// Set default values
const tomorrow = getTomorrow();

// Fetch projects when component mounts
useEffect(() => {
async function fetchProjects() {
try {
const motionClient = getMotionApiClient();
const projectsData = await motionClient.getProjects();
setProjects(projectsData);
} catch (error) {
console.error("Error fetching projects:", error);
await showToast({
style: Toast.Style.Failure,
title: "Failed to load projects",
message: String(error),
});
} finally {
setIsLoadingProjects(false);
}
}

fetchProjects();
}, []);

async function handleSubmit(values: Values) {
setIsLoading(true);

Expand All @@ -39,6 +64,7 @@ export default function Command() {
priority: values.priority,
status: values.status,
label: values.label,
projectId: values.projectId,
});

await showToast({
Expand All @@ -61,7 +87,7 @@ export default function Command() {

return (
<Form
isLoading={isLoading}
isLoading={isLoading || isLoadingProjects}
actions={
<ActionPanel>
<Action.SubmitForm onSubmit={handleSubmit} />
Expand Down Expand Up @@ -98,6 +124,13 @@ export default function Command() {
<Form.Dropdown.Item key={label} value={label} title={label} />
))}
</Form.Dropdown>

<Form.Dropdown id="projectId" title="Project">
<Form.Dropdown.Item value="" title="None" />
{projects.map((project) => (
<Form.Dropdown.Item key={project.id} value={project.id} title={project.name} />
))}
</Form.Dropdown>
</Form>
);
}
76 changes: 70 additions & 6 deletions extensions/motion/src/api/motion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@ interface MotionTask {
workspaceId: string;
status?: "TODO" | "IN_PROGRESS" | "DONE";
label?: string;
projectId?: string;
}

export interface Project {
id: string;
name: string;
workspaceId: string;
}

interface Workspace {
Expand Down Expand Up @@ -99,27 +106,38 @@ export const getMotionApiClient = () => {
console.log("[DEBUG] Using corrected workspace ID:", correctWorkspaceId);

return {
// Get the workspace ID (for use in components)
getWorkspaceId(): string {
return correctWorkspaceId;
},

async createTask(taskInput: {
title: string;
description?: string;
dueDate?: Date;
priority?: "LOW" | "MEDIUM" | "HIGH" | "URGENT";
status?: "TODO" | "IN_PROGRESS" | "DONE";
label?: string;
projectId?: string;
}): Promise<MotionTask> {
console.log("[DEBUG] Creating task with input:", JSON.stringify(taskInput, null, 2));

// Simplify our approach now that we know the correct format
const url = `${BASE_URL}/tasks`;

// Destructure to remove the label property which API doesn't accept
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { label, ...taskData } = taskInput;

// Create task object with the correct properties
const task = {
name: taskInput.title,
description: taskInput.description,
dueDate: taskInput.dueDate?.toISOString(),
priority: taskInput.priority,
status: taskInput.status,
name: taskData.title,
description: taskData.description,
dueDate: taskData.dueDate?.toISOString(),
priority: taskData.priority,
status: taskData.status,
workspaceId: correctWorkspaceId, // Use the correct ID
label: taskInput.label,
projectId: taskData.projectId,
};

logRequest("POST", url, headers, task);
Expand All @@ -143,6 +161,52 @@ export const getMotionApiClient = () => {
}
},

async getProjects(): Promise<Project[]> {
const url = `${BASE_URL}/projects?workspaceId=${correctWorkspaceId}`;
logRequest("GET", url, headers);

try {
const response = await fetch(url, {
method: "GET",
headers,
});

if (!response.ok) {
const responseText = await logResponse(response);
throw new Error(`Failed to get projects: ${response.statusText}${responseText ? ` - ${responseText}` : ""}`);
}

// Parse the response
const data = await response.json();

// Log full response for debugging
console.log("[DEBUG] Projects response data:", JSON.stringify(data, null, 2));

// Handle both array and object with projects property formats
if (Array.isArray(data)) {
return data as Project[];
} else if (data && typeof data === "object") {
// Check for common wrapper properties like 'projects', 'items', 'data', etc.
for (const key of ["projects", "items", "data", "results"]) {
if (Array.isArray(data[key])) {
return data[key] as Project[];
}
}

// If we can't find a projects array in a known property, return whatever we got
console.warn("[DEBUG] Couldn't find projects array in response, returning raw data");
return Array.isArray(data) ? data : ([data] as Project[]);
}

// Fallback to empty array if we couldn't parse anything
console.warn("[DEBUG] Returning empty array as couldn't parse projects response");
return [];
} catch (error) {
console.error("[DEBUG] Get projects error:", error);
throw error;
}
},

async getTasks(): Promise<MotionTask[]> {
// The workspace-based URL approach is not working (404 error)
// Let's try using the base tasks endpoint with a query parameter instead
Expand Down
Loading

0 comments on commit ecf585b

Please sign in to comment.