@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 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("First-run sequence (canonical):");
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");
@@ -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
- out.reason = /auth|logged in|token/i.test(err)
59
- ? "gh not authenticated run: gh auth login"
60
- : (err.split("\n").slice(-1)[0] || "gh issue list failed");
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
- out.reason = String((e && e.message) || e).split("\n").slice(-1)[0] || "sprint probe failed";
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
  }
@@ -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
- if (manifestContents["pyproject.toml"] || manifestContents["requirements.txt"]) return "python-service";
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
- const claudeMd = `# ${discovery.project_name}\n\nStack: ${stack.join(", ") || "unknown"}\nGenerated by 0dai wizard.\n\n## Commands\n\nSee ai/manifest/ for full configuration.\n`;
189
- const agentsMd = `# Agent Configuration\n\nProject: ${discovery.project_name}\nStack: ${stack.join(", ") || "unknown"}\n\nSee ai/manifest/ for configuration details.\n`;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@0dai-dev/cli",
3
- "version": "4.3.8",
3
+ "version": "4.3.9",
4
4
  "description": "One config layer for seven AI coding agents \u2014 Claude Code, Codex, OpenCode, Gemini, Aider, Qoder, Cursor",
5
5
  "bin": {
6
6
  "0dai": "./bin/0dai.js"