@todoforai/cli 0.1.65 → 0.1.67

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.
@@ -37682,19 +37682,19 @@ var require_doctype = __commonJS((exports) => {
37682
37682
  return DOCUMENT_MODE.NO_QUIRKS;
37683
37683
  };
37684
37684
  exports.serializeContent = function(name, publicId, systemId) {
37685
- let str = "!DOCTYPE ";
37685
+ let str2 = "!DOCTYPE ";
37686
37686
  if (name) {
37687
- str += name;
37687
+ str2 += name;
37688
37688
  }
37689
37689
  if (publicId) {
37690
- str += " PUBLIC " + enquoteDoctypeId(publicId);
37690
+ str2 += " PUBLIC " + enquoteDoctypeId(publicId);
37691
37691
  } else if (systemId) {
37692
- str += " SYSTEM";
37692
+ str2 += " SYSTEM";
37693
37693
  }
37694
37694
  if (systemId !== null) {
37695
- str += " " + enquoteDoctypeId(systemId);
37695
+ str2 += " " + enquoteDoctypeId(systemId);
37696
37696
  }
37697
- return str;
37697
+ return str2;
37698
37698
  };
37699
37699
  });
37700
37700
 
@@ -40266,14 +40266,14 @@ var require_serializer = __commonJS((exports, module) => {
40266
40266
  this.html += "<" + doctype.serializeContent(name, null, null) + ">";
40267
40267
  }
40268
40268
  }
40269
- Serializer.escapeString = function(str, attrMode) {
40270
- str = str.replace(AMP_REGEX, "&amp;").replace(NBSP_REGEX, "&nbsp;");
40269
+ Serializer.escapeString = function(str2, attrMode) {
40270
+ str2 = str2.replace(AMP_REGEX, "&amp;").replace(NBSP_REGEX, "&nbsp;");
40271
40271
  if (attrMode) {
40272
- str = str.replace(DOUBLE_QUOTE_REGEX, "&quot;");
40272
+ str2 = str2.replace(DOUBLE_QUOTE_REGEX, "&quot;");
40273
40273
  } else {
40274
- str = str.replace(LT_REGEX, "&lt;").replace(GT_REGEX, "&gt;");
40274
+ str2 = str2.replace(LT_REGEX, "&lt;").replace(GT_REGEX, "&gt;");
40275
40275
  }
40276
- return str;
40276
+ return str2;
40277
40277
  };
40278
40278
  module.exports = Serializer;
40279
40279
  });
@@ -40664,19 +40664,19 @@ var require_doctype2 = __commonJS((exports) => {
40664
40664
  return DOCUMENT_MODE.NO_QUIRKS;
40665
40665
  };
40666
40666
  exports.serializeContent = function(name, publicId, systemId) {
40667
- let str = "!DOCTYPE ";
40667
+ let str2 = "!DOCTYPE ";
40668
40668
  if (name) {
40669
- str += name;
40669
+ str2 += name;
40670
40670
  }
40671
40671
  if (publicId) {
40672
- str += " PUBLIC " + enquoteDoctypeId(publicId);
40672
+ str2 += " PUBLIC " + enquoteDoctypeId(publicId);
40673
40673
  } else if (systemId) {
40674
- str += " SYSTEM";
40674
+ str2 += " SYSTEM";
40675
40675
  }
40676
40676
  if (systemId !== null) {
40677
- str += " " + enquoteDoctypeId(systemId);
40677
+ str2 += " " + enquoteDoctypeId(systemId);
40678
40678
  }
40679
- return str;
40679
+ return str2;
40680
40680
  };
40681
40681
  });
40682
40682
 
@@ -42969,6 +42969,9 @@ class ApiClient {
42969
42969
  unshareDevice(id, email) {
42970
42970
  return this.request("POST", `/api/v1/devices/${id}/unshare`, { id, email });
42971
42971
  }
42972
+ leaveDevice(id) {
42973
+ return this.request("POST", `/api/v1/devices/${id}/leave`, { id });
42974
+ }
42972
42975
  setDeviceWallpaperMode(id, mode) {
42973
42976
  return this.request("POST", `/api/v1/devices/${id}/wallpaper-mode`, { id, mode });
42974
42977
  }
@@ -42981,8 +42984,8 @@ class ApiClient {
42981
42984
  renameBusinessContext(projectId, name) {
42982
42985
  return this.request("PUT", `/api/v1/projects/${projectId}/business-context`, { projectId, name });
42983
42986
  }
42984
- listVoiceSources(projectId) {
42985
- return this.request("GET", `/api/v1/projects/${projectId}/voice-sources`);
42987
+ listVoiceSources(projectId, scope) {
42988
+ return this.request("GET", `/api/v1/projects/${projectId}/voice-sources${scope ? `?scope=${scope}` : ""}`);
42986
42989
  }
42987
42990
  collectVoiceSource(projectId, channel, opts) {
42988
42991
  return this.request("POST", "/api/v1/business-context/collect-voice-source", { projectId, channel, ...opts }, 130000);
@@ -42990,8 +42993,8 @@ class ApiClient {
42990
42993
  removeVoiceSource(projectId, channel, opts = {}) {
42991
42994
  return this.request("POST", "/api/v1/business-context/remove-voice-source", { projectId, channel, ...opts });
42992
42995
  }
42993
- refineBrandVoice(projectId, source, correction) {
42994
- return this.request("POST", "/api/v1/business-context/refine-brand-voice", { projectId, ...source && { source }, ...correction && { correction } });
42996
+ refineBrandVoice(projectId, source, correction, scope) {
42997
+ return this.request("POST", "/api/v1/business-context/refine-brand-voice", { projectId, ...source && { source }, ...correction && { correction }, ...scope && { scope } });
42995
42998
  }
42996
42999
  getProfile() {
42997
43000
  return this.request("GET", "/api/v1/users/profile");
@@ -43008,6 +43011,15 @@ class ApiClient {
43008
43011
  listProjectGroups(projectId) {
43009
43012
  return this.request("GET", `/api/v1/projects/${projectId}/groups`);
43010
43013
  }
43014
+ getProjectGroupIntake(projectId, groupTag) {
43015
+ return this.request("GET", `/api/v1/projects/${projectId}/groups/${encodeURIComponent(groupTag)}/intake`);
43016
+ }
43017
+ updateProjectGroupIntake(projectId, groupTag, fields) {
43018
+ return this.request("PUT", `/api/v1/projects/${projectId}/groups/${encodeURIComponent(groupTag)}/intake`, { projectId, groupTag, ...fields });
43019
+ }
43020
+ regenerateProjectGroupIntake(projectId, groupTag) {
43021
+ return this.request("POST", `/api/v1/projects/${projectId}/groups/${encodeURIComponent(groupTag)}/intake/regenerate`, { projectId, groupTag });
43022
+ }
43011
43023
  upsertProjectGroup(projectId, slug, fields) {
43012
43024
  return this.request("PUT", `/api/v1/projects/${projectId}/groups/${encodeURIComponent(slug)}`, { projectId, slug, ...fields });
43013
43025
  }
@@ -43609,6 +43621,7 @@ var TodoStatus;
43609
43621
  TodoStatus2["PAUSED"] = "PAUSED";
43610
43622
  TodoStatus2["POSTPONED"] = "POSTPONED";
43611
43623
  TodoStatus2["REVIEW_REQUESTED"] = "REVIEW_REQUESTED";
43624
+ TodoStatus2["WAITING_AGENT"] = "WAITING_AGENT";
43612
43625
  TodoStatus2["RUNNING"] = "RUNNING";
43613
43626
  TodoStatus2["WAITING_FOR_USER"] = "WAITING_FOR_USER";
43614
43627
  TodoStatus2["COMPACTING"] = "COMPACTING";
@@ -43623,6 +43636,11 @@ var TodoStatus;
43623
43636
  TodoStatus2["ARCHIVED"] = "ARCHIVED";
43624
43637
  TodoStatus2["DELETED"] = "DELETED";
43625
43638
  })(TodoStatus ||= {});
43639
+ var CLOSED_STATUSES = [
43640
+ "DONE" /* DONE */,
43641
+ "ARCHIVED" /* ARCHIVED */,
43642
+ "DELETED" /* DELETED */
43643
+ ];
43626
43644
  // ../packages/shared-fbe/src/constants.ts
43627
43645
  var APPEAL_EMAIL = "support@todofor.ai";
43628
43646
  var ACCOUNT_SUSPENDED_MESSAGE = `Account suspended for suspected abuse. If you think this is a mistake, email ${APPEAL_EMAIL}.`;
@@ -43639,22 +43657,28 @@ var PROVIDER_BY_AUTHOR = {
43639
43657
  "x-ai": "xai",
43640
43658
  "z-ai": "z.ai",
43641
43659
  google: "google-ai-studio",
43642
- moonshotai: "opencode_go"
43660
+ moonshotai: "opencode_go",
43661
+ meta: "meta"
43643
43662
  };
43644
43663
  var MODEL_PROVIDER_OVERRIDES = {
43645
43664
  "openai/gpt-5.5-pro": "openrouter"
43646
43665
  };
43666
+ var MODEL_ENDPOINT_OVERRIDES = {
43667
+ "google/gemini-4-argon": { "google-ai-studio": "vertex" }
43668
+ };
43647
43669
  function resolveDispatchProvider(modelId, endpointProvider) {
43648
- return MODEL_PROVIDER_OVERRIDES[modelId.toLowerCase()] ?? endpointProvider;
43670
+ const id = modelId.toLowerCase();
43671
+ return MODEL_PROVIDER_OVERRIDES[id] ?? MODEL_ENDPOINT_OVERRIDES[id]?.[endpointProvider.toLowerCase()] ?? endpointProvider;
43649
43672
  }
43650
43673
  var SUBSCRIPTION_PROVIDERS = new Set(["opencode_go"]);
43651
43674
  function applyProviderOverride(qualified) {
43652
43675
  const colon = qualified.indexOf(":");
43676
+ const provider = qualified.slice(0, colon);
43653
43677
  const rest = qualified.slice(colon + 1);
43654
43678
  const suffix = rest.match(/\([^)]+\)$/)?.[0] ?? "";
43655
43679
  const bare = (suffix ? rest.slice(0, -suffix.length) : rest).toLowerCase();
43656
- const override = MODEL_PROVIDER_OVERRIDES[bare];
43657
- return override ? `${override}:${bare}${suffix}` : qualified;
43680
+ const resolved = resolveDispatchProvider(bare, provider);
43681
+ return resolved === provider ? qualified : `${resolved}:${bare}${suffix}`;
43658
43682
  }
43659
43683
  function isQualifiedModel(model) {
43660
43684
  const id = model.trim();
@@ -43677,9 +43701,9 @@ function qualifyModel(model) {
43677
43701
  const override = MODEL_PROVIDER_OVERRIDES[lower];
43678
43702
  if (override)
43679
43703
  return { model: `${override}:${lower}${suffix}` };
43680
- const provider = PROVIDER_BY_AUTHOR[lower.split("/")[0]];
43681
- if (provider && lower.includes("/"))
43682
- return { model: `${provider}:${lower}${suffix}` };
43704
+ const author = PROVIDER_BY_AUTHOR[lower.split("/")[0]];
43705
+ if (author && lower.includes("/"))
43706
+ return { model: `${resolveDispatchProvider(lower, author)}:${lower}${suffix}` };
43683
43707
  return { model: id };
43684
43708
  }
43685
43709
  function qualifiedModelIds(listing, filter) {
@@ -43701,6 +43725,7 @@ function qualifiedModelIds(listing, filter) {
43701
43725
  var HOBBY_ALLOWED = new Set([
43702
43726
  "anthropic/claude-sonnet-5.5",
43703
43727
  "anthropic/claude-sonnet-5",
43728
+ "anthropic/claude-haiku-5.5",
43704
43729
  "anthropic/claude-haiku-4.5"
43705
43730
  ]);
43706
43731
  var USAGE_WINDOWS = {
@@ -43737,6 +43762,7 @@ var mimetypes_default = {
43737
43762
  js: { mime: "text/javascript", type: "text" },
43738
43763
  ts: { mime: "text/typescript", type: "text" },
43739
43764
  html: { mime: "text/html", type: "text" },
43765
+ htm: { mime: "text/html", type: "text" },
43740
43766
  css: { mime: "text/css", type: "text" },
43741
43767
  xml: { mime: "text/xml", type: "text" },
43742
43768
  py: { mime: "text/x-python", type: "text" },
@@ -43775,7 +43801,7 @@ var mimetypes_default = {
43775
43801
  docx: { mime: "application/vnd.openxmlformats-officedocument.wordprocessingml.document", type: "document", tags: ["docx"] },
43776
43802
  ppt: { mime: "application/vnd.ms-powerpoint", type: "document" },
43777
43803
  pptx: { mime: "application/vnd.openxmlformats-officedocument.presentationml.presentation", type: "document", tags: ["pptx"] },
43778
- xls: { mime: "application/vnd.ms-excel", type: "spreadsheet" },
43804
+ xls: { mime: "application/vnd.ms-excel", type: "spreadsheet", tags: ["xls"] },
43779
43805
  xlsx: { mime: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", type: "spreadsheet", tags: ["xlsx"] },
43780
43806
  zip: { mime: "application/zip", type: "archive" },
43781
43807
  rar: { mime: "application/vnd.rar", type: "archive" },
@@ -43836,6 +43862,53 @@ function getMimeTypeFromFilename(filename) {
43836
43862
  const ext = filename.toLowerCase().split(".").pop();
43837
43863
  return EXT_TO_MIME[ext || ""] || "";
43838
43864
  }
43865
+ function truncatedImageReason(data) {
43866
+ const n = data.length;
43867
+ const ascii = (i, len) => String.fromCharCode(...data.subarray(i, i + len));
43868
+ if (n >= 4 && data[0] === 255 && data[1] === 216 && data[2] === 255) {
43869
+ let i = 2;
43870
+ while (i + 1 < n) {
43871
+ if (data[i] !== 255)
43872
+ return;
43873
+ const marker = data[i + 1];
43874
+ if (marker === 217)
43875
+ return;
43876
+ if (marker === 255) {
43877
+ i++;
43878
+ continue;
43879
+ }
43880
+ if (marker === 1 || marker >= 208 && marker <= 216) {
43881
+ i += 2;
43882
+ continue;
43883
+ }
43884
+ if (i + 3 >= n)
43885
+ break;
43886
+ i += 2 + (data[i + 2] << 8 | data[i + 3]);
43887
+ if (marker === 218) {
43888
+ while (i + 1 < n && !(data[i] === 255 && data[i + 1] !== 0 && !(data[i + 1] >= 208 && data[i + 1] <= 215)))
43889
+ i++;
43890
+ }
43891
+ }
43892
+ return "JPEG ends before its end-of-image marker (FF D9)";
43893
+ }
43894
+ if (n >= 8 && ascii(0, 8) === `‰PNG\r
43895
+ \x1A
43896
+ `) {
43897
+ let i = 8;
43898
+ while (i + 8 <= n) {
43899
+ const len = (data[i] << 24 | data[i + 1] << 16 | data[i + 2] << 8 | data[i + 3]) >>> 0;
43900
+ if (ascii(i + 4, 4) === "IEND")
43901
+ return i + 12 <= n ? undefined : "PNG ends inside its IEND chunk";
43902
+ i += 12 + len;
43903
+ }
43904
+ return "PNG ends before its IEND chunk";
43905
+ }
43906
+ if (n >= 12 && ascii(0, 4) === "RIFF" && ascii(8, 4) === "WEBP") {
43907
+ const riff = (data[4] | data[5] << 8 | data[6] << 16 | data[7] << 24) >>> 0;
43908
+ return n >= riff + 8 ? undefined : `WebP RIFF header declares ${riff + 8} bytes, file has ${n}`;
43909
+ }
43910
+ return;
43911
+ }
43839
43912
  var NEEDS_OBJECT_URL = new Set(Object.entries(mimetypes_default.types).filter(([, meta]) => meta.needsObjectUrl).map(([type]) => type));
43840
43913
  var MIME_TO_EXT = {};
43841
43914
  for (const [ext, entry] of Object.entries(mimetypes_default.extensions)) {
@@ -44121,6 +44194,8 @@ var USER_HEADINGS = new Set([
44121
44194
  "steps",
44122
44195
  "data needed"
44123
44196
  ]);
44197
+ // ../packages/shared-fbe/src/bridgeProtocol.ts
44198
+ var ADB_DATA_CHUNK = 40 * 1024;
44124
44199
  // ../packages/shared-fbe/src/bashPatterns.ts
44125
44200
  function splitShellCommands(input) {
44126
44201
  const parts = [];
@@ -44362,7 +44437,6 @@ function getBlockServerId(block) {
44362
44437
  const prefix = getBlockServerPrefix(block);
44363
44438
  return prefix ? prefix.slice(0, -1) : "*";
44364
44439
  }
44365
-
44366
44440
  // ../packages/shared-fbe/src/permissionUtils.ts
44367
44441
  function parsePattern(pattern) {
44368
44442
  const colonIndex = pattern.indexOf(":");
@@ -44592,7 +44666,7 @@ Exactly ONE \`api_recommend_template\` call per proposal: it carries both what t
44592
44666
  };
44593
44667
  var JARVIS_AGENT = {
44594
44668
  id: "00000000-0000-0000-0000-202601013152",
44595
- name: "JARVIS",
44669
+ name: "Tod",
44596
44670
  ownerId: "SYSTEM",
44597
44671
  systemMessage: "",
44598
44672
  model: "",
@@ -45125,7 +45199,7 @@ var tool_catalog_default = {
45125
45199
  installer: "npm",
45126
45200
  label: "TODOforAI",
45127
45201
  summary: "Create and follow up on your TODOs, and show results in the chat.",
45128
- 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, private to the todo\'s readers; `--card <name>` via project renderer; `--alias <name>` updates in place, `--alias <todoId>:<name>` updates another todo\'s artifact; `show share <ref>` makes it public and prints the link, `show unshare <ref>` makes it private again), `open <url>` embeds a live preview. Pick by where the output lives: read once → plain reply; interactive, this chat only → `html_snippet`; kept/revised later → `show --alias`, then `show share` only if someone outside needs a link; format the user asked for (pptx, docx…) → `show` the file. 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.',
45202
+ 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, private to the todo\'s readers; `--card <name>` via project renderer; `--alias <name>` updates in place, `--alias <todoId>:<name>` updates another todo\'s artifact; `show share <ref>` makes it public and prints the link, `show unshare <ref>` makes it private again), `open <url>` embeds a live preview. Show viewer chrome: `--title` appears in the header; a kebab menu holds file actions, versions and sharing when available. Desktop floating previews add minimize (park in a tab), maximize/restore and close (X); inline blocks have no window controls. For authored HTML, do not duplicate the header/controls or add a rounded, bordered root card: the floating viewer owns the rounded frame. Keep content edge-to-edge; internal layout padding is fine. Pick by where the output lives: read once → plain reply; interactive, this chat only → `html_snippet`; kept/revised later → `show --alias`, then `show share` only if someone outside needs a link; format the user asked for (pptx, docx…) → `show` the file. 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. Project files: `memory sync|path [--project <id>]` requires a project and checks out only projects/<id>/{memory,context} and projects/<id>/me/{memory,context}; legacy global files are read-only recovery. Voice commands use `--scope mine|project` (default mine, private within this project; project is shared).',
45129
45203
  installCmd: "npm install -g @todoforai/cli",
45130
45204
  versionCmd: "tfa-cli --version 2>/dev/null | sed -n 1p",
45131
45205
  preinstallCloud: true,
@@ -45358,7 +45432,7 @@ var tool_catalog_default = {
45358
45432
  preinstallCloud: true,
45359
45433
  label: "Browser",
45360
45434
  summary: "Browse websites automatically: click, type, read and screenshot.",
45361
- description: "Browser automation over CDP (`open`/`snapshot`/`click`/`type`/`fill`/`read`/`screenshot`/`eval`). `connect '<url>'` to a listed live browser (prefer the user's real Chrome), verify with `snapshot`; if empty, `close` (own session only) and reconnect once. NEVER `close --all`. Shared browser: `tab new` for your own tab, `tab <id>` before each command. No URL listed: cloud → browser-manager-cli; PC with display → Chrome `--remote-debugging-port=9222`, attach `--cdp 9222`.",
45435
+ description: "Browser automation over CDP (`open`/`snapshot`/`click`/`type`/`fill`/`read`/`screenshot`/`eval`). `connect '<url>'` to a listed live browser (prefer the user's real Chrome), verify with `snapshot`; if empty, `close` (own session only) and reconnect once. NEVER `close --all`. Shared browser: `tab new` for your own tab, `tab <id>` before each command. No URL listed: cloud → browser-manager-cli; PC with display → Chrome `--remote-debugging-port=9222`, attach `--cdp 9222`. Never `sleep`: `open` returns after load, but `click`/`fill` fail at once if the element isn't there yet — `wait '<sel>'`, `wait --text 'Done'`, `wait --url '**/dash'`, `wait --load networkidle` or `wait '#spinner' --state hidden` first.",
45362
45436
  versionCmd: "agent-browser --version 2>/dev/null | sed -n 1p",
45363
45437
  preinstallJarvis: true
45364
45438
  },
@@ -45903,6 +45977,7 @@ var tool_catalog_default = {
45903
45977
  summary: "Post to your Facebook Page and its linked Instagram account, and handle comments.",
45904
45978
  statusCmd: `facebook-pages-api whoami 2>/dev/null | sed -n 's/.*"name"[[:space:]]*:[[:space:]]*"\\([^"]*\\)".*/\\1/p' | grep -m10 .`,
45905
45979
  loginCmd: "facebook-pages-api auth --page-id <id> --page-token <token>",
45980
+ oauthBroker: true,
45906
45981
  credentialPaths: ["~/.config/facebook-pages-api/credentials.json"],
45907
45982
  description: "Facebook Pages CLI (Meta Graph API). `auth --page-id <id> --page-token <token>` (or env FB_PAGE_ID/FB_PAGE_TOKEN). `feed`, `comments`, `insights`, `conversations`, `media`, and `request <GET|POST|DELETE> <path> [--data <json>]` = any Graph edge with the page token attached (use instead of curl). Writes are live: `feed create --published false` for tests; `comments reply` posts publicly — get human approval. Follower totals: `whoami --fields followers_count,fan_count` (many classic insights metrics are retired). Instagram (linked professional account, same page token): `ig whoami|media|get|stories|tags|limit`, `ig publish --image_url U | --video_url U [--media_type STORIES] | --carousel U1,U2 [--caption …]` (public URLs; container → wait → publish; `--create-only` = dry run; 100 posts/24h), `ig comments list|replies|create|reply|hide|delete|toggle`, `ig insights [media_id]` (needs instagram_manage_insights). `--help` for subcommands.",
45908
45983
  versionCmd: "facebook-pages-api --version 2>/dev/null | sed -n 1p"
@@ -49895,21 +49970,67 @@ var tool_catalog_default = {
49895
49970
  },
49896
49971
  statusCmd: `zoo user view --format json 2>/dev/null | sed -n 's/.*"email": *"\\([^"]*\\)".*/\\1/p' | grep -m1 .`,
49897
49972
  loginCmd: "zoo auth login --web",
49973
+ logoutCmd: "printf 'y\\n' | zoo --host api.zoo.dev auth logout",
49898
49974
  loginNeedsPty: true,
49899
49975
  deviceCodePattern: "user code: ([A-Z0-9]{4}-[A-Z0-9]{4})",
49900
- oauthUrlPattern: "(https://api\\.zoo\\.dev/oauth2/device/verify\\S*)",
49976
+ oauthUrlPattern: "(https://api\\.zoo\\.dev/oauth2/device/verify\\?[^\\s']+)",
49901
49977
  canStreamLogin: true,
49902
49978
  credentialPaths: [
49903
49979
  "~/.config/zoo/hosts.toml"
49904
49980
  ],
49905
49981
  description: 'Official Zoo (KittyCAD) CLI. Text-to-CAD: `zoo ml text-to-cad export --output-format step "a 40mm gear"`; convert: `zoo file convert in.obj out/ --output-format step`; mass/volume: `zoo file mass|volume <file>`; KCL: `zoo kcl export|snapshot`; raw: `zoo api /user`. Token alternative: `echo <token> | zoo auth login --with-token`.',
49906
49982
  versionCmd: "zoo --version 2>/dev/null | sed -n 1p"
49983
+ },
49984
+ "python-docx": {
49985
+ category: "documents",
49986
+ pkg: "python-docx",
49987
+ pipModules: ["docx"],
49988
+ installer: "pip",
49989
+ label: "Word documents",
49990
+ summary: "Create, read and edit Word documents.",
49991
+ versionCmd: `{ "$HOME/.todoforai/tools/venv/bin/python" -c 'import docx; print(docx.__version__)' || python -c 'import docx; print(docx.__version__)' || python3 -c 'import docx; print(docx.__version__)' || py -c 'import docx; print(docx.__version__)'; } 2>/dev/null`,
49992
+ description: "Python python-docx library. Use python3 to create, save, reopen and edit Office files; no Microsoft account required."
49993
+ },
49994
+ openpyxl: {
49995
+ category: "documents",
49996
+ pkg: "openpyxl",
49997
+ pipModules: ["openpyxl"],
49998
+ installer: "pip",
49999
+ label: "Excel workbooks",
50000
+ summary: "Create, read and edit Excel workbooks.",
50001
+ versionCmd: `{ "$HOME/.todoforai/tools/venv/bin/python" -c 'import openpyxl; print(openpyxl.__version__)' || python -c 'import openpyxl; print(openpyxl.__version__)' || python3 -c 'import openpyxl; print(openpyxl.__version__)' || py -c 'import openpyxl; print(openpyxl.__version__)'; } 2>/dev/null`,
50002
+ description: "Python openpyxl library. Use python3 to create, save, reopen and edit Office files; no Microsoft account required."
50003
+ },
50004
+ "python-pptx": {
50005
+ category: "documents",
50006
+ pkg: "python-pptx",
50007
+ pipModules: ["pptx"],
50008
+ installer: "pip",
50009
+ label: "PowerPoint decks",
50010
+ summary: "Create, read and edit PowerPoint decks.",
50011
+ versionCmd: `{ "$HOME/.todoforai/tools/venv/bin/python" -c 'import pptx; print(pptx.__version__)' || python -c 'import pptx; print(pptx.__version__)' || python3 -c 'import pptx; print(pptx.__version__)' || py -c 'import pptx; print(pptx.__version__)'; } 2>/dev/null`,
50012
+ description: "Python python-pptx library. Use python3 to create, save, reopen and edit Office files; no Microsoft account required."
50013
+ },
50014
+ "modal-h3": {
50015
+ category: "creative",
50016
+ pkg: "@todoforai/modal-h3",
50017
+ installer: "npm",
50018
+ label: "MiniMax-H3 on Modal",
50019
+ summary: "Generate videos with sound using MiniMax-H3 on your own Modal GPUs.",
50020
+ versionCmd: "modal-h3 --version 2>/dev/null | sed -n 1p",
50021
+ statusCmd: `modal-h3 whoami 2>/dev/null | grep '"authenticated": true' >/dev/null && echo authenticated`,
50022
+ loginCmd: "printf '%s' <proxy_token> | modal-h3 auth --token-stdin --fl2va-url <fl2va_url> --ref2va-url <ref2va_url>",
50023
+ credentialPaths: ["~/.config/modal-h3/credentials.json"],
50024
+ description: "MiniMax-H3 text/image/reference-to-video+audio on a self-deployed Modal app (SGLang, 4x H200, scale-to-zero). `modal-h3 generate -p '…' [--first @a.png] [--last …] [--ref-image|--ref-video|--ref-audio <url|@file>] [--duration 4-15] [--ratio 16:9|auto] [--quality high] -o out.mp4` (task inferred, retries 503 during cold start, polls, downloads), `warmup`, `status|download|delete <id>`, `list`, `api <METHOD> <path>`. Deploy once with `modal run modal/serve.py::download` + `modal deploy modal/serve.py`. Costs ~$18/h of 4x H200 while a container is up (cold start minutes, ~75 s per 5 s 768p clip warm); every request wakes a container, `whoami` is offline. Jobs live in container memory. 2K needs `minimax-api regenerate`."
49907
50025
  }
49908
50026
  };
49909
50027
 
49910
50028
  // ../packages/shared-fbe/src/toolCatalog.ts
49911
50029
  var TOOL_CATALOG = tool_catalog_default;
49912
50030
  // ../packages/shared-fbe/src/toolInstallCommand.ts
50031
+ var NPM_HOME = '"${TFA_NPM_HOME:-$HOME}"';
50032
+ var NPM_TOOLS = NPM_HOME.replace('}"', '}/.todoforai/tools"');
50033
+ var NPM_LOCAL = NPM_HOME.replace('}"', '}/.local"');
49913
50034
  var KEY_OF = new Map(Object.entries(TOOL_CATALOG).map(([k, e]) => [e, k]));
49914
50035
  var NODE_MIN_MAJOR = 24;
49915
50036
  var NODE_OK = `command -v npm >/dev/null 2>&1 && node -e 'process.exit(+process.versions.node.split(".")[0]>=${NODE_MIN_MAJOR}?0:1)' >/dev/null 2>&1`;
@@ -50151,7 +50272,7 @@ var package_default = {
50151
50272
  type: "git",
50152
50273
  url: "git+https://github.com/todoforai/cli.git"
50153
50274
  },
50154
- version: "0.1.65",
50275
+ version: "0.1.67",
50155
50276
  type: "module",
50156
50277
  bin: {
50157
50278
  "todoforai-cli": "bin/todoforai-cli.js",
@@ -50251,6 +50372,7 @@ Options:
50251
50372
  --no-bridge Do not auto-spawn bridge
50252
50373
  --json
50253
50374
  --detailed | --format-anthropic inspect --json: keep ids/timestamps | Anthropic messages shape
50375
+ --full inspect: no truncation of text, thinking, tool calls and results
50254
50376
  --safe Validate API key upfront
50255
50377
  --debug, -d
50256
50378
  --debug-dump Attach LLM request debug info per turn (requires server grant)
@@ -50263,8 +50385,9 @@ tfa-cli show — put a file or url into the chat
50263
50385
 
50264
50386
  Usage:
50265
50387
  tfa-cli show <file|-> [todo-id] Render a file by mimetype (- reads stdin)
50266
- [--title T] [--alias A] [--mime M] [--card <name>] [--link] [--json]
50388
+ [--title T] [--alias A] [--mime M] [--card <name>] [--link] [--force] [--json]
50267
50389
  --link: compact download chip instead of inline render
50390
+ Refuses a likely-truncated image (JPEG/PNG cut before the end marker, WebP shorter than its RIFF size); --force shows it anyway
50268
50391
  Prints "<todoId>:<alias|id>". Private: visible to whoever can read the todo.
50269
50392
  Re-showing with the same --alias updates the block in place (new version)
50270
50393
  --alias <todoId>:<alias> updates that todo's block instead,
@@ -50279,6 +50402,13 @@ Usage:
50279
50402
 
50280
50403
  todo-id defaults to $TODOFORAI_TODO_ID (agent shell) or the last todo this CLI touched.
50281
50404
 
50405
+ Viewer chrome:
50406
+ --title appears in the header; a kebab menu holds file actions, versions and sharing when available.
50407
+ Desktop floating previews add minimize (park in a tab), maximize/restore and close (X).
50408
+ Inline blocks have no window controls. Do not draw these controls or a duplicate title bar in authored HTML.
50409
+ The floating viewer owns the rounded frame. Keep the HTML root flat, without a border, radius, shadow or outer margin;
50410
+ internal content padding and spacing are fine. Existing files and websites keep their own styling.
50411
+
50282
50412
  When to use what:
50283
50413
  read once, move on → plain chat reply
50284
50414
  interactive, this chat only → html_snippet (can exec/connect/chat back)
@@ -50342,10 +50472,12 @@ function parseCliArgs() {
50342
50472
  mime: { type: "string" },
50343
50473
  card: { type: "string" },
50344
50474
  link: { type: "boolean", default: false },
50475
+ force: { type: "boolean", default: false },
50345
50476
  direction: { type: "string" },
50346
50477
  url: { type: "string" },
50347
50478
  account: { type: "string" },
50348
50479
  "from-company": { type: "boolean", default: false },
50480
+ scope: { type: "string" },
50349
50481
  "dry-run": { type: "boolean", default: false },
50350
50482
  max: { type: "string" },
50351
50483
  seed: { type: "string" },
@@ -50370,6 +50502,7 @@ function parseCliArgs() {
50370
50502
  "no-edge": { type: "boolean", default: false },
50371
50503
  json: { type: "boolean", default: false },
50372
50504
  detailed: { type: "boolean", default: false },
50505
+ full: { type: "boolean", default: false },
50373
50506
  "format-anthropic": { type: "boolean", default: false },
50374
50507
  safe: { type: "boolean", default: false },
50375
50508
  debug: { type: "boolean", short: "d", default: false },
@@ -51268,7 +51401,8 @@ function getFrontendUrl(apiUrl, todoId) {
51268
51401
  }
51269
51402
 
51270
51403
  // src/inspect.ts
51271
- var xmlAttr = (v) => JSON.stringify(String(v));
51404
+ var str = (v) => typeof v === "string" ? v : JSON.stringify(v);
51405
+ var xmlAttr = (v) => JSON.stringify(str(v));
51272
51406
  var DROP_IF_EMPTY = new Set(["runMeta", "meta", "attachments", "blocks"]);
51273
51407
  var ALWAYS_DROP = new Set(["messageIds", "data"]);
51274
51408
  var DETAILED_ONLY = new Set([
@@ -51349,15 +51483,20 @@ function blockToAnthropic(block, mode, format) {
51349
51483
  const msg = block.error_message || block.content || "(unknown error)";
51350
51484
  return [{ type: "text", text: `[error] ${msg}` }];
51351
51485
  }
51486
+ const name = t === "named_tool" ? block.tool_name === "run_shell" ? "bash" : block.tool_name : t;
51352
51487
  const inputFields = [];
51353
51488
  for (const [k, v] of Object.entries(block)) {
51354
- if (BLOCK_META_KEYS.has(k))
51489
+ if (BLOCK_META_KEYS.has(k) || k === "args" || t === "named_tool" && k === "tool_name")
51355
51490
  continue;
51356
51491
  if (v === undefined || v === null || v === "")
51357
51492
  continue;
51358
51493
  inputFields.push([k, v]);
51359
51494
  }
51360
- const use = format === "anthropic" ? { type: "tool_use", id: block.id, name: t, input: Object.fromEntries(inputFields) } : { type: "tool_use", content: `<${t}${inputFields.map(([k, v]) => ` ${k}=${xmlAttr(v)}`).join("")}/>` };
51495
+ for (const [k, v] of Object.entries(block.args ?? {})) {
51496
+ if (!inputFields.some(([key]) => key === k))
51497
+ inputFields.push([k, v]);
51498
+ }
51499
+ const use = format === "anthropic" ? { type: "tool_use", id: block.id, name, input: Object.fromEntries(inputFields) } : { type: "tool_use", content: `<${name}${inputFields.map(([k, v]) => ` ${k}=${xmlAttr(v)}`).join("")}/>` };
51361
51500
  if (format === "compact" && mode !== "default")
51362
51501
  use.id = block.id;
51363
51502
  const items = [use];
@@ -51373,8 +51512,9 @@ function blockToAnthropic(block, mode, format) {
51373
51512
  source.name = att.originalName;
51374
51513
  parts.push({ type: isImage ? "image" : "document", source });
51375
51514
  }
51376
- if (typeof r.content === "string" && r.content)
51377
- parts.push({ type: "text", text: r.content });
51515
+ const text = r.content || r.meta?.text;
51516
+ if (typeof text === "string" && text)
51517
+ parts.push({ type: "text", text });
51378
51518
  return parts;
51379
51519
  });
51380
51520
  const result = { type: "tool_result", tool_use_id: block.id };
@@ -51391,8 +51531,9 @@ function blockToAnthropic(block, mode, format) {
51391
51531
  const nameAttr = att.originalName && !isAutoName ? ` name=${xmlAttr(att.originalName)}` : "";
51392
51532
  parts.push(`<attachment uri=${xmlAttr(att.uri)}${nameAttr} size=${att.fileSize}/>`);
51393
51533
  }
51394
- if (typeof r.content === "string" && r.content)
51395
- parts.push(r.content);
51534
+ const text = r.content || r.meta?.text;
51535
+ if (typeof text === "string" && text)
51536
+ parts.push(text);
51396
51537
  }
51397
51538
  const result = { type: "tool_result", content: parts.join(`
51398
51539
  `) };
@@ -51494,12 +51635,14 @@ function applySlice(arr, spec) {
51494
51635
  throw new Error(`Bad slice: '${spec}'`);
51495
51636
  return arr.slice(start, end);
51496
51637
  }
51497
- var trunc = (s, n) => s.length > n ? s.slice(0, n) + `
51498
- ${DIM}... (${s.length} chars)${RESET}` : s;
51638
+ var truncAt = (s, n) => s.length > n ? s.slice(0, n) + `
51639
+ ${DIM}... (${s.length} chars, --full for all)${RESET}` : s;
51499
51640
  var indent = (s, pre) => s.split(`
51500
51641
  `).join(`
51501
51642
  ${pre}`);
51502
- function printFullChat(todo, frontendUrl, slice, mode = "default", format = "compact") {
51643
+ function printFullChat(todo, frontendUrl, slice, mode = "default", format = "compact", full = false) {
51644
+ const out = (s) => process.stdout.write(s);
51645
+ const trunc = (s, n) => full ? s : truncAt(s, n);
51503
51646
  const statusColors = {
51504
51647
  DONE: GREEN,
51505
51648
  READY: GREEN,
@@ -51513,34 +51656,34 @@ function printFullChat(todo, frontendUrl, slice, mode = "default", format = "com
51513
51656
  TODO: CYAN
51514
51657
  };
51515
51658
  const statusColor = statusColors[todo.status] || DIM;
51516
- process.stderr.write(`${BOLD}TODO${RESET} ${todo.id}
51659
+ out(`${BOLD}TODO${RESET} ${todo.id}
51517
51660
  `);
51518
- process.stderr.write(`${DIM}Status:${RESET} ${statusColor}${todo.status}${RESET}
51661
+ out(`${DIM}Status:${RESET} ${statusColor}${todo.status}${RESET}
51519
51662
  `);
51520
- process.stderr.write(`${DIM}URL:${RESET} ${CYAN}${frontendUrl}${RESET}
51663
+ out(`${DIM}URL:${RESET} ${CYAN}${frontendUrl}${RESET}
51521
51664
  `);
51522
- process.stderr.write(`${DIM}Created:${RESET} ${new Date(todo.createdAt).toLocaleString()}
51665
+ out(`${DIM}Created:${RESET} ${new Date(todo.createdAt).toLocaleString()}
51523
51666
  `);
51524
51667
  if (todo.agentSettingsId)
51525
- process.stderr.write(`${DIM}Agent:${RESET} ${todo.agentSettingsId}
51668
+ out(`${DIM}Agent:${RESET} ${todo.agentSettingsId}
51526
51669
  `);
51527
51670
  if (slice)
51528
- process.stderr.write(`${DIM}Slice:${RESET} [${slice}]
51671
+ out(`${DIM}Slice:${RESET} [${slice}]
51529
51672
  `);
51530
- process.stderr.write("─".repeat(60) + `
51673
+ out("─".repeat(60) + `
51531
51674
  `);
51532
51675
  let messages = todo.messages || [];
51533
51676
  if (slice) {
51534
51677
  try {
51535
51678
  messages = applySlice(messages, slice);
51536
51679
  } catch (e) {
51537
- process.stderr.write(`${RED}${e.message}${RESET}
51680
+ out(`${RED}${e.message}${RESET}
51538
51681
  `);
51539
51682
  process.exit(2);
51540
51683
  }
51541
51684
  }
51542
51685
  if (!messages.length) {
51543
- process.stderr.write(`${DIM}(no messages)${RESET}
51686
+ out(`${DIM}(no messages)${RESET}
51544
51687
  `);
51545
51688
  return;
51546
51689
  }
@@ -51551,33 +51694,33 @@ function printFullChat(todo, frontendUrl, slice, mode = "default", format = "com
51551
51694
  const orig = messages[i];
51552
51695
  const ts = mode !== "default" && orig?.createdAt ? ` ${DIM}${new Date(orig.createdAt).toLocaleTimeString()}${RESET}` : "";
51553
51696
  const label = msg.role === "user" ? `${CYAN}▶ USER${RESET}` : `${GREEN}◀ ASSISTANT${RESET}`;
51554
- process.stderr.write(`
51697
+ out(`
51555
51698
  ${label}${ts}
51556
51699
  `);
51557
51700
  const items = Array.isArray(msg.content) ? msg.content : [{ type: "text", text: msg.content }];
51558
51701
  for (const it of items) {
51559
51702
  if (it.type === "text") {
51560
51703
  if (it.text)
51561
- process.stdout.write(trunc(it.text, 2000) + `
51704
+ out(trunc(it.text, 2000) + `
51562
51705
  `);
51563
51706
  } else if (it.type === "image" || it.type === "document") {
51564
51707
  const s = it.source ?? {};
51565
- process.stderr.write(` ${YELLOW}[${it.type}]${RESET} ${DIM}${s.mimeType ?? ""}${RESET} ${s.name ?? ""} ${DIM}(${s.size ?? "?"} bytes)${RESET}
51708
+ out(` ${YELLOW}[${it.type}]${RESET} ${DIM}${s.mimeType ?? ""}${RESET} ${s.name ?? ""} ${DIM}(${s.size ?? "?"} bytes)${RESET}
51566
51709
  ${DIM}${s.uri ?? ""}${RESET}
51567
51710
  `);
51568
51711
  } else if (it.type === "thinking") {
51569
51712
  if (it.thinking)
51570
- process.stderr.write(`${DIM}[thinking]${RESET} ${trunc(it.thinking, 500)}
51713
+ out(`${DIM}[thinking]${RESET} ${trunc(it.thinking, 500)}
51571
51714
  `);
51572
51715
  } else if (it.type === "tool_use") {
51573
51716
  toolUseCount++;
51574
51717
  if (it.content) {
51575
- process.stderr.write(` ${YELLOW}${trunc(it.content, 200)}${RESET}
51718
+ out(` ${YELLOW}${trunc(it.content, 200)}${RESET}
51576
51719
  `);
51577
51720
  } else {
51578
- const argStr = Object.entries(it.input || {}).map(([k, v]) => `${DIM}${k}=${RESET}${String(v).split(`
51721
+ const argStr = Object.entries(it.input || {}).map(([k, v]) => `${DIM}${k}=${RESET}${full ? str(v) : str(v).split(`
51579
51722
  `)[0].slice(0, 80)}`).join(" ");
51580
- process.stderr.write(` ${YELLOW}[${it.name}]${RESET} ${argStr}
51723
+ out(` ${YELLOW}[${it.name}]${RESET} ${argStr}
51581
51724
  `);
51582
51725
  }
51583
51726
  } else if (it.type === "tool_result") {
@@ -51600,15 +51743,15 @@ ${label}${ts}
51600
51743
  }).join(`
51601
51744
  `);
51602
51745
  }
51603
- process.stderr.write(` ${DIM}└─ result:${RESET}${status} ${indent(trunc(bodyStr, 500), ` ${DIM}│${RESET} `)}
51746
+ out(` ${DIM}└─ result:${RESET}${status} ${indent(trunc(bodyStr, 500), ` ${DIM}│${RESET} `)}
51604
51747
  `);
51605
51748
  }
51606
51749
  }
51607
51750
  }
51608
- process.stderr.write(`
51751
+ out(`
51609
51752
  ` + "─".repeat(60) + `
51610
51753
  `);
51611
- process.stderr.write(`${DIM}Messages: ${shaped.length} | Tool calls: ${toolUseCount} | Tool errors: ${errorCount}${RESET}
51754
+ out(`${DIM}Messages: ${shaped.length} | Tool calls: ${toolUseCount} | Tool errors: ${errorCount}${RESET}
51612
51755
  `);
51613
51756
  }
51614
51757
 
@@ -52616,6 +52759,7 @@ function renderDiff(originalContent, modifiedContent, filePath) {
52616
52759
 
52617
52760
  // src/shutdown.ts
52618
52761
  var activeRun = null;
52762
+ var stopOnExit = false;
52619
52763
  var cleanups = [];
52620
52764
  var exiting = null;
52621
52765
  var cancelling = null;
@@ -52623,6 +52767,9 @@ var CANCEL_TIMEOUT_MS = 5000;
52623
52767
  function setActiveRun(run) {
52624
52768
  activeRun = run;
52625
52769
  }
52770
+ var stopsRunOnExit = () => stopOnExit;
52771
+ var detachNotice = (todoId) => `Todo keeps running — reattach: tfa-cli --resume ${todoId}
52772
+ `;
52626
52773
  function withTimeout(p, ms, fallback) {
52627
52774
  let timer;
52628
52775
  const timeout = new Promise((r) => {
@@ -52634,7 +52781,7 @@ function cancelActiveRun(timeoutMs = CANCEL_TIMEOUT_MS) {
52634
52781
  if (cancelling)
52635
52782
  return cancelling;
52636
52783
  const pending = activeRun;
52637
- if (!pending)
52784
+ if (!pending || !stopOnExit)
52638
52785
  return Promise.resolve({ ok: false, todoId: null });
52639
52786
  const res = { ok: false };
52640
52787
  const attempt = (async () => {
@@ -52660,7 +52807,11 @@ function shutdown(code, reason) {
52660
52807
  process.stderr.write(`
52661
52808
  ${reason}
52662
52809
  `);
52663
- if (activeRun) {
52810
+ if (activeRun && !stopOnExit) {
52811
+ const run = await withTimeout(Promise.resolve(activeRun), CANCEL_TIMEOUT_MS, null);
52812
+ if (run)
52813
+ process.stderr.write(detachNotice(run.todoId));
52814
+ } else if (activeRun) {
52664
52815
  const { ok, todoId } = await cancelActiveRun();
52665
52816
  if (ok)
52666
52817
  process.stderr.write(`Stopped todo ${todoId}
@@ -52772,20 +52923,6 @@ async function watchTodo(ws, todoId, projectId, opts = {}) {
52772
52923
  diffStoreByWs.set(ws, diffStore);
52773
52924
  const diffRendered = new Set;
52774
52925
  let approveAll = !!opts.autoApprove;
52775
- let interruptCount = 0;
52776
- const origHandler = process.listeners("SIGINT").slice();
52777
- process.removeAllListeners("SIGINT");
52778
- process.on("SIGINT", () => {
52779
- interruptCount++;
52780
- if (opts.exitOnInterrupt || interruptCount >= 2)
52781
- return void shutdown(130, "Cancelled by user (Ctrl+C)");
52782
- process.stderr.write(`
52783
- ${YELLOW}Interrupting... (Ctrl+C again to force exit)${RESET}
52784
- `);
52785
- if (opts.interruptOnCancel !== false) {
52786
- ws.sendInterrupt(projectId, todoId);
52787
- }
52788
- });
52789
52926
  const pendingBlocks = [];
52790
52927
  let approvalPromptActive = false;
52791
52928
  let activeApprovalBlocks = [];
@@ -53008,14 +53145,10 @@ ${YELLOW}Connection lost — reconnecting...${RESET}
53008
53145
  await cancelActiveRun();
53009
53146
  process.exitCode = 1;
53010
53147
  if (!opts.suppressCancelNotice) {
53011
- process.stderr.write(`${YELLOW}Interrupted${RESET}
53012
- `);
53148
+ process.stderr.write(stopsRunOnExit() ? `${YELLOW}Interrupted${RESET}
53149
+ ` : detachNotice(todoId));
53013
53150
  }
53014
53151
  return false;
53015
- } finally {
53016
- process.removeAllListeners("SIGINT");
53017
- for (const fn of origHandler)
53018
- process.on("SIGINT", fn);
53019
53152
  }
53020
53153
  }
53021
53154
 
@@ -53584,6 +53717,8 @@ Usage:
53584
53717
  tfa-cli project settings <field=value>… Project settings (PUT /settings/projects/{id})
53585
53718
  tfa-cli project groups List todo groups
53586
53719
  tfa-cli project groups set <slug> <field=value>… Create/update a group: name description pinned archived order
53720
+ tfa-cli project groups intake <slug> [on|off|regenerate] [field=value]…
53721
+ Public intake: autoStart maxSubmissions postIpLimit placeholder agentSettingsId
53587
53722
  tfa-cli project groups reorder <slug>… Set wall order
53588
53723
  tfa-cli project surfaces List the board's card surfaces (live refs shown as cards)
53589
53724
  tfa-cli project surfaces set <id> ref=<ref> [title=…] [group=<slug>] [order=N]
@@ -53748,6 +53883,25 @@ async function projectCommand(api, positionals, args, projectId) {
53748
53883
  `);
53749
53884
  return;
53750
53885
  }
53886
+ if (verb === "intake") {
53887
+ const [slug, ...options] = gargs;
53888
+ if (!slug)
53889
+ fail("Usage: tfa-cli project groups intake <slug> [on|off|regenerate] [field=value]…");
53890
+ const action = ["on", "off", "regenerate"].includes(options[0]) ? options.shift() : undefined;
53891
+ if (options.length && action === "regenerate")
53892
+ fail("regenerate does not accept settings");
53893
+ const fields = parseAssignments2(options);
53894
+ const intake = action === "regenerate" ? await api.regenerateProjectGroupIntake(projectId, slug) : action || options.length ? await api.updateProjectGroupIntake(projectId, slug, { ...fields, ...action && { enabled: action === "on" } }) : await api.getProjectGroupIntake(projectId, slug);
53895
+ if (args.json) {
53896
+ console.log(JSON.stringify(intake, null, 2));
53897
+ return;
53898
+ }
53899
+ process.stderr.write(`${slug}: public ${intake.enabled ? "on" : "off"}, ${intake.submissionCount}/${intake.maxSubmissions}, autoStart=${intake.autoStart}
53900
+ `);
53901
+ if (intake.url)
53902
+ console.log(intake.url);
53903
+ return;
53904
+ }
53751
53905
  if (verb === "reorder") {
53752
53906
  if (!gargs.length)
53753
53907
  fail("Usage: tfa-cli project groups reorder <slug>…");
@@ -53823,6 +53977,11 @@ Usage:
53823
53977
  tfa-cli brand voice correct "<what is off>" Tell the learner what it got wrong; profile is updated
53824
53978
  in one pass and the correction is kept for every re-learn
53825
53979
 
53980
+ Every voice verb takes --scope mine|project — which voice to teach:
53981
+ mine yours, only in this project (default)
53982
+ project the project's, shared with its members (needs write access)
53983
+ On the same voice name the narrower wins: mine > project.
53984
+
53826
53985
 
53827
53986
  Questions the UI asks (keys for 'answers'):
53828
53987
  "What makes you different from competitors?"
@@ -53921,10 +54080,13 @@ async function voiceDeviceCommand(rest, args) {
53921
54080
  }
53922
54081
  async function voiceCommand(api, projectId, rest, args) {
53923
54082
  const [verb, ...vargs] = rest;
54083
+ const scope = args.scope ?? "mine";
54084
+ if (!["project", "mine"].includes(scope))
54085
+ fail(`--scope must be mine or project, got '${scope}'`);
53924
54086
  if (verb === "collect" && !vargs[0])
53925
54087
  fail("Usage: tfa-cli brand voice collect <channel> [--url U | --account <page-id> | --max N] [--dry-run]");
53926
54088
  if (verb === "answers") {
53927
- const answers = manualAnswersOf((await api.listVoiceSources(projectId)).sources);
54089
+ const answers = manualAnswersOf((await api.listVoiceSources(projectId, scope)).sources);
53928
54090
  if (vargs.length) {
53929
54091
  for (const a of vargs) {
53930
54092
  const i = a.indexOf("=");
@@ -53932,7 +54094,7 @@ async function voiceCommand(api, projectId, rest, args) {
53932
54094
  fail(`Expected question=answer, got '${a}'`);
53933
54095
  answers[a.slice(0, i)] = a.slice(i + 1);
53934
54096
  }
53935
- await api.collectVoiceSource(projectId, MANUAL_CHANNEL, { samples: manualSamples(answers) });
54097
+ await api.collectVoiceSource(projectId, MANUAL_CHANNEL, { samples: manualSamples(answers), scope });
53936
54098
  process.stderr.write(`${GREEN}✅ ${vargs.length} answer(s) saved${RESET}
53937
54099
  `);
53938
54100
  return;
@@ -53948,7 +54110,7 @@ async function voiceCommand(api, projectId, rest, args) {
53948
54110
  return;
53949
54111
  }
53950
54112
  if (!verb || verb === "show") {
53951
- const [{ sources, profile }, brand] = await Promise.all([api.listVoiceSources(projectId), api.getBusinessContext(projectId)]);
54113
+ const [{ sources, profile }, brand] = await Promise.all([api.listVoiceSources(projectId, scope), api.getBusinessContext(projectId)]);
53952
54114
  if (args.json) {
53953
54115
  console.log(JSON.stringify({ brand, profile, sources }, null, 2));
53954
54116
  return;
@@ -53979,12 +54141,12 @@ ${DIM}match ${profile.match}/100 · source ${profile.source}${RESET}
53979
54141
  const { samples, note } = collectChannel(channel, Number(args.max ?? 40));
53980
54142
  if (!samples.length)
53981
54143
  fail(`${channel}: nothing usable came back${note ? ` (${note})` : ""}`);
53982
- const { sources: sources2 } = await api.collectVoiceSource(projectId, channel, { samples });
54144
+ const { sources: sources2 } = await api.collectVoiceSource(projectId, channel, { samples, scope });
53983
54145
  process.stderr.write(`${GREEN}✅ ${channel}: ${samples.length} reply pairs stored (${sources2.length} source(s))${RESET}
53984
54146
  `);
53985
54147
  return;
53986
54148
  }
53987
- const { sources } = await api.collectVoiceSource(projectId, channel, { url: args.url, account: args.account });
54149
+ const { sources } = await api.collectVoiceSource(projectId, channel, { url: args.url, account: args.account, scope });
53988
54150
  process.stderr.write(`${GREEN}✅ ${channel} collected (${sources.length} source(s))${RESET}
53989
54151
  `);
53990
54152
  return;
@@ -53992,7 +54154,7 @@ ${DIM}match ${profile.match}/100 · source ${profile.source}${RESET}
53992
54154
  if (verb === "remove") {
53993
54155
  if (!vargs[0])
53994
54156
  fail("Usage: tfa-cli brand voice remove <channel> [--account <id>] (no --account: every source of the channel)");
53995
- await api.removeVoiceSource(projectId, vargs[0], args.account ? { account: args.account } : { all: true });
54157
+ await api.removeVoiceSource(projectId, vargs[0], { ...args.account ? { account: args.account } : { all: true }, scope });
53996
54158
  process.stderr.write(`${GREEN}✅ ${vargs[0]} removed${RESET}
53997
54159
  `);
53998
54160
  return;
@@ -54000,7 +54162,7 @@ ${DIM}match ${profile.match}/100 · source ${profile.source}${RESET}
54000
54162
  if (verb === "learn") {
54001
54163
  process.stderr.write(`${DIM}learning voice…${RESET}
54002
54164
  `);
54003
- const res = await api.refineBrandVoice(projectId, args["from-company"] ? "company" : undefined);
54165
+ const res = await api.refineBrandVoice(projectId, args["from-company"] ? "company" : undefined, undefined, scope);
54004
54166
  if (!res.profile)
54005
54167
  fail(args["from-company"] ? "No brand page to learn from — 'tfa-cli brand create <name>' and fill it first" : "Nothing to learn from — add answers, collect a source, or fill the brand page first");
54006
54168
  if (args.json) {
@@ -54016,11 +54178,11 @@ ${res.profile}
54016
54178
  const text = vargs.join(" ").trim();
54017
54179
  if (!text)
54018
54180
  fail('Usage: tfa-cli brand voice correct "<what is off>"');
54019
- if (!(await api.listVoiceSources(projectId)).profile?.profile)
54181
+ if (!(await api.listVoiceSources(projectId, scope)).profile?.profile)
54020
54182
  fail("No voice learned yet — 'tfa-cli brand voice learn' first");
54021
54183
  process.stderr.write(`${DIM}applying correction…${RESET}
54022
54184
  `);
54023
- const res = await api.refineBrandVoice(projectId, undefined, text);
54185
+ const res = await api.refineBrandVoice(projectId, undefined, text, scope);
54024
54186
  if (!res.profile)
54025
54187
  fail("The correction pass returned nothing — try rewording it");
54026
54188
  if (args.json) {
@@ -54047,6 +54209,7 @@ Usage:
54047
54209
  tfa-cli device access <device> who you shared the device with
54048
54210
  tfa-cli device share <device> <email> let a teammate use it (their runs act as THEM)
54049
54211
  tfa-cli device unshare <device> <email>
54212
+ tfa-cli device leave <device> remove your own access to a shared device
54050
54213
 
54051
54214
  <device> is an id, name or hostname (unique partial works).
54052
54215
  Devices shared with you are marked "shared"; only the owner can rename/share them.
@@ -54083,6 +54246,12 @@ async function deviceCommand(api, positionals, args) {
54083
54246
  fail("Usage: tfa-cli device rename <device> <name>");
54084
54247
  await api.renameDevice(found.id, value);
54085
54248
  process.stderr.write(`${GREEN}✅ ${found.name} → ${value}${RESET}
54249
+ `);
54250
+ return;
54251
+ }
54252
+ if (sub === "leave") {
54253
+ await api.leaveDevice(found.id);
54254
+ process.stderr.write(`${GREEN}✅ Removed ${found.name} from your devices${RESET}
54086
54255
  `);
54087
54256
  return;
54088
54257
  }
@@ -54132,15 +54301,7 @@ var STATUS_COLOR = {
54132
54301
  POSTPONED: CYAN,
54133
54302
  PAUSED: CYAN
54134
54303
  };
54135
- var CLOSED = new Set([
54136
- "DONE",
54137
- "READY_CHECKED",
54138
- "CANCELLED",
54139
- "CANCELLED_CHECKED",
54140
- "ERROR_CHECKED",
54141
- "ARCHIVED",
54142
- "DELETED"
54143
- ]);
54304
+ var CLOSED = new Set(CLOSED_STATUSES);
54144
54305
  var VALID_STATUS = new Set(Object.values(TodoStatus));
54145
54306
  var isOpen = (s) => !CLOSED.has(s);
54146
54307
  function printListTodosHelp() {
@@ -54152,11 +54313,16 @@ Usage:
54152
54313
 
54153
54314
  Flags:
54154
54315
  -n, --limit <n> Max rows to show (default: 30)
54155
- --cursor <n> Fetch todos older than this cursor (lastActivityAt)
54156
- --page-size <n> Backend page size per request (default: min(limit, 100), max: 100)
54316
+ --cursor <n> Fetch todos older than this cursor (sort field, epoch ms)
54317
+ --since <t> Only todos with sort field >= t (inclusive)
54318
+ --until <t> Only todos with sort field < t (exclusive)
54319
+ t = epoch ms | YYYY-MM-DD (local midnight) | ISO datetime | 12h / 3d ago
54320
+ --sort <key> activity (lastActivityAt, default) | created (createdAt)
54321
+ --contributor <u> Only todos a member opened/wrote in (id or email; comma = any)
54322
+ --page-size <n> Backend page size per request (default: min(limit, 400), max: 400)
54157
54323
  --search <text> Search content/status on the backend
54158
54324
  -s, --status <S[,S2]> Filter by status (comma-separated, union).
54159
- -A, --all Include DONE (also CANCELLED, ARCHIVED, …)
54325
+ -A, --all Include closed (DONE, ARCHIVED, DELETED)
54160
54326
  --project <id> Project ID (default: current default project)
54161
54327
  --json Output { items, nextCursor } as JSON
54162
54328
  -h, --help Show this help
@@ -54167,6 +54333,8 @@ Examples:
54167
54333
  todoforai-cli list --cursor 1719234567890 # next page
54168
54334
  todoforai-cli list --all # include DONE
54169
54335
  todoforai-cli list -s RUNNING,REVIEW_REQUESTED
54336
+ todoforai-cli list -A --since 2026-10-11 --until 2026-10-12 # touched that day
54337
+ todoforai-cli list -A --sort created --since 7d --contributor a@b.com
54170
54338
  todoforai-cli list --search bug --json | jq '.items[].id'
54171
54339
  `);
54172
54340
  }
@@ -54192,6 +54360,25 @@ function cursorValue(value) {
54192
54360
  }
54193
54361
  return n;
54194
54362
  }
54363
+ function parseTime(value, name, now = Date.now()) {
54364
+ if (value === undefined)
54365
+ return;
54366
+ const s = String(value).trim();
54367
+ const rel = s.match(/^(\d+)([mhd])$/);
54368
+ let n;
54369
+ if (/^\d+$/.test(s))
54370
+ n = Number(s);
54371
+ else if (rel)
54372
+ n = now - Number(rel[1]) * { m: 60000, h: 3600000, d: 86400000 }[rel[2]];
54373
+ else
54374
+ n = Date.parse(/^\d{4}-\d{2}-\d{2}$/.test(s) ? `${s}T00:00` : s);
54375
+ if (!Number.isFinite(n) || n < 0) {
54376
+ process.stderr.write(`${RED}Error: ${name} must be epoch ms, YYYY-MM-DD, ISO datetime or e.g. 12h / 3d${RESET}
54377
+ `);
54378
+ process.exit(2);
54379
+ }
54380
+ return n;
54381
+ }
54195
54382
  function toItemsPage(response) {
54196
54383
  if (Array.isArray(response))
54197
54384
  return { items: response };
@@ -54200,8 +54387,25 @@ function toItemsPage(response) {
54200
54387
  nextCursor: typeof response?.nextCursor === "number" ? response.nextCursor : undefined
54201
54388
  };
54202
54389
  }
54203
- function rowTime(t) {
54204
- return Number(t.lastActivityAt ?? t.createdAt ?? 0);
54390
+ function rowTime(t, sort = "activity") {
54391
+ return Number((sort === "created" ? t.createdAt : t.lastActivityAt ?? t.createdAt) ?? 0);
54392
+ }
54393
+ async function resolveContributors(api, projectId, raw) {
54394
+ const wanted = raw.split(",").map((s) => s.trim()).filter(Boolean);
54395
+ if (!wanted.some((w) => w.includes("@")))
54396
+ return wanted;
54397
+ const members = await api.getProjectMembers(projectId);
54398
+ return wanted.map((w) => {
54399
+ if (!w.includes("@"))
54400
+ return w;
54401
+ const m = members.find((m2) => m2.email?.toLowerCase() === w.toLowerCase());
54402
+ if (!m) {
54403
+ process.stderr.write(`${RED}Error: no project member with email ${w}${RESET}
54404
+ `);
54405
+ process.exit(2);
54406
+ }
54407
+ return m.id;
54408
+ });
54205
54409
  }
54206
54410
  function printStatusError(unknown) {
54207
54411
  process.stderr.write(`${RED}Error: unknown status ${unknown.map((s) => `'${s}'`).join(", ")}${RESET}
@@ -54224,6 +54428,10 @@ async function listTodosCommand(api, defaultProjectId, argv) {
54224
54428
  cursor: { type: "string" },
54225
54429
  "page-size": { type: "string" },
54226
54430
  search: { type: "string" },
54431
+ since: { type: "string" },
54432
+ until: { type: "string" },
54433
+ sort: { type: "string" },
54434
+ contributor: { type: "string" },
54227
54435
  status: { type: "string", short: "s" },
54228
54436
  all: { type: "boolean", short: "A", default: false },
54229
54437
  project: { type: "string" },
@@ -54243,9 +54451,20 @@ async function listTodosCommand(api, defaultProjectId, argv) {
54243
54451
  process.exit(1);
54244
54452
  }
54245
54453
  const limit = positiveInt(values.limit, "--limit") ?? 30;
54246
- const pageSize = Math.min(positiveInt(values["page-size"], "--page-size") ?? Math.min(Math.max(limit, 1), 100), 100);
54454
+ const pageSize = Math.min(positiveInt(values["page-size"], "--page-size") ?? Math.min(Math.max(limit, 1), 400), 400);
54455
+ const sort = values.sort === undefined ? "activity" : String(values.sort);
54456
+ if (sort !== "activity" && sort !== "created") {
54457
+ process.stderr.write(`${RED}Error: --sort must be activity or created${RESET}
54458
+ `);
54459
+ process.exit(2);
54460
+ }
54461
+ const since = parseTime(values.since, "--since");
54462
+ const until = parseTime(values.until, "--until");
54247
54463
  let cursor = cursorValue(values.cursor);
54464
+ if (until !== undefined)
54465
+ cursor = cursor === undefined ? until : Math.min(cursor, until);
54248
54466
  const search = values.search ? String(values.search) : undefined;
54467
+ const contributorIds = values.contributor ? await resolveContributors(api, projectId, String(values.contributor)) : undefined;
54249
54468
  const requested = values.status ? String(values.status).split(",").map((s) => s.trim().toUpperCase()).filter(Boolean) : null;
54250
54469
  const unknown = requested?.filter((s) => s !== "OPEN" && !VALID_STATUS.has(s)) ?? [];
54251
54470
  if (unknown.length)
@@ -54257,9 +54476,24 @@ async function listTodosCommand(api, defaultProjectId, argv) {
54257
54476
  let backendNextCursor;
54258
54477
  while (rows.length < limit) {
54259
54478
  const requestLimit = Math.min(pageSize, limit - rows.length);
54260
- const page = toItemsPage(await api.listTodos(projectId, { limit: requestLimit, cursor, search }));
54479
+ const page = toItemsPage(await api.listTodos(projectId, {
54480
+ limit: requestLimit,
54481
+ cursor,
54482
+ search,
54483
+ since,
54484
+ contributorIds,
54485
+ ...!includeClosed && { excludeClosed: true },
54486
+ ...sort !== "activity" && { sort }
54487
+ }));
54261
54488
  backendNextCursor = page.nextCursor;
54489
+ let exhausted = false;
54262
54490
  for (const t of page.items) {
54491
+ if (since !== undefined && rowTime(t) < since) {
54492
+ exhausted = true;
54493
+ break;
54494
+ }
54495
+ if (since !== undefined && rowTime(t, sort) < since)
54496
+ continue;
54263
54497
  const status = String(t.status).toUpperCase();
54264
54498
  if (!includeClosed && CLOSED.has(status))
54265
54499
  continue;
@@ -54269,14 +54503,18 @@ async function listTodosCommand(api, defaultProjectId, argv) {
54269
54503
  if (rows.length >= limit)
54270
54504
  break;
54271
54505
  }
54506
+ if (exhausted) {
54507
+ backendNextCursor = undefined;
54508
+ break;
54509
+ }
54272
54510
  if (!backendNextCursor || page.items.length === 0)
54273
54511
  break;
54274
54512
  cursor = backendNextCursor;
54275
54513
  }
54276
- rows.sort((a, b) => rowTime(b) - rowTime(a));
54514
+ rows.sort((a, b) => rowTime(b, sort) - rowTime(a, sort));
54277
54515
  const visible = rows.slice(0, limit);
54278
54516
  const hasBufferedRows = rows.length > visible.length;
54279
- const nextCursor = visible.length && (backendNextCursor !== undefined || hasBufferedRows) ? rowTime(visible[visible.length - 1]) : undefined;
54517
+ const nextCursor = visible.length && (backendNextCursor !== undefined || hasBufferedRows) ? rowTime(visible[visible.length - 1], sort) : undefined;
54280
54518
  if (values.json) {
54281
54519
  process.stdout.write(JSON.stringify({ items: visible, nextCursor }, null, 2) + `
54282
54520
  `);
@@ -54294,7 +54532,7 @@ async function listTodosCommand(api, defaultProjectId, argv) {
54294
54532
  for (const t of visible) {
54295
54533
  const status = String(t.status).toUpperCase();
54296
54534
  const sc = STATUS_COLOR[status] || DIM;
54297
- const ts = new Date(rowTime(t)).toISOString().slice(0, 16).replace("T", " ");
54535
+ const ts = new Date(rowTime(t, sort)).toISOString().slice(0, 16).replace("T", " ");
54298
54536
  const title = String(t.content ?? "").split(`
54299
54537
  `)[0].slice(0, 100);
54300
54538
  process.stdout.write(`${sc}${status.padEnd(statusW)}${RESET} ${DIM}${ts}${RESET} ${t.id} ${title}
@@ -55182,21 +55420,26 @@ tfa-cli memory — memory and context folders as git checkouts
55182
55420
 
55183
55421
  Usage:
55184
55422
  tfa-cli memory sync [--project <id>] Clone, or pull, into:
55185
- ~/.todoforai/{memory,context} yours (todoforai:memory, todoforai:context)
55186
- ~/.todoforai/projects/<id>/{memory,context} the project's (todoforai:projects/<id>/…)
55423
+ ~/.todoforai/projects/<id>/{memory,context} the project's (todoforai:projects/<id>/…)
55424
+ ~/.todoforai/projects/<id>/me/{memory,context} yours in it (todoforai:projects/<id>/me/…)
55187
55425
  tfa-cli memory path [--project <id>] Print the checkout paths
55188
55426
 
55189
55427
  Every change to these folders is committed on the server, from any client. In a checkout
55190
55428
  use plain git: log, diff, show, revert, commit, then \`git push\` — the push updates the live
55191
55429
  folder. A push behind the server is refused: \`git pull\` first. Files over 5 MB live outside
55192
- history (upload them instead). Project folders: members with write access push. The project
55193
- defaults to $TODOFORAI_PROJECT_ID.
55430
+ history (upload them instead). Project folders: members with write access push; your own in
55431
+ a project (me/) is private to you. The project
55432
+ defaults to $TODOFORAI_PROJECT_ID and is required. Legacy global folders are read-only
55433
+ recovery, not synced.
55194
55434
  `);
55195
55435
  }
55196
55436
  var FOLDERS = ["memory", "context"];
55197
55437
  function checkouts(projectId) {
55198
55438
  const root = join5(homedir5(), ".todoforai");
55199
- const scopes = [[root, ""], ...projectId ? [[join5(root, "projects", projectId), `projects/${projectId}/`]] : []];
55439
+ if (!projectId)
55440
+ throw new Error("Select a project with --project <id> or $TODOFORAI_PROJECT_ID.");
55441
+ const project = join5(root, "projects", projectId);
55442
+ const scopes = [[project, `projects/${projectId}/`], [join5(project, "me"), `projects/${projectId}/me/`]];
55200
55443
  return scopes.flatMap(([dir, remote]) => FOLDERS.map((f) => [join5(dir, f), `${remote}${f}.git`]));
55201
55444
  }
55202
55445
  function git(args, cwd) {
@@ -56157,6 +56400,7 @@ __export(exports_util, {
56157
56400
  toZod: () => toZod,
56158
56401
  stringifyPrimitive: () => stringifyPrimitive,
56159
56402
  slugify: () => slugify,
56403
+ sizeCaps: () => sizeCaps,
56160
56404
  shallowClone: () => shallowClone,
56161
56405
  safeExtend: () => safeExtend,
56162
56406
  required: () => required,
@@ -56399,14 +56643,14 @@ function promiseAllObject(promisesObj) {
56399
56643
  }
56400
56644
  function randomString(length = 10) {
56401
56645
  const chars = "abcdefghijklmnopqrstuvwxyz";
56402
- let str = "";
56646
+ let str2 = "";
56403
56647
  for (let i = 0;i < length; i++) {
56404
- str += chars[Math.floor(Math.random() * chars.length)];
56648
+ str2 += chars[Math.floor(Math.random() * chars.length)];
56405
56649
  }
56406
- return str;
56650
+ return str2;
56407
56651
  }
56408
- function esc(str) {
56409
- return JSON.stringify(str);
56652
+ function esc(str2) {
56653
+ return JSON.stringify(str2);
56410
56654
  }
56411
56655
  function slugify(input) {
56412
56656
  return input.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
@@ -56519,8 +56763,8 @@ var primitiveTypes = /* @__PURE__ */ new Set([
56519
56763
  "symbol",
56520
56764
  "undefined"
56521
56765
  ]);
56522
- function escapeRegex(str) {
56523
- return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
56766
+ function escapeRegex(str2) {
56767
+ return str2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
56524
56768
  }
56525
56769
  function clone(inst, def, params) {
56526
56770
  const cl = new inst._zod.constr(def ?? inst._zod.def);
@@ -56731,6 +56975,18 @@ function prefixIssues(path4, issues) {
56731
56975
  function unwrapMessage(message) {
56732
56976
  return typeof message === "string" ? message : message?.message;
56733
56977
  }
56978
+ function sizeCaps(defChecks) {
56979
+ const caps = [];
56980
+ for (const ch of defChecks ?? []) {
56981
+ const d = ch._zod.def;
56982
+ if (!/_(length|size)$|_equals$/.test(d.check))
56983
+ break;
56984
+ const max = d.maximum ?? d.length ?? d.size;
56985
+ if (max >= 0)
56986
+ caps.push([max, ch]);
56987
+ }
56988
+ return caps;
56989
+ }
56734
56990
  function attachSchema(issues, start, inst) {
56735
56991
  var _a;
56736
56992
  for (let i = start;i < issues.length; i++) {
@@ -56771,13 +57027,13 @@ function getSizableOrigin(input) {
56771
57027
  return "unknown";
56772
57028
  }
56773
57029
  var highSurrogate = /[\uD800-\uDBFF]/;
56774
- function codePointLength(str) {
56775
- const units = str.length;
56776
- if (!highSurrogate.test(str))
57030
+ function codePointLength(str2) {
57031
+ const units = str2.length;
57032
+ if (!highSurrogate.test(str2))
56777
57033
  return units;
56778
57034
  let count = units;
56779
57035
  for (let i = 0;i < units - 1; i++) {
56780
- if ((str.charCodeAt(i) & 64512) === 55296 && (str.charCodeAt(i + 1) & 64512) === 56320) {
57036
+ if ((str2.charCodeAt(i) & 64512) === 55296 && (str2.charCodeAt(i + 1) & 64512) === 56320) {
56781
57037
  count--;
56782
57038
  i++;
56783
57039
  }
@@ -58150,7 +58406,7 @@ ${content.join(`
58150
58406
  var version = {
58151
58407
  major: 4,
58152
58408
  minor: 6,
58153
- patch: 5
58409
+ patch: 7
58154
58410
  };
58155
58411
 
58156
58412
  // node_modules/zod/v4/core/schemas.js
@@ -58899,6 +59155,31 @@ var $ZodDate = /* @__PURE__ */ $constructor("$ZodDate", (inst, def) => {
58899
59155
  return payload;
58900
59156
  };
58901
59157
  });
59158
+ function capSize(inst, size) {
59159
+ const caps = sizeCaps(inst._zod.def.checks);
59160
+ if (!caps.length)
59161
+ return;
59162
+ const cap = Math.min(...caps.map((c3) => c3[0]));
59163
+ const parse2 = inst._zod.parse;
59164
+ inst._zod.parse = (payload, ctx) => {
59165
+ const n = size(payload.value);
59166
+ if (n > cap && !ctx?.skipChecks && ctx?.direction !== "backward") {
59167
+ for (const [max, ch] of sizeCaps(inst._zod.def.checks)) {
59168
+ if (n > max && ch._zod.def.when(payload)) {
59169
+ const start = payload.issues.length;
59170
+ ch._zod.check(payload);
59171
+ payload.issues[start].continue = false;
59172
+ attachSchema(payload.issues, start, inst);
59173
+ return payload;
59174
+ }
59175
+ }
59176
+ }
59177
+ return parse2(payload, ctx);
59178
+ };
59179
+ }
59180
+ var arraySize = (input) => Array.isArray(input) ? input.length : -1;
59181
+ var mapSize = (input) => input instanceof Map ? input.size : -1;
59182
+ var setSize = (input) => input instanceof Set ? input.size : -1;
58902
59183
  function handleArrayResult(result, final, index) {
58903
59184
  if (result.issues.length) {
58904
59185
  final.issues.push(...prefixIssues(index, result.issues));
@@ -58942,6 +59223,7 @@ var $ZodArray = /* @__PURE__ */ $constructor("$ZodArray", (inst, def) => {
58942
59223
  }
58943
59224
  return payload;
58944
59225
  };
59226
+ capSize(inst, arraySize);
58945
59227
  });
58946
59228
  function handlePropertyResult(result, final, key, input, optin, optout) {
58947
59229
  const isPresent = key in input;
@@ -59896,6 +60178,7 @@ var $ZodMap = /* @__PURE__ */ $constructor("$ZodMap", (inst, def) => {
59896
60178
  return Promise.all(proms).then(() => payload);
59897
60179
  return payload;
59898
60180
  };
60181
+ capSize(inst, mapSize);
59899
60182
  });
59900
60183
  function handleMapResult(keyResult, valueResult, final, key, input, inst, ctx) {
59901
60184
  if (keyResult.issues.length) {
@@ -59962,6 +60245,7 @@ var $ZodSet = /* @__PURE__ */ $constructor("$ZodSet", (inst, def) => {
59962
60245
  return Promise.all(proms).then(() => payload);
59963
60246
  return payload;
59964
60247
  };
60248
+ capSize(inst, setSize);
59965
60249
  });
59966
60250
  function handleSetResult(result, final) {
59967
60251
  if (result.issues.length) {
@@ -69411,9 +69695,15 @@ function generateNullableCheck(doc, ctx, schema, accessor, buildsValue = true) {
69411
69695
  doc.write(`}`);
69412
69696
  return outputVar;
69413
69697
  }
69698
+ function generateSizeGate(doc, schema, size) {
69699
+ for (const [max, check] of sizeCaps(schema._zod.def.checks)) {
69700
+ doc.write(`if (${size} > ${numericOperand(max, check._zod.def.check)}) return INVALID;`);
69701
+ }
69702
+ }
69414
69703
  function generateArrayCheck(doc, ctx, schema, accessor, buildsValue = true) {
69415
69704
  const def = schema._zod.def;
69416
69705
  doc.write(`if (!Array.isArray(${accessor})) return INVALID;`);
69706
+ generateSizeGate(doc, schema, `${accessor}.length`);
69417
69707
  const outputVar = buildsValue ? newVar(ctx) : null;
69418
69708
  const iVar = newVar(ctx);
69419
69709
  const elemVar = newVar(ctx);
@@ -69813,6 +70103,7 @@ function literalPropertyKey(ctx, key) {
69813
70103
  function generateMapCheck(doc, ctx, schema, accessor) {
69814
70104
  const def = schema._zod.def;
69815
70105
  doc.write(`if (!(${accessor} instanceof Map)) return INVALID;`);
70106
+ generateSizeGate(doc, schema, `${accessor}.size`);
69816
70107
  const outputVar = newVar(ctx);
69817
70108
  const kVar = newVar(ctx);
69818
70109
  const valVar = newVar(ctx);
@@ -69829,6 +70120,7 @@ function generateMapCheck(doc, ctx, schema, accessor) {
69829
70120
  function generateSetCheck(doc, ctx, schema, accessor) {
69830
70121
  const def = schema._zod.def;
69831
70122
  doc.write(`if (!(${accessor} instanceof Set)) return INVALID;`);
70123
+ generateSizeGate(doc, schema, `${accessor}.size`);
69832
70124
  const outputVar = newVar(ctx);
69833
70125
  const valVar = newVar(ctx);
69834
70126
  doc.write(`const ${outputVar} = new Set();`);
@@ -74996,6 +75288,21 @@ function preserveCustomPayload(schema, key, knownTags) {
74996
75288
  return output2;
74997
75289
  });
74998
75290
  }
75291
+ function defaultOnErrorOptionalStringTag(schema, key) {
75292
+ return exports_external.preprocess((value) => {
75293
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
75294
+ return value;
75295
+ }
75296
+ const record2 = value;
75297
+ const tag = record2[key];
75298
+ if (tag === undefined || tag === null || typeof tag === "string") {
75299
+ return value;
75300
+ }
75301
+ const normalized = { ...record2 };
75302
+ delete normalized[key];
75303
+ return normalized;
75304
+ }, schema);
75305
+ }
74999
75306
  function vecSkipError(itemSchema) {
75000
75307
  return exports_external.array(itemSchema.catch(skippedItem)).transform((items) => items.filter((item) => item !== skippedItem));
75001
75308
  }
@@ -75014,16 +75321,12 @@ var zWriteTextFileRequest = object({
75014
75321
  var zReadTextFileRequest = object({
75015
75322
  sessionId: zSessionId,
75016
75323
  path: string2(),
75017
- line: defaultOnError(int().gte(0).max(4294967295, {
75324
+ line: int().gte(0).max(4294967295, {
75018
75325
  error: "Invalid value: Expected uint32 to be <= 4294967295"
75019
- }).nullish(), () => {
75020
- return;
75021
- }),
75022
- limit: defaultOnError(int().gte(0).max(4294967295, {
75326
+ }).nullish(),
75327
+ limit: int().gte(0).max(4294967295, {
75023
75328
  error: "Invalid value: Expected uint32 to be <= 4294967295"
75024
- }).nullish(), () => {
75025
- return;
75026
- }),
75329
+ }).nullish(),
75027
75330
  _meta: defaultOnError(record(string2(), unknown()).nullish(), () => {
75028
75331
  return;
75029
75332
  })
@@ -75274,14 +75577,10 @@ var zEnvVariable = object({
75274
75577
  var zCreateTerminalRequest = object({
75275
75578
  sessionId: zSessionId,
75276
75579
  command: string2(),
75277
- args: defaultOnError(vecSkipError(string2()).optional(), () => []),
75278
- env: defaultOnError(vecSkipError(zEnvVariable).optional(), () => []),
75279
- cwd: defaultOnError(string2().nullish(), () => {
75280
- return;
75281
- }),
75282
- outputByteLimit: defaultOnError(number2().nullish(), () => {
75283
- return;
75284
- }),
75580
+ args: array(string2()).optional(),
75581
+ env: array(zEnvVariable).optional(),
75582
+ cwd: string2().nullish(),
75583
+ outputByteLimit: number2().nullish(),
75285
75584
  _meta: defaultOnError(record(string2(), unknown()).nullish(), () => {
75286
75585
  return;
75287
75586
  })
@@ -75807,10 +76106,8 @@ var zAuthMethodTerminal = object({
75807
76106
  description: defaultOnError(string2().nullish(), () => {
75808
76107
  return;
75809
76108
  }),
75810
- args: defaultOnError(vecSkipError(string2()).optional(), () => []),
75811
- env: defaultOnError(record(string2(), string2()).optional(), () => {
75812
- return;
75813
- }),
76109
+ args: array(string2()).optional(),
76110
+ env: record(string2(), string2()).optional(),
75814
76111
  _meta: defaultOnError(record(string2(), unknown()).nullish(), () => {
75815
76112
  return;
75816
76113
  })
@@ -76508,17 +76805,61 @@ var zRunningStateUpdate = object({
76508
76805
  return;
76509
76806
  })
76510
76807
  });
76511
- var zIdleStateUpdate = object({
76512
- stopReason: defaultOnError(zStopReason.nullish(), () => {
76808
+ var zErrorStopReason = object({
76809
+ error: defaultOnError(zError.nullish(), () => {
76513
76810
  return;
76811
+ })
76812
+ });
76813
+ var zIdleStateUpdate = defaultOnErrorOptionalStringTag(preserveCustomPayload(intersection(union([
76814
+ object({
76815
+ stopReason: literal("end_turn")
76816
+ }),
76817
+ object({
76818
+ stopReason: literal("max_tokens")
76819
+ }),
76820
+ object({
76821
+ stopReason: literal("max_turn_requests")
76514
76822
  }),
76823
+ object({
76824
+ stopReason: literal("refusal")
76825
+ }),
76826
+ object({
76827
+ stopReason: literal("cancelled")
76828
+ }),
76829
+ zErrorStopReason.and(object({
76830
+ stopReason: literal("error")
76831
+ })),
76832
+ excludeKnownTags(object({
76833
+ stopReason: string2()
76834
+ }), "stopReason", [
76835
+ "cancelled",
76836
+ "end_turn",
76837
+ "error",
76838
+ "max_tokens",
76839
+ "max_turn_requests",
76840
+ "refusal"
76841
+ ]),
76842
+ object({
76843
+ stopReason: _null3().optional()
76844
+ })
76845
+ ]), object({
76515
76846
  usage: defaultOnError(zUsage.nullish(), () => {
76516
76847
  return;
76517
76848
  }),
76518
76849
  _meta: defaultOnError(record(string2(), unknown()).nullish(), () => {
76519
76850
  return;
76851
+ }),
76852
+ stopReason: defaultOnError(string2().nullish(), () => {
76853
+ return;
76520
76854
  })
76521
- });
76855
+ })), "stopReason", [
76856
+ "cancelled",
76857
+ "end_turn",
76858
+ "error",
76859
+ "max_tokens",
76860
+ "max_turn_requests",
76861
+ "refusal"
76862
+ ]), "stopReason");
76522
76863
  var zRequiresActionStateUpdate = object({
76523
76864
  _meta: defaultOnError(record(string2(), unknown()).nullish(), () => {
76524
76865
  return;
@@ -76901,16 +77242,16 @@ var zMcpServer = union([
76901
77242
  ]);
76902
77243
  var zNewSessionRequest = object({
76903
77244
  cwd: string2(),
76904
- additionalDirectories: defaultOnError(vecSkipError(string2()).optional(), () => []),
76905
- mcpServers: requiredDefaultOnError(vecSkipError(zMcpServer), () => []),
77245
+ additionalDirectories: array(string2()).optional(),
77246
+ mcpServers: array(zMcpServer),
76906
77247
  _meta: defaultOnError(record(string2(), unknown()).nullish(), () => {
76907
77248
  return;
76908
77249
  })
76909
77250
  });
76910
77251
  var zLoadSessionRequest = object({
76911
- mcpServers: requiredDefaultOnError(vecSkipError(zMcpServer), () => []),
77252
+ mcpServers: array(zMcpServer),
76912
77253
  cwd: string2(),
76913
- additionalDirectories: defaultOnError(vecSkipError(string2()).optional(), () => []),
77254
+ additionalDirectories: array(string2()).optional(),
76914
77255
  sessionId: zSessionId,
76915
77256
  _meta: defaultOnError(record(string2(), unknown()).nullish(), () => {
76916
77257
  return;
@@ -76932,8 +77273,8 @@ var zDeleteSessionRequest = object({
76932
77273
  var zForkSessionRequest = object({
76933
77274
  sessionId: zSessionId,
76934
77275
  cwd: string2(),
76935
- additionalDirectories: defaultOnError(vecSkipError(string2()).optional(), () => []),
76936
- mcpServers: defaultOnError(vecSkipError(zMcpServer).optional(), () => []),
77276
+ additionalDirectories: array(string2()).optional(),
77277
+ mcpServers: array(zMcpServer).optional(),
76937
77278
  _meta: defaultOnError(record(string2(), unknown()).nullish(), () => {
76938
77279
  return;
76939
77280
  })
@@ -76941,8 +77282,8 @@ var zForkSessionRequest = object({
76941
77282
  var zResumeSessionRequest = object({
76942
77283
  sessionId: zSessionId,
76943
77284
  cwd: string2(),
76944
- additionalDirectories: defaultOnError(vecSkipError(string2()).optional(), () => []),
76945
- mcpServers: defaultOnError(vecSkipError(zMcpServer).optional(), () => []),
77285
+ additionalDirectories: array(string2()).optional(),
77286
+ mcpServers: array(zMcpServer).optional(),
76946
77287
  _meta: defaultOnError(record(string2(), unknown()).nullish(), () => {
76947
77288
  return;
76948
77289
  })
@@ -77323,7 +77664,7 @@ var zDidChangeDocumentNotification = object({
77323
77664
  sessionId: zSessionId,
77324
77665
  uri: string2(),
77325
77666
  version: number2(),
77326
- contentChanges: requiredDefaultOnError(vecSkipError(zTextDocumentContentChangeEvent), () => []),
77667
+ contentChanges: array(zTextDocumentContentChangeEvent),
77327
77668
  _meta: defaultOnError(record(string2(), unknown()).nullish(), () => {
77328
77669
  return;
77329
77670
  })
@@ -78402,9 +78743,26 @@ var zGuardElicitationPropertySchemaBoolean = zBooleanPropertySchema.and(object({
78402
78743
  var zGuardElicitationPropertySchemaArray = zMultiSelectPropertySchema.and(object({ type: literal("array") }));
78403
78744
  var zGuardMultiSelectItemsString = zStringMultiSelectItems.and(object({ type: literal("string") }));
78404
78745
  var zGuardStateUpdateRunning = zRunningStateUpdate.and(object({ state: literal("running") }));
78405
- var zGuardStateUpdateIdle = zIdleStateUpdate.and(object({ state: literal("idle") }));
78746
+ var zGuardStateUpdateIdle = zIdleStateUpdate.and(object({ usage: zUsage.nullish() })).and(object({ state: literal("idle") }));
78406
78747
  var zGuardStateUpdateRequiresAction = zRequiresActionStateUpdate.and(object({ state: literal("requires_action") }));
78407
78748
  var zGuardStateUpdateUnknown = zUnknownStateUpdate.and(object({ state: literal("unknown") }));
78749
+ var zGuardIdleStateUpdateEndTurn = object({
78750
+ stopReason: literal("end_turn")
78751
+ });
78752
+ var zGuardIdleStateUpdateMaxTokens = object({
78753
+ stopReason: literal("max_tokens")
78754
+ });
78755
+ var zGuardIdleStateUpdateMaxTurnRequests = object({
78756
+ stopReason: literal("max_turn_requests")
78757
+ });
78758
+ var zGuardIdleStateUpdateRefusal = object({
78759
+ stopReason: literal("refusal")
78760
+ });
78761
+ var zGuardIdleStateUpdateCancelled = object({
78762
+ stopReason: literal("cancelled")
78763
+ });
78764
+ var zGuardIdleStateUpdateError = zErrorStopReason.and(object({ error: zError.nullish() })).and(object({ stopReason: literal("error") }));
78765
+ var zGuardIdleStateUpdateNone = object({ stopReason: _null3().optional() });
78408
78766
  var zGuardCreateElicitationResponseAccept = zElicitationAcceptAction.and(object({ action: literal("accept") }));
78409
78767
  var zGuardCreateElicitationResponseDecline = object({
78410
78768
  action: literal("decline")
@@ -79763,6 +80121,7 @@ async function runAcp(apiUrl, apiKey, opts) {
79763
80121
 
79764
80122
  // src/shutdown.ts
79765
80123
  var activeRun2 = null;
80124
+ var stopOnExit2 = false;
79766
80125
  var cleanups2 = [];
79767
80126
  var exiting2 = null;
79768
80127
  var cancelling2 = null;
@@ -79770,6 +80129,11 @@ var CANCEL_TIMEOUT_MS2 = 5000;
79770
80129
  function setActiveRun2(run2) {
79771
80130
  activeRun2 = run2;
79772
80131
  }
80132
+ function stopRunOnExit() {
80133
+ stopOnExit2 = true;
80134
+ }
80135
+ var detachNotice2 = (todoId) => `Todo keeps running — reattach: tfa-cli --resume ${todoId}
80136
+ `;
79773
80137
  function trackStart(ws, projectId, started) {
79774
80138
  setActiveRun2(started.then((todoId) => ({ ws, projectId, todoId }), () => null));
79775
80139
  }
@@ -79787,7 +80151,7 @@ function cancelActiveRun2(timeoutMs = CANCEL_TIMEOUT_MS2) {
79787
80151
  if (cancelling2)
79788
80152
  return cancelling2;
79789
80153
  const pending = activeRun2;
79790
- if (!pending)
80154
+ if (!pending || !stopOnExit2)
79791
80155
  return Promise.resolve({ ok: false, todoId: null });
79792
80156
  const res = { ok: false };
79793
80157
  const attempt = (async () => {
@@ -79813,7 +80177,11 @@ function shutdown2(code, reason) {
79813
80177
  process.stderr.write(`
79814
80178
  ${reason}
79815
80179
  `);
79816
- if (activeRun2) {
80180
+ if (activeRun2 && !stopOnExit2) {
80181
+ const run2 = await withTimeout2(Promise.resolve(activeRun2), CANCEL_TIMEOUT_MS2, null);
80182
+ if (run2)
80183
+ process.stderr.write(detachNotice2(run2.todoId));
80184
+ } else if (activeRun2) {
79817
80185
  const { ok, todoId } = await cancelActiveRun2();
79818
80186
  if (ok)
79819
80187
  process.stderr.write(`Stopped todo ${todoId}
@@ -79843,7 +80211,7 @@ function finish(code) {
79843
80211
  });
79844
80212
  }
79845
80213
  function installSignalHandlers() {
79846
- process.on("SIGINT", () => void shutdown2(130, "Cancelled by user (Ctrl+C)"));
80214
+ process.on("SIGINT", () => void shutdown2(130, "Exited (Ctrl+C)"));
79847
80215
  process.on("SIGTERM", () => void shutdown2(143, "Terminated (SIGTERM)"));
79848
80216
  process.on("SIGHUP", () => void shutdown2(129, "Hangup (SIGHUP)"));
79849
80217
  }
@@ -80266,17 +80634,17 @@ async function main() {
80266
80634
  const [, filePath, todoArg] = positionals;
80267
80635
  const todoId = todoArg || aliasTodoId(args.alias) || getEnv("TODO_ID") || cfgScope.data.last_todo_id;
80268
80636
  if (!filePath || !todoId) {
80269
- process.stderr.write(`${RED3}Usage: tfa-cli show <file|-> [todo-id] [--title T] [--alias A] [--mime M] [--card <name>] [--link]${RESET2}
80637
+ process.stderr.write(`${RED3}Usage: tfa-cli show <file|-> [todo-id] [--title T] [--alias A] [--mime M] [--card <name>] [--link] [--force]${RESET2}
80270
80638
  `);
80271
80639
  process.exit(2);
80272
80640
  }
80273
- let blob, name;
80641
+ let bytes, name, mime;
80274
80642
  if (filePath === "-") {
80275
80643
  const chunks = [];
80276
80644
  for await (const c3 of process.stdin)
80277
80645
  chunks.push(c3);
80278
- blob = new Blob([Buffer.concat(chunks)]);
80279
- if (blob.size === 0) {
80646
+ bytes = Buffer.concat(chunks);
80647
+ if (bytes.length === 0) {
80280
80648
  process.stderr.write(`${RED3}No data on stdin${RESET2}
80281
80649
  `);
80282
80650
  process.exit(1);
@@ -80289,9 +80657,21 @@ async function main() {
80289
80657
  `);
80290
80658
  process.exit(1);
80291
80659
  }
80292
- blob = new Blob([readFileSync5(abs)], { type: getMimeTypeFromFilename(abs) || undefined });
80660
+ bytes = readFileSync5(abs);
80661
+ mime = getMimeTypeFromFilename(abs) || undefined;
80293
80662
  name = path4.basename(filePath);
80294
80663
  }
80664
+ const truncated = truncatedImageReason(bytes);
80665
+ if (truncated) {
80666
+ if (!args.force) {
80667
+ process.stderr.write(`${RED3}${name} looks truncated: ${truncated} (${bytes.length} bytes). Re-export it, or pass --force to show it anyway.${RESET2}
80668
+ `);
80669
+ process.exit(1);
80670
+ }
80671
+ process.stderr.write(`${YELLOW2}\u26A0 ${name} looks truncated: ${truncated} \u2014 showing anyway (--force)${RESET2}
80672
+ `);
80673
+ }
80674
+ const blob = new Blob([bytes], { type: mime });
80295
80675
  const res = await api2.showFile(todoId, blob, name, {
80296
80676
  title: args.title,
80297
80677
  alias: args.alias,
@@ -80494,7 +80874,7 @@ async function main() {
80494
80874
  }
80495
80875
  const mode = args.debug ? "debug" : args.detailed ? "detailed" : "default";
80496
80876
  const format = args["format-anthropic"] ? "anthropic" : "compact";
80497
- printFullChat(todo2, getFrontendUrl(apiUrl, todoId), slice, mode, format);
80877
+ printFullChat(todo2, getFrontendUrl(apiUrl, todoId), slice, mode, format, args.full);
80498
80878
  return;
80499
80879
  }
80500
80880
  if (process.stderr.isTTY)
@@ -80551,8 +80931,7 @@ async function main() {
80551
80931
  await watchTodo(ws2, todoId, projectId2, {
80552
80932
  json: !!args.json,
80553
80933
  autoApprove,
80554
- agentSettings: agent3,
80555
- exitOnInterrupt: !!args["non-interactive"]
80934
+ agentSettings: agent3
80556
80935
  });
80557
80936
  if (!args["non-interactive"]) {
80558
80937
  process.stderr.write(`
@@ -80603,7 +80982,7 @@ Resumed: ${CYAN2}${getFrontendUrl(apiUrl, todoId)}${RESET2}
80603
80982
  cfg.addToHistory(followUp);
80604
80983
  await appendTracked(api2, ws2, projectId2, followUp, agent3, todoId);
80605
80984
  await linkToSpawningBlock(api2, todoId);
80606
- await watchTodo(ws2, todoId, projectId2, { json: !!args.json, autoApprove, agentSettings: agent3, exitOnInterrupt: !!args["non-interactive"] });
80985
+ await watchTodo(ws2, todoId, projectId2, { json: !!args.json, autoApprove, agentSettings: agent3 });
80607
80986
  }
80608
80987
  if (!args["non-interactive"]) {
80609
80988
  await interactiveLoop(ws2, api2, todoId, projectId2, agent3, !!args.json, autoApprove, cfg);
@@ -80625,6 +81004,7 @@ Resumed: ${CYAN2}${getFrontendUrl(apiUrl, todoId)}${RESET2}
80625
81004
  if (mayfly) {
80626
81005
  process.stderr.write(`${DIM2}Isolated bridge:${RESET2} ${CYAN2}mayfly-${isolatedTodoId}${RESET2}
80627
81006
  `);
81007
+ stopRunOnExit();
80628
81008
  onShutdown(mayfly.stop);
80629
81009
  mayfly.child.on("exit", (code, sig) => {
80630
81010
  if (!mayfly.stopped())
@@ -80764,8 +81144,7 @@ Resumed: ${CYAN2}${getFrontendUrl(apiUrl, todoId)}${RESET2}
80764
81144
  await watchTodo(ws, actualTodoId, projectId, {
80765
81145
  json: !!args.json,
80766
81146
  autoApprove,
80767
- agentSettings: agent2,
80768
- exitOnInterrupt: !!args["non-interactive"]
81147
+ agentSettings: agent2
80769
81148
  });
80770
81149
  if (!args["non-interactive"]) {
80771
81150
  process.stderr.write(`