@todoforai/cli 0.1.2 → 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 +19 -3
  2. package/dist/todoai.js +195 -72
  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
@@ -66,14 +82,14 @@ todoai --resume <todo-id> # resume specific todo
66
82
  --continue, -c Continue most recent todo
67
83
  --non-interactive, -n Run to completion and exit
68
84
  --dangerously-skip-permissions Auto-approve all blocks (CI/benchmarks)
85
+ --allow-all Set permissions to allow all tools (no approval needed)
69
86
  --no-watch Create todo and exit
87
+ --no-edge Do not auto-spawn edge daemon
70
88
  --json Output as JSON
71
89
  --safe Validate API key upfront
72
90
  --debug, -d Debug output
73
91
  --show-config Show config
74
- --set-defaults Interactive defaults setup
75
92
  --set-default-api-url Set default API URL
76
- --set-default-api-key Set default API key
77
93
  --reset-config Reset config file
78
94
  --help, -h Show this help
79
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)
@@ -43059,14 +43062,14 @@ Options:
43059
43062
  --continue, -c Continue most recent todo
43060
43063
  --non-interactive, -n Run to completion and exit without interactive prompt
43061
43064
  --dangerously-skip-permissions Auto-approve all blocks (for CI/benchmarks)
43065
+ --allow-all Set permissions to allow all tools (no approval needed)
43062
43066
  --no-watch Create todo and exit
43067
+ --no-edge Do not auto-spawn edge daemon
43063
43068
  --json Output as JSON
43064
43069
  --safe Validate API key upfront
43065
43070
  --debug, -d Debug output
43066
43071
  --show-config Show config
43067
- --set-defaults Interactive defaults setup
43068
43072
  --set-default-api-url Set default API URL
43069
- --set-default-api-key Set default API key
43070
43073
  --reset-config Reset config file
43071
43074
  --help, -h Show this help
43072
43075
  `);
@@ -43078,6 +43081,7 @@ function parseCliArgs() {
43078
43081
  path: { type: "string", default: "." },
43079
43082
  project: { type: "string" },
43080
43083
  agent: { type: "string", short: "a" },
43084
+ "list-agents": { type: "boolean", default: false },
43081
43085
  "api-url": { type: "string" },
43082
43086
  "api-key": { type: "string" },
43083
43087
  inspect: { type: "string", short: "i" },
@@ -43087,14 +43091,14 @@ function parseCliArgs() {
43087
43091
  continue: { type: "boolean", short: "c", default: false },
43088
43092
  "non-interactive": { type: "boolean", short: "n", default: false },
43089
43093
  "dangerously-skip-permissions": { type: "boolean", default: false },
43094
+ "allow-all": { type: "boolean", default: false },
43090
43095
  "no-watch": { type: "boolean", default: false },
43096
+ "no-edge": { type: "boolean", default: false },
43091
43097
  json: { type: "boolean", default: false },
43092
43098
  safe: { type: "boolean", default: false },
43093
43099
  debug: { type: "boolean", short: "d", default: false },
43094
43100
  "show-config": { type: "boolean", default: false },
43095
- "set-defaults": { type: "boolean", default: false },
43096
43101
  "set-default-api-url": { type: "string" },
43097
- "set-default-api-key": { type: "string" },
43098
43102
  "reset-config": { type: "boolean", default: false },
43099
43103
  "config-path": { type: "string" },
43100
43104
  help: { type: "boolean", short: "h", default: false }
@@ -43491,18 +43495,6 @@ function getConfigDir() {
43491
43495
  const xdg = process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
43492
43496
  return join(xdg, "todoai-cli");
43493
43497
  }
43494
- function obfuscate(s) {
43495
- return s ? Buffer.from(s, "utf-8").toString("base64") : s;
43496
- }
43497
- function deobfuscate(s) {
43498
- if (!s)
43499
- return s;
43500
- try {
43501
- return Buffer.from(s, "base64").toString("utf-8");
43502
- } catch {
43503
- return s;
43504
- }
43505
- }
43506
43498
  function defaultConfig() {
43507
43499
  return {
43508
43500
  default_project_id: null,
@@ -43511,7 +43503,6 @@ function defaultConfig() {
43511
43503
  default_agent_settings: null,
43512
43504
  default_agent_settings_updated_at: null,
43513
43505
  default_api_url: null,
43514
- default_api_key: null,
43515
43506
  recent_projects: [],
43516
43507
  recent_agents: [],
43517
43508
  last_todo_id: null,
@@ -43536,8 +43527,7 @@ class ConfigStore {
43536
43527
  return defaultConfig();
43537
43528
  try {
43538
43529
  const raw = JSON.parse(readFileSync(this.path, "utf-8"));
43539
- if (raw.default_api_key)
43540
- raw.default_api_key = deobfuscate(raw.default_api_key);
43530
+ delete raw.default_api_key;
43541
43531
  return { ...defaultConfig(), ...raw };
43542
43532
  } catch {
43543
43533
  return defaultConfig();
@@ -43546,10 +43536,7 @@ class ConfigStore {
43546
43536
  save() {
43547
43537
  try {
43548
43538
  mkdirSync(dirname(this.path), { recursive: true });
43549
- const out = { ...this.data };
43550
- if (out.default_api_key)
43551
- out.default_api_key = obfuscate(out.default_api_key);
43552
- writeFileSync(this.path, JSON.stringify(out, null, 2), "utf-8");
43539
+ writeFileSync(this.path, JSON.stringify(this.data, null, 2), "utf-8");
43553
43540
  } catch {}
43554
43541
  }
43555
43542
  setDefaultProject(id, name) {
@@ -43573,10 +43560,6 @@ class ConfigStore {
43573
43560
  this.data.default_api_url = url;
43574
43561
  this.save();
43575
43562
  }
43576
- setDefaultApiKey(key) {
43577
- this.data.default_api_key = key;
43578
- this.save();
43579
- }
43580
43563
  addToHistory(input) {
43581
43564
  const trimmed = input.trim();
43582
43565
  if (!trimmed)
@@ -43593,6 +43576,30 @@ class ConfigStore {
43593
43576
  }
43594
43577
  }
43595
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
+
43596
43603
  // src/logo.ts
43597
43604
  var LETTERS = {
43598
43605
  t: [" x ", "xxxx", " x ", " xll", " xll", " xxx"],
@@ -43911,6 +43918,12 @@ function splitShellCommands(input) {
43911
43918
  const len = input.length;
43912
43919
  while (i < len) {
43913
43920
  const ch = input[i];
43921
+ if (ch === "\\" && i + 1 < len && input[i + 1] === `
43922
+ `) {
43923
+ current += " ";
43924
+ i += 2;
43925
+ continue;
43926
+ }
43914
43927
  if (ch === "\\" && i + 1 < len) {
43915
43928
  current += ch + input[i + 1];
43916
43929
  i += 2;
@@ -44023,6 +44036,11 @@ function splitTokens(cmd) {
44023
44036
  const len = cmd.length;
44024
44037
  while (i < len) {
44025
44038
  const ch = cmd[i];
44039
+ if (ch === "\\" && i + 1 < len && cmd[i + 1] === `
44040
+ `) {
44041
+ i += 2;
44042
+ continue;
44043
+ }
44026
44044
  if (ch === "\\" && i + 1 < len) {
44027
44045
  current += ch + cmd[i + 1];
44028
44046
  i += 2;
@@ -44113,29 +44131,40 @@ function parseBashCmd(fullCmd) {
44113
44131
  function generalizeBashCmd(fullCmd) {
44114
44132
  return parseBashCmd(fullCmd).map((p) => buildBashPattern(p));
44115
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"]);
44116
44140
  function getBlockPatterns(block) {
44117
44141
  if (Array.isArray(block.generalized_pattern))
44118
44142
  return block.generalized_pattern;
44119
- if (block.type.toLowerCase() === "bash" && block.cmd) {
44120
- const agentPattern = typeof block.generalized_pattern === "string" ? block.generalized_pattern : "";
44121
- const prefixMatch = agentPattern.match(/^(.*?:)BASH\(/);
44122
- 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);
44123
44147
  return generalizeBashCmd(block.cmd).map((p) => prefix + p);
44124
44148
  }
44125
44149
  if (block.generalized_pattern)
44126
44150
  return [block.generalized_pattern];
44127
44151
  return [`${block.type}(*)`];
44128
44152
  }
44153
+ function getBlockServerId(block) {
44154
+ const prefix = getBlockServerPrefix(block);
44155
+ return prefix ? prefix.slice(0, -1) : "*";
44156
+ }
44129
44157
 
44130
44158
  // ../packages/shared-fbe/src/permissionUtils.ts
44131
44159
  function parsePattern(pattern) {
44132
44160
  const colonIndex = pattern.indexOf(":");
44133
44161
  if (colonIndex === -1)
44134
44162
  return null;
44135
- return {
44136
- serverId: pattern.slice(0, colonIndex),
44137
- toolName: pattern.slice(colonIndex + 1)
44138
- };
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) };
44139
44168
  }
44140
44169
  function serverIdMatches(ruleServerId, targetServerId) {
44141
44170
  if (ruleServerId === targetServerId)
@@ -44168,6 +44197,26 @@ function patternMatches(rulePattern, targetPattern) {
44168
44197
  }
44169
44198
  return false;
44170
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
+ }
44171
44220
  function isPatternInList(list, pattern) {
44172
44221
  if (!list)
44173
44222
  return false;
@@ -44179,6 +44228,28 @@ function isPatternAllowed(permissions, pattern) {
44179
44228
  function getNewPatterns(patterns, permissions) {
44180
44229
  return patterns.filter((p) => !isPatternAllowed(permissions, p));
44181
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
+ }
44182
44253
 
44183
44254
  // src/diff-view.ts
44184
44255
  var import_diff_match_patch = __toESM(require_diff_match_patch(), 1);
@@ -44426,12 +44497,14 @@ function renderDiff(originalContent, modifiedContent, filePath) {
44426
44497
  var diffStoreByWs = new WeakMap;
44427
44498
  function classifyBlock(info) {
44428
44499
  const inner = (info.block_type || "").toLowerCase();
44429
- if (["create", "createfile"].includes(inner))
44500
+ if (["create", "createfile", "write"].includes(inner))
44430
44501
  return "create";
44431
44502
  if (["modify", "modifyfile", "update", "edit"].includes(inner))
44432
44503
  return "edit";
44433
44504
  if (["catfile", "read", "readfile"].includes(inner))
44434
44505
  return "read";
44506
+ if (["search", "grep"].includes(inner))
44507
+ return "search";
44435
44508
  if (inner === "mcp")
44436
44509
  return "mcp";
44437
44510
  if (["shell", "bash"].includes(inner) || info.cmd)
@@ -44439,7 +44512,7 @@ function classifyBlock(info) {
44439
44512
  return "unknown";
44440
44513
  }
44441
44514
  function blockDisplay(info) {
44442
- 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" };
44443
44516
  const kind = classifyBlock(info);
44444
44517
  const typeLabel = labels[kind] || info.block_type || "Tool";
44445
44518
  const skipKeys = new Set([
@@ -44585,12 +44658,11 @@ ${YELLOW}\u26A0 ${blocks.length} action(s) awaiting approval:${RESET}
44585
44658
  process.stderr.write(renderDiff(diff.originalContent, diff.modifiedContent, filePath));
44586
44659
  }
44587
44660
  }
44588
- const allPatterns = blocks.flatMap((bi) => getBlockPatterns({
44661
+ const newPatterns = blocks.flatMap((bi) => getBlockNewPatterns({
44589
44662
  type: bi.block_type || "unknown",
44590
44663
  generalized_pattern: bi.generalized_pattern,
44591
44664
  cmd: bi.cmd
44592
- }));
44593
- const newPatterns = getNewPatterns(allPatterns, opts.agentSettings?.permissions);
44665
+ }, opts.agentSettings?.permissions));
44594
44666
  const stripPrefix = (p) => p.replace(/^todoai_(edge|cloud):/, "");
44595
44667
  const patternHint = newPatterns.length ? ` ${DIM}${newPatterns.map(stripPrefix).join(", ")}${RESET}` : "";
44596
44668
  try {
@@ -44603,11 +44675,11 @@ ${YELLOW}\u26A0 ${blocks.length} action(s) awaiting approval:${RESET}
44603
44675
  for (const bi of blocks) {
44604
44676
  let patterns;
44605
44677
  if (response === "r") {
44606
- patterns = getBlockPatterns({
44678
+ patterns = getBlockNewPatterns({
44607
44679
  type: bi.block_type || "unknown",
44608
44680
  generalized_pattern: bi.generalized_pattern,
44609
44681
  cmd: bi.cmd
44610
- });
44682
+ }, opts.agentSettings?.permissions);
44611
44683
  if (patterns.length > 0) {
44612
44684
  process.stderr.write(` ${GREEN}\u2713 Remembering: ${patterns.map(stripPrefix).join(", ")}${RESET}
44613
44685
  `);
@@ -44745,10 +44817,62 @@ ${DIM}[todo:status] ${status}${RESET}
44745
44817
  }
44746
44818
  }
44747
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
+
44748
44872
  // src/index.ts
44749
- function formatPathWithTilde(path2) {
44750
- const home = homedir2();
44751
- 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;
44752
44876
  }
44753
44877
  function getFrontendUrl(apiUrl, projectId, todoId) {
44754
44878
  if (apiUrl.includes("localhost:4000") || apiUrl.includes("127.0.0.1:4000")) {
@@ -44842,8 +44966,8 @@ Cancelled by user (Ctrl+C)
44842
44966
  return;
44843
44967
  }
44844
44968
  if (args["reset-config"]) {
44845
- const { existsSync: existsSync2, unlinkSync } = await import("fs");
44846
- if (existsSync2(cfg.path)) {
44969
+ const { existsSync: existsSync3, unlinkSync } = await import("fs");
44970
+ if (existsSync3(cfg.path)) {
44847
44971
  unlinkSync(cfg.path);
44848
44972
  console.log(`Configuration reset: ${formatPathWithTilde(cfg.path)}`);
44849
44973
  } else
@@ -44855,21 +44979,6 @@ Cancelled by user (Ctrl+C)
44855
44979
  console.log(`Default API URL set to: ${args["set-default-api-url"]}`);
44856
44980
  return;
44857
44981
  }
44858
- if (args["set-default-api-key"]) {
44859
- cfg.setDefaultApiKey(args["set-default-api-key"]);
44860
- console.log("Default API key set");
44861
- return;
44862
- }
44863
- if (args["set-defaults"]) {
44864
- const url = await readLine(`API URL [${cfg.data.default_api_url || DEFAULT_API_URL}]: `);
44865
- if (url)
44866
- cfg.setDefaultApiUrl(url);
44867
- const key = await readLine("API Key: ");
44868
- if (key)
44869
- cfg.setDefaultApiKey(key);
44870
- console.log("Defaults saved.");
44871
- return;
44872
- }
44873
44982
  const apiUrl = normalizeApiUrl(args["api-url"] || cfg.data.default_api_url || getEnv("API_URL") || DEFAULT_API_URL);
44874
44983
  async function deviceLogin() {
44875
44984
  const loginApi = new ApiClient(apiUrl, "");
@@ -44884,12 +44993,12 @@ Cancelled by user (Ctrl+C)
44884
44993
 
44885
44994
  `);
44886
44995
  try {
44887
- const { spawn } = await import("child_process");
44996
+ const { spawn: spawn2 } = await import("child_process");
44888
44997
  if (process.platform === "win32") {
44889
- spawn("cmd", ["/c", "start", "", url], { stdio: "ignore", detached: true }).unref();
44998
+ spawn2("cmd", ["/c", "start", "", url], { stdio: "ignore", detached: true }).unref();
44890
44999
  } else {
44891
45000
  const cmd = process.platform === "darwin" ? "open" : "xdg-open";
44892
- spawn(cmd, [url], { stdio: "ignore", detached: true }).unref();
45001
+ spawn2(cmd, [url], { stdio: "ignore", detached: true }).unref();
44893
45002
  }
44894
45003
  } catch {}
44895
45004
  process.stderr.write(`Waiting for approval (expires in ${Math.round(expiresIn / 60)}min)...
@@ -44902,7 +45011,7 @@ Cancelled by user (Ctrl+C)
44902
45011
  const poll = await loginApi.pollDeviceLogin(code);
44903
45012
  failures = 0;
44904
45013
  if (poll.status === "complete" && poll.apiKey) {
44905
- cfg.setDefaultApiKey(poll.apiKey);
45014
+ writeCredential(apiUrl, poll.apiKey);
44906
45015
  process.stderr.write(`${GREEN}\u2705 Login successful! API key saved.${RESET}
44907
45016
  `);
44908
45017
  return poll.apiKey;
@@ -44925,11 +45034,17 @@ Cancelled by user (Ctrl+C)
44925
45034
  await deviceLogin();
44926
45035
  return;
44927
45036
  }
44928
- let apiKey = args["api-key"] || cfg.data.default_api_key || getEnv("API_KEY") || "";
45037
+ let apiKey = args["api-key"] || getEnv("API_KEY") || readCredential(apiUrl) || "";
44929
45038
  if (!apiKey) {
44930
45039
  apiKey = await deviceLogin();
44931
45040
  }
44932
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
+ }
44933
45048
  if (args.inspect) {
44934
45049
  const todoId = args.inspect;
44935
45050
  const todo2 = await api.getTodo(todoId);
@@ -44995,7 +45110,11 @@ Cancelled by user (Ctrl+C)
44995
45110
  const ws2 = new FrontendWebSocket(apiUrl, apiKey);
44996
45111
  await ws2.connect();
44997
45112
  const autoApprove = !!args["dangerously-skip-permissions"];
44998
- const agent2 = todo2.agentSettings || { id: todo2.agentSettingsId };
45113
+ let agent2 = todo2.agentSettings || { id: todo2.agentSettingsId };
45114
+ if (args["allow-all"]) {
45115
+ const perms = agent2.permissions || { allow: [], ask: [], deny: [] };
45116
+ agent2 = { ...agent2, permissions: { ...perms, allow: [...perms.allow || [], "*:*"] } };
45117
+ }
44999
45118
  await watchTodo(ws2, todoId, projectId2, {
45000
45119
  json: !!args.json,
45001
45120
  autoApprove,
@@ -45005,7 +45124,7 @@ Cancelled by user (Ctrl+C)
45005
45124
  process.stderr.write(`
45006
45125
  ${"\u2500".repeat(40)}
45007
45126
  `);
45008
- await interactiveLoop(ws2, api, todoId, projectId2, agent2, !!args.json, autoApprove);
45127
+ await interactiveLoop(ws2, api, todoId, projectId2, agent2, !!args.json, autoApprove, cfg);
45009
45128
  }
45010
45129
  await ws2.close();
45011
45130
  }
@@ -45135,6 +45254,10 @@ Resumed todo: ${todoId}
45135
45254
  await ws.connect();
45136
45255
  if (args.model)
45137
45256
  agent = { ...agent, model: args.model };
45257
+ if (args["allow-all"]) {
45258
+ const perms = agent.permissions || { allow: [], ask: [], deny: [] };
45259
+ agent = { ...agent, permissions: { ...perms, allow: [...perms.allow || [], "*:*"] } };
45260
+ }
45138
45261
  cfg.addToHistory(content);
45139
45262
  const todo = await api.addMessage(projectId, content, agent);
45140
45263
  const actualTodoId = todo.id || crypto.randomUUID();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@todoforai/cli",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "todoai": "dist/todoai.js"