@todoforai/cli 0.1.3 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +18 -3
  2. package/dist/todoai.js +183 -70
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -10,11 +10,27 @@ bun install -g @todoforai/cli
10
10
 
11
11
  ## Setup
12
12
 
13
+ Just run `todoai` — on first use it opens a browser for **device login** and saves the API key to `~/.todoforai/credentials.json` (shared with the edge daemon).
14
+
15
+ ```bash
16
+ todoai # prompts device login if no key found
17
+ todoai login # explicit login
18
+ ```
19
+
20
+ Optional manual config:
21
+
13
22
  ```bash
14
23
  todoai --set-default-api-url http://localhost:4000 # or https://api.todofor.ai
15
- todoai --set-default-api-key <your-api-key>
16
24
  ```
17
25
 
26
+ Auth resolution order: `--api-key` flag → `TODOFORAI_API_KEY` env → shared credentials (`~/.todoforai/credentials.json`) → device login.
27
+
28
+ ## Edge daemon
29
+
30
+ The CLI talks to the backend over WebSocket; **shell execution, file I/O, and tool calls happen in the edge daemon** running locally. On every run `todoai` spawns a detached edge process if none is running (PID-locked at `~/.todoforai/edge-<hash>.lock`, logs at `~/.todoforai/edge.log`). It keeps running after the CLI exits, so long-running tasks survive `Ctrl+D`.
31
+
32
+ Disable with `--no-edge` if you manage the edge yourself (e.g. systemd, separate terminal).
33
+
18
34
  ## Usage
19
35
 
20
36
  ### Create a todo from a prompt
@@ -68,13 +84,12 @@ todoai --resume <todo-id> # resume specific todo
68
84
  --dangerously-skip-permissions Auto-approve all blocks (CI/benchmarks)
69
85
  --allow-all Set permissions to allow all tools (no approval needed)
70
86
  --no-watch Create todo and exit
87
+ --no-edge Do not auto-spawn edge daemon
71
88
  --json Output as JSON
72
89
  --safe Validate API key upfront
73
90
  --debug, -d Debug output
74
91
  --show-config Show config
75
- --set-defaults Interactive defaults setup
76
92
  --set-default-api-url Set default API URL
77
- --set-default-api-key Set default API key
78
93
  --reset-config Reset config file
79
94
  --help, -h Show this help
80
95
  ```
package/dist/todoai.js CHANGED
@@ -1617,7 +1617,7 @@ var require_core = __commonJS((exports, module) => {
1617
1617
  return match && match.index === 0;
1618
1618
  }
1619
1619
  var BACKREF_RE = /\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./;
1620
- function join2(regexps, separator = "|") {
1620
+ function join3(regexps, separator = "|") {
1621
1621
  let numCaptures = 0;
1622
1622
  return regexps.map((regex) => {
1623
1623
  numCaptures += 1;
@@ -1900,7 +1900,7 @@ var require_core = __commonJS((exports, module) => {
1900
1900
  this.exec = () => null;
1901
1901
  }
1902
1902
  const terminators = this.regexes.map((el) => el[1]);
1903
- this.matcherRe = langRe(join2(terminators), true);
1903
+ this.matcherRe = langRe(join3(terminators), true);
1904
1904
  this.lastIndex = 0;
1905
1905
  }
1906
1906
  exec(s) {
@@ -42663,7 +42663,7 @@ var require_dist = __commonJS((exports) => {
42663
42663
  // src/index.ts
42664
42664
  import { realpathSync } from "fs";
42665
42665
  import { resolve as resolve3 } from "path";
42666
- import { homedir as homedir2 } from "os";
42666
+ import { homedir as homedir3 } from "os";
42667
42667
 
42668
42668
  // src/tips.ts
42669
42669
  var TIPS = [
@@ -42833,6 +42833,7 @@ function normalizeApiUrl(url) {
42833
42833
  return `https://${url}`;
42834
42834
  return url;
42835
42835
  }
42836
+ var SUBCOMMANDS = new Set(["login", "logout"]);
42836
42837
  var CREDENTIALS_PATH = path.join(os.homedir(), ".todoforai", "credentials.json");
42837
42838
 
42838
42839
  // ../edge/bun/src/frontend-ws.ts
@@ -43045,11 +43046,13 @@ Usage:
43045
43046
  todoai --resume <todo-id> # Resume specific todo
43046
43047
  todoai --inspect <todo-id> # Print full chat log (read-only)
43047
43048
  todoai --template <id> [--input k=v] # Start from a registry template
43049
+ todoai --list-agents # List available agents and exit
43048
43050
 
43049
43051
  Options:
43050
43052
  --path <dir> Workspace path (default: cwd)
43051
43053
  --project <id> Project ID
43052
43054
  --agent, -a <name> Agent name (partial match)
43055
+ --list-agents List available agents (name, id, workspace paths) and exit
43053
43056
  --api-url <url> API URL
43054
43057
  --api-key <key> API key
43055
43058
  --inspect, -i <todo-id> Print full chat log (read-only, no interactive)
@@ -43061,13 +43064,12 @@ Options:
43061
43064
  --dangerously-skip-permissions Auto-approve all blocks (for CI/benchmarks)
43062
43065
  --allow-all Set permissions to allow all tools (no approval needed)
43063
43066
  --no-watch Create todo and exit
43067
+ --no-edge Do not auto-spawn edge daemon
43064
43068
  --json Output as JSON
43065
43069
  --safe Validate API key upfront
43066
43070
  --debug, -d Debug output
43067
43071
  --show-config Show config
43068
- --set-defaults Interactive defaults setup
43069
43072
  --set-default-api-url Set default API URL
43070
- --set-default-api-key Set default API key
43071
43073
  --reset-config Reset config file
43072
43074
  --help, -h Show this help
43073
43075
  `);
@@ -43079,6 +43081,7 @@ function parseCliArgs() {
43079
43081
  path: { type: "string", default: "." },
43080
43082
  project: { type: "string" },
43081
43083
  agent: { type: "string", short: "a" },
43084
+ "list-agents": { type: "boolean", default: false },
43082
43085
  "api-url": { type: "string" },
43083
43086
  "api-key": { type: "string" },
43084
43087
  inspect: { type: "string", short: "i" },
@@ -43090,13 +43093,12 @@ function parseCliArgs() {
43090
43093
  "dangerously-skip-permissions": { type: "boolean", default: false },
43091
43094
  "allow-all": { type: "boolean", default: false },
43092
43095
  "no-watch": { type: "boolean", default: false },
43096
+ "no-edge": { type: "boolean", default: false },
43093
43097
  json: { type: "boolean", default: false },
43094
43098
  safe: { type: "boolean", default: false },
43095
43099
  debug: { type: "boolean", short: "d", default: false },
43096
43100
  "show-config": { type: "boolean", default: false },
43097
- "set-defaults": { type: "boolean", default: false },
43098
43101
  "set-default-api-url": { type: "string" },
43099
- "set-default-api-key": { type: "string" },
43100
43102
  "reset-config": { type: "boolean", default: false },
43101
43103
  "config-path": { type: "string" },
43102
43104
  help: { type: "boolean", short: "h", default: false }
@@ -43493,18 +43495,6 @@ function getConfigDir() {
43493
43495
  const xdg = process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
43494
43496
  return join(xdg, "todoai-cli");
43495
43497
  }
43496
- function obfuscate(s) {
43497
- return s ? Buffer.from(s, "utf-8").toString("base64") : s;
43498
- }
43499
- function deobfuscate(s) {
43500
- if (!s)
43501
- return s;
43502
- try {
43503
- return Buffer.from(s, "base64").toString("utf-8");
43504
- } catch {
43505
- return s;
43506
- }
43507
- }
43508
43498
  function defaultConfig() {
43509
43499
  return {
43510
43500
  default_project_id: null,
@@ -43513,7 +43503,6 @@ function defaultConfig() {
43513
43503
  default_agent_settings: null,
43514
43504
  default_agent_settings_updated_at: null,
43515
43505
  default_api_url: null,
43516
- default_api_key: null,
43517
43506
  recent_projects: [],
43518
43507
  recent_agents: [],
43519
43508
  last_todo_id: null,
@@ -43538,8 +43527,7 @@ class ConfigStore {
43538
43527
  return defaultConfig();
43539
43528
  try {
43540
43529
  const raw = JSON.parse(readFileSync(this.path, "utf-8"));
43541
- if (raw.default_api_key)
43542
- raw.default_api_key = deobfuscate(raw.default_api_key);
43530
+ delete raw.default_api_key;
43543
43531
  return { ...defaultConfig(), ...raw };
43544
43532
  } catch {
43545
43533
  return defaultConfig();
@@ -43548,10 +43536,7 @@ class ConfigStore {
43548
43536
  save() {
43549
43537
  try {
43550
43538
  mkdirSync(dirname(this.path), { recursive: true });
43551
- const out = { ...this.data };
43552
- if (out.default_api_key)
43553
- out.default_api_key = obfuscate(out.default_api_key);
43554
- writeFileSync(this.path, JSON.stringify(out, null, 2), "utf-8");
43539
+ writeFileSync(this.path, JSON.stringify(this.data, null, 2), "utf-8");
43555
43540
  } catch {}
43556
43541
  }
43557
43542
  setDefaultProject(id, name) {
@@ -43575,10 +43560,6 @@ class ConfigStore {
43575
43560
  this.data.default_api_url = url;
43576
43561
  this.save();
43577
43562
  }
43578
- setDefaultApiKey(key) {
43579
- this.data.default_api_key = key;
43580
- this.save();
43581
- }
43582
43563
  addToHistory(input) {
43583
43564
  const trimmed = input.trim();
43584
43565
  if (!trimmed)
@@ -43595,6 +43576,30 @@ class ConfigStore {
43595
43576
  }
43596
43577
  }
43597
43578
 
43579
+ // src/credentials.ts
43580
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
43581
+ import { dirname as dirname2, join as join2 } from "path";
43582
+ import { homedir as homedir2 } from "os";
43583
+ var CREDENTIALS_PATH2 = join2(homedir2(), ".todoforai", "credentials.json");
43584
+ function read() {
43585
+ if (!existsSync2(CREDENTIALS_PATH2))
43586
+ return {};
43587
+ try {
43588
+ return JSON.parse(readFileSync2(CREDENTIALS_PATH2, "utf-8"));
43589
+ } catch {
43590
+ return {};
43591
+ }
43592
+ }
43593
+ function readCredential(apiUrl) {
43594
+ return read()[apiUrl] || "";
43595
+ }
43596
+ function writeCredential(apiUrl, apiKey) {
43597
+ const data = read();
43598
+ data[apiUrl] = apiKey;
43599
+ mkdirSync2(dirname2(CREDENTIALS_PATH2), { recursive: true });
43600
+ writeFileSync2(CREDENTIALS_PATH2, JSON.stringify(data, null, 2), "utf-8");
43601
+ }
43602
+
43598
43603
  // src/logo.ts
43599
43604
  var LETTERS = {
43600
43605
  t: [" x ", "xxxx", " x ", " xll", " xll", " xxx"],
@@ -43913,6 +43918,12 @@ function splitShellCommands(input) {
43913
43918
  const len = input.length;
43914
43919
  while (i < len) {
43915
43920
  const ch = input[i];
43921
+ if (ch === "\\" && i + 1 < len && input[i + 1] === `
43922
+ `) {
43923
+ current += " ";
43924
+ i += 2;
43925
+ continue;
43926
+ }
43916
43927
  if (ch === "\\" && i + 1 < len) {
43917
43928
  current += ch + input[i + 1];
43918
43929
  i += 2;
@@ -44025,6 +44036,11 @@ function splitTokens(cmd) {
44025
44036
  const len = cmd.length;
44026
44037
  while (i < len) {
44027
44038
  const ch = cmd[i];
44039
+ if (ch === "\\" && i + 1 < len && cmd[i + 1] === `
44040
+ `) {
44041
+ i += 2;
44042
+ continue;
44043
+ }
44028
44044
  if (ch === "\\" && i + 1 < len) {
44029
44045
  current += ch + cmd[i + 1];
44030
44046
  i += 2;
@@ -44115,29 +44131,40 @@ function parseBashCmd(fullCmd) {
44115
44131
  function generalizeBashCmd(fullCmd) {
44116
44132
  return parseBashCmd(fullCmd).map((p) => buildBashPattern(p));
44117
44133
  }
44134
+ function getBlockServerPrefix(block) {
44135
+ const agentPattern = typeof block.generalized_pattern === "string" ? block.generalized_pattern : "";
44136
+ const prefixMatch = agentPattern.match(/^(.*?:)BASH(?:\(|$)/);
44137
+ return prefixMatch ? prefixMatch[1] : "";
44138
+ }
44139
+ var BASH_LIKE_BLOCK_TYPES = new Set(["bash", "machine_exec"]);
44118
44140
  function getBlockPatterns(block) {
44119
44141
  if (Array.isArray(block.generalized_pattern))
44120
44142
  return block.generalized_pattern;
44121
- if (block.type.toLowerCase() === "bash" && block.cmd) {
44122
- const agentPattern = typeof block.generalized_pattern === "string" ? block.generalized_pattern : "";
44123
- const prefixMatch = agentPattern.match(/^(.*?:)BASH\(/);
44124
- const prefix = prefixMatch ? prefixMatch[1] : "";
44143
+ const agentPattern = typeof block.generalized_pattern === "string" ? block.generalized_pattern : "";
44144
+ const isBashLike = BASH_LIKE_BLOCK_TYPES.has(block.type.toLowerCase()) || /:BASH(\(|$)/.test(agentPattern);
44145
+ if (isBashLike && block.cmd) {
44146
+ const prefix = getBlockServerPrefix(block);
44125
44147
  return generalizeBashCmd(block.cmd).map((p) => prefix + p);
44126
44148
  }
44127
44149
  if (block.generalized_pattern)
44128
44150
  return [block.generalized_pattern];
44129
44151
  return [`${block.type}(*)`];
44130
44152
  }
44153
+ function getBlockServerId(block) {
44154
+ const prefix = getBlockServerPrefix(block);
44155
+ return prefix ? prefix.slice(0, -1) : "*";
44156
+ }
44131
44157
 
44132
44158
  // ../packages/shared-fbe/src/permissionUtils.ts
44133
44159
  function parsePattern(pattern) {
44134
44160
  const colonIndex = pattern.indexOf(":");
44135
44161
  if (colonIndex === -1)
44136
44162
  return null;
44137
- return {
44138
- serverId: pattern.slice(0, colonIndex),
44139
- toolName: pattern.slice(colonIndex + 1)
44140
- };
44163
+ const serverId = pattern.slice(0, colonIndex);
44164
+ if (!/^[A-Za-z0-9_*-]+$/.test(serverId)) {
44165
+ return { serverId: "*", toolName: pattern };
44166
+ }
44167
+ return { serverId, toolName: pattern.slice(colonIndex + 1) };
44141
44168
  }
44142
44169
  function serverIdMatches(ruleServerId, targetServerId) {
44143
44170
  if (ruleServerId === targetServerId)
@@ -44170,6 +44197,26 @@ function patternMatches(rulePattern, targetPattern) {
44170
44197
  }
44171
44198
  return false;
44172
44199
  }
44200
+ function bashRuleMatchesCmd(rulePattern, serverId, cmd) {
44201
+ const rule = parsePattern(rulePattern);
44202
+ if (!rule)
44203
+ return false;
44204
+ if (!serverIdMatches(rule.serverId, serverId))
44205
+ return false;
44206
+ if (rule.toolName === "*" || rule.toolName === "BASH")
44207
+ return true;
44208
+ const m = rule.toolName.match(/^BASH\(cmd:\s*(.*)\)$/);
44209
+ if (!m)
44210
+ return false;
44211
+ let body = m[1];
44212
+ const isPrefix = body.endsWith(" *") || body.endsWith("*");
44213
+ if (isPrefix)
44214
+ body = body.replace(/\s*\*$/, "");
44215
+ const subs = splitShellCommands(cmd);
44216
+ if (subs.length === 0)
44217
+ return false;
44218
+ return subs.every((sub) => isPrefix ? sub === body || sub.startsWith(body + " ") : sub === body);
44219
+ }
44173
44220
  function isPatternInList(list, pattern) {
44174
44221
  if (!list)
44175
44222
  return false;
@@ -44181,6 +44228,28 @@ function isPatternAllowed(permissions, pattern) {
44181
44228
  function getNewPatterns(patterns, permissions) {
44182
44229
  return patterns.filter((p) => !isPatternAllowed(permissions, p));
44183
44230
  }
44231
+ function getBlockNewPatterns(block, permissions) {
44232
+ const allow = permissions?.allow ?? [];
44233
+ if (Array.isArray(block.generalized_pattern)) {
44234
+ return getNewPatterns(block.generalized_pattern, permissions);
44235
+ }
44236
+ const agentPattern = typeof block.generalized_pattern === "string" ? block.generalized_pattern : "";
44237
+ const isBash = (block.type.toLowerCase() === "bash" || agentPattern.includes(":BASH(")) && !!block.cmd;
44238
+ if (!isBash) {
44239
+ return getBlockPatterns(block).filter((p) => !isPatternAllowed(permissions, p));
44240
+ }
44241
+ const serverId = getBlockServerId(block);
44242
+ const prefix = serverId === "*" ? "" : `${serverId}:`;
44243
+ const rawSubs = splitShellCommands(block.cmd);
44244
+ const pairs = [];
44245
+ for (const raw of rawSubs) {
44246
+ const parsed = parseBashCmd(raw);
44247
+ if (parsed.length === 0)
44248
+ continue;
44249
+ pairs.push({ raw, pattern: prefix + buildBashPattern(parsed[0]) });
44250
+ }
44251
+ return pairs.filter(({ raw, pattern }) => !isPatternAllowed(permissions, pattern) && !allow.some((rule) => bashRuleMatchesCmd(rule, serverId, raw))).map(({ pattern }) => pattern);
44252
+ }
44184
44253
 
44185
44254
  // src/diff-view.ts
44186
44255
  var import_diff_match_patch = __toESM(require_diff_match_patch(), 1);
@@ -44428,12 +44497,14 @@ function renderDiff(originalContent, modifiedContent, filePath) {
44428
44497
  var diffStoreByWs = new WeakMap;
44429
44498
  function classifyBlock(info) {
44430
44499
  const inner = (info.block_type || "").toLowerCase();
44431
- if (["create", "createfile"].includes(inner))
44500
+ if (["create", "createfile", "write"].includes(inner))
44432
44501
  return "create";
44433
44502
  if (["modify", "modifyfile", "update", "edit"].includes(inner))
44434
44503
  return "edit";
44435
44504
  if (["catfile", "read", "readfile"].includes(inner))
44436
44505
  return "read";
44506
+ if (["search", "grep"].includes(inner))
44507
+ return "search";
44437
44508
  if (inner === "mcp")
44438
44509
  return "mcp";
44439
44510
  if (["shell", "bash"].includes(inner) || info.cmd)
@@ -44441,7 +44512,7 @@ function classifyBlock(info) {
44441
44512
  return "unknown";
44442
44513
  }
44443
44514
  function blockDisplay(info) {
44444
- const labels = { create: "File", edit: "Edit", read: "Read File", mcp: "MCP", shell: "Shell" };
44515
+ const labels = { create: "File", edit: "Edit", read: "Read File", search: "Search", mcp: "MCP", shell: "Shell" };
44445
44516
  const kind = classifyBlock(info);
44446
44517
  const typeLabel = labels[kind] || info.block_type || "Tool";
44447
44518
  const skipKeys = new Set([
@@ -44587,12 +44658,11 @@ ${YELLOW}\u26A0 ${blocks.length} action(s) awaiting approval:${RESET}
44587
44658
  process.stderr.write(renderDiff(diff.originalContent, diff.modifiedContent, filePath));
44588
44659
  }
44589
44660
  }
44590
- const allPatterns = blocks.flatMap((bi) => getBlockPatterns({
44661
+ const newPatterns = blocks.flatMap((bi) => getBlockNewPatterns({
44591
44662
  type: bi.block_type || "unknown",
44592
44663
  generalized_pattern: bi.generalized_pattern,
44593
44664
  cmd: bi.cmd
44594
- }));
44595
- const newPatterns = getNewPatterns(allPatterns, opts.agentSettings?.permissions);
44665
+ }, opts.agentSettings?.permissions));
44596
44666
  const stripPrefix = (p) => p.replace(/^todoai_(edge|cloud):/, "");
44597
44667
  const patternHint = newPatterns.length ? ` ${DIM}${newPatterns.map(stripPrefix).join(", ")}${RESET}` : "";
44598
44668
  try {
@@ -44605,11 +44675,11 @@ ${YELLOW}\u26A0 ${blocks.length} action(s) awaiting approval:${RESET}
44605
44675
  for (const bi of blocks) {
44606
44676
  let patterns;
44607
44677
  if (response === "r") {
44608
- patterns = getBlockPatterns({
44678
+ patterns = getBlockNewPatterns({
44609
44679
  type: bi.block_type || "unknown",
44610
44680
  generalized_pattern: bi.generalized_pattern,
44611
44681
  cmd: bi.cmd
44612
- });
44682
+ }, opts.agentSettings?.permissions);
44613
44683
  if (patterns.length > 0) {
44614
44684
  process.stderr.write(` ${GREEN}\u2713 Remembering: ${patterns.map(stripPrefix).join(", ")}${RESET}
44615
44685
  `);
@@ -44747,10 +44817,62 @@ ${DIM}[todo:status] ${status}${RESET}
44747
44817
  }
44748
44818
  }
44749
44819
 
44820
+ // src/list-agents.ts
44821
+ async function listAgentsCommand(api, opts) {
44822
+ const agents = await api.listAgentSettings();
44823
+ if (opts.json) {
44824
+ console.log(JSON.stringify(agents, null, 2));
44825
+ return;
44826
+ }
44827
+ if (!agents.length) {
44828
+ process.stderr.write(`No agents found.
44829
+ `);
44830
+ return;
44831
+ }
44832
+ const rows = agents.map((a) => ({
44833
+ name: getDisplayName(a),
44834
+ id: getItemId(a),
44835
+ model: a.model || "",
44836
+ paths: getAgentWorkspacePaths(a).map(opts.formatPath)
44837
+ }));
44838
+ const nameW = Math.max(4, ...rows.map((r) => r.name.length));
44839
+ const modelW = Math.max(5, ...rows.map((r) => r.model.length));
44840
+ process.stderr.write(`${DIM}${"NAME".padEnd(nameW)} ${"MODEL".padEnd(modelW)} ID${" ".repeat(34)}PATHS${RESET}
44841
+ `);
44842
+ for (const r of rows) {
44843
+ process.stderr.write(`${BRAND}${r.name.padEnd(nameW)}${RESET} ${CYAN}${r.model.padEnd(modelW)}${RESET} ${DIM}${r.id}${RESET} ${r.paths.join(", ")}
44844
+ `);
44845
+ }
44846
+ }
44847
+
44848
+ // src/ensure-edge.ts
44849
+ import { spawn } from "child_process";
44850
+ import fs from "fs";
44851
+ import path2 from "path";
44852
+ import os2 from "os";
44853
+ function ensureEdgeRunning(apiUrl, apiKey) {
44854
+ const logDir = path2.join(os2.homedir(), ".todoforai");
44855
+ fs.mkdirSync(logDir, { recursive: true });
44856
+ const logFile = path2.join(logDir, "edge.log");
44857
+ const out = fs.openSync(logFile, "a");
44858
+ const child = spawn("bunx", ["@todoforai/edge", "--api-url", apiUrl, "--api-key", apiKey], {
44859
+ detached: true,
44860
+ stdio: ["ignore", out, out]
44861
+ });
44862
+ child.unref();
44863
+ const pid = child.pid;
44864
+ setTimeout(() => {
44865
+ try {
44866
+ process.kill(pid, 0);
44867
+ console.error(`\x1B[2mStarted edge daemon (pid ${pid}), logs: ${logFile.replace(os2.homedir(), "~")}\x1B[0m`);
44868
+ } catch {}
44869
+ }, 500);
44870
+ }
44871
+
44750
44872
  // src/index.ts
44751
- function formatPathWithTilde(path2) {
44752
- const home = homedir2();
44753
- return path2.startsWith(home) ? path2.replace(home, "~") : path2;
44873
+ function formatPathWithTilde(path3) {
44874
+ const home = homedir3();
44875
+ return path3.startsWith(home) ? path3.replace(home, "~") : path3;
44754
44876
  }
44755
44877
  function getFrontendUrl(apiUrl, projectId, todoId) {
44756
44878
  if (apiUrl.includes("localhost:4000") || apiUrl.includes("127.0.0.1:4000")) {
@@ -44844,8 +44966,8 @@ Cancelled by user (Ctrl+C)
44844
44966
  return;
44845
44967
  }
44846
44968
  if (args["reset-config"]) {
44847
- const { existsSync: existsSync2, unlinkSync } = await import("fs");
44848
- if (existsSync2(cfg.path)) {
44969
+ const { existsSync: existsSync3, unlinkSync } = await import("fs");
44970
+ if (existsSync3(cfg.path)) {
44849
44971
  unlinkSync(cfg.path);
44850
44972
  console.log(`Configuration reset: ${formatPathWithTilde(cfg.path)}`);
44851
44973
  } else
@@ -44857,21 +44979,6 @@ Cancelled by user (Ctrl+C)
44857
44979
  console.log(`Default API URL set to: ${args["set-default-api-url"]}`);
44858
44980
  return;
44859
44981
  }
44860
- if (args["set-default-api-key"]) {
44861
- cfg.setDefaultApiKey(args["set-default-api-key"]);
44862
- console.log("Default API key set");
44863
- return;
44864
- }
44865
- if (args["set-defaults"]) {
44866
- const url = await readLine(`API URL [${cfg.data.default_api_url || DEFAULT_API_URL}]: `);
44867
- if (url)
44868
- cfg.setDefaultApiUrl(url);
44869
- const key = await readLine("API Key: ");
44870
- if (key)
44871
- cfg.setDefaultApiKey(key);
44872
- console.log("Defaults saved.");
44873
- return;
44874
- }
44875
44982
  const apiUrl = normalizeApiUrl(args["api-url"] || cfg.data.default_api_url || getEnv("API_URL") || DEFAULT_API_URL);
44876
44983
  async function deviceLogin() {
44877
44984
  const loginApi = new ApiClient(apiUrl, "");
@@ -44886,12 +44993,12 @@ Cancelled by user (Ctrl+C)
44886
44993
 
44887
44994
  `);
44888
44995
  try {
44889
- const { spawn } = await import("child_process");
44996
+ const { spawn: spawn2 } = await import("child_process");
44890
44997
  if (process.platform === "win32") {
44891
- spawn("cmd", ["/c", "start", "", url], { stdio: "ignore", detached: true }).unref();
44998
+ spawn2("cmd", ["/c", "start", "", url], { stdio: "ignore", detached: true }).unref();
44892
44999
  } else {
44893
45000
  const cmd = process.platform === "darwin" ? "open" : "xdg-open";
44894
- spawn(cmd, [url], { stdio: "ignore", detached: true }).unref();
45001
+ spawn2(cmd, [url], { stdio: "ignore", detached: true }).unref();
44895
45002
  }
44896
45003
  } catch {}
44897
45004
  process.stderr.write(`Waiting for approval (expires in ${Math.round(expiresIn / 60)}min)...
@@ -44904,7 +45011,7 @@ Cancelled by user (Ctrl+C)
44904
45011
  const poll = await loginApi.pollDeviceLogin(code);
44905
45012
  failures = 0;
44906
45013
  if (poll.status === "complete" && poll.apiKey) {
44907
- cfg.setDefaultApiKey(poll.apiKey);
45014
+ writeCredential(apiUrl, poll.apiKey);
44908
45015
  process.stderr.write(`${GREEN}\u2705 Login successful! API key saved.${RESET}
44909
45016
  `);
44910
45017
  return poll.apiKey;
@@ -44927,11 +45034,17 @@ Cancelled by user (Ctrl+C)
44927
45034
  await deviceLogin();
44928
45035
  return;
44929
45036
  }
44930
- let apiKey = args["api-key"] || cfg.data.default_api_key || getEnv("API_KEY") || "";
45037
+ let apiKey = args["api-key"] || getEnv("API_KEY") || readCredential(apiUrl) || "";
44931
45038
  if (!apiKey) {
44932
45039
  apiKey = await deviceLogin();
44933
45040
  }
44934
45041
  const api = new ApiClient(apiUrl, apiKey);
45042
+ if (!args["no-edge"])
45043
+ ensureEdgeRunning(apiUrl, apiKey);
45044
+ if (args["list-agents"]) {
45045
+ await listAgentsCommand(api, { json: !!args.json, formatPath: formatPathWithTilde });
45046
+ return;
45047
+ }
44935
45048
  if (args.inspect) {
44936
45049
  const todoId = args.inspect;
44937
45050
  const todo2 = await api.getTodo(todoId);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@todoforai/cli",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "todoai": "dist/todoai.js"