@todoforai/cli 0.1.26 → 0.1.28

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.
@@ -5,5 +5,5 @@ import { existsSync } from "node:fs";
5
5
  import { fileURLToPath } from "node:url";
6
6
 
7
7
  const src = fileURLToPath(new URL("../src/index.ts", import.meta.url));
8
- const dist = fileURLToPath(new URL("../dist/todoai.js", import.meta.url));
8
+ const dist = fileURLToPath(new URL("../dist/todoforai-cli.js", import.meta.url));
9
9
  await import(existsSync(src) ? src : dist);
@@ -41771,9 +41771,9 @@ var require_route = __commonJS((exports, module) => {
41771
41771
  var conversions = require_conversions();
41772
41772
  function buildGraph() {
41773
41773
  const graph = {};
41774
- const models = Object.keys(conversions);
41775
- for (let len = models.length, i = 0;i < len; i++) {
41776
- graph[models[i]] = {
41774
+ const models2 = Object.keys(conversions);
41775
+ for (let len = models2.length, i = 0;i < len; i++) {
41776
+ graph[models2[i]] = {
41777
41777
  distance: -1,
41778
41778
  parent: null
41779
41779
  };
@@ -41805,23 +41805,23 @@ var require_route = __commonJS((exports, module) => {
41805
41805
  };
41806
41806
  }
41807
41807
  function wrapConversion(toModel, graph) {
41808
- const path4 = [graph[toModel].parent, toModel];
41808
+ const path3 = [graph[toModel].parent, toModel];
41809
41809
  let fn = conversions[graph[toModel].parent][toModel];
41810
41810
  let cur = graph[toModel].parent;
41811
41811
  while (graph[cur].parent) {
41812
- path4.unshift(graph[cur].parent);
41812
+ path3.unshift(graph[cur].parent);
41813
41813
  fn = link(conversions[graph[cur].parent][cur], fn);
41814
41814
  cur = graph[cur].parent;
41815
41815
  }
41816
- fn.conversion = path4;
41816
+ fn.conversion = path3;
41817
41817
  return fn;
41818
41818
  }
41819
41819
  module.exports = function(fromModel) {
41820
41820
  const graph = deriveBFS(fromModel);
41821
41821
  const conversion = {};
41822
- const models = Object.keys(graph);
41823
- for (let len = models.length, i = 0;i < len; i++) {
41824
- const toModel = models[i];
41822
+ const models2 = Object.keys(graph);
41823
+ for (let len = models2.length, i = 0;i < len; i++) {
41824
+ const toModel = models2[i];
41825
41825
  const node = graph[toModel];
41826
41826
  if (node.parent === null) {
41827
41827
  continue;
@@ -41837,7 +41837,7 @@ var require_color_convert = __commonJS((exports, module) => {
41837
41837
  var conversions = require_conversions();
41838
41838
  var route = require_route();
41839
41839
  var convert = {};
41840
- var models = Object.keys(conversions);
41840
+ var models2 = Object.keys(conversions);
41841
41841
  function wrapRaw(fn) {
41842
41842
  const wrappedFn = function(...args) {
41843
41843
  const arg0 = args[0];
@@ -41876,7 +41876,7 @@ var require_color_convert = __commonJS((exports, module) => {
41876
41876
  }
41877
41877
  return wrappedFn;
41878
41878
  }
41879
- models.forEach((fromModel) => {
41879
+ models2.forEach((fromModel) => {
41880
41880
  convert[fromModel] = {};
41881
41881
  Object.defineProperty(convert[fromModel], "channels", { value: conversions[fromModel].channels });
41882
41882
  Object.defineProperty(convert[fromModel], "labels", { value: conversions[fromModel].labels });
@@ -42039,7 +42039,7 @@ var require_has_flag = __commonJS((exports, module) => {
42039
42039
 
42040
42040
  // node_modules/supports-color/index.js
42041
42041
  var require_supports_color = __commonJS((exports, module) => {
42042
- var os4 = __require("os");
42042
+ var os3 = __require("os");
42043
42043
  var tty = __require("tty");
42044
42044
  var hasFlag = require_has_flag();
42045
42045
  var { env } = process;
@@ -42087,7 +42087,7 @@ var require_supports_color = __commonJS((exports, module) => {
42087
42087
  return min;
42088
42088
  }
42089
42089
  if (process.platform === "win32") {
42090
- const osRelease = os4.release().split(".");
42090
+ const osRelease = os3.release().split(".");
42091
42091
  if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
42092
42092
  return Number(osRelease[2]) >= 14931 ? 3 : 2;
42093
42093
  }
@@ -42665,7 +42665,7 @@ import { realpathSync, readFileSync as readFileSync3 } from "fs";
42665
42665
  import { resolve as resolve3 } from "path";
42666
42666
  import { fileURLToPath } from "url";
42667
42667
  import { homedir as homedir3 } from "os";
42668
- import path4 from "path";
42668
+ import path3 from "path";
42669
42669
 
42670
42670
  // node_modules/@todoforai/update-notifier/src/index.ts
42671
42671
  import fs from "fs";
@@ -42731,7 +42731,7 @@ function randomTip() {
42731
42731
  return TIPS[Math.floor(Math.random() * TIPS.length)];
42732
42732
  }
42733
42733
 
42734
- // ../edge/bun/src/api.ts
42734
+ // ../packages/shared-api/src/api.ts
42735
42735
  function restBasePath(apiKey) {
42736
42736
  return apiKey.startsWith("dst_") ? "/dst/v1" : "/api/v1";
42737
42737
  }
@@ -42812,8 +42812,8 @@ class ApiClient {
42812
42812
  deleteProject(projectId) {
42813
42813
  return this.request("DELETE", `/api/v1/projects/${projectId}`);
42814
42814
  }
42815
- createTodo(projectId, content, agentSettings) {
42816
- return this.request("POST", `/api/v1/projects/${projectId}/todos`, { content, agentSettings });
42815
+ createTodo(projectId, content, agentSettings, groupTag, groupName) {
42816
+ return this.request("POST", `/api/v1/projects/${projectId}/todos`, { content, agentSettings, groupTag, groupName });
42817
42817
  }
42818
42818
  listTodos(projectId, opts) {
42819
42819
  if (!projectId)
@@ -42851,12 +42851,16 @@ class ApiClient {
42851
42851
  getAgentSettings(id) {
42852
42852
  return this.request("GET", `/api/v1/agents/${id}`);
42853
42853
  }
42854
- addMessage(projectId, content, agentSettings, todoId, attachments, scheduledTimestamp) {
42854
+ addMessage(projectId, content, agentSettings, todoId, attachments, scheduledTimestamp, groupTag, groupName) {
42855
42855
  const payload = { content, agentSettings, attachments: attachments || [] };
42856
42856
  if (todoId)
42857
42857
  payload.todoId = todoId;
42858
42858
  if (scheduledTimestamp)
42859
42859
  payload.scheduledTimestamp = scheduledTimestamp;
42860
+ if (!todoId && groupTag)
42861
+ payload.groupTag = groupTag;
42862
+ if (!todoId && groupName)
42863
+ payload.groupName = groupName;
42860
42864
  return this.request("POST", `/api/v1/projects/${projectId}/todos`, payload);
42861
42865
  }
42862
42866
  async showFile(todoId, file, filename, opts = {}) {
@@ -42898,6 +42902,9 @@ class ApiClient {
42898
42902
  updateAgentSettings(agentId, agentSettingsId, updates) {
42899
42903
  return this.request("PUT", `/api/v1/agents/${agentId}/settings`, { agentSettingsId, updates });
42900
42904
  }
42905
+ deleteAgentSettings(agentSettingsId) {
42906
+ return this.request("DELETE", `/api/v1/agents/${agentSettingsId}`);
42907
+ }
42901
42908
  getGlobalAgentSettings() {
42902
42909
  return this.request("GET", "/api/v1/agents/global");
42903
42910
  }
@@ -42933,11 +42940,11 @@ class ApiClient {
42933
42940
  throw new Error(`Spec '${specId}' not found (${res.status})`);
42934
42941
  return res.json();
42935
42942
  }
42936
- async initDeviceLogin(clientName = "edge") {
42943
+ async initDeviceLogin(clientName = "edge", opts = {}) {
42937
42944
  const res = await fetch(`${this.apiUrl}/api/v1/cli/login/init`, {
42938
42945
  method: "POST",
42939
42946
  headers: { "content-type": "application/json" },
42940
- body: JSON.stringify({ clientName })
42947
+ body: JSON.stringify({ clientName, ...opts })
42941
42948
  });
42942
42949
  if (!res.ok)
42943
42950
  throw new Error(`Device login init failed: ${res.status}`);
@@ -42957,35 +42964,50 @@ class ApiClient {
42957
42964
  return res.json();
42958
42965
  }
42959
42966
  }
42960
-
42961
- // ../edge/bun/src/frontend-ws.ts
42967
+ // ../packages/shared-api/src/frontend-ws.ts
42962
42968
  import WebSocket from "ws";
42963
42969
 
42964
- // ../edge/bun/src/config.ts
42965
- import path2 from "path";
42966
- import os2 from "os";
42970
+ // ../packages/shared-credentials/src/index.ts
42971
+ import { readFileSync, writeFileSync, mkdirSync, chmodSync } from "fs";
42972
+ import { join, dirname } from "path";
42973
+ import { homedir, platform } from "os";
42967
42974
  function normalizeApiUrl(url) {
42968
42975
  if (url.startsWith("localhost"))
42969
42976
  return `http://${url}`;
42970
42977
  if (!url.startsWith("http://") && !url.startsWith("https://"))
42971
42978
  return `https://${url}`;
42972
- return url;
42979
+ return url.replace(/\/+$/, "");
42973
42980
  }
42974
- var SUBCOMMANDS = new Set(["login", "logout", "update"]);
42975
42981
  function credentialsPath() {
42976
- const sys = os2.platform();
42977
- if (sys === "win32") {
42978
- return path2.join(os2.homedir(), "AppData", "Roaming", "todoforai", "credentials.json");
42979
- }
42980
- if (sys === "darwin") {
42981
- return path2.join(os2.homedir(), "Library", "Application Support", "todoforai", "credentials.json");
42982
+ return platform() === "win32" ? join(homedir(), "AppData", "Roaming", "todoforai", "credentials.json") : platform() === "darwin" ? join(homedir(), "Library", "Application Support", "todoforai", "credentials.json") : join(process.env.XDG_CONFIG_HOME || join(homedir(), ".config"), "todoforai", "credentials.json");
42983
+ }
42984
+ function read() {
42985
+ try {
42986
+ return JSON.parse(readFileSync(credentialsPath(), "utf-8"));
42987
+ } catch {
42988
+ return {};
42982
42989
  }
42983
- const xdg = process.env.XDG_CONFIG_HOME || path2.join(os2.homedir(), ".config");
42984
- return path2.join(xdg, "todoforai", "credentials.json");
42985
42990
  }
42986
- var CREDENTIALS_PATH = credentialsPath();
42991
+ function writeSecure(data) {
42992
+ const p = credentialsPath();
42993
+ mkdirSync(dirname(p), { recursive: true });
42994
+ writeFileSync(p, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
42995
+ try {
42996
+ chmodSync(p, 384);
42997
+ } catch {}
42998
+ }
42999
+ function readCredential(apiUrl) {
43000
+ const c = read();
43001
+ const v = c[apiUrl] || c.apiToken || c.apiKey;
43002
+ return typeof v === "string" ? v : "";
43003
+ }
43004
+ function writeCredential(apiUrl, apiKey) {
43005
+ const c = read();
43006
+ c[apiUrl] = apiKey;
43007
+ writeSecure(c);
43008
+ }
42987
43009
 
42988
- // ../edge/bun/src/frontend-ws.ts
43010
+ // ../packages/shared-api/src/frontend-ws.ts
42989
43011
  var log = (level, ...args) => console.log(`[frontend-ws:${level}]`, ...args);
42990
43012
 
42991
43013
  class FrontendWebSocket {
@@ -43174,50 +43196,6 @@ class FrontendWebSocket {
43174
43196
  });
43175
43197
  }
43176
43198
  }
43177
-
43178
- // src/args.ts
43179
- import { parseArgs } from "util";
43180
- // package.json
43181
- var package_default = {
43182
- name: "@todoforai/cli",
43183
- version: "0.1.24",
43184
- type: "module",
43185
- bin: {
43186
- "todoforai-cli": "bin/todoforai-cli.js",
43187
- todoai: "bin/todoforai-cli.js"
43188
- },
43189
- files: [
43190
- "bin",
43191
- "dist/todoai.js"
43192
- ],
43193
- scripts: {
43194
- build: "bun build src/index.ts --target=bun --outfile dist/todoai.js --external ws",
43195
- prepublishOnly: "bun run build",
43196
- start: "bun run src/index.ts",
43197
- dev: "bun run src/index.ts",
43198
- postinstall: "rm -rf node_modules/@todoforai/edge && ln -s ../../../edge/bun node_modules/@todoforai/edge"
43199
- },
43200
- dependencies: {
43201
- "@shared/credentials": "file:../packages/shared-credentials",
43202
- "@todoforai/update-notifier": "^0.1.0",
43203
- "cli-highlight": "^2.1.11",
43204
- "diff-match-patch": "^1.0.5",
43205
- ws: "^8.18.0"
43206
- },
43207
- peerDependencies: {
43208
- "@todoforai/edge": "file:../edge/bun"
43209
- },
43210
- peerDependenciesMeta: {
43211
- "@todoforai/edge": {
43212
- optional: true
43213
- }
43214
- },
43215
- devDependencies: {
43216
- "@types/ws": "^8.5.13",
43217
- "@todoforai/edge": "file:../edge/bun",
43218
- typescript: "^5.7.0"
43219
- }
43220
- };
43221
43199
  // ../packages/shared-fbe/src/enums.ts
43222
43200
  var TodoStatus;
43223
43201
  ((TodoStatus2) => {
@@ -43240,6 +43218,59 @@ var TodoStatus;
43240
43218
  TodoStatus2["ARCHIVED"] = "ARCHIVED";
43241
43219
  TodoStatus2["DELETED"] = "DELETED";
43242
43220
  })(TodoStatus ||= {});
43221
+ // ../packages/shared-fbe/src/models.ts
43222
+ var DEFAULT_MODEL = "anthropic:anthropic/claude-sonnet-5";
43223
+ var MODEL_ALIASES = {
43224
+ claude: DEFAULT_MODEL
43225
+ };
43226
+ var PROVIDER_BY_AUTHOR = {
43227
+ anthropic: "anthropic",
43228
+ openai: "openai",
43229
+ deepseek: "deepseek",
43230
+ poolside: "poolside",
43231
+ "x-ai": "xai",
43232
+ "z-ai": "z.ai",
43233
+ google: "google-ai-studio",
43234
+ moonshotai: "opencode_go"
43235
+ };
43236
+ function isQualifiedModel(model) {
43237
+ const id = model.trim();
43238
+ const colon = id.indexOf(":");
43239
+ const slash = id.indexOf("/");
43240
+ return colon > 0 && colon < id.length - 1 && (slash === -1 || colon < slash);
43241
+ }
43242
+ function qualifyModel(model) {
43243
+ const id = model?.trim();
43244
+ if (!id)
43245
+ return { model: DEFAULT_MODEL };
43246
+ if (isQualifiedModel(id))
43247
+ return { model: id };
43248
+ const suffix = id.match(/\([^)]+\)$/)?.[0] ?? "";
43249
+ const bare = suffix ? id.slice(0, -suffix.length) : id;
43250
+ const aliased = MODEL_ALIASES[bare.toLowerCase()];
43251
+ if (aliased)
43252
+ return { model: aliased + suffix };
43253
+ const lower = bare.toLowerCase();
43254
+ const provider = PROVIDER_BY_AUTHOR[lower.split("/")[0]];
43255
+ if (provider && lower.includes("/"))
43256
+ return { model: `${provider}:${lower}${suffix}` };
43257
+ return { model: DEFAULT_MODEL, fallback: id };
43258
+ }
43259
+ function qualifiedModelIds(listing, filter) {
43260
+ const q = filter?.toLowerCase();
43261
+ const ids = [];
43262
+ for (const m of listing?.data ?? []) {
43263
+ const id = String(m.id);
43264
+ if (m.provider) {
43265
+ ids.push(`${m.provider.toLowerCase()}:${id}`);
43266
+ continue;
43267
+ }
43268
+ const r = qualifyModel(id);
43269
+ if (!r.fallback)
43270
+ ids.push(r.model);
43271
+ }
43272
+ return q ? ids.filter((id) => id.toLowerCase().includes(q)) : ids;
43273
+ }
43243
43274
  // ../packages/shared-fbe/src/billing.ts
43244
43275
  var USAGE_WINDOWS = {
43245
43276
  SESSION_MS: 5 * 60 * 60 * 1000,
@@ -43290,7 +43321,7 @@ var mimetypes_default = {
43290
43321
  gif: { mime: "image/gif", type: "image" },
43291
43322
  webp: { mime: "image/webp", type: "image" },
43292
43323
  bmp: { mime: "image/bmp", type: "image" },
43293
- svg: { mime: "image/svg+xml", type: "text" },
43324
+ svg: { mime: "image/svg+xml", type: "image" },
43294
43325
  heic: { mime: "image/heic", type: "image", conversionProfile: "to_jpeg_90" },
43295
43326
  heif: { mime: "image/heif", type: "image", conversionProfile: "to_jpeg_90" },
43296
43327
  mp3: { mime: "audio/mpeg", type: "audio" },
@@ -43498,7 +43529,7 @@ var SERVER_TO_FRONTENDS = {
43498
43529
  predicted_reply_updated: (todoId) => `${"predict:reply_updated" /* PREDICTED_REPLY_UPDATED */}:${todoId}`,
43499
43530
  error_result: (todoId) => `${"block:error_result" /* BLOCK_ERROR_RESULT */}:${todoId}`,
43500
43531
  meta_result: (todoId) => `${"block:meta_result" /* BLOCK_META_RESULT */}:${todoId}`,
43501
- file_changed: (edgeId, path3) => `${"block:file_changed" /* BLOCK_FILE_CHANGED */}:${edgeId}:${path3}`,
43532
+ file_changed: (edgeId, path2) => `${"block:file_changed" /* BLOCK_FILE_CHANGED */}:${edgeId}:${path2}`,
43502
43533
  notify: (todoId) => `${"NOTIFY" /* NOTIFY */}:${todoId}`
43503
43534
  },
43504
43535
  notify: {
@@ -43746,13 +43777,10 @@ function getBlockServerPrefix(block) {
43746
43777
  const prefixMatch = agentPattern.match(/^(.*?:)BASH(?:\(|$)/);
43747
43778
  return prefixMatch ? prefixMatch[1] : "";
43748
43779
  }
43749
- var BASH_LIKE_BLOCK_TYPES = new Set(["bash", "machine_exec"]);
43750
43780
  function getBlockPatterns(block) {
43751
43781
  if (Array.isArray(block.generalized_pattern))
43752
43782
  return block.generalized_pattern;
43753
- const agentPattern = typeof block.generalized_pattern === "string" ? block.generalized_pattern : "";
43754
- const isBashLike = BASH_LIKE_BLOCK_TYPES.has(block.type.toLowerCase()) || /:BASH(\(|$)/.test(agentPattern);
43755
- if (isBashLike && block.cmd) {
43783
+ if (block.type.toLowerCase() === "bash" && block.cmd) {
43756
43784
  const prefix = getBlockServerPrefix(block);
43757
43785
  return generalizeBashCmd(block.cmd).map((p) => prefix + p);
43758
43786
  }
@@ -43850,8 +43878,7 @@ function getBlockNewPatterns(block, permissions) {
43850
43878
  if (Array.isArray(block.generalized_pattern)) {
43851
43879
  return getNewPatterns(block.generalized_pattern, permissions);
43852
43880
  }
43853
- const agentPattern = typeof block.generalized_pattern === "string" ? block.generalized_pattern : "";
43854
- const isBash = (block.type.toLowerCase() === "bash" || agentPattern.includes(":BASH(")) && !!block.cmd;
43881
+ const isBash = block.type.toLowerCase() === "bash" && !!block.cmd;
43855
43882
  if (!isBash) {
43856
43883
  return getBlockPatterns(block).filter((p) => !isPatternAllowed(permissions, p));
43857
43884
  }
@@ -43927,10 +43954,9 @@ When the user asks you to recommend how to grow (e.g. "recommend growth TODOs",
43927
43954
  Recommending actionable TODOs from your recommendations:
43928
43955
  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:
43929
43956
 
43930
- 1. GET A TEMPLATE (todoregistry-cli) \u2014 generate-first, reuse only on a close fit:
43931
- - Default: CREATE a template tailored to THIS business, right there (auth is inherited from your shell):
43932
- \`todoregistry-cli create --name "<name>" --description "<one line>" --body @<prompt-file>\` (or \`--body -\` for stdin). It prints the new template id on stdout.
43933
- - Before creating, you MAY \`todoregistry-cli search <query>\` / \`list -c <category>\` / \`get <id>\`; reuse an existing template ONLY when it already fits this business closely with no meaningful edits. If it needs tailoring, create a new one instead \u2014 do not recommend a generic playbook that ignores this company's specifics.
43957
+ 1. GET A TEMPLATE (api_create_template) \u2014 generate-first, reuse only on a close fit:
43958
+ - Default: CREATE a template tailored to THIS business with the \`api_create_template\` tool. It returns the new template id.
43959
+ - 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.
43934
43960
 
43935
43961
  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.
43936
43962
 
@@ -43939,9 +43965,10 @@ To materialize growth recommendations, create recommendation cards in the projec
43939
43965
  - 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.
43940
43966
  - 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.
43941
43967
 
43942
- 2. RECOMMEND (todoforai-cli \u2014 references a template id onto the project; never creates a running todo):
43943
- \`todoforai-cli recommend --template <id> --project <projectId> --business-context <businessContextId> --note "<why>" --priority high|medium|low\`
43944
- 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.
43968
+ 2. RECOMMEND (api_recommend_template \u2014 references a template id onto the project; never creates a running todo):
43969
+ Call \`api_recommend_template\` with the template id, a one-line \`note\` (the WHY for THIS business \u2014 it becomes the card's tooltip) and a \`priority\` of high, medium or low. Project and business context default to the current session, so pass them only when targeting a different one. The card appears in the Recommendations column for the user to review before any credits are spent.
43970
+
43971
+ Both tools run in-process and need no machine or shell \u2014 never shell out to \`todoregistry-cli\` / \`todoforai-cli\` for this.
43945
43972
 
43946
43973
  3. Recommend 3-6 cards (highest priority first). Then reply with a one-line summary per card and tell the user they're waiting in the Recommendations column for review.`,
43947
43974
  mcpConfigs: {},
@@ -43953,8 +43980,6 @@ To materialize growth recommendations, create recommendation cards in the projec
43953
43980
  "cloud:google_rag",
43954
43981
  "builtin:todoforai_api",
43955
43982
  "builtin:question",
43956
- "cloud:BASH(cmd: todoregistry-cli *)",
43957
- "cloud:BASH(cmd: todoforai-cli *)",
43958
43983
  "cloud:BASH(cmd: curl *)"
43959
43984
  ],
43960
43985
  ask: [],
@@ -44128,6 +44153,40 @@ var TOPICS = {
44128
44153
  audience: "frontend"
44129
44154
  }
44130
44155
  };
44156
+ // src/args.ts
44157
+ import { parseArgs } from "util";
44158
+ // package.json
44159
+ var package_default = {
44160
+ name: "@todoforai/cli",
44161
+ version: "0.1.28",
44162
+ type: "module",
44163
+ bin: {
44164
+ "todoforai-cli": "bin/todoforai-cli.js",
44165
+ "tfa-cli": "bin/todoforai-cli.js",
44166
+ todoai: "bin/todoforai-cli.js"
44167
+ },
44168
+ files: [
44169
+ "bin",
44170
+ "dist/todoforai-cli.js"
44171
+ ],
44172
+ scripts: {
44173
+ build: "bun build src/index.ts --target=bun --outfile dist/todoforai-cli.js --external ws",
44174
+ prepublishOnly: "bun run build",
44175
+ start: "bun run src/index.ts",
44176
+ dev: "bun run src/index.ts"
44177
+ },
44178
+ dependencies: {
44179
+ "@todoforai/update-notifier": "^0.1.0",
44180
+ "cli-highlight": "^2.1.11",
44181
+ "diff-match-patch": "^1.0.5",
44182
+ ws: "^8.18.0"
44183
+ },
44184
+ devDependencies: {
44185
+ "@types/ws": "^8.5.13",
44186
+ typescript: "^5.7.0"
44187
+ }
44188
+ };
44189
+
44131
44190
  // src/args.ts
44132
44191
  var DEFAULT_API_URL = "https://api.todofor.ai";
44133
44192
  var VERSION = package_default.version;
@@ -44136,7 +44195,7 @@ function getEnv(name) {
44136
44195
  }
44137
44196
  function printUsage() {
44138
44197
  process.stderr.write(`
44139
- todoforai-cli \u2014 TODOforAI CLI (Bun)
44198
+ todoforai-cli \u2014 TODOforAI CLI (Bun). Aliases: tfa-cli, todoai.
44140
44199
 
44141
44200
  Usage:
44142
44201
  todoforai-cli login # Browser-based device auth
@@ -44167,6 +44226,8 @@ Options:
44167
44226
  --path <dir> Workspace path (default: cwd)
44168
44227
  --project <id> Project ID
44169
44228
  --agent, -a <name> Agent name (partial match)
44229
+ --group <slug> Group new todo; omitted inherits TODOFORAI_GROUP_ID
44230
+ --group-name <name> Display name for --group (last write wins)
44170
44231
  --model <model> Override the agent's model for this todo
44171
44232
  (e.g. anthropic:anthropic/claude-opus-5, openai:openai/gpt-5.6-sol)
44172
44233
  --list-agents List available agents (name, id, workspace paths) and exit
@@ -44225,6 +44286,8 @@ function parseCliArgs() {
44225
44286
  project: { type: "string" },
44226
44287
  agent: { type: "string", short: "a" },
44227
44288
  model: { type: "string" },
44289
+ group: { type: "string" },
44290
+ "group-name": { type: "string" },
44228
44291
  "list-agents": { type: "boolean", default: false },
44229
44292
  "list-models": { type: "boolean", default: false },
44230
44293
  "api-url": { type: "string" },
@@ -44605,8 +44668,8 @@ import { resolve, basename } from "path";
44605
44668
  // src/ensure-bridge.ts
44606
44669
  import { spawn, spawnSync } from "child_process";
44607
44670
  import fs2 from "fs";
44608
- import path3 from "path";
44609
- import os3 from "os";
44671
+ import path2 from "path";
44672
+ import os2 from "os";
44610
44673
  function hasBridge() {
44611
44674
  const probe = spawnSync("todoforai-bridge", ["--version"], { stdio: "ignore" });
44612
44675
  return probe.status === 0;
@@ -44675,31 +44738,44 @@ function ensureBridgeCredentials(apiUrl) {
44675
44738
  const login = spawnSync("todoforai-bridge", bridgeLoginArgs(apiUrl), { stdio: "inherit" });
44676
44739
  return login.status === 0;
44677
44740
  }
44678
- function bridgeAlreadyRunning() {
44679
- if (process.platform === "win32")
44680
- return Promise.resolve(false);
44681
- return new Promise((resolve) => {
44682
- const r = spawn("pgrep", ["-f", "todoforai-bridge"]);
44683
- let out = "";
44684
- r.stdout.on("data", (d) => out += d);
44685
- r.on("error", () => resolve(false));
44686
- r.on("close", (code) => resolve(code === 0 && out.trim().length > 0));
44687
- });
44741
+ async function bridgeState(apiUrl, apiKey, deviceId) {
44742
+ try {
44743
+ const devices = await new ApiClient(apiUrl, apiKey).listDevices();
44744
+ return Array.isArray(devices) && devices.some((device) => device.id === deviceId && device.status === "ONLINE") ? "online" : "offline";
44745
+ } catch {
44746
+ return "unknown";
44747
+ }
44688
44748
  }
44689
- async function ensureBridgeRunning(apiUrl, _apiKey) {
44690
- if (await bridgeAlreadyRunning())
44691
- return;
44749
+ async function waitForBridgeOnline(apiUrl, apiKey, deviceId, timeoutMs = 5000) {
44750
+ const deadline = Date.now() + timeoutMs;
44751
+ do {
44752
+ const state = await bridgeState(apiUrl, apiKey, deviceId);
44753
+ if (state !== "offline")
44754
+ return state === "online";
44755
+ await new Promise((resolve) => setTimeout(resolve, 150));
44756
+ } while (Date.now() < deadline);
44757
+ return false;
44758
+ }
44759
+ async function ensureBridgeRunning(apiUrl, apiKey) {
44692
44760
  if (!hasBridge()) {
44693
44761
  console.error("\x1B[2mBridge not started: `todoforai-bridge` was not found on PATH. Install TODOforAI Bridge, or pass --no-bridge (or deprecated --no-edge) to silence this.\x1B[0m");
44694
- return;
44762
+ return false;
44695
44763
  }
44696
44764
  if (!ensureBridgeCredentials(apiUrl)) {
44697
44765
  console.error("\x1B[33mBridge not started: `todoforai-bridge login` did not complete successfully.\x1B[0m");
44698
- return;
44766
+ return false;
44699
44767
  }
44700
- const logDir = path3.join(os3.homedir(), ".todoforai");
44768
+ const deviceId = bridgeDeviceId(apiUrl);
44769
+ if (!deviceId) {
44770
+ console.error("\x1B[33mBridge not started: could not determine this device's id.\x1B[0m");
44771
+ return false;
44772
+ }
44773
+ const initialState = await bridgeState(apiUrl, apiKey, deviceId);
44774
+ if (initialState === "online" || initialState === "unknown")
44775
+ return initialState === "online";
44776
+ const logDir = path2.join(os2.homedir(), ".todoforai");
44701
44777
  fs2.mkdirSync(logDir, { recursive: true });
44702
- const logFile = path3.join(logDir, "bridge.log");
44778
+ const logFile = path2.join(logDir, "bridge.log");
44703
44779
  const out = fs2.openSync(logFile, "a");
44704
44780
  const child = spawn("todoforai-bridge", bridgeRunArgs(apiUrl), {
44705
44781
  detached: true,
@@ -44716,13 +44792,13 @@ async function ensureBridgeRunning(apiUrl, _apiKey) {
44716
44792
  });
44717
44793
  child.unref();
44718
44794
  if (!child.pid)
44719
- return;
44720
- const shortLog = logFile.replace(os3.homedir(), "~");
44721
- setTimeout(() => {
44722
- if (exited && exitCode !== 0) {
44723
- console.error(`\x1B[2mBridge not running (exit ${exitCode}), see ${shortLog}\x1B[0m`);
44724
- }
44725
- }, 500);
44795
+ return false;
44796
+ if (await waitForBridgeOnline(apiUrl, apiKey, deviceId))
44797
+ return true;
44798
+ const shortLog = logFile.replace(os2.homedir(), "~");
44799
+ const reason = exited ? `exit ${exitCode}` : "startup timed out";
44800
+ console.error(`\x1B[33mBridge not ready (${reason}), see ${shortLog}\x1B[0m`);
44801
+ return false;
44726
44802
  }
44727
44803
 
44728
44804
  // src/agent.ts
@@ -44756,9 +44832,13 @@ async function autoCreateAgent(api, resolvedPath) {
44756
44832
  return resp;
44757
44833
  }
44758
44834
  const edges = await api.listEdges();
44759
- const edgeId = Array.isArray(edges) && edges.length ? edges[0].id : null;
44760
- if (!edgeId)
44835
+ if (!Array.isArray(edges) || edges.length === 0) {
44761
44836
  throw new Error("No bridge device or edge available to configure workspace path");
44837
+ }
44838
+ if (edges.length > 1) {
44839
+ throw new Error("No local bridge device found and multiple legacy edges are available; choose an existing agent explicitly");
44840
+ }
44841
+ const edgeId = edges[0].id;
44762
44842
  await api.setAgentEdgeMcpConfig(agentId, agentSettingsId, edgeId, "todoai_edge", { workspacePaths: [resolvedPath] });
44763
44843
  const matches = await api.listAgentSettings({ workspacePath: resolvedPath });
44764
44844
  if (matches.length)
@@ -44769,20 +44849,20 @@ async function autoCreateAgent(api, resolvedPath) {
44769
44849
  }
44770
44850
 
44771
44851
  // src/config.ts
44772
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
44773
- import { dirname, join, resolve as resolve2 } from "path";
44774
- import { homedir, platform } from "os";
44852
+ import { existsSync, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
44853
+ import { dirname as dirname2, join as join2, resolve as resolve2 } from "path";
44854
+ import { homedir as homedir2, platform as platform2 } from "os";
44775
44855
  function getConfigDir() {
44776
- const sys = platform();
44856
+ const sys = platform2();
44777
44857
  if (sys === "win32") {
44778
- const base = process.env.APPDATA || join(homedir(), "AppData", "Roaming");
44779
- return join(base, "todoai-cli");
44858
+ const base = process.env.APPDATA || join2(homedir2(), "AppData", "Roaming");
44859
+ return join2(base, "todoai-cli");
44780
44860
  }
44781
44861
  if (sys === "darwin") {
44782
- return join(homedir(), "Library", "Application Support", "todoai-cli");
44862
+ return join2(homedir2(), "Library", "Application Support", "todoai-cli");
44783
44863
  }
44784
- const xdg = process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
44785
- return join(xdg, "todoai-cli");
44864
+ const xdg = process.env.XDG_CONFIG_HOME || join2(homedir2(), ".config");
44865
+ return join2(xdg, "todoai-cli");
44786
44866
  }
44787
44867
  function defaultScope() {
44788
44868
  return {
@@ -44834,10 +44914,10 @@ class ConfigStore {
44834
44914
  data;
44835
44915
  constructor(pathArg) {
44836
44916
  if (pathArg) {
44837
- const p = resolve2(pathArg.replace(/^~/, homedir()));
44838
- this.path = p.endsWith(".json") ? p : join(p, "config.json");
44917
+ const p = resolve2(pathArg.replace(/^~/, homedir2()));
44918
+ this.path = p.endsWith(".json") ? p : join2(p, "config.json");
44839
44919
  } else {
44840
- this.path = join(getConfigDir(), "config.json");
44920
+ this.path = join2(getConfigDir(), "config.json");
44841
44921
  }
44842
44922
  this.data = this.load();
44843
44923
  }
@@ -44845,7 +44925,7 @@ class ConfigStore {
44845
44925
  if (!existsSync(this.path))
44846
44926
  return defaultConfig();
44847
44927
  try {
44848
- const raw = JSON.parse(readFileSync(this.path, "utf-8"));
44928
+ const raw = JSON.parse(readFileSync2(this.path, "utf-8"));
44849
44929
  delete raw.default_api_key;
44850
44930
  const cfg = migrate(raw);
44851
44931
  const needsMigration = raw.per_api_url == null;
@@ -44860,8 +44940,8 @@ class ConfigStore {
44860
44940
  }
44861
44941
  save() {
44862
44942
  try {
44863
- mkdirSync(dirname(this.path), { recursive: true });
44864
- writeFileSync(this.path, JSON.stringify(this.data, null, 2), "utf-8");
44943
+ mkdirSync2(dirname2(this.path), { recursive: true });
44944
+ writeFileSync2(this.path, JSON.stringify(this.data, null, 2), "utf-8");
44865
44945
  } catch {}
44866
44946
  }
44867
44947
  scope(apiUrl) {
@@ -44921,39 +45001,6 @@ class ScopedConfig {
44921
45001
  this.store.save();
44922
45002
  }
44923
45003
  }
44924
-
44925
- // ../packages/shared-credentials/src/index.ts
44926
- import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, chmodSync } from "fs";
44927
- import { join as join2, dirname as dirname2 } from "path";
44928
- import { homedir as homedir2, platform as platform2 } from "os";
44929
- function credentialsPath2() {
44930
- return platform2() === "win32" ? join2(homedir2(), "AppData", "Roaming", "todoforai", "credentials.json") : platform2() === "darwin" ? join2(homedir2(), "Library", "Application Support", "todoforai", "credentials.json") : join2(process.env.XDG_CONFIG_HOME || join2(homedir2(), ".config"), "todoforai", "credentials.json");
44931
- }
44932
- function read() {
44933
- try {
44934
- return JSON.parse(readFileSync2(credentialsPath2(), "utf-8"));
44935
- } catch {
44936
- return {};
44937
- }
44938
- }
44939
- function writeSecure(data) {
44940
- const p = credentialsPath2();
44941
- mkdirSync2(dirname2(p), { recursive: true });
44942
- writeFileSync2(p, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
44943
- try {
44944
- chmodSync(p, 384);
44945
- } catch {}
44946
- }
44947
- function readCredential(apiUrl) {
44948
- const c2 = read();
44949
- const v = c2[apiUrl] || c2.apiToken || c2.apiKey;
44950
- return typeof v === "string" ? v : "";
44951
- }
44952
- function writeCredential(apiUrl, apiKey) {
44953
- const c2 = read();
44954
- c2[apiUrl] = apiKey;
44955
- writeSecure(c2);
44956
- }
44957
45004
  // src/logo.ts
44958
45005
  var LETTERS = {
44959
45006
  t: [" x ", "xxxx", " x ", " xll", " xll", " xxx"],
@@ -45027,6 +45074,12 @@ function printLogo() {
45027
45074
  `);
45028
45075
  }
45029
45076
 
45077
+ // src/urls.ts
45078
+ function getFrontendUrl(apiUrl, todoId) {
45079
+ const local = apiUrl.includes("localhost:4000") || apiUrl.includes("127.0.0.1:4000");
45080
+ return `${local ? "http://localhost:3000" : "https://todofor.ai"}/t/${todoId}`;
45081
+ }
45082
+
45030
45083
  // src/inspect.ts
45031
45084
  var xmlAttr = (v) => JSON.stringify(String(v));
45032
45085
  var DROP_IF_EMPTY = new Set(["runMeta", "meta", "attachments", "blocks"]);
@@ -45858,17 +45911,6 @@ async function watchTodo(ws, todoId, projectId, opts = {}) {
45858
45911
  "block:start_catfile"
45859
45912
  ]);
45860
45913
  const signalActivity = () => opts.activityEvent?.set();
45861
- let edgeId;
45862
- let rootPath = "";
45863
- if (opts.agentSettings) {
45864
- const emc = opts.agentSettings.edgesMcpConfigs || {};
45865
- edgeId = Object.keys(emc)[0];
45866
- if (edgeId) {
45867
- const ec = emc[edgeId];
45868
- const tc = ec?.todoai_edge || ec?.todoai || {};
45869
- rootPath = (tc.workspacePaths || [])[0] || "";
45870
- }
45871
- }
45872
45914
  const blocksStore = new Map;
45873
45915
  const diffStore = diffStoreByWs.get(ws) ?? new Map;
45874
45916
  diffStoreByWs.set(ws, diffStore);
@@ -46055,6 +46097,12 @@ ${YELLOW}*${RESET} ${YELLOW}${blockType}${RESET}${extra}
46055
46097
  `);
46056
46098
  signalActivity();
46057
46099
  }
46100
+ } else if (msgType === "NOTIFY") {
46101
+ const sevColor = payload.severity === "error" ? RED : YELLOW;
46102
+ process.stderr.write(`
46103
+ ${sevColor}\u26A0 ${payload.title || "Notice"}${RESET}${payload.message ? ` ${DIM}${payload.message}${RESET}` : ""}
46104
+ `);
46105
+ signalActivity();
46058
46106
  } else if (msgType === "todo:status") {
46059
46107
  const status = payload.status || "";
46060
46108
  process.stderr.write(`
@@ -46085,6 +46133,9 @@ ${DIM}[todo:status] ${status}${RESET}
46085
46133
  const status = result?.payload?.status || result?.type || "unknown";
46086
46134
  process.stderr.write(`Warning: Stopped: ${status}
46087
46135
  `);
46136
+ process.exitCode = 1;
46137
+ } else {
46138
+ process.exitCode = 0;
46088
46139
  }
46089
46140
  return true;
46090
46141
  } catch (e) {
@@ -46128,20 +46179,6 @@ async function listAgentsCommand(api, opts) {
46128
46179
  }
46129
46180
  }
46130
46181
 
46131
- // src/list-models.ts
46132
- async function listModelsCommand(api, opts) {
46133
- const res = await api.listModels();
46134
- const q = opts.filter?.toLowerCase();
46135
- const ids = (res?.data || []).map((m) => String(m.id).includes("/") ? `${String(m.id).split("/")[0]}:${m.id}` : String(m.id)).filter((id) => !q || id.toLowerCase().includes(q));
46136
- if (opts.json) {
46137
- console.log(JSON.stringify(ids, null, 2));
46138
- return;
46139
- }
46140
- for (const id of ids)
46141
- process.stderr.write(`${BRAND}${id}${RESET}
46142
- `);
46143
- }
46144
-
46145
46182
  // src/agent-command.ts
46146
46183
  function printAgentHelp() {
46147
46184
  process.stderr.write(`
@@ -46151,6 +46188,7 @@ Usage:
46151
46188
  todoforai-cli agent list List agents (name, model, id, paths)
46152
46189
  todoforai-cli agent get <agent> Show a single agent's settings
46153
46190
  todoforai-cli agent update <agent> <field=value>\u2026 Update one or more settings
46191
+ todoforai-cli agent delete <agent> Delete an agent configuration (asks to confirm)
46154
46192
 
46155
46193
  <agent> is a name or id (unique partial name also works).
46156
46194
  Fields map directly to agent settings; values are parsed as JSON when possible
@@ -46248,6 +46286,32 @@ async function agentCommand(api, positionals, args, formatPath) {
46248
46286
  }
46249
46287
  const summary = Object.keys(updates).map((k) => `${k}=${JSON.stringify(updated[k])}`).join(" ");
46250
46288
  process.stderr.write(`${GREEN}\u2705 ${getDisplayName(agent)} updated: ${summary}${RESET}
46289
+ `);
46290
+ return;
46291
+ }
46292
+ if (sub === "delete" || sub === "rm") {
46293
+ const query = positionals[2];
46294
+ if (!query) {
46295
+ process.stderr.write(`${RED}Usage: todoforai-cli agent delete <name|id>${RESET}
46296
+ `);
46297
+ process.exit(2);
46298
+ }
46299
+ const agents = await api.listAgentSettings();
46300
+ const agent = resolveAgent(agents, query);
46301
+ const name = getDisplayName(agent);
46302
+ if (!process.stdin.isTTY) {
46303
+ process.stderr.write(`${RED}Refusing to delete '${name}' non-interactively (delete needs an interactive confirm).${RESET}
46304
+ `);
46305
+ process.exit(2);
46306
+ }
46307
+ const ok = await singleChar(`${RED}Delete agent '${name}'? [y/N] ${RESET}`);
46308
+ if (ok !== "y") {
46309
+ process.stderr.write(`Aborted
46310
+ `);
46311
+ return;
46312
+ }
46313
+ await api.deleteAgentSettings(getItemId(agent));
46314
+ process.stderr.write(`${GREEN}\u2705 Deleted ${name}${RESET}
46251
46315
  `);
46252
46316
  return;
46253
46317
  }
@@ -46453,18 +46517,12 @@ async function listTodosCommand(api, defaultProjectId, argv) {
46453
46517
 
46454
46518
  // src/index.ts
46455
46519
  try {
46456
- const pkgPath = path4.resolve(fileURLToPath(import.meta.url), "../../package.json");
46520
+ const pkgPath = path3.resolve(fileURLToPath(import.meta.url), "../../package.json");
46457
46521
  checkForUpdates(JSON.parse(readFileSync3(pkgPath, "utf-8")));
46458
46522
  } catch {}
46459
- function formatPathWithTilde(path5) {
46523
+ function formatPathWithTilde(path4) {
46460
46524
  const home = homedir3();
46461
- return path5.startsWith(home) ? path5.replace(home, "~") : path5;
46462
- }
46463
- function getFrontendUrl(apiUrl, projectId, todoId) {
46464
- if (apiUrl.includes("localhost:4000") || apiUrl.includes("127.0.0.1:4000")) {
46465
- return `http://localhost:3000/${projectId}/${todoId}`;
46466
- }
46467
- return `https://todofor.ai/${projectId}/${todoId}`;
46525
+ return path4.startsWith(home) ? path4.replace(home, "~") : path4;
46468
46526
  }
46469
46527
  async function interactiveLoop(ws, api, todoId, projectId, agent, json, autoApprove, cfg) {
46470
46528
  while (true) {
@@ -46541,6 +46599,8 @@ Cancelled by user (Ctrl+C)
46541
46599
  process.exit(130);
46542
46600
  });
46543
46601
  const { values: args, positionals } = parseCliArgs();
46602
+ const groupTag = args.group === undefined ? getEnv("GROUP_ID") : String(args.group);
46603
+ const groupName = args["group-name"] === undefined ? undefined : String(args["group-name"]);
46544
46604
  if (positionals[0] === "start") {
46545
46605
  if (!positionals[1] && !args.template) {
46546
46606
  process.stderr.write(`${RED}Usage: todoforai-cli start <todo-id>${RESET}
@@ -46703,7 +46763,8 @@ Cancelled by user (Ctrl+C)
46703
46763
  process.exit(2);
46704
46764
  }
46705
46765
  const todo2 = await api.getTodo(todoId);
46706
- const msg = await api.addMessage(todo2.projectId, content2, todo2.agentSettings || { id: todo2.agentSettingsId }, todoId);
46766
+ const agent2 = todo2.agentSettings || await api.getAgentSettings(todo2.agentSettingsId);
46767
+ const msg = await api.addMessage(todo2.projectId, content2, agent2, todoId);
46707
46768
  if (args.json)
46708
46769
  console.log(JSON.stringify(msg, null, 2));
46709
46770
  else
@@ -46736,7 +46797,7 @@ Cancelled by user (Ctrl+C)
46736
46797
  process.exit(1);
46737
46798
  }
46738
46799
  blob = file;
46739
- name = path4.basename(filePath);
46800
+ name = path3.basename(filePath);
46740
46801
  }
46741
46802
  const res = await api.showFile(todoId, blob, name, { title: args.title, alias: args.alias, mime: args.mime });
46742
46803
  if (args.json)
@@ -46879,7 +46940,13 @@ Cancelled by user (Ctrl+C)
46879
46940
  return;
46880
46941
  }
46881
46942
  if (args["list-models"]) {
46882
- await listModelsCommand(api, { json: !!args.json, filter: positionals[0] });
46943
+ const ids = qualifiedModelIds(await api.listModels(), positionals[0]);
46944
+ if (args.json)
46945
+ console.log(JSON.stringify(ids, null, 2));
46946
+ else
46947
+ for (const id of ids)
46948
+ process.stderr.write(`${BRAND}${id}${RESET}
46949
+ `);
46883
46950
  return;
46884
46951
  }
46885
46952
  if (positionals[0] === "agent") {
@@ -46926,14 +46993,14 @@ Cancelled by user (Ctrl+C)
46926
46993
  }
46927
46994
  const mode = args.debug ? "debug" : args.detailed ? "detailed" : "default";
46928
46995
  const format = args["format-anthropic"] ? "anthropic" : "compact";
46929
- printFullChat(todo2, getFrontendUrl(apiUrl, todo2.projectId, todoId), slice, mode, format);
46996
+ printFullChat(todo2, getFrontendUrl(apiUrl, todoId), slice, mode, format);
46930
46997
  return;
46931
46998
  }
46932
46999
  if (process.stderr.isTTY)
46933
47000
  printLogo();
46934
47001
  if (args.template) {
46935
47002
  if (!args["no-bridge"] && !args["no-watch"])
46936
- ensureBridgeRunning(apiUrl, apiKey);
47003
+ await ensureBridgeRunning(apiUrl, apiKey);
46937
47004
  const templateId = args.template;
46938
47005
  const spec = await api.getRegistrySpec(templateId);
46939
47006
  process.stderr.write(`${DIM}Template:${RESET} ${BRAND}${spec.name}${RESET}
@@ -46952,10 +47019,13 @@ Cancelled by user (Ctrl+C)
46952
47019
  `);
46953
47020
  process.exit(1);
46954
47021
  }
46955
- const todo2 = await api.startFromSpec(projectId2, templateId);
47022
+ const todo2 = await api.startFromSpec(projectId2, templateId, {
47023
+ ...groupTag ? { groupTag } : {},
47024
+ ...groupName ? { groupName } : {}
47025
+ });
46956
47026
  const todoId = todo2.id;
46957
47027
  cfgScope.setLastTodoId(todoId);
46958
- const frontendUrl2 = getFrontendUrl(apiUrl, projectId2, todoId);
47028
+ const frontendUrl2 = getFrontendUrl(apiUrl, todoId);
46959
47029
  if (args.json) {
46960
47030
  console.log(JSON.stringify({ ...todo2, frontend_url: frontendUrl2 }, null, 2));
46961
47031
  } else if (args["no-watch"]) {
@@ -47000,7 +47070,7 @@ ${"\u2500".repeat(40)}
47000
47070
  }
47001
47071
  if (args.resume || args.continue) {
47002
47072
  if (!args["no-bridge"])
47003
- ensureBridgeRunning(apiUrl, apiKey);
47073
+ await ensureBridgeRunning(apiUrl, apiKey);
47004
47074
  const todoId = args.resume || cfgScope.data.last_todo_id;
47005
47075
  if (!todoId) {
47006
47076
  process.stderr.write(`Error: No recent todo found
@@ -47017,7 +47087,7 @@ ${"\u2500".repeat(40)}
47017
47087
  }
47018
47088
  process.stderr.write(`
47019
47089
  ${"\u2500".repeat(40)}
47020
- Resumed: ${CYAN}${getFrontendUrl(apiUrl, projectId2, todoId)}${RESET}
47090
+ Resumed: ${CYAN}${getFrontendUrl(apiUrl, todoId)}${RESET}
47021
47091
  `);
47022
47092
  const ws2 = new FrontendWebSocket(apiUrl, apiKey);
47023
47093
  await ws2.connect();
@@ -47041,8 +47111,7 @@ Resumed: ${CYAN}${getFrontendUrl(apiUrl, projectId2, todoId)}${RESET}
47041
47111
  }
47042
47112
  const ws = args["no-watch"] ? null : new FrontendWebSocket(apiUrl, apiKey);
47043
47113
  const wsReady = ws ? ws.connect() : null;
47044
- if (!args["no-bridge"] && !args["no-watch"])
47045
- ensureBridgeRunning(apiUrl, apiKey);
47114
+ const bridgeReady = !args["no-bridge"] && !args["no-watch"] ? ensureBridgeRunning(apiUrl, apiKey) : null;
47046
47115
  let preMatchedAgent = null;
47047
47116
  let agents = null;
47048
47117
  if (args.agent) {
@@ -47137,6 +47206,8 @@ Resumed: ${CYAN}${getFrontendUrl(apiUrl, projectId2, todoId)}${RESET}
47137
47206
  }
47138
47207
  if (wsReady)
47139
47208
  await wsReady;
47209
+ if (bridgeReady)
47210
+ await bridgeReady;
47140
47211
  if (args.model)
47141
47212
  agent = { ...agent, model: args.model };
47142
47213
  if (args["raw-sysmsg"]) {
@@ -47150,7 +47221,7 @@ Resumed: ${CYAN}${getFrontendUrl(apiUrl, projectId2, todoId)}${RESET}
47150
47221
  cfg.addToHistory(content);
47151
47222
  let todo;
47152
47223
  try {
47153
- todo = await api.addMessage(projectId, content, agent);
47224
+ todo = await api.addMessage(projectId, content, agent, undefined, undefined, undefined, groupTag || undefined, groupName);
47154
47225
  } catch (e) {
47155
47226
  if (!args.project && cfgScope.data.default_project_id === projectId && /failed: 403/.test(e.message || "")) {
47156
47227
  cfgScope.clearDefaultProject();
@@ -47162,7 +47233,7 @@ Resumed: ${CYAN}${getFrontendUrl(apiUrl, projectId2, todoId)}${RESET}
47162
47233
  }
47163
47234
  const actualTodoId = todo.id || crypto.randomUUID();
47164
47235
  cfgScope.setLastTodoId(actualTodoId);
47165
- const frontendUrl = getFrontendUrl(apiUrl, projectId, actualTodoId);
47236
+ const frontendUrl = getFrontendUrl(apiUrl, actualTodoId);
47166
47237
  if (args.json) {
47167
47238
  console.log(JSON.stringify({ ...todo, frontend_url: frontendUrl }, null, 2));
47168
47239
  } else if (!ws) {
@@ -47187,7 +47258,7 @@ ${"\u2500".repeat(40)}
47187
47258
  await ws.close();
47188
47259
  }
47189
47260
  }
47190
- main().then(() => process.exit(0)).catch((e) => {
47261
+ main().then(() => process.exit(process.exitCode ?? 0)).catch((e) => {
47191
47262
  process.stderr.write(`Error: ${e.message}
47192
47263
  `);
47193
47264
  process.exit(1);
package/package.json CHANGED
@@ -1,36 +1,28 @@
1
1
  {
2
2
  "name": "@todoforai/cli",
3
- "version": "0.1.26",
3
+ "version": "0.1.28",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "todoforai-cli": "bin/todoforai-cli.js",
7
+ "tfa-cli": "bin/todoforai-cli.js",
7
8
  "todoai": "bin/todoforai-cli.js"
8
9
  },
9
10
  "files": [
10
11
  "bin",
11
- "dist/todoai.js"
12
+ "dist/todoforai-cli.js"
12
13
  ],
13
14
  "scripts": {
14
- "build": "bun build src/index.ts --target=bun --outfile dist/todoai.js --external ws",
15
+ "build": "bun build src/index.ts --target=bun --outfile dist/todoforai-cli.js --external ws",
15
16
  "prepublishOnly": "bun run build",
16
17
  "start": "bun run src/index.ts",
17
18
  "dev": "bun run src/index.ts"
18
19
  },
19
20
  "dependencies": {
20
- "@shared/credentials": "file:../packages/shared-credentials",
21
21
  "@todoforai/update-notifier": "^0.1.0",
22
22
  "cli-highlight": "^2.1.11",
23
23
  "diff-match-patch": "^1.0.5",
24
24
  "ws": "^8.18.0"
25
25
  },
26
- "peerDependencies": {
27
- "@todoforai/edge": ">=0.12.0"
28
- },
29
- "peerDependenciesMeta": {
30
- "@todoforai/edge": {
31
- "optional": true
32
- }
33
- },
34
26
  "devDependencies": {
35
27
  "@types/ws": "^8.5.13",
36
28
  "typescript": "^5.7.0"