olakai-cli 0.12.0 → 0.13.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/dist/index.js CHANGED
@@ -14,8 +14,10 @@ import {
14
14
  reconcileCurrentWorkspace,
15
15
  removeEntry,
16
16
  runMonitorInstall
17
- } from "./chunk-ULJXILXR.js";
17
+ } from "./chunk-HDP2NYQI.js";
18
18
  import {
19
+ RateLimitError,
20
+ bulkProvisionAgents,
19
21
  createAgent,
20
22
  createCustomDataConfig,
21
23
  createKpi,
@@ -46,12 +48,19 @@ import {
46
48
  updateKpi,
47
49
  updateWorkflow,
48
50
  validateKpiFormula
49
- } from "./chunk-KNGRF4XU.js";
51
+ } from "./chunk-X25PWFMN.js";
50
52
  import {
53
+ CLAUDE_DIR,
54
+ HOOK_DEFINITIONS,
55
+ OLAKAI_DIR,
56
+ SETTINGS_FILE,
51
57
  findConfiguredWorkspace,
52
58
  getLegacyClaudeMonitorConfigPath,
53
- getMonitorConfigPath
54
- } from "./chunk-KY6OHQZW.js";
59
+ getMonitorConfigFileName,
60
+ getMonitorConfigPath,
61
+ mergeHooksSettings,
62
+ writeJsonFile
63
+ } from "./chunk-KMQAGJWN.js";
55
64
  import {
56
65
  HOSTS,
57
66
  clearToken,
@@ -75,7 +84,6 @@ import {
75
84
  } from "./chunk-AVB4N2UN.js";
76
85
 
77
86
  // src/index.ts
78
- import { createRequire } from "module";
79
87
  import { Command } from "commander";
80
88
 
81
89
  // src/commands/login.ts
@@ -160,7 +168,7 @@ Login failed: ${error.message}`);
160
168
  }
161
169
  }
162
170
  function sleep(ms) {
163
- return new Promise((resolve) => setTimeout(resolve, ms));
171
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
164
172
  }
165
173
 
166
174
  // src/commands/logout.ts
@@ -2324,19 +2332,446 @@ function registerActivityCommand(program2) {
2324
2332
  activity.command("sessions").description("Inspect chat/session decoration status for troubleshooting").requiredOption("--agent-id <id>", "Agent ID (required)").option("--since <date>", "Start date (ISO format)").option("--until <date>", "End date (ISO format)").option("--limit <n>", "Results per page", "20").option("--offset <n>", "Skip first N results", "0").option("--json", "Output as JSON").action(sessionsCommand);
2325
2333
  }
2326
2334
 
2327
- // src/commands/monitor.ts
2335
+ // src/commands/admin.ts
2328
2336
  import * as fs2 from "fs";
2337
+ import * as path2 from "path";
2338
+ var TOOL_AGENT_SOURCE = {
2339
+ "claude-code": "CLAUDE_CODE"
2340
+ };
2341
+ var UNSUPPORTED_TOOL_HINTS = {
2342
+ codex: "~/.codex/config.toml",
2343
+ cursor: "~/.cursor/hooks.json",
2344
+ "gemini-cli": "~/.gemini/settings.json",
2345
+ antigravity: "~/.antigravity/hooks.json"
2346
+ };
2347
+ var MAX_EMAILS = 500;
2348
+ var CHUNK_SIZE = 100;
2349
+ var RATE_LIMIT_FALLBACK_WAIT_SECONDS = 30;
2350
+ var EMAIL_REGEX2 = /^[^\s@,]+@[^\s@,]+\.[^\s@,]+$/;
2351
+ function parseEmailsFile(content) {
2352
+ const lines = content.split(/\r?\n/);
2353
+ const emails = [];
2354
+ const seen = /* @__PURE__ */ new Set();
2355
+ const invalidLines = [];
2356
+ let firstDataLineSeen = false;
2357
+ for (let i = 0; i < lines.length; i++) {
2358
+ const line = lines[i].trim();
2359
+ if (line.length === 0 || line.startsWith("#")) continue;
2360
+ const firstCell = line.split(",")[0].trim().replace(/^["']|["']$/g, "");
2361
+ if (!firstDataLineSeen) {
2362
+ firstDataLineSeen = true;
2363
+ if (!firstCell.includes("@") && (line.includes(",") || /^["']?email\b/i.test(line))) {
2364
+ continue;
2365
+ }
2366
+ }
2367
+ if (firstCell.length === 0 || !EMAIL_REGEX2.test(firstCell)) {
2368
+ invalidLines.push({ lineNumber: i + 1, content: line });
2369
+ continue;
2370
+ }
2371
+ const key = firstCell.toLowerCase();
2372
+ if (seen.has(key)) continue;
2373
+ seen.add(key);
2374
+ emails.push(firstCell);
2375
+ }
2376
+ return { emails, invalidLines };
2377
+ }
2378
+ function bundleDirName(email, taken) {
2379
+ const localPart = email.split("@")[0];
2380
+ const sanitized = localPart.replace(/[^a-zA-Z0-9._-]/g, "-").replace(/^\.+/, "") || "developer";
2381
+ let candidate = sanitized;
2382
+ let suffix = 2;
2383
+ while (taken.has(candidate.toLowerCase())) {
2384
+ candidate = `${sanitized}-${suffix}`;
2385
+ suffix++;
2386
+ }
2387
+ taken.add(candidate.toLowerCase());
2388
+ return candidate;
2389
+ }
2390
+ function chunkEmails(emails, size = CHUNK_SIZE) {
2391
+ const chunks = [];
2392
+ for (let i = 0; i < emails.length; i += size) {
2393
+ chunks.push(emails.slice(i, i + size));
2394
+ }
2395
+ return chunks;
2396
+ }
2397
+ function mergeBulkProvisionResponses(responses) {
2398
+ const merged = {
2399
+ results: [],
2400
+ summary: { requested: 0, created: 0, reused: 0, rotated: 0, errors: 0 }
2401
+ };
2402
+ for (const response of responses) {
2403
+ merged.results.push(...response.results);
2404
+ merged.summary.requested += response.summary.requested;
2405
+ merged.summary.created += response.summary.created;
2406
+ merged.summary.reused += response.summary.reused;
2407
+ merged.summary.rotated += response.summary.rotated;
2408
+ merged.summary.errors += response.summary.errors;
2409
+ }
2410
+ return merged;
2411
+ }
2412
+ function sleep2(ms) {
2413
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
2414
+ }
2415
+ async function provisionChunkWithRetry(payload) {
2416
+ try {
2417
+ return await bulkProvisionAgents(payload);
2418
+ } catch (error) {
2419
+ if (!(error instanceof RateLimitError)) throw error;
2420
+ const waitSeconds = error.retryAfterSeconds ?? RATE_LIMIT_FALLBACK_WAIT_SECONDS;
2421
+ console.error(
2422
+ `Rate limited by the backend \u2014 waiting ${waitSeconds}s before retrying this batch\u2026`
2423
+ );
2424
+ await sleep2(waitSeconds * 1e3);
2425
+ return await bulkProvisionAgents(payload);
2426
+ }
2427
+ }
2428
+ function csvEscape(value) {
2429
+ let cell = value;
2430
+ if (/^[=+\-@\t\r]/.test(cell)) {
2431
+ cell = `'${cell}`;
2432
+ }
2433
+ if (/[",\r\n]/.test(cell)) {
2434
+ return `"${cell.replace(/"/g, '""')}"`;
2435
+ }
2436
+ return cell;
2437
+ }
2438
+ function chmod600(filePath) {
2439
+ try {
2440
+ fs2.chmodSync(filePath, 384);
2441
+ } catch {
2442
+ }
2443
+ }
2444
+ function writeSecretFile(filePath, content) {
2445
+ const dir = path2.dirname(filePath);
2446
+ if (!fs2.existsSync(dir)) {
2447
+ fs2.mkdirSync(dir, { recursive: true });
2448
+ }
2449
+ fs2.writeFileSync(filePath, content, { encoding: "utf-8", mode: 384 });
2450
+ chmod600(filePath);
2451
+ }
2452
+ function writeDeveloperBundle(outDir, dirName, result, monitoringEndpoint) {
2453
+ const bundleRoot = path2.join(outDir, dirName);
2454
+ const settingsPath = path2.join(bundleRoot, CLAUDE_DIR, SETTINGS_FILE);
2455
+ writeJsonFile(settingsPath, {
2456
+ hooks: mergeHooksSettings(void 0, HOOK_DEFINITIONS)
2457
+ });
2458
+ const monitorConfig = {
2459
+ agentId: result.agentId ?? "",
2460
+ apiKey: result.apiKey ?? "",
2461
+ agentName: result.agentName ?? "",
2462
+ source: "claude-code",
2463
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
2464
+ monitoringEndpoint
2465
+ };
2466
+ const configPath = path2.join(
2467
+ bundleRoot,
2468
+ OLAKAI_DIR,
2469
+ getMonitorConfigFileName("claude-code")
2470
+ );
2471
+ writeSecretFile(configPath, JSON.stringify(monitorConfig, null, 2) + "\n");
2472
+ return [settingsPath, configPath];
2473
+ }
2474
+ function writeKeymaps(outDir, results) {
2475
+ const jsonPath = path2.join(outDir, "keymap.json");
2476
+ const csvPath = path2.join(outDir, "keymap.csv");
2477
+ const rows = results.map((r) => ({
2478
+ email: r.email,
2479
+ agentId: r.agentId ?? "",
2480
+ agentName: r.agentName ?? "",
2481
+ status: r.status,
2482
+ apiKey: r.apiKey ?? ""
2483
+ }));
2484
+ writeSecretFile(jsonPath, JSON.stringify(rows, null, 2) + "\n");
2485
+ const csvLines = ["email,agentId,agentName,status,apiKey"];
2486
+ for (const row of rows) {
2487
+ csvLines.push(
2488
+ [row.email, row.agentId, row.agentName, row.status, row.apiKey].map(csvEscape).join(",")
2489
+ );
2490
+ }
2491
+ writeSecretFile(csvPath, csvLines.join("\n") + "\n");
2492
+ return [jsonPath, csvPath];
2493
+ }
2494
+ async function bulkProvisionCommand(options) {
2495
+ try {
2496
+ const tool = options.tool;
2497
+ if (!isToolId(tool)) {
2498
+ console.error(
2499
+ `Unknown tool: "${tool}". Bulk provisioning currently supports: claude-code`
2500
+ );
2501
+ process.exit(1);
2502
+ }
2503
+ const source = TOOL_AGENT_SOURCE[tool];
2504
+ if (!source) {
2505
+ const hint = UNSUPPORTED_TOOL_HINTS[tool];
2506
+ console.error(
2507
+ `Bulk provisioning does not support "${tool}" yet.
2508
+
2509
+ ${tool} keeps its hooks in a global per-machine file${hint ? ` (${hint})` : ""}, not in per-repo config, so an Intune-pushable per-developer bundle isn't possible for it yet. Only claude-code is supported in v1.`
2510
+ );
2511
+ process.exit(1);
2512
+ }
2513
+ const token = getValidToken();
2514
+ if (!token) {
2515
+ console.error("Not logged in. Run 'olakai login' first.");
2516
+ process.exit(1);
2517
+ }
2518
+ const emailsPath = path2.resolve(options.emails);
2519
+ if (!fs2.existsSync(emailsPath)) {
2520
+ console.error(`Emails file not found: ${emailsPath}`);
2521
+ process.exit(1);
2522
+ }
2523
+ const { emails, invalidLines } = parseEmailsFile(
2524
+ fs2.readFileSync(emailsPath, "utf-8")
2525
+ );
2526
+ if (invalidLines.length > 0) {
2527
+ console.error(
2528
+ `Invalid email lines in ${emailsPath} (fix or remove them, '#' comments are allowed):`
2529
+ );
2530
+ for (const { lineNumber, content } of invalidLines) {
2531
+ console.error(` line ${lineNumber}: ${content}`);
2532
+ }
2533
+ process.exit(1);
2534
+ }
2535
+ if (emails.length === 0) {
2536
+ console.error(`No valid emails found in ${emailsPath}.`);
2537
+ process.exit(1);
2538
+ }
2539
+ if (emails.length > MAX_EMAILS) {
2540
+ console.error(
2541
+ `Too many emails: ${emails.length} (max ${MAX_EMAILS} per run). Split the file and run again.`
2542
+ );
2543
+ process.exit(1);
2544
+ }
2545
+ const outDir = path2.resolve(options.out);
2546
+ fs2.mkdirSync(outDir, { recursive: true });
2547
+ const takenDirs = /* @__PURE__ */ new Set();
2548
+ const dirNameByEmail = /* @__PURE__ */ new Map();
2549
+ for (const email of emails) {
2550
+ dirNameByEmail.set(email.toLowerCase(), bundleDirName(email, takenDirs));
2551
+ }
2552
+ if (!options.yes) {
2553
+ const conflicts = [];
2554
+ for (const name of ["keymap.json", "keymap.csv"]) {
2555
+ const p = path2.join(outDir, name);
2556
+ if (fs2.existsSync(p) && fs2.statSync(p).size > 0) conflicts.push(p);
2557
+ }
2558
+ for (const dirName of dirNameByEmail.values()) {
2559
+ const bundleDir = path2.join(outDir, dirName);
2560
+ if (fs2.existsSync(bundleDir)) conflicts.push(bundleDir);
2561
+ }
2562
+ if (conflicts.length > 0) {
2563
+ console.error(
2564
+ "Refusing to overwrite existing output from a previous run (it may hold API keys / already-deployed configs):"
2565
+ );
2566
+ for (const conflict of conflicts) {
2567
+ console.error(` ${conflict}`);
2568
+ }
2569
+ console.error(
2570
+ "Pass --yes to overwrite, or use a fresh --out directory."
2571
+ );
2572
+ process.exit(1);
2573
+ }
2574
+ }
2575
+ const payloadBase = {
2576
+ source,
2577
+ ...options.namePrefix ? { namePrefix: options.namePrefix } : {},
2578
+ rotateExistingKeys: Boolean(options.rotateExistingKeys),
2579
+ setVcsEmailHint: true
2580
+ };
2581
+ const chunks = chunkEmails(emails);
2582
+ const chunkResponses = [];
2583
+ let abort = null;
2584
+ for (let i = 0; i < chunks.length; i++) {
2585
+ const chunk = chunks[i];
2586
+ if (chunks.length > 1) {
2587
+ console.error(
2588
+ `Provisioning batch ${i + 1}/${chunks.length} (${chunk.length} email${chunk.length === 1 ? "" : "s"})\u2026`
2589
+ );
2590
+ }
2591
+ try {
2592
+ chunkResponses.push(
2593
+ await provisionChunkWithRetry({ ...payloadBase, emails: chunk })
2594
+ );
2595
+ } catch (error) {
2596
+ abort = {
2597
+ message: error instanceof Error ? error.message : "Unknown error",
2598
+ unattempted: chunks.slice(i).flat()
2599
+ };
2600
+ break;
2601
+ }
2602
+ }
2603
+ const response = mergeBulkProvisionResponses(chunkResponses);
2604
+ const monitoringEndpoint = `${getBaseUrl()}/api/monitoring/prompt`;
2605
+ const bundlePaths = [];
2606
+ let bundleCount = 0;
2607
+ for (const result of response.results) {
2608
+ if (!result.apiKey) continue;
2609
+ const dirName = dirNameByEmail.get(result.email.toLowerCase()) ?? bundleDirName(result.email, takenDirs);
2610
+ bundlePaths.push(
2611
+ ...writeDeveloperBundle(outDir, dirName, result, monitoringEndpoint)
2612
+ );
2613
+ bundleCount++;
2614
+ }
2615
+ const keymapPaths = response.results.length > 0 ? writeKeymaps(outDir, response.results) : [];
2616
+ let unprocessedPath = null;
2617
+ if (abort && abort.unattempted.length > 0) {
2618
+ unprocessedPath = path2.join(outDir, "unprocessed.txt");
2619
+ fs2.writeFileSync(
2620
+ unprocessedPath,
2621
+ abort.unattempted.join("\n") + "\n",
2622
+ "utf-8"
2623
+ );
2624
+ }
2625
+ if (options.json) {
2626
+ console.log(
2627
+ JSON.stringify(
2628
+ {
2629
+ ...response,
2630
+ bundles: [...bundlePaths, ...keymapPaths],
2631
+ ...abort ? {
2632
+ aborted: {
2633
+ error: abort.message,
2634
+ unattemptedEmails: abort.unattempted,
2635
+ unprocessedPath
2636
+ }
2637
+ } : {}
2638
+ },
2639
+ null,
2640
+ 2
2641
+ )
2642
+ );
2643
+ if (abort || response.summary.errors > 0) {
2644
+ process.exit(1);
2645
+ }
2646
+ return;
2647
+ }
2648
+ const { summary } = response;
2649
+ console.log("");
2650
+ console.log(
2651
+ `Provisioned ${summary.requested} developer${summary.requested === 1 ? "" : "s"} for ${tool}:`
2652
+ );
2653
+ console.log(` created: ${summary.created}`);
2654
+ console.log(` reused: ${summary.reused}`);
2655
+ console.log(` rotated: ${summary.rotated}`);
2656
+ console.log(` errors: ${summary.errors}`);
2657
+ const errorRows = response.results.filter((r) => r.status === "error");
2658
+ if (errorRows.length > 0) {
2659
+ console.log("");
2660
+ console.log("Failed rows:");
2661
+ for (const row of errorRows) {
2662
+ console.log(` ${row.email}: ${row.error ?? "unknown error"}`);
2663
+ }
2664
+ }
2665
+ console.log("");
2666
+ console.log(`\u2713 ${bundleCount} config bundle(s) written to ${outDir}`);
2667
+ if (response.results.length > 0) {
2668
+ console.log(`\u2713 Keymap written to keymap.json / keymap.csv (0600)`);
2669
+ }
2670
+ if (abort) {
2671
+ console.log("");
2672
+ console.log(`\u2717 Provisioning stopped early: ${abort.message}`);
2673
+ console.log(
2674
+ ` ${abort.unattempted.length} email(s) were NOT attempted${unprocessedPath ? ` \u2014 written to ${unprocessedPath}` : ""}.`
2675
+ );
2676
+ if (unprocessedPath) {
2677
+ console.log(
2678
+ ` Re-run with --emails ${unprocessedPath} --out <fresh-dir> to finish the rollout`
2679
+ );
2680
+ console.log(
2681
+ " (use a fresh --out so this run's keymaps aren't overwritten)."
2682
+ );
2683
+ }
2684
+ }
2685
+ if (summary.reused > 0) {
2686
+ console.log("");
2687
+ console.log(
2688
+ `${summary.reused} developer(s) already had an agent, so no bundle was written for them`
2689
+ );
2690
+ console.log(
2691
+ "(plaintext API keys are only returned at creation). Re-run with"
2692
+ );
2693
+ console.log(
2694
+ "--rotate-existing-keys to regenerate their keys and bundles \u2014 but note"
2695
+ );
2696
+ console.log(
2697
+ "rotation INVALIDATES any configs already deployed to their machines."
2698
+ );
2699
+ }
2700
+ if (response.results.length > 0) {
2701
+ console.log("");
2702
+ console.log(
2703
+ "\u26A0 These files contain live API credentials. Package the per-developer"
2704
+ );
2705
+ console.log(
2706
+ " directories into your device-management tool (e.g. Intune) so each"
2707
+ );
2708
+ console.log(
2709
+ ` developer's repo gets its ${CLAUDE_DIR}/${SETTINGS_FILE} and ${OLAKAI_DIR}/${getMonitorConfigFileName("claude-code")},`
2710
+ );
2711
+ console.log(
2712
+ " then DELETE the local copies (including the keymap files)."
2713
+ );
2714
+ }
2715
+ if (abort || summary.errors > 0) {
2716
+ process.exit(1);
2717
+ }
2718
+ } catch (error) {
2719
+ console.error(
2720
+ `Error: ${error instanceof Error ? error.message : "Unknown error"}`
2721
+ );
2722
+ process.exit(1);
2723
+ }
2724
+ }
2725
+ function registerAdminCommand(program2) {
2726
+ const admin = program2.command("admin").description("Admin operations (require the ADMIN role)");
2727
+ const adminMonitor = admin.command("monitor").description("Admin operations for local coding-agent monitoring");
2728
+ adminMonitor.command("bulk-provision").description(
2729
+ "Bulk-provision developer monitoring agents from an email roster and write Intune-ready per-developer config bundles. Large rosters are provisioned in sequential batches of 100 emails. Exits non-zero when any roster row fails to provision or a batch cannot be completed (bundles/keymaps for provisioned rows are still written; unattempted emails go to <out>/unprocessed.txt)."
2730
+ ).requiredOption(
2731
+ "--emails <file>",
2732
+ "Roster file (.txt or .csv): one email per line, or the first CSV column. '#' comments and a header row are skipped."
2733
+ ).requiredOption(
2734
+ "--out <dir>",
2735
+ "Output directory for per-developer bundles + keymap.json/keymap.csv"
2736
+ ).option(
2737
+ "--tool <tool>",
2738
+ "Tool to provision for (v1: claude-code only)",
2739
+ "claude-code"
2740
+ ).option(
2741
+ "--rotate-existing-keys",
2742
+ "Regenerate API keys for developers that already have an agent (invalidates already-deployed configs)"
2743
+ ).option(
2744
+ "--name-prefix <prefix>",
2745
+ "Agent name prefix (backend default applies when omitted)"
2746
+ ).option("--json", "Output the raw response JSON plus written bundle paths").option(
2747
+ "--yes",
2748
+ "Overwrite existing output in --out (non-empty keymap files and/or per-developer bundle directories from a previous run)"
2749
+ ).action(bulkProvisionCommand);
2750
+ }
2751
+
2752
+ // src/commands/monitor.ts
2753
+ import * as fs3 from "fs";
2754
+
2755
+ // src/version.ts
2756
+ import { createRequire } from "module";
2757
+ var require2 = createRequire(import.meta.url);
2758
+ var packageJson = require2("../package.json");
2759
+ function getCliVersion() {
2760
+ return packageJson.version;
2761
+ }
2762
+
2763
+ // src/commands/monitor.ts
2329
2764
  function readStdin(timeoutMs = 3e3) {
2330
- return new Promise((resolve) => {
2765
+ return new Promise((resolve2) => {
2331
2766
  if (process.stdin.isTTY) {
2332
- resolve("");
2767
+ resolve2("");
2333
2768
  return;
2334
2769
  }
2335
2770
  let data = "";
2336
2771
  const timer = setTimeout(() => {
2337
2772
  process.stdin.removeAllListeners();
2338
2773
  process.stdin.destroy();
2339
- resolve(data);
2774
+ resolve2(data);
2340
2775
  }, timeoutMs);
2341
2776
  process.stdin.setEncoding("utf-8");
2342
2777
  process.stdin.on("data", (chunk) => {
@@ -2344,11 +2779,11 @@ function readStdin(timeoutMs = 3e3) {
2344
2779
  });
2345
2780
  process.stdin.on("end", () => {
2346
2781
  clearTimeout(timer);
2347
- resolve(data);
2782
+ resolve2(data);
2348
2783
  });
2349
2784
  process.stdin.on("error", () => {
2350
2785
  clearTimeout(timer);
2351
- resolve(data);
2786
+ resolve2(data);
2352
2787
  });
2353
2788
  });
2354
2789
  }
@@ -2413,7 +2848,7 @@ async function statusCommand(options) {
2413
2848
  return;
2414
2849
  }
2415
2850
  if (plugin.id === "claude-code") {
2416
- const { printClaudeCodeStatus } = await import("./status-IZCIMES2.js");
2851
+ const { printClaudeCodeStatus } = await import("./status-FTC6XEQH.js");
2417
2852
  await printClaudeCodeStatus({ projectRoot: process.cwd() });
2418
2853
  return;
2419
2854
  }
@@ -2462,7 +2897,7 @@ async function disableCommand(options) {
2462
2897
  }
2463
2898
  }
2464
2899
  if (agentIdToDelete) {
2465
- const { deleteAgent: deleteAgent2 } = await import("./api-JBVSHCKY.js");
2900
+ const { deleteAgent: deleteAgent2 } = await import("./api-VIUUUQNR.js");
2466
2901
  try {
2467
2902
  await deleteAgent2(agentIdToDelete);
2468
2903
  console.log(`\u2713 Remote agent ${agentIdToDelete} deleted.`);
@@ -2491,7 +2926,7 @@ async function listCommand6(options) {
2491
2926
  console.log(formatRegistryTable(registry));
2492
2927
  }
2493
2928
  async function doctorCommand(options) {
2494
- const { runDoctor, printDoctorResult, exitCodeForStatus } = await import("./doctor-7Q7SPMVD.js");
2929
+ const { runDoctor, printDoctorResult, exitCodeForStatus } = await import("./doctor-CE634QRJ.js");
2495
2930
  let tool;
2496
2931
  if (!options.all) {
2497
2932
  tool = await resolveToolFromOptions(options.tool, "status");
@@ -2509,7 +2944,7 @@ async function doctorCommand(options) {
2509
2944
  }
2510
2945
  async function repairCommand(options) {
2511
2946
  const tool = await resolveToolFromOptions(options.tool, "init");
2512
- const { runRepair, formatRepairResult, exitCodeForRepair } = await import("./repair-NIKAW7NS.js");
2947
+ const { runRepair, formatRepairResult, exitCodeForRepair } = await import("./repair-ILYFGYX5.js");
2513
2948
  const result = await runRepair({
2514
2949
  tool,
2515
2950
  interactive: isInteractive()
@@ -2569,7 +3004,7 @@ function debugInvalidToolFlag(value, event) {
2569
3004
  { event, tool: value ?? null }
2570
3005
  )}
2571
3006
  `;
2572
- fs2.appendFileSync(logPath, line, "utf-8");
3007
+ fs3.appendFileSync(logPath, line, "utf-8");
2573
3008
  } catch {
2574
3009
  }
2575
3010
  }
@@ -2581,7 +3016,7 @@ async function postMonitoringPayload(result) {
2581
3016
  const log = (event, data) => {
2582
3017
  if (!debug) return;
2583
3018
  try {
2584
- fs2.appendFileSync(
3019
+ fs3.appendFileSync(
2585
3020
  logPath,
2586
3021
  `[${(/* @__PURE__ */ new Date()).toISOString()}] dispatcher/${event}: ${JSON.stringify(data)}
2587
3022
  `,
@@ -2597,11 +3032,13 @@ async function postMonitoringPayload(result) {
2597
3032
  apiKeyPrefix: result.transport.apiKey ? result.transport.apiKey.slice(0, 8) + "..." : null,
2598
3033
  bodyBytes: JSON.stringify([result.payload]).length
2599
3034
  });
3035
+ result.payload.cliVersion = getCliVersion();
2600
3036
  const response = await fetch(result.transport.endpoint, {
2601
3037
  method: "POST",
2602
3038
  headers: {
2603
3039
  "x-api-key": result.transport.apiKey,
2604
- "Content-Type": "application/json"
3040
+ "Content-Type": "application/json",
3041
+ "User-Agent": `olakai-cli/${getCliVersion()}`
2605
3042
  },
2606
3043
  body: JSON.stringify([result.payload]),
2607
3044
  signal: controller.signal
@@ -2893,19 +3330,19 @@ function sectionHeader(title) {
2893
3330
  }
2894
3331
  function noProfileMessage(backend, backendError) {
2895
3332
  if (backendError === "not logged in") {
2896
- return "Run `olakai login` to see your Builder Profile.";
3333
+ return "Run `olakai login` to see your AI Fluency.";
2897
3334
  }
2898
3335
  if (backendError === "session expired") {
2899
3336
  return "Session expired. Run `olakai login` to refresh your credentials.";
2900
3337
  }
2901
3338
  if (backendError === "unreachable") {
2902
- return "Could not reach Olakai. Your Builder Profile will be here once you're back online.";
3339
+ return "Could not reach Olakai. Your AI Fluency will be here once you're back online.";
2903
3340
  }
2904
3341
  if (!backend || !backend.found || !backend.profile) {
2905
3342
  if (backend?.reason === "opted_out") {
2906
- return "Your Builder Profile is turned off. Re-enable it from your Olakai settings to start seeing your digest.";
3343
+ return "Your AI Fluency is turned off. Re-enable it from your Olakai settings to start seeing your digest.";
2907
3344
  }
2908
- return "Not enough coding sessions yet to build your profile. Keep working with monitoring on \u2014 your Builder Profile appears after your first few sessions are analyzed.";
3345
+ return "Not enough coding sessions yet to build your profile. Keep working with monitoring on \u2014 your AI Fluency appears after your first few sessions are analyzed.";
2909
3346
  }
2910
3347
  return null;
2911
3348
  }
@@ -2916,7 +3353,7 @@ function renderHuman(backend, backendError, reportUrl) {
2916
3353
  return;
2917
3354
  }
2918
3355
  const profile = backend.profile;
2919
- console.log(sectionHeader("BUILDER PROFILE"));
3356
+ console.log(sectionHeader("AI FLUENCY"));
2920
3357
  if (profile.identity.displayName) {
2921
3358
  console.log(` ${profile.identity.displayName}`);
2922
3359
  }
@@ -3010,7 +3447,7 @@ async function profileCommand(options) {
3010
3447
  } else {
3011
3448
  backend = result.data;
3012
3449
  }
3013
- const reportUrl = `${getBaseUrl()}/dashboard/coding-iq/builder-profile`;
3450
+ const reportUrl = `${getBaseUrl()}/dashboard/coding-iq/my-ai-fluency`;
3014
3451
  if (options.json) {
3015
3452
  const output = {
3016
3453
  backend,
@@ -3024,7 +3461,7 @@ async function profileCommand(options) {
3024
3461
  }
3025
3462
 
3026
3463
  // src/commands/status.ts
3027
- import * as fs3 from "fs";
3464
+ import * as fs4 from "fs";
3028
3465
  function formatDollars2(cents) {
3029
3466
  return `$${(cents / 100).toFixed(2)}`;
3030
3467
  }
@@ -3058,7 +3495,7 @@ function collectLocalState() {
3058
3495
  tool: entry.tool,
3059
3496
  agentId: entry.agentId,
3060
3497
  workspacePath: entry.path,
3061
- configured: fs3.existsSync(entry.configPath)
3498
+ configured: fs4.existsSync(entry.configPath)
3062
3499
  }));
3063
3500
  }
3064
3501
  async function fetchBackendStatus() {
@@ -3208,10 +3645,8 @@ async function statusCommand2(options) {
3208
3645
  }
3209
3646
 
3210
3647
  // src/index.ts
3211
- var require2 = createRequire(import.meta.url);
3212
- var packageJson = require2("../package.json");
3213
3648
  var program = new Command();
3214
- program.name("olakai").description("Olakai CLI tool").version(packageJson.version).option(
3649
+ program.name("olakai").description("Olakai CLI tool").version(getCliVersion()).option(
3215
3650
  "-e, --env <environment>",
3216
3651
  `Environment to use (${getValidEnvironments().join(", ")})`,
3217
3652
  "production"
@@ -3257,11 +3692,12 @@ registerKpisCommand(program);
3257
3692
  registerCustomDataCommand(program);
3258
3693
  registerActivityCommand(program);
3259
3694
  registerMonitorCommand(program);
3695
+ registerAdminCommand(program);
3260
3696
  program.command("status").description("Show your Olakai monitoring state and personal Coding IQ status").option("--json", "Output as JSON (for machine consumption)").option("--workspace-only", "Show local monitor state only (no backend call)").action(
3261
3697
  (opts) => statusCommand2(opts)
3262
3698
  );
3263
3699
  program.command("profile").description(
3264
- "Show your Olakai Builder Profile \u2014 archetype, dimension scores, and personal ROI"
3700
+ "Show your Olakai AI Fluency \u2014 archetype, dimension scores, and personal ROI"
3265
3701
  ).option("--json", "Output as JSON (for machine consumption)").action((opts) => profileCommand(opts));
3266
3702
  registerProfilesCommand(program);
3267
3703
  program.parse();