@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 CHANGED
@@ -17,13 +17,11 @@ todoai # prompts device login if no key found
17
17
  todoai login # explicit login
18
18
  ```
19
19
 
20
- Optional manual config:
20
+ API URL resolution: `--api-url` flag → `TODOFORAI_API_URL` env → `https://api.todofor.ai`.
21
21
 
22
- ```bash
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
- Auth resolution order: `--api-key` flag `TODOFORAI_API_KEY` env shared credentials (`~/.todoforai/credentials.json`) device login.
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 defaultConfig() {
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
- input_history: []
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
- return { ...defaultConfig(), ...raw };
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
- setDefaultProject(id, name) {
43583
- this.data.default_project_id = id;
43584
- this.data.default_project_name = name || id;
43585
- const recent = this.data.recent_projects.filter((p) => p.id !== id);
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
- if (args["set-default-api-url"]) {
45022
- cfg.setDefaultApiUrl(args["set-default-api-url"]);
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 = cfg.data.default_project_id || projects2.find((p) => p.project?.isDefault)?.project?.id || projects2[0]?.project?.id;
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
- cfg.data.last_todo_id = todoId;
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 || cfg.data.last_todo_id;
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
- cfg.setDefaultAgent(getDisplayName(preMatchedAgent), preMatchedAgent);
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
- cfg.setDefaultAgent(getDisplayName(preMatchedAgent), preMatchedAgent);
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
- cfg.setDefaultAgent(getDisplayName(preMatchedAgent), preMatchedAgent);
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 || cfg.data.default_project_id;
45266
- const storedAgent = cfg.data.default_agent_settings;
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 (cfg.data.default_project_id && !projects) {
45285
- projectId = cfg.data.default_project_id;
45286
- projectName = cfg.data.default_project_name || projectId;
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, cfg.data.default_project_id, (id, name) => cfg.setDefaultProject(id, name));
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, cfg.data.default_agent_name, (name, settings) => cfg.setDefaultAgent(name, settings));
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
- cfg.data.last_todo_id = actualTodoId;
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));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@todoforai/cli",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "todoai": "dist/todoai.js"