@todoforai/cli 0.1.27 → 0.1.29

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 = {}) {
@@ -42889,6 +42893,65 @@ class ApiClient {
42889
42893
  throw new Error(`API POST /resources/open failed: ${res.status} ${await res.text()}`);
42890
42894
  return res.json();
42891
42895
  }
42896
+ async listShows(opts = {}) {
42897
+ const qs = new URLSearchParams;
42898
+ if (opts.todoId)
42899
+ qs.set("todoId", opts.todoId);
42900
+ if (opts.projectId)
42901
+ qs.set("projectId", opts.projectId);
42902
+ if (opts.card)
42903
+ qs.set("card", opts.card);
42904
+ const url = `${this.apiUrl}${restBasePath(this.apiKey)}/resources/show?${qs}`;
42905
+ const res = await fetch(url, { headers: { "x-api-key": this.apiKey }, signal: AbortSignal.timeout(30000) });
42906
+ if (!res.ok)
42907
+ throw new Error(`API GET /resources/show failed: ${res.status} ${await res.text()}`);
42908
+ return res.json();
42909
+ }
42910
+ async upsertCard(projectId, name, html, opts = {}) {
42911
+ const form = new FormData;
42912
+ form.append("file", html, `${name}.html`);
42913
+ for (const [k, v] of Object.entries(opts))
42914
+ if (v)
42915
+ form.append(k, v);
42916
+ const url = `${this.apiUrl}${restBasePath(this.apiKey)}/resources/cards/${projectId}/${name}`;
42917
+ const res = await fetch(url, {
42918
+ method: "PUT",
42919
+ headers: { "x-api-key": this.apiKey },
42920
+ body: form,
42921
+ signal: AbortSignal.timeout(60000)
42922
+ });
42923
+ if (!res.ok)
42924
+ throw new Error(`API PUT cards failed: ${res.status} ${await res.text()}`);
42925
+ return res.json();
42926
+ }
42927
+ async listCards(projectId) {
42928
+ const url = `${this.apiUrl}${restBasePath(this.apiKey)}/resources/cards/${projectId}`;
42929
+ const res = await fetch(url, { headers: { "x-api-key": this.apiKey }, signal: AbortSignal.timeout(30000) });
42930
+ if (!res.ok)
42931
+ throw new Error(`API GET cards failed: ${res.status} ${await res.text()}`);
42932
+ return res.json();
42933
+ }
42934
+ async getCard(projectId, name) {
42935
+ const url = `${this.apiUrl}${restBasePath(this.apiKey)}/resources/cards/${projectId}/${name}`;
42936
+ const res = await fetch(url, { headers: { "x-api-key": this.apiKey }, signal: AbortSignal.timeout(30000) });
42937
+ if (!res.ok)
42938
+ throw new Error(`API GET card failed: ${res.status} ${await res.text()}`);
42939
+ return res.json();
42940
+ }
42941
+ async getCardBody(projectId, name) {
42942
+ const url = `${this.apiUrl}${restBasePath(this.apiKey)}/resources/cards/${projectId}/${name}?body=1`;
42943
+ const res = await fetch(url, { headers: { "x-api-key": this.apiKey }, signal: AbortSignal.timeout(30000) });
42944
+ if (!res.ok)
42945
+ throw new Error(`API GET card body failed: ${res.status} ${await res.text()}`);
42946
+ return res.text();
42947
+ }
42948
+ async deleteCard(projectId, name) {
42949
+ const url = `${this.apiUrl}${restBasePath(this.apiKey)}/resources/cards/${projectId}/${name}`;
42950
+ const res = await fetch(url, { method: "DELETE", headers: { "x-api-key": this.apiKey }, signal: AbortSignal.timeout(30000) });
42951
+ if (!res.ok)
42952
+ throw new Error(`API DELETE card failed: ${res.status} ${await res.text()}`);
42953
+ return res.json();
42954
+ }
42892
42955
  patchEdgeConfig(edgeId, updates) {
42893
42956
  return this.request("PATCH", `/api/v1/edges/${edgeId}`, { updates });
42894
42957
  }
@@ -42898,6 +42961,9 @@ class ApiClient {
42898
42961
  updateAgentSettings(agentId, agentSettingsId, updates) {
42899
42962
  return this.request("PUT", `/api/v1/agents/${agentId}/settings`, { agentSettingsId, updates });
42900
42963
  }
42964
+ deleteAgentSettings(agentSettingsId) {
42965
+ return this.request("DELETE", `/api/v1/agents/${agentSettingsId}`);
42966
+ }
42901
42967
  getGlobalAgentSettings() {
42902
42968
  return this.request("GET", "/api/v1/agents/global");
42903
42969
  }
@@ -42933,11 +42999,11 @@ class ApiClient {
42933
42999
  throw new Error(`Spec '${specId}' not found (${res.status})`);
42934
43000
  return res.json();
42935
43001
  }
42936
- async initDeviceLogin(clientName = "edge") {
43002
+ async initDeviceLogin(clientName = "edge", opts = {}) {
42937
43003
  const res = await fetch(`${this.apiUrl}/api/v1/cli/login/init`, {
42938
43004
  method: "POST",
42939
43005
  headers: { "content-type": "application/json" },
42940
- body: JSON.stringify({ clientName })
43006
+ body: JSON.stringify({ clientName, ...opts })
42941
43007
  });
42942
43008
  if (!res.ok)
42943
43009
  throw new Error(`Device login init failed: ${res.status}`);
@@ -42957,35 +43023,50 @@ class ApiClient {
42957
43023
  return res.json();
42958
43024
  }
42959
43025
  }
42960
-
42961
- // ../edge/bun/src/frontend-ws.ts
43026
+ // ../packages/shared-api/src/frontend-ws.ts
42962
43027
  import WebSocket from "ws";
42963
43028
 
42964
- // ../edge/bun/src/config.ts
42965
- import path2 from "path";
42966
- import os2 from "os";
43029
+ // ../packages/shared-credentials/src/index.ts
43030
+ import { readFileSync, writeFileSync, mkdirSync, chmodSync } from "fs";
43031
+ import { join, dirname } from "path";
43032
+ import { homedir, platform } from "os";
42967
43033
  function normalizeApiUrl(url) {
42968
43034
  if (url.startsWith("localhost"))
42969
43035
  return `http://${url}`;
42970
43036
  if (!url.startsWith("http://") && !url.startsWith("https://"))
42971
43037
  return `https://${url}`;
42972
- return url;
43038
+ return url.replace(/\/+$/, "");
42973
43039
  }
42974
- var SUBCOMMANDS = new Set(["login", "logout", "update"]);
42975
43040
  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");
43041
+ 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");
43042
+ }
43043
+ function read() {
43044
+ try {
43045
+ return JSON.parse(readFileSync(credentialsPath(), "utf-8"));
43046
+ } catch {
43047
+ return {};
42982
43048
  }
42983
- const xdg = process.env.XDG_CONFIG_HOME || path2.join(os2.homedir(), ".config");
42984
- return path2.join(xdg, "todoforai", "credentials.json");
42985
43049
  }
42986
- var CREDENTIALS_PATH = credentialsPath();
43050
+ function writeSecure(data) {
43051
+ const p = credentialsPath();
43052
+ mkdirSync(dirname(p), { recursive: true });
43053
+ writeFileSync(p, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
43054
+ try {
43055
+ chmodSync(p, 384);
43056
+ } catch {}
43057
+ }
43058
+ function readCredential(apiUrl) {
43059
+ const c = read();
43060
+ const v = c[apiUrl] || c.apiToken || c.apiKey;
43061
+ return typeof v === "string" ? v : "";
43062
+ }
43063
+ function writeCredential(apiUrl, apiKey) {
43064
+ const c = read();
43065
+ c[apiUrl] = apiKey;
43066
+ writeSecure(c);
43067
+ }
42987
43068
 
42988
- // ../edge/bun/src/frontend-ws.ts
43069
+ // ../packages/shared-api/src/frontend-ws.ts
42989
43070
  var log = (level, ...args) => console.log(`[frontend-ws:${level}]`, ...args);
42990
43071
 
42991
43072
  class FrontendWebSocket {
@@ -43174,51 +43255,11 @@ class FrontendWebSocket {
43174
43255
  });
43175
43256
  }
43176
43257
  }
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.27",
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
- },
43199
- dependencies: {
43200
- "@shared/credentials": "file:../packages/shared-credentials",
43201
- "@todoforai/update-notifier": "^0.1.0",
43202
- "cli-highlight": "^2.1.11",
43203
- "diff-match-patch": "^1.0.5",
43204
- ws: "^8.18.0"
43205
- },
43206
- peerDependencies: {
43207
- "@todoforai/edge": ">=0.12.0"
43208
- },
43209
- peerDependenciesMeta: {
43210
- "@todoforai/edge": {
43211
- optional: true
43212
- }
43213
- },
43214
- devDependencies: {
43215
- "@types/ws": "^8.5.13",
43216
- typescript: "^5.7.0"
43217
- }
43218
- };
43219
43258
  // ../packages/shared-fbe/src/enums.ts
43220
43259
  var TodoStatus;
43221
43260
  ((TodoStatus2) => {
43261
+ TodoStatus2["PROPOSED"] = "PROPOSED";
43262
+ TodoStatus2["DISMISSED"] = "DISMISSED";
43222
43263
  TodoStatus2["TODO"] = "TODO";
43223
43264
  TodoStatus2["SCHEDULED"] = "SCHEDULED";
43224
43265
  TodoStatus2["PAUSED"] = "PAUSED";
@@ -43238,6 +43279,59 @@ var TodoStatus;
43238
43279
  TodoStatus2["ARCHIVED"] = "ARCHIVED";
43239
43280
  TodoStatus2["DELETED"] = "DELETED";
43240
43281
  })(TodoStatus ||= {});
43282
+ // ../packages/shared-fbe/src/models.ts
43283
+ var DEFAULT_MODEL = "anthropic:anthropic/claude-sonnet-5";
43284
+ var MODEL_ALIASES = {
43285
+ claude: DEFAULT_MODEL
43286
+ };
43287
+ var PROVIDER_BY_AUTHOR = {
43288
+ anthropic: "anthropic",
43289
+ openai: "openai",
43290
+ deepseek: "deepseek",
43291
+ poolside: "poolside",
43292
+ "x-ai": "xai",
43293
+ "z-ai": "z.ai",
43294
+ google: "google-ai-studio",
43295
+ moonshotai: "opencode_go"
43296
+ };
43297
+ function isQualifiedModel(model) {
43298
+ const id = model.trim();
43299
+ const colon = id.indexOf(":");
43300
+ const slash = id.indexOf("/");
43301
+ return colon > 0 && colon < id.length - 1 && (slash === -1 || colon < slash);
43302
+ }
43303
+ function qualifyModel(model) {
43304
+ const id = model?.trim();
43305
+ if (!id)
43306
+ return { model: DEFAULT_MODEL };
43307
+ if (isQualifiedModel(id))
43308
+ return { model: id };
43309
+ const suffix = id.match(/\([^)]+\)$/)?.[0] ?? "";
43310
+ const bare = suffix ? id.slice(0, -suffix.length) : id;
43311
+ const aliased = MODEL_ALIASES[bare.toLowerCase()];
43312
+ if (aliased)
43313
+ return { model: aliased + suffix };
43314
+ const lower = bare.toLowerCase();
43315
+ const provider = PROVIDER_BY_AUTHOR[lower.split("/")[0]];
43316
+ if (provider && lower.includes("/"))
43317
+ return { model: `${provider}:${lower}${suffix}` };
43318
+ return { model: DEFAULT_MODEL, fallback: id };
43319
+ }
43320
+ function qualifiedModelIds(listing, filter) {
43321
+ const q = filter?.toLowerCase();
43322
+ const ids = [];
43323
+ for (const m of listing?.data ?? []) {
43324
+ const id = String(m.id);
43325
+ if (m.provider) {
43326
+ ids.push(`${m.provider.toLowerCase()}:${id}`);
43327
+ continue;
43328
+ }
43329
+ const r = qualifyModel(id);
43330
+ if (!r.fallback)
43331
+ ids.push(r.model);
43332
+ }
43333
+ return q ? ids.filter((id) => id.toLowerCase().includes(q)) : ids;
43334
+ }
43241
43335
  // ../packages/shared-fbe/src/billing.ts
43242
43336
  var USAGE_WINDOWS = {
43243
43337
  SESSION_MS: 5 * 60 * 60 * 1000,
@@ -43288,7 +43382,7 @@ var mimetypes_default = {
43288
43382
  gif: { mime: "image/gif", type: "image" },
43289
43383
  webp: { mime: "image/webp", type: "image" },
43290
43384
  bmp: { mime: "image/bmp", type: "image" },
43291
- svg: { mime: "image/svg+xml", type: "text" },
43385
+ svg: { mime: "image/svg+xml", type: "image" },
43292
43386
  heic: { mime: "image/heic", type: "image", conversionProfile: "to_jpeg_90" },
43293
43387
  heif: { mime: "image/heif", type: "image", conversionProfile: "to_jpeg_90" },
43294
43388
  mp3: { mime: "audio/mpeg", type: "audio" },
@@ -43446,9 +43540,6 @@ var SERVER_TO_FRONTENDS = {
43446
43540
  hexGrid: {
43447
43541
  updated: (projectId) => `${"hexgrid:updated" /* HEXGRID_UPDATED */}:${projectId}`
43448
43542
  },
43449
- placement: {
43450
- updated: (projectId) => `${"placement:updated" /* PLACEMENT_UPDATED */}:${projectId}`
43451
- },
43452
43543
  sandbox: {
43453
43544
  state: (userId) => `${"sandbox:state" /* SANDBOX_STATE */}:${userId}`
43454
43545
  },
@@ -43496,7 +43587,7 @@ var SERVER_TO_FRONTENDS = {
43496
43587
  predicted_reply_updated: (todoId) => `${"predict:reply_updated" /* PREDICTED_REPLY_UPDATED */}:${todoId}`,
43497
43588
  error_result: (todoId) => `${"block:error_result" /* BLOCK_ERROR_RESULT */}:${todoId}`,
43498
43589
  meta_result: (todoId) => `${"block:meta_result" /* BLOCK_META_RESULT */}:${todoId}`,
43499
- file_changed: (edgeId, path3) => `${"block:file_changed" /* BLOCK_FILE_CHANGED */}:${edgeId}:${path3}`,
43590
+ file_changed: (edgeId, path2) => `${"block:file_changed" /* BLOCK_FILE_CHANGED */}:${edgeId}:${path2}`,
43500
43591
  notify: (todoId) => `${"NOTIFY" /* NOTIFY */}:${todoId}`
43501
43592
  },
43502
43593
  notify: {
@@ -43744,13 +43835,10 @@ function getBlockServerPrefix(block) {
43744
43835
  const prefixMatch = agentPattern.match(/^(.*?:)BASH(?:\(|$)/);
43745
43836
  return prefixMatch ? prefixMatch[1] : "";
43746
43837
  }
43747
- var BASH_LIKE_BLOCK_TYPES = new Set(["bash", "machine_exec"]);
43748
43838
  function getBlockPatterns(block) {
43749
43839
  if (Array.isArray(block.generalized_pattern))
43750
43840
  return block.generalized_pattern;
43751
- const agentPattern = typeof block.generalized_pattern === "string" ? block.generalized_pattern : "";
43752
- const isBashLike = BASH_LIKE_BLOCK_TYPES.has(block.type.toLowerCase()) || /:BASH(\(|$)/.test(agentPattern);
43753
- if (isBashLike && block.cmd) {
43841
+ if (block.type.toLowerCase() === "bash" && block.cmd) {
43754
43842
  const prefix = getBlockServerPrefix(block);
43755
43843
  return generalizeBashCmd(block.cmd).map((p) => prefix + p);
43756
43844
  }
@@ -43848,8 +43936,7 @@ function getBlockNewPatterns(block, permissions) {
43848
43936
  if (Array.isArray(block.generalized_pattern)) {
43849
43937
  return getNewPatterns(block.generalized_pattern, permissions);
43850
43938
  }
43851
- const agentPattern = typeof block.generalized_pattern === "string" ? block.generalized_pattern : "";
43852
- const isBash = (block.type.toLowerCase() === "bash" || agentPattern.includes(":BASH(")) && !!block.cmd;
43939
+ const isBash = block.type.toLowerCase() === "bash" && !!block.cmd;
43853
43940
  if (!isBash) {
43854
43941
  return getBlockPatterns(block).filter((p) => !isPatternAllowed(permissions, p));
43855
43942
  }
@@ -43910,7 +43997,7 @@ Rules for the Presence block:
43910
43997
  - \`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.
43911
43998
  - Keep it consistent with the prose Online Presence section \u2014 the JSON is the structured mirror of those findings.
43912
43999
 
43913
- 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.
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.
43914
44001
 
43915
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.)
43916
44003
 
@@ -43922,8 +44009,14 @@ In follow-up conversations, act as an ongoing advisor: answer growth and digital
43922
44009
 
43923
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.
43924
44011
 
44012
+ 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.
44017
+
43925
44018
  Recommending actionable TODOs from your recommendations:
43926
- 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:
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:
43927
44020
 
43928
44021
  1. GET A TEMPLATE (api_create_template) \u2014 generate-first, reuse only on a close fit:
43929
44022
  - Default: CREATE a template tailored to THIS business with the \`api_create_template\` tool. It returns the new template id.
@@ -43936,12 +44029,12 @@ To materialize growth recommendations, create recommendation cards in the projec
43936
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.
43937
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.
43938
44031
 
43939
- 2. RECOMMEND (api_recommend_template \u2014 references a template id onto the project; never creates a running todo):
43940
- 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.
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.
43941
44034
 
43942
44035
  Both tools run in-process and need no machine or shell \u2014 never shell out to \`todoregistry-cli\` / \`todoforai-cli\` for this.
43943
44036
 
43944
- 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.`,
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.`,
43945
44038
  mcpConfigs: {},
43946
44039
  edgesMcpConfigs: {},
43947
44040
  permissions: {
@@ -43985,6 +44078,9 @@ var OUTPUT_POLICIES = {
43985
44078
  full: { firstLimit: Infinity, lastLimit: 0, hardCap: RUN_OUTPUT_CAP, lineLimit: MAX_LINE_LEN },
43986
44079
  raw: { firstLimit: Infinity, lastLimit: 0, hardCap: Infinity, lineLimit: Infinity }
43987
44080
  };
44081
+ // ../packages/shared-fbe/src/toolInstallCommand.ts
44082
+ var PYTHON_MIN_MINOR = 10;
44083
+ var PYTHON_VERSION_CHECK = `-c 'import sys; sys.exit(0 if sys.version_info[:2] >= (3, ${PYTHON_MIN_MINOR}) else 1)'`;
43988
44084
  // ../packages/shared-fbe/src/realtime/topics.ts
43989
44085
  var TOPICS = {
43990
44086
  ["todo:new" /* NEW_TODO */]: {
@@ -44023,10 +44119,6 @@ var TOPICS = {
44023
44119
  channel: (p) => SERVER_TO_FRONTENDS.hexGrid.updated(p.projectId),
44024
44120
  audience: "frontend"
44025
44121
  },
44026
- ["placement:updated" /* PLACEMENT_UPDATED */]: {
44027
- channel: (p) => SERVER_TO_FRONTENDS.placement.updated(p.projectId),
44028
- audience: "frontend"
44029
- },
44030
44122
  ["sandbox:state" /* SANDBOX_STATE */]: {
44031
44123
  channel: (p) => SERVER_TO_FRONTENDS.sandbox.state(p.userId),
44032
44124
  audience: "frontend"
@@ -44124,15 +44216,51 @@ var TOPICS = {
44124
44216
  audience: "frontend"
44125
44217
  }
44126
44218
  };
44219
+ // src/args.ts
44220
+ import { parseArgs } from "util";
44221
+ // package.json
44222
+ var package_default = {
44223
+ name: "@todoforai/cli",
44224
+ version: "0.1.29",
44225
+ type: "module",
44226
+ bin: {
44227
+ "todoforai-cli": "bin/todoforai-cli.js",
44228
+ "tfa-cli": "bin/todoforai-cli.js",
44229
+ todoai: "bin/todoforai-cli.js"
44230
+ },
44231
+ files: [
44232
+ "bin",
44233
+ "dist/todoforai-cli.js"
44234
+ ],
44235
+ scripts: {
44236
+ build: "bun build src/index.ts --target=bun --outfile dist/todoforai-cli.js --external ws",
44237
+ prepublishOnly: "bun run build",
44238
+ start: "bun run src/index.ts",
44239
+ dev: "bun run src/index.ts"
44240
+ },
44241
+ dependencies: {
44242
+ "@shared/api": "file:../packages/shared-api",
44243
+ "@shared/credentials": "file:../packages/shared-credentials",
44244
+ "@todoforai/update-notifier": "^0.1.0",
44245
+ "cli-highlight": "^2.1.11",
44246
+ "diff-match-patch": "^1.0.5",
44247
+ ws: "^8.18.0"
44248
+ },
44249
+ devDependencies: {
44250
+ "@types/ws": "^8.5.13",
44251
+ typescript: "^5.7.0"
44252
+ }
44253
+ };
44254
+
44127
44255
  // src/args.ts
44128
44256
  var DEFAULT_API_URL = "https://api.todofor.ai";
44129
44257
  var VERSION = package_default.version;
44130
44258
  function getEnv(name) {
44131
- return process.env[`TODOFORAI_${name}`] || process.env[`TODO4AI_${name}`] || "";
44259
+ return (process.env[`TODOFORAI_${name}`] || process.env[`TODO4AI_${name}`] || "").trim();
44132
44260
  }
44133
44261
  function printUsage() {
44134
44262
  process.stderr.write(`
44135
- todoforai-cli \u2014 TODOforAI CLI (Bun)
44263
+ todoforai-cli \u2014 TODOforAI CLI (Bun). Aliases: tfa-cli, todoai.
44136
44264
 
44137
44265
  Usage:
44138
44266
  todoforai-cli login # Browser-based device auth
@@ -44152,7 +44280,10 @@ Usage:
44152
44280
  todoforai-cli delete <todo-id> # Permanently delete a todo
44153
44281
  todoforai-cli addmessage <todo-id> "text" # Add a message to an existing todo
44154
44282
  todoforai-cli show <file|-> [todo-id] # Show a file in the chat (rendered by mimetype; - reads stdin)
44155
- # [--title T] [--alias A] [--mime M] [--json]
44283
+ # [--title T] [--alias A] [--mime M] [--card <name>] [--json]
44284
+ todoforai-cli show list [todo-id] # List show blocks (ref, title, mime/url, card)
44285
+ # [--project <id>] [--card <name>] [--json]
44286
+ # no todo-id + --project (or $TODOFORAI_PROJECT_ID) = every todo
44156
44287
  todoforai-cli open <url> [todo-id] # Show a live http(s) url in the chat as a preview
44157
44288
  # [--title T] [--alias A] [--json]
44158
44289
  todoforai-cli recommend --template <id> # Add a template as a recommendation card (see 'todoregistry-cli create')
@@ -44163,6 +44294,8 @@ Options:
44163
44294
  --path <dir> Workspace path (default: cwd)
44164
44295
  --project <id> Project ID
44165
44296
  --agent, -a <name> Agent name (partial match)
44297
+ --group <slug> Group new todo; omitted inherits TODOFORAI_GROUP_ID
44298
+ --group-name <name> Display name for --group (last write wins)
44166
44299
  --model <model> Override the agent's model for this todo
44167
44300
  (e.g. anthropic:anthropic/claude-opus-5, openai:openai/gpt-5.6-sol)
44168
44301
  --list-agents List available agents (name, id, workspace paths) and exit
@@ -44221,6 +44354,9 @@ function parseCliArgs() {
44221
44354
  project: { type: "string" },
44222
44355
  agent: { type: "string", short: "a" },
44223
44356
  model: { type: "string" },
44357
+ group: { type: "string" },
44358
+ "group-name": { type: "string" },
44359
+ "group-description": { type: "string" },
44224
44360
  "list-agents": { type: "boolean", default: false },
44225
44361
  "list-models": { type: "boolean", default: false },
44226
44362
  "api-url": { type: "string" },
@@ -44233,6 +44369,7 @@ function parseCliArgs() {
44233
44369
  title: { type: "string" },
44234
44370
  alias: { type: "string" },
44235
44371
  mime: { type: "string" },
44372
+ card: { type: "string" },
44236
44373
  direction: { type: "string" },
44237
44374
  "business-context": { type: "string" },
44238
44375
  seed: { type: "string" },
@@ -44601,8 +44738,8 @@ import { resolve, basename } from "path";
44601
44738
  // src/ensure-bridge.ts
44602
44739
  import { spawn, spawnSync } from "child_process";
44603
44740
  import fs2 from "fs";
44604
- import path3 from "path";
44605
- import os3 from "os";
44741
+ import path2 from "path";
44742
+ import os2 from "os";
44606
44743
  function hasBridge() {
44607
44744
  const probe = spawnSync("todoforai-bridge", ["--version"], { stdio: "ignore" });
44608
44745
  return probe.status === 0;
@@ -44671,31 +44808,44 @@ function ensureBridgeCredentials(apiUrl) {
44671
44808
  const login = spawnSync("todoforai-bridge", bridgeLoginArgs(apiUrl), { stdio: "inherit" });
44672
44809
  return login.status === 0;
44673
44810
  }
44674
- function bridgeAlreadyRunning() {
44675
- if (process.platform === "win32")
44676
- return Promise.resolve(false);
44677
- return new Promise((resolve) => {
44678
- const r = spawn("pgrep", ["-f", "todoforai-bridge"]);
44679
- let out = "";
44680
- r.stdout.on("data", (d) => out += d);
44681
- r.on("error", () => resolve(false));
44682
- r.on("close", (code) => resolve(code === 0 && out.trim().length > 0));
44683
- });
44811
+ async function bridgeState(apiUrl, apiKey, deviceId) {
44812
+ try {
44813
+ const devices = await new ApiClient(apiUrl, apiKey).listDevices();
44814
+ return Array.isArray(devices) && devices.some((device) => device.id === deviceId && device.status === "ONLINE") ? "online" : "offline";
44815
+ } catch {
44816
+ return "unknown";
44817
+ }
44684
44818
  }
44685
- async function ensureBridgeRunning(apiUrl, _apiKey) {
44686
- if (await bridgeAlreadyRunning())
44687
- return;
44819
+ async function waitForBridgeOnline(apiUrl, apiKey, deviceId, timeoutMs = 5000) {
44820
+ const deadline = Date.now() + timeoutMs;
44821
+ do {
44822
+ const state = await bridgeState(apiUrl, apiKey, deviceId);
44823
+ if (state !== "offline")
44824
+ return state === "online";
44825
+ await new Promise((resolve) => setTimeout(resolve, 150));
44826
+ } while (Date.now() < deadline);
44827
+ return false;
44828
+ }
44829
+ async function ensureBridgeRunning(apiUrl, apiKey) {
44688
44830
  if (!hasBridge()) {
44689
44831
  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");
44690
- return;
44832
+ return false;
44691
44833
  }
44692
44834
  if (!ensureBridgeCredentials(apiUrl)) {
44693
44835
  console.error("\x1B[33mBridge not started: `todoforai-bridge login` did not complete successfully.\x1B[0m");
44694
- return;
44836
+ return false;
44695
44837
  }
44696
- const logDir = path3.join(os3.homedir(), ".todoforai");
44838
+ const deviceId = bridgeDeviceId(apiUrl);
44839
+ if (!deviceId) {
44840
+ console.error("\x1B[33mBridge not started: could not determine this device's id.\x1B[0m");
44841
+ return false;
44842
+ }
44843
+ const initialState = await bridgeState(apiUrl, apiKey, deviceId);
44844
+ if (initialState === "online" || initialState === "unknown")
44845
+ return initialState === "online";
44846
+ const logDir = path2.join(os2.homedir(), ".todoforai");
44697
44847
  fs2.mkdirSync(logDir, { recursive: true });
44698
- const logFile = path3.join(logDir, "bridge.log");
44848
+ const logFile = path2.join(logDir, "bridge.log");
44699
44849
  const out = fs2.openSync(logFile, "a");
44700
44850
  const child = spawn("todoforai-bridge", bridgeRunArgs(apiUrl), {
44701
44851
  detached: true,
@@ -44712,13 +44862,13 @@ async function ensureBridgeRunning(apiUrl, _apiKey) {
44712
44862
  });
44713
44863
  child.unref();
44714
44864
  if (!child.pid)
44715
- return;
44716
- const shortLog = logFile.replace(os3.homedir(), "~");
44717
- setTimeout(() => {
44718
- if (exited && exitCode !== 0) {
44719
- console.error(`\x1B[2mBridge not running (exit ${exitCode}), see ${shortLog}\x1B[0m`);
44720
- }
44721
- }, 500);
44865
+ return false;
44866
+ if (await waitForBridgeOnline(apiUrl, apiKey, deviceId))
44867
+ return true;
44868
+ const shortLog = logFile.replace(os2.homedir(), "~");
44869
+ const reason = exited ? `exit ${exitCode}` : "startup timed out";
44870
+ console.error(`\x1B[33mBridge not ready (${reason}), see ${shortLog}\x1B[0m`);
44871
+ return false;
44722
44872
  }
44723
44873
 
44724
44874
  // src/agent.ts
@@ -44752,9 +44902,13 @@ async function autoCreateAgent(api, resolvedPath) {
44752
44902
  return resp;
44753
44903
  }
44754
44904
  const edges = await api.listEdges();
44755
- const edgeId = Array.isArray(edges) && edges.length ? edges[0].id : null;
44756
- if (!edgeId)
44905
+ if (!Array.isArray(edges) || edges.length === 0) {
44757
44906
  throw new Error("No bridge device or edge available to configure workspace path");
44907
+ }
44908
+ if (edges.length > 1) {
44909
+ throw new Error("No local bridge device found and multiple legacy edges are available; choose an existing agent explicitly");
44910
+ }
44911
+ const edgeId = edges[0].id;
44758
44912
  await api.setAgentEdgeMcpConfig(agentId, agentSettingsId, edgeId, "todoai_edge", { workspacePaths: [resolvedPath] });
44759
44913
  const matches = await api.listAgentSettings({ workspacePath: resolvedPath });
44760
44914
  if (matches.length)
@@ -44765,20 +44919,29 @@ async function autoCreateAgent(api, resolvedPath) {
44765
44919
  }
44766
44920
 
44767
44921
  // src/config.ts
44768
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
44769
- import { dirname, join, resolve as resolve2 } from "path";
44770
- import { homedir, platform } from "os";
44922
+ import { existsSync, mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync, writeFileSync as writeFileSync2 } from "fs";
44923
+ import { dirname as dirname2, join as join2, resolve as resolve2 } from "path";
44924
+ import { homedir as homedir2, platform as platform2 } from "os";
44925
+ function getConfigBase() {
44926
+ const sys = platform2();
44927
+ if (sys === "win32")
44928
+ return process.env.APPDATA || join2(homedir2(), "AppData", "Roaming");
44929
+ if (sys === "darwin")
44930
+ return join2(homedir2(), "Library", "Application Support");
44931
+ return process.env.XDG_CONFIG_HOME || join2(homedir2(), ".config");
44932
+ }
44771
44933
  function getConfigDir() {
44772
- const sys = platform();
44773
- if (sys === "win32") {
44774
- const base = process.env.APPDATA || join(homedir(), "AppData", "Roaming");
44775
- return join(base, "todoai-cli");
44776
- }
44777
- if (sys === "darwin") {
44778
- return join(homedir(), "Library", "Application Support", "todoai-cli");
44934
+ const base = getConfigBase();
44935
+ const dir = join2(base, "todoforai-cli");
44936
+ const legacy = join2(base, "todoai-cli");
44937
+ if (!existsSync(dir) && existsSync(legacy)) {
44938
+ try {
44939
+ renameSync(legacy, dir);
44940
+ } catch {
44941
+ return legacy;
44942
+ }
44779
44943
  }
44780
- const xdg = process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
44781
- return join(xdg, "todoai-cli");
44944
+ return dir;
44782
44945
  }
44783
44946
  function defaultScope() {
44784
44947
  return {
@@ -44830,10 +44993,10 @@ class ConfigStore {
44830
44993
  data;
44831
44994
  constructor(pathArg) {
44832
44995
  if (pathArg) {
44833
- const p = resolve2(pathArg.replace(/^~/, homedir()));
44834
- this.path = p.endsWith(".json") ? p : join(p, "config.json");
44996
+ const p = resolve2(pathArg.replace(/^~/, homedir2()));
44997
+ this.path = p.endsWith(".json") ? p : join2(p, "config.json");
44835
44998
  } else {
44836
- this.path = join(getConfigDir(), "config.json");
44999
+ this.path = join2(getConfigDir(), "config.json");
44837
45000
  }
44838
45001
  this.data = this.load();
44839
45002
  }
@@ -44841,7 +45004,7 @@ class ConfigStore {
44841
45004
  if (!existsSync(this.path))
44842
45005
  return defaultConfig();
44843
45006
  try {
44844
- const raw = JSON.parse(readFileSync(this.path, "utf-8"));
45007
+ const raw = JSON.parse(readFileSync2(this.path, "utf-8"));
44845
45008
  delete raw.default_api_key;
44846
45009
  const cfg = migrate(raw);
44847
45010
  const needsMigration = raw.per_api_url == null;
@@ -44856,8 +45019,8 @@ class ConfigStore {
44856
45019
  }
44857
45020
  save() {
44858
45021
  try {
44859
- mkdirSync(dirname(this.path), { recursive: true });
44860
- writeFileSync(this.path, JSON.stringify(this.data, null, 2), "utf-8");
45022
+ mkdirSync2(dirname2(this.path), { recursive: true });
45023
+ writeFileSync2(this.path, JSON.stringify(this.data, null, 2), "utf-8");
44861
45024
  } catch {}
44862
45025
  }
44863
45026
  scope(apiUrl) {
@@ -44917,39 +45080,6 @@ class ScopedConfig {
44917
45080
  this.store.save();
44918
45081
  }
44919
45082
  }
44920
-
44921
- // ../packages/shared-credentials/src/index.ts
44922
- import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, chmodSync } from "fs";
44923
- import { join as join2, dirname as dirname2 } from "path";
44924
- import { homedir as homedir2, platform as platform2 } from "os";
44925
- function credentialsPath2() {
44926
- 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");
44927
- }
44928
- function read() {
44929
- try {
44930
- return JSON.parse(readFileSync2(credentialsPath2(), "utf-8"));
44931
- } catch {
44932
- return {};
44933
- }
44934
- }
44935
- function writeSecure(data) {
44936
- const p = credentialsPath2();
44937
- mkdirSync2(dirname2(p), { recursive: true });
44938
- writeFileSync2(p, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
44939
- try {
44940
- chmodSync(p, 384);
44941
- } catch {}
44942
- }
44943
- function readCredential(apiUrl) {
44944
- const c2 = read();
44945
- const v = c2[apiUrl] || c2.apiToken || c2.apiKey;
44946
- return typeof v === "string" ? v : "";
44947
- }
44948
- function writeCredential(apiUrl, apiKey) {
44949
- const c2 = read();
44950
- c2[apiUrl] = apiKey;
44951
- writeSecure(c2);
44952
- }
44953
45083
  // src/logo.ts
44954
45084
  var LETTERS = {
44955
45085
  t: [" x ", "xxxx", " x ", " xll", " xll", " xxx"],
@@ -45023,6 +45153,12 @@ function printLogo() {
45023
45153
  `);
45024
45154
  }
45025
45155
 
45156
+ // src/urls.ts
45157
+ function getFrontendUrl(apiUrl, todoId) {
45158
+ const local = apiUrl.includes("localhost:4000") || apiUrl.includes("127.0.0.1:4000");
45159
+ return `${local ? "http://localhost:3000" : "https://todofor.ai"}/t/${todoId}`;
45160
+ }
45161
+
45026
45162
  // src/inspect.ts
45027
45163
  var xmlAttr = (v) => JSON.stringify(String(v));
45028
45164
  var DROP_IF_EMPTY = new Set(["runMeta", "meta", "attachments", "blocks"]);
@@ -45448,7 +45584,16 @@ function selectProject(projects, defaultId, setDefault) {
45448
45584
  return Promise.resolve({ id: defaultId, name });
45449
45585
  }
45450
45586
  }
45587
+ const accountDefault = projects.find((p) => p?.project?.isDefault) || projects[0];
45588
+ if (!process.stdin.isTTY) {
45589
+ const id = getItemId(accountDefault);
45590
+ const name = getDisplayName(accountDefault);
45591
+ process.stderr.write(`Using account default project: ${name}
45592
+ `);
45593
+ return Promise.resolve({ id, name });
45594
+ }
45451
45595
  return (async () => {
45596
+ const defIdx = projects.indexOf(accountDefault);
45452
45597
  process.stderr.write(`
45453
45598
  Please choose a project:
45454
45599
 
@@ -45456,7 +45601,7 @@ Please choose a project:
45456
45601
  for (let i = 0;i < projects.length; i++) {
45457
45602
  const name = getDisplayName(projects[i]);
45458
45603
  const id = getItemId(projects[i]);
45459
- process.stderr.write(` [${i + 1}] ${name}
45604
+ process.stderr.write(` [${i + 1}] ${name}${i === defIdx ? " (default)" : ""}
45460
45605
  `);
45461
45606
  if (id && id !== name)
45462
45607
  process.stderr.write(` ${id}
@@ -45465,8 +45610,8 @@ Please choose a project:
45465
45610
  process.stderr.write(`
45466
45611
  `);
45467
45612
  while (true) {
45468
- const choice = await terminalLine("Please enter your numeric choice: ");
45469
- const idx = parseInt(choice, 10) - 1;
45613
+ const choice = await terminalLine(`Please enter your numeric choice [${defIdx + 1}]: `);
45614
+ const idx = choice === "" ? defIdx : parseInt(choice, 10) - 1;
45470
45615
  if (idx >= 0 && idx < projects.length) {
45471
45616
  const id = getItemId(projects[idx]);
45472
45617
  const name = getDisplayName(projects[idx]);
@@ -45854,17 +45999,6 @@ async function watchTodo(ws, todoId, projectId, opts = {}) {
45854
45999
  "block:start_catfile"
45855
46000
  ]);
45856
46001
  const signalActivity = () => opts.activityEvent?.set();
45857
- let edgeId;
45858
- let rootPath = "";
45859
- if (opts.agentSettings) {
45860
- const emc = opts.agentSettings.edgesMcpConfigs || {};
45861
- edgeId = Object.keys(emc)[0];
45862
- if (edgeId) {
45863
- const ec = emc[edgeId];
45864
- const tc = ec?.todoai_edge || ec?.todoai || {};
45865
- rootPath = (tc.workspacePaths || [])[0] || "";
45866
- }
45867
- }
45868
46002
  const blocksStore = new Map;
45869
46003
  const diffStore = diffStoreByWs.get(ws) ?? new Map;
45870
46004
  diffStoreByWs.set(ws, diffStore);
@@ -46051,6 +46185,12 @@ ${YELLOW}*${RESET} ${YELLOW}${blockType}${RESET}${extra}
46051
46185
  `);
46052
46186
  signalActivity();
46053
46187
  }
46188
+ } else if (msgType === "NOTIFY") {
46189
+ const sevColor = payload.severity === "error" ? RED : YELLOW;
46190
+ process.stderr.write(`
46191
+ ${sevColor}\u26A0 ${payload.title || "Notice"}${RESET}${payload.message ? ` ${DIM}${payload.message}${RESET}` : ""}
46192
+ `);
46193
+ signalActivity();
46054
46194
  } else if (msgType === "todo:status") {
46055
46195
  const status = payload.status || "";
46056
46196
  process.stderr.write(`
@@ -46081,6 +46221,9 @@ ${DIM}[todo:status] ${status}${RESET}
46081
46221
  const status = result?.payload?.status || result?.type || "unknown";
46082
46222
  process.stderr.write(`Warning: Stopped: ${status}
46083
46223
  `);
46224
+ process.exitCode = 1;
46225
+ } else {
46226
+ process.exitCode = 0;
46084
46227
  }
46085
46228
  return true;
46086
46229
  } catch (e) {
@@ -46124,20 +46267,6 @@ async function listAgentsCommand(api, opts) {
46124
46267
  }
46125
46268
  }
46126
46269
 
46127
- // src/list-models.ts
46128
- async function listModelsCommand(api, opts) {
46129
- const res = await api.listModels();
46130
- const q = opts.filter?.toLowerCase();
46131
- 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));
46132
- if (opts.json) {
46133
- console.log(JSON.stringify(ids, null, 2));
46134
- return;
46135
- }
46136
- for (const id of ids)
46137
- process.stderr.write(`${BRAND}${id}${RESET}
46138
- `);
46139
- }
46140
-
46141
46270
  // src/agent-command.ts
46142
46271
  function printAgentHelp() {
46143
46272
  process.stderr.write(`
@@ -46147,6 +46276,7 @@ Usage:
46147
46276
  todoforai-cli agent list List agents (name, model, id, paths)
46148
46277
  todoforai-cli agent get <agent> Show a single agent's settings
46149
46278
  todoforai-cli agent update <agent> <field=value>\u2026 Update one or more settings
46279
+ todoforai-cli agent delete <agent> Delete an agent configuration (asks to confirm)
46150
46280
 
46151
46281
  <agent> is a name or id (unique partial name also works).
46152
46282
  Fields map directly to agent settings; values are parsed as JSON when possible
@@ -46244,6 +46374,32 @@ async function agentCommand(api, positionals, args, formatPath) {
46244
46374
  }
46245
46375
  const summary = Object.keys(updates).map((k) => `${k}=${JSON.stringify(updated[k])}`).join(" ");
46246
46376
  process.stderr.write(`${GREEN}\u2705 ${getDisplayName(agent)} updated: ${summary}${RESET}
46377
+ `);
46378
+ return;
46379
+ }
46380
+ if (sub === "delete" || sub === "rm") {
46381
+ const query = positionals[2];
46382
+ if (!query) {
46383
+ process.stderr.write(`${RED}Usage: todoforai-cli agent delete <name|id>${RESET}
46384
+ `);
46385
+ process.exit(2);
46386
+ }
46387
+ const agents = await api.listAgentSettings();
46388
+ const agent = resolveAgent(agents, query);
46389
+ const name = getDisplayName(agent);
46390
+ if (!process.stdin.isTTY) {
46391
+ process.stderr.write(`${RED}Refusing to delete '${name}' non-interactively (delete needs an interactive confirm).${RESET}
46392
+ `);
46393
+ process.exit(2);
46394
+ }
46395
+ const ok = await singleChar(`${RED}Delete agent '${name}'? [y/N] ${RESET}`);
46396
+ if (ok !== "y") {
46397
+ process.stderr.write(`Aborted
46398
+ `);
46399
+ return;
46400
+ }
46401
+ await api.deleteAgentSettings(getItemId(agent));
46402
+ process.stderr.write(`${GREEN}\u2705 Deleted ${name}${RESET}
46247
46403
  `);
46248
46404
  return;
46249
46405
  }
@@ -46449,18 +46605,12 @@ async function listTodosCommand(api, defaultProjectId, argv) {
46449
46605
 
46450
46606
  // src/index.ts
46451
46607
  try {
46452
- const pkgPath = path4.resolve(fileURLToPath(import.meta.url), "../../package.json");
46608
+ const pkgPath = path3.resolve(fileURLToPath(import.meta.url), "../../package.json");
46453
46609
  checkForUpdates(JSON.parse(readFileSync3(pkgPath, "utf-8")));
46454
46610
  } catch {}
46455
- function formatPathWithTilde(path5) {
46611
+ function formatPathWithTilde(path4) {
46456
46612
  const home = homedir3();
46457
- return path5.startsWith(home) ? path5.replace(home, "~") : path5;
46458
- }
46459
- function getFrontendUrl(apiUrl, projectId, todoId) {
46460
- if (apiUrl.includes("localhost:4000") || apiUrl.includes("127.0.0.1:4000")) {
46461
- return `http://localhost:3000/${projectId}/${todoId}`;
46462
- }
46463
- return `https://todofor.ai/${projectId}/${todoId}`;
46613
+ return path4.startsWith(home) ? path4.replace(home, "~") : path4;
46464
46614
  }
46465
46615
  async function interactiveLoop(ws, api, todoId, projectId, agent, json, autoApprove, cfg) {
46466
46616
  while (true) {
@@ -46537,6 +46687,8 @@ Cancelled by user (Ctrl+C)
46537
46687
  process.exit(130);
46538
46688
  });
46539
46689
  const { values: args, positionals } = parseCliArgs();
46690
+ const groupTag = args.group === undefined ? getEnv("GROUP_ID") : String(args.group);
46691
+ const groupName = args["group-name"] === undefined ? undefined : String(args["group-name"]);
46540
46692
  if (positionals[0] === "start") {
46541
46693
  if (!positionals[1] && !args.template) {
46542
46694
  process.stderr.write(`${RED}Usage: todoforai-cli start <todo-id>${RESET}
@@ -46699,7 +46851,8 @@ Cancelled by user (Ctrl+C)
46699
46851
  process.exit(2);
46700
46852
  }
46701
46853
  const todo2 = await api.getTodo(todoId);
46702
- const msg = await api.addMessage(todo2.projectId, content2, todo2.agentSettings || { id: todo2.agentSettingsId }, todoId);
46854
+ const agent2 = todo2.agentSettings || await api.getAgentSettings(todo2.agentSettingsId);
46855
+ const msg = await api.addMessage(todo2.projectId, content2, agent2, todoId);
46703
46856
  if (args.json)
46704
46857
  console.log(JSON.stringify(msg, null, 2));
46705
46858
  else
@@ -46707,6 +46860,32 @@ Cancelled by user (Ctrl+C)
46707
46860
  `);
46708
46861
  return;
46709
46862
  }
46863
+ if (positionals[0] === "show" && positionals[1] === "list") {
46864
+ const explicitProject = !!args.project;
46865
+ const todoId = positionals[2] || (!explicitProject ? getEnv("TODO_ID") || cfgScope.data.last_todo_id : undefined);
46866
+ const projectId2 = todoId ? undefined : args.project || getEnv("PROJECT_ID") || cfgScope.data.default_project_id;
46867
+ if (!todoId && !projectId2) {
46868
+ process.stderr.write(`${RED}Usage: todoforai-cli show list [todo-id] [--project <id>] [--card <name>]${RESET}
46869
+ `);
46870
+ process.exit(2);
46871
+ }
46872
+ const { items } = await api.listShows({ todoId, projectId: projectId2, card: args.card });
46873
+ if (args.json) {
46874
+ console.log(JSON.stringify(items, null, 2));
46875
+ return;
46876
+ }
46877
+ if (items.length === 0) {
46878
+ process.stderr.write(`${DIM}No show blocks in ${todoId || `project ${projectId2}`}${RESET}
46879
+ `);
46880
+ return;
46881
+ }
46882
+ for (const it of items) {
46883
+ const kind = it.url ? `url ${it.url}` : it.mime || "";
46884
+ const card = it.cardRef ? ` card=${it.cardRef}` : "";
46885
+ console.log(`${it.ref} ${it.title || it.filename || ""} ${kind}${card}`);
46886
+ }
46887
+ return;
46888
+ }
46710
46889
  if (positionals[0] === "show") {
46711
46890
  const [, filePath, todoArg] = positionals;
46712
46891
  const todoId = todoArg || getEnv("TODO_ID") || cfgScope.data.last_todo_id;
@@ -46732,9 +46911,9 @@ Cancelled by user (Ctrl+C)
46732
46911
  process.exit(1);
46733
46912
  }
46734
46913
  blob = file;
46735
- name = path4.basename(filePath);
46914
+ name = path3.basename(filePath);
46736
46915
  }
46737
- const res = await api.showFile(todoId, blob, name, { title: args.title, alias: args.alias, mime: args.mime });
46916
+ const res = await api.showFile(todoId, blob, name, { title: args.title, alias: args.alias, mime: args.mime, card: args.card });
46738
46917
  if (args.json)
46739
46918
  console.log(JSON.stringify(res, null, 2));
46740
46919
  else
@@ -46759,13 +46938,13 @@ Cancelled by user (Ctrl+C)
46759
46938
  if (positionals[0] === "recommend") {
46760
46939
  const templateId = args.template || positionals[1];
46761
46940
  if (!templateId) {
46762
- process.stderr.write(`${RED}Usage: todoforai-cli recommend --template <id> [--note "why"] [--priority high|medium|low] [--title "..."] [--project <id>]${RESET}
46941
+ 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}
46763
46942
  `);
46764
46943
  process.stderr.write(`${DIM}Create a template first with: todoregistry-cli create --name ... --description ... --body @prompt.md${RESET}
46765
46944
  `);
46766
46945
  process.exit(2);
46767
46946
  }
46768
- let projectId2 = args.project || cfgScope.data.default_project_id;
46947
+ let projectId2 = args.project || getEnv("PROJECT_ID") || cfgScope.data.default_project_id;
46769
46948
  if (!projectId2) {
46770
46949
  const projects2 = await api.listProjects();
46771
46950
  projectId2 = projects2.find((p) => p.project?.isDefault)?.project?.id || projects2[0]?.project?.id;
@@ -46787,7 +46966,10 @@ Cancelled by user (Ctrl+C)
46787
46966
  ...args.title ? { title: args.title } : {},
46788
46967
  ...args.note ? { note: args.note } : {},
46789
46968
  ...priority ? { priority } : {},
46790
- ...args["business-context"] ? { businessContextId: args["business-context"] } : {}
46969
+ ...args["business-context"] ? { businessContextId: args["business-context"] } : {},
46970
+ ...args.group ? { group: args.group } : {},
46971
+ ...args["group-name"] ? { groupName: args["group-name"] } : {},
46972
+ ...args["group-description"] ? { groupDescription: args["group-description"] } : {}
46791
46973
  });
46792
46974
  if (args.json)
46793
46975
  console.log(JSON.stringify(rec, null, 2));
@@ -46797,7 +46979,7 @@ Cancelled by user (Ctrl+C)
46797
46979
  return;
46798
46980
  }
46799
46981
  if (positionals[0] === "claim" && positionals[1] === "mint") {
46800
- let seedProjectId = args.seed || args.project || cfgScope.data.default_project_id;
46982
+ let seedProjectId = args.seed || args.project || getEnv("PROJECT_ID") || cfgScope.data.default_project_id;
46801
46983
  if (!seedProjectId) {
46802
46984
  const projects2 = await api.listProjects();
46803
46985
  seedProjectId = projects2.find((p) => p.project?.isDefault)?.project?.id || projects2[0]?.project?.id;
@@ -46839,7 +47021,7 @@ Cancelled by user (Ctrl+C)
46839
47021
  return;
46840
47022
  }
46841
47023
  if (positionals[0] === "next") {
46842
- let projectId2 = args.project || cfgScope.data.default_project_id;
47024
+ let projectId2 = args.project || getEnv("PROJECT_ID") || cfgScope.data.default_project_id;
46843
47025
  if (!projectId2) {
46844
47026
  const projects2 = await api.listProjects();
46845
47027
  projectId2 = projects2.find((p) => p.project?.isDefault)?.project?.id || projects2[0]?.project?.id;
@@ -46875,7 +47057,13 @@ Cancelled by user (Ctrl+C)
46875
47057
  return;
46876
47058
  }
46877
47059
  if (args["list-models"]) {
46878
- await listModelsCommand(api, { json: !!args.json, filter: positionals[0] });
47060
+ const ids = qualifiedModelIds(await api.listModels(), positionals[0]);
47061
+ if (args.json)
47062
+ console.log(JSON.stringify(ids, null, 2));
47063
+ else
47064
+ for (const id of ids)
47065
+ process.stderr.write(`${BRAND}${id}${RESET}
47066
+ `);
46879
47067
  return;
46880
47068
  }
46881
47069
  if (positionals[0] === "agent") {
@@ -46883,7 +47071,7 @@ Cancelled by user (Ctrl+C)
46883
47071
  return;
46884
47072
  }
46885
47073
  if (positionals[0] === "list" || positionals[0] === "ls") {
46886
- let defaultProjectId = args.project || cfgScope.data.default_project_id;
47074
+ let defaultProjectId = args.project || getEnv("PROJECT_ID") || cfgScope.data.default_project_id;
46887
47075
  if (!defaultProjectId) {
46888
47076
  const projects2 = await api.listProjects();
46889
47077
  defaultProjectId = projects2.find((p) => p.project?.isDefault)?.project?.id || projects2[0]?.project?.id;
@@ -46922,14 +47110,14 @@ Cancelled by user (Ctrl+C)
46922
47110
  }
46923
47111
  const mode = args.debug ? "debug" : args.detailed ? "detailed" : "default";
46924
47112
  const format = args["format-anthropic"] ? "anthropic" : "compact";
46925
- printFullChat(todo2, getFrontendUrl(apiUrl, todo2.projectId, todoId), slice, mode, format);
47113
+ printFullChat(todo2, getFrontendUrl(apiUrl, todoId), slice, mode, format);
46926
47114
  return;
46927
47115
  }
46928
47116
  if (process.stderr.isTTY)
46929
47117
  printLogo();
46930
47118
  if (args.template) {
46931
47119
  if (!args["no-bridge"] && !args["no-watch"])
46932
- ensureBridgeRunning(apiUrl, apiKey);
47120
+ await ensureBridgeRunning(apiUrl, apiKey);
46933
47121
  const templateId = args.template;
46934
47122
  const spec = await api.getRegistrySpec(templateId);
46935
47123
  process.stderr.write(`${DIM}Template:${RESET} ${BRAND}${spec.name}${RESET}
@@ -46938,7 +47126,7 @@ Cancelled by user (Ctrl+C)
46938
47126
  process.stderr.write(`${DIM}${spec.description}${RESET}
46939
47127
  `);
46940
47128
  const projects2 = await api.listProjects();
46941
- let projectId2 = args.project;
47129
+ let projectId2 = args.project || getEnv("PROJECT_ID");
46942
47130
  if (!projectId2) {
46943
47131
  const cached = cfgScope.data.default_project_id;
46944
47132
  projectId2 = (cached && projects2.some((p) => getItemId(p) === cached) ? cached : null) || projects2.find((p) => p.project?.isDefault)?.project?.id || projects2[0]?.project?.id;
@@ -46948,10 +47136,13 @@ Cancelled by user (Ctrl+C)
46948
47136
  `);
46949
47137
  process.exit(1);
46950
47138
  }
46951
- const todo2 = await api.startFromSpec(projectId2, templateId);
47139
+ const todo2 = await api.startFromSpec(projectId2, templateId, {
47140
+ ...groupTag ? { groupTag } : {},
47141
+ ...groupName ? { groupName } : {}
47142
+ });
46952
47143
  const todoId = todo2.id;
46953
47144
  cfgScope.setLastTodoId(todoId);
46954
- const frontendUrl2 = getFrontendUrl(apiUrl, projectId2, todoId);
47145
+ const frontendUrl2 = getFrontendUrl(apiUrl, todoId);
46955
47146
  if (args.json) {
46956
47147
  console.log(JSON.stringify({ ...todo2, frontend_url: frontendUrl2 }, null, 2));
46957
47148
  } else if (args["no-watch"]) {
@@ -46996,7 +47187,7 @@ ${"\u2500".repeat(40)}
46996
47187
  }
46997
47188
  if (args.resume || args.continue) {
46998
47189
  if (!args["no-bridge"])
46999
- ensureBridgeRunning(apiUrl, apiKey);
47190
+ await ensureBridgeRunning(apiUrl, apiKey);
47000
47191
  const todoId = args.resume || cfgScope.data.last_todo_id;
47001
47192
  if (!todoId) {
47002
47193
  process.stderr.write(`Error: No recent todo found
@@ -47013,7 +47204,7 @@ ${"\u2500".repeat(40)}
47013
47204
  }
47014
47205
  process.stderr.write(`
47015
47206
  ${"\u2500".repeat(40)}
47016
- Resumed: ${CYAN}${getFrontendUrl(apiUrl, projectId2, todoId)}${RESET}
47207
+ Resumed: ${CYAN}${getFrontendUrl(apiUrl, todoId)}${RESET}
47017
47208
  `);
47018
47209
  const ws2 = new FrontendWebSocket(apiUrl, apiKey);
47019
47210
  await ws2.connect();
@@ -47037,8 +47228,7 @@ Resumed: ${CYAN}${getFrontendUrl(apiUrl, projectId2, todoId)}${RESET}
47037
47228
  }
47038
47229
  const ws = args["no-watch"] ? null : new FrontendWebSocket(apiUrl, apiKey);
47039
47230
  const wsReady = ws ? ws.connect() : null;
47040
- if (!args["no-bridge"] && !args["no-watch"])
47041
- ensureBridgeRunning(apiUrl, apiKey);
47231
+ const bridgeReady = !args["no-bridge"] && !args["no-watch"] ? ensureBridgeRunning(apiUrl, apiKey) : null;
47042
47232
  let preMatchedAgent = null;
47043
47233
  let agents = null;
47044
47234
  if (args.agent) {
@@ -47096,7 +47286,8 @@ Resumed: ${CYAN}${getFrontendUrl(apiUrl, projectId2, todoId)}${RESET}
47096
47286
  } else {
47097
47287
  content = await readStdin();
47098
47288
  }
47099
- const hasProject = args.project || cfgScope.data.default_project_id;
47289
+ const envProjectId = getEnv("PROJECT_ID");
47290
+ const hasProject = args.project || envProjectId || cfgScope.data.default_project_id;
47100
47291
  const storedAgent = cfgScope.data.default_agent_settings;
47101
47292
  const hasAgent = preMatchedAgent || storedAgent?.id && !args.agent;
47102
47293
  let projects = null;
@@ -47107,8 +47298,8 @@ Resumed: ${CYAN}${getFrontendUrl(apiUrl, projectId2, todoId)}${RESET}
47107
47298
  }
47108
47299
  let projectId;
47109
47300
  let projectName;
47110
- if (args.project) {
47111
- projectId = args.project;
47301
+ if (args.project || envProjectId) {
47302
+ projectId = args.project || envProjectId;
47112
47303
  projectName = projectId;
47113
47304
  if (projects) {
47114
47305
  const match = projects.find((p) => getItemId(p) === projectId);
@@ -47133,6 +47324,8 @@ Resumed: ${CYAN}${getFrontendUrl(apiUrl, projectId2, todoId)}${RESET}
47133
47324
  }
47134
47325
  if (wsReady)
47135
47326
  await wsReady;
47327
+ if (bridgeReady)
47328
+ await bridgeReady;
47136
47329
  if (args.model)
47137
47330
  agent = { ...agent, model: args.model };
47138
47331
  if (args["raw-sysmsg"]) {
@@ -47146,9 +47339,9 @@ Resumed: ${CYAN}${getFrontendUrl(apiUrl, projectId2, todoId)}${RESET}
47146
47339
  cfg.addToHistory(content);
47147
47340
  let todo;
47148
47341
  try {
47149
- todo = await api.addMessage(projectId, content, agent);
47342
+ todo = await api.addMessage(projectId, content, agent, undefined, undefined, undefined, groupTag || undefined, groupName);
47150
47343
  } catch (e) {
47151
- if (!args.project && cfgScope.data.default_project_id === projectId && /failed: 403/.test(e.message || "")) {
47344
+ if (!args.project && !envProjectId && cfgScope.data.default_project_id === projectId && /failed: 403/.test(e.message || "")) {
47152
47345
  cfgScope.clearDefaultProject();
47153
47346
  process.stderr.write(`${RED}Not authorized for cached default project ${projectName} (${projectId}) \u2014 cleared it. Re-run to pick a project.${RESET}
47154
47347
  `);
@@ -47158,7 +47351,7 @@ Resumed: ${CYAN}${getFrontendUrl(apiUrl, projectId2, todoId)}${RESET}
47158
47351
  }
47159
47352
  const actualTodoId = todo.id || crypto.randomUUID();
47160
47353
  cfgScope.setLastTodoId(actualTodoId);
47161
- const frontendUrl = getFrontendUrl(apiUrl, projectId, actualTodoId);
47354
+ const frontendUrl = getFrontendUrl(apiUrl, actualTodoId);
47162
47355
  if (args.json) {
47163
47356
  console.log(JSON.stringify({ ...todo, frontend_url: frontendUrl }, null, 2));
47164
47357
  } else if (!ws) {
@@ -47183,7 +47376,7 @@ ${"\u2500".repeat(40)}
47183
47376
  await ws.close();
47184
47377
  }
47185
47378
  }
47186
- main().then(() => process.exit(0)).catch((e) => {
47379
+ main().then(() => process.exit(process.exitCode ?? 0)).catch((e) => {
47187
47380
  process.stderr.write(`Error: ${e.message}
47188
47381
  `);
47189
47382
  process.exit(1);
package/package.json CHANGED
@@ -1,36 +1,30 @@
1
1
  {
2
2
  "name": "@todoforai/cli",
3
- "version": "0.1.27",
3
+ "version": "0.1.29",
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": {
21
+ "@shared/api": "file:../packages/shared-api",
20
22
  "@shared/credentials": "file:../packages/shared-credentials",
21
23
  "@todoforai/update-notifier": "^0.1.0",
22
24
  "cli-highlight": "^2.1.11",
23
25
  "diff-match-patch": "^1.0.5",
24
26
  "ws": "^8.18.0"
25
27
  },
26
- "peerDependencies": {
27
- "@todoforai/edge": ">=0.12.0"
28
- },
29
- "peerDependenciesMeta": {
30
- "@todoforai/edge": {
31
- "optional": true
32
- }
33
- },
34
28
  "devDependencies": {
35
29
  "@types/ws": "^8.5.13",
36
30
  "typescript": "^5.7.0"