@0dai-dev/cli 1.2.1 → 1.3.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.
- package/bin/0dai.js +111 -9
- package/package.json +1 -1
package/bin/0dai.js
CHANGED
|
@@ -27,16 +27,37 @@ const PROBE_DIRS = [
|
|
|
27
27
|
"android", "ios", "linux", "macos", "windows",
|
|
28
28
|
];
|
|
29
29
|
|
|
30
|
+
function deviceFingerprint() {
|
|
31
|
+
const crypto = require("crypto");
|
|
32
|
+
const parts = [
|
|
33
|
+
os.hostname(),
|
|
34
|
+
os.userInfo().username,
|
|
35
|
+
os.platform(),
|
|
36
|
+
os.arch(),
|
|
37
|
+
os.cpus().length.toString(),
|
|
38
|
+
os.totalmem().toString(),
|
|
39
|
+
];
|
|
40
|
+
// Try machine-id
|
|
41
|
+
try {
|
|
42
|
+
if (os.platform() === "linux") parts.push(fs.readFileSync("/etc/machine-id", "utf8").trim());
|
|
43
|
+
else if (os.platform() === "darwin") {
|
|
44
|
+
const { execSync } = require("child_process");
|
|
45
|
+
parts.push(execSync("ioreg -rd1 -c IOPlatformExpertDevice | awk '/IOPlatformUUID/'", { encoding: "utf8" }).trim());
|
|
46
|
+
}
|
|
47
|
+
} catch {}
|
|
48
|
+
return crypto.createHash("sha256").update(parts.join(":")).digest("hex").slice(0, 32);
|
|
49
|
+
}
|
|
50
|
+
|
|
30
51
|
function apiCall(endpoint, data) {
|
|
31
52
|
return new Promise((resolve, reject) => {
|
|
32
53
|
const url = new URL(endpoint, API_URL);
|
|
33
54
|
const mod = url.protocol === "https:" ? https : http;
|
|
34
55
|
const body = data ? JSON.stringify(data) : null;
|
|
35
|
-
const headers = { "Content-Type": "application/json" };
|
|
56
|
+
const headers = { "Content-Type": "application/json", "X-Device-ID": deviceFingerprint() };
|
|
36
57
|
|
|
37
58
|
try {
|
|
38
59
|
const auth = JSON.parse(fs.readFileSync(AUTH_FILE, "utf8"));
|
|
39
|
-
const token = auth.api_key || auth.token;
|
|
60
|
+
const token = auth.api_key || auth.access_token || auth.token;
|
|
40
61
|
if (token) headers["Authorization"] = `Bearer ${token}`;
|
|
41
62
|
} catch {}
|
|
42
63
|
|
|
@@ -46,7 +67,7 @@ function apiCall(endpoint, data) {
|
|
|
46
67
|
path: url.pathname,
|
|
47
68
|
method: body ? "POST" : "GET",
|
|
48
69
|
headers,
|
|
49
|
-
timeout:
|
|
70
|
+
timeout: 60000,
|
|
50
71
|
};
|
|
51
72
|
if (body) opts.headers["Content-Length"] = Buffer.byteLength(body);
|
|
52
73
|
|
|
@@ -136,7 +157,15 @@ async function cmdInit(target) {
|
|
|
136
157
|
available_clis: clis,
|
|
137
158
|
});
|
|
138
159
|
|
|
139
|
-
if (result.error) {
|
|
160
|
+
if (result.error) {
|
|
161
|
+
if (result.hint) {
|
|
162
|
+
console.log(`\n[0dai] ${result.message || result.error}`);
|
|
163
|
+
console.log(` ${result.hint}\n`);
|
|
164
|
+
} else {
|
|
165
|
+
console.error(`[0dai] error: ${result.error}`);
|
|
166
|
+
}
|
|
167
|
+
process.exit(1);
|
|
168
|
+
}
|
|
140
169
|
|
|
141
170
|
console.log(`[0dai] detected: ${result.stack || "?"}`);
|
|
142
171
|
writeFiles(target, result.files || {});
|
|
@@ -239,6 +268,67 @@ function cmdStatus(target) {
|
|
|
239
268
|
} catch {}
|
|
240
269
|
}
|
|
241
270
|
|
|
271
|
+
async function cmdAuthLogin() {
|
|
272
|
+
// Step 1: request device code
|
|
273
|
+
const result = await apiCall("/v1/auth/device", { client_id: "cli" });
|
|
274
|
+
if (result.error) { console.error(`[0dai] error: ${result.error}`); process.exit(1); }
|
|
275
|
+
|
|
276
|
+
console.log(`[0dai] Open this URL in your browser:\n`);
|
|
277
|
+
console.log(` ${result.verification_uri}\n`);
|
|
278
|
+
console.log(`[0dai] Enter code: ${result.user_code}\n`);
|
|
279
|
+
console.log("[0dai] Waiting for authorization...");
|
|
280
|
+
|
|
281
|
+
// Step 2: poll for token
|
|
282
|
+
const interval = (result.interval || 5) * 1000;
|
|
283
|
+
const deadline = Date.now() + (result.expires_in || 600) * 1000;
|
|
284
|
+
|
|
285
|
+
while (Date.now() < deadline) {
|
|
286
|
+
await new Promise(r => setTimeout(r, interval));
|
|
287
|
+
const poll = await apiCall("/v1/auth/token", { device_code: result.device_code });
|
|
288
|
+
if (poll.access_token) {
|
|
289
|
+
// Save token
|
|
290
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
|
|
291
|
+
fs.writeFileSync(AUTH_FILE, JSON.stringify({
|
|
292
|
+
access_token: poll.access_token,
|
|
293
|
+
email: poll.email,
|
|
294
|
+
plan: poll.plan || "free",
|
|
295
|
+
authenticated_at: new Date().toISOString(),
|
|
296
|
+
expires_at: poll.expires_at,
|
|
297
|
+
}, null, 2) + "\n", { mode: 0o600 });
|
|
298
|
+
console.log(`[0dai] Logged in as ${poll.email} (${poll.plan} plan)`);
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
if (poll.error && poll.error !== "authorization_pending") {
|
|
302
|
+
console.error(`[0dai] ${poll.error}`);
|
|
303
|
+
process.exit(1);
|
|
304
|
+
}
|
|
305
|
+
process.stdout.write(".");
|
|
306
|
+
}
|
|
307
|
+
console.error("\n[0dai] Authorization timed out. Try again.");
|
|
308
|
+
process.exit(1);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function cmdAuthLogout() {
|
|
312
|
+
try { fs.unlinkSync(AUTH_FILE); } catch {}
|
|
313
|
+
console.log("[0dai] Logged out");
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
async function cmdAuthStatus() {
|
|
317
|
+
try {
|
|
318
|
+
const auth = JSON.parse(fs.readFileSync(AUTH_FILE, "utf8"));
|
|
319
|
+
console.log(`[0dai] ${auth.email} (${auth.plan} plan)`);
|
|
320
|
+
// Get usage from API
|
|
321
|
+
const status = await apiCall("/v1/auth/status");
|
|
322
|
+
if (status.usage_today) {
|
|
323
|
+
console.log(" Usage today:");
|
|
324
|
+
for (const [k, v] of Object.entries(status.usage_today))
|
|
325
|
+
console.log(` ${k}: ${v} / ${status.limits[k]}`);
|
|
326
|
+
}
|
|
327
|
+
} catch {
|
|
328
|
+
console.log("[0dai] Not logged in. Run: 0dai auth login");
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
242
332
|
async function main() {
|
|
243
333
|
const args = process.argv.slice(2);
|
|
244
334
|
let target = process.cwd();
|
|
@@ -246,6 +336,7 @@ async function main() {
|
|
|
246
336
|
if (ti >= 0 && args[ti + 1]) { target = path.resolve(args[ti + 1]); args.splice(ti, 2); }
|
|
247
337
|
|
|
248
338
|
const cmd = args[0] || "help";
|
|
339
|
+
const sub = args[1] || "";
|
|
249
340
|
|
|
250
341
|
switch (cmd) {
|
|
251
342
|
case "init": await cmdInit(target); break;
|
|
@@ -253,15 +344,26 @@ async function main() {
|
|
|
253
344
|
case "detect": await cmdDetect(target); break;
|
|
254
345
|
case "doctor": cmdDoctor(target); break;
|
|
255
346
|
case "status": cmdStatus(target); break;
|
|
347
|
+
case "auth":
|
|
348
|
+
if (sub === "login") await cmdAuthLogin();
|
|
349
|
+
else if (sub === "logout") cmdAuthLogout();
|
|
350
|
+
else if (sub === "status") await cmdAuthStatus();
|
|
351
|
+
else {
|
|
352
|
+
console.log("Usage: 0dai auth [login|logout|status]");
|
|
353
|
+
}
|
|
354
|
+
break;
|
|
256
355
|
case "--version": console.log(`0dai ${VERSION}`); break;
|
|
257
356
|
case "help": case "--help": case "-h":
|
|
258
357
|
console.log(`0dai v${VERSION} — One config for 5 AI agent CLIs\n`);
|
|
259
358
|
console.log("Commands:");
|
|
260
|
-
console.log(" init
|
|
261
|
-
console.log(" sync
|
|
262
|
-
console.log(" detect
|
|
263
|
-
console.log(" doctor
|
|
264
|
-
console.log(" status
|
|
359
|
+
console.log(" init Initialize ai/ layer (via API)");
|
|
360
|
+
console.log(" sync Update ai/ layer (via API)");
|
|
361
|
+
console.log(" detect Show detected stack");
|
|
362
|
+
console.log(" doctor Check health");
|
|
363
|
+
console.log(" status Show maturity, swarm, session");
|
|
364
|
+
console.log(" auth login Authenticate (device code flow)");
|
|
365
|
+
console.log(" auth logout Remove credentials");
|
|
366
|
+
console.log(" auth status Show account and usage");
|
|
265
367
|
console.log(" --version\n");
|
|
266
368
|
console.log("https://0dai.dev");
|
|
267
369
|
break;
|