Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@ jobs:
- run: bun install
- run: bun run typecheck
- run: bun run lint
- run: bun run format:check
- run: bun run build
- run: bun test
3 changes: 3 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,11 @@ bun install # Install dependencies
bun run build # Build TypeScript to dist/
bun run dev # Watch mode for development
bun link # Link binary for local testing (creates `of` command)
bun run format # Format src/ with Biome (format:check runs in CI)
```

Biome is v2: `biome.json` uses `files.includes` with `!` negations. Biome 1 keys (`include`, `ignore`) make it abort before formatting. After a Biome major bump, run `bunx biome migrate --write`.

### Testing the CLI
After `bun link`, use `of` command globally:
```bash
Expand Down
5 changes: 2 additions & 3 deletions biome.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
{
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
"$schema": "https://biomejs.dev/schemas/2.3.11/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true
},
"files": {
"include": ["src/**/*.ts"],
"ignore": ["dist", "node_modules"]
"includes": ["**/src/**/*.ts", "!**/dist", "!**/node_modules"]
},
"formatter": {
"enabled": true,
Expand Down
7 changes: 6 additions & 1 deletion src/lib/__tests__/errors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,12 @@ interface HandleErrorResult {
}

async function runHandleError(
scenario: 'omnifocus_cli_error' | 'error_not_found' | 'error_multiple' | 'error_plain' | 'non_error'
scenario:
| 'omnifocus_cli_error'
| 'error_not_found'
| 'error_multiple'
| 'error_plain'
| 'non_error'
): Promise<HandleErrorResult> {
const tmp = await mkdtemp(join(tmpdir(), 'of-errors-test-'));
const script = join(tmp, 'run.mjs');
Expand Down
4 changes: 1 addition & 3 deletions src/lib/__tests__/pipe-truncation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,7 @@ async function pipedStdoutLength(args: string[]): Promise<number> {
describe('pipe truncation regression (issue #20)', () => {
beforeAll(() => {
if (!existsSync(distCli)) {
throw new Error(
`dist/cli.js not found — run \`bun run build\` before this test suite.`
);
throw new Error(`dist/cli.js not found — run \`bun run build\` before this test suite.`);
}
});

Expand Down
27 changes: 20 additions & 7 deletions src/mcp/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,13 @@ server.tool(
async ({ query }) => jsonResponse(await of.searchTasks(query))
);

server.tool('get_task_stats', 'Get task statistics', {}, async () => jsonResponse(await of.getTaskStats()));
server.tool('get_task_stats', 'Get task statistics', {}, async () =>
jsonResponse(await of.getTaskStats())
);

server.tool('list_inbox', 'List all inbox tasks', {}, async () => jsonResponse(await of.listInboxTasks()));
server.tool('list_inbox', 'List all inbox tasks', {}, async () =>
jsonResponse(await of.listInboxTasks())
);

server.tool('get_inbox_count', 'Get the number of inbox tasks', {}, async () =>
jsonResponse({ count: await of.getInboxCount() })
Expand Down Expand Up @@ -178,7 +182,9 @@ server.tool(
}
);

server.tool('get_project_stats', 'Get project statistics', {}, async () => jsonResponse(await of.getProjectStats()));
server.tool('get_project_stats', 'Get project statistics', {}, async () =>
jsonResponse(await of.getProjectStats())
);

server.tool('list_perspectives', 'List all available perspectives', {}, async () =>
jsonResponse(await of.listPerspectives())
Expand Down Expand Up @@ -241,7 +247,9 @@ server.tool(
}
);

server.tool('get_tag_stats', 'Get tag statistics', {}, async () => jsonResponse(await of.getTagStats()));
server.tool('get_tag_stats', 'Get tag statistics', {}, async () =>
jsonResponse(await of.getTagStats())
);

server.tool(
'list_folders',
Expand All @@ -257,19 +265,24 @@ server.tool(
idOrName: z.string().describe('Folder ID or name'),
includeDropped: z.boolean().optional().describe('Include dropped children'),
},
async ({ idOrName, includeDropped }) => jsonResponse(await of.getFolder(idOrName, { includeDropped }))
async ({ idOrName, includeDropped }) =>
jsonResponse(await of.getFolder(idOrName, { includeDropped }))
);

server.tool(
'search_tools',
'Search for available tools by name or description using regex. Returns matching tool names.',
{
query: z.string().describe('Regex pattern to match against tool names and descriptions (case-insensitive)'),
query: z
.string()
.describe('Regex pattern to match against tool names and descriptions (case-insensitive)'),
},
async ({ query }) => {
try {
const pattern = new RegExp(query, 'i');
const matches = toolRegistry.filter((t) => pattern.test(t.name) || pattern.test(t.description));
const matches = toolRegistry.filter(
(t) => pattern.test(t.name) || pattern.test(t.description)
);
return jsonResponse({ tools: matches });
} catch {
return jsonResponse({ error: 'Invalid regex pattern' });
Expand Down
Loading