ai-todo-cli 0.3.0 → 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/dist/index.js +95 -5
- package/package.json +6 -3
package/dist/index.js
CHANGED
|
@@ -79,6 +79,7 @@ async function login(tokenDirect) {
|
|
|
79
79
|
}
|
|
80
80
|
saveCredentials({
|
|
81
81
|
access_token: data.access_token,
|
|
82
|
+
session_token: data.session_token,
|
|
82
83
|
user_id: data.user_id,
|
|
83
84
|
email: data.email
|
|
84
85
|
});
|
|
@@ -143,14 +144,14 @@ async function fetchManifest() {
|
|
|
143
144
|
}
|
|
144
145
|
|
|
145
146
|
// src/client.ts
|
|
146
|
-
async function apiRequest(method, pathTemplate,
|
|
147
|
+
async function apiRequest(method, pathTemplate, pathParams2, queryParams, bodyParams, fixedBody) {
|
|
147
148
|
const creds = loadCredentials();
|
|
148
149
|
if (!creds) {
|
|
149
150
|
console.log(JSON.stringify({ error: "Not logged in. Run: ai-todo login" }));
|
|
150
151
|
process.exit(2);
|
|
151
152
|
}
|
|
152
153
|
let path = pathTemplate;
|
|
153
|
-
for (const [key, value] of Object.entries(
|
|
154
|
+
for (const [key, value] of Object.entries(pathParams2)) {
|
|
154
155
|
path = path.replace(`:${key}`, encodeURIComponent(value));
|
|
155
156
|
}
|
|
156
157
|
const url = new URL(path, API_BASE_URL);
|
|
@@ -160,7 +161,7 @@ async function apiRequest(method, pathTemplate, pathParams, queryParams, bodyPar
|
|
|
160
161
|
}
|
|
161
162
|
}
|
|
162
163
|
const headers = {
|
|
163
|
-
Authorization: `Bearer ${creds.access_token}`
|
|
164
|
+
Authorization: `Bearer ${creds.session_token || creds.access_token}`
|
|
164
165
|
};
|
|
165
166
|
let body;
|
|
166
167
|
const mergedBody = { ...bodyParams, ...fixedBody };
|
|
@@ -185,20 +186,62 @@ async function apiRequest(method, pathTemplate, pathParams, queryParams, bodyPar
|
|
|
185
186
|
}
|
|
186
187
|
|
|
187
188
|
// src/commands.ts
|
|
189
|
+
function toCamelCase(s) {
|
|
190
|
+
return s.replace(/[-_]([a-z])/g, (_, c) => c.toUpperCase());
|
|
191
|
+
}
|
|
188
192
|
function registerDynamicCommands(program2, operations) {
|
|
189
193
|
for (const op of operations) {
|
|
190
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();
|
|
191
200
|
for (const param of op.params) {
|
|
192
201
|
const flag = `--${param.name} <value>`;
|
|
193
202
|
const desc = buildParamDesc(param.description, param.enum);
|
|
194
|
-
if (param.required) {
|
|
203
|
+
if (param.required && !param.aliases?.length) {
|
|
195
204
|
cmd.requiredOption(flag, desc);
|
|
196
205
|
} else {
|
|
197
206
|
cmd.option(flag, desc);
|
|
207
|
+
if (param.required && param.aliases?.length) {
|
|
208
|
+
requiredParamsWithAliases.add(param.name);
|
|
209
|
+
}
|
|
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
|
+
}
|
|
198
220
|
}
|
|
199
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
|
+
});
|
|
243
|
+
}
|
|
200
244
|
cmd.action(async (opts) => {
|
|
201
|
-
const pathParams = {};
|
|
202
245
|
const queryParams = {};
|
|
203
246
|
const bodyParams = {};
|
|
204
247
|
for (const param of op.params) {
|
|
@@ -235,6 +278,31 @@ function buildParamDesc(desc, enumValues) {
|
|
|
235
278
|
}
|
|
236
279
|
return desc;
|
|
237
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
|
+
}
|
|
238
306
|
function coerceValue(value, type) {
|
|
239
307
|
if (type === "number") {
|
|
240
308
|
const n = Number(value);
|
|
@@ -269,6 +337,27 @@ program.command("whoami").description("Show current authenticated user").action(
|
|
|
269
337
|
email: creds.email
|
|
270
338
|
}));
|
|
271
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
|
+
}
|
|
272
361
|
async function main() {
|
|
273
362
|
const firstArg = process.argv[2];
|
|
274
363
|
const skipCommands = ["login", "logout", "whoami"];
|
|
@@ -278,6 +367,7 @@ async function main() {
|
|
|
278
367
|
try {
|
|
279
368
|
const manifest = await fetchManifest();
|
|
280
369
|
registerDynamicCommands(program, manifest.operations);
|
|
370
|
+
setupUnknownCommandHandler(manifest.operations);
|
|
281
371
|
} catch {
|
|
282
372
|
const isHelpOrEmpty = !firstArg || ["help", "--help", "-h"].includes(firstArg);
|
|
283
373
|
if (!isHelpOrEmpty) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
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
6
|
"repository": {
|
|
@@ -19,7 +19,9 @@
|
|
|
19
19
|
],
|
|
20
20
|
"scripts": {
|
|
21
21
|
"build": "tsup",
|
|
22
|
-
"dev": "tsup --watch"
|
|
22
|
+
"dev": "tsup --watch",
|
|
23
|
+
"test": "vitest run",
|
|
24
|
+
"test:watch": "vitest"
|
|
23
25
|
},
|
|
24
26
|
"dependencies": {
|
|
25
27
|
"commander": "^13.0.0",
|
|
@@ -28,7 +30,8 @@
|
|
|
28
30
|
"devDependencies": {
|
|
29
31
|
"@types/node": "^22.0.0",
|
|
30
32
|
"tsup": "^8.0.0",
|
|
31
|
-
"typescript": "^5.9.0"
|
|
33
|
+
"typescript": "^5.9.0",
|
|
34
|
+
"vitest": "^4.1.0"
|
|
32
35
|
},
|
|
33
36
|
"engines": {
|
|
34
37
|
"node": ">=18"
|