@todoforai/cli 0.1.5 → 0.1.7
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 +3 -6
- package/dist/todoai.js +130 -56
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,13 +17,11 @@ todoai # prompts device login if no key found
|
|
|
17
17
|
todoai login # explicit login
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
API URL resolution: `--api-url` flag → `TODOFORAI_API_URL` env → `https://api.todofor.ai`.
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
todoai --set-default-api-url http://localhost:4000 # or https://api.todofor.ai
|
|
24
|
-
```
|
|
22
|
+
Auth resolution: `--api-key` flag → `TODOFORAI_API_KEY` env → shared credentials (`~/.todoforai/credentials.json`) → device login.
|
|
25
23
|
|
|
26
|
-
|
|
24
|
+
Project, agent, and last-todo state are stored **per API URL** under `per_api_url[<url>]` in the config — switching between e.g. `https://api.todofor.ai` and `http://localhost:4000` keeps each environment's defaults isolated. Legacy top-level fields are auto-migrated on first run.
|
|
27
25
|
|
|
28
26
|
## Edge daemon
|
|
29
27
|
|
|
@@ -89,7 +87,6 @@ todoai --resume <todo-id> # resume specific todo
|
|
|
89
87
|
--safe Validate API key upfront
|
|
90
88
|
--debug, -d Debug output
|
|
91
89
|
--show-config Show config
|
|
92
|
-
--set-default-api-url Set default API URL
|
|
93
90
|
--reset-config Reset config file
|
|
94
91
|
--help, -h Show this help
|
|
95
92
|
```
|
package/dist/todoai.js
CHANGED
|
@@ -42776,6 +42776,12 @@ class ApiClient {
|
|
|
42776
42776
|
updateAgentSettings(agentId, agentSettingsId, updates) {
|
|
42777
42777
|
return this.request("PUT", `/api/v1/agents/${agentId}/settings`, { agentSettingsId, updates });
|
|
42778
42778
|
}
|
|
42779
|
+
getGlobalAgentSettings() {
|
|
42780
|
+
return this.request("GET", "/api/v1/agents/global");
|
|
42781
|
+
}
|
|
42782
|
+
updateGlobalAgentSettings(updates) {
|
|
42783
|
+
return this.request("PUT", "/api/v1/agents/global", updates);
|
|
42784
|
+
}
|
|
42779
42785
|
setAgentEdgeMcpConfig(agentId, agentSettingsId, edgeId, mcpName, config) {
|
|
42780
42786
|
return this.request("PUT", `/api/v1/agents/${agentId}/edge-mcp-config`, { agentSettingsId, edgeId, mcpName, config });
|
|
42781
42787
|
}
|
|
@@ -43107,7 +43113,6 @@ Options:
|
|
|
43107
43113
|
--safe Validate API key upfront
|
|
43108
43114
|
--debug, -d Debug output
|
|
43109
43115
|
--show-config Show config
|
|
43110
|
-
--set-default-api-url Set default API URL
|
|
43111
43116
|
--reset-config Reset config file
|
|
43112
43117
|
--version, -v Print version and exit
|
|
43113
43118
|
--help, -h Show this help
|
|
@@ -43137,7 +43142,6 @@ function parseCliArgs() {
|
|
|
43137
43142
|
safe: { type: "boolean", default: false },
|
|
43138
43143
|
debug: { type: "boolean", short: "d", default: false },
|
|
43139
43144
|
"show-config": { type: "boolean", default: false },
|
|
43140
|
-
"set-default-api-url": { type: "string" },
|
|
43141
43145
|
"reset-config": { type: "boolean", default: false },
|
|
43142
43146
|
"config-path": { type: "string" },
|
|
43143
43147
|
help: { type: "boolean", short: "h", default: false },
|
|
@@ -43535,20 +43539,50 @@ function getConfigDir() {
|
|
|
43535
43539
|
const xdg = process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
|
|
43536
43540
|
return join(xdg, "todoai-cli");
|
|
43537
43541
|
}
|
|
43538
|
-
function
|
|
43542
|
+
function defaultScope() {
|
|
43539
43543
|
return {
|
|
43540
43544
|
default_project_id: null,
|
|
43541
43545
|
default_project_name: null,
|
|
43542
43546
|
default_agent_name: null,
|
|
43543
43547
|
default_agent_settings: null,
|
|
43544
43548
|
default_agent_settings_updated_at: null,
|
|
43545
|
-
default_api_url: null,
|
|
43546
43549
|
recent_projects: [],
|
|
43547
43550
|
recent_agents: [],
|
|
43548
|
-
last_todo_id: null
|
|
43549
|
-
input_history: []
|
|
43551
|
+
last_todo_id: null
|
|
43550
43552
|
};
|
|
43551
43553
|
}
|
|
43554
|
+
function defaultConfig() {
|
|
43555
|
+
return {
|
|
43556
|
+
input_history: [],
|
|
43557
|
+
per_api_url: {}
|
|
43558
|
+
};
|
|
43559
|
+
}
|
|
43560
|
+
function migrate(raw) {
|
|
43561
|
+
const cfg = { ...defaultConfig(), ...raw, per_api_url: raw.per_api_url || {} };
|
|
43562
|
+
const legacyKeys = [
|
|
43563
|
+
"default_project_id",
|
|
43564
|
+
"default_project_name",
|
|
43565
|
+
"default_agent_name",
|
|
43566
|
+
"default_agent_settings",
|
|
43567
|
+
"default_agent_settings_updated_at",
|
|
43568
|
+
"recent_projects",
|
|
43569
|
+
"recent_agents",
|
|
43570
|
+
"last_todo_id"
|
|
43571
|
+
];
|
|
43572
|
+
const hasLegacy = legacyKeys.some((k) => raw[k] != null);
|
|
43573
|
+
if (hasLegacy) {
|
|
43574
|
+
const url = raw.default_api_url || "https://api.todofor.ai";
|
|
43575
|
+
const scope = { ...defaultScope(), ...cfg.per_api_url[url] || {} };
|
|
43576
|
+
for (const k of legacyKeys)
|
|
43577
|
+
if (raw[k] != null)
|
|
43578
|
+
scope[k] = raw[k];
|
|
43579
|
+
cfg.per_api_url[url] = scope;
|
|
43580
|
+
}
|
|
43581
|
+
for (const k of legacyKeys)
|
|
43582
|
+
delete cfg[k];
|
|
43583
|
+
delete cfg.default_api_url;
|
|
43584
|
+
return cfg;
|
|
43585
|
+
}
|
|
43552
43586
|
|
|
43553
43587
|
class ConfigStore {
|
|
43554
43588
|
path;
|
|
@@ -43568,7 +43602,13 @@ class ConfigStore {
|
|
|
43568
43602
|
try {
|
|
43569
43603
|
const raw = JSON.parse(readFileSync(this.path, "utf-8"));
|
|
43570
43604
|
delete raw.default_api_key;
|
|
43571
|
-
|
|
43605
|
+
const cfg = migrate(raw);
|
|
43606
|
+
const needsMigration = raw.per_api_url == null;
|
|
43607
|
+
if (needsMigration) {
|
|
43608
|
+
this.data = cfg;
|
|
43609
|
+
this.save();
|
|
43610
|
+
}
|
|
43611
|
+
return cfg;
|
|
43572
43612
|
} catch {
|
|
43573
43613
|
return defaultConfig();
|
|
43574
43614
|
}
|
|
@@ -43579,26 +43619,10 @@ class ConfigStore {
|
|
|
43579
43619
|
writeFileSync(this.path, JSON.stringify(this.data, null, 2), "utf-8");
|
|
43580
43620
|
} catch {}
|
|
43581
43621
|
}
|
|
43582
|
-
|
|
43583
|
-
this.data.
|
|
43584
|
-
|
|
43585
|
-
|
|
43586
|
-
recent.unshift({ id, name: name || id });
|
|
43587
|
-
this.data.recent_projects = recent.slice(0, 10);
|
|
43588
|
-
this.save();
|
|
43589
|
-
}
|
|
43590
|
-
setDefaultAgent(name, settings) {
|
|
43591
|
-
this.data.default_agent_name = name;
|
|
43592
|
-
this.data.default_agent_settings = settings || null;
|
|
43593
|
-
this.data.default_agent_settings_updated_at = new Date().toISOString();
|
|
43594
|
-
const recent = this.data.recent_agents.filter((a) => a !== name);
|
|
43595
|
-
recent.unshift(name);
|
|
43596
|
-
this.data.recent_agents = recent.slice(0, 10);
|
|
43597
|
-
this.save();
|
|
43598
|
-
}
|
|
43599
|
-
setDefaultApiUrl(url) {
|
|
43600
|
-
this.data.default_api_url = url;
|
|
43601
|
-
this.save();
|
|
43622
|
+
scope(apiUrl) {
|
|
43623
|
+
if (!this.data.per_api_url[apiUrl])
|
|
43624
|
+
this.data.per_api_url[apiUrl] = defaultScope();
|
|
43625
|
+
return new ScopedConfig(this, apiUrl);
|
|
43602
43626
|
}
|
|
43603
43627
|
addToHistory(input) {
|
|
43604
43628
|
const trimmed = input.trim();
|
|
@@ -43616,6 +43640,37 @@ class ConfigStore {
|
|
|
43616
43640
|
}
|
|
43617
43641
|
}
|
|
43618
43642
|
|
|
43643
|
+
class ScopedConfig {
|
|
43644
|
+
store;
|
|
43645
|
+
apiUrl;
|
|
43646
|
+
constructor(store, apiUrl) {
|
|
43647
|
+
this.store = store;
|
|
43648
|
+
this.apiUrl = apiUrl;
|
|
43649
|
+
}
|
|
43650
|
+
get data() {
|
|
43651
|
+
return this.store.data.per_api_url[this.apiUrl];
|
|
43652
|
+
}
|
|
43653
|
+
setDefaultProject(id, name) {
|
|
43654
|
+
const s = this.data;
|
|
43655
|
+
s.default_project_id = id;
|
|
43656
|
+
s.default_project_name = name || id;
|
|
43657
|
+
s.recent_projects = [{ id, name: name || id }, ...s.recent_projects.filter((p) => p.id !== id)].slice(0, 10);
|
|
43658
|
+
this.store.save();
|
|
43659
|
+
}
|
|
43660
|
+
setDefaultAgent(name, settings) {
|
|
43661
|
+
const s = this.data;
|
|
43662
|
+
s.default_agent_name = name;
|
|
43663
|
+
s.default_agent_settings = settings || null;
|
|
43664
|
+
s.default_agent_settings_updated_at = new Date().toISOString();
|
|
43665
|
+
s.recent_agents = [name, ...s.recent_agents.filter((a) => a !== name)].slice(0, 10);
|
|
43666
|
+
this.store.save();
|
|
43667
|
+
}
|
|
43668
|
+
setLastTodoId(id) {
|
|
43669
|
+
this.data.last_todo_id = id;
|
|
43670
|
+
this.store.save();
|
|
43671
|
+
}
|
|
43672
|
+
}
|
|
43673
|
+
|
|
43619
43674
|
// src/credentials.ts
|
|
43620
43675
|
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
43621
43676
|
import { dirname as dirname2, join as join2 } from "path";
|
|
@@ -44886,11 +44941,19 @@ async function listAgentsCommand(api, opts) {
|
|
|
44886
44941
|
}
|
|
44887
44942
|
|
|
44888
44943
|
// src/ensure-edge.ts
|
|
44889
|
-
import { spawn } from "child_process";
|
|
44944
|
+
import { spawn, spawnSync } from "child_process";
|
|
44890
44945
|
import fs from "fs";
|
|
44891
44946
|
import path2 from "path";
|
|
44892
44947
|
import os2 from "os";
|
|
44948
|
+
function hasBunx() {
|
|
44949
|
+
const probe = spawnSync(process.platform === "win32" ? "where" : "which", ["bunx"], { stdio: "ignore" });
|
|
44950
|
+
return probe.status === 0;
|
|
44951
|
+
}
|
|
44893
44952
|
function ensureEdgeRunning(apiUrl, apiKey) {
|
|
44953
|
+
if (!hasBunx()) {
|
|
44954
|
+
console.error("\x1B[2mEdge daemon not started: `bunx` is missing. Install Bun from https://bun.sh to enable it, or pass --no-edge to silence this.\x1B[0m");
|
|
44955
|
+
return;
|
|
44956
|
+
}
|
|
44894
44957
|
const logDir = path2.join(os2.homedir(), ".todoforai");
|
|
44895
44958
|
fs.mkdirSync(logDir, { recursive: true });
|
|
44896
44959
|
const logFile = path2.join(logDir, "edge.log");
|
|
@@ -44899,13 +44962,30 @@ function ensureEdgeRunning(apiUrl, apiKey) {
|
|
|
44899
44962
|
detached: true,
|
|
44900
44963
|
stdio: ["ignore", out, out]
|
|
44901
44964
|
});
|
|
44965
|
+
child.on("error", (err) => {
|
|
44966
|
+
console.error(`\x1B[33mFailed to start edge daemon: ${err.message}\x1B[0m`);
|
|
44967
|
+
});
|
|
44968
|
+
let exited = false;
|
|
44969
|
+
let exitCode = null;
|
|
44970
|
+
child.on("exit", (code) => {
|
|
44971
|
+
exited = true;
|
|
44972
|
+
exitCode = code;
|
|
44973
|
+
});
|
|
44902
44974
|
child.unref();
|
|
44903
44975
|
const pid = child.pid;
|
|
44976
|
+
if (!pid)
|
|
44977
|
+
return;
|
|
44978
|
+
const shortLog = logFile.replace(os2.homedir(), "~");
|
|
44904
44979
|
setTimeout(() => {
|
|
44905
|
-
|
|
44906
|
-
|
|
44907
|
-
|
|
44908
|
-
}
|
|
44980
|
+
if (!exited) {
|
|
44981
|
+
console.error(`\x1B[2mStarted edge daemon (pid ${pid}), logs: ${shortLog}\x1B[0m`);
|
|
44982
|
+
return;
|
|
44983
|
+
}
|
|
44984
|
+
if (exitCode === 0) {
|
|
44985
|
+
console.error(`\x1B[2mEdge daemon exited cleanly (another instance likely already running). Logs: ${shortLog}\x1B[0m`);
|
|
44986
|
+
} else {
|
|
44987
|
+
console.error(`\x1B[31mEdge daemon died (exit ${exitCode}). Check logs: ${shortLog}\x1B[0m`);
|
|
44988
|
+
}
|
|
44909
44989
|
}, 500);
|
|
44910
44990
|
}
|
|
44911
44991
|
|
|
@@ -45018,12 +45098,8 @@ Cancelled by user (Ctrl+C)
|
|
|
45018
45098
|
console.log("No configuration file to reset");
|
|
45019
45099
|
return;
|
|
45020
45100
|
}
|
|
45021
|
-
|
|
45022
|
-
|
|
45023
|
-
console.log(`Default API URL set to: ${args["set-default-api-url"]}`);
|
|
45024
|
-
return;
|
|
45025
|
-
}
|
|
45026
|
-
const apiUrl = normalizeApiUrl(args["api-url"] || cfg.data.default_api_url || getEnv("API_URL") || DEFAULT_API_URL);
|
|
45101
|
+
const apiUrl = normalizeApiUrl(args["api-url"] || getEnv("API_URL") || DEFAULT_API_URL);
|
|
45102
|
+
const cfgScope = cfg.scope(apiUrl);
|
|
45027
45103
|
async function deviceLogin() {
|
|
45028
45104
|
const loginApi = new ApiClient(apiUrl, "");
|
|
45029
45105
|
const { code, url, expiresIn } = await loginApi.initDeviceLogin("cli");
|
|
@@ -45096,7 +45172,7 @@ Cancelled by user (Ctrl+C)
|
|
|
45096
45172
|
if (process.stderr.isTTY)
|
|
45097
45173
|
printLogo();
|
|
45098
45174
|
if (args.template) {
|
|
45099
|
-
if (!args["no-edge"])
|
|
45175
|
+
if (!args["no-edge"] && !args["no-watch"])
|
|
45100
45176
|
ensureEdgeRunning(apiUrl, apiKey);
|
|
45101
45177
|
const templateId = args.template;
|
|
45102
45178
|
const inputValues = {};
|
|
@@ -45132,7 +45208,7 @@ Cancelled by user (Ctrl+C)
|
|
|
45132
45208
|
const projects2 = await api.listProjects();
|
|
45133
45209
|
let projectId2 = args.project;
|
|
45134
45210
|
if (!projectId2) {
|
|
45135
|
-
projectId2 =
|
|
45211
|
+
projectId2 = cfgScope.data.default_project_id || projects2.find((p) => p.project?.isDefault)?.project?.id || projects2[0]?.project?.id;
|
|
45136
45212
|
}
|
|
45137
45213
|
if (!projectId2) {
|
|
45138
45214
|
process.stderr.write(`Error: No project found
|
|
@@ -45141,8 +45217,7 @@ Cancelled by user (Ctrl+C)
|
|
|
45141
45217
|
}
|
|
45142
45218
|
const todo2 = await api.startFromTemplate(projectId2, templateId, { inputValues });
|
|
45143
45219
|
const todoId = todo2.id;
|
|
45144
|
-
|
|
45145
|
-
cfg.save();
|
|
45220
|
+
cfgScope.setLastTodoId(todoId);
|
|
45146
45221
|
const frontendUrl2 = getFrontendUrl(apiUrl, projectId2, todoId);
|
|
45147
45222
|
if (args.json) {
|
|
45148
45223
|
console.log(JSON.stringify({ ...todo2, frontend_url: frontendUrl2 }, null, 2));
|
|
@@ -45187,7 +45262,7 @@ ${"\u2500".repeat(40)}
|
|
|
45187
45262
|
if (args.resume || args.continue) {
|
|
45188
45263
|
if (!args["no-edge"])
|
|
45189
45264
|
ensureEdgeRunning(apiUrl, apiKey);
|
|
45190
|
-
const todoId = args.resume ||
|
|
45265
|
+
const todoId = args.resume || cfgScope.data.last_todo_id;
|
|
45191
45266
|
if (!todoId) {
|
|
45192
45267
|
process.stderr.write(`Error: No recent todo found
|
|
45193
45268
|
`);
|
|
@@ -45222,20 +45297,20 @@ Resumed todo: ${todoId}
|
|
|
45222
45297
|
`);
|
|
45223
45298
|
process.exit(1);
|
|
45224
45299
|
}
|
|
45225
|
-
|
|
45300
|
+
cfgScope.setDefaultAgent(getDisplayName(preMatchedAgent), preMatchedAgent);
|
|
45226
45301
|
} else {
|
|
45227
45302
|
const pathArg = args.path || ".";
|
|
45228
45303
|
const resolved = realpathSync(resolve3(pathArg));
|
|
45229
45304
|
const matches = await api.listAgentSettings({ workspacePath: resolved });
|
|
45230
45305
|
if (matches.length > 0) {
|
|
45231
45306
|
preMatchedAgent = matches[0];
|
|
45232
|
-
|
|
45307
|
+
cfgScope.setDefaultAgent(getDisplayName(preMatchedAgent), preMatchedAgent);
|
|
45233
45308
|
} else if (args.path) {
|
|
45234
45309
|
process.stderr.write(`No agent found for '${formatPathWithTilde(resolved)}', creating one...
|
|
45235
45310
|
`);
|
|
45236
45311
|
try {
|
|
45237
45312
|
preMatchedAgent = await autoCreateAgent(api, resolved);
|
|
45238
|
-
|
|
45313
|
+
cfgScope.setDefaultAgent(getDisplayName(preMatchedAgent), preMatchedAgent);
|
|
45239
45314
|
} catch (e) {
|
|
45240
45315
|
process.stderr.write(`Error: Failed to auto-create agent: ${e.message}
|
|
45241
45316
|
`);
|
|
@@ -45254,7 +45329,7 @@ Resumed todo: ${todoId}
|
|
|
45254
45329
|
}
|
|
45255
45330
|
process.stderr.write(`${DIM}Tip: ${randomTip()}${RESET}
|
|
45256
45331
|
`);
|
|
45257
|
-
if (!args["no-edge"])
|
|
45332
|
+
if (!args["no-edge"] && !args["no-watch"])
|
|
45258
45333
|
ensureEdgeRunning(apiUrl, apiKey);
|
|
45259
45334
|
let content;
|
|
45260
45335
|
if (positionals.length > 0) {
|
|
@@ -45262,8 +45337,8 @@ Resumed todo: ${todoId}
|
|
|
45262
45337
|
} else {
|
|
45263
45338
|
content = await readStdin();
|
|
45264
45339
|
}
|
|
45265
|
-
const hasProject = args.project ||
|
|
45266
|
-
const storedAgent =
|
|
45340
|
+
const hasProject = args.project || cfgScope.data.default_project_id;
|
|
45341
|
+
const storedAgent = cfgScope.data.default_agent_settings;
|
|
45267
45342
|
const hasAgent = preMatchedAgent || storedAgent?.id && !args.agent;
|
|
45268
45343
|
let projects = null;
|
|
45269
45344
|
if (!hasProject || !hasAgent || args.safe || args.debug) {
|
|
@@ -45281,11 +45356,11 @@ Resumed todo: ${todoId}
|
|
|
45281
45356
|
if (match)
|
|
45282
45357
|
projectName = getDisplayName(match);
|
|
45283
45358
|
}
|
|
45284
|
-
} else if (
|
|
45285
|
-
projectId =
|
|
45286
|
-
projectName =
|
|
45359
|
+
} else if (cfgScope.data.default_project_id && !projects) {
|
|
45360
|
+
projectId = cfgScope.data.default_project_id;
|
|
45361
|
+
projectName = cfgScope.data.default_project_name || projectId;
|
|
45287
45362
|
} else {
|
|
45288
|
-
const sel = await selectProject(projects,
|
|
45363
|
+
const sel = await selectProject(projects, cfgScope.data.default_project_id, (id, name) => cfgScope.setDefaultProject(id, name));
|
|
45289
45364
|
projectId = sel.id;
|
|
45290
45365
|
projectName = sel.name;
|
|
45291
45366
|
}
|
|
@@ -45295,7 +45370,7 @@ Resumed todo: ${todoId}
|
|
|
45295
45370
|
} else if (storedAgent?.id && !agents) {
|
|
45296
45371
|
agent = storedAgent;
|
|
45297
45372
|
} else {
|
|
45298
|
-
agent = await selectAgent(agents,
|
|
45373
|
+
agent = await selectAgent(agents, cfgScope.data.default_agent_name, (name, settings) => cfgScope.setDefaultAgent(name, settings));
|
|
45299
45374
|
}
|
|
45300
45375
|
const ws = args["no-watch"] ? null : new FrontendWebSocket(apiUrl, apiKey);
|
|
45301
45376
|
if (ws)
|
|
@@ -45309,8 +45384,7 @@ Resumed todo: ${todoId}
|
|
|
45309
45384
|
cfg.addToHistory(content);
|
|
45310
45385
|
const todo = await api.addMessage(projectId, content, agent);
|
|
45311
45386
|
const actualTodoId = todo.id || crypto.randomUUID();
|
|
45312
|
-
|
|
45313
|
-
cfg.save();
|
|
45387
|
+
cfgScope.setLastTodoId(actualTodoId);
|
|
45314
45388
|
const frontendUrl = getFrontendUrl(apiUrl, projectId, actualTodoId);
|
|
45315
45389
|
if (args.json) {
|
|
45316
45390
|
console.log(JSON.stringify({ ...todo, frontend_url: frontendUrl }, null, 2));
|