@0dai-dev/cli 4.3.8 → 4.3.9
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 +5 -1
- package/lib/commands/status.js +12 -4
- package/lib/utils/identity.js +19 -2
- package/lib/wizard.js +45 -3
- package/package.json +1 -1
package/bin/0dai.js
CHANGED
|
@@ -201,7 +201,11 @@ const { cmdTrust } = require("../lib/commands/trust");
|
|
|
201
201
|
function printHelp() {
|
|
202
202
|
const counts = loadCanonicalCounts();
|
|
203
203
|
console.log(`\n ${T}0dai${R} v${VERSION} — One config for ${counts.agent_clis_total} AI agent CLIs · ${mcpToolsLabel(counts)}\n`);
|
|
204
|
-
console.log("
|
|
204
|
+
console.log("Try it now — no account, no network:");
|
|
205
|
+
console.log(" 0dai init --local --dry-run # preview configs for this repo, writes nothing");
|
|
206
|
+
console.log(" 0dai init --local # generate them on disk, still no account");
|
|
207
|
+
console.log("");
|
|
208
|
+
console.log("First-run sequence (canonical — full per-CLI configs + sync):");
|
|
205
209
|
console.log(" npm install -g @0dai-dev/cli # install once, globally");
|
|
206
210
|
console.log(" 0dai auth login # sign in (OAuth / device code)");
|
|
207
211
|
console.log(" 0dai activate free # claim free-tier license");
|
package/lib/commands/status.js
CHANGED
|
@@ -55,9 +55,14 @@ function collectSprintPayload(target, options = {}) {
|
|
|
55
55
|
out.available = true;
|
|
56
56
|
if (r.status !== 0) {
|
|
57
57
|
const err = String(r.stderr || "").trim();
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
const lastLine = (err.split("\n").slice(-1)[0] || "").replace(/^(fatal:|error:|failed to run git:)\s*/i, "");
|
|
59
|
+
if (/not a git repository/i.test(err)) {
|
|
60
|
+
out.reason = "not a git repository — run inside a repo";
|
|
61
|
+
} else if (/auth|logged in|token/i.test(err)) {
|
|
62
|
+
out.reason = "gh not authenticated — run: gh auth login";
|
|
63
|
+
} else {
|
|
64
|
+
out.reason = lastLine || "gh issue list failed";
|
|
65
|
+
}
|
|
61
66
|
return out;
|
|
62
67
|
}
|
|
63
68
|
out.authenticated = true;
|
|
@@ -66,7 +71,10 @@ function collectSprintPayload(target, options = {}) {
|
|
|
66
71
|
? parsed.map((i) => ({ number: i.number, title: i.title || "" }))
|
|
67
72
|
: [];
|
|
68
73
|
} catch (e) {
|
|
69
|
-
|
|
74
|
+
const msg = (String((e && e.message) || e).split("\n").slice(-1)[0] || "").replace(/^(fatal:|error:)\s*/i, "");
|
|
75
|
+
out.reason = /not a git repository/i.test(msg)
|
|
76
|
+
? "not a git repository — run inside a repo"
|
|
77
|
+
: (msg || "sprint probe failed");
|
|
70
78
|
}
|
|
71
79
|
return out;
|
|
72
80
|
}
|
package/lib/utils/identity.js
CHANGED
|
@@ -314,17 +314,34 @@ function inferProjectName(target, manifestContents) {
|
|
|
314
314
|
}
|
|
315
315
|
|
|
316
316
|
function detectStackHint(projectFiles, manifestContents) {
|
|
317
|
+
// Config-file signals first (strongest).
|
|
317
318
|
if (manifestContents["next.config.js"] || manifestContents["next.config.mjs"] || manifestContents["next.config.ts"]) return "nextjs";
|
|
319
|
+
// Flutter: a pubspec.yaml that pulls the flutter SDK (a plain Dart pubspec has no flutter dep).
|
|
320
|
+
if (manifestContents["pubspec.yaml"] && /(^|\n)\s*flutter\s*:|sdk:\s*flutter/i.test(manifestContents["pubspec.yaml"])) return "flutter";
|
|
321
|
+
|
|
322
|
+
// Node deps fallback (#4493): when config files are absent, infer the stack from
|
|
323
|
+
// package.json dependencies so an account-free `init --local` still gets a real stack
|
|
324
|
+
// instead of "unknown". Order matters — react-native ships react, so it must win first.
|
|
318
325
|
if (manifestContents["package.json"]) {
|
|
319
326
|
try {
|
|
320
327
|
const pkg = JSON.parse(manifestContents["package.json"]);
|
|
321
328
|
const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
|
|
329
|
+
if (deps["react-native"] || deps.expo) return "react-native";
|
|
322
330
|
if (deps.next) return "nextjs";
|
|
323
|
-
if (deps.react || deps.vue || deps.svelte) return "frontend";
|
|
331
|
+
if (deps.react || deps.vue || deps.svelte || deps["@angular/core"]) return "frontend";
|
|
332
|
+
if (deps.express || deps.fastify || deps.koa || deps["@nestjs/core"] || deps["@hapi/hapi"]) return "backend-api";
|
|
324
333
|
} catch {}
|
|
325
334
|
}
|
|
335
|
+
|
|
326
336
|
if (manifestContents["go.mod"]) return "go-service";
|
|
327
|
-
|
|
337
|
+
|
|
338
|
+
// Python: a data/ML signal wins over the generic python service hint.
|
|
339
|
+
if (manifestContents["pyproject.toml"] || manifestContents["requirements.txt"]) {
|
|
340
|
+
const pyText = `${manifestContents["requirements.txt"] || ""}\n${manifestContents["pyproject.toml"] || ""}`;
|
|
341
|
+
if (/\b(pandas|numpy|scikit-learn|sklearn|torch|tensorflow|keras|jupyter|transformers)\b/i.test(pyText)) return "data-ml";
|
|
342
|
+
return "python-service";
|
|
343
|
+
}
|
|
344
|
+
|
|
328
345
|
if (projectFiles.some((name) => name.startsWith("apps/") || name.startsWith("packages/"))) return "monorepo";
|
|
329
346
|
return "unknown";
|
|
330
347
|
}
|
package/lib/wizard.js
CHANGED
|
@@ -149,6 +149,41 @@ async function stepDetect(rl, target) {
|
|
|
149
149
|
return detected;
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
+
// Best-effort offline command detection for the account-free scaffold (#4493 follow-up).
|
|
153
|
+
// Surfaces real, runnable commands so the generated CLAUDE.md/AGENTS.md is useful without
|
|
154
|
+
// an account. Only emits commands actually present (package.json scripts / Makefile) or
|
|
155
|
+
// safe stack conventions — never invents project-specific commands.
|
|
156
|
+
function detectProjectCommands(target, stack = []) {
|
|
157
|
+
const cmds = [];
|
|
158
|
+
const seen = new Set();
|
|
159
|
+
const add = (cmd) => { if (cmd && !seen.has(cmd)) { seen.add(cmd); cmds.push(cmd); } };
|
|
160
|
+
try {
|
|
161
|
+
const pkgPath = path.join(target, "package.json");
|
|
162
|
+
if (fs.existsSync(pkgPath)) {
|
|
163
|
+
const scripts = JSON.parse(fs.readFileSync(pkgPath, "utf8")).scripts || {};
|
|
164
|
+
for (const name of ["dev", "start", "build", "test", "lint", "typecheck"]) {
|
|
165
|
+
if (scripts[name]) add(`npm run ${name}`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
} catch {}
|
|
169
|
+
try {
|
|
170
|
+
const mkPath = path.join(target, "Makefile");
|
|
171
|
+
if (fs.existsSync(mkPath)) {
|
|
172
|
+
const targets = fs.readFileSync(mkPath, "utf8").split("\n")
|
|
173
|
+
.map((l) => (l.match(/^([a-zA-Z][\w-]*):/) || [])[1]).filter(Boolean);
|
|
174
|
+
for (const t of ["build", "test", "lint", "run", "dev"]) {
|
|
175
|
+
if (targets.includes(t)) add(`make ${t}`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
} catch {}
|
|
179
|
+
if (!cmds.length) {
|
|
180
|
+
const s = stack[0] || "";
|
|
181
|
+
if (s === "go-service") { add("go build ./..."); add("go test ./..."); }
|
|
182
|
+
else if (s === "python-service" || s === "fastapi" || s === "data-ml") { add("pytest"); add("ruff check ."); }
|
|
183
|
+
}
|
|
184
|
+
return cmds;
|
|
185
|
+
}
|
|
186
|
+
|
|
152
187
|
function stepGenerate(target, agent, stack) {
|
|
153
188
|
console.log("");
|
|
154
189
|
console.log(" Generating AI agent configs...");
|
|
@@ -184,9 +219,15 @@ function stepGenerate(target, agent, stack) {
|
|
|
184
219
|
);
|
|
185
220
|
ensureLiveManifestDefaults(target);
|
|
186
221
|
|
|
187
|
-
// CLAUDE.md
|
|
188
|
-
|
|
189
|
-
const
|
|
222
|
+
// CLAUDE.md / AGENTS.md — enriched offline scaffold (#4493 follow-up): real detected
|
|
223
|
+
// commands + an honest pointer to the account-gated full config, instead of a stub.
|
|
224
|
+
const stackLabel = stack.join(", ") || "unknown";
|
|
225
|
+
const commands = detectProjectCommands(target, stack);
|
|
226
|
+
const cmdBlock = commands.length
|
|
227
|
+
? commands.map((c) => `- \`${c}\``).join("\n")
|
|
228
|
+
: "_No build/test commands detected. Add them to package.json scripts or a Makefile, then re-run._";
|
|
229
|
+
const claudeMd = `# ${discovery.project_name}\n\nStack: ${stackLabel}\nGenerated offline by \`0dai init --local\`.\n\n## Commands\n\n${cmdBlock}\n\n## Project memory\n\nDurable context (manifest, decisions, roadmap) lives in \`ai/\`. Run \`0dai sync\` with a free account to generate the full per-CLI config set tailored to ${stackLabel}.\n`;
|
|
230
|
+
const agentsMd = `# Agent Configuration\n\nProject: ${discovery.project_name}\nStack: ${stackLabel}\n\n## Commands\n\n${cmdBlock}\n\nProject context lives in \`ai/\`. Run \`0dai sync\` (free account) for the full per-CLI configuration.\n`;
|
|
190
231
|
writeFiles(target, {
|
|
191
232
|
"CLAUDE.md": claudeMd,
|
|
192
233
|
"AGENTS.md": agentsMd,
|
|
@@ -317,5 +358,6 @@ module.exports = {
|
|
|
317
358
|
needsWizard,
|
|
318
359
|
isInteractive,
|
|
319
360
|
stepGenerate,
|
|
361
|
+
detectProjectCommands,
|
|
320
362
|
AGENTS,
|
|
321
363
|
};
|
package/package.json
CHANGED