@todoforai/cli 0.1.21 → 0.1.24

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.
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env bun
2
+ // Dev (linked repo): run src directly so there is no stale-build step.
3
+ // Published package ships only dist/ (the @shared/* file: deps are bundled in).
4
+ import { existsSync } from "node:fs";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ const src = fileURLToPath(new URL("../src/index.ts", import.meta.url));
8
+ const dist = fileURLToPath(new URL("../dist/todoai.js", import.meta.url));
9
+ await import(existsSync(src) ? src : dist);
package/dist/todoai.js CHANGED
@@ -42732,6 +42732,10 @@ function randomTip() {
42732
42732
  }
42733
42733
 
42734
42734
  // ../edge/bun/src/api.ts
42735
+ function restBasePath(apiKey) {
42736
+ return apiKey.startsWith("dst_") ? "/dst/v1" : "/api/v1";
42737
+ }
42738
+
42735
42739
  class ApiClient {
42736
42740
  apiUrl;
42737
42741
  apiKey;
@@ -42743,7 +42747,7 @@ class ApiClient {
42743
42747
  return { "content-type": "application/json", "x-api-key": this.apiKey };
42744
42748
  }
42745
42749
  async request(method, endpoint, body) {
42746
- const url = `${this.apiUrl}${endpoint}`;
42750
+ const url = `${this.apiUrl}${endpoint.replace(/^\/api\/v1/, restBasePath(this.apiKey))}`;
42747
42751
  const opts = { method, headers: this.headers, signal: AbortSignal.timeout(30000) };
42748
42752
  if (body)
42749
42753
  opts.body = JSON.stringify(body);
@@ -42777,7 +42781,7 @@ class ApiClient {
42777
42781
  if (!this.apiKey)
42778
42782
  return { valid: false, error: "No API key provided" };
42779
42783
  try {
42780
- const res = await fetch(`${this.apiUrl}/api/v1/apikey/validate`, {
42784
+ const res = await fetch(`${this.apiUrl}${restBasePath(this.apiKey)}/apikey/validate`, {
42781
42785
  headers: { "x-api-key": this.apiKey },
42782
42786
  signal: AbortSignal.timeout(1e4)
42783
42787
  });
@@ -42814,6 +42818,13 @@ class ApiClient {
42814
42818
  listTodos(projectId, opts) {
42815
42819
  if (!projectId)
42816
42820
  return this.request("GET", "/api/v1/todos");
42821
+ if (this.apiKey.startsWith("dst_")) {
42822
+ const qs = new URLSearchParams;
42823
+ for (const [k, v] of Object.entries(opts ?? {}))
42824
+ if (v !== undefined)
42825
+ qs.set(k, String(v));
42826
+ return this.request("GET", `/api/v1/projects/${projectId}/todos${qs.size ? `?${qs}` : ""}`);
42827
+ }
42817
42828
  return this.trpcQuery("todo.list", { projectId, ...opts });
42818
42829
  }
42819
42830
  getTodo(todoId) {
@@ -42875,17 +42886,21 @@ class ApiClient {
42875
42886
  listDevices() {
42876
42887
  return this.request("GET", "/api/v1/devices");
42877
42888
  }
42878
- startFromTemplate(projectId, templateId, opts) {
42889
+ startFromSpec(projectId, specId, opts) {
42879
42890
  return this.request("POST", `/api/v1/projects/${projectId}/todos/from-template`, {
42880
- templateId,
42891
+ specId,
42881
42892
  ...opts
42882
42893
  });
42883
42894
  }
42884
- async getRegistryTemplate(templateId) {
42895
+ recommend(input) {
42896
+ const { projectId, ...body } = input;
42897
+ return this.request("POST", `/api/v1/projects/${projectId}/recommendations`, body);
42898
+ }
42899
+ async getRegistrySpec(specId) {
42885
42900
  const base = this.apiUrl.replace(/\/api\/v1\/?$/, "").replace(/\/$/, "");
42886
- const res = await fetch(`${base}/cookie/v1/registry/templates/${templateId}`);
42901
+ const res = await fetch(`${base}/cookie/v1/registry/templates/${specId}`);
42887
42902
  if (!res.ok)
42888
- throw new Error(`Template '${templateId}' not found (${res.status})`);
42903
+ throw new Error(`Spec '${specId}' not found (${res.status})`);
42889
42904
  return res.json();
42890
42905
  }
42891
42906
  async initDeviceLogin(clientName = "edge") {
@@ -42904,7 +42919,7 @@ class ApiClient {
42904
42919
  throw new Error(`Device login poll failed: ${res.status}`);
42905
42920
  return res.json();
42906
42921
  }
42907
- async listRegistryTemplates(category = "all") {
42922
+ async listRegistrySpecs(category = "all") {
42908
42923
  const base = this.apiUrl.replace(/\/api\/v1\/?$/, "").replace(/\/$/, "");
42909
42924
  const res = await fetch(`${base}/cookie/v1/registry/templates?category=${category}`);
42910
42925
  if (!res.ok)
@@ -43135,13 +43150,16 @@ import { parseArgs } from "util";
43135
43150
  // package.json
43136
43151
  var package_default = {
43137
43152
  name: "@todoforai/cli",
43138
- version: "0.1.3",
43153
+ version: "0.1.24",
43139
43154
  type: "module",
43140
43155
  bin: {
43141
- "todoforai-cli": "dist/todoai.js",
43142
- todoai: "dist/todoai.js"
43156
+ "todoforai-cli": "bin/todoforai-cli.js",
43157
+ todoai: "bin/todoforai-cli.js"
43143
43158
  },
43144
- files: ["dist/todoai.js"],
43159
+ files: [
43160
+ "bin",
43161
+ "dist/todoai.js"
43162
+ ],
43145
43163
  scripts: {
43146
43164
  build: "bun build src/index.ts --target=bun --outfile dist/todoai.js --external ws",
43147
43165
  prepublishOnly: "bun run build",
@@ -43400,8 +43418,8 @@ var SERVER_TO_FRONTENDS = {
43400
43418
  hexGrid: {
43401
43419
  updated: (projectId) => `${"hexgrid:updated" /* HEXGRID_UPDATED */}:${projectId}`
43402
43420
  },
43403
- recommendation: {
43404
- updated: (projectId) => `${"recommendation:updated" /* RECOMMENDATION_UPDATED */}:${projectId}`
43421
+ placement: {
43422
+ updated: (projectId) => `${"placement:updated" /* PLACEMENT_UPDATED */}:${projectId}`
43405
43423
  },
43406
43424
  sandbox: {
43407
43425
  state: (userId) => `${"sandbox:state" /* SANDBOX_STATE */}:${userId}`
@@ -43827,12 +43845,12 @@ var BUSINESS_ANALYZER_AGENT = {
43827
43845
 
43828
43846
  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.
43829
43847
 
43830
- 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. Whenever you call update_business_context, 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.
43848
+ 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.
43831
43849
 
43832
43850
  How to analyze and update the business context:
43833
- 1. Read the current business context markdown from the system prompt
43851
+ 1. Read the current business context with \`Read todoforai:business-context\`
43834
43852
  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
43835
- 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 with update_business_context
43853
+ 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.
43836
43854
 
43837
43855
  Cover these sections (skip any without data):
43838
43856
  - **Company Info** \u2014 name, founding year, location, team size, stage
@@ -43846,7 +43864,7 @@ Cover these sections (skip any without data):
43846
43864
 
43847
43865
  If anything important remains unclear, add an **Open Questions** section at the end.
43848
43866
 
43849
- Maintain a single machine-readable **Growth Plan** block so the user's brand page can render a live presence map and a ranked, actionable growth plan. Write it as one fenced code block tagged \`growth-plan\` containing JSON (the user never sees this raw \u2014 it powers the presence hexes and the Growth Plan panel). Keep exactly one such block; rewrite it in full each time you update it. Concrete example (emit strictly valid JSON like this \u2014 no comments, no ranges, no "|" alternatives):
43867
+ 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).
43850
43868
 
43851
43869
  \`\`\`growth-plan
43852
43870
  {
@@ -43854,25 +43872,16 @@ Maintain a single machine-readable **Growth Plan** block so the user's brand pag
43854
43872
  "linkedin-networking": { "score": 45, "handle": "https://linkedin.com/company/example", "note": "Company page exists but posts infrequently" },
43855
43873
  "x-twitter-presence": { "score": 60, "handle": "@example", "note": "Active, regular posts" },
43856
43874
  "newsletter": { "score": null, "note": "No newsletter signup found" }
43857
- },
43858
- "recommendations": [
43859
- {
43860
- "activityId": "linkedin-networking",
43861
- "title": "Publish two founder-led LinkedIn posts each week",
43862
- "rationale": "Target buyers are active on LinkedIn, but the page is nearly dormant.",
43863
- "angle": "Explain the operational pain through short customer scenarios.",
43864
- "track": "Qualified profile visits and demo requests per week",
43865
- "priority": "high"
43866
- }
43867
- ]
43875
+ }
43868
43876
  }
43869
43877
  \`\`\`
43870
43878
 
43871
- Rules for the Growth Plan block:
43872
- - \`activityId\` 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.
43879
+ Rules for the Presence block:
43880
+ - 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.
43873
43881
  - \`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.
43874
- - \`recommendations\`: 3-6 items. \`priority\` is exactly one of "high", "medium", "low"; put the single highest-leverage action first. Choose channels that fit THIS business's model, audience, and stage \u2014 don't recommend all 20; prioritize gaps where presence is weak/absent but impact is high. Make \`title\` a concrete next action, \`angle\` the actual message/positioning to use, and \`track\` a specific metric or cadence to record.
43875
- - Keep it consistent with the prose Online Presence and Next Steps sections \u2014 the JSON is the structured mirror of that advice.
43882
+ - Keep it consistent with the prose Online Presence section \u2014 the JSON is the structured mirror of those findings.
43883
+
43884
+ Growth recommendations are NOT part of this block \u2014 they live in the project's Recommendations column as placement cards (see "Recommending actionable TODOs" below). After any analysis that surfaces growth opportunities, materialize your top recommendations as cards there; the prose Next Steps section stays human-readable advice.
43876
43885
 
43877
43886
  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.)
43878
43887
 
@@ -43882,10 +43891,10 @@ Guide the user one step at a time \u2014 they should always know the single next
43882
43891
 
43883
43892
  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.
43884
43893
 
43885
- When the user asks you to recommend how to grow (e.g. "recommend growth TODOs", the brand page's guided suggestion), treat it as a growth-plan request: read the current business context (including any existing \`growth-plan\` block), research only where information is missing or stale, then update the business context with a refreshed Growth Plan block and reply in chat with the same ranked recommendations in plain language \u2014 lead with the single highest-leverage action and why. This must work at any point in the conversation, not just after a full website analysis.
43894
+ 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.
43886
43895
 
43887
43896
  Recommending actionable TODOs from your recommendations:
43888
- When the user asks you to generate/recommend TODOs (e.g. "generate TODOs", the board's Generate button), turn your top recommendations into recommendation cards in the NEXT column. You NEVER create running todos \u2014 you only recommend templates by reference. Two tools, strict roles:
43897
+ To materialize growth recommendations, create recommendation cards in the project's Recommendations column. You NEVER create running todos \u2014 you only recommend templates by reference. Two tools, strict roles:
43889
43898
 
43890
43899
  1. FIND OR CREATE A TEMPLATE (todoregistry-cli):
43891
43900
  - \`todoregistry-cli search <query>\` / \`todoregistry-cli list -c <category>\` / \`todoregistry-cli get <id>\` \u2014 prefer a matching existing template (proven playbook).
@@ -43894,9 +43903,9 @@ When the user asks you to generate/recommend TODOs (e.g. "generate TODOs", the b
43894
43903
 
43895
43904
  2. RECOMMEND (todoforai-cli \u2014 references a template id onto the project; never creates a running todo):
43896
43905
  \`todoforai-cli recommend --template <id> --project <projectId> --business-context <businessContextId> --note "<why>" --priority high|medium|low\`
43897
- Take <projectId> and <businessContextId> from your current session context and pass them explicitly. The card appears PAUSED in the NEXT column for the user to review before any credits are spent.
43906
+ Take <projectId> and <businessContextId> from your current session context and pass them explicitly. Put the one-line WHY for THIS business in --note (the card's tooltip). The card appears in the Recommendations column for the user to review before any credits are spent.
43898
43907
 
43899
- 3. Recommend 3-6 templates matching your Growth Plan (highest priority first), then reply with a one-line summary per card and tell the user they're waiting in the NEXT column for review.`,
43908
+ 3. Recommend 3-6 cards (highest priority first). When creating a spec for a business-specific action, write the body so the positioning angle and what to track are part of the task. Then reply with a one-line summary per card and tell the user they're waiting in the Recommendations column for review.`,
43900
43909
  mcpConfigs: {},
43901
43910
  edgesMcpConfigs: {},
43902
43911
  permissions: {
@@ -43976,8 +43985,8 @@ var TOPICS = {
43976
43985
  channel: (p) => SERVER_TO_FRONTENDS.hexGrid.updated(p.projectId),
43977
43986
  audience: "frontend"
43978
43987
  },
43979
- ["recommendation:updated" /* RECOMMENDATION_UPDATED */]: {
43980
- channel: (p) => SERVER_TO_FRONTENDS.recommendation.updated(p.projectId),
43988
+ ["placement:updated" /* PLACEMENT_UPDATED */]: {
43989
+ channel: (p) => SERVER_TO_FRONTENDS.placement.updated(p.projectId),
43981
43990
  audience: "frontend"
43982
43991
  },
43983
43992
  ["sandbox:state" /* SANDBOX_STATE */]: {
@@ -44096,7 +44105,7 @@ Usage:
44096
44105
  todoforai-cli -c ["prompt"] # Resume last todo (optional prompt sent on attach)
44097
44106
  todoforai-cli --resume <todo-id> ["prompt"] # Resume specific todo (optional prompt sent on attach)
44098
44107
  todoforai-cli --inspect <todo-id>[@<slice>] # Read chat log. <slice> = -3:, :1, 5:10, 7 (Python-style)
44099
- todoforai-cli --template <id> [--input k=v] # Start from a registry template
44108
+ todoforai-cli start <id> # Start a TODO from the registry (todoregistry.com)
44100
44109
  todoforai-cli --list-agents # List available agents and exit
44101
44110
  todoforai-cli --list-models [filter] # List models usable with --model and exit
44102
44111
  todoforai-cli agent update <agent> model=<model> # Update agent settings (see 'agent --help')
@@ -44104,12 +44113,12 @@ Usage:
44104
44113
  todoforai-cli status <todo-id> <STATUS> # Update a todo's status (run 'status --help' for the full list)
44105
44114
  todoforai-cli delete <todo-id> # Permanently delete a todo
44106
44115
  todoforai-cli addmessage <todo-id> "text" # Add a message to an existing todo
44107
- todoforai-cli recommend --template <id> # Add a template as a NEXT-column card (see 'todoregistry-cli create')
44116
+ todoforai-cli recommend --template <id> # Add a template as a recommendation card (see 'todoregistry-cli create')
44108
44117
  todoforai-cli claim mint --seed <projectId> [--emails a@x,b@y] [--ttl <sec>] # Mint /claim/<token> ownership links for a project you own
44109
44118
  todoforai-cli steering get # Show recommendation steering (direction + boosted/muted categories)
44110
44119
  todoforai-cli steering direction "<text>" # Set the direction the generator should follow
44111
44120
  todoforai-cli steering boost|mute <cat> # Favour / avoid a category
44112
- todoforai-cli next # Generate steered NEXT-column recommendations
44121
+ todoforai-cli next # Generate steered recommendation cards
44113
44122
 
44114
44123
  Options:
44115
44124
  --path <dir> Workspace path (default: cwd)
@@ -44123,8 +44132,7 @@ Options:
44123
44132
  --api-key <key> API key
44124
44133
  --user-id <id> Admin HTTP impersonation; requires --no-watch
44125
44134
  --inspect, -i <todo-id>[@<slice>] Print chat log (read-only)
44126
- --template, -t <id> Start from a registry template
44127
- --input <key=value> Template input (repeatable)
44135
+ --template, -t <id> Start from a registry template (alias: start <id>)
44128
44136
  --resume, -r [todo-id] Resume existing todo
44129
44137
  --continue, -c Continue most recent todo
44130
44138
  --non-interactive, -n Run to completion and exit without interactive prompt
@@ -44181,7 +44189,6 @@ function parseCliArgs() {
44181
44189
  "user-id": { type: "string" },
44182
44190
  inspect: { type: "string", short: "i" },
44183
44191
  template: { type: "string", short: "t" },
44184
- input: { type: "string", multiple: true },
44185
44192
  note: { type: "string" },
44186
44193
  priority: { type: "string" },
44187
44194
  title: { type: "string" },
@@ -44217,9 +44224,6 @@ function parseCliArgs() {
44217
44224
  return { values, positionals };
44218
44225
  }
44219
44226
 
44220
- // src/input.ts
44221
- import { createInterface } from "readline";
44222
-
44223
44227
  // src/colors.ts
44224
44228
  var on = !process.env.NO_COLOR && !!process.stdout.isTTY;
44225
44229
  var c = (seq) => on ? seq : "";
@@ -44240,15 +44244,6 @@ var BG_RED_HL = c("\x1B[48;2;100;35;35m");
44240
44244
  var BG_GREEN_HL = c("\x1B[48;2;35;85;35m");
44241
44245
 
44242
44246
  // src/input.ts
44243
- function readLine(prompt) {
44244
- const rl = createInterface({ input: process.stdin, output: process.stderr });
44245
- return new Promise((res) => {
44246
- rl.question(prompt, (ans) => {
44247
- rl.close();
44248
- res(ans.trim());
44249
- });
44250
- });
44251
- }
44252
44247
  function readMultiline(prompt, history) {
44253
44248
  let cancelFn = () => {};
44254
44249
  const promise = new Promise((resolve, reject) => {
@@ -44854,6 +44849,12 @@ class ScopedConfig {
44854
44849
  get data() {
44855
44850
  return this.store.data.per_api_url[this.apiUrl];
44856
44851
  }
44852
+ clearDefaultProject() {
44853
+ const s = this.data;
44854
+ s.default_project_id = null;
44855
+ s.default_project_name = null;
44856
+ this.store.save();
44857
+ }
44857
44858
  setDefaultProject(id, name) {
44858
44859
  const s = this.data;
44859
44860
  s.default_project_id = id;
@@ -45320,7 +45321,7 @@ ${label}${ts}
45320
45321
  }
45321
45322
 
45322
45323
  // src/select.ts
45323
- import { createInterface as createInterface2 } from "readline";
45324
+ import { createInterface } from "readline";
45324
45325
  function getDisplayName(item) {
45325
45326
  if (item?.project?.name)
45326
45327
  return item.project.name;
@@ -45354,7 +45355,7 @@ function resolveAgentMatch(agents, query) {
45354
45355
  return {};
45355
45356
  }
45356
45357
  function terminalLine(prompt) {
45357
- const rl = createInterface2({ input: process.stdin, output: process.stderr });
45358
+ const rl = createInterface({ input: process.stdin, output: process.stderr });
45358
45359
  return new Promise((resolve3) => {
45359
45360
  rl.question(prompt, (answer) => {
45360
45361
  rl.close();
@@ -46108,7 +46109,7 @@ Usage:
46108
46109
  <agent> is a name or id (unique partial name also works).
46109
46110
  Fields map directly to agent settings; values are parsed as JSON when possible
46110
46111
  (numbers, booleans), otherwise treated as strings. Common fields:
46111
- model e.g. claude | anthropic:anthropic/claude-opus-4.8
46112
+ model e.g. claude | anthropic:anthropic/claude-opus-5
46112
46113
  ('claude' is the rolling alias \u2192 latest Claude Opus)
46113
46114
  systemMessage freeform prompt text (alias: sysmsg)
46114
46115
  temperature number, e.g. 0.7
@@ -46117,7 +46118,7 @@ Fields map directly to agent settings; values are parsed as JSON when possible
46117
46118
 
46118
46119
  Examples:
46119
46120
  todoforai-cli agent update <agent> model=claude
46120
- todoforai-cli agent update <agent> model=anthropic:anthropic/claude-opus-4.8 temperature=0.5
46121
+ todoforai-cli agent update <agent> model=anthropic:anthropic/claude-opus-5 temperature=0.5
46121
46122
  todoforai-cli agent update <agent> sysmsg="You are a terse video editor."
46122
46123
  `);
46123
46124
  }
@@ -46410,7 +46411,7 @@ var GREEN2 = "\x1B[32m";
46410
46411
  var DIM2 = "\x1B[2m";
46411
46412
  var RESET2 = "\x1B[0m";
46412
46413
  async function rest(ctx, method, path4, body) {
46413
- const res = await fetch(`${ctx.apiUrl}/api/v1${path4}`, {
46414
+ const res = await fetch(`${ctx.apiUrl}${restBasePath(ctx.apiKey)}${path4}`, {
46414
46415
  method,
46415
46416
  headers: { "content-type": "application/json", "x-api-key": ctx.apiKey },
46416
46417
  ...body ? { body: JSON.stringify(body) } : {},
@@ -46580,6 +46581,15 @@ Cancelled by user (Ctrl+C)
46580
46581
  process.exit(130);
46581
46582
  });
46582
46583
  const { values: args, positionals } = parseCliArgs();
46584
+ if (positionals[0] === "start") {
46585
+ if (!positionals[1] && !args.template) {
46586
+ process.stderr.write(`${RED}Usage: todoforai-cli start <todo-id>${RESET}
46587
+ `);
46588
+ process.exit(2);
46589
+ }
46590
+ if (!args.template)
46591
+ args.template = positionals[1];
46592
+ }
46583
46593
  if (args.version) {
46584
46594
  console.log(VERSION);
46585
46595
  process.exit(0);
@@ -46681,8 +46691,7 @@ Cancelled by user (Ctrl+C)
46681
46691
  await deviceLogin();
46682
46692
  return;
46683
46693
  }
46684
- const notDst = (t) => t.startsWith("dst_") ? "" : t;
46685
- let apiKey = args["api-key"] || notDst(readCredential(apiUrl)) || notDst(getEnv("API_TOKEN")) || "";
46694
+ let apiKey = args["api-key"] || readCredential(apiUrl) || getEnv("API_TOKEN") || "";
46686
46695
  if (!apiKey) {
46687
46696
  apiKey = await deviceLogin();
46688
46697
  }
@@ -46769,7 +46778,7 @@ Cancelled by user (Ctrl+C)
46769
46778
  }
46770
46779
  const rec = await api.recommend({
46771
46780
  projectId: projectId2,
46772
- templateId,
46781
+ specId: templateId,
46773
46782
  ...args.title ? { title: args.title } : {},
46774
46783
  ...args.note ? { note: args.note } : {},
46775
46784
  ...priority ? { priority } : {},
@@ -46803,7 +46812,7 @@ Cancelled by user (Ctrl+C)
46803
46812
  process.exit(2);
46804
46813
  }
46805
46814
  }
46806
- const res = await fetch(`${apiUrl}/api/v1/claims/mint`, {
46815
+ const res = await fetch(`${apiUrl}${restBasePath(apiKey)}/claims/mint`, {
46807
46816
  method: "POST",
46808
46817
  headers: { "content-type": "application/json", "x-api-key": apiKey },
46809
46818
  body: JSON.stringify({ seedProjectId, ...emails?.length ? { emails } : {}, ...ttlSec !== undefined ? { ttlSec } : {} }),
@@ -46837,7 +46846,7 @@ Cancelled by user (Ctrl+C)
46837
46846
  }
46838
46847
  const sctx = { apiUrl, apiKey, projectId: projectId2, json: !!args.json };
46839
46848
  if (positionals[0] === "next") {
46840
- const res = await fetch(`${apiUrl}/api/v1/projects/${projectId2}/recommendations/generate`, {
46849
+ const res = await fetch(`${apiUrl}${restBasePath(apiKey)}/projects/${projectId2}/recommendations/generate`, {
46841
46850
  method: "POST",
46842
46851
  headers: { "content-type": "application/json", "x-api-key": apiKey },
46843
46852
  body: JSON.stringify({ projectId: projectId2, ...args["business-context"] ? { businessContextId: args["business-context"] } : {} }),
@@ -46921,47 +46930,24 @@ Cancelled by user (Ctrl+C)
46921
46930
  if (!args["no-bridge"] && !args["no-watch"])
46922
46931
  ensureBridgeRunning(apiUrl, apiKey);
46923
46932
  const templateId = args.template;
46924
- const inputValues = {};
46925
- for (const kv of args.input || []) {
46926
- const eq = kv.indexOf("=");
46927
- if (eq > 0)
46928
- inputValues[kv.slice(0, eq)] = kv.slice(eq + 1);
46929
- }
46930
- const template = await api.getRegistryTemplate(templateId);
46931
- process.stderr.write(`${DIM}Template:${RESET} ${BRAND}${template.todoname}${RESET}
46933
+ const spec = await api.getRegistrySpec(templateId);
46934
+ process.stderr.write(`${DIM}Template:${RESET} ${BRAND}${spec.name}${RESET}
46932
46935
  `);
46933
- if (template.description)
46934
- process.stderr.write(`${DIM}${template.description}${RESET}
46936
+ if (spec.description)
46937
+ process.stderr.write(`${DIM}${spec.description}${RESET}
46935
46938
  `);
46936
- const templateInputs = template.inputs || [];
46937
- if (templateInputs.length && !args["non-interactive"]) {
46938
- for (const inp of templateInputs) {
46939
- const key = inp.label.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/_+$/, "");
46940
- if (inputValues[key])
46941
- continue;
46942
- const req = inp.required ? ` ${RED}*${RESET}` : "";
46943
- const hint = inp.placeholder ? ` ${DIM}(${inp.placeholder.split(`
46944
- `)[0]})${RESET}` : "";
46945
- const val = await readLine(`${inp.label}${req}${hint}: `);
46946
- if (val)
46947
- inputValues[key] = val;
46948
- }
46949
- }
46950
- if (Object.keys(inputValues).length) {
46951
- process.stderr.write(`${DIM}Inputs:${RESET} ${JSON.stringify(inputValues)}
46952
- `);
46953
- }
46954
46939
  const projects2 = await api.listProjects();
46955
46940
  let projectId2 = args.project;
46956
46941
  if (!projectId2) {
46957
- projectId2 = cfgScope.data.default_project_id || projects2.find((p) => p.project?.isDefault)?.project?.id || projects2[0]?.project?.id;
46942
+ const cached = cfgScope.data.default_project_id;
46943
+ projectId2 = (cached && projects2.some((p) => getItemId(p) === cached) ? cached : null) || projects2.find((p) => p.project?.isDefault)?.project?.id || projects2[0]?.project?.id;
46958
46944
  }
46959
46945
  if (!projectId2) {
46960
46946
  process.stderr.write(`Error: No project found
46961
46947
  `);
46962
46948
  process.exit(1);
46963
46949
  }
46964
- const todo2 = await api.startFromTemplate(projectId2, templateId, { inputValues });
46950
+ const todo2 = await api.startFromSpec(projectId2, templateId);
46965
46951
  const todoId = todo2.id;
46966
46952
  cfgScope.setLastTodoId(todoId);
46967
46953
  const frontendUrl2 = getFrontendUrl(apiUrl, projectId2, todoId);
@@ -47157,7 +47143,18 @@ Resumed: ${CYAN}${getFrontendUrl(apiUrl, projectId2, todoId)}${RESET}
47157
47143
  agent = { ...agent, permissions: { ...perms, allow: [...perms.allow || [], "*:*"] } };
47158
47144
  }
47159
47145
  cfg.addToHistory(content);
47160
- const todo = await api.addMessage(projectId, content, agent);
47146
+ let todo;
47147
+ try {
47148
+ todo = await api.addMessage(projectId, content, agent);
47149
+ } catch (e) {
47150
+ if (!args.project && cfgScope.data.default_project_id === projectId && /failed: 403/.test(e.message || "")) {
47151
+ cfgScope.clearDefaultProject();
47152
+ process.stderr.write(`${RED}Not authorized for cached default project ${projectName} (${projectId}) \u2014 cleared it. Re-run to pick a project.${RESET}
47153
+ `);
47154
+ process.exit(1);
47155
+ }
47156
+ throw e;
47157
+ }
47161
47158
  const actualTodoId = todo.id || crypto.randomUUID();
47162
47159
  cfgScope.setLastTodoId(actualTodoId);
47163
47160
  const frontendUrl = getFrontendUrl(apiUrl, projectId, actualTodoId);
package/package.json CHANGED
@@ -1,19 +1,21 @@
1
1
  {
2
2
  "name": "@todoforai/cli",
3
- "version": "0.1.21",
3
+ "version": "0.1.24",
4
4
  "type": "module",
5
5
  "bin": {
6
- "todoforai-cli": "dist/todoai.js",
7
- "todoai": "dist/todoai.js"
6
+ "todoforai-cli": "bin/todoforai-cli.js",
7
+ "todoai": "bin/todoforai-cli.js"
8
8
  },
9
9
  "files": [
10
+ "bin",
10
11
  "dist/todoai.js"
11
12
  ],
12
13
  "scripts": {
13
14
  "build": "bun build src/index.ts --target=bun --outfile dist/todoai.js --external ws",
14
15
  "prepublishOnly": "bun run build",
15
16
  "start": "bun run src/index.ts",
16
- "dev": "bun run src/index.ts"
17
+ "dev": "bun run src/index.ts",
18
+ "postinstall": "rm -rf node_modules/@todoforai/edge && ln -s ../../../edge/bun node_modules/@todoforai/edge"
17
19
  },
18
20
  "dependencies": {
19
21
  "@shared/credentials": "file:../packages/shared-credentials",
@@ -23,7 +25,7 @@
23
25
  "ws": "^8.18.0"
24
26
  },
25
27
  "peerDependencies": {
26
- "@todoforai/edge": ">=0.12.0"
28
+ "@todoforai/edge": "file:../edge/bun"
27
29
  },
28
30
  "peerDependenciesMeta": {
29
31
  "@todoforai/edge": {
@@ -32,6 +34,7 @@
32
34
  },
33
35
  "devDependencies": {
34
36
  "@types/ws": "^8.5.13",
37
+ "@todoforai/edge": "file:../edge/bun",
35
38
  "typescript": "^5.7.0"
36
39
  }
37
40
  }