@todoforai/cli 0.1.20 → 0.1.21

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.
Files changed (2) hide show
  1. package/dist/todoai.js +207 -4
  2. package/package.json +1 -1
package/dist/todoai.js CHANGED
@@ -42825,6 +42825,9 @@ class ApiClient {
42825
42825
  deleteTodo(todoId) {
42826
42826
  return this.request("DELETE", `/api/v1/todos/${todoId}`);
42827
42827
  }
42828
+ listModels() {
42829
+ return this.request("GET", "/api/v1/models");
42830
+ }
42828
42831
  listAgentSettings(filters) {
42829
42832
  const params = new URLSearchParams;
42830
42833
  if (filters?.workspacePath)
@@ -42923,7 +42926,7 @@ function normalizeApiUrl(url) {
42923
42926
  return `https://${url}`;
42924
42927
  return url;
42925
42928
  }
42926
- var SUBCOMMANDS = new Set(["login", "logout"]);
42929
+ var SUBCOMMANDS = new Set(["login", "logout", "update"]);
42927
42930
  function credentialsPath() {
42928
42931
  const sys = os2.platform();
42929
42932
  if (sys === "win32") {
@@ -44095,18 +44098,27 @@ Usage:
44095
44098
  todoforai-cli --inspect <todo-id>[@<slice>] # Read chat log. <slice> = -3:, :1, 5:10, 7 (Python-style)
44096
44099
  todoforai-cli --template <id> [--input k=v] # Start from a registry template
44097
44100
  todoforai-cli --list-agents # List available agents and exit
44101
+ todoforai-cli --list-models [filter] # List models usable with --model and exit
44098
44102
  todoforai-cli agent update <agent> model=<model> # Update agent settings (see 'agent --help')
44099
44103
  todoforai-cli list [-n 30] [--cursor N] [--all] [--status S] # List todos (paginated); see 'list --help'
44100
44104
  todoforai-cli status <todo-id> <STATUS> # Update a todo's status (run 'status --help' for the full list)
44101
44105
  todoforai-cli delete <todo-id> # Permanently delete a todo
44102
44106
  todoforai-cli addmessage <todo-id> "text" # Add a message to an existing todo
44103
44107
  todoforai-cli recommend --template <id> # Add a template as a NEXT-column card (see 'todoregistry-cli create')
44108
+ todoforai-cli claim mint --seed <projectId> [--emails a@x,b@y] [--ttl <sec>] # Mint /claim/<token> ownership links for a project you own
44109
+ todoforai-cli steering get # Show recommendation steering (direction + boosted/muted categories)
44110
+ todoforai-cli steering direction "<text>" # Set the direction the generator should follow
44111
+ todoforai-cli steering boost|mute <cat> # Favour / avoid a category
44112
+ todoforai-cli next # Generate steered NEXT-column recommendations
44104
44113
 
44105
44114
  Options:
44106
44115
  --path <dir> Workspace path (default: cwd)
44107
44116
  --project <id> Project ID
44108
44117
  --agent, -a <name> Agent name (partial match)
44118
+ --model <model> Override the agent's model for this todo
44119
+ (e.g. anthropic:anthropic/claude-opus-5, openai:openai/gpt-5.6-sol)
44109
44120
  --list-agents List available agents (name, id, workspace paths) and exit
44121
+ --list-models List models usable with --model (optional substring filter) and exit
44110
44122
  --api-url <url> API URL
44111
44123
  --api-key <key> API key
44112
44124
  --user-id <id> Admin HTTP impersonation; requires --no-watch
@@ -44163,6 +44175,7 @@ function parseCliArgs() {
44163
44175
  agent: { type: "string", short: "a" },
44164
44176
  model: { type: "string" },
44165
44177
  "list-agents": { type: "boolean", default: false },
44178
+ "list-models": { type: "boolean", default: false },
44166
44179
  "api-url": { type: "string" },
44167
44180
  "api-key": { type: "string" },
44168
44181
  "user-id": { type: "string" },
@@ -44173,6 +44186,9 @@ function parseCliArgs() {
44173
44186
  priority: { type: "string" },
44174
44187
  title: { type: "string" },
44175
44188
  "business-context": { type: "string" },
44189
+ seed: { type: "string" },
44190
+ emails: { type: "string" },
44191
+ ttl: { type: "string" },
44176
44192
  resume: { type: "string", short: "r" },
44177
44193
  continue: { type: "boolean", short: "c", default: false },
44178
44194
  "non-interactive": { type: "boolean", short: "n", default: false },
@@ -46065,6 +46081,20 @@ async function listAgentsCommand(api, opts) {
46065
46081
  }
46066
46082
  }
46067
46083
 
46084
+ // src/list-models.ts
46085
+ async function listModelsCommand(api, opts) {
46086
+ const res = await api.listModels();
46087
+ const q = opts.filter?.toLowerCase();
46088
+ const ids = (res?.data || []).map((m) => String(m.id).includes("/") ? `${String(m.id).split("/")[0]}:${m.id}` : String(m.id)).filter((id) => !q || id.toLowerCase().includes(q));
46089
+ if (opts.json) {
46090
+ console.log(JSON.stringify(ids, null, 2));
46091
+ return;
46092
+ }
46093
+ for (const id of ids)
46094
+ process.stderr.write(`${BRAND}${id}${RESET}
46095
+ `);
46096
+ }
46097
+
46068
46098
  // src/agent-command.ts
46069
46099
  function printAgentHelp() {
46070
46100
  process.stderr.write(`
@@ -46374,6 +46404,92 @@ async function listTodosCommand(api, defaultProjectId, argv) {
46374
46404
  `);
46375
46405
  }
46376
46406
 
46407
+ // src/steering-command.ts
46408
+ var RED2 = "\x1B[31m";
46409
+ var GREEN2 = "\x1B[32m";
46410
+ var DIM2 = "\x1B[2m";
46411
+ var RESET2 = "\x1B[0m";
46412
+ async function rest(ctx, method, path4, body) {
46413
+ const res = await fetch(`${ctx.apiUrl}/api/v1${path4}`, {
46414
+ method,
46415
+ headers: { "content-type": "application/json", "x-api-key": ctx.apiKey },
46416
+ ...body ? { body: JSON.stringify(body) } : {},
46417
+ signal: AbortSignal.timeout(30000)
46418
+ });
46419
+ const text = await res.text();
46420
+ if (!res.ok)
46421
+ throw new Error(`${method} ${path4} failed: ${res.status} ${text}`);
46422
+ return text ? JSON.parse(text) : null;
46423
+ }
46424
+ function printSteering(s) {
46425
+ process.stderr.write(`${GREEN2}Direction:${RESET2} ${s.direction || DIM2 + "(none)" + RESET2}
46426
+ `);
46427
+ process.stderr.write(`${GREEN2}Boosted:${RESET2} ${s.boostedCategoryIds?.join(", ") || DIM2 + "(none)" + RESET2}
46428
+ `);
46429
+ process.stderr.write(`${GREEN2}Muted:${RESET2} ${s.mutedCategoryIds?.join(", ") || DIM2 + "(none)" + RESET2}
46430
+ `);
46431
+ }
46432
+ var USAGE = `${RED2}Usage:
46433
+ todoforai-cli steering get [--project <id>]
46434
+ todoforai-cli steering direction "<text>" [--project <id>] (empty text clears it)
46435
+ todoforai-cli steering boost <categoryId> [--project <id>]
46436
+ todoforai-cli steering mute <categoryId> [--project <id>]
46437
+ todoforai-cli steering clear [--project <id>] (clears all steering)${RESET2}`;
46438
+ async function steeringCommand(ctx, argv) {
46439
+ const sub = argv[0];
46440
+ const path4 = `/projects/${ctx.projectId}/recommendation-steering`;
46441
+ if (!sub || sub === "get") {
46442
+ const s = await rest(ctx, "GET", path4);
46443
+ if (ctx.json)
46444
+ console.log(JSON.stringify(s, null, 2));
46445
+ else
46446
+ printSteering(s);
46447
+ return;
46448
+ }
46449
+ if (sub === "direction") {
46450
+ const text = argv.slice(1).join(" ").trim();
46451
+ const s = await rest(ctx, "PATCH", path4, { projectId: ctx.projectId, direction: text || null });
46452
+ if (ctx.json)
46453
+ console.log(JSON.stringify(s, null, 2));
46454
+ else
46455
+ process.stderr.write(`${GREEN2}\u2705 ${text ? `Direction set: ${text}` : "Direction cleared"}${RESET2}
46456
+ `);
46457
+ return;
46458
+ }
46459
+ if (sub === "boost" || sub === "mute") {
46460
+ const categoryId = argv[1];
46461
+ if (!categoryId) {
46462
+ process.stderr.write(USAGE + `
46463
+ `);
46464
+ process.exit(2);
46465
+ }
46466
+ const current = await rest(ctx, "GET", path4);
46467
+ const key = sub === "boost" ? "boostedCategoryIds" : "mutedCategoryIds";
46468
+ const other = sub === "boost" ? "mutedCategoryIds" : "boostedCategoryIds";
46469
+ const next = Array.from(new Set([...current[key] ?? [], categoryId]));
46470
+ const nextOther = (current[other] ?? []).filter((c2) => c2 !== categoryId);
46471
+ const s = await rest(ctx, "PATCH", path4, { projectId: ctx.projectId, [key]: next, [other]: nextOther });
46472
+ if (ctx.json)
46473
+ console.log(JSON.stringify(s, null, 2));
46474
+ else
46475
+ process.stderr.write(`${GREEN2}\u2705 ${sub === "boost" ? "Boosted" : "Muted"} ${categoryId}${RESET2}
46476
+ `);
46477
+ return;
46478
+ }
46479
+ if (sub === "clear") {
46480
+ const s = await rest(ctx, "PATCH", path4, { projectId: ctx.projectId, direction: null, boostedCategoryIds: [], mutedCategoryIds: [] });
46481
+ if (ctx.json)
46482
+ console.log(JSON.stringify(s, null, 2));
46483
+ else
46484
+ process.stderr.write(`${GREEN2}\u2705 Steering cleared${RESET2}
46485
+ `);
46486
+ return;
46487
+ }
46488
+ process.stderr.write(USAGE + `
46489
+ `);
46490
+ process.exit(2);
46491
+ }
46492
+
46377
46493
  // src/index.ts
46378
46494
  try {
46379
46495
  const pkgPath = path4.resolve(fileURLToPath(import.meta.url), "../../package.json");
@@ -46565,7 +46681,8 @@ Cancelled by user (Ctrl+C)
46565
46681
  await deviceLogin();
46566
46682
  return;
46567
46683
  }
46568
- let apiKey = args["api-key"] || readCredential(apiUrl) || getEnv("API_TOKEN") || "";
46684
+ const notDst = (t) => t.startsWith("dst_") ? "" : t;
46685
+ let apiKey = args["api-key"] || notDst(readCredential(apiUrl)) || notDst(getEnv("API_TOKEN")) || "";
46569
46686
  if (!apiKey) {
46570
46687
  apiKey = await deviceLogin();
46571
46688
  }
@@ -46609,8 +46726,8 @@ Cancelled by user (Ctrl+C)
46609
46726
  return;
46610
46727
  }
46611
46728
  if (positionals[0] === "addmessage") {
46612
- const [, todoId, ...rest] = positionals;
46613
- const content2 = rest.join(" ") || await readStdin();
46729
+ const [, todoId, ...rest2] = positionals;
46730
+ const content2 = rest2.join(" ") || await readStdin();
46614
46731
  if (!todoId || !content2) {
46615
46732
  process.stderr.write(`${RED}Usage: todoforai-cli addmessage <todo-id> "content"${RESET}
46616
46733
  `);
@@ -46665,10 +46782,92 @@ Cancelled by user (Ctrl+C)
46665
46782
  `);
46666
46783
  return;
46667
46784
  }
46785
+ if (positionals[0] === "claim" && positionals[1] === "mint") {
46786
+ let seedProjectId = args.seed || args.project || cfgScope.data.default_project_id;
46787
+ if (!seedProjectId) {
46788
+ const projects2 = await api.listProjects();
46789
+ seedProjectId = projects2.find((p) => p.project?.isDefault)?.project?.id || projects2[0]?.project?.id;
46790
+ }
46791
+ if (!seedProjectId) {
46792
+ process.stderr.write(`${RED}Usage: todoforai-cli claim mint --seed <projectId> [--emails a@x,b@y] [--ttl <sec>]${RESET}
46793
+ `);
46794
+ process.exit(2);
46795
+ }
46796
+ const emails = args.emails?.split(",").map((e) => e.trim()).filter(Boolean);
46797
+ let ttlSec;
46798
+ if (args.ttl !== undefined) {
46799
+ ttlSec = Number(args.ttl);
46800
+ if (!Number.isFinite(ttlSec)) {
46801
+ process.stderr.write(`${RED}--ttl must be a number (seconds)${RESET}
46802
+ `);
46803
+ process.exit(2);
46804
+ }
46805
+ }
46806
+ const res = await fetch(`${apiUrl}/api/v1/claims/mint`, {
46807
+ method: "POST",
46808
+ headers: { "content-type": "application/json", "x-api-key": apiKey },
46809
+ body: JSON.stringify({ seedProjectId, ...emails?.length ? { emails } : {}, ...ttlSec !== undefined ? { ttlSec } : {} }),
46810
+ signal: AbortSignal.timeout(30000)
46811
+ });
46812
+ const text = await res.text();
46813
+ if (!res.ok) {
46814
+ process.stderr.write(`${RED}Mint failed: ${res.status} ${text}${RESET}
46815
+ `);
46816
+ process.exit(1);
46817
+ }
46818
+ const out = JSON.parse(text);
46819
+ if (args.json)
46820
+ console.log(JSON.stringify(out, null, 2));
46821
+ else
46822
+ for (const l of out.links)
46823
+ process.stdout.write(`${l.email ? `${l.email} ` : ""}${CYAN}${l.url}${RESET}
46824
+ `);
46825
+ return;
46826
+ }
46827
+ if (positionals[0] === "steering" || positionals[0] === "next") {
46828
+ let projectId2 = args.project || cfgScope.data.default_project_id;
46829
+ if (!projectId2) {
46830
+ const projects2 = await api.listProjects();
46831
+ projectId2 = projects2.find((p) => p.project?.isDefault)?.project?.id || projects2[0]?.project?.id;
46832
+ }
46833
+ if (!projectId2) {
46834
+ process.stderr.write(`${RED}No project found \u2014 pass --project <id>${RESET}
46835
+ `);
46836
+ process.exit(2);
46837
+ }
46838
+ const sctx = { apiUrl, apiKey, projectId: projectId2, json: !!args.json };
46839
+ if (positionals[0] === "next") {
46840
+ const res = await fetch(`${apiUrl}/api/v1/projects/${projectId2}/recommendations/generate`, {
46841
+ method: "POST",
46842
+ headers: { "content-type": "application/json", "x-api-key": apiKey },
46843
+ body: JSON.stringify({ projectId: projectId2, ...args["business-context"] ? { businessContextId: args["business-context"] } : {} }),
46844
+ signal: AbortSignal.timeout(30000)
46845
+ });
46846
+ const text = await res.text();
46847
+ if (!res.ok) {
46848
+ process.stderr.write(`${RED}Generate failed: ${res.status} ${text}${RESET}
46849
+ `);
46850
+ process.exit(1);
46851
+ }
46852
+ const out = JSON.parse(text);
46853
+ if (args.json)
46854
+ console.log(JSON.stringify(out, null, 2));
46855
+ else
46856
+ process.stderr.write(`${GREEN}\u2705 Generating recommendations \u2014 analyzer todo ${out.todoId}${RESET}
46857
+ `);
46858
+ return;
46859
+ }
46860
+ await steeringCommand(sctx, positionals.slice(1));
46861
+ return;
46862
+ }
46668
46863
  if (args["list-agents"]) {
46669
46864
  await listAgentsCommand(api, { json: !!args.json, formatPath: formatPathWithTilde });
46670
46865
  return;
46671
46866
  }
46867
+ if (args["list-models"]) {
46868
+ await listModelsCommand(api, { json: !!args.json, filter: positionals[0] });
46869
+ return;
46870
+ }
46672
46871
  if (positionals[0] === "agent") {
46673
46872
  await agentCommand(api, positionals, args, formatPathWithTilde);
46674
46873
  return;
@@ -46768,6 +46967,8 @@ Cancelled by user (Ctrl+C)
46768
46967
  const frontendUrl2 = getFrontendUrl(apiUrl, projectId2, todoId);
46769
46968
  if (args.json) {
46770
46969
  console.log(JSON.stringify({ ...todo2, frontend_url: frontendUrl2 }, null, 2));
46970
+ } else if (args["no-watch"]) {
46971
+ console.log(`created ${todoId}`);
46771
46972
  } else {
46772
46973
  process.stderr.write(`${DIM}TODO:${RESET} ${CYAN}${frontendUrl2}${RESET}
46773
46974
  `);
@@ -46962,6 +47163,8 @@ Resumed: ${CYAN}${getFrontendUrl(apiUrl, projectId2, todoId)}${RESET}
46962
47163
  const frontendUrl = getFrontendUrl(apiUrl, projectId, actualTodoId);
46963
47164
  if (args.json) {
46964
47165
  console.log(JSON.stringify({ ...todo, frontend_url: frontendUrl }, null, 2));
47166
+ } else if (!ws) {
47167
+ console.log(`created ${actualTodoId}`);
46965
47168
  } else {
46966
47169
  process.stderr.write(`${DIM}TODO:${RESET} ${CYAN}${frontendUrl}${RESET}
46967
47170
  `);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@todoforai/cli",
3
- "version": "0.1.20",
3
+ "version": "0.1.21",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "todoforai-cli": "dist/todoai.js",