@todoforai/cli 0.1.20 → 0.1.22

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 +226 -52
  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") {
@@ -44093,26 +44096,34 @@ Usage:
44093
44096
  todoforai-cli -c ["prompt"] # Resume last todo (optional prompt sent on attach)
44094
44097
  todoforai-cli --resume <todo-id> ["prompt"] # Resume specific todo (optional prompt sent on attach)
44095
44098
  todoforai-cli --inspect <todo-id>[@<slice>] # Read chat log. <slice> = -3:, :1, 5:10, 7 (Python-style)
44096
- todoforai-cli --template <id> [--input k=v] # Start from a registry template
44099
+ todoforai-cli start <id> # Start a TODO from the registry (todoregistry.com)
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
44113
44125
  --inspect, -i <todo-id>[@<slice>] Print chat log (read-only)
44114
- --template, -t <id> Start from a registry template
44115
- --input <key=value> Template input (repeatable)
44126
+ --template, -t <id> Start from a registry template (alias: start <id>)
44116
44127
  --resume, -r [todo-id] Resume existing todo
44117
44128
  --continue, -c Continue most recent todo
44118
44129
  --non-interactive, -n Run to completion and exit without interactive prompt
@@ -44163,16 +44174,19 @@ function parseCliArgs() {
44163
44174
  agent: { type: "string", short: "a" },
44164
44175
  model: { type: "string" },
44165
44176
  "list-agents": { type: "boolean", default: false },
44177
+ "list-models": { type: "boolean", default: false },
44166
44178
  "api-url": { type: "string" },
44167
44179
  "api-key": { type: "string" },
44168
44180
  "user-id": { type: "string" },
44169
44181
  inspect: { type: "string", short: "i" },
44170
44182
  template: { type: "string", short: "t" },
44171
- input: { type: "string", multiple: true },
44172
44183
  note: { type: "string" },
44173
44184
  priority: { type: "string" },
44174
44185
  title: { type: "string" },
44175
44186
  "business-context": { type: "string" },
44187
+ seed: { type: "string" },
44188
+ emails: { type: "string" },
44189
+ ttl: { type: "string" },
44176
44190
  resume: { type: "string", short: "r" },
44177
44191
  continue: { type: "boolean", short: "c", default: false },
44178
44192
  "non-interactive": { type: "boolean", short: "n", default: false },
@@ -44201,9 +44215,6 @@ function parseCliArgs() {
44201
44215
  return { values, positionals };
44202
44216
  }
44203
44217
 
44204
- // src/input.ts
44205
- import { createInterface } from "readline";
44206
-
44207
44218
  // src/colors.ts
44208
44219
  var on = !process.env.NO_COLOR && !!process.stdout.isTTY;
44209
44220
  var c = (seq) => on ? seq : "";
@@ -44224,15 +44235,6 @@ var BG_RED_HL = c("\x1B[48;2;100;35;35m");
44224
44235
  var BG_GREEN_HL = c("\x1B[48;2;35;85;35m");
44225
44236
 
44226
44237
  // src/input.ts
44227
- function readLine(prompt) {
44228
- const rl = createInterface({ input: process.stdin, output: process.stderr });
44229
- return new Promise((res) => {
44230
- rl.question(prompt, (ans) => {
44231
- rl.close();
44232
- res(ans.trim());
44233
- });
44234
- });
44235
- }
44236
44238
  function readMultiline(prompt, history) {
44237
44239
  let cancelFn = () => {};
44238
44240
  const promise = new Promise((resolve, reject) => {
@@ -45304,7 +45306,7 @@ ${label}${ts}
45304
45306
  }
45305
45307
 
45306
45308
  // src/select.ts
45307
- import { createInterface as createInterface2 } from "readline";
45309
+ import { createInterface } from "readline";
45308
45310
  function getDisplayName(item) {
45309
45311
  if (item?.project?.name)
45310
45312
  return item.project.name;
@@ -45338,7 +45340,7 @@ function resolveAgentMatch(agents, query) {
45338
45340
  return {};
45339
45341
  }
45340
45342
  function terminalLine(prompt) {
45341
- const rl = createInterface2({ input: process.stdin, output: process.stderr });
45343
+ const rl = createInterface({ input: process.stdin, output: process.stderr });
45342
45344
  return new Promise((resolve3) => {
45343
45345
  rl.question(prompt, (answer) => {
45344
45346
  rl.close();
@@ -46065,6 +46067,20 @@ async function listAgentsCommand(api, opts) {
46065
46067
  }
46066
46068
  }
46067
46069
 
46070
+ // src/list-models.ts
46071
+ async function listModelsCommand(api, opts) {
46072
+ const res = await api.listModels();
46073
+ const q = opts.filter?.toLowerCase();
46074
+ 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));
46075
+ if (opts.json) {
46076
+ console.log(JSON.stringify(ids, null, 2));
46077
+ return;
46078
+ }
46079
+ for (const id of ids)
46080
+ process.stderr.write(`${BRAND}${id}${RESET}
46081
+ `);
46082
+ }
46083
+
46068
46084
  // src/agent-command.ts
46069
46085
  function printAgentHelp() {
46070
46086
  process.stderr.write(`
@@ -46374,6 +46390,92 @@ async function listTodosCommand(api, defaultProjectId, argv) {
46374
46390
  `);
46375
46391
  }
46376
46392
 
46393
+ // src/steering-command.ts
46394
+ var RED2 = "\x1B[31m";
46395
+ var GREEN2 = "\x1B[32m";
46396
+ var DIM2 = "\x1B[2m";
46397
+ var RESET2 = "\x1B[0m";
46398
+ async function rest(ctx, method, path4, body) {
46399
+ const res = await fetch(`${ctx.apiUrl}/api/v1${path4}`, {
46400
+ method,
46401
+ headers: { "content-type": "application/json", "x-api-key": ctx.apiKey },
46402
+ ...body ? { body: JSON.stringify(body) } : {},
46403
+ signal: AbortSignal.timeout(30000)
46404
+ });
46405
+ const text = await res.text();
46406
+ if (!res.ok)
46407
+ throw new Error(`${method} ${path4} failed: ${res.status} ${text}`);
46408
+ return text ? JSON.parse(text) : null;
46409
+ }
46410
+ function printSteering(s) {
46411
+ process.stderr.write(`${GREEN2}Direction:${RESET2} ${s.direction || DIM2 + "(none)" + RESET2}
46412
+ `);
46413
+ process.stderr.write(`${GREEN2}Boosted:${RESET2} ${s.boostedCategoryIds?.join(", ") || DIM2 + "(none)" + RESET2}
46414
+ `);
46415
+ process.stderr.write(`${GREEN2}Muted:${RESET2} ${s.mutedCategoryIds?.join(", ") || DIM2 + "(none)" + RESET2}
46416
+ `);
46417
+ }
46418
+ var USAGE = `${RED2}Usage:
46419
+ todoforai-cli steering get [--project <id>]
46420
+ todoforai-cli steering direction "<text>" [--project <id>] (empty text clears it)
46421
+ todoforai-cli steering boost <categoryId> [--project <id>]
46422
+ todoforai-cli steering mute <categoryId> [--project <id>]
46423
+ todoforai-cli steering clear [--project <id>] (clears all steering)${RESET2}`;
46424
+ async function steeringCommand(ctx, argv) {
46425
+ const sub = argv[0];
46426
+ const path4 = `/projects/${ctx.projectId}/recommendation-steering`;
46427
+ if (!sub || sub === "get") {
46428
+ const s = await rest(ctx, "GET", path4);
46429
+ if (ctx.json)
46430
+ console.log(JSON.stringify(s, null, 2));
46431
+ else
46432
+ printSteering(s);
46433
+ return;
46434
+ }
46435
+ if (sub === "direction") {
46436
+ const text = argv.slice(1).join(" ").trim();
46437
+ const s = await rest(ctx, "PATCH", path4, { projectId: ctx.projectId, direction: text || null });
46438
+ if (ctx.json)
46439
+ console.log(JSON.stringify(s, null, 2));
46440
+ else
46441
+ process.stderr.write(`${GREEN2}\u2705 ${text ? `Direction set: ${text}` : "Direction cleared"}${RESET2}
46442
+ `);
46443
+ return;
46444
+ }
46445
+ if (sub === "boost" || sub === "mute") {
46446
+ const categoryId = argv[1];
46447
+ if (!categoryId) {
46448
+ process.stderr.write(USAGE + `
46449
+ `);
46450
+ process.exit(2);
46451
+ }
46452
+ const current = await rest(ctx, "GET", path4);
46453
+ const key = sub === "boost" ? "boostedCategoryIds" : "mutedCategoryIds";
46454
+ const other = sub === "boost" ? "mutedCategoryIds" : "boostedCategoryIds";
46455
+ const next = Array.from(new Set([...current[key] ?? [], categoryId]));
46456
+ const nextOther = (current[other] ?? []).filter((c2) => c2 !== categoryId);
46457
+ const s = await rest(ctx, "PATCH", path4, { projectId: ctx.projectId, [key]: next, [other]: nextOther });
46458
+ if (ctx.json)
46459
+ console.log(JSON.stringify(s, null, 2));
46460
+ else
46461
+ process.stderr.write(`${GREEN2}\u2705 ${sub === "boost" ? "Boosted" : "Muted"} ${categoryId}${RESET2}
46462
+ `);
46463
+ return;
46464
+ }
46465
+ if (sub === "clear") {
46466
+ const s = await rest(ctx, "PATCH", path4, { projectId: ctx.projectId, direction: null, boostedCategoryIds: [], mutedCategoryIds: [] });
46467
+ if (ctx.json)
46468
+ console.log(JSON.stringify(s, null, 2));
46469
+ else
46470
+ process.stderr.write(`${GREEN2}\u2705 Steering cleared${RESET2}
46471
+ `);
46472
+ return;
46473
+ }
46474
+ process.stderr.write(USAGE + `
46475
+ `);
46476
+ process.exit(2);
46477
+ }
46478
+
46377
46479
  // src/index.ts
46378
46480
  try {
46379
46481
  const pkgPath = path4.resolve(fileURLToPath(import.meta.url), "../../package.json");
@@ -46464,6 +46566,15 @@ Cancelled by user (Ctrl+C)
46464
46566
  process.exit(130);
46465
46567
  });
46466
46568
  const { values: args, positionals } = parseCliArgs();
46569
+ if (positionals[0] === "start") {
46570
+ if (!positionals[1] && !args.template) {
46571
+ process.stderr.write(`${RED}Usage: todoforai-cli start <todo-id>${RESET}
46572
+ `);
46573
+ process.exit(2);
46574
+ }
46575
+ if (!args.template)
46576
+ args.template = positionals[1];
46577
+ }
46467
46578
  if (args.version) {
46468
46579
  console.log(VERSION);
46469
46580
  process.exit(0);
@@ -46565,7 +46676,8 @@ Cancelled by user (Ctrl+C)
46565
46676
  await deviceLogin();
46566
46677
  return;
46567
46678
  }
46568
- let apiKey = args["api-key"] || readCredential(apiUrl) || getEnv("API_TOKEN") || "";
46679
+ const notDst = (t) => t.startsWith("dst_") ? "" : t;
46680
+ let apiKey = args["api-key"] || notDst(readCredential(apiUrl)) || notDst(getEnv("API_TOKEN")) || "";
46569
46681
  if (!apiKey) {
46570
46682
  apiKey = await deviceLogin();
46571
46683
  }
@@ -46609,8 +46721,8 @@ Cancelled by user (Ctrl+C)
46609
46721
  return;
46610
46722
  }
46611
46723
  if (positionals[0] === "addmessage") {
46612
- const [, todoId, ...rest] = positionals;
46613
- const content2 = rest.join(" ") || await readStdin();
46724
+ const [, todoId, ...rest2] = positionals;
46725
+ const content2 = rest2.join(" ") || await readStdin();
46614
46726
  if (!todoId || !content2) {
46615
46727
  process.stderr.write(`${RED}Usage: todoforai-cli addmessage <todo-id> "content"${RESET}
46616
46728
  `);
@@ -46652,7 +46764,7 @@ Cancelled by user (Ctrl+C)
46652
46764
  }
46653
46765
  const rec = await api.recommend({
46654
46766
  projectId: projectId2,
46655
- templateId,
46767
+ specId: templateId,
46656
46768
  ...args.title ? { title: args.title } : {},
46657
46769
  ...args.note ? { note: args.note } : {},
46658
46770
  ...priority ? { priority } : {},
@@ -46665,10 +46777,92 @@ Cancelled by user (Ctrl+C)
46665
46777
  `);
46666
46778
  return;
46667
46779
  }
46780
+ if (positionals[0] === "claim" && positionals[1] === "mint") {
46781
+ let seedProjectId = args.seed || args.project || cfgScope.data.default_project_id;
46782
+ if (!seedProjectId) {
46783
+ const projects2 = await api.listProjects();
46784
+ seedProjectId = projects2.find((p) => p.project?.isDefault)?.project?.id || projects2[0]?.project?.id;
46785
+ }
46786
+ if (!seedProjectId) {
46787
+ process.stderr.write(`${RED}Usage: todoforai-cli claim mint --seed <projectId> [--emails a@x,b@y] [--ttl <sec>]${RESET}
46788
+ `);
46789
+ process.exit(2);
46790
+ }
46791
+ const emails = args.emails?.split(",").map((e) => e.trim()).filter(Boolean);
46792
+ let ttlSec;
46793
+ if (args.ttl !== undefined) {
46794
+ ttlSec = Number(args.ttl);
46795
+ if (!Number.isFinite(ttlSec)) {
46796
+ process.stderr.write(`${RED}--ttl must be a number (seconds)${RESET}
46797
+ `);
46798
+ process.exit(2);
46799
+ }
46800
+ }
46801
+ const res = await fetch(`${apiUrl}/api/v1/claims/mint`, {
46802
+ method: "POST",
46803
+ headers: { "content-type": "application/json", "x-api-key": apiKey },
46804
+ body: JSON.stringify({ seedProjectId, ...emails?.length ? { emails } : {}, ...ttlSec !== undefined ? { ttlSec } : {} }),
46805
+ signal: AbortSignal.timeout(30000)
46806
+ });
46807
+ const text = await res.text();
46808
+ if (!res.ok) {
46809
+ process.stderr.write(`${RED}Mint failed: ${res.status} ${text}${RESET}
46810
+ `);
46811
+ process.exit(1);
46812
+ }
46813
+ const out = JSON.parse(text);
46814
+ if (args.json)
46815
+ console.log(JSON.stringify(out, null, 2));
46816
+ else
46817
+ for (const l of out.links)
46818
+ process.stdout.write(`${l.email ? `${l.email} ` : ""}${CYAN}${l.url}${RESET}
46819
+ `);
46820
+ return;
46821
+ }
46822
+ if (positionals[0] === "steering" || positionals[0] === "next") {
46823
+ let projectId2 = args.project || cfgScope.data.default_project_id;
46824
+ if (!projectId2) {
46825
+ const projects2 = await api.listProjects();
46826
+ projectId2 = projects2.find((p) => p.project?.isDefault)?.project?.id || projects2[0]?.project?.id;
46827
+ }
46828
+ if (!projectId2) {
46829
+ process.stderr.write(`${RED}No project found \u2014 pass --project <id>${RESET}
46830
+ `);
46831
+ process.exit(2);
46832
+ }
46833
+ const sctx = { apiUrl, apiKey, projectId: projectId2, json: !!args.json };
46834
+ if (positionals[0] === "next") {
46835
+ const res = await fetch(`${apiUrl}/api/v1/projects/${projectId2}/recommendations/generate`, {
46836
+ method: "POST",
46837
+ headers: { "content-type": "application/json", "x-api-key": apiKey },
46838
+ body: JSON.stringify({ projectId: projectId2, ...args["business-context"] ? { businessContextId: args["business-context"] } : {} }),
46839
+ signal: AbortSignal.timeout(30000)
46840
+ });
46841
+ const text = await res.text();
46842
+ if (!res.ok) {
46843
+ process.stderr.write(`${RED}Generate failed: ${res.status} ${text}${RESET}
46844
+ `);
46845
+ process.exit(1);
46846
+ }
46847
+ const out = JSON.parse(text);
46848
+ if (args.json)
46849
+ console.log(JSON.stringify(out, null, 2));
46850
+ else
46851
+ process.stderr.write(`${GREEN}\u2705 Generating recommendations \u2014 analyzer todo ${out.todoId}${RESET}
46852
+ `);
46853
+ return;
46854
+ }
46855
+ await steeringCommand(sctx, positionals.slice(1));
46856
+ return;
46857
+ }
46668
46858
  if (args["list-agents"]) {
46669
46859
  await listAgentsCommand(api, { json: !!args.json, formatPath: formatPathWithTilde });
46670
46860
  return;
46671
46861
  }
46862
+ if (args["list-models"]) {
46863
+ await listModelsCommand(api, { json: !!args.json, filter: positionals[0] });
46864
+ return;
46865
+ }
46672
46866
  if (positionals[0] === "agent") {
46673
46867
  await agentCommand(api, positionals, args, formatPathWithTilde);
46674
46868
  return;
@@ -46722,36 +46916,12 @@ Cancelled by user (Ctrl+C)
46722
46916
  if (!args["no-bridge"] && !args["no-watch"])
46723
46917
  ensureBridgeRunning(apiUrl, apiKey);
46724
46918
  const templateId = args.template;
46725
- const inputValues = {};
46726
- for (const kv of args.input || []) {
46727
- const eq = kv.indexOf("=");
46728
- if (eq > 0)
46729
- inputValues[kv.slice(0, eq)] = kv.slice(eq + 1);
46730
- }
46731
- const template = await api.getRegistryTemplate(templateId);
46732
- process.stderr.write(`${DIM}Template:${RESET} ${BRAND}${template.todoname}${RESET}
46733
- `);
46734
- if (template.description)
46735
- process.stderr.write(`${DIM}${template.description}${RESET}
46919
+ const spec = await api.getRegistrySpec(templateId);
46920
+ process.stderr.write(`${DIM}Template:${RESET} ${BRAND}${spec.name}${RESET}
46736
46921
  `);
46737
- const templateInputs = template.inputs || [];
46738
- if (templateInputs.length && !args["non-interactive"]) {
46739
- for (const inp of templateInputs) {
46740
- const key = inp.label.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/_+$/, "");
46741
- if (inputValues[key])
46742
- continue;
46743
- const req = inp.required ? ` ${RED}*${RESET}` : "";
46744
- const hint = inp.placeholder ? ` ${DIM}(${inp.placeholder.split(`
46745
- `)[0]})${RESET}` : "";
46746
- const val = await readLine(`${inp.label}${req}${hint}: `);
46747
- if (val)
46748
- inputValues[key] = val;
46749
- }
46750
- }
46751
- if (Object.keys(inputValues).length) {
46752
- process.stderr.write(`${DIM}Inputs:${RESET} ${JSON.stringify(inputValues)}
46922
+ if (spec.description)
46923
+ process.stderr.write(`${DIM}${spec.description}${RESET}
46753
46924
  `);
46754
- }
46755
46925
  const projects2 = await api.listProjects();
46756
46926
  let projectId2 = args.project;
46757
46927
  if (!projectId2) {
@@ -46762,12 +46932,14 @@ Cancelled by user (Ctrl+C)
46762
46932
  `);
46763
46933
  process.exit(1);
46764
46934
  }
46765
- const todo2 = await api.startFromTemplate(projectId2, templateId, { inputValues });
46935
+ const todo2 = await api.startFromSpec(projectId2, templateId);
46766
46936
  const todoId = todo2.id;
46767
46937
  cfgScope.setLastTodoId(todoId);
46768
46938
  const frontendUrl2 = getFrontendUrl(apiUrl, projectId2, todoId);
46769
46939
  if (args.json) {
46770
46940
  console.log(JSON.stringify({ ...todo2, frontend_url: frontendUrl2 }, null, 2));
46941
+ } else if (args["no-watch"]) {
46942
+ console.log(`created ${todoId}`);
46771
46943
  } else {
46772
46944
  process.stderr.write(`${DIM}TODO:${RESET} ${CYAN}${frontendUrl2}${RESET}
46773
46945
  `);
@@ -46962,6 +47134,8 @@ Resumed: ${CYAN}${getFrontendUrl(apiUrl, projectId2, todoId)}${RESET}
46962
47134
  const frontendUrl = getFrontendUrl(apiUrl, projectId, actualTodoId);
46963
47135
  if (args.json) {
46964
47136
  console.log(JSON.stringify({ ...todo, frontend_url: frontendUrl }, null, 2));
47137
+ } else if (!ws) {
47138
+ console.log(`created ${actualTodoId}`);
46965
47139
  } else {
46966
47140
  process.stderr.write(`${DIM}TODO:${RESET} ${CYAN}${frontendUrl}${RESET}
46967
47141
  `);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@todoforai/cli",
3
- "version": "0.1.20",
3
+ "version": "0.1.22",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "todoforai-cli": "dist/todoai.js",