@todoforai/cli 0.1.71 → 0.1.72

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.
@@ -44079,7 +44079,9 @@ var SERVER_TO_FRONTENDS = {
44079
44079
  updated: (userId) => `${"business_context:updated" /* BUSINESS_CONTEXT_UPDATED */}:${userId}`
44080
44080
  },
44081
44081
  resource: {
44082
- contentUpdated: (userId) => `${"resource:content_updated" /* RESOURCE_CONTENT_UPDATED */}:${userId}`
44082
+ contentUpdated: (userId) => `${"resource:content_updated" /* RESOURCE_CONTENT_UPDATED */}:${userId}`,
44083
+ contentUpdatedInTodo: (todoId) => `${"resource:content_updated" /* RESOURCE_CONTENT_UPDATED */}:todo:${todoId}`,
44084
+ contentUpdatedInProject: (projectId) => `${"resource:content_updated" /* RESOURCE_CONTENT_UPDATED */}:project:${projectId}`
44083
44085
  },
44084
44086
  hexGrid: {
44085
44087
  updated: (projectId) => `${"hexgrid:updated" /* HEXGRID_UPDATED */}:${projectId}`
@@ -44132,6 +44134,7 @@ var SERVER_TO_FRONTENDS = {
44132
44134
  start_universal: (todoId) => `${"block:start_universal" /* BLOCK_START_UNIVERSAL */}:${todoId}`,
44133
44135
  message: (todoId) => `${"block:message" /* BLOCK_MESSAGE */}:${todoId}`,
44134
44136
  end: (todoId) => `${"block:end" /* BLOCK_END */}:${todoId}`,
44137
+ toolDelta: (todoId) => `${"block:tool_delta" /* BLOCK_TOOL_DELTA */}:${todoId}`,
44135
44138
  new_message: (todoId) => `${"todo:new_message" /* TODO_NEW_MESSAGE */}:${todoId}`,
44136
44139
  sh_msg_result: (todoId) => `${"block:sh_msg_result" /* BLOCK_SH_MSG_RESULT */}:${todoId}`,
44137
44140
  sh_msg_start_result: (todoId) => `${"block:sh_msg_start" /* BLOCK_SH_MSG_START */}:${todoId}`,
@@ -44218,6 +44221,7 @@ var USER_HEADINGS = new Set([
44218
44221
  ]);
44219
44222
  // ../packages/shared-fbe/src/bridgeProtocol.ts
44220
44223
  var ADB_DATA_CHUNK = 40 * 1024;
44224
+ var SHELL_DATA_CHUNK = 40 * 1024;
44221
44225
  // ../packages/shared-fbe/src/bashPatterns.ts
44222
44226
  function splitShellCommands(input) {
44223
44227
  const parts = [];
@@ -45607,6 +45611,16 @@ var tool_catalog_default = {
45607
45611
  installCmd: "npm install -g @todoforai/tfa-summary",
45608
45612
  internal: true
45609
45613
  },
45614
+ "tfa-cat": {
45615
+ category: "todoforai",
45616
+ pkg: "tfa-cat",
45617
+ installer: "system",
45618
+ label: "Cat",
45619
+ summary: "Show images and PDFs from the shell to the agent.",
45620
+ 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`).",
45621
+ versionCmd: "tfa-cat --version 2>/dev/null | sed -n 1p",
45622
+ internal: true
45623
+ },
45610
45624
  "tfa-handoff": {
45611
45625
  preinstallCloud: true,
45612
45626
  category: "todoforai",
@@ -50352,6 +50366,10 @@ var TOPICS = {
50352
50366
  channel: (p) => SERVER_TO_FRONTENDS.todo.msgStopSequence(p.todoId),
50353
50367
  audience: "frontend"
50354
50368
  },
50369
+ ["block:tool_delta" /* BLOCK_TOOL_DELTA */]: {
50370
+ channel: (p) => SERVER_TO_FRONTENDS.todo.toolDelta(p.todoId),
50371
+ audience: "frontend"
50372
+ },
50355
50373
  ["block:diff_result" /* BLOCK_DIFF_RESULT */]: {
50356
50374
  channel: (p) => SERVER_TO_FRONTENDS.todo.diff_result(p.todoId),
50357
50375
  audience: "frontend"
@@ -50427,7 +50445,7 @@ var package_default = {
50427
50445
  type: "git",
50428
50446
  url: "git+https://github.com/todoforai/cli.git"
50429
50447
  },
50430
- version: "0.1.71",
50448
+ version: "0.1.72",
50431
50449
  type: "module",
50432
50450
  bin: {
50433
50451
  "todoforai-cli": "bin/todoforai-cli.js",
@@ -50496,7 +50514,7 @@ Examples:
50496
50514
  tfa-cli show list|ref|rm … # List / re-surface / take down shown files
50497
50515
  tfa-cli open <url> [todo-id] # Live url preview in the chat
50498
50516
  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
50517
+ tfa-cli recommend --template <id> | --title "…" [--info] [--source "label|ref|url"] # ✧ proposal card, or an ⓘ info row
50500
50518
  tfa-cli inbox [needs-you|messages|activity] [--unread] # Read the notification feed (the bell)
50501
50519
  tfa-cli inbox seen [tab] # Mark notifications read
50502
50520
  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 +50642,8 @@ function parseCliArgs() {
50624
50642
  inspect: { type: "string", short: "i" },
50625
50643
  template: { type: "string", short: "t" },
50626
50644
  note: { type: "string" },
50645
+ info: { type: "boolean", default: false },
50646
+ source: { type: "string" },
50627
50647
  priority: { type: "string" },
50628
50648
  title: { type: "string" },
50629
50649
  alias: { type: "string" },
@@ -51400,6 +51420,16 @@ class ScopedConfig {
51400
51420
  s.recent_projects = [{ id, name: name || id }, ...s.recent_projects.filter((p) => p.id !== id)].slice(0, 10);
51401
51421
  this.store.save();
51402
51422
  }
51423
+ cachedUserId(tokenFp) {
51424
+ const s = this.data;
51425
+ return s.user_id && s.user_id_token_fp === tokenFp ? s.user_id : null;
51426
+ }
51427
+ setUserId(id, tokenFp) {
51428
+ const s = this.data;
51429
+ s.user_id = id;
51430
+ s.user_id_token_fp = tokenFp;
51431
+ this.store.save();
51432
+ }
51403
51433
  setDefaultAgent(name, settings) {
51404
51434
  const s = this.data;
51405
51435
  s.default_agent_name = name;
@@ -53447,10 +53477,9 @@ Examples:
53447
53477
  tfa-cli agent update <agent> model=anthropic:anthropic/claude-opus-5 temperature=0.5
53448
53478
  tfa-cli agent update <agent> sysmsg="You are a terse video editor."
53449
53479
  tfa-cli agent create name=Reviewer model=claude sysmsg="Review diffs only."
53480
+ tfa-cli agent update <agent> judgeSysmsg="Ask before sending email." (approval checker; ""=off, null=inherit global)
53450
53481
 
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.
53482
+ From an agent shell (dst_ token) permissions, edgesMcpConfigs, devicesConfig and delete are refused by the backend.
53454
53483
  `);
53455
53484
  }
53456
53485
  var FIELD_ALIASES = {
@@ -53852,6 +53881,7 @@ Fields (PUT /todos/{id}):
53852
53881
  schedule unix ms timestamp, 0 = immediate (alias: scheduledTimestamp)
53853
53882
  agent agentSettingsId (alias: agentSettingsId)
53854
53883
  public true|false — refused from an agent shell (isPublic)
53884
+ kind task|info — promote an ⓘ info row to a task (or back)
53855
53885
 
53856
53886
  tfa-cli todo message <todo-id|-> <message-id> <field=value>…
53857
53887
  Edit a SCHEDULED (not yet run) message: content, schedule (unix ms;
@@ -54469,19 +54499,22 @@ var VALID_STATUS = new Set(Object.values(TodoStatus));
54469
54499
  var isOpen = (s) => !CLOSED.has(s);
54470
54500
  function printListTodosHelp() {
54471
54501
  process.stderr.write(`
54472
- todoforai-cli list — list todos in a project (recent first)
54502
+ todoforai-cli list — list YOUR todos in a project (recent first)
54503
+
54504
+ Default: only todos you opened or wrote in. --project-wide shows everyone's.
54473
54505
 
54474
54506
  Usage:
54475
54507
  todoforai-cli list [flags]
54476
54508
 
54477
54509
  Flags:
54510
+ --project-wide Every member's todos, not just yours
54478
54511
  -n, --limit <n> Max rows to show (default: 30)
54479
54512
  --cursor <n> Fetch todos older than this cursor (sort field, epoch ms)
54480
54513
  --since <t> Only todos with sort field >= t (inclusive)
54481
54514
  --until <t> Only todos with sort field < t (exclusive)
54482
54515
  t = epoch ms | YYYY-MM-DD (local midnight) | ISO datetime | 12h / 3d ago
54483
54516
  --sort <key> activity (lastActivityAt, default) | created (createdAt)
54484
- --contributor <u> Only todos a member opened/wrote in (id or email; comma = any)
54517
+ --contributor <u> Only todos a member opened/wrote in (id or email; comma = any; replaces the "mine" default)
54485
54518
  --page-size <n> Backend page size per request (default: min(limit, 400), max: 400)
54486
54519
  --search <text> Search content/status on the backend
54487
54520
  -s, --status <S[,S2]> Filter by status (comma-separated, union).
@@ -54491,7 +54524,8 @@ Flags:
54491
54524
  -h, --help Show this help
54492
54525
 
54493
54526
  Examples:
54494
- todoforai-cli list # 30 most recent open todos
54527
+ todoforai-cli list # 30 most recent open todos of yours
54528
+ todoforai-cli list --project-wide # … of everyone in the project
54495
54529
  todoforai-cli list -n 50 # last 50 open
54496
54530
  todoforai-cli list --cursor 1719234567890 # next page
54497
54531
  todoforai-cli list --all # include DONE
@@ -54583,7 +54617,7 @@ function matchesRequestedStatus(t, requested, wanted, wantOpen) {
54583
54617
  const s = String(t.status).toUpperCase();
54584
54618
  return wantOpen && isOpen(s) || (wanted?.has(s) ?? false);
54585
54619
  }
54586
- async function listTodosCommand(api, defaultProjectId, argv) {
54620
+ async function listTodosCommand(api, defaultProjectId, argv, resolveUserId) {
54587
54621
  const { values } = parseArgs2({
54588
54622
  args: argv,
54589
54623
  options: {
@@ -54595,6 +54629,7 @@ async function listTodosCommand(api, defaultProjectId, argv) {
54595
54629
  until: { type: "string" },
54596
54630
  sort: { type: "string" },
54597
54631
  contributor: { type: "string" },
54632
+ "project-wide": { type: "boolean", default: false },
54598
54633
  status: { type: "string", short: "s" },
54599
54634
  all: { type: "boolean", short: "A", default: false },
54600
54635
  project: { type: "string" },
@@ -54627,7 +54662,15 @@ async function listTodosCommand(api, defaultProjectId, argv) {
54627
54662
  if (until !== undefined)
54628
54663
  cursor = cursor === undefined ? until : Math.min(cursor, until);
54629
54664
  const search = values.search ? String(values.search) : undefined;
54630
- const contributorIds = values.contributor ? await resolveContributors(api, projectId, String(values.contributor)) : undefined;
54665
+ let contributorIds = values.contributor ? await resolveContributors(api, projectId, String(values.contributor)) : undefined;
54666
+ let mineOnly = false;
54667
+ if (!contributorIds && !values["project-wide"] && resolveUserId) {
54668
+ const me = await resolveUserId();
54669
+ if (me) {
54670
+ contributorIds = [me];
54671
+ mineOnly = true;
54672
+ }
54673
+ }
54631
54674
  const requested = values.status ? String(values.status).split(",").map((s) => s.trim().toUpperCase()).filter(Boolean) : null;
54632
54675
  const unknown = requested?.filter((s) => s !== "OPEN" && !VALID_STATUS.has(s)) ?? [];
54633
54676
  if (unknown.length)
@@ -54683,8 +54726,9 @@ async function listTodosCommand(api, defaultProjectId, argv) {
54683
54726
  `);
54684
54727
  return;
54685
54728
  }
54729
+ const scopeHint = mineOnly ? " of yours (--project-wide for everyone's)" : "";
54686
54730
  if (!visible.length) {
54687
- process.stderr.write(`${DIM}(no todos)${RESET}
54731
+ process.stderr.write(`${DIM}(no todos${scopeHint})${RESET}
54688
54732
  `);
54689
54733
  if (nextCursor !== undefined)
54690
54734
  process.stderr.write(`${DIM}Next cursor: ${nextCursor} (re-run with the same filters plus --cursor ${nextCursor})${RESET}
@@ -54701,7 +54745,7 @@ async function listTodosCommand(api, defaultProjectId, argv) {
54701
54745
  process.stdout.write(`${sc}${status.padEnd(statusW)}${RESET} ${DIM}${ts}${RESET} ${t.id} ${title}
54702
54746
  `);
54703
54747
  }
54704
- process.stderr.write(`${DIM}${visible.length} todo(s)${RESET}
54748
+ process.stderr.write(`${DIM}${visible.length} todo(s)${scopeHint}${RESET}
54705
54749
  `);
54706
54750
  if (nextCursor !== undefined)
54707
54751
  process.stderr.write(`${DIM}Next cursor: ${nextCursor} (re-run with the same filters plus --cursor ${nextCursor})${RESET}
@@ -55656,7 +55700,7 @@ password=${apiKey}
55656
55700
  }
55657
55701
 
55658
55702
  // src/index.ts
55659
- import { randomUUID as randomUUID3 } from "crypto";
55703
+ import { createHash as createHash3, randomUUID as randomUUID3 } from "crypto";
55660
55704
 
55661
55705
  // src/ensure-bridge.ts
55662
55706
  import { spawn as spawn4, spawnSync as spawnSync4 } from "child_process";
@@ -80376,13 +80420,25 @@ async function main() {
80376
80420
  }
80377
80421
  if (positionals[0] === "recommend") {
80378
80422
  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}
80423
+ const inlineTitle = !templateId && args.title?.trim();
80424
+ if (!templateId && !inlineTitle) {
80425
+ 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
80426
  `);
80382
- process.stderr.write(`${DIM2}Create a template first with: todoregistry-cli create --name ... --description ... --body @prompt.md${RESET2}
80427
+ 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
80428
  `);
80384
80429
  process.exit(2);
80385
80430
  }
80431
+ const source = (() => {
80432
+ if (!args.source)
80433
+ return;
80434
+ const [label, ref, url2, from] = String(args.source).split("|");
80435
+ if (!label || !ref) {
80436
+ process.stderr.write(`${RED3}--source needs at least "label|ref"${RESET2}
80437
+ `);
80438
+ process.exit(2);
80439
+ }
80440
+ return { label, ref, ...url2 ? { url: url2 } : {}, ...from ? { from } : {} };
80441
+ })();
80386
80442
  let projectId2 = args.project || getEnv("PROJECT_ID") || cfgScope.data.default_project_id;
80387
80443
  if (!projectId2) {
80388
80444
  const projects2 = await api2.listProjects();
@@ -80401,7 +80457,9 @@ async function main() {
80401
80457
  }
80402
80458
  const rec = await api2.recommend({
80403
80459
  projectId: projectId2,
80404
- specId: templateId,
80460
+ ...templateId ? { specId: templateId } : { template: { name: inlineTitle, body: args.note || inlineTitle } },
80461
+ ...args.info ? { todoKind: "info" } : {},
80462
+ ...source ? { source } : {},
80405
80463
  ...args.title ? { title: args.title } : {},
80406
80464
  ...args.note ? { note: args.note } : {},
80407
80465
  ...priority ? { priority } : {},
@@ -80412,7 +80470,7 @@ async function main() {
80412
80470
  if (args.json)
80413
80471
  console.log(JSON.stringify(rec, null, 2));
80414
80472
  else
80415
- process.stderr.write(`${GREEN2}\u2705 Recommended template ${templateId} on project ${projectId2}${RESET2}
80473
+ process.stderr.write(`${GREEN2}\u2705 ${args.info ? "\u24D8 info" : "\u2727 proposal"} ${rec?.id ?? ""} on project ${projectId2}${RESET2}
80416
80474
  `);
80417
80475
  return;
80418
80476
  }
@@ -80515,7 +80573,20 @@ async function main() {
80515
80573
  defaultProjectId = projects2.find((p) => p.project?.isDefault)?.project?.id || projects2[0]?.project?.id;
80516
80574
  }
80517
80575
  const sub = process.argv.slice(process.argv.indexOf(positionals[0]) + 1);
80518
- await listTodosCommand(api2, defaultProjectId ?? undefined, sub);
80576
+ const tokenFp = createHash3("sha256").update(apiKey).digest("hex").slice(0, 16);
80577
+ const resolveUserId = async () => {
80578
+ const envId = getEnv("USER_ID");
80579
+ if (envId)
80580
+ return envId;
80581
+ const cached2 = cfgScope.cachedUserId(tokenFp);
80582
+ if (cached2)
80583
+ return cached2;
80584
+ const v = await api2.validateApiKey();
80585
+ if (v.userId)
80586
+ cfgScope.setUserId(v.userId, tokenFp);
80587
+ return v.userId;
80588
+ };
80589
+ await listTodosCommand(api2, defaultProjectId ?? undefined, sub, resolveUserId);
80519
80590
  return;
80520
80591
  }
80521
80592
  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.72",
11
11
  "type": "module",
12
12
  "bin": {
13
13
  "todoforai-cli": "bin/todoforai-cli.js",