Skip to content

Commit d4dc8d5

Browse files
committed
chore(release): 1.1.1
1 parent 56914c6 commit d4dc8d5

File tree

10 files changed

+188
-47
lines changed

10 files changed

+188
-47
lines changed

MCP_SETUP.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,8 @@ npm install --save-dev @aexol-studio/basecamp-to-llm
8181
{
8282
"mcpServers": {
8383
"basecamp": {
84-
"command": "npx",
85-
"args": ["@aexol-studio/basecamp-to-llm", "mcp"],
84+
"command": "npx",
85+
"args": ["-y", "@aexol-studio/basecamp-to-llm", "mcp"],
8686
"env": {
8787
"BASECAMP_CLIENT_ID": "${env:BASECAMP_CLIENT_ID}",
8888
"BASECAMP_CLIENT_SECRET": "${env:BASECAMP_CLIENT_SECRET}",
@@ -111,8 +111,8 @@ npm install --save-dev @aexol-studio/basecamp-to-llm
111111
{
112112
"mcpServers": {
113113
"basecamp": {
114-
"command": "npx",
115-
"args": ["@aexol-studio/basecamp-to-llm", "mcp"],
114+
"command": "npx",
115+
"args": ["-y", "@aexol-studio/basecamp-to-llm", "mcp"],
116116
"env": {
117117
"BASECAMP_CLIENT_ID": "${env:BASECAMP_CLIENT_ID}",
118118
"BASECAMP_CLIENT_SECRET": "${env:BASECAMP_CLIENT_SECRET}",
@@ -214,7 +214,7 @@ You can override environment variables in the config:
214214
"mcpServers": {
215215
"basecamp": {
216216
"command": "npx",
217-
"args": ["@aexol-studio/basecamp-to-llm", "mcp"],
217+
"args": ["-y", "@aexol-studio/basecamp-to-llm", "mcp"],
218218
"env": {
219219
"BASECAMP_CLIENT_ID": "your_actual_client_id",
220220
"BASECAMP_CLIENT_SECRET": "your_actual_client_secret",
@@ -235,7 +235,7 @@ To use multiple Basecamp accounts, create separate MCP server configurations:
235235
"mcpServers": {
236236
"basecamp-personal": {
237237
"command": "npx",
238-
"args": ["@aexol-studio/basecamp-to-llm", "mcp"],
238+
"args": ["-y", "@aexol-studio/basecamp-to-llm", "mcp"],
239239
"env": {
240240
"BASECAMP_CLIENT_ID": "personal_client_id",
241241
"BASECAMP_CLIENT_SECRET": "personal_client_secret",

configs/codex.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"basecamp": {
44
"command": "npx",
55
"args": [
6+
"-y",
67
"@aexol-studio/basecamp-to-llm",
78
"mcp"
89
],
@@ -14,4 +15,4 @@
1415
}
1516
}
1617
}
17-
}
18+
}

configs/cursor.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"basecamp": {
44
"command": "npx",
55
"args": [
6+
"-y",
67
"@aexol-studio/basecamp-to-llm",
78
"mcp"
89
],
@@ -14,4 +15,4 @@
1415
}
1516
}
1617
}
17-
}
18+
}

examples/live-list-check.ts

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
#!/usr/bin/env tsx
2+
3+
/*
4+
* Live list endpoints smoke test.
5+
* Requires real Basecamp OAuth env vars:
6+
* - BASECAMP_CLIENT_ID
7+
* - BASECAMP_CLIENT_SECRET
8+
* - BASECAMP_REDIRECT_URI (e.g. http://localhost:8787/callback)
9+
* - BASECAMP_USER_AGENT (e.g. "Your App (you@example.com)")
10+
*
11+
* Optional IDs to test deeper list endpoints:
12+
* - BASECAMP_PROJECT_ID
13+
* - BASECAMP_TODOLIST_ID
14+
* - BASECAMP_MESSAGE_BOARD_ID
15+
* - BASECAMP_CARD_TABLE_ID
16+
*/
17+
18+
import { BasecampClient } from '../src/sdk/client.js';
19+
import { ProjectsResource } from '../src/sdk/resources/projects.js';
20+
import { PeopleResource } from '../src/sdk/resources/people.js';
21+
import { TodosResource } from '../src/sdk/resources/todos.js';
22+
import { MessagesResource } from '../src/sdk/resources/messages.js';
23+
import { CardTablesResource } from '../src/sdk/resources/cardTables.js';
24+
25+
type TestResult = { name: string; ok: boolean; detail?: string };
26+
27+
async function main() {
28+
const required = [
29+
'BASECAMP_CLIENT_ID',
30+
'BASECAMP_CLIENT_SECRET',
31+
'BASECAMP_REDIRECT_URI',
32+
'BASECAMP_USER_AGENT',
33+
];
34+
const missing = required.filter(k => !process.env[k]);
35+
if (missing.length) {
36+
console.error(`Missing required env: ${missing.join(', ')}`);
37+
process.exit(1);
38+
}
39+
40+
// Ensure protocol logs, if any, go to stderr (safety)
41+
process.env['BASECAMP_MCP_STDERR'] = '1';
42+
43+
const client = new BasecampClient();
44+
const results: TestResult[] = [];
45+
46+
async function check(name: string, fn: () => Promise<unknown>) {
47+
try {
48+
const r = await fn();
49+
let detail: string | undefined;
50+
if (Array.isArray(r)) detail = `count=${r.length}`;
51+
results.push({ name, ok: true, detail });
52+
} catch (e) {
53+
results.push({ name, ok: false, detail: e instanceof Error ? e.message : String(e) });
54+
}
55+
}
56+
57+
const projects = new ProjectsResource(client);
58+
const people = new PeopleResource(client);
59+
const todos = new TodosResource(client);
60+
const messages = new MessagesResource(client);
61+
const cards = new CardTablesResource(client);
62+
63+
// Always-run list endpoints
64+
await check('projects.list (/projects.json)', () => projects.list());
65+
await check('people.list (/people.json)', () => people.list());
66+
67+
// Optional: deeper lists requiring IDs
68+
const projectId = process.env['BASECAMP_PROJECT_ID']
69+
? Number(process.env['BASECAMP_PROJECT_ID'])
70+
: undefined;
71+
const todoListId = process.env['BASECAMP_TODOLIST_ID']
72+
? Number(process.env['BASECAMP_TODOLIST_ID'])
73+
: undefined;
74+
const boardId = process.env['BASECAMP_MESSAGE_BOARD_ID']
75+
? Number(process.env['BASECAMP_MESSAGE_BOARD_ID'])
76+
: undefined;
77+
const tableId = process.env['BASECAMP_CARD_TABLE_ID']
78+
? Number(process.env['BASECAMP_CARD_TABLE_ID'])
79+
: undefined;
80+
81+
if (projectId && todoListId) {
82+
await check(
83+
'todos.list (/buckets/:projectId/todolists/:listId/todos.json)',
84+
() => todos.list(projectId, todoListId)
85+
);
86+
}
87+
88+
if (projectId && boardId) {
89+
await check(
90+
'messages.list (/buckets/:projectId/message_boards/:boardId/messages.json)',
91+
() => messages.list(projectId, boardId)
92+
);
93+
}
94+
95+
if (projectId && tableId) {
96+
await check(
97+
'card_tables.get (/buckets/:projectId/card_tables/:tableId.json)',
98+
() => cards.get(projectId, tableId)
99+
);
100+
}
101+
102+
// Report
103+
const ok = results.filter(r => r.ok);
104+
const fail = results.filter(r => !r.ok);
105+
106+
console.error('\nLive list checks summary:');
107+
for (const r of results) {
108+
if (r.ok) console.error(`✓ ${r.name}${r.detail ? ` (${r.detail})` : ''}`);
109+
else console.error(`✗ ${r.name} -> ${r.detail}`);
110+
}
111+
console.error(`\nPassed: ${ok.length}, Failed: ${fail.length}`);
112+
113+
process.exit(fail.length ? 1 : 0);
114+
}
115+
116+
main().catch(err => {
117+
console.error('Fatal error:', err?.message || String(err));
118+
process.exit(1);
119+
});
120+

examples/mcp-client.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ async function main() {
1515
const env = {
1616
...process.env,
1717
BASECAMP_USER_AGENT: process.env.BASECAMP_USER_AGENT || 'Basecamp MCP Test (test@example.com)',
18+
BASECAMP_MCP_STDERR: '1',
1819
};
1920
const transport = new StdioClientTransport({
2021
command: 'node',

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@aexol-studio/basecamp-to-llm",
3-
"version": "1.1.0",
3+
"version": "1.1.1",
44
"description": "CLI tool to fetch Basecamp todos and convert them to LLM-friendly task formats",
55
"main": "dist/index.js",
66
"types": "dist/index.d.ts",
@@ -31,6 +31,7 @@
3131
"example:js": "node examples/basic-usage.js",
3232
"example:mcp": "tsx examples/mcp-usage.ts",
3333
"example:mcp-client": "node examples/mcp-client.mjs",
34+
"example:live-lists": "tsx examples/live-list-check.ts",
3435
"setup-mcp": "node scripts/setup-mcp.js"
3536
},
3637
"keywords": [
@@ -75,4 +76,4 @@
7576
"tsx": "^4.6.0",
7677
"typescript": "^5.3.0"
7778
}
78-
}
79+
}

src/basecamp-fetcher.ts

Lines changed: 22 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,16 @@ export class BasecampFetcher {
6868
this.validateEnvironment();
6969
}
7070

71+
private log(...args: unknown[]): void {
72+
if (process.env['BASECAMP_MCP_STDERR'] === '1') {
73+
// eslint-disable-next-line no-console
74+
console.error(...args);
75+
} else {
76+
// eslint-disable-next-line no-console
77+
console.log(...args);
78+
}
79+
}
80+
7181
private validateEnvironment(): void {
7282
if (!this.USER_AGENT) {
7383
throw new Error('Missing env BASECAMP_USER_AGENT');
@@ -98,14 +108,7 @@ export class BasecampFetcher {
98108
`${this.API_BASE}/${accountId}/projects.json`,
99109
accessToken
100110
);
101-
const direct = projects.find(p => p.name.toLowerCase() === name.toLowerCase());
102-
if (direct) return direct;
103-
// Try archived as a fallback
104-
const archived = await this.api<Project[]>(
105-
`${this.API_BASE}/${accountId}/projects/archived.json`,
106-
accessToken
107-
);
108-
return archived.find(p => p.name.toLowerCase() === name.toLowerCase());
111+
return projects.find(p => p.name.toLowerCase() === name.toLowerCase());
109112
}
110113

111114
private async listCards(
@@ -216,14 +219,14 @@ export class BasecampFetcher {
216219
const url = `${this.LAUNCHPAD}/authorization/new?type=web_server&client_id=${encodeURIComponent(
217220
this.CLIENT_ID
218221
)}&redirect_uri=${encodeURIComponent(this.REDIRECT_URI)}`;
219-
console.log('\nAuthorize this app by visiting:');
220-
console.log(url);
222+
this.log('\nAuthorize this app by visiting:');
223+
this.log(url);
221224
if (openBrowserFlag) await this.openUrl(url);
222225
try {
223226
const { code } = await this.startLocalCallbackServer();
224227
return code;
225228
} catch (e) {
226-
console.log('Local callback failed. Paste the "code" param from redirected URL here:');
229+
this.log('Local callback failed. Paste the "code" param from redirected URL here:');
227230
const code = await new Promise<string>(resolve => {
228231
process.stdout.write('Code: ');
229232
process.stdin.setEncoding('utf8');
@@ -320,22 +323,17 @@ export class BasecampFetcher {
320323
`${this.API_BASE}/${accountId}/projects.json`,
321324
token.access_token
322325
);
323-
const archivedProjects = await this.api<Project[]>(
324-
`${this.API_BASE}/${accountId}/projects/archived.json`,
325-
token.access_token
326-
);
327-
328-
return [...activeProjects, ...archivedProjects];
326+
return activeProjects;
329327
}
330328

331329
public async fetchTodos(projectName: string, options: FetchOptions = {}): Promise<void> {
332330
const token = await this.getAccessToken(options.openBrowser || false);
333331
const accountId = await this.resolveAccountId(token.access_token);
334332

335-
console.log(`Looking up project: ${projectName}`);
333+
this.log(`Looking up project: ${projectName}`);
336334
const project = await this.findProjectByName(projectName, accountId, token.access_token);
337335
if (!project) throw new Error(`Project not found: ${projectName}`);
338-
console.log(`Found project #${project.id} (${project.name})`);
336+
this.log(`Found project #${project.id} (${project.name})`);
339337

340338
const plan: { step: string; status: 'pending' }[] = [];
341339

@@ -366,7 +364,7 @@ export class BasecampFetcher {
366364
}
367365
}
368366

369-
console.log(`Collected ${plan.length} open item(s)`);
367+
this.log(`Collected ${plan.length} open item(s)`);
370368

371369
const codexDir = path.join(process.cwd(), '.codex');
372370
await fs.mkdir(codexDir, { recursive: true });
@@ -386,9 +384,9 @@ export class BasecampFetcher {
386384
].join('\n');
387385
await fs.writeFile(mdPath, md, 'utf8');
388386

389-
console.log(`Wrote ${plan.length} task(s) to:`);
390-
console.log(`- ${outJsonPath}`);
391-
console.log(`- ${mdPath}`);
392-
console.log('You can load these into Codex CLI as a plan.');
387+
this.log(`Wrote ${plan.length} task(s) to:`);
388+
this.log(`- ${outJsonPath}`);
389+
this.log(`- ${mdPath}`);
390+
this.log('You can load these into Codex CLI as a plan.');
393391
}
394392
}

0 commit comments

Comments
 (0)