@0dai-dev/cli 2.8.0 → 2.9.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.
Files changed (2) hide show
  1. package/bin/0dai.js +159 -6
  2. package/package.json +1 -1
package/bin/0dai.js CHANGED
@@ -7,7 +7,7 @@ const fs = require("fs");
7
7
  const path = require("path");
8
8
  const os = require("os");
9
9
 
10
- const VERSION = "2.8.0";
10
+ const VERSION = "2.9.0";
11
11
  const API_URL = process.env.ODAI_API_URL || "https://api.0dai.dev";
12
12
  const T = process.stdout.isTTY ? "\x1b[38;2;45;212;168m" : ""; // teal
13
13
  const R = process.stdout.isTTY ? "\x1b[0m" : ""; // reset
@@ -147,6 +147,7 @@ function writeFiles(target, files) {
147
147
 
148
148
  async function cmdInit(target, args = []) {
149
149
  const dryRun = args.includes("--dry-run");
150
+ const minimal = args.includes("--minimal");
150
151
 
151
152
  if (fs.existsSync(path.join(target, "ai", "VERSION"))) {
152
153
  const v = fs.readFileSync(path.join(target, "ai", "VERSION"), "utf8").trim();
@@ -169,6 +170,7 @@ async function cmdInit(target, args = []) {
169
170
  file_contents: fileContents,
170
171
  available_clis: clis,
171
172
  dry_run: dryRun,
173
+ minimal: minimal,
172
174
  });
173
175
 
174
176
  if (result.error) {
@@ -216,6 +218,7 @@ async function cmdInit(target, args = []) {
216
218
 
217
219
  async function cmdSync(target, args = []) {
218
220
  const dryRun = args.includes("--dry-run");
221
+ const quiet = args.includes("--quiet") || args.includes("-q");
219
222
 
220
223
  // Quick local check: skip API if already at current version (unless dry-run)
221
224
  let version = "unknown";
@@ -257,7 +260,7 @@ async function cmdSync(target, args = []) {
257
260
  const result = await apiCall("/v1/sync", {
258
261
  ai_version: version, stack, agents: agents.length ? agents : clis,
259
262
  current_files: currentFiles, file_contents: fileContents,
260
- dry_run: dryRun,
263
+ dry_run: dryRun, quiet,
261
264
  });
262
265
 
263
266
  if (result.error) { log(`error: ${result.error}`); process.exit(1); }
@@ -273,8 +276,16 @@ async function cmdSync(target, args = []) {
273
276
  }
274
277
  return;
275
278
  }
276
- if (Object.keys(updated).length) writeFiles(target, updated);
277
- else log("already up to date");
279
+ const changedCount = Object.keys(updated).length;
280
+ if (changedCount) {
281
+ writeFiles(target, updated);
282
+ if (!quiet) {
283
+ for (const f of Object.keys(updated)) console.log(` ~ ${f}`);
284
+ }
285
+ log(`sync: ${changedCount} file(s) updated`);
286
+ } else {
287
+ log("already up to date");
288
+ }
278
289
  }
279
290
 
280
291
  async function cmdDetect(target) {
@@ -712,6 +723,146 @@ function cmdReflect(target, args) {
712
723
  console.log();
713
724
  }
714
725
 
726
+ function cmdMetrics(target) {
727
+ const ai = path.join(target, "ai");
728
+ const G = "\x1b[32m", W = "\x1b[33m", R2 = "\x1b[0m", D = "\x1b[2m",
729
+ B = "\x1b[34m", T = "\x1b[36m", M = "\x1b[35m";
730
+
731
+ // --- Data sources ---
732
+ let stats = {}, budget = {}, discovery = {};
733
+ try { stats = JSON.parse(fs.readFileSync(path.join(ai, "feedback", ".usage_stats.json"), "utf8")); } catch {}
734
+ try { budget = JSON.parse(fs.readFileSync(path.join(ai, "swarm", "budget.json"), "utf8")); } catch {}
735
+ try { discovery = JSON.parse(fs.readFileSync(path.join(ai, "manifest", "discovery.json"), "utf8")); } catch {}
736
+
737
+ const projectName = discovery.project_name || path.basename(target);
738
+ const stack = discovery.stack || "?";
739
+ const totalSessions = stats.total_sessions || 0;
740
+ const agentBreakdown = stats.agents || {};
741
+ const layerInfo = stats.layer || {};
742
+ const lastSession = stats.last_session ? new Date(stats.last_session) : null;
743
+ const layerVersion = stats.version || "?";
744
+
745
+ // Swarm tasks: count done files
746
+ let tasksDone = 0, tasksQueue = 0;
747
+ const doneDir = path.join(ai, "swarm", "done");
748
+ const queueDir = path.join(ai, "swarm", "queue");
749
+ try { tasksDone = fs.readdirSync(doneDir).filter(f => f.endsWith(".json")).length; } catch {}
750
+ try { tasksQueue = fs.readdirSync(queueDir).filter(f => f.endsWith(".json")).length; } catch {}
751
+
752
+ // Activity: count events from activity.jsonl
753
+ let activityEvents = 0;
754
+ try {
755
+ const lines = fs.readFileSync(path.join(ai, "swarm", "activity.jsonl"), "utf8").trim().split("\n").filter(Boolean);
756
+ activityEvents = lines.length;
757
+ } catch {}
758
+
759
+ // Feedback submissions
760
+ let feedbackCount = layerInfo.feedback_reports || 0;
761
+
762
+ // Budget totals
763
+ const totalSpent = budget.total_spent || 0;
764
+ const sessionsWithBudget = Object.keys(budget.sessions || {}).length;
765
+
766
+ // --- Effectiveness score (0-100) ---
767
+ let score = 0, scoreNotes = [];
768
+
769
+ // Sessions depth: 1 = tried, 3 = habit forming, 7 = regular use
770
+ const sessionScore = Math.min(Math.floor((totalSessions / 7) * 35), 35);
771
+ score += sessionScore;
772
+ if (totalSessions === 0) scoreNotes.push("not started");
773
+ else if (totalSessions === 1) scoreNotes.push("first session");
774
+ else if (totalSessions < 3) scoreNotes.push("early");
775
+ else if (totalSessions < 7) scoreNotes.push("habit forming");
776
+ else scoreNotes.push("regular use");
777
+
778
+ // Delegation: did they delegate to swarm?
779
+ const delegationScore = tasksDone > 0 ? Math.min(Math.floor((tasksDone / 5) * 30), 30) : 0;
780
+ score += delegationScore;
781
+ if (tasksDone > 0) scoreNotes.push(`${tasksDone} tasks delegated`);
782
+
783
+ // Feedback: submitted = trust signal
784
+ const feedbackScore = feedbackCount > 0 ? 20 : 0;
785
+ score += feedbackScore;
786
+ if (feedbackCount > 0) scoreNotes.push("feedback submitted");
787
+
788
+ // Layer completeness: has playbooks and commands?
789
+ const layerScore = (layerInfo.playbooks && layerInfo.commands) ? 15 : (layerInfo.commands ? 8 : 0);
790
+ score += layerScore;
791
+
792
+ const scoreColor = score >= 70 ? G : score >= 40 ? W : "\x1b[31m";
793
+ const bar = "█".repeat(Math.round(score / 5)).padEnd(20, "░");
794
+
795
+ // --- Output ---
796
+ console.log(`\n ${T}Metrics${R2} ${D}${projectName} · ${stack} · ai v${layerVersion}${R2}\n`);
797
+
798
+ // Effectiveness score
799
+ console.log(` ${B}Effectiveness${R2}`);
800
+ console.log(` ${scoreColor}${score}/100${R2} ${D}${bar}${R2}`);
801
+ if (scoreNotes.length) console.log(` ${D}${scoreNotes.join(" · ")}${R2}`);
802
+
803
+ // Adoption funnel
804
+ console.log(`\n ${B}Adoption funnel${R2}`);
805
+ const funnelStep = (label, value, done, hint) => {
806
+ const icon = done ? `${G}✓${R2}` : `${D}○${R2}`;
807
+ const val = value !== null ? ` ${D}${value}${R2}` : "";
808
+ const h = !done && hint ? ` ${D}← ${hint}${R2}` : "";
809
+ console.log(` ${icon} ${label}${val}${h}`);
810
+ };
811
+ funnelStep("Initialized", `ai/ v${layerVersion}`, true);
812
+ funnelStep("Returned (>1 session)", `${totalSessions} total`, totalSessions > 1, "run 0dai reflect after each session");
813
+ funnelStep("Used swarm delegation", tasksDone > 0 ? `${tasksDone} tasks done` : null, tasksDone > 0, "try: 0dai swarm add --task '...' --to codex");
814
+ funnelStep("Submitted feedback", feedbackCount > 0 ? `${feedbackCount} reports` : null, feedbackCount > 0, "0dai feedback log + push");
815
+
816
+ // Session stats
817
+ if (totalSessions > 0) {
818
+ console.log(`\n ${B}Sessions${R2}`);
819
+ console.log(` Total ${totalSessions}`);
820
+ if (lastSession) {
821
+ const daysAgo = Math.floor((Date.now() - lastSession.getTime()) / 86400000);
822
+ const when = daysAgo === 0 ? "today" : daysAgo === 1 ? "yesterday" : `${daysAgo}d ago`;
823
+ console.log(` Last ${when}`);
824
+ }
825
+ const agentEntries = Object.entries(agentBreakdown).sort((a, b) => b[1] - a[1]);
826
+ if (agentEntries.length) {
827
+ console.log(` Agents ${agentEntries.map(([a, n]) => `${a}: ${n}`).join(" ")}`);
828
+ }
829
+ if (sessionsWithBudget > 0 && totalSpent > 0) {
830
+ console.log(` Cost $${totalSpent.toFixed(4)} total ${D}(${sessionsWithBudget} sessions tracked)${R2}`);
831
+ }
832
+ }
833
+
834
+ // Delegation stats
835
+ if (tasksDone > 0 || tasksQueue > 0) {
836
+ console.log(`\n ${B}Delegation${R2}`);
837
+ if (tasksDone > 0) console.log(` Done ${G}${tasksDone}${R2}`);
838
+ if (tasksQueue > 0) console.log(` Queue ${W}${tasksQueue}${R2}`);
839
+ if (activityEvents > 0) console.log(` Events ${activityEvents}`);
840
+ }
841
+
842
+ // Layer health
843
+ console.log(`\n ${B}ai/ layer${R2}`);
844
+ const checks = [
845
+ ["commands.yaml", layerInfo.commands],
846
+ ["playbooks", layerInfo.playbooks],
847
+ ["personas", layerInfo.personas],
848
+ ["session roaming", layerInfo.session_active],
849
+ ["swarm queue", (layerInfo.swarm_queue || 0) > 0],
850
+ ];
851
+ for (const [label, ok] of checks) {
852
+ const icon = ok ? `${G}✓${R2}` : `${D}—${R2}`;
853
+ console.log(` ${icon} ${label}`);
854
+ }
855
+
856
+ // Next suggested action
857
+ console.log(`\n ${B}Next${R2}`);
858
+ if (totalSessions === 0) console.log(` ${D}Start a Claude Code session — session_start hook will print project context${R2}`);
859
+ else if (tasksDone === 0) console.log(` ${D}Try delegating a task: 0dai swarm add --task "write tests for auth module" --to codex${R2}`);
860
+ else if (feedbackCount === 0) console.log(` ${D}Submit feedback: 0dai feedback log --type positive --detail "what worked"${R2}`);
861
+ else console.log(` ${D}Score ${score}/100 — keep delegating and submitting feedback${R2}`);
862
+
863
+ console.log();
864
+ }
865
+
715
866
  function cmdStatus(target) {
716
867
  const ai = path.join(target, "ai");
717
868
  let v = "?", stack = "?";
@@ -1245,6 +1396,7 @@ async function main() {
1245
1396
  case "doctor": cmdDoctor(target); break;
1246
1397
  case "validate": cmdValidate(target); break;
1247
1398
  case "reflect": cmdReflect(target, args); break;
1399
+ case "metrics": cmdMetrics(target); break;
1248
1400
  case "status": cmdStatus(target); break;
1249
1401
  case "auth":
1250
1402
  if (sub === "login") await cmdAuthLogin();
@@ -1285,12 +1437,13 @@ async function main() {
1285
1437
  console.log(`\n ${T}0dai${R} v${VERSION} — One config for 5 AI agent CLIs\n`);
1286
1438
  console.log("Commands:");
1287
1439
  console.log(" audit Scan ai/ and agent configs for leaked secrets");
1288
- console.log(" init Initialize ai/ layer (via API) [--dry-run]");
1289
- console.log(" sync Update ai/ layer (via API) [--dry-run]");
1440
+ console.log(" init Initialize ai/ layer (via API) [--dry-run] [--minimal]");
1441
+ console.log(" sync Update ai/ layer (via API) [--dry-run] [--quiet]");
1290
1442
  console.log(" detect Show detected stack");
1291
1443
  console.log(" doctor Check health + credentials checklist");
1292
1444
  console.log(" validate Validate ai/ layer completeness");
1293
1445
  console.log(" reflect Session reflection: delivered, delegation rate, blockers");
1446
+ console.log(" metrics Effectiveness score: adoption funnel, sessions, delegation");
1294
1447
  console.log(" status Show maturity, swarm, session");
1295
1448
  console.log(" session save Save session for roaming");
1296
1449
  console.log(" swarm status Task queue & delegation");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@0dai-dev/cli",
3
- "version": "2.8.0",
3
+ "version": "2.9.0",
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"