@todoforai/cli 0.1.56 → 0.1.58

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.
@@ -42767,9 +42767,9 @@ class ApiClient {
42767
42767
  }
42768
42768
  return h;
42769
42769
  }
42770
- async request(method, endpoint, body) {
42770
+ async request(method, endpoint, body, timeoutMs = 30000) {
42771
42771
  const url = `${this.apiUrl}${endpoint.replace(/^\/api\/v1/, restBasePath(this.apiKey))}`;
42772
- const opts = { method, headers: this.headers, signal: AbortSignal.timeout(30000) };
42772
+ const opts = { method, headers: this.headers, signal: AbortSignal.timeout(timeoutMs) };
42773
42773
  if (body)
42774
42774
  opts.body = JSON.stringify(body);
42775
42775
  const res = await fetch(url, opts);
@@ -42900,7 +42900,7 @@ class ApiClient {
42900
42900
  return this.request("GET", `/api/v1/projects/${projectId}/voice-sources`);
42901
42901
  }
42902
42902
  collectVoiceSource(projectId, channel, opts) {
42903
- return this.request("POST", "/api/v1/business-context/collect-voice-source", { projectId, channel, ...opts });
42903
+ return this.request("POST", "/api/v1/business-context/collect-voice-source", { projectId, channel, ...opts }, 130000);
42904
42904
  }
42905
42905
  removeVoiceSource(projectId, channel, opts = {}) {
42906
42906
  return this.request("POST", "/api/v1/business-context/remove-voice-source", { projectId, channel, ...opts });
@@ -42939,6 +42939,12 @@ class ApiClient {
42939
42939
  sendFeedMessage(msg) {
42940
42940
  return this.request("POST", "/api/v1/feed/message", msg);
42941
42941
  }
42942
+ getProjectMembers(projectId) {
42943
+ return this.request("GET", `/api/v1/projects/${projectId}/members`);
42944
+ }
42945
+ sendMemberMessage(msg) {
42946
+ return this.request("POST", "/api/v1/feed/message/member", msg);
42947
+ }
42942
42948
  listProjectSurfaces(projectId) {
42943
42949
  return this.request("GET", `/api/v1/projects/${projectId}/surfaces`);
42944
42950
  }
@@ -43882,7 +43888,7 @@ function manualAnswersOf(sources) {
43882
43888
  return out;
43883
43889
  }
43884
43890
  var manualSamples = (answers) => Object.entries(answers).filter(([, a]) => a.trim()).map(([prompt, text]) => ({ prompt, text: text.trim() }));
43885
- var senderLabel = (s) => s.kind === "team" ? `${s.name} · TODO for AI team` : `${s.name} · your agent`;
43891
+ var senderLabel = (s) => s.kind === "team" ? `${s.name} · TODO for AI team` : s.kind === "member" ? `${s.name} · teammate` : `${s.name} · your agent`;
43886
43892
  // ../packages/shared-fbe/src/templateBody.ts
43887
43893
  var USER_HEADINGS = new Set([
43888
43894
  "your task",
@@ -44861,7 +44867,7 @@ var tool_catalog_default = {
44861
44867
  installer: "npm",
44862
44868
  label: "TODOforAI",
44863
44869
  summary: "Create and follow up on your TODOs, and show results in the chat.",
44864
- description: 'Manage the user\'s OWN TODOforAI tasks (not code `// TODO` comments). Delegate/spin off work: `tfa-cli "prompt"` creates a real TODO with its own agent and chat (`-n` wait for completion, `--no-watch` fire-and-forget, `--project/--agent/--model`, `--resume <id>`, `addmessage <id> "text"`). Inspect: `list [--status open]`, `--inspect <id>`. Edit any todo without opening it: `status <id> DONE|READY|ARCHIVED|POSTPONED|CANCELLED|DELETED` (close, reopen, shelve, trash — `todo restore <id>` undoes a delete) and `todo set <id|-> title=… group=… star=true schedule=<unix-ms> agent=… brand=…` (own todo fields). Both take ONE id, so a loop over `list --json | jq -r \'.items[].id\'` is how you close, retitle, refile or archive MANY todos at once; `project set|settings|groups …` (name, settings, todo groups), `agent create|update <agent> model=… sysmsg=…` (permissions/MCP/device wiring and delete are refused from an agent shell). Surface results into chat: `show <file|-> [todo-id]` (renders by mimetype; `--card <name>` via project renderer; `--alias <name>` updates in place), `open <url>` embeds a live preview. Notifications: `inbox [needs-you|messages|activity] [--unread]` reads the user\'s bell feed (what finished/failed/needs them while away), `inbox seen [tab]` marks it read; `notify "Title" "message" [--href /t/<id>]` leaves a note in the user\'s Messages tab + phone push — recipient is ALWAYS the key\'s own user (no --to; cannot message teammates or anyone else), shown as from you, same text is delivered once, max 10/day, use for things they must see later (not chat replies). This is the ONLY way to display a local file (image, pdf, html) in the chat: markdown `![](path.png)` with a local/relative path does NOT resolve to workspace files or attachments and 404s in the browser. `--template` for registry workflows.',
44870
+ description: 'Manage the user\'s OWN TODOforAI tasks (not code `// TODO` comments). Delegate/spin off work: `tfa-cli "prompt"` creates a real TODO with its own agent and chat (`-n` wait for completion, `--no-watch` fire-and-forget, `--project/--agent/--model`, `--resume <id>`, `addmessage <id> "text"`). Inspect: `list [--status open]`, `--inspect <id>`. Edit any todo without opening it: `status <id> DONE|READY|ARCHIVED|POSTPONED|CANCELLED|DELETED` (close, reopen, shelve, trash — `todo restore <id>` undoes a delete) and `todo set <id|-> title=… group=… star=true schedule=<unix-ms> agent=… brand=…` (own todo fields). Both take ONE id, so a loop over `list --json | jq -r \'.items[].id\'` is how you close, retitle, refile or archive MANY todos at once; `project set|settings|groups …` (name, settings, todo groups), `agent create|update <agent> model=… sysmsg=…` (permissions/MCP/device wiring and delete are refused from an agent shell). Surface results into chat: `show <file|-> [todo-id]` (renders by mimetype; `--card <name>` via project renderer; `--alias <name>` updates in place), `open <url>` embeds a live preview. Notifications: `inbox [needs-you|messages|activity] [--unread]` reads the user\'s bell feed (what finished/failed/needs them while away), `inbox seen [tab]` marks it read; `notify "Title" "message" [--href /t/<id>]` leaves a note in the user\'s Messages tab + phone push (shown as from you); `--to <email>` reaches a member of the current project instead (`project members` lists them, 20/day) — nobody else; same text is delivered once, max 10/day to the user, use for things they must see later (not chat replies). This is the ONLY way to display a local file (image, pdf, html) in the chat: markdown `![](path.png)` with a local/relative path does NOT resolve to workspace files or attachments and 404s in the browser. `--template` for registry workflows.',
44865
44871
  installCmd: "npm install -g @todoforai/cli",
44866
44872
  versionCmd: "tfa-cli --version 2>/dev/null | sed -n 1p",
44867
44873
  preinstallCloud: true,
@@ -44987,7 +44993,8 @@ var tool_catalog_default = {
44987
44993
  installer: "system",
44988
44994
  label: "Google Cloud",
44989
44995
  summary: "Manage your Google Cloud projects and services.",
44990
- installCmd: 'curl -sSL https://sdk.cloud.google.com | bash -s -- --disable-prompts && mkdir -p "$HOME/.todoforai/tools/bin" && ln -sf "$HOME/google-cloud-sdk/bin/gcloud" "$HOME/.todoforai/tools/bin/gcloud"',
44996
+ needsPython: true,
44997
+ installCmd: `curl -fsSL https://dl.google.com/dl/cloudsdk/channels/rapid/google-cloud-sdk.tar.gz | tar -xzC "$HOME" && CLOUDSDK_PYTHON="$py" "$HOME/google-cloud-sdk/install.sh" --quiet --install-python false --usage-reporting false --path-update false --command-completion false && mkdir -p "$HOME/.todoforai/tools/bin" && rm -f "$HOME/.todoforai/tools/bin/gcloud" && printf '#!/bin/sh\\nCLOUDSDK_PYTHON="\${CLOUDSDK_PYTHON:-%s}" exec "$HOME/google-cloud-sdk/bin/gcloud" "$@"\\n' "$py" > "$HOME/.todoforai/tools/bin/gcloud" && chmod +x "$HOME/.todoforai/tools/bin/gcloud"`,
44991
44998
  statusCmd: `gcloud auth list --filter=status:ACTIVE --format="value(account)" 2>/dev/null | tr -d '\\r' | grep -m1 .`,
44992
44999
  loginCmd: "gcloud auth login --no-launch-browser",
44993
45000
  loginNeedsPty: true,
@@ -45270,7 +45277,7 @@ var tool_catalog_default = {
45270
45277
  label: "Surface",
45271
45278
  summary: "See and control what is on your TODOforAI screen.",
45272
45279
  platformAuth: true,
45273
- description: "The user's live UI from the shell. Everything on screen is a SURFACE holding one ref (a todo id, `<todoId>:<alias>` = a shown artifact, or a url). `describe` lists them (kind, id, ref, focus, embedded-browser CDP URL; `--json`). `open overlay <todoId>` (full page), `open window <ref>` (floating), `open panel changes|preview|browser|terminal [--url]` (a todo's side panel), `open card <ref> [--title] [--group <slug>]` (a persisted board card every member sees); `close|focus <kind> <id>`. Persistent state (plans, reports, live status) → `show chat.panel <todoId> \"...md\"` (stdin ok; re-show updates in place), `clear` removes; events go in chat. `card add|list|get|rm <name>` = project HTML renderers. Exit 2 = no frontend connected.",
45280
+ description: "The user's live UI from the shell. Everything on screen is a SURFACE holding one ref (a todo id, `<todoId>:<alias>` = a shown artifact, or a url). `describe` lists them (kind, id, ref, focus, embedded-browser CDP URL; `--json`). `open overlay <todoId>` (full page), `open window <ref> [--detached]` (floating; --detached = out of the grid, own size, resizable), `open panel changes|preview|browser|terminal [--url]` (a todo's side panel), `open card <ref> [--title] [--group <slug>]` (a persisted board card every member sees); `close|focus <kind> <id>`. Persistent state (plans, reports, live status) → `show chat.panel <todoId> \"...md\"` (stdin ok; re-show updates in place), `clear` removes; events go in chat. `card add|list|get|rm <name>` = project HTML renderers. Exit 2 = no frontend connected.",
45274
45281
  versionCmd: "tfa-surface --version 2>/dev/null | sed -n 1p",
45275
45282
  installCmd: "npm install -g @todoforai/tfa-surface",
45276
45283
  internal: true
@@ -45844,7 +45851,7 @@ var tool_catalog_default = {
45844
45851
  logoutCmd: "cd ~ && mcporter config remove blender",
45845
45852
  credentialPaths: ["~/.mcporter/mcporter.json"],
45846
45853
  description: "Blender 3D via the ahujasid/blender-mcp server through mcporter: `mcporter list blender --schema`, `mcporter call blender.<tool> key=value` (`code=@file.py`; every tool needs `user_prompt`). Workhorse: execute_blender_code (any bpy Python); also scene/object info, viewport screenshot (`--save-images <dir>`), PolyHaven/Sketchfab import. Needs a GUI Blender with the addon server started (socket :9876; headless has none). 'Could not connect' → `blender-mcp install-addon`, enable in Preferences → Add-ons, N-panel → Start MCP Server (scripted: `blender --python s.py`, s.py registers a `bpy.app.timers` calling `bpy.ops.blendermcp.start_server()`). Code runs unguarded: save first (`bpy.ops.wm.save_mainfile()`), approve destructive ops.",
45847
- versionCmd: `{ python -c 'from importlib.metadata import version; print(version("blender-mcp"))' || python3 -c 'from importlib.metadata import version; print(version("blender-mcp"))'; } 2>/dev/null`
45854
+ versionCmd: "{ python -c 'import sys; from importlib.metadata import version; print(version(sys.argv[1]))' blender-mcp || python3 -c 'import sys; from importlib.metadata import version; print(version(sys.argv[1]))' blender-mcp; } 2>/dev/null"
45848
45855
  },
45849
45856
  "google-ads": {
45850
45857
  category: "marketing",
@@ -46116,7 +46123,7 @@ import { parseArgs } from "util";
46116
46123
  // package.json
46117
46124
  var package_default = {
46118
46125
  name: "@todoforai/cli",
46119
- version: "0.1.56",
46126
+ version: "0.1.58",
46120
46127
  type: "module",
46121
46128
  bin: {
46122
46129
  "todoforai-cli": "bin/todoforai-cli.js",
@@ -46186,7 +46193,7 @@ Examples:
46186
46193
  tfa-cli recommend --template <id> # Add a template as a recommendation card
46187
46194
  tfa-cli inbox [needs-you|messages|activity] [--unread] # Read the notification feed (the bell)
46188
46195
  tfa-cli inbox seen [tab] # Mark notifications read
46189
- tfa-cli notify "Title" "message" [--href /t/<id>] # Note to YOUR OWN user only (Messages tab + phone push); no --to
46196
+ tfa-cli notify [--to <email>] "Title" "message" [--href /t/<id>] # Note to your own user, or --to a project member (Messages tab + phone push)
46190
46197
  tfa-cli claim mint --seed <projectId> [--emails a@x,b@y] [--ttl <sec>] # Mint /claim/<token> links for a project you own
46191
46198
  tfa-cli next [--direction "<text>"] # Analyzer growth recommendation cards
46192
46199
  tfa-cli <command> help # Details per command
@@ -46304,6 +46311,7 @@ function parseCliArgs() {
46304
46311
  ttl: { type: "string" },
46305
46312
  subject: { type: "string" },
46306
46313
  href: { type: "string" },
46314
+ to: { type: "string" },
46307
46315
  unread: { type: "boolean", default: false },
46308
46316
  limit: { type: "string" },
46309
46317
  resume: { type: "string", short: "r" },
@@ -49524,6 +49532,7 @@ tfa-cli project — edit the current project
49524
49532
 
49525
49533
  Usage:
49526
49534
  tfa-cli project list Projects you can access (* = default)
49535
+ tfa-cli project members Who is on it (notify --to <email> reaches them)
49527
49536
  tfa-cli project set <field=value>… name=… description=…
49528
49537
  (isPublic refused from an agent shell)
49529
49538
  tfa-cli project default Make it the project you land on at /
@@ -49625,6 +49634,17 @@ async function projectCommand(api, positionals, args, projectId) {
49625
49634
  }
49626
49635
  if (!projectId)
49627
49636
  fail(NO_PROJECT);
49637
+ if (sub === "members") {
49638
+ const members = await api.getProjectMembers(projectId);
49639
+ if (args.json) {
49640
+ console.log(JSON.stringify(members, null, 2));
49641
+ return;
49642
+ }
49643
+ for (const m of members)
49644
+ process.stderr.write(` ${m.name ?? ""} ${m.email ?? ""} ${DIM}${m.id}${RESET}
49645
+ `);
49646
+ return;
49647
+ }
49628
49648
  if (sub === "set") {
49629
49649
  if (!rest.length)
49630
49650
  fail("Usage: tfa-cli project set <field=value>…");
@@ -50557,16 +50577,19 @@ Filters (same as the bell's chips):
50557
50577
  }
50558
50578
  function printNotifyHelp() {
50559
50579
  process.stderr.write(`
50560
- tfa-cli notify — leave a note for YOUR user (their Messages tab + phone push)
50580
+ tfa-cli notify — leave a note in someone's Messages inbox (+ phone push)
50561
50581
 
50562
50582
  Usage:
50563
50583
  tfa-cli notify "Title" "message" [--href /t/<todo-id>] [--subject <key>] [--json]
50584
+ tfa-cli notify --to <email> "Title" "message" [--project <id>]
50564
50585
  echo "message" | tfa-cli notify "Title"
50565
50586
 
50566
- Recipient: ALWAYS the user this key belongs to (the person you work for). There is no --to:
50567
- it cannot message a teammate, a customer or any other account. Shown as from your agent.
50587
+ Recipient: your own user by default. --to <email> reaches a TEAMMATE: someone who is a member
50588
+ of the current project (--project, $TODOFORAI_PROJECT_ID or your default), shown as from you.
50589
+ Anyone else is refused. List them with: tfa-cli project members
50568
50590
  --subject dedupes: the same subject is delivered once (default: hash of title+message,
50569
- so a retry never double-notifies). --href must be an in-app path. Max 10 new notes/day.
50591
+ so a retry never double-notifies). --href must be an in-app path.
50592
+ Limits: 10 notes/day to yourself, 20/day to teammates.
50570
50593
  `);
50571
50594
  }
50572
50595
  function tierArg(name) {
@@ -50618,12 +50641,22 @@ async function inboxCommand(api, positionals, args) {
50618
50641
  console.log(` ${DIM}${e.href}${RESET}`);
50619
50642
  }
50620
50643
  }
50621
- var RECIPIENT_FLAGS = ["--to", "--email", "--user", "--recipient", "--cc"];
50622
- async function notifyCommand(api, positionals, args) {
50623
- const argv = process.argv.slice(2);
50624
- const bad = argv.find((a) => RECIPIENT_FLAGS.some((f) => a === f || a.startsWith(`${f}=`)));
50644
+ var OTHER_RECIPIENT_FLAGS = ["--email", "--user", "--recipient", "--cc"];
50645
+ async function notifyCommand(api, positionals, args, projectId) {
50646
+ const bad = process.argv.slice(2).find((a) => OTHER_RECIPIENT_FLAGS.some((f) => a === f || a.startsWith(`${f}=`)));
50625
50647
  if (bad) {
50626
- process.stderr.write(`${RED}notify has no recipient option (${bad.split("=")[0]}): it only reaches the user this key belongs to — your own user. Messaging other people is not supported.${RESET}
50648
+ process.stderr.write(`${RED}Unknown option ${bad.split("=")[0]}: use --to <email> for a project teammate${RESET}
50649
+ `);
50650
+ process.exit(2);
50651
+ }
50652
+ const to = args.to === undefined ? undefined : String(args.to).trim();
50653
+ if (to === "") {
50654
+ process.stderr.write(`${RED}--to is empty: pass a teammate's email, or drop --to to notify your own user${RESET}
50655
+ `);
50656
+ process.exit(2);
50657
+ }
50658
+ if (to && !projectId) {
50659
+ process.stderr.write(`${RED}--to needs a project: pass --project <id> (the recipient must be a member of it)${RESET}
50627
50660
  `);
50628
50661
  process.exit(2);
50629
50662
  }
@@ -50635,12 +50668,13 @@ async function notifyCommand(api, positionals, args) {
50635
50668
  }
50636
50669
  const subject = args.subject || createHash("sha256").update(`${title}
50637
50670
  ${message}`).digest("hex").slice(0, 16);
50638
- const row = await api.sendFeedMessage({ subject, title, message, ...args.href ? { href: String(args.href) } : {} });
50671
+ const msg = { subject, title, message, ...args.href ? { href: String(args.href) } : {} };
50672
+ const row = await (to ? api.sendMemberMessage({ ...msg, projectId, to }) : api.sendFeedMessage(msg));
50639
50673
  if (args.json) {
50640
50674
  console.log(JSON.stringify(row));
50641
50675
  return;
50642
50676
  }
50643
- process.stderr.write(row.created !== false ? `${GREEN}✅ Delivered to your own user's Messages inbox (not anyone else)${RESET} ${DIM}(subject ${subject})${RESET}
50677
+ process.stderr.write(row.created !== false ? `${GREEN}✅ Delivered to ${to ?? "your own user"}'s Messages inbox${RESET} ${DIM}(subject ${subject})${RESET}
50644
50678
  ` : `${DIM}Already sent (subject ${subject}) — nothing new delivered${RESET}
50645
50679
  `);
50646
50680
  }
@@ -75353,7 +75387,7 @@ async function main() {
75353
75387
  return;
75354
75388
  }
75355
75389
  if (positionals[0] === "notify") {
75356
- await notifyCommand(api2, positionals, args);
75390
+ await notifyCommand(api2, positionals, args, scopedProject);
75357
75391
  return;
75358
75392
  }
75359
75393
  if (positionals[0] === "delete") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@todoforai/cli",
3
- "version": "0.1.56",
3
+ "version": "0.1.58",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "todoforai-cli": "bin/todoforai-cli.js",