@todoforai/cli 0.1.31 → 0.1.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/todoforai-cli.js +870 -137
  2. package/package.json +2 -1
@@ -42136,7 +42136,7 @@ var require_supports_color = __commonJS((exports, module) => {
42136
42136
  };
42137
42137
  });
42138
42138
 
42139
- // node_modules/chalk/source/util.js
42139
+ // node_modules/cli-highlight/node_modules/chalk/source/util.js
42140
42140
  var require_util = __commonJS((exports, module) => {
42141
42141
  var stringReplaceAll = (string, substring, replacer) => {
42142
42142
  let index = string.indexOf(substring);
@@ -42175,7 +42175,7 @@ var require_util = __commonJS((exports, module) => {
42175
42175
  };
42176
42176
  });
42177
42177
 
42178
- // node_modules/chalk/source/templates.js
42178
+ // node_modules/cli-highlight/node_modules/chalk/source/templates.js
42179
42179
  var require_templates = __commonJS((exports, module) => {
42180
42180
  var TEMPLATE_REGEX = /(?:\\(u(?:[a-f\d]{4}|\{[a-f\d]{1,6}\})|x[a-f\d]{2}|.))|(?:\{(~)?(\w+(?:\([^)]*\))?(?:\.\w+(?:\([^)]*\))?)*)(?:[ \t]|(?=\r?\n)))|(\})|((?:.|[\r\n\f])+?)/gi;
42181
42181
  var STYLE_REGEX = /(?:^|\.)(\w+)(?:\(([^)]*)\))?/g;
@@ -42287,7 +42287,7 @@ var require_templates = __commonJS((exports, module) => {
42287
42287
  };
42288
42288
  });
42289
42289
 
42290
- // node_modules/chalk/source/index.js
42290
+ // node_modules/cli-highlight/node_modules/chalk/source/index.js
42291
42291
  var require_source = __commonJS((exports, module) => {
42292
42292
  var ansiStyles = require_ansi_styles();
42293
42293
  var { stdout: stdoutColor, stderr: stderrColor } = require_supports_color();
@@ -42744,7 +42744,14 @@ class ApiClient {
42744
42744
  this.apiKey = apiKey;
42745
42745
  }
42746
42746
  get headers() {
42747
- return { "content-type": "application/json", "x-api-key": this.apiKey };
42747
+ const h = { "content-type": "application/json", "x-api-key": this.apiKey };
42748
+ const extra = typeof process !== "undefined" && process.env?.TODOFORAI_EXTRA_HEADERS;
42749
+ if (extra) {
42750
+ try {
42751
+ Object.assign(h, JSON.parse(extra));
42752
+ } catch {}
42753
+ }
42754
+ return h;
42748
42755
  }
42749
42756
  async request(method, endpoint, body) {
42750
42757
  const url = `${this.apiUrl}${endpoint.replace(/^\/api\/v1/, restBasePath(this.apiKey))}`;
@@ -42857,12 +42864,27 @@ class ApiClient {
42857
42864
  payload.todoId = todoId;
42858
42865
  if (scheduledTimestamp)
42859
42866
  payload.scheduledTimestamp = scheduledTimestamp;
42860
- if (!todoId && groupTag)
42867
+ if (groupTag)
42861
42868
  payload.groupTag = groupTag;
42862
- if (!todoId && groupName)
42869
+ if (groupName)
42863
42870
  payload.groupName = groupName;
42864
42871
  return this.request("POST", `/api/v1/projects/${projectId}/todos`, payload);
42865
42872
  }
42873
+ async registerAttachment(file, filename, opts = {}) {
42874
+ const form = new FormData;
42875
+ form.append("file", file, filename);
42876
+ if (opts.todoId)
42877
+ form.append("todoId", opts.todoId);
42878
+ const res = await fetch(`${this.apiUrl}${restBasePath(this.apiKey)}/resources/register`, {
42879
+ method: "POST",
42880
+ headers: { "x-api-key": this.apiKey },
42881
+ body: form,
42882
+ signal: AbortSignal.timeout(60000)
42883
+ });
42884
+ if (!res.ok)
42885
+ throw new Error(`API POST /resources/register failed: ${res.status} ${await res.text()}`);
42886
+ return res.json();
42887
+ }
42866
42888
  async showFile(todoId, file, filename, opts = {}) {
42867
42889
  const form = new FormData;
42868
42890
  form.append("file", file, filename);
@@ -43280,7 +43302,7 @@ var TodoStatus;
43280
43302
  TodoStatus2["DELETED"] = "DELETED";
43281
43303
  })(TodoStatus ||= {});
43282
43304
  // ../packages/shared-fbe/src/models.ts
43283
- var DEFAULT_MODEL = "anthropic:anthropic/claude-sonnet-5";
43305
+ var DEFAULT_MODEL = "anthropic:anthropic/claude-opus-5";
43284
43306
  var MODEL_ALIASES = {
43285
43307
  claude: DEFAULT_MODEL
43286
43308
  };
@@ -43336,8 +43358,8 @@ function qualifiedModelIds(listing, filter) {
43336
43358
  var USAGE_WINDOWS = {
43337
43359
  SESSION_MS: 5 * 60 * 60 * 1000,
43338
43360
  WEEK_MS: 7 * 24 * 60 * 60 * 1000,
43339
- WEEKLY_FACTOR: 0.28,
43340
- SESSION_FACTOR: 1 / 7
43361
+ WEEKLY_FACTOR: 0.35,
43362
+ SESSION_FACTOR: 1 / 5
43341
43363
  };
43342
43364
  // ../packages/shared-fbe/src/mimetypes.json
43343
43365
  var mimetypes_default = {
@@ -43517,7 +43539,11 @@ var API_TOOL_TYPES = new Set([
43517
43539
  "api_list_mcps" /* ApiListMcps */,
43518
43540
  "api_get_usage" /* ApiGetUsage */,
43519
43541
  "api_get_balance" /* ApiGetBalance */,
43520
- "api_get_api_schema" /* ApiGetApiSchema */
43542
+ "api_get_api_schema" /* ApiGetApiSchema */,
43543
+ "api_list_board" /* ApiListBoard */,
43544
+ "api_upsert_group" /* ApiUpsertGroup */,
43545
+ "api_create_template" /* ApiCreateTemplate */,
43546
+ "api_recommend_template" /* ApiRecommendTemplate */
43521
43547
  ]);
43522
43548
  var FILE_TOOL_TYPES = new Set([
43523
43549
  "read" /* Read */,
@@ -43959,34 +43985,36 @@ var BUSINESS_ANALYZER_AGENT = {
43959
43985
  ownerId: "SYSTEM",
43960
43986
  systemMessage: `You are a business analyst and growth advisor. Beyond analyzing and updating the business context, you provide ongoing, actionable advice for growing the company's digital presence and business.
43961
43987
 
43962
- You have no previous context about this business yet \u2014 everything you know must come from the current business context markdown and what you research now. Do not assume prior knowledge or earlier conversations.
43988
+ You have no previous context about this business yet. Everything you know must come from the current business context markdown and what you research now. Do not assume prior knowledge or earlier conversations.
43989
+
43990
+ Write dense, not long. Use concrete names, numbers and URLs, with no filler or restated context. Cut any sentence that adds no information and keep scannable structure. Use commas, colons and periods only. Never use em-dashes or semicolons anywhere, including in generated templates, notes and group descriptions.
43963
43991
 
43964
- This chat lives on the user's Brand page: the business context markdown you maintain is rendered live right next to this conversation, together with the brand voice answers and the company's online-presence map. The document lives at the \`todoforai:business-context\` resource; whenever you save to it, the user sees the document change immediately \u2014 refer to it as "your brand page" (never as a file), and keep it polished since it doubles as the company's profile.
43992
+ This chat lives on the user's Brand page. The business context markdown you maintain is rendered live right next to this conversation, together with the brand voice answers and the company's online-presence map. The document lives at the \`todoforai:business-context\` resource. Whenever you save to it, the user sees the document change immediately. Refer to it as "your brand page", never as a file, and keep it polished since it doubles as the company's profile.
43965
43993
 
43966
43994
  How to analyze and update the business context:
43967
43995
  1. Read the current business context with \`Read todoforai:business-context\`
43968
- 2. Research: fetch websites with webfetch (follow key subpages like /about, /pricing, /contact if they exist); use google_search and google_rag to find competitors and fill knowledge gaps
43969
- 3. Merge findings into the existing context \u2014 preserve valid existing information, update conflicting facts only when the new source is clearer or more specific \u2014 and save it with \`Edit todoforai:business-context\` (or \`Write todoforai:business-context\` to create/overwrite). You MUST persist your findings this way; analysis that is only spoken in chat and never written to the brand page is lost.
43996
+ 2. Research the company. Fetch websites with webfetch and follow key subpages like /about, /pricing and /contact when they exist. Use google_search and google_rag to find competitors and fill knowledge gaps
43997
+ 3. Merge findings into the existing context and save it with \`Edit todoforai:business-context\`, or \`Write todoforai:business-context\` to create or overwrite it. Preserve valid existing information. Update a conflicting fact only when the new source is clearer or more specific. You MUST persist your findings this way. Analysis that is only spoken in chat and never written to the brand page is lost.
43970
43998
 
43971
43999
  Cover these sections (skip any without data):
43972
- - **Company Info** \u2014 name, founding year, location, team size, stage
43973
- - **Product & Services** \u2014 offerings, key features, pricing model, target audience
43974
- - **Value Proposition** \u2014 core problem solved, unique selling points
43975
- - **Voice & Tone** \u2014 brand personality, communication style (derive from website copy)
43976
- - **Target Market** \u2014 customer segments, industries, geographic focus, B2B vs B2C
43977
- - **Tech Stack** \u2014 technologies, integrations, platforms
43978
- - **Competitors** \u2014 3-5 direct competitors with one-line comparisons
43979
- - **Online Presence** \u2014 where the company is present: search for its accounts on X/Twitter, LinkedIn, Facebook, Instagram, YouTube, Reddit, GitHub, blog/newsletter, and any industry-relevant platforms. List each found channel as \`platform \u2014 handle/URL \u2014 one-line activity note (e.g. active, stale, unclaimed)\`; also note important channels where no presence was found
44000
+ - **Company Info**: name, founding year, location, team size, stage
44001
+ - **Product & Services**: offerings, key features, pricing model, target audience
44002
+ - **Value Proposition**: core problem solved, unique selling points
44003
+ - **Voice & Tone**: brand personality, communication style (derive from website copy)
44004
+ - **Target Market**: customer segments, industries, geographic focus, B2B vs B2C
44005
+ - **Tech Stack**: technologies, integrations, platforms
44006
+ - **Competitors**: 3-5 direct competitors with one-line comparisons
44007
+ - **Online Presence**: where the company is present. Search for its accounts on X/Twitter, LinkedIn, Facebook, Instagram, YouTube, Reddit, GitHub, blog/newsletter, and any industry-relevant platforms. List each found channel as \`platform: absolute profile URL (one-line activity note such as active, stale or unclaimed)\`. Also note important channels where no presence was found. Always record the full public URL like \`https://x.com/acme\`, never a bare handle. These links are read back to learn the company's writing voice, so an unfetchable \`@acme\` is a dead end
43980
44008
 
43981
44009
  If anything important remains unclear, add an **Open Questions** section at the end.
43982
44010
 
43983
- Maintain a single machine-readable **Presence** block so the user's brand page can render a live presence map. Write it as one fenced code block tagged \`growth-plan\` containing JSON (the user never sees this raw \u2014 it powers the presence hexes on the brand page).
44011
+ Maintain a single machine-readable **Presence** block so the user's brand page can render a live presence map. Write it as one fenced code block tagged \`growth-plan\` containing JSON. The user never sees this raw. It powers the presence hexes on the brand page.
43984
44012
 
43985
44013
  \`\`\`growth-plan
43986
44014
  {
43987
44015
  "presence": {
43988
44016
  "linkedin-networking": { "score": 45, "handle": "https://linkedin.com/company/example", "note": "Company page exists but posts infrequently" },
43989
- "x-twitter-presence": { "score": 60, "handle": "@example", "note": "Active, regular posts" },
44017
+ "x-twitter-presence": { "score": 60, "handle": "https://x.com/example", "note": "Active, regular posts" },
43990
44018
  "newsletter": { "score": null, "note": "No newsletter signup found" }
43991
44019
  }
43992
44020
  }
@@ -43995,46 +44023,66 @@ Maintain a single machine-readable **Presence** block so the user's brand page c
43995
44023
  Rules for the Presence block:
43996
44024
  - Keys MUST be one of these exact ids: facebook-posting, x-twitter-presence, tiktok-content, instagram-content, linkedin-networking, reddit-engagement, blog-writing, seo-optimization, backlink-building, keyword-research, aso-optimization, llm-optimization, google-ads-management, meta-ads-management, lead-outreach, crm-management, newsletter, affiliate-program, partnership-outreach, analytics-monitoring. Skip channels you did not check.
43997
44025
  - \`score\` is an integer 0-100, or \`null\` when you checked but found no presence. Base scores on real findings, not guesses. Include every channel you actually checked.
43998
- - Keep it consistent with the prose Online Presence section \u2014 the JSON is the structured mirror of those findings.
44026
+ - \`handle\` is the channel's ABSOLUTE public URL such as \`https://www.linkedin.com/company/acme\` or \`https://x.com/acme\`, never a bare \`@handle\`. The brand page reads these pages back to learn the company's writing voice, so a URL that can't be fetched is worthless. Omit \`handle\` entirely when you found no account.
44027
+ - Keep it consistent with the prose Online Presence section. The JSON is the structured mirror of those findings.
43999
44028
 
44000
- Growth recommendations are NOT part of this block \u2014 they live on the project board as proposed todos (\u2727 rows on their group cards; see "Recommending actionable TODOs" below). After any analysis that surfaces growth opportunities, materialize your top recommendations as proposals there; the prose Next Steps section stays human-readable advice.
44029
+ Growth recommendations are NOT part of this block. They live on the project board as proposed todos, shown as \u2727 rows on their group cards (see "Recommending actionable TODOs" below). After any analysis that surfaces growth opportunities, materialize your top recommendations as proposals there. The prose Next Steps section stays human-readable advice.
44001
44030
 
44002
- Document order for the saved brand page: human-readable sections first, then the **Next Steps** section, then Open Questions (if any), and finally exactly one \`growth-plan\` block as the very last element. (Your chat reply can still end with a short spoken summary; only the saved markdown must end with the machine block.)
44031
+ Document order for the saved brand page: human-readable sections first, then the **Next Steps** section, then Open Questions if any, and finally exactly one \`growth-plan\` block as the very last element. Your chat reply can still end with a short spoken summary. Only the saved markdown must end with the machine block.
44003
44032
 
44004
- Guide the user one step at a time \u2014 they should always know the single next thing to do:
44005
- - When you need input (brand voice, tone, target audience, which channel to prioritize), prefer the question tool with short multi-choice options over asking open-ended prose questions.
44006
- - After completing any analysis or context update, always close with a short **Next Steps** section: 2-4 concrete, prioritized tips tailored to this specific business (e.g. SEO/content gaps, social channels worth activating, positioning against the competitors you found, conversion or messaging improvements). Base tips on what you actually observed, keep each to one or two sentences, and lead with the one you'd do first.
44033
+ Guide the user one step at a time. They should always know the single next thing to do:
44034
+ - When you need input on brand voice, tone, target audience or which channel to prioritize, prefer the question tool with short multi-choice options over asking open-ended prose questions.
44035
+ - After completing any analysis or context update, always close with a short **Next Steps** section: 2-4 concrete, prioritized tips tailored to this specific business, such as SEO and content gaps, social channels worth activating, positioning against the competitors you found, or conversion and messaging improvements. Base tips on what you actually observed, keep each to one or two sentences, and lead with the one you'd do first.
44007
44036
 
44008
44037
  In follow-up conversations, act as an ongoing advisor: answer growth and digital-presence questions using the business context, refresh your analysis when asked, and suggest what to tackle next.
44009
44038
 
44010
- When the user asks you to recommend how to grow (e.g. "recommend growth TODOs", the brand page's guided suggestion, the board's Recommend button), read the current business context (including the presence block), research only where information is missing or stale, refresh the presence block if findings changed, then materialize your top growth actions as recommendation cards (see below). The cards ARE the growth plan \u2014 do not write a separate recommendations list into the markdown.
44039
+ When the user asks you to recommend how to grow, for example "recommend growth TODOs", the brand page's guided suggestion, or the board's Recommend button, read the current business context including the presence block, read the board with \`api_list_board\`, research only where information is missing or stale, refresh the presence block if findings changed, then materialize your top growth actions as recommendation cards (see below). The cards ARE the growth plan. Do not write a separate recommendations list into the markdown.
44011
44040
 
44012
44041
  Strategic workstreams (board groups):
44013
- The project board is organized into group cards, and each group should be a strategic workstream for THIS company \u2014 a front the business is (or should be) pushing on, e.g. "SEO & Content", "Outbound to agencies", "Community on Reddit", "Retention & Email". Your recommendation-generation prompt includes the board's existing groups as JSON (\`slug\`, \`name\`, and \`description\` when set \u2014 the description states the workstream's strategic intent). Treat them as the company's current strategic map:
44014
- - FIRST fit each recommendation into an existing group when it genuinely advances that workstream \u2014 pass its slug as the \`group\` parameter of \`api_recommend_template\`. Match on strategy (read the descriptions), not keywords.
44015
- - When your analysis surfaces a strategic direction the board doesn't cover yet, introduce it: pass a new short kebab-case \`group\` slug plus a human \`groupName\` (2-4 words, strategy-level \u2014 "Partner Channel", not "Misc" or "Growth") and a one-sentence \`groupDescription\` stating the strategic bet and its target outcome for THIS company (e.g. "Win comparison searches against Zapier and n8n to capture switchers"). The group is created with the card.
44016
- - Keep the map tight: 3-7 workstreams total is healthy. Never invent a new group when an existing one fits, never create catch-all groups, and never mirror priority or channel-lists as groups \u2014 a group is a strategic bet, its cards are the moves.
44042
+ The project board is organized into group cards. Each group should be a strategic workstream for THIS company, a front the business is or should be pushing on, such as "SEO & Content", "Outbound to agencies", "Community on Reddit" or "Retention & Email". That board IS the company's current strategic map, so ALWAYS read it with \`api_list_board\` before you propose anything. It returns every live group with its \`slug\`, \`name\` and \`description\`, the workstream's strategic intent, together with the cards already sitting on it. Pass \`includeArchived: "true"\` when you also need the retired ones, for example before restoring one. Recommendation-generation runs also hand you the group list as JSON up front, but that is only a routing hint. It carries no cards, so it can't tell you whether a move is already proposed. Propose blind and you will duplicate what is already on the board.
44043
+ - FIRST fit each recommendation into an existing group when it genuinely advances that workstream, passing its slug as the \`group\` parameter of \`api_recommend_template\`. Match on strategy by reading the descriptions, not on keywords.
44044
+ - When your analysis surfaces a strategic direction the board doesn't cover yet, introduce it. Pass a new short kebab-case \`group\` slug plus a human \`groupName\` of 2-4 words at strategy level, like "Partner Channel" rather than "Misc" or "Growth". Add a one-sentence \`groupDescription\` stating the strategic bet and its target outcome for THIS company, for example "Win comparison searches against Zapier and n8n to capture switchers". The group is created with the card.
44045
+ - Keep the map tight. 3-7 workstreams total is healthy. Never invent a new group when an existing one fits, never create catch-all groups, and never mirror priority or channel-lists as groups. A group is a strategic bet and its cards are the moves.
44046
+ - Curate the map, don't just append to it. \`api_upsert_group\` edits a group in place. Sharpen a vague or missing \`description\` so future todos route correctly, rename a group whose cards have drifted away from its name, reorder with \`order\` to put the most important bet first, and archive a workstream the business has genuinely abandoned with \`archived: "yes"\`, or \`"no"\` to bring one back. Do this when the board tells you it's needed, such as untitled \`c-xxxx\` groups, a description that no longer matches its cards, or more than 7 live groups. Say what you changed and why in your reply, and never archive a group that still has open cards without telling the user.
44017
44047
 
44018
44048
  Recommending actionable TODOs from your recommendations:
44019
- To materialize growth recommendations, propose todos on the project board. A proposal is a real todo in PROPOSED state \u2014 it sits as a \u2727 row on its group card until the user starts (accepts) or dismisses it. You NEVER create running todos \u2014 you only propose. Two tools, strict roles:
44049
+ To materialize growth recommendations, propose todos on the project board. A proposal is a real todo in PROPOSED state. It sits as a \u2727 row on its group card until the user accepts it by starting it, or dismisses it. You NEVER create running todos. You only propose. One tool does it: \`api_recommend_template\`.
44050
+
44051
+ Exactly ONE \`api_recommend_template\` call per proposal: it carries both what the todo is and where it lands on the board.
44052
+
44053
+ 1. WHAT IT IS. Pass \`name\` + \`body\`, written for THIS business, and optionally \`description\`, \`short\` and \`categories\`.
44054
+ - Reuse an existing registry template instead ONLY when it already fits this business closely with no meaningful edits: pass its id as \`templateId\` and omit \`name\`/\`body\`. If it needs tailoring, write yours inline. Do not recommend a generic playbook that ignores this company's specifics.
44055
+
44056
+ Language: match the business. Write user-facing text such as names, bodies and notes in the language the company itself communicates in. When the company is international or unclear, use English. Only the frame stays in English. The heading lines \`Your task:\`, \`Steps:\` and \`Data needed:\` are parsed, so keep them exactly as-is. Everything else follows the business's language.
44057
+
44058
+ The body is read by a PERSON deciding whether to run it, so it must be understandable at a glance. The executing agent is capable and has the business context at run time, so it will elaborate and figure out the details itself. Write a minimal brief, not a playbook. Include ONLY what the agent could not figure out on its own: the business-specific direction such as company, competitor, segment, channel and the metric to move, plus any hard constraint. Cut everything else, including background, caveats, generic how-to and restated context.
44059
+
44060
+ Hard limits: body \u226480 words total. One sentence of persona. One-sentence task. 2-4 steps, each a short fragment of at most 8 words naming WHAT, not how. Data needed is a comma-separated list. If a sentence wouldn't change what the agent does, delete it.
44020
44061
 
44021
- 1. GET A TEMPLATE (api_create_template) \u2014 generate-first, reuse only on a close fit:
44022
- - Default: CREATE a template tailored to THIS business with the \`api_create_template\` tool. It returns the new template id.
44023
- - Reuse an existing registry template ONLY when it already fits this business closely with no meaningful edits \u2014 pass its id straight to \`api_recommend_template\`. If it needs tailoring, create a new one instead \u2014 do not recommend a generic playbook that ignores this company's specifics.
44062
+ \`name\`: 3-6 words, action-first like "Win Reddit comparison threads", with no company name prefix. Give \`description\` and \`short\` one tight sentence each.
44024
44063
 
44025
- Write every generated body to be unmistakably about THIS company \u2014 bake the specifics from the business context into the task itself: the company name, its actual competitors, the target segment, the specific channel/platform, and the concrete metric to move. A reader should never mistake it for a generic template.
44064
+ Example body at the right density (adapt specifics, keep the brevity):
44065
+ "You are a B2B outbound specialist for dev-tool companies.
44066
+ Your task:
44067
+ Land Acme in 10 agency partner conversations this month.
44068
+ Steps:
44069
+ - Shortlist 30 EU automation agencies
44070
+ - Draft one-line personalized openers
44071
+ - Send, track replies in a sheet
44072
+ Data needed:
44073
+ Acme pricing page, current partner list, founder's LinkedIn"
44026
44074
 
44027
- Body structure (REQUIRED \u2014 the body is split into the agent's system prompt and the first user turn by section headings, so follow this exactly):
44028
- - Start with the persona/expertise and any Rules as plain prose/paragraphs at the top (no heading needed) \u2014 this becomes the agent's system prompt.
44029
- - Then the request, using these exact standalone heading lines (letters only, each on its own line, ending with a colon): \`Your task:\`, \`Steps:\`, \`Data needed:\`. Everything under these headings becomes the first user turn.
44030
- - Headings must match exactly (e.g. \`Your task:\`, not \`Your task (SEO):\`) or the split breaks. Put the business-specific detail in the body text under each heading, never in the heading itself.
44075
+ Body structure (REQUIRED). The body is split into the agent's system prompt and the first user turn by section headings, so follow this exactly:
44076
+ - Start with the persona, expertise and any Rules as plain prose at the top with no heading. This becomes the agent's system prompt.
44077
+ - Then write the request using these exact standalone heading lines, letters only, each on its own line and ending with a colon: \`Your task:\`, \`Steps:\`, \`Data needed:\`. Everything under these headings becomes the first user turn.
44078
+ - Headings must match exactly or the split breaks. Write \`Your task:\`, not \`Your task (SEO):\`. Put the business-specific detail in the body text under each heading, never in the heading itself.
44031
44079
 
44032
- 2. RECOMMEND (api_recommend_template \u2014 proposes the template as a PROPOSED todo on the project; never creates a running todo):
44033
- Call \`api_recommend_template\` with the template id, a one-line \`note\` (the WHY for THIS business \u2014 it becomes the proposal's tooltip), a \`priority\` of high, medium or low, and the \`group\` slug of the strategic workstream it belongs to (see "Strategic workstreams" above \u2014 reuse an existing slug when one fits; a new slug + \`groupName\` creates the workstream). Project and business context default to the current session, so pass them only when targeting a different one. The proposal appears as a \u2727 row on that group's board card for the user to review before any credits are spent. Re-proposing the same template refreshes the open proposal instead of duplicating it; dismissed proposals since your last run are fed back to you \u2014 don't re-propose those, steer elsewhere.
44080
+ 2. WHERE IT LANDS, in that same call. It becomes a PROPOSED todo on the project and never a running todo:
44081
+ Give it a one-line \`note\` of roughly 15 words at most giving the WHY for THIS business, which becomes the proposal's tooltip, a \`priority\` of high, medium or low, and the \`group\` slug of the strategic workstream it belongs to. See "Strategic workstreams" above. Reuse an existing slug when one fits. A new slug plus \`groupName\` creates the workstream. Project and business context default to the current session, so pass them only when targeting a different one. The proposal appears as a \u2727 row on that group's board card for the user to review before any credits are spent. Re-proposing the same \`templateId\` refreshes that open proposal instead of duplicating it; an inline proposal is always a new one, so check the board you just read before repeating a move that is already there. Dismissed proposals since your last run are fed back to you. Don't re-propose those, steer elsewhere.
44034
44082
 
44035
- Both tools run in-process and need no machine or shell \u2014 never shell out to \`todoregistry-cli\` / \`todoforai-cli\` for this.
44083
+ It runs in-process and needs no machine or shell. Never shell out to \`todoregistry-cli\` or \`todoforai-cli\` for this.
44036
44084
 
44037
- 3. Propose 3-6 todos (highest priority first). Then reply with a one-line summary per proposal grouped by workstream, and tell the user they're waiting on the board for review.`,
44085
+ 3. Propose 3-6 todos, highest priority first. Make sure a few of them are the trivial, high-probability moves for this company, the strategic directions its owner would nod at instantly, alongside any cleverer bets. Then reply with a one-line summary per proposal grouped by workstream, and tell the user they're waiting on the board for review.`,
44038
44086
  mcpConfigs: {},
44039
44087
  edgesMcpConfigs: {},
44040
44088
  permissions: {
@@ -44221,7 +44269,7 @@ import { parseArgs } from "util";
44221
44269
  // package.json
44222
44270
  var package_default = {
44223
44271
  name: "@todoforai/cli",
44224
- version: "0.1.31",
44272
+ version: "0.1.33",
44225
44273
  type: "module",
44226
44274
  bin: {
44227
44275
  "todoforai-cli": "bin/todoforai-cli.js",
@@ -44240,6 +44288,7 @@ var package_default = {
44240
44288
  },
44241
44289
  dependencies: {
44242
44290
  "@todoforai/update-notifier": "^0.1.0",
44291
+ chalk: "^6.0.0",
44243
44292
  "cli-highlight": "^2.1.11",
44244
44293
  "diff-match-patch": "^1.0.5",
44245
44294
  ws: "^8.18.0"
@@ -44258,35 +44307,35 @@ function getEnv(name) {
44258
44307
  }
44259
44308
  function printUsage() {
44260
44309
  process.stderr.write(`
44261
- todoforai-cli \u2014 TODOforAI CLI (Bun). Aliases: tfa-cli, todoai.
44310
+ tfa-cli \u2014 TODOforAI CLI (Bun). Legacy aliases: todoforai-cli, todoai.
44262
44311
 
44263
44312
  Usage:
44264
- todoforai-cli login # Browser-based device auth
44265
- todoforai-cli "prompt text" # Prompt as argument
44266
- todoforai-cli -n "Quick task" # Non-interactive (run and exit)
44267
- echo "content" | todoforai-cli # Pipe from stdin
44268
- todoforai-cli --path /my/project "Fix bug" # Explicit workspace path
44269
- todoforai-cli -c ["prompt"] # Resume last todo (optional prompt sent on attach)
44270
- todoforai-cli --resume <todo-id> ["prompt"] # Resume specific todo (optional prompt sent on attach)
44271
- todoforai-cli --inspect <todo-id>[@<slice>] # Read chat log. <slice> = -3:, :1, 5:10, 7 (Python-style)
44272
- todoforai-cli start <id> # Start a TODO from the registry (todoregistry.com)
44273
- todoforai-cli --list-agents # List available agents and exit
44274
- todoforai-cli --list-models [filter] # List models usable with --model and exit
44275
- todoforai-cli agent update <agent> model=<model> # Update agent settings (see 'agent --help')
44276
- todoforai-cli list [-n 30] [--cursor N] [--all] [--status S] # List todos (paginated); see 'list --help'
44277
- todoforai-cli status <todo-id> <STATUS> # Update a todo's status (run 'status --help' for the full list)
44278
- todoforai-cli delete <todo-id> # Permanently delete a todo
44279
- todoforai-cli addmessage <todo-id> "text" # Add a message to an existing todo
44280
- todoforai-cli show <file|-> [todo-id] # Show a file in the chat (rendered by mimetype; - reads stdin)
44313
+ tfa-cli login # Browser-based device auth
44314
+ tfa-cli "prompt text" # Prompt as argument
44315
+ tfa-cli -n "Quick task" # Non-interactive (run and exit)
44316
+ echo "content" | tfa-cli # Pipe from stdin
44317
+ tfa-cli --path /my/project "Fix bug" # Explicit workspace path
44318
+ tfa-cli -c ["prompt"] # Resume last todo (optional prompt sent on attach)
44319
+ tfa-cli --resume <todo-id> ["prompt"] # Resume specific todo (optional prompt sent on attach)
44320
+ tfa-cli --inspect <todo-id>[@<slice>] # Read chat log. <slice> = -3:, :1, 5:10, 7 (Python-style)
44321
+ tfa-cli start <id> # Start a TODO from the registry (todoregistry.com)
44322
+ tfa-cli --list-agents # List available agents and exit
44323
+ tfa-cli --list-models [filter] # List models usable with --model and exit
44324
+ tfa-cli agent update <agent> model=<model> # Update agent settings (see 'agent --help')
44325
+ tfa-cli list [-n 30] [--cursor N] [--all] [--status S] # List todos (paginated); see 'list --help'
44326
+ tfa-cli status <todo-id> <STATUS> # Update a todo's status (run 'status --help' for the full list)
44327
+ tfa-cli delete <todo-id> # Permanently delete a todo
44328
+ tfa-cli addmessage <todo-id> "text" # Add a message to an existing todo
44329
+ tfa-cli show <file|-> [todo-id] # Show a file in the chat (rendered by mimetype; - reads stdin)
44281
44330
  # [--title T] [--alias A] [--mime M] [--card <name>] [--json]
44282
- todoforai-cli show list [todo-id] # List show blocks (ref, title, mime/url, card)
44331
+ tfa-cli show list [todo-id] # List show blocks (ref, title, mime/url, card)
44283
44332
  # [--project <id>] [--card <name>] [--json]
44284
44333
  # no todo-id + --project (or $TODOFORAI_PROJECT_ID) = every todo
44285
- todoforai-cli open <url> [todo-id] # Show a live http(s) url in the chat as a preview
44334
+ tfa-cli open <url> [todo-id] # Show a live http(s) url in the chat as a preview
44286
44335
  # [--title T] [--alias A] [--json]
44287
- todoforai-cli recommend --template <id> # Add a template as a recommendation card (see 'todoregistry-cli create')
44288
- todoforai-cli claim mint --seed <projectId> [--emails a@x,b@y] [--ttl <sec>] # Mint /claim/<token> ownership links for a project you own
44289
- todoforai-cli next [--direction "<text>"] # Ask the analyzer for growth recommendation cards (optional free-text steer)
44336
+ tfa-cli recommend --template <id> # Add a template as a recommendation card (see 'todoregistry-cli create')
44337
+ tfa-cli claim mint --seed <projectId> [--emails a@x,b@y] [--ttl <sec>] # Mint /claim/<token> ownership links for a project you own
44338
+ tfa-cli next [--direction "<text>"] # Ask the analyzer for growth recommendation cards (optional free-text steer)
44290
44339
 
44291
44340
  Options:
44292
44341
  --path <dir> Workspace path (default: cwd)
@@ -44310,6 +44359,8 @@ Options:
44310
44359
  --allow-all Set permissions to allow all tools (no approval needed)
44311
44360
  --raw-sysmsg <file> Use file contents verbatim as system prompt (new TODO only)
44312
44361
  --no-watch Create todo and exit
44362
+ --isolated Spawn an ephemeral, task-scoped bridge: the agent sees ONLY
44363
+ this machine (workspace = --path/cwd); session dies with the CLI
44313
44364
  --no-bridge Do not auto-spawn bridge
44314
44365
  --no-edge Deprecated alias for --no-bridge
44315
44366
  --json Output as JSON
@@ -44317,6 +44368,7 @@ Options:
44317
44368
  --format-anthropic 'inspect --json': Anthropic-style shape (tool_result in next user msg); attachment sources are uri-typed, so not a 1:1 messages.create input
44318
44369
  --safe Validate API key upfront
44319
44370
  --debug, -d Debug output
44371
+ --debug-dump Attach LLM request debug info per turn (requires server grant)
44320
44372
  --show-config Show config
44321
44373
  --reset-config Reset config file
44322
44374
  --version, -v Print version and exit
@@ -44334,7 +44386,7 @@ var STATUS_HELP = {
44334
44386
  };
44335
44387
  function printStatusHelp() {
44336
44388
  process.stderr.write(`
44337
- todoforai-cli status <todo-id> <STATUS>
44389
+ tfa-cli status <todo-id> <STATUS>
44338
44390
 
44339
44391
  Common statuses:
44340
44392
  ${Object.entries(STATUS_HELP).map(([s, d]) => ` ${s.padEnd(18)}${d}`).join(`
@@ -44380,6 +44432,8 @@ function parseCliArgs() {
44380
44432
  "allow-all": { type: "boolean", default: false },
44381
44433
  "raw-sysmsg": { type: "string" },
44382
44434
  "no-watch": { type: "boolean", default: false },
44435
+ isolated: { type: "boolean", default: false },
44436
+ "debug-dump": { type: "boolean", default: false },
44383
44437
  "no-bridge": { type: "boolean", default: false },
44384
44438
  "no-edge": { type: "boolean", default: false },
44385
44439
  json: { type: "boolean", default: false },
@@ -44770,14 +44824,12 @@ function bridgeRunArgs(apiUrl) {
44770
44824
  const url = parseApiUrl(apiUrl);
44771
44825
  if (!url)
44772
44826
  return [];
44773
- if (isLocalHost(url.hostname)) {
44827
+ if (url.hostname && url.hostname !== "api.todofor.ai") {
44774
44828
  const args = ["--host", url.hostname];
44775
44829
  if (url.port)
44776
44830
  args.push("--port", url.port);
44777
44831
  return withProfile(args, apiUrl);
44778
44832
  }
44779
- if (url.hostname && url.hostname !== "api.todofor.ai")
44780
- return withProfile(["--host", url.hostname], apiUrl);
44781
44833
  return [];
44782
44834
  }
44783
44835
  function bridgeLoginArgs(apiUrl) {
@@ -45175,7 +45227,9 @@ var DETAILED_ONLY = new Set([
45175
45227
  "stop_sequence",
45176
45228
  "permissions",
45177
45229
  "isPublic",
45178
- "metadata"
45230
+ "metadata",
45231
+ "systemPrompt",
45232
+ "toolSchemas"
45179
45233
  ]);
45180
45234
  var DEBUG_ONLY = new Set([
45181
45235
  "runMeta",
@@ -45303,6 +45357,10 @@ function toAnthropicShape(messages, mode = "default", format = "compact") {
45303
45357
  msg.id = m.id;
45304
45358
  if (m.createdAt)
45305
45359
  msg.createdAt = m.createdAt;
45360
+ if (m.systemPrompt !== undefined)
45361
+ msg.systemPrompt = m.systemPrompt;
45362
+ if (m.toolSchemas !== undefined)
45363
+ msg.toolSchemas = m.toolSchemas;
45306
45364
  }
45307
45365
  if (m.role === "user") {
45308
45366
  const atts = (m.attachments ?? []).filter((a) => !prevToolAttachmentIds.has(a.id)).map((a) => {
@@ -45676,45 +45734,594 @@ Please choose an agent:
45676
45734
  // src/diff-view.ts
45677
45735
  var import_diff_match_patch = __toESM(require_diff_match_patch(), 1);
45678
45736
  var import_cli_highlight = __toESM(require_dist(), 1);
45679
- var import_chalk = __toESM(require_source(), 1);
45680
45737
  import { extname } from "path";
45738
+
45739
+ // node_modules/chalk/source/utilities.js
45740
+ function stringReplaceAll(string, substring, postfix) {
45741
+ let index = string.indexOf(substring);
45742
+ if (index === -1) {
45743
+ return string;
45744
+ }
45745
+ const substringLength = substring.length;
45746
+ let endIndex = 0;
45747
+ let returnValue = "";
45748
+ do {
45749
+ returnValue += string.slice(endIndex, index) + substring + postfix;
45750
+ endIndex = index + substringLength;
45751
+ index = string.indexOf(substring, endIndex);
45752
+ } while (index !== -1);
45753
+ returnValue += string.slice(endIndex);
45754
+ return returnValue;
45755
+ }
45756
+ function stringEncaseCRLFWithFirstIndex(string, prefix, postfix, index) {
45757
+ let endIndex = 0;
45758
+ let returnValue = "";
45759
+ do {
45760
+ const isGotCR = string[index - 1] === "\r";
45761
+ returnValue += string.slice(endIndex, isGotCR ? index - 1 : index) + prefix + (isGotCR ? `\r
45762
+ ` : `
45763
+ `) + postfix;
45764
+ endIndex = index + 1;
45765
+ index = string.indexOf(`
45766
+ `, endIndex);
45767
+ } while (index !== -1);
45768
+ returnValue += string.slice(endIndex);
45769
+ return returnValue;
45770
+ }
45771
+
45772
+ // node_modules/chalk/source/vendor/ansi-styles/index.js
45773
+ var ANSI_BACKGROUND_OFFSET = 10;
45774
+ var ANSI_UNDERLINE_OFFSET = 20;
45775
+ var wrapAnsi16 = (offset = 0) => (code) => `\x1B[${code + offset}m`;
45776
+ var wrapAnsi256 = (offset = 0) => (code) => `\x1B[${38 + offset};5;${code}m`;
45777
+ var wrapAnsi16m = (offset = 0) => (red, green, blue) => `\x1B[${38 + offset};2;${red};${green};${blue}m`;
45778
+ var wrapUnderlineAnsi = (code) => `\x1B[58;5;${code < 90 ? code - 30 : code - 90 + 8}m`;
45779
+ var styles = {
45780
+ modifier: {
45781
+ reset: [0, 0],
45782
+ bold: [1, 22],
45783
+ dim: [2, 22],
45784
+ italic: [3, 23],
45785
+ underline: [4, 24],
45786
+ underlineDouble: ["4:2", 24],
45787
+ underlineCurly: ["4:3", 24],
45788
+ underlineDotted: ["4:4", 24],
45789
+ underlineDashed: ["4:5", 24],
45790
+ overline: [53, 55],
45791
+ inverse: [7, 27],
45792
+ hidden: [8, 28],
45793
+ strikethrough: [9, 29]
45794
+ },
45795
+ color: {
45796
+ black: [30, 39],
45797
+ red: [31, 39],
45798
+ green: [32, 39],
45799
+ yellow: [33, 39],
45800
+ blue: [34, 39],
45801
+ magenta: [35, 39],
45802
+ cyan: [36, 39],
45803
+ white: [37, 39],
45804
+ blackBright: [90, 39],
45805
+ gray: [90, 39],
45806
+ grey: [90, 39],
45807
+ redBright: [91, 39],
45808
+ greenBright: [92, 39],
45809
+ yellowBright: [93, 39],
45810
+ blueBright: [94, 39],
45811
+ magentaBright: [95, 39],
45812
+ cyanBright: [96, 39],
45813
+ whiteBright: [97, 39]
45814
+ },
45815
+ bgColor: {
45816
+ bgBlack: [40, 49],
45817
+ bgRed: [41, 49],
45818
+ bgGreen: [42, 49],
45819
+ bgYellow: [43, 49],
45820
+ bgBlue: [44, 49],
45821
+ bgMagenta: [45, 49],
45822
+ bgCyan: [46, 49],
45823
+ bgWhite: [47, 49],
45824
+ bgBlackBright: [100, 49],
45825
+ bgGray: [100, 49],
45826
+ bgGrey: [100, 49],
45827
+ bgRedBright: [101, 49],
45828
+ bgGreenBright: [102, 49],
45829
+ bgYellowBright: [103, 49],
45830
+ bgBlueBright: [104, 49],
45831
+ bgMagentaBright: [105, 49],
45832
+ bgCyanBright: [106, 49],
45833
+ bgWhiteBright: [107, 49]
45834
+ },
45835
+ underlineColor: {
45836
+ underlineBlack: ["58;5;0", 59],
45837
+ underlineRed: ["58;5;1", 59],
45838
+ underlineGreen: ["58;5;2", 59],
45839
+ underlineYellow: ["58;5;3", 59],
45840
+ underlineBlue: ["58;5;4", 59],
45841
+ underlineMagenta: ["58;5;5", 59],
45842
+ underlineCyan: ["58;5;6", 59],
45843
+ underlineWhite: ["58;5;7", 59],
45844
+ underlineBlackBright: ["58;5;8", 59],
45845
+ underlineGray: ["58;5;8", 59],
45846
+ underlineGrey: ["58;5;8", 59],
45847
+ underlineRedBright: ["58;5;9", 59],
45848
+ underlineGreenBright: ["58;5;10", 59],
45849
+ underlineYellowBright: ["58;5;11", 59],
45850
+ underlineBlueBright: ["58;5;12", 59],
45851
+ underlineMagentaBright: ["58;5;13", 59],
45852
+ underlineCyanBright: ["58;5;14", 59],
45853
+ underlineWhiteBright: ["58;5;15", 59]
45854
+ }
45855
+ };
45856
+ var modifierNames = Object.keys(styles.modifier);
45857
+ var foregroundColorNames = Object.keys(styles.color);
45858
+ var backgroundColorNames = Object.keys(styles.bgColor);
45859
+ var underlineColorNames = Object.keys(styles.underlineColor);
45860
+ var colorNames = [...foregroundColorNames, ...backgroundColorNames];
45861
+ function assembleStyles() {
45862
+ const codes = new Map;
45863
+ for (const [groupName, group] of Object.entries(styles)) {
45864
+ for (const [styleName, style] of Object.entries(group)) {
45865
+ styles[styleName] = {
45866
+ open: `\x1B[${style[0]}m`,
45867
+ close: `\x1B[${style[1]}m`
45868
+ };
45869
+ group[styleName] = styles[styleName];
45870
+ codes.set(Number.parseInt(style[0], 10), style[1]);
45871
+ }
45872
+ Object.defineProperty(styles, groupName, {
45873
+ value: group,
45874
+ enumerable: false
45875
+ });
45876
+ }
45877
+ Object.defineProperty(styles, "codes", {
45878
+ value: codes,
45879
+ enumerable: false
45880
+ });
45881
+ styles.color.close = "\x1B[39m";
45882
+ styles.bgColor.close = "\x1B[49m";
45883
+ styles.underlineColor.close = "\x1B[59m";
45884
+ styles.color.ansi = wrapAnsi16();
45885
+ styles.color.ansi256 = wrapAnsi256();
45886
+ styles.color.ansi16m = wrapAnsi16m();
45887
+ styles.bgColor.ansi = wrapAnsi16(ANSI_BACKGROUND_OFFSET);
45888
+ styles.bgColor.ansi256 = wrapAnsi256(ANSI_BACKGROUND_OFFSET);
45889
+ styles.bgColor.ansi16m = wrapAnsi16m(ANSI_BACKGROUND_OFFSET);
45890
+ styles.underlineColor.ansi = wrapUnderlineAnsi;
45891
+ styles.underlineColor.ansi256 = wrapAnsi256(ANSI_UNDERLINE_OFFSET);
45892
+ styles.underlineColor.ansi16m = wrapAnsi16m(ANSI_UNDERLINE_OFFSET);
45893
+ Object.defineProperties(styles, {
45894
+ rgbToAnsi256: {
45895
+ value(red, green, blue) {
45896
+ if (red === green && green === blue) {
45897
+ if (red < 8) {
45898
+ return 16;
45899
+ }
45900
+ if (red > 248) {
45901
+ return 231;
45902
+ }
45903
+ return Math.round((red - 8) / 247 * 24) + 232;
45904
+ }
45905
+ return 16 + 36 * Math.round(red / 255 * 5) + 6 * Math.round(green / 255 * 5) + Math.round(blue / 255 * 5);
45906
+ },
45907
+ enumerable: false
45908
+ },
45909
+ hexToRgb: {
45910
+ value(hex) {
45911
+ const matches = /[\da-f]{6}|[\da-f]{3}/i.exec(hex.toString(16));
45912
+ if (!matches) {
45913
+ return [0, 0, 0];
45914
+ }
45915
+ let [colorString] = matches;
45916
+ if (colorString.length === 3) {
45917
+ colorString = [...colorString].map((character) => character + character).join("");
45918
+ }
45919
+ const integer = Number.parseInt(colorString, 16);
45920
+ return [
45921
+ integer >> 16 & 255,
45922
+ integer >> 8 & 255,
45923
+ integer & 255
45924
+ ];
45925
+ },
45926
+ enumerable: false
45927
+ },
45928
+ hexToAnsi256: {
45929
+ value: (hex) => styles.rgbToAnsi256(...styles.hexToRgb(hex)),
45930
+ enumerable: false
45931
+ },
45932
+ ansi256ToAnsi: {
45933
+ value(code) {
45934
+ if (code < 8) {
45935
+ return 30 + code;
45936
+ }
45937
+ if (code < 16) {
45938
+ return 90 + (code - 8);
45939
+ }
45940
+ let red;
45941
+ let green;
45942
+ let blue;
45943
+ if (code >= 232) {
45944
+ red = ((code - 232) * 10 + 8) / 255;
45945
+ green = red;
45946
+ blue = red;
45947
+ } else {
45948
+ code -= 16;
45949
+ const remainder = code % 36;
45950
+ red = Math.floor(code / 36) / 5;
45951
+ green = Math.floor(remainder / 6) / 5;
45952
+ blue = remainder % 6 / 5;
45953
+ }
45954
+ const value = Math.max(red, green, blue) * 2;
45955
+ if (value === 0) {
45956
+ return 30;
45957
+ }
45958
+ let result = 30 + (Math.round(blue) << 2 | Math.round(green) << 1 | Math.round(red));
45959
+ if (value === 2) {
45960
+ result += 60;
45961
+ }
45962
+ return result;
45963
+ },
45964
+ enumerable: false
45965
+ },
45966
+ rgbToAnsi: {
45967
+ value: (red, green, blue) => styles.ansi256ToAnsi(styles.rgbToAnsi256(red, green, blue)),
45968
+ enumerable: false
45969
+ },
45970
+ hexToAnsi: {
45971
+ value: (hex) => styles.ansi256ToAnsi(styles.hexToAnsi256(hex)),
45972
+ enumerable: false
45973
+ }
45974
+ });
45975
+ return styles;
45976
+ }
45977
+ var ansiStyles = assembleStyles();
45978
+ var ansi_styles_default = ansiStyles;
45979
+
45980
+ // node_modules/chalk/source/vendor/supports-color/index.js
45981
+ import process2 from "process";
45982
+ import os3 from "os";
45983
+ import tty from "tty";
45984
+ function hasFlag(flag, argv = globalThis.Deno ? globalThis.Deno.args : process2.argv) {
45985
+ const prefix = flag.startsWith("-") ? "" : flag.length === 1 ? "-" : "--";
45986
+ const position = argv.indexOf(prefix + flag);
45987
+ const terminatorPosition = argv.indexOf("--");
45988
+ return position !== -1 && (terminatorPosition === -1 || position < terminatorPosition);
45989
+ }
45990
+ var { env } = process2;
45991
+ var flagForceColor;
45992
+ if (hasFlag("no-color") || hasFlag("no-colors") || hasFlag("color=false") || hasFlag("color=never")) {
45993
+ flagForceColor = 0;
45994
+ } else if (hasFlag("color") || hasFlag("colors") || hasFlag("color=true") || hasFlag("color=always")) {
45995
+ flagForceColor = 1;
45996
+ }
45997
+ function hasNumericForceColor() {
45998
+ return /^\d+$/.test(env.FORCE_COLOR);
45999
+ }
46000
+ function envForceColor() {
46001
+ if (!("FORCE_COLOR" in env)) {
46002
+ return;
46003
+ }
46004
+ if (env.FORCE_COLOR === "false") {
46005
+ return 0;
46006
+ }
46007
+ if (env.FORCE_COLOR === "true" || env.FORCE_COLOR.length === 0) {
46008
+ return 1;
46009
+ }
46010
+ if (!hasNumericForceColor()) {
46011
+ return;
46012
+ }
46013
+ return Math.min(Number.parseInt(env.FORCE_COLOR, 10), 3);
46014
+ }
46015
+ function translateLevel(level) {
46016
+ if (level === 0) {
46017
+ return false;
46018
+ }
46019
+ return {
46020
+ level,
46021
+ hasBasic: true,
46022
+ has256: level >= 2,
46023
+ has16m: level >= 3
46024
+ };
46025
+ }
46026
+ function _supportsColor(haveStream, { streamIsTTY, sniffFlags = true } = {}) {
46027
+ const noFlagForceColor = envForceColor();
46028
+ if (noFlagForceColor !== undefined) {
46029
+ flagForceColor = noFlagForceColor;
46030
+ }
46031
+ const forceColor = sniffFlags ? flagForceColor : noFlagForceColor;
46032
+ if (forceColor === 0) {
46033
+ return 0;
46034
+ }
46035
+ if (sniffFlags) {
46036
+ if (hasFlag("color=16m") || hasFlag("color=full") || hasFlag("color=truecolor")) {
46037
+ return 3;
46038
+ }
46039
+ if (hasFlag("color=256")) {
46040
+ return 2;
46041
+ }
46042
+ }
46043
+ if (forceColor !== undefined && hasNumericForceColor()) {
46044
+ return forceColor;
46045
+ }
46046
+ if ("TF_BUILD" in env && "AGENT_NAME" in env) {
46047
+ return 1;
46048
+ }
46049
+ if (haveStream && !streamIsTTY && forceColor === undefined) {
46050
+ return 0;
46051
+ }
46052
+ const min = forceColor || 0;
46053
+ if (env.TERM === "dumb") {
46054
+ return min;
46055
+ }
46056
+ if (process2.platform === "win32") {
46057
+ const osRelease = os3.release().split(".");
46058
+ if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
46059
+ return Number(osRelease[2]) >= 14931 ? 3 : 2;
46060
+ }
46061
+ return 1;
46062
+ }
46063
+ if ("CI" in env) {
46064
+ if (["GITHUB_ACTIONS", "GITEA_ACTIONS", "CIRCLECI"].some((key) => (key in env))) {
46065
+ return 3;
46066
+ }
46067
+ if (["TRAVIS", "APPVEYOR", "GITLAB_CI", "BUILDKITE", "DRONE"].some((sign) => (sign in env)) || env.CI_NAME === "codeship") {
46068
+ return 1;
46069
+ }
46070
+ return min;
46071
+ }
46072
+ if ("TEAMCITY_VERSION" in env) {
46073
+ return /^(?:9\.0*[1-9]\d*\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ? 1 : 0;
46074
+ }
46075
+ if (env.COLORTERM === "truecolor") {
46076
+ return 3;
46077
+ }
46078
+ if (env.TERM === "xterm-kitty") {
46079
+ return 3;
46080
+ }
46081
+ if (env.TERM === "xterm-ghostty") {
46082
+ return 3;
46083
+ }
46084
+ if (env.TERM === "wezterm") {
46085
+ return 3;
46086
+ }
46087
+ if ("TERM_PROGRAM" in env) {
46088
+ const version = Number.parseInt((env.TERM_PROGRAM_VERSION || "").split(".", 1)[0], 10);
46089
+ switch (env.TERM_PROGRAM) {
46090
+ case "iTerm.app": {
46091
+ return version >= 3 ? 3 : 2;
46092
+ }
46093
+ case "Apple_Terminal": {
46094
+ return 2;
46095
+ }
46096
+ }
46097
+ }
46098
+ if (/-256(?:color)?$/i.test(env.TERM)) {
46099
+ return 2;
46100
+ }
46101
+ if (/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM)) {
46102
+ return 1;
46103
+ }
46104
+ if ("COLORTERM" in env) {
46105
+ return 1;
46106
+ }
46107
+ return min;
46108
+ }
46109
+ function createSupportsColor(stream, options = {}) {
46110
+ const level = _supportsColor(stream, {
46111
+ streamIsTTY: stream && stream.isTTY,
46112
+ ...options
46113
+ });
46114
+ return translateLevel(level);
46115
+ }
46116
+ var supportsColor = {
46117
+ stdout: createSupportsColor({ isTTY: tty.isatty(1) }),
46118
+ stderr: createSupportsColor({ isTTY: tty.isatty(2) })
46119
+ };
46120
+ var supports_color_default = supportsColor;
46121
+
46122
+ // node_modules/chalk/source/index.js
46123
+ var { stdout: stdoutColor, stderr: stderrColor } = supports_color_default;
46124
+ var GENERATOR = Symbol("GENERATOR");
46125
+ var STYLER = Symbol("STYLER");
46126
+ var IS_EMPTY = Symbol("IS_EMPTY");
46127
+ var LEVEL = Symbol("LEVEL");
46128
+ var styles2 = Object.create(null);
46129
+ var assertValidLevel = (level) => {
46130
+ if (!Number.isSafeInteger(level) || level < 0 || level > 3) {
46131
+ throw new Error("The `level` should be an integer from 0 to 3");
46132
+ }
46133
+ };
46134
+ var levelDescriptor = {
46135
+ enumerable: true,
46136
+ get() {
46137
+ return this[LEVEL];
46138
+ },
46139
+ set(level) {
46140
+ assertValidLevel(level);
46141
+ this[LEVEL] = level;
46142
+ }
46143
+ };
46144
+ var applyOptions = (object, options = {}) => {
46145
+ if (options.level !== undefined) {
46146
+ assertValidLevel(options.level);
46147
+ }
46148
+ const colorLevel = stdoutColor ? stdoutColor.level : 0;
46149
+ object[LEVEL] = options.level === undefined ? colorLevel : options.level;
46150
+ };
46151
+ var chalkFactory = (options) => {
46152
+ const chalk = (...strings) => strings.join(" ");
46153
+ applyOptions(chalk, options);
46154
+ Object.setPrototypeOf(chalk, createChalk.prototype);
46155
+ return chalk;
46156
+ };
46157
+ function createChalk(options) {
46158
+ return chalkFactory(options);
46159
+ }
46160
+ Object.setPrototypeOf(createChalk.prototype, Function.prototype);
46161
+ for (const [styleName, style] of Object.entries(ansi_styles_default)) {
46162
+ styles2[styleName] = {
46163
+ get() {
46164
+ const builder = createBuilder(this, createStyler(style.open, style.close, this[STYLER]), this[IS_EMPTY]);
46165
+ Object.defineProperty(this, styleName, { value: builder });
46166
+ return builder;
46167
+ }
46168
+ };
46169
+ }
46170
+ styles2.visible = {
46171
+ get() {
46172
+ const builder = createBuilder(this, this[STYLER], true);
46173
+ Object.defineProperty(this, "visible", { value: builder });
46174
+ return builder;
46175
+ }
46176
+ };
46177
+ var createModelConverters = (model, type) => {
46178
+ const style = ansi_styles_default[type];
46179
+ if (model === "rgb") {
46180
+ const ansi2 = (red, green, blue) => style.ansi(ansi_styles_default.rgbToAnsi(red, green, blue));
46181
+ const ansi256 = (red, green, blue) => style.ansi256(ansi_styles_default.rgbToAnsi256(red, green, blue));
46182
+ return [ansi2, ansi2, ansi256, style.ansi16m];
46183
+ }
46184
+ if (model === "hex") {
46185
+ const ansi2 = (hex) => style.ansi(ansi_styles_default.hexToAnsi(hex));
46186
+ const ansi256 = (hex) => style.ansi256(ansi_styles_default.hexToAnsi256(hex));
46187
+ return [ansi2, ansi2, ansi256, (hex) => style.ansi16m(...ansi_styles_default.hexToRgb(hex))];
46188
+ }
46189
+ const ansi = (code) => style.ansi(ansi_styles_default.ansi256ToAnsi(code));
46190
+ return [ansi, ansi, style.ansi256, style.ansi256];
46191
+ };
46192
+ var usedModels = ["rgb", "hex", "ansi256"];
46193
+ for (const model of usedModels) {
46194
+ const capitalizedModel = model[0].toUpperCase() + model.slice(1);
46195
+ for (const [styleName, type] of [
46196
+ [model, "color"],
46197
+ ["bg" + capitalizedModel, "bgColor"],
46198
+ ["underline" + capitalizedModel, "underlineColor"]
46199
+ ]) {
46200
+ const { close } = ansi_styles_default[type];
46201
+ const converters = createModelConverters(model, type);
46202
+ styles2[styleName] = {
46203
+ get() {
46204
+ const styleFunction = function(first, second, third) {
46205
+ const open = converters[this.level](first, second, third);
46206
+ return createBuilder(this, createStyler(open, close, this[STYLER]), this[IS_EMPTY]);
46207
+ };
46208
+ Object.defineProperty(this, styleName, { value: styleFunction });
46209
+ return styleFunction;
46210
+ }
46211
+ };
46212
+ }
46213
+ }
46214
+ var proto = Object.defineProperties(() => {}, {
46215
+ ...styles2,
46216
+ level: {
46217
+ enumerable: true,
46218
+ get() {
46219
+ return this[GENERATOR].level;
46220
+ },
46221
+ set(level) {
46222
+ this[GENERATOR].level = level;
46223
+ }
46224
+ }
46225
+ });
46226
+ var createStyler = (open, close, parent) => {
46227
+ let openAll;
46228
+ let closeAll;
46229
+ if (parent === undefined) {
46230
+ openAll = open;
46231
+ closeAll = close;
46232
+ } else {
46233
+ openAll = parent.openAll + open;
46234
+ closeAll = close + parent.closeAll;
46235
+ }
46236
+ return {
46237
+ open,
46238
+ close,
46239
+ openAll,
46240
+ closeAll,
46241
+ parent
46242
+ };
46243
+ };
46244
+ var createBuilder = (self, _styler, _isEmpty) => {
46245
+ const builder = (...arguments_) => {
46246
+ if (arguments_.length === 1) {
46247
+ return applyStyle(builder, "" + arguments_[0]);
46248
+ }
46249
+ if (arguments_.length === 2) {
46250
+ return applyStyle(builder, arguments_[0] + " " + arguments_[1]);
46251
+ }
46252
+ return applyStyle(builder, arguments_.join(" "));
46253
+ };
46254
+ Object.setPrototypeOf(builder, proto);
46255
+ builder[GENERATOR] = self[GENERATOR] ?? self;
46256
+ builder[STYLER] = _styler;
46257
+ builder[IS_EMPTY] = _isEmpty;
46258
+ return builder;
46259
+ };
46260
+ var applyStyle = (self, string) => {
46261
+ if (self[GENERATOR][LEVEL] <= 0 || !string) {
46262
+ return self[IS_EMPTY] ? "" : string;
46263
+ }
46264
+ let styler = self[STYLER];
46265
+ if (styler === undefined) {
46266
+ return string;
46267
+ }
46268
+ const { openAll, closeAll } = styler;
46269
+ if (string.includes("\x1B")) {
46270
+ while (styler !== undefined) {
46271
+ string = stringReplaceAll(string, styler.close, styler.open);
46272
+ styler = styler.parent;
46273
+ }
46274
+ }
46275
+ const lfIndex = string.indexOf(`
46276
+ `);
46277
+ if (lfIndex !== -1) {
46278
+ string = stringEncaseCRLFWithFirstIndex(string, closeAll, openAll, lfIndex);
46279
+ }
46280
+ return openAll + string + closeAll;
46281
+ };
46282
+ Object.defineProperties(createChalk.prototype, { ...styles2, level: levelDescriptor });
46283
+ var chalk = createChalk();
46284
+ var chalkStderr = createChalk({ level: stderrColor ? stderrColor.level : 0 });
46285
+ var source_default = chalk;
46286
+
46287
+ // src/diff-view.ts
45681
46288
  var CONTEXT_LINES = 3;
45682
46289
  var MAX_OUTPUT_LINES = 80;
45683
46290
  var SYNTAX_THEME = {
45684
- default: import_chalk.default.rgb(248, 248, 242),
45685
- keyword: import_chalk.default.rgb(249, 38, 114),
45686
- built_in: import_chalk.default.rgb(102, 217, 239),
45687
- type: import_chalk.default.rgb(102, 217, 239),
45688
- literal: import_chalk.default.rgb(174, 129, 255),
45689
- number: import_chalk.default.rgb(174, 129, 255),
45690
- regexp: import_chalk.default.rgb(230, 219, 116),
45691
- string: import_chalk.default.rgb(230, 219, 116),
45692
- subst: import_chalk.default.rgb(248, 248, 242),
45693
- symbol: import_chalk.default.rgb(174, 129, 255),
45694
- class: import_chalk.default.rgb(166, 226, 46),
45695
- function: import_chalk.default.rgb(166, 226, 46),
45696
- title: import_chalk.default.rgb(166, 226, 46),
45697
- params: import_chalk.default.rgb(253, 151, 31),
45698
- comment: import_chalk.default.rgb(117, 113, 94),
45699
- doctag: import_chalk.default.rgb(117, 113, 94),
45700
- meta: import_chalk.default.rgb(249, 38, 114),
45701
- "meta-keyword": import_chalk.default.rgb(249, 38, 114),
45702
- "meta-string": import_chalk.default.rgb(230, 219, 116),
45703
- section: import_chalk.default.rgb(166, 226, 46),
45704
- tag: import_chalk.default.rgb(249, 38, 114),
45705
- name: import_chalk.default.rgb(249, 38, 114),
45706
- attr: import_chalk.default.rgb(166, 226, 46),
45707
- attribute: import_chalk.default.rgb(166, 226, 46),
45708
- variable: import_chalk.default.rgb(248, 248, 242),
45709
- bullet: import_chalk.default.rgb(174, 129, 255),
45710
- code: import_chalk.default.rgb(230, 219, 116),
45711
- emphasis: import_chalk.default.italic,
45712
- strong: import_chalk.default.bold,
45713
- formula: import_chalk.default.rgb(248, 248, 242),
45714
- link: import_chalk.default.rgb(102, 217, 239).underline,
45715
- quote: import_chalk.default.rgb(117, 113, 94).italic,
45716
- addition: import_chalk.default.rgb(166, 226, 46),
45717
- deletion: import_chalk.default.rgb(249, 38, 114)
46291
+ default: source_default.rgb(248, 248, 242),
46292
+ keyword: source_default.rgb(249, 38, 114),
46293
+ built_in: source_default.rgb(102, 217, 239),
46294
+ type: source_default.rgb(102, 217, 239),
46295
+ literal: source_default.rgb(174, 129, 255),
46296
+ number: source_default.rgb(174, 129, 255),
46297
+ regexp: source_default.rgb(230, 219, 116),
46298
+ string: source_default.rgb(230, 219, 116),
46299
+ subst: source_default.rgb(248, 248, 242),
46300
+ symbol: source_default.rgb(174, 129, 255),
46301
+ class: source_default.rgb(166, 226, 46),
46302
+ function: source_default.rgb(166, 226, 46),
46303
+ title: source_default.rgb(166, 226, 46),
46304
+ params: source_default.rgb(253, 151, 31),
46305
+ comment: source_default.rgb(117, 113, 94),
46306
+ doctag: source_default.rgb(117, 113, 94),
46307
+ meta: source_default.rgb(249, 38, 114),
46308
+ "meta-keyword": source_default.rgb(249, 38, 114),
46309
+ "meta-string": source_default.rgb(230, 219, 116),
46310
+ section: source_default.rgb(166, 226, 46),
46311
+ tag: source_default.rgb(249, 38, 114),
46312
+ name: source_default.rgb(249, 38, 114),
46313
+ attr: source_default.rgb(166, 226, 46),
46314
+ attribute: source_default.rgb(166, 226, 46),
46315
+ variable: source_default.rgb(248, 248, 242),
46316
+ bullet: source_default.rgb(174, 129, 255),
46317
+ code: source_default.rgb(230, 219, 116),
46318
+ emphasis: source_default.italic,
46319
+ strong: source_default.bold,
46320
+ formula: source_default.rgb(248, 248, 242),
46321
+ link: source_default.rgb(102, 217, 239).underline,
46322
+ quote: source_default.rgb(117, 113, 94).italic,
46323
+ addition: source_default.rgb(166, 226, 46),
46324
+ deletion: source_default.rgb(249, 38, 114)
45718
46325
  };
45719
46326
  function patchResets(text, bg) {
45720
46327
  return text.replace(/\x1b\[0m/g, `\x1B[0m${bg}`);
@@ -46268,13 +46875,13 @@ async function listAgentsCommand(api, opts) {
46268
46875
  // src/agent-command.ts
46269
46876
  function printAgentHelp() {
46270
46877
  process.stderr.write(`
46271
- todoforai-cli agent \u2014 inspect and update agent settings
46878
+ tfa-cli agent \u2014 inspect and update agent settings
46272
46879
 
46273
46880
  Usage:
46274
- todoforai-cli agent list List agents (name, model, id, paths)
46275
- todoforai-cli agent get <agent> Show a single agent's settings
46276
- todoforai-cli agent update <agent> <field=value>\u2026 Update one or more settings
46277
- todoforai-cli agent delete <agent> Delete an agent configuration (asks to confirm)
46881
+ tfa-cli agent list List agents (name, model, id, paths)
46882
+ tfa-cli agent get <agent> Show a single agent's settings
46883
+ tfa-cli agent update <agent> <field=value>\u2026 Update one or more settings
46884
+ tfa-cli agent delete <agent> Delete an agent configuration (asks to confirm)
46278
46885
 
46279
46886
  <agent> is a name or id (unique partial name also works).
46280
46887
  Fields map directly to agent settings; values are parsed as JSON when possible
@@ -46287,9 +46894,9 @@ Fields map directly to agent settings; values are parsed as JSON when possible
46287
46894
  name agent display name
46288
46895
 
46289
46896
  Examples:
46290
- todoforai-cli agent update <agent> model=claude
46291
- todoforai-cli agent update <agent> model=anthropic:anthropic/claude-opus-5 temperature=0.5
46292
- todoforai-cli agent update <agent> sysmsg="You are a terse video editor."
46897
+ tfa-cli agent update <agent> model=claude
46898
+ tfa-cli agent update <agent> model=anthropic:anthropic/claude-opus-5 temperature=0.5
46899
+ tfa-cli agent update <agent> sysmsg="You are a terse video editor."
46293
46900
  `);
46294
46901
  }
46295
46902
  var FIELD_ALIASES = {
@@ -46357,7 +46964,7 @@ async function agentCommand(api, positionals, args, formatPath) {
46357
46964
  const query = positionals[2];
46358
46965
  const assignments = positionals.slice(3);
46359
46966
  if (!query || !assignments.length) {
46360
- process.stderr.write(`${RED}Usage: todoforai-cli agent update <name|id> <field=value>\u2026${RESET}
46967
+ process.stderr.write(`${RED}Usage: tfa-cli agent update <name|id> <field=value>\u2026${RESET}
46361
46968
  `);
46362
46969
  process.exit(2);
46363
46970
  }
@@ -46378,7 +46985,7 @@ async function agentCommand(api, positionals, args, formatPath) {
46378
46985
  if (sub === "delete" || sub === "rm") {
46379
46986
  const query = positionals[2];
46380
46987
  if (!query) {
46381
- process.stderr.write(`${RED}Usage: todoforai-cli agent delete <name|id>${RESET}
46988
+ process.stderr.write(`${RED}Usage: tfa-cli agent delete <name|id>${RESET}
46382
46989
  `);
46383
46990
  process.exit(2);
46384
46991
  }
@@ -46601,6 +47208,87 @@ async function listTodosCommand(api, defaultProjectId, argv) {
46601
47208
  `);
46602
47209
  }
46603
47210
 
47211
+ // src/isolated.ts
47212
+ import { spawn as spawn2 } from "child_process";
47213
+ async function mintMayflyToken(apiUrl, apiKey, todoId) {
47214
+ const res = await fetch(`${apiUrl}${restBasePath(apiKey)}/cli/mayfly/token`, {
47215
+ method: "POST",
47216
+ headers: { "content-type": "application/json", "x-api-key": apiKey },
47217
+ body: JSON.stringify({ todoId }),
47218
+ signal: AbortSignal.timeout(30000)
47219
+ });
47220
+ const text = await res.text();
47221
+ if (!res.ok)
47222
+ throw new Error(`--isolated: could not mint a session token (${res.status} ${text.slice(0, 200)}).`);
47223
+ const token = JSON.parse(text)?.token;
47224
+ if (typeof token !== "string" || !token)
47225
+ throw new Error("--isolated: token mint returned no token.");
47226
+ return token;
47227
+ }
47228
+ async function spawnMayflyBridge(apiUrl, todoId, workspace, opts = {}) {
47229
+ if (!hasBridge())
47230
+ throw new Error("--isolated requires `todoforai-bridge` on PATH.");
47231
+ const loginless = !!opts.apiKey && !opts.apiKey.startsWith("dst_");
47232
+ if (!loginless && !ensureBridgeCredentials(apiUrl))
47233
+ throw new Error("`todoforai-bridge login` did not complete.");
47234
+ const sessionToken = loginless ? await mintMayflyToken(apiUrl, opts.apiKey, todoId) : null;
47235
+ const args = [...bridgeRunArgs(apiUrl), "--mayfly", todoId, "--workspace", workspace];
47236
+ const env2 = { ...process.env };
47237
+ if (sessionToken) {
47238
+ for (const n of ["API_TOKEN", "MAYFLY_API_KEY"])
47239
+ for (const p of ["TODOFORAI_", "TODO4AI_"])
47240
+ delete env2[p + n];
47241
+ env2.TODOFORAI_MAYFLY_TOKEN = sessionToken;
47242
+ }
47243
+ const child = spawn2("todoforai-bridge", args, { stdio: ["ignore", "pipe", "pipe"], env: env2 });
47244
+ let stopped = false;
47245
+ const stop = () => {
47246
+ if (stopped)
47247
+ return;
47248
+ stopped = true;
47249
+ try {
47250
+ child.kill("SIGTERM");
47251
+ } catch {}
47252
+ };
47253
+ process.on("exit", stop);
47254
+ const readyRx = new RegExp(`^BRIDGE_READY id=mayfly-${todoId}\\b`, "m");
47255
+ await new Promise((resolvePromise, reject) => {
47256
+ const timeoutMs = opts.timeoutMs ?? 15000;
47257
+ let buf = "";
47258
+ let lastErr = "";
47259
+ const timer = setTimeout(() => {
47260
+ stop();
47261
+ reject(new Error(`Isolated bridge not ready after ${timeoutMs / 1000}s.${lastErr ? ` ${lastErr}` : ""}`));
47262
+ }, timeoutMs);
47263
+ child.stdout.on("data", (d) => {
47264
+ buf += d.toString();
47265
+ if (readyRx.test(buf)) {
47266
+ clearTimeout(timer);
47267
+ resolvePromise();
47268
+ }
47269
+ });
47270
+ child.stderr.on("data", (d) => {
47271
+ const s = d.toString();
47272
+ const err = s.match(/^error: .*$/m);
47273
+ if (err)
47274
+ lastErr = err[0];
47275
+ if (opts.debug)
47276
+ process.stderr.write(d);
47277
+ });
47278
+ child.on("error", (err) => {
47279
+ clearTimeout(timer);
47280
+ reject(err);
47281
+ });
47282
+ child.on("exit", (code) => {
47283
+ if (!stopped) {
47284
+ clearTimeout(timer);
47285
+ reject(new Error(lastErr || `Isolated bridge exited early (code ${code}).`));
47286
+ }
47287
+ });
47288
+ });
47289
+ return { child, stop };
47290
+ }
47291
+
46604
47292
  // src/index.ts
46605
47293
  try {
46606
47294
  const pkgPath = path3.resolve(fileURLToPath(import.meta.url), "../../package.json");
@@ -46689,7 +47377,7 @@ Cancelled by user (Ctrl+C)
46689
47377
  const groupName = args["group-name"] === undefined ? undefined : String(args["group-name"]);
46690
47378
  if (positionals[0] === "start") {
46691
47379
  if (!positionals[1] && !args.template) {
46692
- process.stderr.write(`${RED}Usage: todoforai-cli start <todo-id>${RESET}
47380
+ process.stderr.write(`${RED}Usage: tfa-cli start <todo-id>${RESET}
46693
47381
  `);
46694
47382
  process.exit(2);
46695
47383
  }
@@ -46716,6 +47404,23 @@ Cancelled by user (Ctrl+C)
46716
47404
  printUsage();
46717
47405
  process.exit(0);
46718
47406
  }
47407
+ if (args.isolated) {
47408
+ if (args.resume || args.continue) {
47409
+ process.stderr.write(`Error: --isolated cannot be combined with --resume/--continue (isolation binds at create)
47410
+ `);
47411
+ process.exit(2);
47412
+ }
47413
+ if (args["no-watch"]) {
47414
+ process.stderr.write(`Error: --isolated requires watching the run (drop --no-watch)
47415
+ `);
47416
+ process.exit(2);
47417
+ }
47418
+ if (args.template) {
47419
+ process.stderr.write(`Error: --isolated does not support --template yet
47420
+ `);
47421
+ process.exit(2);
47422
+ }
47423
+ }
46719
47424
  const cfg = new ConfigStore(args["config-path"]);
46720
47425
  if (args["show-config"]) {
46721
47426
  console.log(`Config file: ${formatPathWithTilde(cfg.path)}`);
@@ -46756,12 +47461,12 @@ Cancelled by user (Ctrl+C)
46756
47461
 
46757
47462
  `);
46758
47463
  try {
46759
- const { spawn: spawn2 } = await import("child_process");
47464
+ const { spawn: spawn3 } = await import("child_process");
46760
47465
  if (process.platform === "win32") {
46761
- spawn2("cmd", ["/c", "start", "", url], { stdio: "ignore", detached: true }).unref();
47466
+ spawn3("cmd", ["/c", "start", "", url], { stdio: "ignore", detached: true }).unref();
46762
47467
  } else {
46763
47468
  const cmd = process.platform === "darwin" ? "open" : "xdg-open";
46764
- spawn2(cmd, [url], { stdio: "ignore", detached: true }).unref();
47469
+ spawn3(cmd, [url], { stdio: "ignore", detached: true }).unref();
46765
47470
  }
46766
47471
  } catch {}
46767
47472
  process.stderr.write(`Waiting for approval (expires in ${Math.round(expiresIn / 60)}min)...
@@ -46801,6 +47506,20 @@ Cancelled by user (Ctrl+C)
46801
47506
  if (!apiKey) {
46802
47507
  apiKey = await deviceLogin();
46803
47508
  }
47509
+ if (args["debug-dump"]) {
47510
+ const debugSecret = getEnv("DEBUG_SECRET");
47511
+ if (!debugSecret) {
47512
+ process.stderr.write(`Error: --debug-dump requires TODOFORAI_DEBUG_SECRET
47513
+ `);
47514
+ process.exit(2);
47515
+ }
47516
+ let extraHeaders = {};
47517
+ try {
47518
+ extraHeaders = JSON.parse(process.env.TODOFORAI_EXTRA_HEADERS || "{}");
47519
+ } catch {}
47520
+ extraHeaders["x-tfa-debug"] = debugSecret;
47521
+ process.env.TODOFORAI_EXTRA_HEADERS = JSON.stringify(extraHeaders);
47522
+ }
46804
47523
  const api = new ApiClient(apiUrl, apiKey, args["user-id"]);
46805
47524
  if (args["user-id"] && args.safe) {
46806
47525
  process.stderr.write(`Error: --safe is not supported with --user-id
@@ -46831,7 +47550,7 @@ Cancelled by user (Ctrl+C)
46831
47550
  if (positionals[0] === "delete") {
46832
47551
  const todoId = positionals[1];
46833
47552
  if (!todoId) {
46834
- process.stderr.write(`${RED}Usage: todoforai-cli delete <todo-id>${RESET}
47553
+ process.stderr.write(`${RED}Usage: tfa-cli delete <todo-id>${RESET}
46835
47554
  `);
46836
47555
  process.exit(2);
46837
47556
  }
@@ -46844,7 +47563,7 @@ Cancelled by user (Ctrl+C)
46844
47563
  const [, todoId, ...rest] = positionals;
46845
47564
  const content2 = rest.join(" ") || await readStdin();
46846
47565
  if (!todoId || !content2) {
46847
- process.stderr.write(`${RED}Usage: todoforai-cli addmessage <todo-id> "content"${RESET}
47566
+ process.stderr.write(`${RED}Usage: tfa-cli addmessage <todo-id> "content"${RESET}
46848
47567
  `);
46849
47568
  process.exit(2);
46850
47569
  }
@@ -46863,7 +47582,7 @@ Cancelled by user (Ctrl+C)
46863
47582
  const todoId = positionals[2] || (!explicitProject ? getEnv("TODO_ID") || cfgScope.data.last_todo_id : undefined);
46864
47583
  const projectId2 = todoId ? undefined : args.project || getEnv("PROJECT_ID") || cfgScope.data.default_project_id;
46865
47584
  if (!todoId && !projectId2) {
46866
- process.stderr.write(`${RED}Usage: todoforai-cli show list [todo-id] [--project <id>] [--card <name>]${RESET}
47585
+ process.stderr.write(`${RED}Usage: tfa-cli show list [todo-id] [--project <id>] [--card <name>]${RESET}
46867
47586
  `);
46868
47587
  process.exit(2);
46869
47588
  }
@@ -46888,7 +47607,7 @@ Cancelled by user (Ctrl+C)
46888
47607
  const [, filePath, todoArg] = positionals;
46889
47608
  const todoId = todoArg || getEnv("TODO_ID") || cfgScope.data.last_todo_id;
46890
47609
  if (!filePath || !todoId) {
46891
- process.stderr.write(`${RED}Usage: todoforai-cli show <file|-> [todo-id]${RESET}
47610
+ process.stderr.write(`${RED}Usage: tfa-cli show <file|-> [todo-id]${RESET}
46892
47611
  `);
46893
47612
  process.exit(2);
46894
47613
  }
@@ -46922,7 +47641,7 @@ Cancelled by user (Ctrl+C)
46922
47641
  const [, url, todoArg] = positionals;
46923
47642
  const todoId = todoArg || getEnv("TODO_ID") || cfgScope.data.last_todo_id;
46924
47643
  if (!url || !todoId) {
46925
- process.stderr.write(`${RED}Usage: todoforai-cli open <url> [todo-id]${RESET}
47644
+ process.stderr.write(`${RED}Usage: tfa-cli open <url> [todo-id]${RESET}
46926
47645
  `);
46927
47646
  process.exit(2);
46928
47647
  }
@@ -46936,7 +47655,7 @@ Cancelled by user (Ctrl+C)
46936
47655
  if (positionals[0] === "recommend") {
46937
47656
  const templateId = args.template || positionals[1];
46938
47657
  if (!templateId) {
46939
- process.stderr.write(`${RED}Usage: todoforai-cli recommend --template <id> [--note "why"] [--priority high|medium|low] [--title "..."] [--group <slug> [--group-name "..."] [--group-description "..."]] [--project <id>]${RESET}
47658
+ process.stderr.write(`${RED}Usage: tfa-cli recommend --template <id> [--note "why"] [--priority high|medium|low] [--title "..."] [--group <slug> [--group-name "..."] [--group-description "..."]] [--project <id>]${RESET}
46940
47659
  `);
46941
47660
  process.stderr.write(`${DIM}Create a template first with: todoregistry-cli create --name ... --description ... --body @prompt.md${RESET}
46942
47661
  `);
@@ -46983,7 +47702,7 @@ Cancelled by user (Ctrl+C)
46983
47702
  seedProjectId = projects2.find((p) => p.project?.isDefault)?.project?.id || projects2[0]?.project?.id;
46984
47703
  }
46985
47704
  if (!seedProjectId) {
46986
- process.stderr.write(`${RED}Usage: todoforai-cli claim mint --seed <projectId> [--emails a@x,b@y] [--ttl <sec>]${RESET}
47705
+ process.stderr.write(`${RED}Usage: tfa-cli claim mint --seed <projectId> [--emails a@x,b@y] [--ttl <sec>]${RESET}
46987
47706
  `);
46988
47707
  process.exit(2);
46989
47708
  }
@@ -47226,7 +47945,12 @@ Resumed: ${CYAN}${getFrontendUrl(apiUrl, todoId)}${RESET}
47226
47945
  }
47227
47946
  const ws = args["no-watch"] ? null : new FrontendWebSocket(apiUrl, apiKey);
47228
47947
  const wsReady = ws ? ws.connect() : null;
47229
- const bridgeReady = !args["no-bridge"] && !args["no-watch"] ? ensureBridgeRunning(apiUrl, apiKey) : null;
47948
+ const bridgeReady = !args["no-bridge"] && !args["no-watch"] && !args.isolated ? ensureBridgeRunning(apiUrl, apiKey) : null;
47949
+ const isolatedTodoId = args.isolated ? crypto.randomUUID() : undefined;
47950
+ const mayfly = isolatedTodoId ? await spawnMayflyBridge(apiUrl, isolatedTodoId, realpathSync(resolve3(args.path || ".")), { debug: !!args.debug, apiKey }) : null;
47951
+ if (mayfly)
47952
+ process.stderr.write(`${DIM}Isolated bridge:${RESET} ${CYAN}mayfly-${isolatedTodoId}${RESET}
47953
+ `);
47230
47954
  let preMatchedAgent = null;
47231
47955
  let agents = null;
47232
47956
  if (args.agent) {
@@ -47337,7 +48061,7 @@ Resumed: ${CYAN}${getFrontendUrl(apiUrl, todoId)}${RESET}
47337
48061
  cfg.addToHistory(content);
47338
48062
  let todo;
47339
48063
  try {
47340
- todo = await api.addMessage(projectId, content, agent, undefined, undefined, undefined, groupTag || undefined, groupName);
48064
+ todo = await api.addMessage(projectId, content, agent, isolatedTodoId, undefined, undefined, groupTag || undefined, groupName);
47341
48065
  } catch (e) {
47342
48066
  if (!args.project && !envProjectId && cfgScope.data.default_project_id === projectId && /failed: 403/.test(e.message || "")) {
47343
48067
  cfgScope.clearDefaultProject();
@@ -47373,9 +48097,18 @@ ${"\u2500".repeat(40)}
47373
48097
  }
47374
48098
  await ws.close();
47375
48099
  }
48100
+ mayfly?.stop();
47376
48101
  }
47377
- main().then(() => process.exit(process.exitCode ?? 0)).catch((e) => {
48102
+ var finish = (code) => {
48103
+ process.exitCode = code;
48104
+ const poll = setInterval(() => {
48105
+ if (process.stdout.writableLength === 0)
48106
+ process.exit(code);
48107
+ }, 5);
48108
+ poll.unref?.();
48109
+ };
48110
+ main().then(() => finish(process.exitCode ?? 0)).catch((e) => {
47378
48111
  process.stderr.write(`Error: ${e.message}
47379
48112
  `);
47380
- process.exit(1);
48113
+ finish(1);
47381
48114
  });