ai-todo-cli 0.1.2 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -0
- package/dist/index.js +104 -7
- package/package.json +15 -4
package/README.md
CHANGED
|
@@ -36,6 +36,17 @@ ai-todo spaces:list
|
|
|
36
36
|
|
|
37
37
|
Run `ai-todo --help` to see all available commands (fetched from server).
|
|
38
38
|
|
|
39
|
+
## Release
|
|
40
|
+
|
|
41
|
+
This package is published to npm via GitHub Actions when a tag like `v0.1.3` is pushed.
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npm version patch
|
|
45
|
+
git push origin main --follow-tags
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The workflow will verify that the Git tag matches `package.json` before publishing.
|
|
49
|
+
|
|
39
50
|
## For AI Agents
|
|
40
51
|
|
|
41
52
|
This CLI is designed for AI agent integration. Key features:
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { Command } from "commander";
|
|
5
|
+
import { createRequire } from "module";
|
|
5
6
|
|
|
6
7
|
// src/auth.ts
|
|
7
8
|
import { createServer } from "http";
|
|
@@ -78,6 +79,7 @@ async function login(tokenDirect) {
|
|
|
78
79
|
}
|
|
79
80
|
saveCredentials({
|
|
80
81
|
access_token: data.access_token,
|
|
82
|
+
session_token: data.session_token,
|
|
81
83
|
user_id: data.user_id,
|
|
82
84
|
email: data.email
|
|
83
85
|
});
|
|
@@ -142,14 +144,14 @@ async function fetchManifest() {
|
|
|
142
144
|
}
|
|
143
145
|
|
|
144
146
|
// src/client.ts
|
|
145
|
-
async function apiRequest(method, pathTemplate,
|
|
147
|
+
async function apiRequest(method, pathTemplate, pathParams2, queryParams, bodyParams, fixedBody) {
|
|
146
148
|
const creds = loadCredentials();
|
|
147
149
|
if (!creds) {
|
|
148
150
|
console.log(JSON.stringify({ error: "Not logged in. Run: ai-todo login" }));
|
|
149
151
|
process.exit(2);
|
|
150
152
|
}
|
|
151
153
|
let path = pathTemplate;
|
|
152
|
-
for (const [key, value] of Object.entries(
|
|
154
|
+
for (const [key, value] of Object.entries(pathParams2)) {
|
|
153
155
|
path = path.replace(`:${key}`, encodeURIComponent(value));
|
|
154
156
|
}
|
|
155
157
|
const url = new URL(path, API_BASE_URL);
|
|
@@ -159,7 +161,7 @@ async function apiRequest(method, pathTemplate, pathParams, queryParams, bodyPar
|
|
|
159
161
|
}
|
|
160
162
|
}
|
|
161
163
|
const headers = {
|
|
162
|
-
Authorization: `Bearer ${creds.access_token}`
|
|
164
|
+
Authorization: `Bearer ${creds.session_token || creds.access_token}`
|
|
163
165
|
};
|
|
164
166
|
let body;
|
|
165
167
|
const mergedBody = { ...bodyParams, ...fixedBody };
|
|
@@ -184,20 +186,62 @@ async function apiRequest(method, pathTemplate, pathParams, queryParams, bodyPar
|
|
|
184
186
|
}
|
|
185
187
|
|
|
186
188
|
// src/commands.ts
|
|
189
|
+
function toCamelCase(s) {
|
|
190
|
+
return s.replace(/[-_]([a-z])/g, (_, c) => c.toUpperCase());
|
|
191
|
+
}
|
|
187
192
|
function registerDynamicCommands(program2, operations) {
|
|
188
193
|
for (const op of operations) {
|
|
189
194
|
const cmd = program2.command(op.name).description(op.description);
|
|
195
|
+
if (op.aliases?.length) {
|
|
196
|
+
cmd.aliases(op.aliases);
|
|
197
|
+
}
|
|
198
|
+
const paramAliasMap = {};
|
|
199
|
+
const requiredParamsWithAliases = /* @__PURE__ */ new Set();
|
|
190
200
|
for (const param of op.params) {
|
|
191
201
|
const flag = `--${param.name} <value>`;
|
|
192
202
|
const desc = buildParamDesc(param.description, param.enum);
|
|
193
|
-
if (param.required) {
|
|
203
|
+
if (param.required && !param.aliases?.length) {
|
|
194
204
|
cmd.requiredOption(flag, desc);
|
|
195
205
|
} else {
|
|
196
206
|
cmd.option(flag, desc);
|
|
207
|
+
if (param.required && param.aliases?.length) {
|
|
208
|
+
requiredParamsWithAliases.add(param.name);
|
|
209
|
+
}
|
|
197
210
|
}
|
|
211
|
+
if (param.aliases?.length) {
|
|
212
|
+
for (const alias of param.aliases) {
|
|
213
|
+
cmd.option(`--${alias} <value>`);
|
|
214
|
+
const camelAlias = toCamelCase(alias);
|
|
215
|
+
paramAliasMap[camelAlias] = param.name;
|
|
216
|
+
if (camelAlias !== alias) {
|
|
217
|
+
paramAliasMap[alias] = param.name;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
if (Object.keys(paramAliasMap).length > 0 || requiredParamsWithAliases.size > 0) {
|
|
223
|
+
cmd.hook("preAction", (thisCommand) => {
|
|
224
|
+
const opts = thisCommand.opts();
|
|
225
|
+
for (const [alias, original] of Object.entries(paramAliasMap)) {
|
|
226
|
+
if (opts[alias] !== void 0 && opts[original] === void 0) {
|
|
227
|
+
thisCommand.setOptionValue(original, opts[alias]);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
const updatedOpts = thisCommand.opts();
|
|
231
|
+
for (const name of requiredParamsWithAliases) {
|
|
232
|
+
if (updatedOpts[name] === void 0) {
|
|
233
|
+
const param = op.params.find((p) => p.name === name);
|
|
234
|
+
const aliasList = param?.aliases?.map((a) => `--${a}`).join(", ") ?? "";
|
|
235
|
+
console.log(JSON.stringify({
|
|
236
|
+
error: `Missing required option: --${name}`,
|
|
237
|
+
aliases: aliasList ? `Also accepts: ${aliasList}` : void 0
|
|
238
|
+
}));
|
|
239
|
+
process.exit(1);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
});
|
|
198
243
|
}
|
|
199
244
|
cmd.action(async (opts) => {
|
|
200
|
-
const pathParams = {};
|
|
201
245
|
const queryParams = {};
|
|
202
246
|
const bodyParams = {};
|
|
203
247
|
for (const param of op.params) {
|
|
@@ -219,7 +263,11 @@ function registerDynamicCommands(program2, operations) {
|
|
|
219
263
|
bodyParams,
|
|
220
264
|
op.fixed_body
|
|
221
265
|
);
|
|
222
|
-
|
|
266
|
+
if (op.format === "text" && typeof data === "object" && data !== null && "output" in data) {
|
|
267
|
+
console.log(data.output);
|
|
268
|
+
} else {
|
|
269
|
+
console.log(JSON.stringify(data, null, 2));
|
|
270
|
+
}
|
|
223
271
|
});
|
|
224
272
|
}
|
|
225
273
|
}
|
|
@@ -230,6 +278,31 @@ function buildParamDesc(desc, enumValues) {
|
|
|
230
278
|
}
|
|
231
279
|
return desc;
|
|
232
280
|
}
|
|
281
|
+
function levenshtein(a, b) {
|
|
282
|
+
const m = a.length, n = b.length;
|
|
283
|
+
const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
|
|
284
|
+
for (let i = 0; i <= m; i++) dp[i][0] = i;
|
|
285
|
+
for (let j = 0; j <= n; j++) dp[0][j] = j;
|
|
286
|
+
for (let i = 1; i <= m; i++) {
|
|
287
|
+
for (let j = 1; j <= n; j++) {
|
|
288
|
+
dp[i][j] = a[i - 1] === b[j - 1] ? dp[i - 1][j - 1] : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return dp[m][n];
|
|
292
|
+
}
|
|
293
|
+
function findClosestCommand(input, candidates) {
|
|
294
|
+
let best = "";
|
|
295
|
+
let bestDist = Infinity;
|
|
296
|
+
for (const candidate of candidates) {
|
|
297
|
+
const dist = levenshtein(input.toLowerCase(), candidate.toLowerCase());
|
|
298
|
+
if (dist < bestDist) {
|
|
299
|
+
bestDist = dist;
|
|
300
|
+
best = candidate;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
const threshold = Math.min(Math.ceil(input.length / 2), 3);
|
|
304
|
+
return bestDist <= threshold ? best : null;
|
|
305
|
+
}
|
|
233
306
|
function coerceValue(value, type) {
|
|
234
307
|
if (type === "number") {
|
|
235
308
|
const n = Number(value);
|
|
@@ -242,8 +315,10 @@ function coerceValue(value, type) {
|
|
|
242
315
|
}
|
|
243
316
|
|
|
244
317
|
// src/index.ts
|
|
318
|
+
var require2 = createRequire(import.meta.url);
|
|
319
|
+
var { version } = require2("../package.json");
|
|
245
320
|
var program = new Command();
|
|
246
|
-
program.name("ai-todo").description("CLI for AI agents to interact with ai-todo").version(
|
|
321
|
+
program.name("ai-todo").description("CLI for AI agents to interact with ai-todo").version(version);
|
|
247
322
|
program.command("login").description("Authenticate with ai-todo via browser").option("--token <jwt>", "Directly provide a JWT token (for headless environments)").action(async (opts) => {
|
|
248
323
|
await login(opts.token);
|
|
249
324
|
});
|
|
@@ -262,6 +337,27 @@ program.command("whoami").description("Show current authenticated user").action(
|
|
|
262
337
|
email: creds.email
|
|
263
338
|
}));
|
|
264
339
|
});
|
|
340
|
+
function setupUnknownCommandHandler(operations) {
|
|
341
|
+
program.on("command:*", (operands) => {
|
|
342
|
+
const unknown = operands[0];
|
|
343
|
+
const allNames = [];
|
|
344
|
+
for (const op of operations) {
|
|
345
|
+
allNames.push(op.name);
|
|
346
|
+
if (op.aliases) allNames.push(...op.aliases);
|
|
347
|
+
}
|
|
348
|
+
allNames.push("login", "logout", "whoami");
|
|
349
|
+
const suggestion = findClosestCommand(unknown, allNames);
|
|
350
|
+
const result = {
|
|
351
|
+
error: `Unknown command: ${unknown}`
|
|
352
|
+
};
|
|
353
|
+
if (suggestion) {
|
|
354
|
+
result.suggestion = `Did you mean: ai-todo ${suggestion}`;
|
|
355
|
+
}
|
|
356
|
+
result.hint = "Run 'ai-todo --help' to see all available commands";
|
|
357
|
+
console.log(JSON.stringify(result));
|
|
358
|
+
process.exit(1);
|
|
359
|
+
});
|
|
360
|
+
}
|
|
265
361
|
async function main() {
|
|
266
362
|
const firstArg = process.argv[2];
|
|
267
363
|
const skipCommands = ["login", "logout", "whoami"];
|
|
@@ -271,6 +367,7 @@ async function main() {
|
|
|
271
367
|
try {
|
|
272
368
|
const manifest = await fetchManifest();
|
|
273
369
|
registerDynamicCommands(program, manifest.operations);
|
|
370
|
+
setupUnknownCommandHandler(manifest.operations);
|
|
274
371
|
} catch {
|
|
275
372
|
const isHelpOrEmpty = !firstArg || ["help", "--help", "-h"].includes(firstArg);
|
|
276
373
|
if (!isHelpOrEmpty) {
|
package/package.json
CHANGED
|
@@ -1,26 +1,37 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-todo-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"description": "CLI tool for AI agents to interact with ai-todo",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/strzhao/ai-todo-cli.git"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://github.com/strzhao/ai-todo-cli#readme",
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/strzhao/ai-todo-cli/issues"
|
|
13
|
+
},
|
|
6
14
|
"bin": {
|
|
7
|
-
"ai-todo": "
|
|
15
|
+
"ai-todo": "dist/index.js"
|
|
8
16
|
},
|
|
9
17
|
"files": [
|
|
10
18
|
"dist"
|
|
11
19
|
],
|
|
12
20
|
"scripts": {
|
|
13
21
|
"build": "tsup",
|
|
14
|
-
"dev": "tsup --watch"
|
|
22
|
+
"dev": "tsup --watch",
|
|
23
|
+
"test": "vitest run",
|
|
24
|
+
"test:watch": "vitest"
|
|
15
25
|
},
|
|
16
26
|
"dependencies": {
|
|
17
27
|
"commander": "^13.0.0",
|
|
18
28
|
"open": "^10.0.0"
|
|
19
29
|
},
|
|
20
30
|
"devDependencies": {
|
|
31
|
+
"@types/node": "^22.0.0",
|
|
21
32
|
"tsup": "^8.0.0",
|
|
22
33
|
"typescript": "^5.9.0",
|
|
23
|
-
"
|
|
34
|
+
"vitest": "^4.1.0"
|
|
24
35
|
},
|
|
25
36
|
"engines": {
|
|
26
37
|
"node": ">=18"
|