@todoforai/cli 0.1.3 → 0.1.5
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.
- package/README.md +18 -3
- package/dist/todoai.js +237 -76
- 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
|
|
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(
|
|
1903
|
+
this.matcherRe = langRe(join3(terminators), true);
|
|
1904
1904
|
this.lastIndex = 0;
|
|
1905
1905
|
}
|
|
1906
1906
|
exec(s) {
|
|
@@ -12960,7 +12960,7 @@ var require_http = __commonJS((exports, module) => {
|
|
|
12960
12960
|
return joined;
|
|
12961
12961
|
}
|
|
12962
12962
|
function http(hljs) {
|
|
12963
|
-
const
|
|
12963
|
+
const VERSION2 = "HTTP/(2|1\\.[01])";
|
|
12964
12964
|
const HEADER_NAME = /[A-Za-z][A-Za-z0-9-]*/;
|
|
12965
12965
|
const HEADER = {
|
|
12966
12966
|
className: "attribute",
|
|
@@ -12992,12 +12992,12 @@ var require_http = __commonJS((exports, module) => {
|
|
|
12992
12992
|
illegal: /\S/,
|
|
12993
12993
|
contains: [
|
|
12994
12994
|
{
|
|
12995
|
-
begin: "^(?=" +
|
|
12995
|
+
begin: "^(?=" + VERSION2 + " \\d{3})",
|
|
12996
12996
|
end: /$/,
|
|
12997
12997
|
contains: [
|
|
12998
12998
|
{
|
|
12999
12999
|
className: "meta",
|
|
13000
|
-
begin:
|
|
13000
|
+
begin: VERSION2
|
|
13001
13001
|
},
|
|
13002
13002
|
{
|
|
13003
13003
|
className: "number",
|
|
@@ -13011,7 +13011,7 @@ var require_http = __commonJS((exports, module) => {
|
|
|
13011
13011
|
}
|
|
13012
13012
|
},
|
|
13013
13013
|
{
|
|
13014
|
-
begin: "(?=^[A-Z]+ (.*?) " +
|
|
13014
|
+
begin: "(?=^[A-Z]+ (.*?) " + VERSION2 + "$)",
|
|
13015
13015
|
end: /$/,
|
|
13016
13016
|
contains: [
|
|
13017
13017
|
{
|
|
@@ -13023,7 +13023,7 @@ var require_http = __commonJS((exports, module) => {
|
|
|
13023
13023
|
},
|
|
13024
13024
|
{
|
|
13025
13025
|
className: "meta",
|
|
13026
|
-
begin:
|
|
13026
|
+
begin: VERSION2
|
|
13027
13027
|
},
|
|
13028
13028
|
{
|
|
13029
13029
|
className: "keyword",
|
|
@@ -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
|
|
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
|
|
@@ -43027,7 +43028,45 @@ class FrontendWebSocket {
|
|
|
43027
43028
|
|
|
43028
43029
|
// src/args.ts
|
|
43029
43030
|
import { parseArgs } from "util";
|
|
43031
|
+
// package.json
|
|
43032
|
+
var package_default = {
|
|
43033
|
+
name: "@todoforai/cli",
|
|
43034
|
+
version: "0.1.3",
|
|
43035
|
+
type: "module",
|
|
43036
|
+
bin: {
|
|
43037
|
+
todoai: "dist/todoai.js"
|
|
43038
|
+
},
|
|
43039
|
+
files: ["dist/todoai.js"],
|
|
43040
|
+
scripts: {
|
|
43041
|
+
build: "bun build src/index.ts --target=bun --outfile dist/todoai.js --external ws",
|
|
43042
|
+
prepublishOnly: "bun run build",
|
|
43043
|
+
start: "bun run src/index.ts",
|
|
43044
|
+
dev: "bun run src/index.ts",
|
|
43045
|
+
postinstall: "rm -rf node_modules/@todoforai/edge && ln -s ../../../edge/bun node_modules/@todoforai/edge"
|
|
43046
|
+
},
|
|
43047
|
+
dependencies: {
|
|
43048
|
+
"cli-highlight": "^2.1.11",
|
|
43049
|
+
"diff-match-patch": "^1.0.5",
|
|
43050
|
+
ws: "^8.18.0"
|
|
43051
|
+
},
|
|
43052
|
+
peerDependencies: {
|
|
43053
|
+
"@todoforai/edge": "file:../edge/bun"
|
|
43054
|
+
},
|
|
43055
|
+
peerDependenciesMeta: {
|
|
43056
|
+
"@todoforai/edge": {
|
|
43057
|
+
optional: true
|
|
43058
|
+
}
|
|
43059
|
+
},
|
|
43060
|
+
devDependencies: {
|
|
43061
|
+
"@types/ws": "^8.5.13",
|
|
43062
|
+
"@todoforai/edge": "file:../edge/bun",
|
|
43063
|
+
typescript: "^5.7.0"
|
|
43064
|
+
}
|
|
43065
|
+
};
|
|
43066
|
+
|
|
43067
|
+
// src/args.ts
|
|
43030
43068
|
var DEFAULT_API_URL = "https://api.todofor.ai";
|
|
43069
|
+
var VERSION = package_default.version;
|
|
43031
43070
|
function getEnv(name) {
|
|
43032
43071
|
return process.env[`TODOFORAI_${name}`] || process.env[`TODO4AI_${name}`] || "";
|
|
43033
43072
|
}
|
|
@@ -43045,11 +43084,13 @@ Usage:
|
|
|
43045
43084
|
todoai --resume <todo-id> # Resume specific todo
|
|
43046
43085
|
todoai --inspect <todo-id> # Print full chat log (read-only)
|
|
43047
43086
|
todoai --template <id> [--input k=v] # Start from a registry template
|
|
43087
|
+
todoai --list-agents # List available agents and exit
|
|
43048
43088
|
|
|
43049
43089
|
Options:
|
|
43050
43090
|
--path <dir> Workspace path (default: cwd)
|
|
43051
43091
|
--project <id> Project ID
|
|
43052
43092
|
--agent, -a <name> Agent name (partial match)
|
|
43093
|
+
--list-agents List available agents (name, id, workspace paths) and exit
|
|
43053
43094
|
--api-url <url> API URL
|
|
43054
43095
|
--api-key <key> API key
|
|
43055
43096
|
--inspect, -i <todo-id> Print full chat log (read-only, no interactive)
|
|
@@ -43061,14 +43102,14 @@ Options:
|
|
|
43061
43102
|
--dangerously-skip-permissions Auto-approve all blocks (for CI/benchmarks)
|
|
43062
43103
|
--allow-all Set permissions to allow all tools (no approval needed)
|
|
43063
43104
|
--no-watch Create todo and exit
|
|
43105
|
+
--no-edge Do not auto-spawn edge daemon
|
|
43064
43106
|
--json Output as JSON
|
|
43065
43107
|
--safe Validate API key upfront
|
|
43066
43108
|
--debug, -d Debug output
|
|
43067
43109
|
--show-config Show config
|
|
43068
|
-
--set-defaults Interactive defaults setup
|
|
43069
43110
|
--set-default-api-url Set default API URL
|
|
43070
|
-
--set-default-api-key Set default API key
|
|
43071
43111
|
--reset-config Reset config file
|
|
43112
|
+
--version, -v Print version and exit
|
|
43072
43113
|
--help, -h Show this help
|
|
43073
43114
|
`);
|
|
43074
43115
|
}
|
|
@@ -43079,6 +43120,7 @@ function parseCliArgs() {
|
|
|
43079
43120
|
path: { type: "string", default: "." },
|
|
43080
43121
|
project: { type: "string" },
|
|
43081
43122
|
agent: { type: "string", short: "a" },
|
|
43123
|
+
"list-agents": { type: "boolean", default: false },
|
|
43082
43124
|
"api-url": { type: "string" },
|
|
43083
43125
|
"api-key": { type: "string" },
|
|
43084
43126
|
inspect: { type: "string", short: "i" },
|
|
@@ -43090,16 +43132,16 @@ function parseCliArgs() {
|
|
|
43090
43132
|
"dangerously-skip-permissions": { type: "boolean", default: false },
|
|
43091
43133
|
"allow-all": { type: "boolean", default: false },
|
|
43092
43134
|
"no-watch": { type: "boolean", default: false },
|
|
43135
|
+
"no-edge": { type: "boolean", default: false },
|
|
43093
43136
|
json: { type: "boolean", default: false },
|
|
43094
43137
|
safe: { type: "boolean", default: false },
|
|
43095
43138
|
debug: { type: "boolean", short: "d", default: false },
|
|
43096
43139
|
"show-config": { type: "boolean", default: false },
|
|
43097
|
-
"set-defaults": { type: "boolean", default: false },
|
|
43098
43140
|
"set-default-api-url": { type: "string" },
|
|
43099
|
-
"set-default-api-key": { type: "string" },
|
|
43100
43141
|
"reset-config": { type: "boolean", default: false },
|
|
43101
43142
|
"config-path": { type: "string" },
|
|
43102
|
-
help: { type: "boolean", short: "h", default: false }
|
|
43143
|
+
help: { type: "boolean", short: "h", default: false },
|
|
43144
|
+
version: { type: "boolean", short: "v", default: false }
|
|
43103
43145
|
},
|
|
43104
43146
|
allowPositionals: true,
|
|
43105
43147
|
strict: false
|
|
@@ -43493,18 +43535,6 @@ function getConfigDir() {
|
|
|
43493
43535
|
const xdg = process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
|
|
43494
43536
|
return join(xdg, "todoai-cli");
|
|
43495
43537
|
}
|
|
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
43538
|
function defaultConfig() {
|
|
43509
43539
|
return {
|
|
43510
43540
|
default_project_id: null,
|
|
@@ -43513,7 +43543,6 @@ function defaultConfig() {
|
|
|
43513
43543
|
default_agent_settings: null,
|
|
43514
43544
|
default_agent_settings_updated_at: null,
|
|
43515
43545
|
default_api_url: null,
|
|
43516
|
-
default_api_key: null,
|
|
43517
43546
|
recent_projects: [],
|
|
43518
43547
|
recent_agents: [],
|
|
43519
43548
|
last_todo_id: null,
|
|
@@ -43538,8 +43567,7 @@ class ConfigStore {
|
|
|
43538
43567
|
return defaultConfig();
|
|
43539
43568
|
try {
|
|
43540
43569
|
const raw = JSON.parse(readFileSync(this.path, "utf-8"));
|
|
43541
|
-
|
|
43542
|
-
raw.default_api_key = deobfuscate(raw.default_api_key);
|
|
43570
|
+
delete raw.default_api_key;
|
|
43543
43571
|
return { ...defaultConfig(), ...raw };
|
|
43544
43572
|
} catch {
|
|
43545
43573
|
return defaultConfig();
|
|
@@ -43548,10 +43576,7 @@ class ConfigStore {
|
|
|
43548
43576
|
save() {
|
|
43549
43577
|
try {
|
|
43550
43578
|
mkdirSync(dirname(this.path), { recursive: true });
|
|
43551
|
-
|
|
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");
|
|
43579
|
+
writeFileSync(this.path, JSON.stringify(this.data, null, 2), "utf-8");
|
|
43555
43580
|
} catch {}
|
|
43556
43581
|
}
|
|
43557
43582
|
setDefaultProject(id, name) {
|
|
@@ -43575,10 +43600,6 @@ class ConfigStore {
|
|
|
43575
43600
|
this.data.default_api_url = url;
|
|
43576
43601
|
this.save();
|
|
43577
43602
|
}
|
|
43578
|
-
setDefaultApiKey(key) {
|
|
43579
|
-
this.data.default_api_key = key;
|
|
43580
|
-
this.save();
|
|
43581
|
-
}
|
|
43582
43603
|
addToHistory(input) {
|
|
43583
43604
|
const trimmed = input.trim();
|
|
43584
43605
|
if (!trimmed)
|
|
@@ -43595,6 +43616,30 @@ class ConfigStore {
|
|
|
43595
43616
|
}
|
|
43596
43617
|
}
|
|
43597
43618
|
|
|
43619
|
+
// src/credentials.ts
|
|
43620
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
43621
|
+
import { dirname as dirname2, join as join2 } from "path";
|
|
43622
|
+
import { homedir as homedir2 } from "os";
|
|
43623
|
+
var CREDENTIALS_PATH2 = join2(homedir2(), ".todoforai", "credentials.json");
|
|
43624
|
+
function read() {
|
|
43625
|
+
if (!existsSync2(CREDENTIALS_PATH2))
|
|
43626
|
+
return {};
|
|
43627
|
+
try {
|
|
43628
|
+
return JSON.parse(readFileSync2(CREDENTIALS_PATH2, "utf-8"));
|
|
43629
|
+
} catch {
|
|
43630
|
+
return {};
|
|
43631
|
+
}
|
|
43632
|
+
}
|
|
43633
|
+
function readCredential(apiUrl) {
|
|
43634
|
+
return read()[apiUrl] || "";
|
|
43635
|
+
}
|
|
43636
|
+
function writeCredential(apiUrl, apiKey) {
|
|
43637
|
+
const data = read();
|
|
43638
|
+
data[apiUrl] = apiKey;
|
|
43639
|
+
mkdirSync2(dirname2(CREDENTIALS_PATH2), { recursive: true });
|
|
43640
|
+
writeFileSync2(CREDENTIALS_PATH2, JSON.stringify(data, null, 2), "utf-8");
|
|
43641
|
+
}
|
|
43642
|
+
|
|
43598
43643
|
// src/logo.ts
|
|
43599
43644
|
var LETTERS = {
|
|
43600
43645
|
t: [" x ", "xxxx", " x ", " xll", " xll", " xxx"],
|
|
@@ -43913,6 +43958,12 @@ function splitShellCommands(input) {
|
|
|
43913
43958
|
const len = input.length;
|
|
43914
43959
|
while (i < len) {
|
|
43915
43960
|
const ch = input[i];
|
|
43961
|
+
if (ch === "\\" && i + 1 < len && input[i + 1] === `
|
|
43962
|
+
`) {
|
|
43963
|
+
current += " ";
|
|
43964
|
+
i += 2;
|
|
43965
|
+
continue;
|
|
43966
|
+
}
|
|
43916
43967
|
if (ch === "\\" && i + 1 < len) {
|
|
43917
43968
|
current += ch + input[i + 1];
|
|
43918
43969
|
i += 2;
|
|
@@ -44025,6 +44076,11 @@ function splitTokens(cmd) {
|
|
|
44025
44076
|
const len = cmd.length;
|
|
44026
44077
|
while (i < len) {
|
|
44027
44078
|
const ch = cmd[i];
|
|
44079
|
+
if (ch === "\\" && i + 1 < len && cmd[i + 1] === `
|
|
44080
|
+
`) {
|
|
44081
|
+
i += 2;
|
|
44082
|
+
continue;
|
|
44083
|
+
}
|
|
44028
44084
|
if (ch === "\\" && i + 1 < len) {
|
|
44029
44085
|
current += ch + cmd[i + 1];
|
|
44030
44086
|
i += 2;
|
|
@@ -44115,29 +44171,40 @@ function parseBashCmd(fullCmd) {
|
|
|
44115
44171
|
function generalizeBashCmd(fullCmd) {
|
|
44116
44172
|
return parseBashCmd(fullCmd).map((p) => buildBashPattern(p));
|
|
44117
44173
|
}
|
|
44174
|
+
function getBlockServerPrefix(block) {
|
|
44175
|
+
const agentPattern = typeof block.generalized_pattern === "string" ? block.generalized_pattern : "";
|
|
44176
|
+
const prefixMatch = agentPattern.match(/^(.*?:)BASH(?:\(|$)/);
|
|
44177
|
+
return prefixMatch ? prefixMatch[1] : "";
|
|
44178
|
+
}
|
|
44179
|
+
var BASH_LIKE_BLOCK_TYPES = new Set(["bash", "machine_exec"]);
|
|
44118
44180
|
function getBlockPatterns(block) {
|
|
44119
44181
|
if (Array.isArray(block.generalized_pattern))
|
|
44120
44182
|
return block.generalized_pattern;
|
|
44121
|
-
|
|
44122
|
-
|
|
44123
|
-
|
|
44124
|
-
const prefix =
|
|
44183
|
+
const agentPattern = typeof block.generalized_pattern === "string" ? block.generalized_pattern : "";
|
|
44184
|
+
const isBashLike = BASH_LIKE_BLOCK_TYPES.has(block.type.toLowerCase()) || /:BASH(\(|$)/.test(agentPattern);
|
|
44185
|
+
if (isBashLike && block.cmd) {
|
|
44186
|
+
const prefix = getBlockServerPrefix(block);
|
|
44125
44187
|
return generalizeBashCmd(block.cmd).map((p) => prefix + p);
|
|
44126
44188
|
}
|
|
44127
44189
|
if (block.generalized_pattern)
|
|
44128
44190
|
return [block.generalized_pattern];
|
|
44129
44191
|
return [`${block.type}(*)`];
|
|
44130
44192
|
}
|
|
44193
|
+
function getBlockServerId(block) {
|
|
44194
|
+
const prefix = getBlockServerPrefix(block);
|
|
44195
|
+
return prefix ? prefix.slice(0, -1) : "*";
|
|
44196
|
+
}
|
|
44131
44197
|
|
|
44132
44198
|
// ../packages/shared-fbe/src/permissionUtils.ts
|
|
44133
44199
|
function parsePattern(pattern) {
|
|
44134
44200
|
const colonIndex = pattern.indexOf(":");
|
|
44135
44201
|
if (colonIndex === -1)
|
|
44136
44202
|
return null;
|
|
44137
|
-
|
|
44138
|
-
|
|
44139
|
-
toolName: pattern
|
|
44140
|
-
}
|
|
44203
|
+
const serverId = pattern.slice(0, colonIndex);
|
|
44204
|
+
if (!/^[A-Za-z0-9_*-]+$/.test(serverId)) {
|
|
44205
|
+
return { serverId: "*", toolName: pattern };
|
|
44206
|
+
}
|
|
44207
|
+
return { serverId, toolName: pattern.slice(colonIndex + 1) };
|
|
44141
44208
|
}
|
|
44142
44209
|
function serverIdMatches(ruleServerId, targetServerId) {
|
|
44143
44210
|
if (ruleServerId === targetServerId)
|
|
@@ -44170,6 +44237,26 @@ function patternMatches(rulePattern, targetPattern) {
|
|
|
44170
44237
|
}
|
|
44171
44238
|
return false;
|
|
44172
44239
|
}
|
|
44240
|
+
function bashRuleMatchesCmd(rulePattern, serverId, cmd) {
|
|
44241
|
+
const rule = parsePattern(rulePattern);
|
|
44242
|
+
if (!rule)
|
|
44243
|
+
return false;
|
|
44244
|
+
if (!serverIdMatches(rule.serverId, serverId))
|
|
44245
|
+
return false;
|
|
44246
|
+
if (rule.toolName === "*" || rule.toolName === "BASH")
|
|
44247
|
+
return true;
|
|
44248
|
+
const m = rule.toolName.match(/^BASH\(cmd:\s*(.*)\)$/);
|
|
44249
|
+
if (!m)
|
|
44250
|
+
return false;
|
|
44251
|
+
let body = m[1];
|
|
44252
|
+
const isPrefix = body.endsWith(" *") || body.endsWith("*");
|
|
44253
|
+
if (isPrefix)
|
|
44254
|
+
body = body.replace(/\s*\*$/, "");
|
|
44255
|
+
const subs = splitShellCommands(cmd);
|
|
44256
|
+
if (subs.length === 0)
|
|
44257
|
+
return false;
|
|
44258
|
+
return subs.every((sub) => isPrefix ? sub === body || sub.startsWith(body + " ") : sub === body);
|
|
44259
|
+
}
|
|
44173
44260
|
function isPatternInList(list, pattern) {
|
|
44174
44261
|
if (!list)
|
|
44175
44262
|
return false;
|
|
@@ -44181,6 +44268,28 @@ function isPatternAllowed(permissions, pattern) {
|
|
|
44181
44268
|
function getNewPatterns(patterns, permissions) {
|
|
44182
44269
|
return patterns.filter((p) => !isPatternAllowed(permissions, p));
|
|
44183
44270
|
}
|
|
44271
|
+
function getBlockNewPatterns(block, permissions) {
|
|
44272
|
+
const allow = permissions?.allow ?? [];
|
|
44273
|
+
if (Array.isArray(block.generalized_pattern)) {
|
|
44274
|
+
return getNewPatterns(block.generalized_pattern, permissions);
|
|
44275
|
+
}
|
|
44276
|
+
const agentPattern = typeof block.generalized_pattern === "string" ? block.generalized_pattern : "";
|
|
44277
|
+
const isBash = (block.type.toLowerCase() === "bash" || agentPattern.includes(":BASH(")) && !!block.cmd;
|
|
44278
|
+
if (!isBash) {
|
|
44279
|
+
return getBlockPatterns(block).filter((p) => !isPatternAllowed(permissions, p));
|
|
44280
|
+
}
|
|
44281
|
+
const serverId = getBlockServerId(block);
|
|
44282
|
+
const prefix = serverId === "*" ? "" : `${serverId}:`;
|
|
44283
|
+
const rawSubs = splitShellCommands(block.cmd);
|
|
44284
|
+
const pairs = [];
|
|
44285
|
+
for (const raw of rawSubs) {
|
|
44286
|
+
const parsed = parseBashCmd(raw);
|
|
44287
|
+
if (parsed.length === 0)
|
|
44288
|
+
continue;
|
|
44289
|
+
pairs.push({ raw, pattern: prefix + buildBashPattern(parsed[0]) });
|
|
44290
|
+
}
|
|
44291
|
+
return pairs.filter(({ raw, pattern }) => !isPatternAllowed(permissions, pattern) && !allow.some((rule) => bashRuleMatchesCmd(rule, serverId, raw))).map(({ pattern }) => pattern);
|
|
44292
|
+
}
|
|
44184
44293
|
|
|
44185
44294
|
// src/diff-view.ts
|
|
44186
44295
|
var import_diff_match_patch = __toESM(require_diff_match_patch(), 1);
|
|
@@ -44428,12 +44537,14 @@ function renderDiff(originalContent, modifiedContent, filePath) {
|
|
|
44428
44537
|
var diffStoreByWs = new WeakMap;
|
|
44429
44538
|
function classifyBlock(info) {
|
|
44430
44539
|
const inner = (info.block_type || "").toLowerCase();
|
|
44431
|
-
if (["create", "createfile"].includes(inner))
|
|
44540
|
+
if (["create", "createfile", "write"].includes(inner))
|
|
44432
44541
|
return "create";
|
|
44433
44542
|
if (["modify", "modifyfile", "update", "edit"].includes(inner))
|
|
44434
44543
|
return "edit";
|
|
44435
44544
|
if (["catfile", "read", "readfile"].includes(inner))
|
|
44436
44545
|
return "read";
|
|
44546
|
+
if (["search", "grep"].includes(inner))
|
|
44547
|
+
return "search";
|
|
44437
44548
|
if (inner === "mcp")
|
|
44438
44549
|
return "mcp";
|
|
44439
44550
|
if (["shell", "bash"].includes(inner) || info.cmd)
|
|
@@ -44441,7 +44552,7 @@ function classifyBlock(info) {
|
|
|
44441
44552
|
return "unknown";
|
|
44442
44553
|
}
|
|
44443
44554
|
function blockDisplay(info) {
|
|
44444
|
-
const labels = { create: "File", edit: "Edit", read: "Read File", mcp: "MCP", shell: "Shell" };
|
|
44555
|
+
const labels = { create: "File", edit: "Edit", read: "Read File", search: "Search", mcp: "MCP", shell: "Shell" };
|
|
44445
44556
|
const kind = classifyBlock(info);
|
|
44446
44557
|
const typeLabel = labels[kind] || info.block_type || "Tool";
|
|
44447
44558
|
const skipKeys = new Set([
|
|
@@ -44587,12 +44698,11 @@ ${YELLOW}\u26A0 ${blocks.length} action(s) awaiting approval:${RESET}
|
|
|
44587
44698
|
process.stderr.write(renderDiff(diff.originalContent, diff.modifiedContent, filePath));
|
|
44588
44699
|
}
|
|
44589
44700
|
}
|
|
44590
|
-
const
|
|
44701
|
+
const newPatterns = blocks.flatMap((bi) => getBlockNewPatterns({
|
|
44591
44702
|
type: bi.block_type || "unknown",
|
|
44592
44703
|
generalized_pattern: bi.generalized_pattern,
|
|
44593
44704
|
cmd: bi.cmd
|
|
44594
|
-
}));
|
|
44595
|
-
const newPatterns = getNewPatterns(allPatterns, opts.agentSettings?.permissions);
|
|
44705
|
+
}, opts.agentSettings?.permissions));
|
|
44596
44706
|
const stripPrefix = (p) => p.replace(/^todoai_(edge|cloud):/, "");
|
|
44597
44707
|
const patternHint = newPatterns.length ? ` ${DIM}${newPatterns.map(stripPrefix).join(", ")}${RESET}` : "";
|
|
44598
44708
|
try {
|
|
@@ -44605,11 +44715,11 @@ ${YELLOW}\u26A0 ${blocks.length} action(s) awaiting approval:${RESET}
|
|
|
44605
44715
|
for (const bi of blocks) {
|
|
44606
44716
|
let patterns;
|
|
44607
44717
|
if (response === "r") {
|
|
44608
|
-
patterns =
|
|
44718
|
+
patterns = getBlockNewPatterns({
|
|
44609
44719
|
type: bi.block_type || "unknown",
|
|
44610
44720
|
generalized_pattern: bi.generalized_pattern,
|
|
44611
44721
|
cmd: bi.cmd
|
|
44612
|
-
});
|
|
44722
|
+
}, opts.agentSettings?.permissions);
|
|
44613
44723
|
if (patterns.length > 0) {
|
|
44614
44724
|
process.stderr.write(` ${GREEN}\u2713 Remembering: ${patterns.map(stripPrefix).join(", ")}${RESET}
|
|
44615
44725
|
`);
|
|
@@ -44747,10 +44857,62 @@ ${DIM}[todo:status] ${status}${RESET}
|
|
|
44747
44857
|
}
|
|
44748
44858
|
}
|
|
44749
44859
|
|
|
44860
|
+
// src/list-agents.ts
|
|
44861
|
+
async function listAgentsCommand(api, opts) {
|
|
44862
|
+
const agents = await api.listAgentSettings();
|
|
44863
|
+
if (opts.json) {
|
|
44864
|
+
console.log(JSON.stringify(agents, null, 2));
|
|
44865
|
+
return;
|
|
44866
|
+
}
|
|
44867
|
+
if (!agents.length) {
|
|
44868
|
+
process.stderr.write(`No agents found.
|
|
44869
|
+
`);
|
|
44870
|
+
return;
|
|
44871
|
+
}
|
|
44872
|
+
const rows = agents.map((a) => ({
|
|
44873
|
+
name: getDisplayName(a),
|
|
44874
|
+
id: getItemId(a),
|
|
44875
|
+
model: a.model || "",
|
|
44876
|
+
paths: getAgentWorkspacePaths(a).map(opts.formatPath)
|
|
44877
|
+
}));
|
|
44878
|
+
const nameW = Math.max(4, ...rows.map((r) => r.name.length));
|
|
44879
|
+
const modelW = Math.max(5, ...rows.map((r) => r.model.length));
|
|
44880
|
+
process.stderr.write(`${DIM}${"NAME".padEnd(nameW)} ${"MODEL".padEnd(modelW)} ID${" ".repeat(34)}PATHS${RESET}
|
|
44881
|
+
`);
|
|
44882
|
+
for (const r of rows) {
|
|
44883
|
+
process.stderr.write(`${BRAND}${r.name.padEnd(nameW)}${RESET} ${CYAN}${r.model.padEnd(modelW)}${RESET} ${DIM}${r.id}${RESET} ${r.paths.join(", ")}
|
|
44884
|
+
`);
|
|
44885
|
+
}
|
|
44886
|
+
}
|
|
44887
|
+
|
|
44888
|
+
// src/ensure-edge.ts
|
|
44889
|
+
import { spawn } from "child_process";
|
|
44890
|
+
import fs from "fs";
|
|
44891
|
+
import path2 from "path";
|
|
44892
|
+
import os2 from "os";
|
|
44893
|
+
function ensureEdgeRunning(apiUrl, apiKey) {
|
|
44894
|
+
const logDir = path2.join(os2.homedir(), ".todoforai");
|
|
44895
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
44896
|
+
const logFile = path2.join(logDir, "edge.log");
|
|
44897
|
+
const out = fs.openSync(logFile, "a");
|
|
44898
|
+
const child = spawn("bunx", ["@todoforai/edge", "--api-url", apiUrl, "--api-key", apiKey], {
|
|
44899
|
+
detached: true,
|
|
44900
|
+
stdio: ["ignore", out, out]
|
|
44901
|
+
});
|
|
44902
|
+
child.unref();
|
|
44903
|
+
const pid = child.pid;
|
|
44904
|
+
setTimeout(() => {
|
|
44905
|
+
try {
|
|
44906
|
+
process.kill(pid, 0);
|
|
44907
|
+
console.error(`\x1B[2mStarted edge daemon (pid ${pid}), logs: ${logFile.replace(os2.homedir(), "~")}\x1B[0m`);
|
|
44908
|
+
} catch {}
|
|
44909
|
+
}, 500);
|
|
44910
|
+
}
|
|
44911
|
+
|
|
44750
44912
|
// src/index.ts
|
|
44751
|
-
function formatPathWithTilde(
|
|
44752
|
-
const home =
|
|
44753
|
-
return
|
|
44913
|
+
function formatPathWithTilde(path3) {
|
|
44914
|
+
const home = homedir3();
|
|
44915
|
+
return path3.startsWith(home) ? path3.replace(home, "~") : path3;
|
|
44754
44916
|
}
|
|
44755
44917
|
function getFrontendUrl(apiUrl, projectId, todoId) {
|
|
44756
44918
|
if (apiUrl.includes("localhost:4000") || apiUrl.includes("127.0.0.1:4000")) {
|
|
@@ -44833,6 +44995,10 @@ Cancelled by user (Ctrl+C)
|
|
|
44833
44995
|
process.exit(130);
|
|
44834
44996
|
});
|
|
44835
44997
|
const { values: args, positionals } = parseCliArgs();
|
|
44998
|
+
if (args.version) {
|
|
44999
|
+
console.log(VERSION);
|
|
45000
|
+
process.exit(0);
|
|
45001
|
+
}
|
|
44836
45002
|
if (args.help) {
|
|
44837
45003
|
printUsage();
|
|
44838
45004
|
process.exit(0);
|
|
@@ -44844,8 +45010,8 @@ Cancelled by user (Ctrl+C)
|
|
|
44844
45010
|
return;
|
|
44845
45011
|
}
|
|
44846
45012
|
if (args["reset-config"]) {
|
|
44847
|
-
const { existsSync:
|
|
44848
|
-
if (
|
|
45013
|
+
const { existsSync: existsSync3, unlinkSync } = await import("fs");
|
|
45014
|
+
if (existsSync3(cfg.path)) {
|
|
44849
45015
|
unlinkSync(cfg.path);
|
|
44850
45016
|
console.log(`Configuration reset: ${formatPathWithTilde(cfg.path)}`);
|
|
44851
45017
|
} else
|
|
@@ -44857,21 +45023,6 @@ Cancelled by user (Ctrl+C)
|
|
|
44857
45023
|
console.log(`Default API URL set to: ${args["set-default-api-url"]}`);
|
|
44858
45024
|
return;
|
|
44859
45025
|
}
|
|
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
45026
|
const apiUrl = normalizeApiUrl(args["api-url"] || cfg.data.default_api_url || getEnv("API_URL") || DEFAULT_API_URL);
|
|
44876
45027
|
async function deviceLogin() {
|
|
44877
45028
|
const loginApi = new ApiClient(apiUrl, "");
|
|
@@ -44886,12 +45037,12 @@ Cancelled by user (Ctrl+C)
|
|
|
44886
45037
|
|
|
44887
45038
|
`);
|
|
44888
45039
|
try {
|
|
44889
|
-
const { spawn } = await import("child_process");
|
|
45040
|
+
const { spawn: spawn2 } = await import("child_process");
|
|
44890
45041
|
if (process.platform === "win32") {
|
|
44891
|
-
|
|
45042
|
+
spawn2("cmd", ["/c", "start", "", url], { stdio: "ignore", detached: true }).unref();
|
|
44892
45043
|
} else {
|
|
44893
45044
|
const cmd = process.platform === "darwin" ? "open" : "xdg-open";
|
|
44894
|
-
|
|
45045
|
+
spawn2(cmd, [url], { stdio: "ignore", detached: true }).unref();
|
|
44895
45046
|
}
|
|
44896
45047
|
} catch {}
|
|
44897
45048
|
process.stderr.write(`Waiting for approval (expires in ${Math.round(expiresIn / 60)}min)...
|
|
@@ -44904,7 +45055,7 @@ Cancelled by user (Ctrl+C)
|
|
|
44904
45055
|
const poll = await loginApi.pollDeviceLogin(code);
|
|
44905
45056
|
failures = 0;
|
|
44906
45057
|
if (poll.status === "complete" && poll.apiKey) {
|
|
44907
|
-
|
|
45058
|
+
writeCredential(apiUrl, poll.apiKey);
|
|
44908
45059
|
process.stderr.write(`${GREEN}\u2705 Login successful! API key saved.${RESET}
|
|
44909
45060
|
`);
|
|
44910
45061
|
return poll.apiKey;
|
|
@@ -44927,11 +45078,15 @@ Cancelled by user (Ctrl+C)
|
|
|
44927
45078
|
await deviceLogin();
|
|
44928
45079
|
return;
|
|
44929
45080
|
}
|
|
44930
|
-
let apiKey = args["api-key"] ||
|
|
45081
|
+
let apiKey = args["api-key"] || getEnv("API_KEY") || readCredential(apiUrl) || "";
|
|
44931
45082
|
if (!apiKey) {
|
|
44932
45083
|
apiKey = await deviceLogin();
|
|
44933
45084
|
}
|
|
44934
45085
|
const api = new ApiClient(apiUrl, apiKey);
|
|
45086
|
+
if (args["list-agents"]) {
|
|
45087
|
+
await listAgentsCommand(api, { json: !!args.json, formatPath: formatPathWithTilde });
|
|
45088
|
+
return;
|
|
45089
|
+
}
|
|
44935
45090
|
if (args.inspect) {
|
|
44936
45091
|
const todoId = args.inspect;
|
|
44937
45092
|
const todo2 = await api.getTodo(todoId);
|
|
@@ -44941,6 +45096,8 @@ Cancelled by user (Ctrl+C)
|
|
|
44941
45096
|
if (process.stderr.isTTY)
|
|
44942
45097
|
printLogo();
|
|
44943
45098
|
if (args.template) {
|
|
45099
|
+
if (!args["no-edge"])
|
|
45100
|
+
ensureEdgeRunning(apiUrl, apiKey);
|
|
44944
45101
|
const templateId = args.template;
|
|
44945
45102
|
const inputValues = {};
|
|
44946
45103
|
for (const kv of args.input || []) {
|
|
@@ -45028,6 +45185,8 @@ ${"\u2500".repeat(40)}
|
|
|
45028
45185
|
`);
|
|
45029
45186
|
}
|
|
45030
45187
|
if (args.resume || args.continue) {
|
|
45188
|
+
if (!args["no-edge"])
|
|
45189
|
+
ensureEdgeRunning(apiUrl, apiKey);
|
|
45031
45190
|
const todoId = args.resume || cfg.data.last_todo_id;
|
|
45032
45191
|
if (!todoId) {
|
|
45033
45192
|
process.stderr.write(`Error: No recent todo found
|
|
@@ -45095,6 +45254,8 @@ Resumed todo: ${todoId}
|
|
|
45095
45254
|
}
|
|
45096
45255
|
process.stderr.write(`${DIM}Tip: ${randomTip()}${RESET}
|
|
45097
45256
|
`);
|
|
45257
|
+
if (!args["no-edge"])
|
|
45258
|
+
ensureEdgeRunning(apiUrl, apiKey);
|
|
45098
45259
|
let content;
|
|
45099
45260
|
if (positionals.length > 0) {
|
|
45100
45261
|
content = positionals.join(" ");
|