@todoforai/cli 0.1.71 → 0.1.73

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/todoforai-cli.js +132 -43
  2. package/package.json +1 -1
@@ -43281,6 +43281,10 @@ class ApiClient {
43281
43281
  const { projectId, ...body } = input;
43282
43282
  return this.request("POST", `/api/v1/projects/${projectId}/recommendations`, body);
43283
43283
  }
43284
+ async feedIgnore(input) {
43285
+ const { projectId, ...body } = input;
43286
+ return this.request("POST", `/api/v1/projects/${projectId}/feed/ignored`, body);
43287
+ }
43284
43288
  async getRegistrySpec(specId) {
43285
43289
  const base = this.apiUrl.replace(/\/api\/v1\/?$/, "").replace(/\/$/, "");
43286
43290
  const res = await fetch(`${base}/cookie/v1/registry/templates/${specId}`);
@@ -44079,7 +44083,9 @@ var SERVER_TO_FRONTENDS = {
44079
44083
  updated: (userId) => `${"business_context:updated" /* BUSINESS_CONTEXT_UPDATED */}:${userId}`
44080
44084
  },
44081
44085
  resource: {
44082
- contentUpdated: (userId) => `${"resource:content_updated" /* RESOURCE_CONTENT_UPDATED */}:${userId}`
44086
+ contentUpdated: (userId) => `${"resource:content_updated" /* RESOURCE_CONTENT_UPDATED */}:${userId}`,
44087
+ contentUpdatedInTodo: (todoId) => `${"resource:content_updated" /* RESOURCE_CONTENT_UPDATED */}:todo:${todoId}`,
44088
+ contentUpdatedInProject: (projectId) => `${"resource:content_updated" /* RESOURCE_CONTENT_UPDATED */}:project:${projectId}`
44083
44089
  },
44084
44090
  hexGrid: {
44085
44091
  updated: (projectId) => `${"hexgrid:updated" /* HEXGRID_UPDATED */}:${projectId}`
@@ -44132,6 +44138,7 @@ var SERVER_TO_FRONTENDS = {
44132
44138
  start_universal: (todoId) => `${"block:start_universal" /* BLOCK_START_UNIVERSAL */}:${todoId}`,
44133
44139
  message: (todoId) => `${"block:message" /* BLOCK_MESSAGE */}:${todoId}`,
44134
44140
  end: (todoId) => `${"block:end" /* BLOCK_END */}:${todoId}`,
44141
+ toolDelta: (todoId) => `${"block:tool_delta" /* BLOCK_TOOL_DELTA */}:${todoId}`,
44135
44142
  new_message: (todoId) => `${"todo:new_message" /* TODO_NEW_MESSAGE */}:${todoId}`,
44136
44143
  sh_msg_result: (todoId) => `${"block:sh_msg_result" /* BLOCK_SH_MSG_RESULT */}:${todoId}`,
44137
44144
  sh_msg_start_result: (todoId) => `${"block:sh_msg_start" /* BLOCK_SH_MSG_START */}:${todoId}`,
@@ -44218,6 +44225,7 @@ var USER_HEADINGS = new Set([
44218
44225
  ]);
44219
44226
  // ../packages/shared-fbe/src/bridgeProtocol.ts
44220
44227
  var ADB_DATA_CHUNK = 40 * 1024;
44228
+ var SHELL_DATA_CHUNK = 40 * 1024;
44221
44229
  // ../packages/shared-fbe/src/bashPatterns.ts
44222
44230
  function splitShellCommands(input) {
44223
44231
  const parts = [];
@@ -44633,32 +44641,15 @@ To materialize growth recommendations, propose todos on the project board. A pro
44633
44641
 
44634
44642
  Exactly ONE \`api_recommend_template\` call per proposal: it carries both what the todo is and where it lands on the board.
44635
44643
 
44636
- 1. WHAT IT IS. Pass \`name\` + \`body\`, written for THIS business, and optionally \`description\`, \`short\` and \`categories\`.
44644
+ 1. WHAT IT IS. Pass \`name\`, \`description\` and \`body\`, written for THIS business.
44637
44645
  - Reuse an existing registry template instead ONLY when it already fits this business closely with no meaningful edits: pass its id as \`templateId\` and omit \`name\`/\`body\`. If it needs tailoring, write yours inline. Do not recommend a generic playbook that ignores this company's specifics.
44638
44646
 
44639
- Language: match the business. Write user-facing text such as names, bodies and notes in the language the company itself communicates in. When the company is international or unclear, use English. Only the frame stays in English. The heading lines \`Your task:\`, \`Steps:\` and \`Data needed:\` are parsed, so keep them exactly as-is. Everything else follows the business's language.
44640
-
44641
- The body is read by a PERSON deciding whether to run it, so it must be understandable at a glance. The executing agent is capable and has the business context at run time, so it will elaborate and figure out the details itself. Write a minimal brief, not a playbook. Include ONLY what the agent could not figure out on its own: the business-specific direction such as company, competitor, segment, channel and the metric to move, plus any hard constraint. Cut everything else, including background, caveats, generic how-to and restated context.
44642
-
44643
- Hard limits: body ≤80 words total. One sentence of persona. One-sentence task. 2-4 steps, each a short fragment of at most 8 words naming WHAT, not how. Data needed is a comma-separated list. If a sentence wouldn't change what the agent does, delete it.
44644
-
44645
- \`name\`: 3-6 words, action-first like "Win Reddit comparison threads", with no company name prefix. Give \`description\` and \`short\` one tight sentence each.
44647
+ A person reads it at a glance and decides whether to run it, so use as few words as possible. The executing agent is capable, reads the business context at run time and asks for anything it is missing, so give no steps, no list of inputs and no background.
44648
+ - \`name\`: 3-6 words, action-first like "Win Reddit comparison threads", with no company name prefix.
44649
+ - \`description\`: one plain sentence of at most 15 words saying what the user gets, such as "Three vs-Gmail pages, drafted and ready to publish." Never repeat the name.
44650
+ - \`body\`: one sentence naming the expert to act as, such as "You are an SEO writer for email products." Nothing else: no headings, no task, no steps.
44646
44651
 
44647
- Example body at the right density (adapt specifics, keep the brevity):
44648
- "You are a B2B outbound specialist for dev-tool companies.
44649
- Your task:
44650
- Land Acme in 10 agency partner conversations this month.
44651
- Steps:
44652
- - Shortlist 30 EU automation agencies
44653
- - Draft one-line personalized openers
44654
- - Send, track replies in a sheet
44655
- Data needed:
44656
- Acme pricing page, current partner list, founder's LinkedIn"
44657
-
44658
- Body structure (REQUIRED). The body is split into the agent's system prompt and the first user turn by section headings, so follow this exactly:
44659
- - Start with the persona, expertise and any Rules as plain prose at the top with no heading. This becomes the agent's system prompt.
44660
- - Then write the request using these exact standalone heading lines, letters only, each on its own line and ending with a colon: \`Your task:\`, \`Steps:\`, \`Data needed:\`. Everything under these headings becomes the first user turn.
44661
- - Headings must match exactly or the split breaks. Write \`Your task:\`, not \`Your task (SEO):\`. Put the business-specific detail in the body text under each heading, never in the heading itself.
44652
+ Language: write the name, description, body and note in the language the company itself communicates in. When the company is international or unclear, use English.
44662
44653
 
44663
44654
  2. WHERE IT LANDS, in that same call. It becomes a PROPOSED todo on the project and never a running todo:
44664
44655
  Give it a one-line \`note\` of roughly 15 words at most giving the WHY for THIS business, which becomes the proposal's tooltip, a \`priority\` of high, medium or low. Leave \`group\` empty: proposals land on the board without a group, and you never create, rename or archive groups. Project and business context default to the current session, so pass them only when targeting a different one. The proposal appears as a ✧ row on the board for the user to review before any credits are spent. Re-proposing the same \`templateId\` refreshes that open proposal instead of duplicating it. An inline proposal is always a new one, so check the board you just read before repeating a move that is already there. Dismissed proposals since your last run are fed back to you. Don't re-propose those, steer elsewhere.
@@ -45607,6 +45598,16 @@ var tool_catalog_default = {
45607
45598
  installCmd: "npm install -g @todoforai/tfa-summary",
45608
45599
  internal: true
45609
45600
  },
45601
+ "tfa-cat": {
45602
+ category: "todoforai",
45603
+ pkg: "tfa-cat",
45604
+ installer: "system",
45605
+ label: "Cat",
45606
+ summary: "Show images and PDFs from the shell to the agent.",
45607
+ description: "`cat` that lets you SEE media: `tfa-cat shot.png` (or `… | tfa-cat`) attaches images/PDFs to this bash result; text prints like cat; its stdout redirected/piped → raw bytes. Also `todoforai:<path>` files (`tfa-cat todoforai:todos/<id>/data.csv > data.csv`; binaries like docx/xlsx: redirect, then edit with your tools). Big text costs context: read what you need (`| head`, `| sed -n 40,80p`, `| grep -n`).",
45608
+ versionCmd: "tfa-cat --version 2>/dev/null | sed -n 1p",
45609
+ internal: true
45610
+ },
45610
45611
  "tfa-handoff": {
45611
45612
  preinstallCloud: true,
45612
45613
  category: "todoforai",
@@ -50352,6 +50353,10 @@ var TOPICS = {
50352
50353
  channel: (p) => SERVER_TO_FRONTENDS.todo.msgStopSequence(p.todoId),
50353
50354
  audience: "frontend"
50354
50355
  },
50356
+ ["block:tool_delta" /* BLOCK_TOOL_DELTA */]: {
50357
+ channel: (p) => SERVER_TO_FRONTENDS.todo.toolDelta(p.todoId),
50358
+ audience: "frontend"
50359
+ },
50355
50360
  ["block:diff_result" /* BLOCK_DIFF_RESULT */]: {
50356
50361
  channel: (p) => SERVER_TO_FRONTENDS.todo.diff_result(p.todoId),
50357
50362
  audience: "frontend"
@@ -50427,7 +50432,7 @@ var package_default = {
50427
50432
  type: "git",
50428
50433
  url: "git+https://github.com/todoforai/cli.git"
50429
50434
  },
50430
- version: "0.1.71",
50435
+ version: "0.1.73",
50431
50436
  type: "module",
50432
50437
  bin: {
50433
50438
  "todoforai-cli": "bin/todoforai-cli.js",
@@ -50496,7 +50501,8 @@ Examples:
50496
50501
  tfa-cli show list|ref|rm … # List / re-surface / take down shown files
50497
50502
  tfa-cli open <url> [todo-id] # Live url preview in the chat
50498
50503
  tfa-cli connect <key>… [--reason "why"] # Sign-in card for connectors on this machine (several keys = one choice)
50499
- tfa-cli recommend --template <id> # Add a template as a recommendation card
50504
+ tfa-cli recommend --template <id> | --title "…" [--info] [--source "label|ref|url"] # ✧ proposal card, or an ⓘ info row
50505
+ tfa-cli ignore --title "…" [--source "label|ref|url|from"] [--note "why"] # Log an inbound item as noise (folded under the Feed card)
50500
50506
  tfa-cli inbox [needs-you|messages|activity] [--unread] # Read the notification feed (the bell)
50501
50507
  tfa-cli inbox seen [tab] # Mark notifications read
50502
50508
  tfa-cli notify [--to <email>] "Title" "message" [--href /t/<id>] [--url <link>] # Note to your own user, or --to a project member (Messages tab + phone push)
@@ -50624,6 +50630,8 @@ function parseCliArgs() {
50624
50630
  inspect: { type: "string", short: "i" },
50625
50631
  template: { type: "string", short: "t" },
50626
50632
  note: { type: "string" },
50633
+ info: { type: "boolean", default: false },
50634
+ source: { type: "string" },
50627
50635
  priority: { type: "string" },
50628
50636
  title: { type: "string" },
50629
50637
  alias: { type: "string" },
@@ -51400,6 +51408,16 @@ class ScopedConfig {
51400
51408
  s.recent_projects = [{ id, name: name || id }, ...s.recent_projects.filter((p) => p.id !== id)].slice(0, 10);
51401
51409
  this.store.save();
51402
51410
  }
51411
+ cachedUserId(tokenFp) {
51412
+ const s = this.data;
51413
+ return s.user_id && s.user_id_token_fp === tokenFp ? s.user_id : null;
51414
+ }
51415
+ setUserId(id, tokenFp) {
51416
+ const s = this.data;
51417
+ s.user_id = id;
51418
+ s.user_id_token_fp = tokenFp;
51419
+ this.store.save();
51420
+ }
51403
51421
  setDefaultAgent(name, settings) {
51404
51422
  const s = this.data;
51405
51423
  s.default_agent_name = name;
@@ -53447,10 +53465,9 @@ Examples:
53447
53465
  tfa-cli agent update <agent> model=anthropic:anthropic/claude-opus-5 temperature=0.5
53448
53466
  tfa-cli agent update <agent> sysmsg="You are a terse video editor."
53449
53467
  tfa-cli agent create name=Reviewer model=claude sysmsg="Review diffs only."
53468
+ tfa-cli agent update <agent> judgeSysmsg="Ask before sending email." (approval checker; ""=off, null=inherit global)
53450
53469
 
53451
- tfa-cli agent update <agent> judgeSysmsg="Ask before destructive actions. Everything else can run." (auto-approve policy)
53452
-
53453
- From an agent shell (dst_ token) permissions, judgeSysmsg, edgesMcpConfigs, devicesConfig and delete are refused by the backend.
53470
+ From an agent shell (dst_ token) permissions, edgesMcpConfigs, devicesConfig and delete are refused by the backend.
53454
53471
  `);
53455
53472
  }
53456
53473
  var FIELD_ALIASES = {
@@ -53852,6 +53869,7 @@ Fields (PUT /todos/{id}):
53852
53869
  schedule unix ms timestamp, 0 = immediate (alias: scheduledTimestamp)
53853
53870
  agent agentSettingsId (alias: agentSettingsId)
53854
53871
  public true|false — refused from an agent shell (isPublic)
53872
+ kind task|info — promote an ⓘ info row to a task (or back)
53855
53873
 
53856
53874
  tfa-cli todo message <todo-id|-> <message-id> <field=value>…
53857
53875
  Edit a SCHEDULED (not yet run) message: content, schedule (unix ms;
@@ -54469,19 +54487,22 @@ var VALID_STATUS = new Set(Object.values(TodoStatus));
54469
54487
  var isOpen = (s) => !CLOSED.has(s);
54470
54488
  function printListTodosHelp() {
54471
54489
  process.stderr.write(`
54472
- todoforai-cli list — list todos in a project (recent first)
54490
+ todoforai-cli list — list YOUR todos in a project (recent first)
54491
+
54492
+ Default: only todos you opened or wrote in. --project-wide shows everyone's.
54473
54493
 
54474
54494
  Usage:
54475
54495
  todoforai-cli list [flags]
54476
54496
 
54477
54497
  Flags:
54498
+ --project-wide Every member's todos, not just yours
54478
54499
  -n, --limit <n> Max rows to show (default: 30)
54479
54500
  --cursor <n> Fetch todos older than this cursor (sort field, epoch ms)
54480
54501
  --since <t> Only todos with sort field >= t (inclusive)
54481
54502
  --until <t> Only todos with sort field < t (exclusive)
54482
54503
  t = epoch ms | YYYY-MM-DD (local midnight) | ISO datetime | 12h / 3d ago
54483
54504
  --sort <key> activity (lastActivityAt, default) | created (createdAt)
54484
- --contributor <u> Only todos a member opened/wrote in (id or email; comma = any)
54505
+ --contributor <u> Only todos a member opened/wrote in (id or email; comma = any; replaces the "mine" default)
54485
54506
  --page-size <n> Backend page size per request (default: min(limit, 400), max: 400)
54486
54507
  --search <text> Search content/status on the backend
54487
54508
  -s, --status <S[,S2]> Filter by status (comma-separated, union).
@@ -54491,7 +54512,8 @@ Flags:
54491
54512
  -h, --help Show this help
54492
54513
 
54493
54514
  Examples:
54494
- todoforai-cli list # 30 most recent open todos
54515
+ todoforai-cli list # 30 most recent open todos of yours
54516
+ todoforai-cli list --project-wide # … of everyone in the project
54495
54517
  todoforai-cli list -n 50 # last 50 open
54496
54518
  todoforai-cli list --cursor 1719234567890 # next page
54497
54519
  todoforai-cli list --all # include DONE
@@ -54583,7 +54605,7 @@ function matchesRequestedStatus(t, requested, wanted, wantOpen) {
54583
54605
  const s = String(t.status).toUpperCase();
54584
54606
  return wantOpen && isOpen(s) || (wanted?.has(s) ?? false);
54585
54607
  }
54586
- async function listTodosCommand(api, defaultProjectId, argv) {
54608
+ async function listTodosCommand(api, defaultProjectId, argv, resolveUserId) {
54587
54609
  const { values } = parseArgs2({
54588
54610
  args: argv,
54589
54611
  options: {
@@ -54595,6 +54617,7 @@ async function listTodosCommand(api, defaultProjectId, argv) {
54595
54617
  until: { type: "string" },
54596
54618
  sort: { type: "string" },
54597
54619
  contributor: { type: "string" },
54620
+ "project-wide": { type: "boolean", default: false },
54598
54621
  status: { type: "string", short: "s" },
54599
54622
  all: { type: "boolean", short: "A", default: false },
54600
54623
  project: { type: "string" },
@@ -54627,7 +54650,15 @@ async function listTodosCommand(api, defaultProjectId, argv) {
54627
54650
  if (until !== undefined)
54628
54651
  cursor = cursor === undefined ? until : Math.min(cursor, until);
54629
54652
  const search = values.search ? String(values.search) : undefined;
54630
- const contributorIds = values.contributor ? await resolveContributors(api, projectId, String(values.contributor)) : undefined;
54653
+ let contributorIds = values.contributor ? await resolveContributors(api, projectId, String(values.contributor)) : undefined;
54654
+ let mineOnly = false;
54655
+ if (!contributorIds && !values["project-wide"] && resolveUserId) {
54656
+ const me = await resolveUserId();
54657
+ if (me) {
54658
+ contributorIds = [me];
54659
+ mineOnly = true;
54660
+ }
54661
+ }
54631
54662
  const requested = values.status ? String(values.status).split(",").map((s) => s.trim().toUpperCase()).filter(Boolean) : null;
54632
54663
  const unknown = requested?.filter((s) => s !== "OPEN" && !VALID_STATUS.has(s)) ?? [];
54633
54664
  if (unknown.length)
@@ -54683,8 +54714,9 @@ async function listTodosCommand(api, defaultProjectId, argv) {
54683
54714
  `);
54684
54715
  return;
54685
54716
  }
54717
+ const scopeHint = mineOnly ? " of yours (--project-wide for everyone's)" : "";
54686
54718
  if (!visible.length) {
54687
- process.stderr.write(`${DIM}(no todos)${RESET}
54719
+ process.stderr.write(`${DIM}(no todos${scopeHint})${RESET}
54688
54720
  `);
54689
54721
  if (nextCursor !== undefined)
54690
54722
  process.stderr.write(`${DIM}Next cursor: ${nextCursor} (re-run with the same filters plus --cursor ${nextCursor})${RESET}
@@ -54701,7 +54733,7 @@ async function listTodosCommand(api, defaultProjectId, argv) {
54701
54733
  process.stdout.write(`${sc}${status.padEnd(statusW)}${RESET} ${DIM}${ts}${RESET} ${t.id} ${title}
54702
54734
  `);
54703
54735
  }
54704
- process.stderr.write(`${DIM}${visible.length} todo(s)${RESET}
54736
+ process.stderr.write(`${DIM}${visible.length} todo(s)${scopeHint}${RESET}
54705
54737
  `);
54706
54738
  if (nextCursor !== undefined)
54707
54739
  process.stderr.write(`${DIM}Next cursor: ${nextCursor} (re-run with the same filters plus --cursor ${nextCursor})${RESET}
@@ -55656,7 +55688,7 @@ password=${apiKey}
55656
55688
  }
55657
55689
 
55658
55690
  // src/index.ts
55659
- import { randomUUID as randomUUID3 } from "crypto";
55691
+ import { createHash as createHash3, randomUUID as randomUUID3 } from "crypto";
55660
55692
 
55661
55693
  // src/ensure-bridge.ts
55662
55694
  import { spawn as spawn4, spawnSync as spawnSync4 } from "child_process";
@@ -80374,15 +80406,49 @@ async function main() {
80374
80406
  console.log(res.ref);
80375
80407
  return;
80376
80408
  }
80409
+ if (positionals[0] === "ignore") {
80410
+ const title = args.title?.trim();
80411
+ if (!title) {
80412
+ process.stderr.write(`${RED3}Usage: tfa-cli ignore --title "..." [--source "label|ref|url|from"] [--note "why"] [--project <id>]${RESET2}
80413
+ `);
80414
+ process.exit(2);
80415
+ }
80416
+ const source = args.source ? (() => {
80417
+ const [label, ref, url2, from] = String(args.source).split("|");
80418
+ return label && ref ? { label, ref, ...url2 ? { url: url2 } : {}, ...from ? { from } : {} } : undefined;
80419
+ })() : undefined;
80420
+ const projectId2 = args.project || getEnv("PROJECT_ID") || cfgScope.data.default_project_id;
80421
+ if (!projectId2) {
80422
+ process.stderr.write(`${RED3}No project found \u2014 pass --project <id>${RESET2}
80423
+ `);
80424
+ process.exit(2);
80425
+ }
80426
+ await api2.feedIgnore({ projectId: projectId2, title, ...source ? { source } : {}, ...args.note ? { why: args.note } : {} });
80427
+ process.stderr.write(`${DIM2}ignored: ${title}${RESET2}
80428
+ `);
80429
+ return;
80430
+ }
80377
80431
  if (positionals[0] === "recommend") {
80378
80432
  const templateId = args.template || positionals[1];
80379
- if (!templateId) {
80380
- process.stderr.write(`${RED3}Usage: tfa-cli recommend --template <id> [--note "why"] [--priority high|medium|low] [--title "..."] [--group <slug> [--group-name "..."] [--group-description "..."]] [--project <id>]${RESET2}
80433
+ const inlineTitle = !templateId && args.title?.trim();
80434
+ if (!templateId && !inlineTitle) {
80435
+ process.stderr.write(`${RED3}Usage: tfa-cli recommend --template <id> | --title "..." [--info] [--source "label|ref|url|from"] [--note "why"] [--priority high|medium|low] [--group <slug> [--group-name "..."] [--group-description "..."]] [--project <id>]${RESET2}
80381
80436
  `);
80382
- process.stderr.write(`${DIM2}Create a template first with: todoregistry-cli create --name ... --description ... --body @prompt.md${RESET2}
80437
+ process.stderr.write(`${DIM2}--info: an information row (no \u2727, nothing to accept). --source: provenance; the same label|ref never creates a second row.${RESET2}
80383
80438
  `);
80384
80439
  process.exit(2);
80385
80440
  }
80441
+ const source = (() => {
80442
+ if (!args.source)
80443
+ return;
80444
+ const [label, ref, url2, from] = String(args.source).split("|");
80445
+ if (!label || !ref) {
80446
+ process.stderr.write(`${RED3}--source needs at least "label|ref"${RESET2}
80447
+ `);
80448
+ process.exit(2);
80449
+ }
80450
+ return { label, ref, ...url2 ? { url: url2 } : {}, ...from ? { from } : {} };
80451
+ })();
80386
80452
  let projectId2 = args.project || getEnv("PROJECT_ID") || cfgScope.data.default_project_id;
80387
80453
  if (!projectId2) {
80388
80454
  const projects2 = await api2.listProjects();
@@ -80401,7 +80467,17 @@ async function main() {
80401
80467
  }
80402
80468
  const rec = await api2.recommend({
80403
80469
  projectId: projectId2,
80404
- specId: templateId,
80470
+ ...templateId ? { specId: templateId } : { template: {
80471
+ name: inlineTitle,
80472
+ description: args.note || (source ? `From ${source.label}` : inlineTitle),
80473
+ body: `Handle this item: ${inlineTitle}${args.note ? `
80474
+
80475
+ ${args.note}` : ""}${source?.url ? `
80476
+
80477
+ Original: ${source.url}` : ""}`
80478
+ } },
80479
+ ...args.info ? { todoKind: "info" } : {},
80480
+ ...source ? { source } : {},
80405
80481
  ...args.title ? { title: args.title } : {},
80406
80482
  ...args.note ? { note: args.note } : {},
80407
80483
  ...priority ? { priority } : {},
@@ -80412,7 +80488,7 @@ async function main() {
80412
80488
  if (args.json)
80413
80489
  console.log(JSON.stringify(rec, null, 2));
80414
80490
  else
80415
- process.stderr.write(`${GREEN2}\u2705 Recommended template ${templateId} on project ${projectId2}${RESET2}
80491
+ process.stderr.write(`${GREEN2}\u2705 ${args.info ? "\u24D8 info" : "\u2727 proposal"} ${rec?.id ?? ""} on project ${projectId2}${RESET2}
80416
80492
  `);
80417
80493
  return;
80418
80494
  }
@@ -80515,7 +80591,20 @@ async function main() {
80515
80591
  defaultProjectId = projects2.find((p) => p.project?.isDefault)?.project?.id || projects2[0]?.project?.id;
80516
80592
  }
80517
80593
  const sub = process.argv.slice(process.argv.indexOf(positionals[0]) + 1);
80518
- await listTodosCommand(api2, defaultProjectId ?? undefined, sub);
80594
+ const tokenFp = createHash3("sha256").update(apiKey).digest("hex").slice(0, 16);
80595
+ const resolveUserId = async () => {
80596
+ const envId = getEnv("USER_ID");
80597
+ if (envId)
80598
+ return envId;
80599
+ const cached2 = cfgScope.cachedUserId(tokenFp);
80600
+ if (cached2)
80601
+ return cached2;
80602
+ const v = await api2.validateApiKey();
80603
+ if (v.userId)
80604
+ cfgScope.setUserId(v.userId, tokenFp);
80605
+ return v.userId;
80606
+ };
80607
+ await listTodosCommand(api2, defaultProjectId ?? undefined, sub, resolveUserId);
80519
80608
  return;
80520
80609
  }
80521
80610
  if (args.inspect !== undefined) {
package/package.json CHANGED
@@ -7,7 +7,7 @@
7
7
  "type": "git",
8
8
  "url": "git+https://github.com/todoforai/cli.git"
9
9
  },
10
- "version": "0.1.71",
10
+ "version": "0.1.73",
11
11
  "type": "module",
12
12
  "bin": {
13
13
  "todoforai-cli": "bin/todoforai-cli.js",