@todoforai/cli 0.1.5 → 0.1.6
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 +94 -51
- 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
|
@@ -43107,7 +43107,6 @@ Options:
|
|
|
43107
43107
|
--safe Validate API key upfront
|
|
43108
43108
|
--debug, -d Debug output
|
|
43109
43109
|
--show-config Show config
|
|
43110
|
-
--set-default-api-url Set default API URL
|
|
43111
43110
|
--reset-config Reset config file
|
|
43112
43111
|
--version, -v Print version and exit
|
|
43113
43112
|
--help, -h Show this help
|
|
@@ -43137,7 +43136,6 @@ function parseCliArgs() {
|
|
|
43137
43136
|
safe: { type: "boolean", default: false },
|
|
43138
43137
|
debug: { type: "boolean", short: "d", default: false },
|
|
43139
43138
|
"show-config": { type: "boolean", default: false },
|
|
43140
|
-
"set-default-api-url": { type: "string" },
|
|
43141
43139
|
"reset-config": { type: "boolean", default: false },
|
|
43142
43140
|
"config-path": { type: "string" },
|
|
43143
43141
|
help: { type: "boolean", short: "h", default: false },
|
|
@@ -43535,20 +43533,50 @@ function getConfigDir() {
|
|
|
43535
43533
|
const xdg = process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
|
|
43536
43534
|
return join(xdg, "todoai-cli");
|
|
43537
43535
|
}
|
|
43538
|
-
function
|
|
43536
|
+
function defaultScope() {
|
|
43539
43537
|
return {
|
|
43540
43538
|
default_project_id: null,
|
|
43541
43539
|
default_project_name: null,
|
|
43542
43540
|
default_agent_name: null,
|
|
43543
43541
|
default_agent_settings: null,
|
|
43544
43542
|
default_agent_settings_updated_at: null,
|
|
43545
|
-
default_api_url: null,
|
|
43546
43543
|
recent_projects: [],
|
|
43547
43544
|
recent_agents: [],
|
|
43548
|
-
last_todo_id: null
|
|
43549
|
-
|
|
43545
|
+
last_todo_id: null
|
|
43546
|
+
};
|
|
43547
|
+
}
|
|
43548
|
+
function defaultConfig() {
|
|
43549
|
+
return {
|
|
43550
|
+
input_history: [],
|
|
43551
|
+
per_api_url: {}
|
|
43550
43552
|
};
|
|
43551
43553
|
}
|
|
43554
|
+
function migrate(raw) {
|
|
43555
|
+
const cfg = { ...defaultConfig(), ...raw, per_api_url: raw.per_api_url || {} };
|
|
43556
|
+
const legacyKeys = [
|
|
43557
|
+
"default_project_id",
|
|
43558
|
+
"default_project_name",
|
|
43559
|
+
"default_agent_name",
|
|
43560
|
+
"default_agent_settings",
|
|
43561
|
+
"default_agent_settings_updated_at",
|
|
43562
|
+
"recent_projects",
|
|
43563
|
+
"recent_agents",
|
|
43564
|
+
"last_todo_id"
|
|
43565
|
+
];
|
|
43566
|
+
const hasLegacy = legacyKeys.some((k) => raw[k] != null);
|
|
43567
|
+
if (hasLegacy) {
|
|
43568
|
+
const url = raw.default_api_url || "https://api.todofor.ai";
|
|
43569
|
+
const scope = { ...defaultScope(), ...cfg.per_api_url[url] || {} };
|
|
43570
|
+
for (const k of legacyKeys)
|
|
43571
|
+
if (raw[k] != null)
|
|
43572
|
+
scope[k] = raw[k];
|
|
43573
|
+
cfg.per_api_url[url] = scope;
|
|
43574
|
+
}
|
|
43575
|
+
for (const k of legacyKeys)
|
|
43576
|
+
delete cfg[k];
|
|
43577
|
+
delete cfg.default_api_url;
|
|
43578
|
+
return cfg;
|
|
43579
|
+
}
|
|
43552
43580
|
|
|
43553
43581
|
class ConfigStore {
|
|
43554
43582
|
path;
|
|
@@ -43568,7 +43596,13 @@ class ConfigStore {
|
|
|
43568
43596
|
try {
|
|
43569
43597
|
const raw = JSON.parse(readFileSync(this.path, "utf-8"));
|
|
43570
43598
|
delete raw.default_api_key;
|
|
43571
|
-
|
|
43599
|
+
const cfg = migrate(raw);
|
|
43600
|
+
const needsMigration = raw.per_api_url == null;
|
|
43601
|
+
if (needsMigration) {
|
|
43602
|
+
this.data = cfg;
|
|
43603
|
+
this.save();
|
|
43604
|
+
}
|
|
43605
|
+
return cfg;
|
|
43572
43606
|
} catch {
|
|
43573
43607
|
return defaultConfig();
|
|
43574
43608
|
}
|
|
@@ -43579,26 +43613,10 @@ class ConfigStore {
|
|
|
43579
43613
|
writeFileSync(this.path, JSON.stringify(this.data, null, 2), "utf-8");
|
|
43580
43614
|
} catch {}
|
|
43581
43615
|
}
|
|
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();
|
|
43616
|
+
scope(apiUrl) {
|
|
43617
|
+
if (!this.data.per_api_url[apiUrl])
|
|
43618
|
+
this.data.per_api_url[apiUrl] = defaultScope();
|
|
43619
|
+
return new ScopedConfig(this, apiUrl);
|
|
43602
43620
|
}
|
|
43603
43621
|
addToHistory(input) {
|
|
43604
43622
|
const trimmed = input.trim();
|
|
@@ -43616,6 +43634,37 @@ class ConfigStore {
|
|
|
43616
43634
|
}
|
|
43617
43635
|
}
|
|
43618
43636
|
|
|
43637
|
+
class ScopedConfig {
|
|
43638
|
+
store;
|
|
43639
|
+
apiUrl;
|
|
43640
|
+
constructor(store, apiUrl) {
|
|
43641
|
+
this.store = store;
|
|
43642
|
+
this.apiUrl = apiUrl;
|
|
43643
|
+
}
|
|
43644
|
+
get data() {
|
|
43645
|
+
return this.store.data.per_api_url[this.apiUrl];
|
|
43646
|
+
}
|
|
43647
|
+
setDefaultProject(id, name) {
|
|
43648
|
+
const s = this.data;
|
|
43649
|
+
s.default_project_id = id;
|
|
43650
|
+
s.default_project_name = name || id;
|
|
43651
|
+
s.recent_projects = [{ id, name: name || id }, ...s.recent_projects.filter((p) => p.id !== id)].slice(0, 10);
|
|
43652
|
+
this.store.save();
|
|
43653
|
+
}
|
|
43654
|
+
setDefaultAgent(name, settings) {
|
|
43655
|
+
const s = this.data;
|
|
43656
|
+
s.default_agent_name = name;
|
|
43657
|
+
s.default_agent_settings = settings || null;
|
|
43658
|
+
s.default_agent_settings_updated_at = new Date().toISOString();
|
|
43659
|
+
s.recent_agents = [name, ...s.recent_agents.filter((a) => a !== name)].slice(0, 10);
|
|
43660
|
+
this.store.save();
|
|
43661
|
+
}
|
|
43662
|
+
setLastTodoId(id) {
|
|
43663
|
+
this.data.last_todo_id = id;
|
|
43664
|
+
this.store.save();
|
|
43665
|
+
}
|
|
43666
|
+
}
|
|
43667
|
+
|
|
43619
43668
|
// src/credentials.ts
|
|
43620
43669
|
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
43621
43670
|
import { dirname as dirname2, join as join2 } from "path";
|
|
@@ -45018,12 +45067,8 @@ Cancelled by user (Ctrl+C)
|
|
|
45018
45067
|
console.log("No configuration file to reset");
|
|
45019
45068
|
return;
|
|
45020
45069
|
}
|
|
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);
|
|
45070
|
+
const apiUrl = normalizeApiUrl(args["api-url"] || getEnv("API_URL") || DEFAULT_API_URL);
|
|
45071
|
+
const cfgScope = cfg.scope(apiUrl);
|
|
45027
45072
|
async function deviceLogin() {
|
|
45028
45073
|
const loginApi = new ApiClient(apiUrl, "");
|
|
45029
45074
|
const { code, url, expiresIn } = await loginApi.initDeviceLogin("cli");
|
|
@@ -45096,7 +45141,7 @@ Cancelled by user (Ctrl+C)
|
|
|
45096
45141
|
if (process.stderr.isTTY)
|
|
45097
45142
|
printLogo();
|
|
45098
45143
|
if (args.template) {
|
|
45099
|
-
if (!args["no-edge"])
|
|
45144
|
+
if (!args["no-edge"] && !args["no-watch"])
|
|
45100
45145
|
ensureEdgeRunning(apiUrl, apiKey);
|
|
45101
45146
|
const templateId = args.template;
|
|
45102
45147
|
const inputValues = {};
|
|
@@ -45132,7 +45177,7 @@ Cancelled by user (Ctrl+C)
|
|
|
45132
45177
|
const projects2 = await api.listProjects();
|
|
45133
45178
|
let projectId2 = args.project;
|
|
45134
45179
|
if (!projectId2) {
|
|
45135
|
-
projectId2 =
|
|
45180
|
+
projectId2 = cfgScope.data.default_project_id || projects2.find((p) => p.project?.isDefault)?.project?.id || projects2[0]?.project?.id;
|
|
45136
45181
|
}
|
|
45137
45182
|
if (!projectId2) {
|
|
45138
45183
|
process.stderr.write(`Error: No project found
|
|
@@ -45141,8 +45186,7 @@ Cancelled by user (Ctrl+C)
|
|
|
45141
45186
|
}
|
|
45142
45187
|
const todo2 = await api.startFromTemplate(projectId2, templateId, { inputValues });
|
|
45143
45188
|
const todoId = todo2.id;
|
|
45144
|
-
|
|
45145
|
-
cfg.save();
|
|
45189
|
+
cfgScope.setLastTodoId(todoId);
|
|
45146
45190
|
const frontendUrl2 = getFrontendUrl(apiUrl, projectId2, todoId);
|
|
45147
45191
|
if (args.json) {
|
|
45148
45192
|
console.log(JSON.stringify({ ...todo2, frontend_url: frontendUrl2 }, null, 2));
|
|
@@ -45187,7 +45231,7 @@ ${"\u2500".repeat(40)}
|
|
|
45187
45231
|
if (args.resume || args.continue) {
|
|
45188
45232
|
if (!args["no-edge"])
|
|
45189
45233
|
ensureEdgeRunning(apiUrl, apiKey);
|
|
45190
|
-
const todoId = args.resume ||
|
|
45234
|
+
const todoId = args.resume || cfgScope.data.last_todo_id;
|
|
45191
45235
|
if (!todoId) {
|
|
45192
45236
|
process.stderr.write(`Error: No recent todo found
|
|
45193
45237
|
`);
|
|
@@ -45222,20 +45266,20 @@ Resumed todo: ${todoId}
|
|
|
45222
45266
|
`);
|
|
45223
45267
|
process.exit(1);
|
|
45224
45268
|
}
|
|
45225
|
-
|
|
45269
|
+
cfgScope.setDefaultAgent(getDisplayName(preMatchedAgent), preMatchedAgent);
|
|
45226
45270
|
} else {
|
|
45227
45271
|
const pathArg = args.path || ".";
|
|
45228
45272
|
const resolved = realpathSync(resolve3(pathArg));
|
|
45229
45273
|
const matches = await api.listAgentSettings({ workspacePath: resolved });
|
|
45230
45274
|
if (matches.length > 0) {
|
|
45231
45275
|
preMatchedAgent = matches[0];
|
|
45232
|
-
|
|
45276
|
+
cfgScope.setDefaultAgent(getDisplayName(preMatchedAgent), preMatchedAgent);
|
|
45233
45277
|
} else if (args.path) {
|
|
45234
45278
|
process.stderr.write(`No agent found for '${formatPathWithTilde(resolved)}', creating one...
|
|
45235
45279
|
`);
|
|
45236
45280
|
try {
|
|
45237
45281
|
preMatchedAgent = await autoCreateAgent(api, resolved);
|
|
45238
|
-
|
|
45282
|
+
cfgScope.setDefaultAgent(getDisplayName(preMatchedAgent), preMatchedAgent);
|
|
45239
45283
|
} catch (e) {
|
|
45240
45284
|
process.stderr.write(`Error: Failed to auto-create agent: ${e.message}
|
|
45241
45285
|
`);
|
|
@@ -45254,7 +45298,7 @@ Resumed todo: ${todoId}
|
|
|
45254
45298
|
}
|
|
45255
45299
|
process.stderr.write(`${DIM}Tip: ${randomTip()}${RESET}
|
|
45256
45300
|
`);
|
|
45257
|
-
if (!args["no-edge"])
|
|
45301
|
+
if (!args["no-edge"] && !args["no-watch"])
|
|
45258
45302
|
ensureEdgeRunning(apiUrl, apiKey);
|
|
45259
45303
|
let content;
|
|
45260
45304
|
if (positionals.length > 0) {
|
|
@@ -45262,8 +45306,8 @@ Resumed todo: ${todoId}
|
|
|
45262
45306
|
} else {
|
|
45263
45307
|
content = await readStdin();
|
|
45264
45308
|
}
|
|
45265
|
-
const hasProject = args.project ||
|
|
45266
|
-
const storedAgent =
|
|
45309
|
+
const hasProject = args.project || cfgScope.data.default_project_id;
|
|
45310
|
+
const storedAgent = cfgScope.data.default_agent_settings;
|
|
45267
45311
|
const hasAgent = preMatchedAgent || storedAgent?.id && !args.agent;
|
|
45268
45312
|
let projects = null;
|
|
45269
45313
|
if (!hasProject || !hasAgent || args.safe || args.debug) {
|
|
@@ -45281,11 +45325,11 @@ Resumed todo: ${todoId}
|
|
|
45281
45325
|
if (match)
|
|
45282
45326
|
projectName = getDisplayName(match);
|
|
45283
45327
|
}
|
|
45284
|
-
} else if (
|
|
45285
|
-
projectId =
|
|
45286
|
-
projectName =
|
|
45328
|
+
} else if (cfgScope.data.default_project_id && !projects) {
|
|
45329
|
+
projectId = cfgScope.data.default_project_id;
|
|
45330
|
+
projectName = cfgScope.data.default_project_name || projectId;
|
|
45287
45331
|
} else {
|
|
45288
|
-
const sel = await selectProject(projects,
|
|
45332
|
+
const sel = await selectProject(projects, cfgScope.data.default_project_id, (id, name) => cfgScope.setDefaultProject(id, name));
|
|
45289
45333
|
projectId = sel.id;
|
|
45290
45334
|
projectName = sel.name;
|
|
45291
45335
|
}
|
|
@@ -45295,7 +45339,7 @@ Resumed todo: ${todoId}
|
|
|
45295
45339
|
} else if (storedAgent?.id && !agents) {
|
|
45296
45340
|
agent = storedAgent;
|
|
45297
45341
|
} else {
|
|
45298
|
-
agent = await selectAgent(agents,
|
|
45342
|
+
agent = await selectAgent(agents, cfgScope.data.default_agent_name, (name, settings) => cfgScope.setDefaultAgent(name, settings));
|
|
45299
45343
|
}
|
|
45300
45344
|
const ws = args["no-watch"] ? null : new FrontendWebSocket(apiUrl, apiKey);
|
|
45301
45345
|
if (ws)
|
|
@@ -45309,8 +45353,7 @@ Resumed todo: ${todoId}
|
|
|
45309
45353
|
cfg.addToHistory(content);
|
|
45310
45354
|
const todo = await api.addMessage(projectId, content, agent);
|
|
45311
45355
|
const actualTodoId = todo.id || crypto.randomUUID();
|
|
45312
|
-
|
|
45313
|
-
cfg.save();
|
|
45356
|
+
cfgScope.setLastTodoId(actualTodoId);
|
|
45314
45357
|
const frontendUrl = getFrontendUrl(apiUrl, projectId, actualTodoId);
|
|
45315
45358
|
if (args.json) {
|
|
45316
45359
|
console.log(JSON.stringify({ ...todo, frontend_url: frontendUrl }, null, 2));
|