@todoforai/cli 0.1.30 → 0.1.32

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 +902 -158
  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.30",
44272
+ version: "0.1.32",
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"
@@ -44254,39 +44303,39 @@ var package_default = {
44254
44303
  var DEFAULT_API_URL = "https://api.todofor.ai";
44255
44304
  var VERSION = package_default.version;
44256
44305
  function getEnv(name) {
44257
- return process.env[`TODOFORAI_${name}`] || process.env[`TODO4AI_${name}`] || "";
44306
+ return (process.env[`TODOFORAI_${name}`] || process.env[`TODO4AI_${name}`] || "").trim();
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) {
@@ -44917,20 +44969,29 @@ async function autoCreateAgent(api, resolvedPath) {
44917
44969
  }
44918
44970
 
44919
44971
  // src/config.ts
44920
- import { existsSync, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
44972
+ import { existsSync, mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync, writeFileSync as writeFileSync2 } from "fs";
44921
44973
  import { dirname as dirname2, join as join2, resolve as resolve2 } from "path";
44922
44974
  import { homedir as homedir2, platform as platform2 } from "os";
44923
- function getConfigDir() {
44975
+ function getConfigBase() {
44924
44976
  const sys = platform2();
44925
- if (sys === "win32") {
44926
- const base = process.env.APPDATA || join2(homedir2(), "AppData", "Roaming");
44927
- return join2(base, "todoai-cli");
44928
- }
44929
- if (sys === "darwin") {
44930
- return join2(homedir2(), "Library", "Application Support", "todoai-cli");
44977
+ if (sys === "win32")
44978
+ return process.env.APPDATA || join2(homedir2(), "AppData", "Roaming");
44979
+ if (sys === "darwin")
44980
+ return join2(homedir2(), "Library", "Application Support");
44981
+ return process.env.XDG_CONFIG_HOME || join2(homedir2(), ".config");
44982
+ }
44983
+ function getConfigDir() {
44984
+ const base = getConfigBase();
44985
+ const dir = join2(base, "todoforai-cli");
44986
+ const legacy = join2(base, "todoai-cli");
44987
+ if (!existsSync(dir) && existsSync(legacy)) {
44988
+ try {
44989
+ renameSync(legacy, dir);
44990
+ } catch {
44991
+ return legacy;
44992
+ }
44931
44993
  }
44932
- const xdg = process.env.XDG_CONFIG_HOME || join2(homedir2(), ".config");
44933
- return join2(xdg, "todoai-cli");
44994
+ return dir;
44934
44995
  }
44935
44996
  function defaultScope() {
44936
44997
  return {
@@ -45166,7 +45227,9 @@ var DETAILED_ONLY = new Set([
45166
45227
  "stop_sequence",
45167
45228
  "permissions",
45168
45229
  "isPublic",
45169
- "metadata"
45230
+ "metadata",
45231
+ "systemPrompt",
45232
+ "toolSchemas"
45170
45233
  ]);
45171
45234
  var DEBUG_ONLY = new Set([
45172
45235
  "runMeta",
@@ -45294,6 +45357,10 @@ function toAnthropicShape(messages, mode = "default", format = "compact") {
45294
45357
  msg.id = m.id;
45295
45358
  if (m.createdAt)
45296
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;
45297
45364
  }
45298
45365
  if (m.role === "user") {
45299
45366
  const atts = (m.attachments ?? []).filter((a) => !prevToolAttachmentIds.has(a.id)).map((a) => {
@@ -45573,7 +45640,16 @@ function selectProject(projects, defaultId, setDefault) {
45573
45640
  return Promise.resolve({ id: defaultId, name });
45574
45641
  }
45575
45642
  }
45643
+ const accountDefault = projects.find((p) => p?.project?.isDefault) || projects[0];
45644
+ if (!process.stdin.isTTY) {
45645
+ const id = getItemId(accountDefault);
45646
+ const name = getDisplayName(accountDefault);
45647
+ process.stderr.write(`Using account default project: ${name}
45648
+ `);
45649
+ return Promise.resolve({ id, name });
45650
+ }
45576
45651
  return (async () => {
45652
+ const defIdx = projects.indexOf(accountDefault);
45577
45653
  process.stderr.write(`
45578
45654
  Please choose a project:
45579
45655
 
@@ -45581,7 +45657,7 @@ Please choose a project:
45581
45657
  for (let i = 0;i < projects.length; i++) {
45582
45658
  const name = getDisplayName(projects[i]);
45583
45659
  const id = getItemId(projects[i]);
45584
- process.stderr.write(` [${i + 1}] ${name}
45660
+ process.stderr.write(` [${i + 1}] ${name}${i === defIdx ? " (default)" : ""}
45585
45661
  `);
45586
45662
  if (id && id !== name)
45587
45663
  process.stderr.write(` ${id}
@@ -45590,8 +45666,8 @@ Please choose a project:
45590
45666
  process.stderr.write(`
45591
45667
  `);
45592
45668
  while (true) {
45593
- const choice = await terminalLine("Please enter your numeric choice: ");
45594
- const idx = parseInt(choice, 10) - 1;
45669
+ const choice = await terminalLine(`Please enter your numeric choice [${defIdx + 1}]: `);
45670
+ const idx = choice === "" ? defIdx : parseInt(choice, 10) - 1;
45595
45671
  if (idx >= 0 && idx < projects.length) {
45596
45672
  const id = getItemId(projects[idx]);
45597
45673
  const name = getDisplayName(projects[idx]);
@@ -45658,45 +45734,594 @@ Please choose an agent:
45658
45734
  // src/diff-view.ts
45659
45735
  var import_diff_match_patch = __toESM(require_diff_match_patch(), 1);
45660
45736
  var import_cli_highlight = __toESM(require_dist(), 1);
45661
- var import_chalk = __toESM(require_source(), 1);
45662
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
45663
46288
  var CONTEXT_LINES = 3;
45664
46289
  var MAX_OUTPUT_LINES = 80;
45665
46290
  var SYNTAX_THEME = {
45666
- default: import_chalk.default.rgb(248, 248, 242),
45667
- keyword: import_chalk.default.rgb(249, 38, 114),
45668
- built_in: import_chalk.default.rgb(102, 217, 239),
45669
- type: import_chalk.default.rgb(102, 217, 239),
45670
- literal: import_chalk.default.rgb(174, 129, 255),
45671
- number: import_chalk.default.rgb(174, 129, 255),
45672
- regexp: import_chalk.default.rgb(230, 219, 116),
45673
- string: import_chalk.default.rgb(230, 219, 116),
45674
- subst: import_chalk.default.rgb(248, 248, 242),
45675
- symbol: import_chalk.default.rgb(174, 129, 255),
45676
- class: import_chalk.default.rgb(166, 226, 46),
45677
- function: import_chalk.default.rgb(166, 226, 46),
45678
- title: import_chalk.default.rgb(166, 226, 46),
45679
- params: import_chalk.default.rgb(253, 151, 31),
45680
- comment: import_chalk.default.rgb(117, 113, 94),
45681
- doctag: import_chalk.default.rgb(117, 113, 94),
45682
- meta: import_chalk.default.rgb(249, 38, 114),
45683
- "meta-keyword": import_chalk.default.rgb(249, 38, 114),
45684
- "meta-string": import_chalk.default.rgb(230, 219, 116),
45685
- section: import_chalk.default.rgb(166, 226, 46),
45686
- tag: import_chalk.default.rgb(249, 38, 114),
45687
- name: import_chalk.default.rgb(249, 38, 114),
45688
- attr: import_chalk.default.rgb(166, 226, 46),
45689
- attribute: import_chalk.default.rgb(166, 226, 46),
45690
- variable: import_chalk.default.rgb(248, 248, 242),
45691
- bullet: import_chalk.default.rgb(174, 129, 255),
45692
- code: import_chalk.default.rgb(230, 219, 116),
45693
- emphasis: import_chalk.default.italic,
45694
- strong: import_chalk.default.bold,
45695
- formula: import_chalk.default.rgb(248, 248, 242),
45696
- link: import_chalk.default.rgb(102, 217, 239).underline,
45697
- quote: import_chalk.default.rgb(117, 113, 94).italic,
45698
- addition: import_chalk.default.rgb(166, 226, 46),
45699
- 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)
45700
46325
  };
45701
46326
  function patchResets(text, bg) {
45702
46327
  return text.replace(/\x1b\[0m/g, `\x1B[0m${bg}`);
@@ -46250,13 +46875,13 @@ async function listAgentsCommand(api, opts) {
46250
46875
  // src/agent-command.ts
46251
46876
  function printAgentHelp() {
46252
46877
  process.stderr.write(`
46253
- todoforai-cli agent \u2014 inspect and update agent settings
46878
+ tfa-cli agent \u2014 inspect and update agent settings
46254
46879
 
46255
46880
  Usage:
46256
- todoforai-cli agent list List agents (name, model, id, paths)
46257
- todoforai-cli agent get <agent> Show a single agent's settings
46258
- todoforai-cli agent update <agent> <field=value>\u2026 Update one or more settings
46259
- 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)
46260
46885
 
46261
46886
  <agent> is a name or id (unique partial name also works).
46262
46887
  Fields map directly to agent settings; values are parsed as JSON when possible
@@ -46269,9 +46894,9 @@ Fields map directly to agent settings; values are parsed as JSON when possible
46269
46894
  name agent display name
46270
46895
 
46271
46896
  Examples:
46272
- todoforai-cli agent update <agent> model=claude
46273
- todoforai-cli agent update <agent> model=anthropic:anthropic/claude-opus-5 temperature=0.5
46274
- 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."
46275
46900
  `);
46276
46901
  }
46277
46902
  var FIELD_ALIASES = {
@@ -46339,7 +46964,7 @@ async function agentCommand(api, positionals, args, formatPath) {
46339
46964
  const query = positionals[2];
46340
46965
  const assignments = positionals.slice(3);
46341
46966
  if (!query || !assignments.length) {
46342
- 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}
46343
46968
  `);
46344
46969
  process.exit(2);
46345
46970
  }
@@ -46360,7 +46985,7 @@ async function agentCommand(api, positionals, args, formatPath) {
46360
46985
  if (sub === "delete" || sub === "rm") {
46361
46986
  const query = positionals[2];
46362
46987
  if (!query) {
46363
- 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}
46364
46989
  `);
46365
46990
  process.exit(2);
46366
46991
  }
@@ -46583,6 +47208,87 @@ async function listTodosCommand(api, defaultProjectId, argv) {
46583
47208
  `);
46584
47209
  }
46585
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
+
46586
47292
  // src/index.ts
46587
47293
  try {
46588
47294
  const pkgPath = path3.resolve(fileURLToPath(import.meta.url), "../../package.json");
@@ -46671,7 +47377,7 @@ Cancelled by user (Ctrl+C)
46671
47377
  const groupName = args["group-name"] === undefined ? undefined : String(args["group-name"]);
46672
47378
  if (positionals[0] === "start") {
46673
47379
  if (!positionals[1] && !args.template) {
46674
- process.stderr.write(`${RED}Usage: todoforai-cli start <todo-id>${RESET}
47380
+ process.stderr.write(`${RED}Usage: tfa-cli start <todo-id>${RESET}
46675
47381
  `);
46676
47382
  process.exit(2);
46677
47383
  }
@@ -46698,6 +47404,23 @@ Cancelled by user (Ctrl+C)
46698
47404
  printUsage();
46699
47405
  process.exit(0);
46700
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
+ }
46701
47424
  const cfg = new ConfigStore(args["config-path"]);
46702
47425
  if (args["show-config"]) {
46703
47426
  console.log(`Config file: ${formatPathWithTilde(cfg.path)}`);
@@ -46738,12 +47461,12 @@ Cancelled by user (Ctrl+C)
46738
47461
 
46739
47462
  `);
46740
47463
  try {
46741
- const { spawn: spawn2 } = await import("child_process");
47464
+ const { spawn: spawn3 } = await import("child_process");
46742
47465
  if (process.platform === "win32") {
46743
- spawn2("cmd", ["/c", "start", "", url], { stdio: "ignore", detached: true }).unref();
47466
+ spawn3("cmd", ["/c", "start", "", url], { stdio: "ignore", detached: true }).unref();
46744
47467
  } else {
46745
47468
  const cmd = process.platform === "darwin" ? "open" : "xdg-open";
46746
- spawn2(cmd, [url], { stdio: "ignore", detached: true }).unref();
47469
+ spawn3(cmd, [url], { stdio: "ignore", detached: true }).unref();
46747
47470
  }
46748
47471
  } catch {}
46749
47472
  process.stderr.write(`Waiting for approval (expires in ${Math.round(expiresIn / 60)}min)...
@@ -46783,6 +47506,20 @@ Cancelled by user (Ctrl+C)
46783
47506
  if (!apiKey) {
46784
47507
  apiKey = await deviceLogin();
46785
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
+ }
46786
47523
  const api = new ApiClient(apiUrl, apiKey, args["user-id"]);
46787
47524
  if (args["user-id"] && args.safe) {
46788
47525
  process.stderr.write(`Error: --safe is not supported with --user-id
@@ -46813,7 +47550,7 @@ Cancelled by user (Ctrl+C)
46813
47550
  if (positionals[0] === "delete") {
46814
47551
  const todoId = positionals[1];
46815
47552
  if (!todoId) {
46816
- process.stderr.write(`${RED}Usage: todoforai-cli delete <todo-id>${RESET}
47553
+ process.stderr.write(`${RED}Usage: tfa-cli delete <todo-id>${RESET}
46817
47554
  `);
46818
47555
  process.exit(2);
46819
47556
  }
@@ -46826,7 +47563,7 @@ Cancelled by user (Ctrl+C)
46826
47563
  const [, todoId, ...rest] = positionals;
46827
47564
  const content2 = rest.join(" ") || await readStdin();
46828
47565
  if (!todoId || !content2) {
46829
- 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}
46830
47567
  `);
46831
47568
  process.exit(2);
46832
47569
  }
@@ -46845,7 +47582,7 @@ Cancelled by user (Ctrl+C)
46845
47582
  const todoId = positionals[2] || (!explicitProject ? getEnv("TODO_ID") || cfgScope.data.last_todo_id : undefined);
46846
47583
  const projectId2 = todoId ? undefined : args.project || getEnv("PROJECT_ID") || cfgScope.data.default_project_id;
46847
47584
  if (!todoId && !projectId2) {
46848
- 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}
46849
47586
  `);
46850
47587
  process.exit(2);
46851
47588
  }
@@ -46870,7 +47607,7 @@ Cancelled by user (Ctrl+C)
46870
47607
  const [, filePath, todoArg] = positionals;
46871
47608
  const todoId = todoArg || getEnv("TODO_ID") || cfgScope.data.last_todo_id;
46872
47609
  if (!filePath || !todoId) {
46873
- 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}
46874
47611
  `);
46875
47612
  process.exit(2);
46876
47613
  }
@@ -46904,7 +47641,7 @@ Cancelled by user (Ctrl+C)
46904
47641
  const [, url, todoArg] = positionals;
46905
47642
  const todoId = todoArg || getEnv("TODO_ID") || cfgScope.data.last_todo_id;
46906
47643
  if (!url || !todoId) {
46907
- 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}
46908
47645
  `);
46909
47646
  process.exit(2);
46910
47647
  }
@@ -46918,13 +47655,13 @@ Cancelled by user (Ctrl+C)
46918
47655
  if (positionals[0] === "recommend") {
46919
47656
  const templateId = args.template || positionals[1];
46920
47657
  if (!templateId) {
46921
- 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}
46922
47659
  `);
46923
47660
  process.stderr.write(`${DIM}Create a template first with: todoregistry-cli create --name ... --description ... --body @prompt.md${RESET}
46924
47661
  `);
46925
47662
  process.exit(2);
46926
47663
  }
46927
- let projectId2 = args.project || cfgScope.data.default_project_id;
47664
+ let projectId2 = args.project || getEnv("PROJECT_ID") || cfgScope.data.default_project_id;
46928
47665
  if (!projectId2) {
46929
47666
  const projects2 = await api.listProjects();
46930
47667
  projectId2 = projects2.find((p) => p.project?.isDefault)?.project?.id || projects2[0]?.project?.id;
@@ -46959,13 +47696,13 @@ Cancelled by user (Ctrl+C)
46959
47696
  return;
46960
47697
  }
46961
47698
  if (positionals[0] === "claim" && positionals[1] === "mint") {
46962
- let seedProjectId = args.seed || args.project || cfgScope.data.default_project_id;
47699
+ let seedProjectId = args.seed || args.project || getEnv("PROJECT_ID") || cfgScope.data.default_project_id;
46963
47700
  if (!seedProjectId) {
46964
47701
  const projects2 = await api.listProjects();
46965
47702
  seedProjectId = projects2.find((p) => p.project?.isDefault)?.project?.id || projects2[0]?.project?.id;
46966
47703
  }
46967
47704
  if (!seedProjectId) {
46968
- 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}
46969
47706
  `);
46970
47707
  process.exit(2);
46971
47708
  }
@@ -47001,7 +47738,7 @@ Cancelled by user (Ctrl+C)
47001
47738
  return;
47002
47739
  }
47003
47740
  if (positionals[0] === "next") {
47004
- let projectId2 = args.project || cfgScope.data.default_project_id;
47741
+ let projectId2 = args.project || getEnv("PROJECT_ID") || cfgScope.data.default_project_id;
47005
47742
  if (!projectId2) {
47006
47743
  const projects2 = await api.listProjects();
47007
47744
  projectId2 = projects2.find((p) => p.project?.isDefault)?.project?.id || projects2[0]?.project?.id;
@@ -47051,7 +47788,7 @@ Cancelled by user (Ctrl+C)
47051
47788
  return;
47052
47789
  }
47053
47790
  if (positionals[0] === "list" || positionals[0] === "ls") {
47054
- let defaultProjectId = args.project || cfgScope.data.default_project_id;
47791
+ let defaultProjectId = args.project || getEnv("PROJECT_ID") || cfgScope.data.default_project_id;
47055
47792
  if (!defaultProjectId) {
47056
47793
  const projects2 = await api.listProjects();
47057
47794
  defaultProjectId = projects2.find((p) => p.project?.isDefault)?.project?.id || projects2[0]?.project?.id;
@@ -47106,7 +47843,7 @@ Cancelled by user (Ctrl+C)
47106
47843
  process.stderr.write(`${DIM}${spec.description}${RESET}
47107
47844
  `);
47108
47845
  const projects2 = await api.listProjects();
47109
- let projectId2 = args.project;
47846
+ let projectId2 = args.project || getEnv("PROJECT_ID");
47110
47847
  if (!projectId2) {
47111
47848
  const cached = cfgScope.data.default_project_id;
47112
47849
  projectId2 = (cached && projects2.some((p) => getItemId(p) === cached) ? cached : null) || projects2.find((p) => p.project?.isDefault)?.project?.id || projects2[0]?.project?.id;
@@ -47208,7 +47945,12 @@ Resumed: ${CYAN}${getFrontendUrl(apiUrl, todoId)}${RESET}
47208
47945
  }
47209
47946
  const ws = args["no-watch"] ? null : new FrontendWebSocket(apiUrl, apiKey);
47210
47947
  const wsReady = ws ? ws.connect() : null;
47211
- 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
+ `);
47212
47954
  let preMatchedAgent = null;
47213
47955
  let agents = null;
47214
47956
  if (args.agent) {
@@ -47266,7 +48008,8 @@ Resumed: ${CYAN}${getFrontendUrl(apiUrl, todoId)}${RESET}
47266
48008
  } else {
47267
48009
  content = await readStdin();
47268
48010
  }
47269
- const hasProject = args.project || cfgScope.data.default_project_id;
48011
+ const envProjectId = getEnv("PROJECT_ID");
48012
+ const hasProject = args.project || envProjectId || cfgScope.data.default_project_id;
47270
48013
  const storedAgent = cfgScope.data.default_agent_settings;
47271
48014
  const hasAgent = preMatchedAgent || storedAgent?.id && !args.agent;
47272
48015
  let projects = null;
@@ -47277,8 +48020,8 @@ Resumed: ${CYAN}${getFrontendUrl(apiUrl, todoId)}${RESET}
47277
48020
  }
47278
48021
  let projectId;
47279
48022
  let projectName;
47280
- if (args.project) {
47281
- projectId = args.project;
48023
+ if (args.project || envProjectId) {
48024
+ projectId = args.project || envProjectId;
47282
48025
  projectName = projectId;
47283
48026
  if (projects) {
47284
48027
  const match = projects.find((p) => getItemId(p) === projectId);
@@ -47318,9 +48061,9 @@ Resumed: ${CYAN}${getFrontendUrl(apiUrl, todoId)}${RESET}
47318
48061
  cfg.addToHistory(content);
47319
48062
  let todo;
47320
48063
  try {
47321
- 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);
47322
48065
  } catch (e) {
47323
- if (!args.project && cfgScope.data.default_project_id === projectId && /failed: 403/.test(e.message || "")) {
48066
+ if (!args.project && !envProjectId && cfgScope.data.default_project_id === projectId && /failed: 403/.test(e.message || "")) {
47324
48067
  cfgScope.clearDefaultProject();
47325
48068
  process.stderr.write(`${RED}Not authorized for cached default project ${projectName} (${projectId}) \u2014 cleared it. Re-run to pick a project.${RESET}
47326
48069
  `);
@@ -47354,6 +48097,7 @@ ${"\u2500".repeat(40)}
47354
48097
  }
47355
48098
  await ws.close();
47356
48099
  }
48100
+ mayfly?.stop();
47357
48101
  }
47358
48102
  main().then(() => process.exit(process.exitCode ?? 0)).catch((e) => {
47359
48103
  process.stderr.write(`Error: ${e.message}