@0dai-dev/cli 1.3.1 → 2.0.1

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 (2) hide show
  1. package/bin/0dai.js +56 -29
  2. package/package.json +1 -1
package/bin/0dai.js CHANGED
@@ -7,10 +7,15 @@ const fs = require("fs");
7
7
  const path = require("path");
8
8
  const os = require("os");
9
9
 
10
- const VERSION = "1.2.1";
10
+ const VERSION = "2.0.1";
11
11
  const API_URL = process.env.ODAI_API_URL || "https://api.0dai.dev";
12
+ const T = process.stdout.isTTY ? "\x1b[38;2;45;212;168m" : ""; // teal
13
+ const R = process.stdout.isTTY ? "\x1b[0m" : ""; // reset
14
+ const D = process.stdout.isTTY ? "\x1b[2m" : ""; // dim
15
+ const log = (msg) => console.log(`${T}[0dai]${R} ${msg}`);
12
16
  const CONFIG_DIR = path.join(os.homedir(), ".0dai");
13
17
  const AUTH_FILE = path.join(CONFIG_DIR, "auth.json");
18
+ const VERSION_CHECK_FILE = path.join(CONFIG_DIR, ".version_check");
14
19
 
15
20
  const MANIFEST_FILES = [
16
21
  "package.json", "go.mod", "pyproject.toml", "requirements.txt",
@@ -136,21 +141,21 @@ function writeFiles(target, files) {
136
141
  } catch { created++; }
137
142
  fs.writeFileSync(p, content, "utf8");
138
143
  }
139
- console.log(`[0dai] ${created} created, ${updated} updated, ${unchanged} unchanged`);
144
+ log(`${created} created, ${updated} updated, ${unchanged} unchanged`);
140
145
  return created + updated;
141
146
  }
142
147
 
143
148
  async function cmdInit(target) {
144
149
  if (fs.existsSync(path.join(target, "ai", "VERSION"))) {
145
150
  const v = fs.readFileSync(path.join(target, "ai", "VERSION"), "utf8").trim();
146
- console.log(`[0dai] ai/ layer already exists (v${v}). Run '0dai sync' to update.`);
151
+ log(`ai/ layer already exists (v${v}). Run '0dai sync' to update.`);
147
152
  return;
148
153
  }
149
154
 
150
- console.log("[0dai] collecting project metadata...");
155
+ log("collecting project metadata...");
151
156
  const { projectFiles, fileContents, clis } = collectMetadata(target);
152
157
 
153
- console.log(`[0dai] sending to API (${projectFiles.length} files, ${clis.length} CLIs)...`);
158
+ log(`sending to API (${projectFiles.length} files, ${clis.length} CLIs)...`);
154
159
  const result = await apiCall("/v1/init", {
155
160
  project_files: projectFiles,
156
161
  file_contents: fileContents,
@@ -159,15 +164,15 @@ async function cmdInit(target) {
159
164
 
160
165
  if (result.error) {
161
166
  if (result.hint) {
162
- console.log(`\n[0dai] ${result.message || result.error}`);
167
+ log(`${result.message || result.error}`);
163
168
  console.log(` ${result.hint}\n`);
164
169
  } else {
165
- console.error(`[0dai] error: ${result.error}`);
170
+ log(`error: ${result.error}`);
166
171
  }
167
172
  process.exit(1);
168
173
  }
169
174
 
170
- console.log(`[0dai] detected: ${result.stack || "?"}`);
175
+ log(`detected: ${result.stack || "?"}`);
171
176
  writeFiles(target, result.files || {});
172
177
 
173
178
  // Add to .gitignore
@@ -177,7 +182,7 @@ async function cmdInit(target) {
177
182
  if (!text.includes(".0dai")) fs.appendFileSync(gi, "\n.0dai/\n");
178
183
  } catch {}
179
184
 
180
- console.log(`[0dai] initialized (${result.file_count || "?"} files)`);
185
+ log(`initialized (${result.file_count || "?"} files)`);
181
186
  console.log(" skills: /build /review /status /feedback /bugfix /delegate");
182
187
  }
183
188
 
@@ -216,24 +221,24 @@ async function cmdSync(target) {
216
221
  current_files: currentFiles, file_contents: fileContents,
217
222
  });
218
223
 
219
- if (result.error) { console.error(`[0dai] error: ${result.error}`); process.exit(1); }
224
+ if (result.error) { log(`error: ${result.error}`); process.exit(1); }
220
225
 
221
226
  const updated = result.files_updated || {};
222
227
  if (Object.keys(updated).length) writeFiles(target, updated);
223
- else console.log("[0dai] already up to date");
228
+ else log("already up to date");
224
229
  }
225
230
 
226
231
  async function cmdDetect(target) {
227
232
  const { projectFiles } = collectMetadata(target);
228
233
  const result = await apiCall("/v1/detect", { files: projectFiles });
229
- if (result.error) { console.error(`[0dai] error: ${result.error}`); return; }
234
+ if (result.error) { log(`error: ${result.error}`); return; }
230
235
  console.log(`stack: ${result.stack || "?"}`);
231
236
  console.log(`clis: ${(result.available_clis || []).join(",")}`);
232
237
  }
233
238
 
234
239
  function cmdDoctor(target) {
235
240
  const ai = path.join(target, "ai");
236
- if (!fs.existsSync(ai)) { console.log("[0dai] no ai/ layer. Run '0dai init' first."); return; }
241
+ if (!fs.existsSync(ai)) { log("no ai/ layer. Run '0dai init' first."); return; }
237
242
  let v = "?";
238
243
  try { v = fs.readFileSync(path.join(ai, "VERSION"), "utf8").trim(); } catch {}
239
244
  const checks = {
@@ -245,7 +250,7 @@ function cmdDoctor(target) {
245
250
  "AGENTS.md": fs.existsSync(path.join(target, "AGENTS.md")),
246
251
  };
247
252
  const ok = Object.values(checks).every(Boolean);
248
- console.log(`[0dai] v${v} — ${ok ? "healthy" : "issues found"}`);
253
+ log(`v${v} — ${ok ? "healthy" : "issues found"}`);
249
254
  for (const [name, exists] of Object.entries(checks)) console.log(` ${exists ? "ok" : "MISSING"}: ${name}`);
250
255
  }
251
256
 
@@ -254,7 +259,7 @@ function cmdStatus(target) {
254
259
  let v = "?", stack = "?";
255
260
  try { v = fs.readFileSync(path.join(ai, "VERSION"), "utf8").trim(); } catch {}
256
261
  try { stack = JSON.parse(fs.readFileSync(path.join(ai, "manifest", "discovery.json"), "utf8")).stack || "?"; } catch {}
257
- console.log(`[0dai] v${v} | stack: ${stack}`);
262
+ log(`v${v} | stack: ${stack}`);
258
263
 
259
264
  const count = (dir) => { try { return fs.readdirSync(dir).filter(f => f.endsWith(".json")).length; } catch { return 0; } };
260
265
  const q = count(path.join(ai, "swarm", "queue"));
@@ -268,15 +273,34 @@ function cmdStatus(target) {
268
273
  } catch {}
269
274
  }
270
275
 
276
+ async function checkVersion() {
277
+ try {
278
+ // Check interval: 1 hour during debug, configurable via env
279
+ const intervalSec = parseInt(process.env.ODAI_UPDATE_CHECK_INTERVAL || "3600");
280
+ let lastCheck = 0;
281
+ try { lastCheck = parseFloat(fs.readFileSync(VERSION_CHECK_FILE, "utf8")); } catch {}
282
+ if (Date.now() / 1000 - lastCheck < intervalSec) return;
283
+
284
+ fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
285
+ fs.writeFileSync(VERSION_CHECK_FILE, String(Date.now() / 1000));
286
+
287
+ const result = await apiCall("/v1/version");
288
+ if (result.version && result.version !== VERSION) {
289
+ log(`Update available: ${VERSION} → ${result.version}`);
290
+ console.log(` Run: npm update -g @0dai-dev/cli\n`);
291
+ }
292
+ } catch {}
293
+ }
294
+
271
295
  async function cmdAuthLogin() {
272
296
  // Step 1: request device code
273
297
  const result = await apiCall("/v1/auth/device", { client_id: "cli" });
274
- if (result.error) { console.error(`[0dai] error: ${result.error}`); process.exit(1); }
298
+ if (result.error) { log(`error: ${result.error}`); process.exit(1); }
275
299
 
276
- console.log(`[0dai] Open this URL in your browser:\n`);
300
+ log(`Open this URL in your browser:\n`);
277
301
  console.log(` ${result.verification_uri}\n`);
278
- console.log(`[0dai] Enter code: ${result.user_code}\n`);
279
- console.log("[0dai] Waiting for authorization...");
302
+ log(`Enter code: ${result.user_code}\n`);
303
+ log("Waiting for authorization...");
280
304
 
281
305
  // Step 2: poll for token
282
306
  const interval = (result.interval || 5) * 1000;
@@ -295,28 +319,28 @@ async function cmdAuthLogin() {
295
319
  authenticated_at: new Date().toISOString(),
296
320
  expires_at: poll.expires_at,
297
321
  }, null, 2) + "\n", { mode: 0o600 });
298
- console.log(`[0dai] Logged in as ${poll.email} (${poll.plan} plan)`);
322
+ log(`Logged in as ${poll.email} (${poll.plan} plan)`);
299
323
  return;
300
324
  }
301
325
  if (poll.error && poll.error !== "authorization_pending") {
302
- console.error(`[0dai] ${poll.error}`);
326
+ log(`${poll.error}`);
303
327
  process.exit(1);
304
328
  }
305
329
  process.stdout.write(".");
306
330
  }
307
- console.error("\n[0dai] Authorization timed out. Try again.");
331
+ log("Authorization timed out. Try again.");
308
332
  process.exit(1);
309
333
  }
310
334
 
311
335
  function cmdAuthLogout() {
312
336
  try { fs.unlinkSync(AUTH_FILE); } catch {}
313
- console.log("[0dai] Logged out");
337
+ log("Logged out");
314
338
  }
315
339
 
316
340
  async function cmdAuthStatus() {
317
341
  try {
318
342
  const auth = JSON.parse(fs.readFileSync(AUTH_FILE, "utf8"));
319
- console.log(`[0dai] ${auth.email} (${auth.plan} plan)`);
343
+ log(`${auth.email} (${auth.plan} plan)`);
320
344
  // Get usage from API
321
345
  const status = await apiCall("/v1/auth/status");
322
346
  if (status.usage_today) {
@@ -325,7 +349,7 @@ async function cmdAuthStatus() {
325
349
  console.log(` ${k}: ${v} / ${status.limits[k]}`);
326
350
  }
327
351
  } catch {
328
- console.log("[0dai] Not logged in. Run: 0dai auth login");
352
+ log("Not logged in. Run: 0dai auth login");
329
353
  }
330
354
  }
331
355
 
@@ -338,6 +362,9 @@ async function main() {
338
362
  const cmd = args[0] || "help";
339
363
  const sub = args[1] || "";
340
364
 
365
+ // Non-blocking version check (runs in background, once per day)
366
+ checkVersion();
367
+
341
368
  switch (cmd) {
342
369
  case "init": await cmdInit(target); break;
343
370
  case "sync": await cmdSync(target); break;
@@ -352,9 +379,9 @@ async function main() {
352
379
  console.log("Usage: 0dai auth [login|logout|status]");
353
380
  }
354
381
  break;
355
- case "--version": console.log(`0dai ${VERSION}`); break;
382
+ case "--version": console.log(`${T}0dai${R} ${VERSION}`); break;
356
383
  case "help": case "--help": case "-h":
357
- console.log(`0dai v${VERSION} — One config for 5 AI agent CLIs\n`);
384
+ console.log(`\n ${T}0dai${R} v${VERSION} — One config for 5 AI agent CLIs\n`);
358
385
  console.log("Commands:");
359
386
  console.log(" init Initialize ai/ layer (via API)");
360
387
  console.log(" sync Update ai/ layer (via API)");
@@ -368,9 +395,9 @@ async function main() {
368
395
  console.log("https://0dai.dev");
369
396
  break;
370
397
  default:
371
- console.error(`[0dai] unknown command: ${cmd}. Run '0dai --help'`);
398
+ log(`unknown command: ${cmd}. Run '0dai --help'`);
372
399
  process.exit(1);
373
400
  }
374
401
  }
375
402
 
376
- main().catch((e) => { console.error(`[0dai] ${e.message}`); process.exit(1); });
403
+ main().catch((e) => { log(`${e.message}`); process.exit(1); });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@0dai-dev/cli",
3
- "version": "1.3.1",
3
+ "version": "2.0.1",
4
4
  "description": "One config layer for 5 AI agent CLIs — Claude Code, Codex, OpenCode, Gemini, Aider",
5
5
  "bin": {
6
6
  "0dai": "./bin/0dai.js"