@todoforai/cli 0.1.4 → 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.
Files changed (3) hide show
  1. package/README.md +3 -6
  2. package/dist/todoai.js +148 -57
  3. 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
- 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
@@ -12960,7 +12960,7 @@ var require_http = __commonJS((exports, module) => {
12960
12960
  return joined;
12961
12961
  }
12962
12962
  function http(hljs) {
12963
- const VERSION = "HTTP/(2|1\\.[01])";
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: "^(?=" + VERSION + " \\d{3})",
12995
+ begin: "^(?=" + VERSION2 + " \\d{3})",
12996
12996
  end: /$/,
12997
12997
  contains: [
12998
12998
  {
12999
12999
  className: "meta",
13000
- begin: VERSION
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]+ (.*?) " + VERSION + "$)",
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: VERSION
13026
+ begin: VERSION2
13027
13027
  },
13028
13028
  {
13029
13029
  className: "keyword",
@@ -43028,7 +43028,45 @@ class FrontendWebSocket {
43028
43028
 
43029
43029
  // src/args.ts
43030
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
43031
43068
  var DEFAULT_API_URL = "https://api.todofor.ai";
43069
+ var VERSION = package_default.version;
43032
43070
  function getEnv(name) {
43033
43071
  return process.env[`TODOFORAI_${name}`] || process.env[`TODO4AI_${name}`] || "";
43034
43072
  }
@@ -43069,8 +43107,8 @@ Options:
43069
43107
  --safe Validate API key upfront
43070
43108
  --debug, -d Debug output
43071
43109
  --show-config Show config
43072
- --set-default-api-url Set default API URL
43073
43110
  --reset-config Reset config file
43111
+ --version, -v Print version and exit
43074
43112
  --help, -h Show this help
43075
43113
  `);
43076
43114
  }
@@ -43098,10 +43136,10 @@ function parseCliArgs() {
43098
43136
  safe: { type: "boolean", default: false },
43099
43137
  debug: { type: "boolean", short: "d", default: false },
43100
43138
  "show-config": { type: "boolean", default: false },
43101
- "set-default-api-url": { type: "string" },
43102
43139
  "reset-config": { type: "boolean", default: false },
43103
43140
  "config-path": { type: "string" },
43104
- help: { type: "boolean", short: "h", default: false }
43141
+ help: { type: "boolean", short: "h", default: false },
43142
+ version: { type: "boolean", short: "v", default: false }
43105
43143
  },
43106
43144
  allowPositionals: true,
43107
43145
  strict: false
@@ -43495,20 +43533,50 @@ function getConfigDir() {
43495
43533
  const xdg = process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
43496
43534
  return join(xdg, "todoai-cli");
43497
43535
  }
43498
- function defaultConfig() {
43536
+ function defaultScope() {
43499
43537
  return {
43500
43538
  default_project_id: null,
43501
43539
  default_project_name: null,
43502
43540
  default_agent_name: null,
43503
43541
  default_agent_settings: null,
43504
43542
  default_agent_settings_updated_at: null,
43505
- default_api_url: null,
43506
43543
  recent_projects: [],
43507
43544
  recent_agents: [],
43508
- last_todo_id: null,
43509
- input_history: []
43545
+ last_todo_id: null
43546
+ };
43547
+ }
43548
+ function defaultConfig() {
43549
+ return {
43550
+ input_history: [],
43551
+ per_api_url: {}
43510
43552
  };
43511
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
+ }
43512
43580
 
43513
43581
  class ConfigStore {
43514
43582
  path;
@@ -43528,7 +43596,13 @@ class ConfigStore {
43528
43596
  try {
43529
43597
  const raw = JSON.parse(readFileSync(this.path, "utf-8"));
43530
43598
  delete raw.default_api_key;
43531
- 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;
43532
43606
  } catch {
43533
43607
  return defaultConfig();
43534
43608
  }
@@ -43539,26 +43613,10 @@ class ConfigStore {
43539
43613
  writeFileSync(this.path, JSON.stringify(this.data, null, 2), "utf-8");
43540
43614
  } catch {}
43541
43615
  }
43542
- setDefaultProject(id, name) {
43543
- this.data.default_project_id = id;
43544
- this.data.default_project_name = name || id;
43545
- const recent = this.data.recent_projects.filter((p) => p.id !== id);
43546
- recent.unshift({ id, name: name || id });
43547
- this.data.recent_projects = recent.slice(0, 10);
43548
- this.save();
43549
- }
43550
- setDefaultAgent(name, settings) {
43551
- this.data.default_agent_name = name;
43552
- this.data.default_agent_settings = settings || null;
43553
- this.data.default_agent_settings_updated_at = new Date().toISOString();
43554
- const recent = this.data.recent_agents.filter((a) => a !== name);
43555
- recent.unshift(name);
43556
- this.data.recent_agents = recent.slice(0, 10);
43557
- this.save();
43558
- }
43559
- setDefaultApiUrl(url) {
43560
- this.data.default_api_url = url;
43561
- 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);
43562
43620
  }
43563
43621
  addToHistory(input) {
43564
43622
  const trimmed = input.trim();
@@ -43576,6 +43634,37 @@ class ConfigStore {
43576
43634
  }
43577
43635
  }
43578
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
+
43579
43668
  // src/credentials.ts
43580
43669
  import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
43581
43670
  import { dirname as dirname2, join as join2 } from "path";
@@ -44955,6 +45044,10 @@ Cancelled by user (Ctrl+C)
44955
45044
  process.exit(130);
44956
45045
  });
44957
45046
  const { values: args, positionals } = parseCliArgs();
45047
+ if (args.version) {
45048
+ console.log(VERSION);
45049
+ process.exit(0);
45050
+ }
44958
45051
  if (args.help) {
44959
45052
  printUsage();
44960
45053
  process.exit(0);
@@ -44974,12 +45067,8 @@ Cancelled by user (Ctrl+C)
44974
45067
  console.log("No configuration file to reset");
44975
45068
  return;
44976
45069
  }
44977
- if (args["set-default-api-url"]) {
44978
- cfg.setDefaultApiUrl(args["set-default-api-url"]);
44979
- console.log(`Default API URL set to: ${args["set-default-api-url"]}`);
44980
- return;
44981
- }
44982
- 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);
44983
45072
  async function deviceLogin() {
44984
45073
  const loginApi = new ApiClient(apiUrl, "");
44985
45074
  const { code, url, expiresIn } = await loginApi.initDeviceLogin("cli");
@@ -45039,8 +45128,6 @@ Cancelled by user (Ctrl+C)
45039
45128
  apiKey = await deviceLogin();
45040
45129
  }
45041
45130
  const api = new ApiClient(apiUrl, apiKey);
45042
- if (!args["no-edge"])
45043
- ensureEdgeRunning(apiUrl, apiKey);
45044
45131
  if (args["list-agents"]) {
45045
45132
  await listAgentsCommand(api, { json: !!args.json, formatPath: formatPathWithTilde });
45046
45133
  return;
@@ -45054,6 +45141,8 @@ Cancelled by user (Ctrl+C)
45054
45141
  if (process.stderr.isTTY)
45055
45142
  printLogo();
45056
45143
  if (args.template) {
45144
+ if (!args["no-edge"] && !args["no-watch"])
45145
+ ensureEdgeRunning(apiUrl, apiKey);
45057
45146
  const templateId = args.template;
45058
45147
  const inputValues = {};
45059
45148
  for (const kv of args.input || []) {
@@ -45088,7 +45177,7 @@ Cancelled by user (Ctrl+C)
45088
45177
  const projects2 = await api.listProjects();
45089
45178
  let projectId2 = args.project;
45090
45179
  if (!projectId2) {
45091
- 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;
45092
45181
  }
45093
45182
  if (!projectId2) {
45094
45183
  process.stderr.write(`Error: No project found
@@ -45097,8 +45186,7 @@ Cancelled by user (Ctrl+C)
45097
45186
  }
45098
45187
  const todo2 = await api.startFromTemplate(projectId2, templateId, { inputValues });
45099
45188
  const todoId = todo2.id;
45100
- cfg.data.last_todo_id = todoId;
45101
- cfg.save();
45189
+ cfgScope.setLastTodoId(todoId);
45102
45190
  const frontendUrl2 = getFrontendUrl(apiUrl, projectId2, todoId);
45103
45191
  if (args.json) {
45104
45192
  console.log(JSON.stringify({ ...todo2, frontend_url: frontendUrl2 }, null, 2));
@@ -45141,7 +45229,9 @@ ${"\u2500".repeat(40)}
45141
45229
  `);
45142
45230
  }
45143
45231
  if (args.resume || args.continue) {
45144
- const todoId = args.resume || cfg.data.last_todo_id;
45232
+ if (!args["no-edge"])
45233
+ ensureEdgeRunning(apiUrl, apiKey);
45234
+ const todoId = args.resume || cfgScope.data.last_todo_id;
45145
45235
  if (!todoId) {
45146
45236
  process.stderr.write(`Error: No recent todo found
45147
45237
  `);
@@ -45176,20 +45266,20 @@ Resumed todo: ${todoId}
45176
45266
  `);
45177
45267
  process.exit(1);
45178
45268
  }
45179
- cfg.setDefaultAgent(getDisplayName(preMatchedAgent), preMatchedAgent);
45269
+ cfgScope.setDefaultAgent(getDisplayName(preMatchedAgent), preMatchedAgent);
45180
45270
  } else {
45181
45271
  const pathArg = args.path || ".";
45182
45272
  const resolved = realpathSync(resolve3(pathArg));
45183
45273
  const matches = await api.listAgentSettings({ workspacePath: resolved });
45184
45274
  if (matches.length > 0) {
45185
45275
  preMatchedAgent = matches[0];
45186
- cfg.setDefaultAgent(getDisplayName(preMatchedAgent), preMatchedAgent);
45276
+ cfgScope.setDefaultAgent(getDisplayName(preMatchedAgent), preMatchedAgent);
45187
45277
  } else if (args.path) {
45188
45278
  process.stderr.write(`No agent found for '${formatPathWithTilde(resolved)}', creating one...
45189
45279
  `);
45190
45280
  try {
45191
45281
  preMatchedAgent = await autoCreateAgent(api, resolved);
45192
- cfg.setDefaultAgent(getDisplayName(preMatchedAgent), preMatchedAgent);
45282
+ cfgScope.setDefaultAgent(getDisplayName(preMatchedAgent), preMatchedAgent);
45193
45283
  } catch (e) {
45194
45284
  process.stderr.write(`Error: Failed to auto-create agent: ${e.message}
45195
45285
  `);
@@ -45208,14 +45298,16 @@ Resumed todo: ${todoId}
45208
45298
  }
45209
45299
  process.stderr.write(`${DIM}Tip: ${randomTip()}${RESET}
45210
45300
  `);
45301
+ if (!args["no-edge"] && !args["no-watch"])
45302
+ ensureEdgeRunning(apiUrl, apiKey);
45211
45303
  let content;
45212
45304
  if (positionals.length > 0) {
45213
45305
  content = positionals.join(" ");
45214
45306
  } else {
45215
45307
  content = await readStdin();
45216
45308
  }
45217
- const hasProject = args.project || cfg.data.default_project_id;
45218
- 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;
45219
45311
  const hasAgent = preMatchedAgent || storedAgent?.id && !args.agent;
45220
45312
  let projects = null;
45221
45313
  if (!hasProject || !hasAgent || args.safe || args.debug) {
@@ -45233,11 +45325,11 @@ Resumed todo: ${todoId}
45233
45325
  if (match)
45234
45326
  projectName = getDisplayName(match);
45235
45327
  }
45236
- } else if (cfg.data.default_project_id && !projects) {
45237
- projectId = cfg.data.default_project_id;
45238
- 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;
45239
45331
  } else {
45240
- 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));
45241
45333
  projectId = sel.id;
45242
45334
  projectName = sel.name;
45243
45335
  }
@@ -45247,7 +45339,7 @@ Resumed todo: ${todoId}
45247
45339
  } else if (storedAgent?.id && !agents) {
45248
45340
  agent = storedAgent;
45249
45341
  } else {
45250
- 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));
45251
45343
  }
45252
45344
  const ws = args["no-watch"] ? null : new FrontendWebSocket(apiUrl, apiKey);
45253
45345
  if (ws)
@@ -45261,8 +45353,7 @@ Resumed todo: ${todoId}
45261
45353
  cfg.addToHistory(content);
45262
45354
  const todo = await api.addMessage(projectId, content, agent);
45263
45355
  const actualTodoId = todo.id || crypto.randomUUID();
45264
- cfg.data.last_todo_id = actualTodoId;
45265
- cfg.save();
45356
+ cfgScope.setLastTodoId(actualTodoId);
45266
45357
  const frontendUrl = getFrontendUrl(apiUrl, projectId, actualTodoId);
45267
45358
  if (args.json) {
45268
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.4",
3
+ "version": "0.1.6",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "todoai": "dist/todoai.js"