@0dai-dev/cli 3.10.1 → 4.1.0
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 +4 -2
- package/bin/0dai.js +153 -2154
- package/lib/commands/audit.js +129 -0
- package/lib/commands/auth.js +241 -0
- package/lib/commands/detect.js +31 -0
- package/lib/commands/doctor.js +194 -0
- package/lib/commands/experience.js +65 -0
- package/lib/commands/feedback.js +92 -0
- package/lib/commands/graph.js +270 -0
- package/lib/commands/init.js +327 -0
- package/lib/commands/metrics.js +145 -0
- package/lib/commands/models.js +90 -0
- package/lib/commands/portfolio.js +96 -0
- package/lib/commands/reflect.js +211 -0
- package/lib/commands/report.js +29 -0
- package/lib/commands/run.js +77 -0
- package/lib/commands/session.js +61 -0
- package/lib/commands/status.js +69 -0
- package/lib/commands/swarm.js +199 -0
- package/lib/commands/update.js +69 -0
- package/lib/commands/validate.js +71 -0
- package/lib/commands/watch.js +118 -0
- package/lib/commands/workspace.js +296 -0
- package/lib/onboarding.js +171 -0
- package/lib/shared.js +360 -0
- package/lib/utils/auth.js +142 -0
- package/lib/utils/constants.js +76 -0
- package/lib/utils/identity.js +147 -0
- package/lib/utils/plan.js +73 -0
- package/lib/wizard.js +311 -0
- package/package.json +13 -4
- package/scripts/postinstall.js +29 -0
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const shared = require("../shared");
|
|
3
|
+
const { log, T, R, D, fs, path, spawnSync } = shared;
|
|
4
|
+
const os = require("os");
|
|
5
|
+
|
|
6
|
+
const CONFIG_NAME = "workspace.json";
|
|
7
|
+
|
|
8
|
+
function findConfig(target) {
|
|
9
|
+
const local = path.join(target, ".0dai", CONFIG_NAME);
|
|
10
|
+
if (fs.existsSync(local)) return { file: local, scope: "local" };
|
|
11
|
+
const globalFile = path.join(os.homedir(), ".0dai", CONFIG_NAME);
|
|
12
|
+
if (fs.existsSync(globalFile)) return { file: globalFile, scope: "global" };
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function loadConfig(target) {
|
|
17
|
+
const found = findConfig(target);
|
|
18
|
+
if (!found) return null;
|
|
19
|
+
try { return JSON.parse(fs.readFileSync(found.file, "utf8")); } catch { return null; }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function saveConfig(target, ws, globalFlag) {
|
|
23
|
+
const configFile = globalFlag
|
|
24
|
+
? path.join(os.homedir(), ".0dai", CONFIG_NAME)
|
|
25
|
+
: path.join(target, ".0dai", CONFIG_NAME);
|
|
26
|
+
fs.mkdirSync(path.dirname(configFile), { recursive: true, mode: 0o700 });
|
|
27
|
+
fs.writeFileSync(configFile, JSON.stringify(ws, null, 2) + "\n", { mode: 0o600 });
|
|
28
|
+
return configFile;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function tmux(args) {
|
|
32
|
+
try {
|
|
33
|
+
return spawnSync("tmux", args, { encoding: "utf8", timeout: 5000 });
|
|
34
|
+
} catch { return { status: 1, stderr: "tmux not found" }; }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function sessionName(name) {
|
|
38
|
+
return "0dai-" + name.replace(/[^a-z0-9-]/gi, "-").toLowerCase();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function listTmuxSessions() {
|
|
42
|
+
const r = tmux(["list-sessions", "-F", "#{session_name}\t#{session_created}\t#{pane_pid}"]);
|
|
43
|
+
if (r.status !== 0) return [];
|
|
44
|
+
return r.stdout.trim().split("\n").filter(Boolean).map(line => {
|
|
45
|
+
const [name, created, pid] = line.split("\t");
|
|
46
|
+
return { name, created, pid };
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// --- Auto-detection ---
|
|
51
|
+
|
|
52
|
+
function detectSessions(target) {
|
|
53
|
+
const sessions = [];
|
|
54
|
+
|
|
55
|
+
// API server
|
|
56
|
+
if (fs.existsSync(path.join(target, "scripts", "api_server.py"))) {
|
|
57
|
+
sessions.push({
|
|
58
|
+
name: "api",
|
|
59
|
+
cmd: "python3 api_server.py --public --port 8440",
|
|
60
|
+
dir: "scripts",
|
|
61
|
+
auto_start: true,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Web dev (Next.js, etc.)
|
|
66
|
+
const pkgPath = path.join(target, "web", "package.json");
|
|
67
|
+
if (fs.existsSync(pkgPath)) {
|
|
68
|
+
try {
|
|
69
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
70
|
+
if (pkg.scripts && pkg.scripts.dev) {
|
|
71
|
+
sessions.push({ name: "web", cmd: "npm run dev", dir: "web", auto_start: true });
|
|
72
|
+
}
|
|
73
|
+
} catch {}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Uptime monitor
|
|
77
|
+
if (fs.existsSync(path.join(target, "scripts", "uptime_monitor.py"))) {
|
|
78
|
+
sessions.push({
|
|
79
|
+
name: "monitor",
|
|
80
|
+
cmd: "python3 uptime_monitor.py --url http://localhost:8440",
|
|
81
|
+
dir: "scripts",
|
|
82
|
+
auto_start: false,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Procfile support
|
|
87
|
+
const procfile = path.join(target, "Procfile");
|
|
88
|
+
if (fs.existsSync(procfile)) {
|
|
89
|
+
try {
|
|
90
|
+
const lines = fs.readFileSync(procfile, "utf8").split("\n");
|
|
91
|
+
for (const line of lines) {
|
|
92
|
+
const m = line.match(/^(\w+):\s*(.+)$/);
|
|
93
|
+
if (m) {
|
|
94
|
+
const existing = sessions.find(s => s.name === m[1]);
|
|
95
|
+
if (!existing) {
|
|
96
|
+
sessions.push({ name: m[1], cmd: m[2], dir: ".", auto_start: true });
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
} catch {}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return sessions;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// --- Commands ---
|
|
107
|
+
|
|
108
|
+
function cmdWorkspaceInit(target, args) {
|
|
109
|
+
const globalFlag = args.includes("--global");
|
|
110
|
+
|
|
111
|
+
if (sessions.length === 0) {
|
|
112
|
+
log("no services detected. Create workspace config manually with: 0dai workspace add");
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const ws = {
|
|
117
|
+
name: path.basename(target),
|
|
118
|
+
root: target,
|
|
119
|
+
sessions,
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const configFile = saveConfig(target, ws, globalFlag);
|
|
123
|
+
log(`workspace config created: ${T}${configFile}${R}`);
|
|
124
|
+
log(`${sessions.length} session(s) detected: ${sessions.map(s => s.name).join(", ")}`);
|
|
125
|
+
log(`run: ${T}0dai workspace up${R}`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function cmdWorkspaceAdd(target, args) {
|
|
129
|
+
const nameIdx = args.indexOf("add");
|
|
130
|
+
const name = nameIdx >= 0 ? args[nameIdx + 1] : args[0];
|
|
131
|
+
if (!name) { log("usage: 0dai workspace add <name> --cmd '...' [--dir scripts] [--auto]"); return; }
|
|
132
|
+
|
|
133
|
+
const ws = loadConfig(target) || { name: path.basename(target), sessions: [] };
|
|
134
|
+
const cmdIdx = args.indexOf("--cmd");
|
|
135
|
+
const dirIdx = args.indexOf("--dir");
|
|
136
|
+
const cmd = cmdIdx >= 0 && args[cmdIdx + 1] ? args[cmdIdx + 1] : name;
|
|
137
|
+
const dir = dirIdx >= 0 && args[dirIdx + 1] ? args[dirIdx + 1] : ".";
|
|
138
|
+
const auto = args.includes("--auto");
|
|
139
|
+
|
|
140
|
+
const existing = ws.sessions.findIndex(s => s.name === name);
|
|
141
|
+
const entry = { name, cmd, dir, auto_start: auto };
|
|
142
|
+
if (existing >= 0) ws.sessions[existing] = entry;
|
|
143
|
+
else ws.sessions.push(entry);
|
|
144
|
+
|
|
145
|
+
const found = findConfig(target);
|
|
146
|
+
const configFile = found ? found.file : saveConfig(target, ws, false);
|
|
147
|
+
fs.writeFileSync(configFile, JSON.stringify(ws, null, 2) + "\n", { mode: 0o600 });
|
|
148
|
+
log(`session "${T}${name}${R}" ${existing >= 0 ? "updated" : "added"}`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function cmdWorkspaceRm(target, name) {
|
|
152
|
+
if (!name) { log("usage: 0dai workspace rm <name>"); return; }
|
|
153
|
+
const ws = loadConfig(target);
|
|
154
|
+
if (!ws) { log("no workspace config"); return; }
|
|
155
|
+
ws.sessions = ws.sessions.filter(s => s.name !== name);
|
|
156
|
+
const found = findConfig(target);
|
|
157
|
+
fs.writeFileSync(found.file, JSON.stringify(ws, null, 2) + "\n", { mode: 0o600 });
|
|
158
|
+
log(`session "${T}${name}${R}" removed`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function cmdWorkspaceUp(target, args) {
|
|
162
|
+
const ws = loadConfig(target);
|
|
163
|
+
if (!ws) { log("no workspace config. Run: 0dai workspace init"); return; }
|
|
164
|
+
|
|
165
|
+
// Filter by name if specified
|
|
166
|
+
const filterNames = args.filter(a => !a.startsWith("-"));
|
|
167
|
+
const running = listTmuxSessions();
|
|
168
|
+
|
|
169
|
+
log(`starting workspace "${T}${ws.name}${R}"`);
|
|
170
|
+
|
|
171
|
+
for (const s of ws.sessions) {
|
|
172
|
+
if (!s.auto_start) continue;
|
|
173
|
+
if (filterNames.length && !filterNames.includes(s.name)) continue;
|
|
174
|
+
|
|
175
|
+
const sname = sessionName(s.name);
|
|
176
|
+
if (running.find(x => x.name === sname)) {
|
|
177
|
+
log(`${D}${s.name} already running (pid ${running.find(x => x.name === sname).pid})${R}`);
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const dir = s.dir ? path.join(ws.root || target, s.dir) : (ws.root || target);
|
|
182
|
+
const cmd = `cd ${dir} && ${s.cmd}`;
|
|
183
|
+
const r = tmux(["new-session", "-d", "-s", sname, "bash", "-lc", cmd]);
|
|
184
|
+
if (r.status === 0) log(`${T}✓${R} ${s.name} started`);
|
|
185
|
+
else log(`${R}✗${R} ${s.name} failed: ${r.stderr?.trim() || r.error || "unknown"}`);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (filterNames.length === 0) {
|
|
189
|
+
log(`workspace ready. ${D}Status: 0dai workspace status${R}`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function cmdWorkspaceDown(target, args) {
|
|
194
|
+
const ws = loadConfig(target);
|
|
195
|
+
if (!ws) { log("no workspace config"); return; }
|
|
196
|
+
|
|
197
|
+
const filterNames = args.filter(a => !a.startsWith("-"));
|
|
198
|
+
log(`stopping workspace "${T}${ws.name}${R}"`);
|
|
199
|
+
|
|
200
|
+
for (const s of ws.sessions) {
|
|
201
|
+
if (filterNames.length && !filterNames.includes(s.name)) continue;
|
|
202
|
+
const sname = sessionName(s.name);
|
|
203
|
+
tmux(["kill-session", "-t", sname]);
|
|
204
|
+
log(`${D}${s.name} stopped${R}`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function cmdWorkspaceStatus(target) {
|
|
209
|
+
const ws = loadConfig(target);
|
|
210
|
+
if (!ws) { log("no workspace config. Run: 0dai workspace init"); return; }
|
|
211
|
+
|
|
212
|
+
const running = listTmuxSessions();
|
|
213
|
+
const isTTY = process.stdout.isTTY;
|
|
214
|
+
const G = isTTY ? "\x1b[32m" : "";
|
|
215
|
+
const RD = isTTY ? "\x1b[31m" : "";
|
|
216
|
+
const DIM = isTTY ? "\x1b[2m" : "";
|
|
217
|
+
|
|
218
|
+
console.log(`\n ${T}Workspace: ${ws.name}${R}\n`);
|
|
219
|
+
console.log(` ${"SESSION".padEnd(14)} ${"STATUS".padEnd(10)} ${"PID".padEnd(8)} DIR`);
|
|
220
|
+
console.log(` ${"-".repeat(60)}`);
|
|
221
|
+
|
|
222
|
+
for (const s of ws.sessions) {
|
|
223
|
+
const sname = sessionName(s.name);
|
|
224
|
+
const match = running.find(x => x.name === sname);
|
|
225
|
+
const status = match ? `${G}running${R}` : `${RD}stopped${R}`;
|
|
226
|
+
const pid = match ? match.pid : "—";
|
|
227
|
+
const dir = s.dir || ".";
|
|
228
|
+
console.log(` ${s.name.padEnd(14)} ${status.padEnd(10)} ${String(pid).padEnd(8)} ${DIM}${dir}${R}`);
|
|
229
|
+
}
|
|
230
|
+
console.log();
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function cmdWorkspaceAttach(target, name) {
|
|
234
|
+
if (!name) { log("usage: 0dai workspace attach <name>"); return; }
|
|
235
|
+
const sname = sessionName(name);
|
|
236
|
+
const running = listTmuxSessions();
|
|
237
|
+
if (!running.find(x => x.name === sname)) {
|
|
238
|
+
log(`session "${name}" not running. Run: 0dai workspace up`);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
log(`attaching to ${T}${name}${R}`);
|
|
242
|
+
try {
|
|
243
|
+
const { execSync } = require("child_process");
|
|
244
|
+
execSync(`tmux attach -t ${sname}`, { stdio: "inherit" });
|
|
245
|
+
} catch (e) { log(`error: ${e.message}`); }
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function cmdWorkspaceLogs(target, name, args) {
|
|
249
|
+
if (!name) { log("usage: 0dai workspace logs <name> [-f]"); return; }
|
|
250
|
+
const sname = sessionName(name);
|
|
251
|
+
const running = listTmuxSessions();
|
|
252
|
+
if (!running.find(x => x.name === sname)) { log(`session "${name}" not running`); return; }
|
|
253
|
+
|
|
254
|
+
const follow = args.includes("-f") || args.includes("--follow");
|
|
255
|
+
if (follow) {
|
|
256
|
+
// Live follow mode
|
|
257
|
+
log(`following ${T}${name}${R} logs (Ctrl+C to exit)`);
|
|
258
|
+
try {
|
|
259
|
+
const { execSync } = require("child_process");
|
|
260
|
+
execSync(`tmux capture-pane -t ${sname} -p -e`, { stdio: "inherit" });
|
|
261
|
+
} catch (e) { log(`error: ${e.message}`); }
|
|
262
|
+
} else {
|
|
263
|
+
const r = tmux(["capture-pane", "-t", sname, "-p"]);
|
|
264
|
+
if (r.stdout) {
|
|
265
|
+
const lines = r.stdout.trim().split("\n").slice(-50);
|
|
266
|
+
console.log(lines.join("\n"));
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function cmdWorkspace(target, sub, args) {
|
|
272
|
+
switch (sub) {
|
|
273
|
+
case "init": cmdWorkspaceInit(target, args); break;
|
|
274
|
+
case "up": cmdWorkspaceUp(target, args); break;
|
|
275
|
+
case "down": cmdWorkspaceDown(target, args); break;
|
|
276
|
+
case "status": cmdWorkspaceStatus(target); break;
|
|
277
|
+
case "attach": cmdWorkspaceAttach(target, args[0]); break;
|
|
278
|
+
case "logs": cmdWorkspaceLogs(target, args[0], args); break;
|
|
279
|
+
case "add": cmdWorkspaceAdd(target, args); break;
|
|
280
|
+
case "rm": cmdWorkspaceRm(target, args[0]); break;
|
|
281
|
+
default:
|
|
282
|
+
console.log(`\n ${T}0dai workspace${R} — tmux session manager\n`);
|
|
283
|
+
console.log("Commands:");
|
|
284
|
+
console.log(" workspace init [--global] Create config (auto-detect services)");
|
|
285
|
+
console.log(" workspace up [name...] Start auto_start sessions (filter by name)");
|
|
286
|
+
console.log(" workspace down [name...] Stop sessions (filter by name)");
|
|
287
|
+
console.log(" workspace status Show session status table");
|
|
288
|
+
console.log(" workspace attach <name> Attach to a running session");
|
|
289
|
+
console.log(" workspace logs <name> [-f] Show session output (-f = follow)");
|
|
290
|
+
console.log(" workspace add <name> --cmd '...' [--dir X] [--auto]");
|
|
291
|
+
console.log(" workspace rm <name> Remove session from config");
|
|
292
|
+
console.log();
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
module.exports = { cmdWorkspace };
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Onboarding helpers — post-install welcome, post-init checklist,
|
|
3
|
+
* first-status tip, time-to-init tracking.
|
|
4
|
+
*/
|
|
5
|
+
"use strict";
|
|
6
|
+
|
|
7
|
+
const fs = require("fs");
|
|
8
|
+
const path = require("path");
|
|
9
|
+
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// State file
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
function _statePath(target) {
|
|
15
|
+
return path.join(target, "ai", ".0dai_state.json");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function _loadState(target) {
|
|
19
|
+
try {
|
|
20
|
+
return JSON.parse(fs.readFileSync(_statePath(target), "utf8"));
|
|
21
|
+
} catch {
|
|
22
|
+
return {};
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function _saveState(target, state) {
|
|
27
|
+
try {
|
|
28
|
+
const dir = path.join(target, "ai");
|
|
29
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
30
|
+
fs.writeFileSync(_statePath(target), JSON.stringify(state, null, 2) + "\n");
|
|
31
|
+
} catch {}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
// Post-init What's Next
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
|
|
38
|
+
function showWhatsNext(mode, isAuthed) {
|
|
39
|
+
console.log("");
|
|
40
|
+
console.log(" \u2705 0dai initialized! Your AI agents are configured.");
|
|
41
|
+
console.log("");
|
|
42
|
+
console.log(" What's next:");
|
|
43
|
+
console.log(" \u2610 0dai status \u2014 check your config");
|
|
44
|
+
console.log(" \u2610 0dai doctor \u2014 verify everything works");
|
|
45
|
+
if (mode === "local" && !isAuthed) {
|
|
46
|
+
console.log(" \u2610 0dai auth login \u2014 sign in for full features (optional)");
|
|
47
|
+
} else if (mode === "cloud") {
|
|
48
|
+
console.log(" \u2610 0dai sync \u2014 refresh configs from server");
|
|
49
|
+
}
|
|
50
|
+
console.log("");
|
|
51
|
+
console.log(" Pro features: 0dai upgrade");
|
|
52
|
+
console.log("");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
// First-status tip
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
function showFirstStatusTip(target) {
|
|
60
|
+
const state = _loadState(target);
|
|
61
|
+
if (state.first_status_shown) return;
|
|
62
|
+
console.log("");
|
|
63
|
+
console.log(" \ud83d\udca1 Tip: Run 0dai doctor to check config health");
|
|
64
|
+
state.first_status_shown = true;
|
|
65
|
+
_saveState(target, state);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
// Time-to-init tracking
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
|
|
72
|
+
function trackFirstRun(target) {
|
|
73
|
+
const state = _loadState(target);
|
|
74
|
+
if (!state.first_run_at) {
|
|
75
|
+
state.first_run_at = new Date().toISOString();
|
|
76
|
+
_saveState(target, state);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function trackFirstInit(target) {
|
|
81
|
+
const state = _loadState(target);
|
|
82
|
+
if (!state.first_init_at) {
|
|
83
|
+
state.first_init_at = new Date().toISOString();
|
|
84
|
+
_saveState(target, state);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function getTimeToInit(target) {
|
|
89
|
+
const state = _loadState(target);
|
|
90
|
+
if (!state.first_run_at || !state.first_init_at) return null;
|
|
91
|
+
const start = new Date(state.first_run_at).getTime();
|
|
92
|
+
const end = new Date(state.first_init_at).getTime();
|
|
93
|
+
if (isNaN(start) || isNaN(end)) return null;
|
|
94
|
+
return Math.round((end - start) / 1000);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ---------------------------------------------------------------------------
|
|
98
|
+
// Quickstart
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
|
|
101
|
+
async function cmdQuickstart(target, { cmdDoctor, cmdStatus, cmdInit, log, ensureAuthenticated }) {
|
|
102
|
+
const start = Date.now();
|
|
103
|
+
const steps = 5;
|
|
104
|
+
|
|
105
|
+
// Step 1: Auth
|
|
106
|
+
console.log(`\n [1/${steps}] Checking authentication...`);
|
|
107
|
+
let authInfo = "not signed in";
|
|
108
|
+
try {
|
|
109
|
+
const auth = JSON.parse(fs.readFileSync(path.join(require("os").homedir(), ".0dai", "auth.json"), "utf8"));
|
|
110
|
+
if (auth.access_token) {
|
|
111
|
+
authInfo = `signed in (${auth.plan || "free"} plan)`;
|
|
112
|
+
}
|
|
113
|
+
} catch {}
|
|
114
|
+
console.log(` \u2713 ${authInfo}`);
|
|
115
|
+
|
|
116
|
+
// Step 2: Init
|
|
117
|
+
console.log(` [2/${steps}] Checking project config...`);
|
|
118
|
+
const aiExists = fs.existsSync(path.join(target, "ai", "VERSION"));
|
|
119
|
+
if (aiExists) {
|
|
120
|
+
console.log(" \u2713 ai/ layer found");
|
|
121
|
+
} else {
|
|
122
|
+
console.log(" initializing...");
|
|
123
|
+
try {
|
|
124
|
+
await cmdInit(target, ["--quickstart"]);
|
|
125
|
+
trackFirstInit(target);
|
|
126
|
+
console.log(" \u2713 project initialized");
|
|
127
|
+
} catch (err) {
|
|
128
|
+
console.log(` \u2717 init failed: ${err.message || err}`);
|
|
129
|
+
console.log(" Run: 0dai init");
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Step 3: Doctor
|
|
134
|
+
console.log(` [3/${steps}] Running health check...`);
|
|
135
|
+
if (fs.existsSync(path.join(target, "ai"))) {
|
|
136
|
+
try {
|
|
137
|
+
cmdDoctor(target);
|
|
138
|
+
} catch {}
|
|
139
|
+
console.log(" \u2713 health check complete");
|
|
140
|
+
} else {
|
|
141
|
+
console.log(" \u2717 skipped (no ai/ layer)");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Step 4: Status
|
|
145
|
+
console.log(` [4/${steps}] Project status:`);
|
|
146
|
+
if (fs.existsSync(path.join(target, "ai"))) {
|
|
147
|
+
try {
|
|
148
|
+
cmdStatus(target);
|
|
149
|
+
} catch {}
|
|
150
|
+
} else {
|
|
151
|
+
console.log(" (no project configured)");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Step 5: Summary
|
|
155
|
+
const elapsed = ((Date.now() - start) / 1000).toFixed(1);
|
|
156
|
+
console.log(` [5/${steps}] Ready! (${elapsed}s)`);
|
|
157
|
+
console.log("");
|
|
158
|
+
console.log(" Your project is set up. Try:");
|
|
159
|
+
console.log(" 0dai swarm run --goal \"add auth\" (Pro)");
|
|
160
|
+
console.log(" 0dai graph push (Pro)");
|
|
161
|
+
console.log("");
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
module.exports = {
|
|
165
|
+
showWhatsNext,
|
|
166
|
+
showFirstStatusTip,
|
|
167
|
+
trackFirstRun,
|
|
168
|
+
trackFirstInit,
|
|
169
|
+
getTimeToInit,
|
|
170
|
+
cmdQuickstart,
|
|
171
|
+
};
|