@todoforai/cli 0.1.14 → 0.1.15
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 +15 -15
- package/dist/todoai.js +218 -76
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# todoforai-cli CLI
|
|
2
2
|
|
|
3
3
|
CLI for [TODOforAI](https://todofor.ai) — create, watch, and inspect AI-powered todos.
|
|
4
4
|
|
|
@@ -10,11 +10,11 @@ bun install -g @todoforai/cli
|
|
|
10
10
|
|
|
11
11
|
## Setup
|
|
12
12
|
|
|
13
|
-
Just run `
|
|
13
|
+
Just run `todoforai-cli` — 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
14
|
|
|
15
15
|
```bash
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
todoforai-cli # prompts device login if no key found
|
|
17
|
+
todoforai-cli login # explicit login
|
|
18
18
|
```
|
|
19
19
|
|
|
20
20
|
API URL resolution: `--api-url` flag → `TODOFORAI_API_URL` env → `https://api.todofor.ai`.
|
|
@@ -25,7 +25,7 @@ Project, agent, and last-todo state are stored **per API URL** under `per_api_ur
|
|
|
25
25
|
|
|
26
26
|
## Edge daemon
|
|
27
27
|
|
|
28
|
-
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 `
|
|
28
|
+
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 `todoforai-cli` 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`.
|
|
29
29
|
|
|
30
30
|
Disable with `--no-edge` if you manage the edge yourself (e.g. systemd, separate terminal).
|
|
31
31
|
|
|
@@ -34,18 +34,18 @@ Disable with `--no-edge` if you manage the edge yourself (e.g. systemd, separate
|
|
|
34
34
|
### Create a todo from a prompt
|
|
35
35
|
|
|
36
36
|
```bash
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
echo "content" |
|
|
40
|
-
|
|
37
|
+
todoforai-cli "Fix the login bug"
|
|
38
|
+
todoforai-cli -n "Quick task" # non-interactive (run and exit)
|
|
39
|
+
echo "content" | todoforai-cli # pipe from stdin
|
|
40
|
+
todoforai-cli --path /my/project "Fix bug" # explicit workspace
|
|
41
41
|
```
|
|
42
42
|
|
|
43
43
|
### Start from a registry template
|
|
44
44
|
|
|
45
45
|
```bash
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
todoforai-cli --template alternativeto-listing # interactive input prompts
|
|
47
|
+
todoforai-cli --template f5bot-monitoring-setup --input "monitoring_details=My Brand" # with inputs
|
|
48
|
+
todoforai-cli --template f5bot-monitoring-setup --no-watch --json # create only
|
|
49
49
|
```
|
|
50
50
|
|
|
51
51
|
When inputs are missing, the CLI prompts interactively (unless `-n`).
|
|
@@ -53,7 +53,7 @@ When inputs are missing, the CLI prompts interactively (unless `-n`).
|
|
|
53
53
|
### Inspect a todo (read-only)
|
|
54
54
|
|
|
55
55
|
```bash
|
|
56
|
-
|
|
56
|
+
todoforai-cli --inspect <todo-id>
|
|
57
57
|
```
|
|
58
58
|
|
|
59
59
|
Prints the full chat log: messages, tool calls (type, status, path/cmd), results, and errors. No logo, no interactive mode.
|
|
@@ -61,8 +61,8 @@ Prints the full chat log: messages, tool calls (type, status, path/cmd), results
|
|
|
61
61
|
### Resume / continue
|
|
62
62
|
|
|
63
63
|
```bash
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
todoforai-cli -c # continue most recent todo
|
|
65
|
+
todoforai-cli --resume <todo-id> # resume specific todo
|
|
66
66
|
```
|
|
67
67
|
|
|
68
68
|
## All Options
|
package/dist/todoai.js
CHANGED
|
@@ -42752,6 +42752,27 @@ class ApiClient {
|
|
|
42752
42752
|
throw new Error(`API ${method} ${endpoint} failed: ${res.status} ${await res.text()}`);
|
|
42753
42753
|
return res.json();
|
|
42754
42754
|
}
|
|
42755
|
+
async trpcQuery(path2, input) {
|
|
42756
|
+
const base = this.apiUrl.replace(/\/api\/v1\/?$/, "").replace(/\/$/, "");
|
|
42757
|
+
const params = new URLSearchParams({
|
|
42758
|
+
batch: "1",
|
|
42759
|
+
input: JSON.stringify({ 0: input })
|
|
42760
|
+
});
|
|
42761
|
+
const res = await fetch(`${base}/trpc/api/${path2}?${params}`, {
|
|
42762
|
+
method: "GET",
|
|
42763
|
+
headers: this.headers,
|
|
42764
|
+
signal: AbortSignal.timeout(30000)
|
|
42765
|
+
});
|
|
42766
|
+
const text = await res.text();
|
|
42767
|
+
if (!res.ok)
|
|
42768
|
+
throw new Error(`API tRPC ${path2} failed: ${res.status} ${text}`);
|
|
42769
|
+
const payload = JSON.parse(text);
|
|
42770
|
+
const item = Array.isArray(payload) ? payload[0] : payload;
|
|
42771
|
+
if (item?.error)
|
|
42772
|
+
throw new Error(`API tRPC ${path2} failed: ${item.error.message || JSON.stringify(item.error)}`);
|
|
42773
|
+
const data = item?.result?.data;
|
|
42774
|
+
return data?.json !== undefined ? data.json : data;
|
|
42775
|
+
}
|
|
42755
42776
|
async validateApiKey() {
|
|
42756
42777
|
if (!this.apiKey)
|
|
42757
42778
|
return { valid: false, error: "No API key provided" };
|
|
@@ -42790,9 +42811,10 @@ class ApiClient {
|
|
|
42790
42811
|
createTodo(projectId, content, agentSettings) {
|
|
42791
42812
|
return this.request("POST", `/api/v1/projects/${projectId}/todos`, { content, agentSettings });
|
|
42792
42813
|
}
|
|
42793
|
-
listTodos(projectId) {
|
|
42794
|
-
|
|
42795
|
-
|
|
42814
|
+
listTodos(projectId, opts) {
|
|
42815
|
+
if (!projectId)
|
|
42816
|
+
return this.request("GET", "/api/v1/todos");
|
|
42817
|
+
return this.trpcQuery("todo.list", { projectId, ...opts });
|
|
42796
42818
|
}
|
|
42797
42819
|
getTodo(todoId) {
|
|
42798
42820
|
return this.request("GET", `/api/v1/todos/${todoId}`);
|
|
@@ -43107,8 +43129,8 @@ var package_default = {
|
|
|
43107
43129
|
version: "0.1.3",
|
|
43108
43130
|
type: "module",
|
|
43109
43131
|
bin: {
|
|
43110
|
-
|
|
43111
|
-
|
|
43132
|
+
"todoforai-cli": "dist/todoai.js",
|
|
43133
|
+
todoai: "dist/todoai.js"
|
|
43112
43134
|
},
|
|
43113
43135
|
files: ["dist/todoai.js"],
|
|
43114
43136
|
scripts: {
|
|
@@ -43147,6 +43169,7 @@ var TodoStatus;
|
|
|
43147
43169
|
TodoStatus2["POSTPONED"] = "POSTPONED";
|
|
43148
43170
|
TodoStatus2["REVIEW_REQUESTED"] = "REVIEW_REQUESTED";
|
|
43149
43171
|
TodoStatus2["RUNNING"] = "RUNNING";
|
|
43172
|
+
TodoStatus2["WAITING_FOR_USER"] = "WAITING_FOR_USER";
|
|
43150
43173
|
TodoStatus2["COMPACTING"] = "COMPACTING";
|
|
43151
43174
|
TodoStatus2["STOPPING"] = "STOPPING";
|
|
43152
43175
|
TodoStatus2["READY"] = "READY";
|
|
@@ -43451,6 +43474,15 @@ var SERVER_TO_FRONTENDS = {
|
|
|
43451
43474
|
},
|
|
43452
43475
|
functions: {}
|
|
43453
43476
|
};
|
|
43477
|
+
// ../packages/shared-fbe/src/templateBody.ts
|
|
43478
|
+
var USER_HEADINGS = new Set([
|
|
43479
|
+
"your task",
|
|
43480
|
+
"your tasks",
|
|
43481
|
+
"tasks",
|
|
43482
|
+
"task",
|
|
43483
|
+
"steps",
|
|
43484
|
+
"data needed"
|
|
43485
|
+
]);
|
|
43454
43486
|
// ../packages/shared-fbe/src/bashPatterns.ts
|
|
43455
43487
|
function splitShellCommands(input) {
|
|
43456
43488
|
const parts = [];
|
|
@@ -43940,24 +43972,24 @@ function getEnv(name) {
|
|
|
43940
43972
|
}
|
|
43941
43973
|
function printUsage() {
|
|
43942
43974
|
process.stderr.write(`
|
|
43943
|
-
|
|
43975
|
+
todoforai-cli \u2014 TODOforAI CLI (Bun)
|
|
43944
43976
|
|
|
43945
43977
|
Usage:
|
|
43946
|
-
|
|
43947
|
-
|
|
43948
|
-
|
|
43949
|
-
echo "content" |
|
|
43950
|
-
|
|
43951
|
-
|
|
43952
|
-
|
|
43953
|
-
|
|
43954
|
-
|
|
43955
|
-
|
|
43956
|
-
|
|
43957
|
-
|
|
43958
|
-
|
|
43959
|
-
|
|
43960
|
-
|
|
43978
|
+
todoforai-cli login # Browser-based device auth
|
|
43979
|
+
todoforai-cli "prompt text" # Prompt as argument
|
|
43980
|
+
todoforai-cli -n "Quick task" # Non-interactive (run and exit)
|
|
43981
|
+
echo "content" | todoforai-cli # Pipe from stdin
|
|
43982
|
+
todoforai-cli --path /my/project "Fix bug" # Explicit workspace path
|
|
43983
|
+
todoforai-cli -c ["prompt"] # Resume last todo (optional prompt sent on attach)
|
|
43984
|
+
todoforai-cli --resume <todo-id> ["prompt"] # Resume specific todo (optional prompt sent on attach)
|
|
43985
|
+
todoforai-cli --inspect <todo-id>[@<slice>] # Read chat log. <slice> = -3:, :1, 5:10, 7 (Python-style)
|
|
43986
|
+
todoforai-cli --template <id> [--input k=v] # Start from a registry template
|
|
43987
|
+
todoforai-cli --list-agents # List available agents and exit
|
|
43988
|
+
todoforai-cli agent update <agent> model=<model> # Update agent settings (see 'agent --help')
|
|
43989
|
+
todoforai-cli list [-n 30] [--cursor N] [--all] [--status S] # List todos (paginated); see 'list --help'
|
|
43990
|
+
todoforai-cli status <todo-id> <STATUS> # Update a todo's status (run 'status --help' for the full list)
|
|
43991
|
+
todoforai-cli delete <todo-id> # Permanently delete a todo
|
|
43992
|
+
todoforai-cli addmessage <todo-id> "text" # Add a message to an existing todo
|
|
43961
43993
|
|
|
43962
43994
|
Options:
|
|
43963
43995
|
--path <dir> Workspace path (default: cwd)
|
|
@@ -43999,7 +44031,7 @@ var STATUS_HELP = {
|
|
|
43999
44031
|
};
|
|
44000
44032
|
function printStatusHelp() {
|
|
44001
44033
|
process.stderr.write(`
|
|
44002
|
-
|
|
44034
|
+
todoforai-cli status <todo-id> <STATUS>
|
|
44003
44035
|
|
|
44004
44036
|
Common statuses:
|
|
44005
44037
|
${Object.entries(STATUS_HELP).map(([s, d]) => ` ${s.padEnd(18)}${d}`).join(`
|
|
@@ -45027,6 +45059,28 @@ function getItemId(item) {
|
|
|
45027
45059
|
return item.project.id;
|
|
45028
45060
|
return item?.id || "";
|
|
45029
45061
|
}
|
|
45062
|
+
function resolveAgentMatch(agents, query) {
|
|
45063
|
+
const byId = agents.find((a) => getItemId(a) === query);
|
|
45064
|
+
if (byId)
|
|
45065
|
+
return { match: byId };
|
|
45066
|
+
const q = query.toLowerCase();
|
|
45067
|
+
const esc = q.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
45068
|
+
const boundary = new RegExp(`\\b${esc}`);
|
|
45069
|
+
const name = (a) => getDisplayName(a).toLowerCase();
|
|
45070
|
+
const tiers = [
|
|
45071
|
+
agents.filter((a) => name(a) === q),
|
|
45072
|
+
agents.filter((a) => name(a).startsWith(q)),
|
|
45073
|
+
agents.filter((a) => boundary.test(name(a))),
|
|
45074
|
+
agents.filter((a) => name(a).includes(q))
|
|
45075
|
+
];
|
|
45076
|
+
for (const tier of tiers) {
|
|
45077
|
+
if (tier.length === 1)
|
|
45078
|
+
return { match: tier[0] };
|
|
45079
|
+
if (tier.length > 1)
|
|
45080
|
+
return { ambiguous: tier };
|
|
45081
|
+
}
|
|
45082
|
+
return {};
|
|
45083
|
+
}
|
|
45030
45084
|
function terminalLine(prompt) {
|
|
45031
45085
|
const rl = createInterface2({ input: process.stdin, output: process.stderr });
|
|
45032
45086
|
return new Promise((resolve3) => {
|
|
@@ -45758,12 +45812,12 @@ async function listAgentsCommand(api, opts) {
|
|
|
45758
45812
|
// src/agent-command.ts
|
|
45759
45813
|
function printAgentHelp() {
|
|
45760
45814
|
process.stderr.write(`
|
|
45761
|
-
|
|
45815
|
+
todoforai-cli agent \u2014 inspect and update agent settings
|
|
45762
45816
|
|
|
45763
45817
|
Usage:
|
|
45764
|
-
|
|
45765
|
-
|
|
45766
|
-
|
|
45818
|
+
todoforai-cli agent list List agents (name, model, id, paths)
|
|
45819
|
+
todoforai-cli agent get <agent> Show a single agent's settings
|
|
45820
|
+
todoforai-cli agent update <agent> <field=value>\u2026 Update one or more settings
|
|
45767
45821
|
|
|
45768
45822
|
<agent> is a name or id (unique partial name also works).
|
|
45769
45823
|
Fields map directly to agent settings; values are parsed as JSON when possible
|
|
@@ -45776,9 +45830,9 @@ Fields map directly to agent settings; values are parsed as JSON when possible
|
|
|
45776
45830
|
name agent display name
|
|
45777
45831
|
|
|
45778
45832
|
Examples:
|
|
45779
|
-
|
|
45780
|
-
|
|
45781
|
-
|
|
45833
|
+
todoforai-cli agent update <agent> model=claude
|
|
45834
|
+
todoforai-cli agent update <agent> model=anthropic:anthropic/claude-opus-4.8 temperature=0.5
|
|
45835
|
+
todoforai-cli agent update <agent> sysmsg="You are a terse video editor."
|
|
45782
45836
|
`);
|
|
45783
45837
|
}
|
|
45784
45838
|
var FIELD_ALIASES = {
|
|
@@ -45804,20 +45858,13 @@ function parseAssignment(arg) {
|
|
|
45804
45858
|
}
|
|
45805
45859
|
}
|
|
45806
45860
|
function resolveAgent(agents, query) {
|
|
45807
|
-
const
|
|
45808
|
-
if (
|
|
45809
|
-
return
|
|
45810
|
-
|
|
45811
|
-
|
|
45812
|
-
if (exact.length === 1)
|
|
45813
|
-
return exact[0];
|
|
45814
|
-
const partial = agents.filter((a) => getDisplayName(a).toLowerCase().includes(q));
|
|
45815
|
-
if (partial.length === 1)
|
|
45816
|
-
return partial[0];
|
|
45817
|
-
if (partial.length > 1) {
|
|
45818
|
-
process.stderr.write(`${RED}Ambiguous agent '${query}', matches:${RESET}
|
|
45861
|
+
const { match, ambiguous } = resolveAgentMatch(agents, query);
|
|
45862
|
+
if (match)
|
|
45863
|
+
return match;
|
|
45864
|
+
if (ambiguous) {
|
|
45865
|
+
process.stderr.write(`${RED}Ambiguous agent '${query}' \u2014 ${ambiguous.length} matches. Re-run with the exact id:${RESET}
|
|
45819
45866
|
`);
|
|
45820
|
-
for (const a of
|
|
45867
|
+
for (const a of ambiguous)
|
|
45821
45868
|
process.stderr.write(` ${getDisplayName(a)} ${DIM}${getItemId(a)}${RESET}
|
|
45822
45869
|
`);
|
|
45823
45870
|
process.exit(2);
|
|
@@ -45853,7 +45900,7 @@ async function agentCommand(api, positionals, args, formatPath) {
|
|
|
45853
45900
|
const query = positionals[2];
|
|
45854
45901
|
const assignments = positionals.slice(3);
|
|
45855
45902
|
if (!query || !assignments.length) {
|
|
45856
|
-
process.stderr.write(`${RED}Usage:
|
|
45903
|
+
process.stderr.write(`${RED}Usage: todoforai-cli agent update <name|id> <field=value>\u2026${RESET}
|
|
45857
45904
|
`);
|
|
45858
45905
|
process.exit(2);
|
|
45859
45906
|
}
|
|
@@ -45907,34 +45954,87 @@ var CLOSED = new Set([
|
|
|
45907
45954
|
]);
|
|
45908
45955
|
var VALID_STATUS = new Set(Object.values(TodoStatus));
|
|
45909
45956
|
var isOpen = (s) => !CLOSED.has(s);
|
|
45910
|
-
function
|
|
45957
|
+
function printListTodosHelp() {
|
|
45911
45958
|
process.stderr.write(`
|
|
45912
|
-
|
|
45959
|
+
todoforai-cli list \u2014 list todos in a project (recent first)
|
|
45913
45960
|
|
|
45914
45961
|
Usage:
|
|
45915
|
-
|
|
45962
|
+
todoforai-cli list [flags]
|
|
45916
45963
|
|
|
45917
45964
|
Flags:
|
|
45918
45965
|
-n, --limit <n> Max rows to show (default: 30)
|
|
45966
|
+
--cursor <n> Fetch todos older than this cursor (lastActivityAt)
|
|
45967
|
+
--page-size <n> Backend page size per request (default: min(limit, 100), max: 100)
|
|
45968
|
+
--search <text> Search content/status on the backend
|
|
45919
45969
|
-s, --status <S[,S2]> Filter by status (comma-separated, union).
|
|
45920
45970
|
-A, --all Include DONE (also CANCELLED, ARCHIVED, \u2026)
|
|
45921
45971
|
--project <id> Project ID (default: current default project)
|
|
45922
|
-
--json Output
|
|
45972
|
+
--json Output { items, nextCursor } as JSON
|
|
45923
45973
|
-h, --help Show this help
|
|
45924
45974
|
|
|
45925
45975
|
Examples:
|
|
45926
|
-
|
|
45927
|
-
|
|
45928
|
-
|
|
45929
|
-
|
|
45930
|
-
|
|
45976
|
+
todoforai-cli list # 30 most recent open todos
|
|
45977
|
+
todoforai-cli list -n 50 # last 50 open
|
|
45978
|
+
todoforai-cli list --cursor 1719234567890 # next page
|
|
45979
|
+
todoforai-cli list --all # include DONE
|
|
45980
|
+
todoforai-cli list -s RUNNING,REVIEW_REQUESTED
|
|
45981
|
+
todoforai-cli list --search bug --json | jq '.items[].id'
|
|
45982
|
+
`);
|
|
45983
|
+
}
|
|
45984
|
+
function positiveInt(value, name) {
|
|
45985
|
+
if (value === undefined)
|
|
45986
|
+
return;
|
|
45987
|
+
const n = Number(value);
|
|
45988
|
+
if (!Number.isFinite(n) || !Number.isInteger(n) || n < 1) {
|
|
45989
|
+
process.stderr.write(`${RED}Error: ${name} must be a positive integer${RESET}
|
|
45990
|
+
`);
|
|
45991
|
+
process.exit(2);
|
|
45992
|
+
}
|
|
45993
|
+
return n;
|
|
45994
|
+
}
|
|
45995
|
+
function cursorValue(value) {
|
|
45996
|
+
if (value === undefined)
|
|
45997
|
+
return;
|
|
45998
|
+
const n = Number(value);
|
|
45999
|
+
if (!Number.isFinite(n) || n < 0) {
|
|
46000
|
+
process.stderr.write(`${RED}Error: --cursor must be a non-negative number${RESET}
|
|
45931
46001
|
`);
|
|
46002
|
+
process.exit(2);
|
|
46003
|
+
}
|
|
46004
|
+
return n;
|
|
46005
|
+
}
|
|
46006
|
+
function toItemsPage(response) {
|
|
46007
|
+
if (Array.isArray(response))
|
|
46008
|
+
return { items: response };
|
|
46009
|
+
return {
|
|
46010
|
+
items: Array.isArray(response?.items) ? response.items : [],
|
|
46011
|
+
nextCursor: typeof response?.nextCursor === "number" ? response.nextCursor : undefined
|
|
46012
|
+
};
|
|
46013
|
+
}
|
|
46014
|
+
function rowTime(t) {
|
|
46015
|
+
return Number(t.lastActivityAt ?? t.createdAt ?? 0);
|
|
46016
|
+
}
|
|
46017
|
+
function printStatusError(unknown) {
|
|
46018
|
+
process.stderr.write(`${RED}Error: unknown status ${unknown.map((s) => `'${s}'`).join(", ")}${RESET}
|
|
46019
|
+
`);
|
|
46020
|
+
process.stderr.write(`${DIM}Use OPEN or one of: ${Object.values(TodoStatus).join(", ")}${RESET}
|
|
46021
|
+
`);
|
|
46022
|
+
process.exit(2);
|
|
46023
|
+
}
|
|
46024
|
+
function matchesRequestedStatus(t, requested, wanted, wantOpen) {
|
|
46025
|
+
if (!requested)
|
|
46026
|
+
return true;
|
|
46027
|
+
const s = String(t.status).toUpperCase();
|
|
46028
|
+
return wantOpen && isOpen(s) || (wanted?.has(s) ?? false);
|
|
45932
46029
|
}
|
|
45933
46030
|
async function listTodosCommand(api, defaultProjectId, argv) {
|
|
45934
46031
|
const { values } = parseArgs2({
|
|
45935
46032
|
args: argv,
|
|
45936
46033
|
options: {
|
|
45937
46034
|
limit: { type: "string", short: "n" },
|
|
46035
|
+
cursor: { type: "string" },
|
|
46036
|
+
"page-size": { type: "string" },
|
|
46037
|
+
search: { type: "string" },
|
|
45938
46038
|
status: { type: "string", short: "s" },
|
|
45939
46039
|
all: { type: "boolean", short: "A", default: false },
|
|
45940
46040
|
project: { type: "string" },
|
|
@@ -45944,7 +46044,7 @@ async function listTodosCommand(api, defaultProjectId, argv) {
|
|
|
45944
46044
|
strict: true
|
|
45945
46045
|
});
|
|
45946
46046
|
if (values.help) {
|
|
45947
|
-
|
|
46047
|
+
printListTodosHelp();
|
|
45948
46048
|
return;
|
|
45949
46049
|
}
|
|
45950
46050
|
const projectId = values.project || defaultProjectId;
|
|
@@ -45953,38 +46053,68 @@ async function listTodosCommand(api, defaultProjectId, argv) {
|
|
|
45953
46053
|
`);
|
|
45954
46054
|
process.exit(1);
|
|
45955
46055
|
}
|
|
45956
|
-
const limit = values.limit
|
|
46056
|
+
const limit = positiveInt(values.limit, "--limit") ?? 30;
|
|
46057
|
+
const pageSize = Math.min(positiveInt(values["page-size"], "--page-size") ?? Math.min(Math.max(limit, 1), 100), 100);
|
|
46058
|
+
let cursor = cursorValue(values.cursor);
|
|
46059
|
+
const search = values.search ? String(values.search) : undefined;
|
|
45957
46060
|
const requested = values.status ? String(values.status).split(",").map((s) => s.trim().toUpperCase()).filter(Boolean) : null;
|
|
45958
|
-
const
|
|
46061
|
+
const unknown = requested?.filter((s) => s !== "OPEN" && !VALID_STATUS.has(s)) ?? [];
|
|
46062
|
+
if (unknown.length)
|
|
46063
|
+
printStatusError(unknown);
|
|
46064
|
+
const wantOpen = !!requested?.some((s) => s === "OPEN");
|
|
45959
46065
|
const wanted = requested ? new Set(requested.filter((s) => VALID_STATUS.has(s))) : null;
|
|
45960
46066
|
const includeClosed = !!values.all || !!wanted && wanted.size > 0;
|
|
45961
|
-
const
|
|
45962
|
-
let
|
|
45963
|
-
|
|
45964
|
-
|
|
45965
|
-
const
|
|
45966
|
-
|
|
45967
|
-
|
|
46067
|
+
const rows = [];
|
|
46068
|
+
let backendNextCursor;
|
|
46069
|
+
while (rows.length < limit) {
|
|
46070
|
+
const requestLimit = Math.min(pageSize, limit - rows.length);
|
|
46071
|
+
const page = toItemsPage(await api.listTodos(projectId, { limit: requestLimit, cursor, search }));
|
|
46072
|
+
backendNextCursor = page.nextCursor;
|
|
46073
|
+
for (const t of page.items) {
|
|
46074
|
+
const status = String(t.status).toUpperCase();
|
|
46075
|
+
if (!includeClosed && CLOSED.has(status))
|
|
46076
|
+
continue;
|
|
46077
|
+
if (!matchesRequestedStatus(t, requested, wanted, wantOpen))
|
|
46078
|
+
continue;
|
|
46079
|
+
rows.push(t);
|
|
46080
|
+
if (rows.length >= limit)
|
|
46081
|
+
break;
|
|
46082
|
+
}
|
|
46083
|
+
if (!backendNextCursor || page.items.length === 0)
|
|
46084
|
+
break;
|
|
46085
|
+
cursor = backendNextCursor;
|
|
46086
|
+
}
|
|
46087
|
+
rows.sort((a, b) => rowTime(b) - rowTime(a));
|
|
46088
|
+
const visible = rows.slice(0, limit);
|
|
46089
|
+
const hasBufferedRows = rows.length > visible.length;
|
|
46090
|
+
const nextCursor = visible.length && (backendNextCursor !== undefined || hasBufferedRows) ? rowTime(visible[visible.length - 1]) : undefined;
|
|
45968
46091
|
if (values.json) {
|
|
45969
|
-
process.stdout.write(JSON.stringify(
|
|
46092
|
+
process.stdout.write(JSON.stringify({ items: visible, nextCursor }, null, 2) + `
|
|
45970
46093
|
`);
|
|
45971
46094
|
return;
|
|
45972
46095
|
}
|
|
45973
|
-
if (!
|
|
46096
|
+
if (!visible.length) {
|
|
45974
46097
|
process.stderr.write(`${DIM}(no todos)${RESET}
|
|
46098
|
+
`);
|
|
46099
|
+
if (nextCursor !== undefined)
|
|
46100
|
+
process.stderr.write(`${DIM}Next cursor: ${nextCursor} (re-run with the same filters plus --cursor ${nextCursor})${RESET}
|
|
45975
46101
|
`);
|
|
45976
46102
|
return;
|
|
45977
46103
|
}
|
|
45978
|
-
const statusW =
|
|
45979
|
-
for (const t of
|
|
45980
|
-
const
|
|
45981
|
-
const
|
|
46104
|
+
const statusW = visible.reduce((n, r) => Math.max(n, String(r.status).length), 0);
|
|
46105
|
+
for (const t of visible) {
|
|
46106
|
+
const status = String(t.status).toUpperCase();
|
|
46107
|
+
const sc = STATUS_COLOR[status] || DIM;
|
|
46108
|
+
const ts = new Date(rowTime(t)).toISOString().slice(0, 16).replace("T", " ");
|
|
45982
46109
|
const title = String(t.content ?? "").split(`
|
|
45983
46110
|
`)[0].slice(0, 100);
|
|
45984
|
-
process.stdout.write(`${sc}${
|
|
46111
|
+
process.stdout.write(`${sc}${status.padEnd(statusW)}${RESET} ${DIM}${ts}${RESET} ${t.id} ${title}
|
|
45985
46112
|
`);
|
|
45986
46113
|
}
|
|
45987
|
-
process.stderr.write(`${DIM}${
|
|
46114
|
+
process.stderr.write(`${DIM}${visible.length} todo(s)${RESET}
|
|
46115
|
+
`);
|
|
46116
|
+
if (nextCursor !== undefined)
|
|
46117
|
+
process.stderr.write(`${DIM}Next cursor: ${nextCursor} (re-run with the same filters plus --cursor ${nextCursor})${RESET}
|
|
45988
46118
|
`);
|
|
45989
46119
|
}
|
|
45990
46120
|
|
|
@@ -46139,6 +46269,10 @@ Cancelled by user (Ctrl+C)
|
|
|
46139
46269
|
printAgentHelp();
|
|
46140
46270
|
process.exit(0);
|
|
46141
46271
|
}
|
|
46272
|
+
if ((positionals[0] === "list" || positionals[0] === "ls") && args.help) {
|
|
46273
|
+
printListTodosHelp();
|
|
46274
|
+
process.exit(0);
|
|
46275
|
+
}
|
|
46142
46276
|
if (args.help && !["list", "ls", "agent"].includes(positionals[0])) {
|
|
46143
46277
|
printUsage();
|
|
46144
46278
|
process.exit(0);
|
|
@@ -46234,7 +46368,7 @@ Cancelled by user (Ctrl+C)
|
|
|
46234
46368
|
if (positionals[0] === "delete") {
|
|
46235
46369
|
const todoId = positionals[1];
|
|
46236
46370
|
if (!todoId) {
|
|
46237
|
-
process.stderr.write(`${RED}Usage:
|
|
46371
|
+
process.stderr.write(`${RED}Usage: todoforai-cli delete <todo-id>${RESET}
|
|
46238
46372
|
`);
|
|
46239
46373
|
process.exit(2);
|
|
46240
46374
|
}
|
|
@@ -46247,7 +46381,7 @@ Cancelled by user (Ctrl+C)
|
|
|
46247
46381
|
const [, todoId, ...rest] = positionals;
|
|
46248
46382
|
const content2 = rest.join(" ") || await readStdin();
|
|
46249
46383
|
if (!todoId || !content2) {
|
|
46250
|
-
process.stderr.write(`${RED}Usage:
|
|
46384
|
+
process.stderr.write(`${RED}Usage: todoforai-cli addmessage <todo-id> "content"${RESET}
|
|
46251
46385
|
`);
|
|
46252
46386
|
process.exit(2);
|
|
46253
46387
|
}
|
|
@@ -46440,14 +46574,22 @@ Resumed: ${CYAN}${getFrontendUrl(apiUrl, projectId2, todoId)}${RESET}
|
|
|
46440
46574
|
let preMatchedAgent = null;
|
|
46441
46575
|
let agents = null;
|
|
46442
46576
|
if (args.agent) {
|
|
46443
|
-
|
|
46444
|
-
|
|
46445
|
-
|
|
46446
|
-
|
|
46577
|
+
agents = await api.listAgentSettings();
|
|
46578
|
+
const { match, ambiguous } = resolveAgentMatch(agents, args.agent);
|
|
46579
|
+
if (ambiguous) {
|
|
46580
|
+
process.stderr.write(`Error: Ambiguous agent '${args.agent}' \u2014 ${ambiguous.length} matches. Re-run with the exact id:
|
|
46581
|
+
`);
|
|
46582
|
+
for (const a of ambiguous)
|
|
46583
|
+
process.stderr.write(` ${getDisplayName(a)} ${DIM}${getItemId(a)}${RESET}
|
|
46584
|
+
`);
|
|
46585
|
+
process.exit(1);
|
|
46586
|
+
}
|
|
46587
|
+
if (!match) {
|
|
46447
46588
|
process.stderr.write(`Error: Agent '${args.agent}' not found
|
|
46448
46589
|
`);
|
|
46449
46590
|
process.exit(1);
|
|
46450
46591
|
}
|
|
46592
|
+
preMatchedAgent = match;
|
|
46451
46593
|
cfgScope.setDefaultAgent(getDisplayName(preMatchedAgent), preMatchedAgent);
|
|
46452
46594
|
} else {
|
|
46453
46595
|
const pathArg = args.path || ".";
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@todoforai/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.15",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
|
-
"
|
|
7
|
-
"
|
|
6
|
+
"todoforai-cli": "dist/todoai.js",
|
|
7
|
+
"todoai": "dist/todoai.js"
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"dist/todoai.js"
|