@whenlabs/when 0.9.3 → 0.11.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
@@ -1,13 +1,20 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- findBin
4
- } from "./chunk-2A2EZZF4.js";
3
+ CACHE_DIR,
4
+ CONFIG_FILENAME,
5
+ deriveProject,
6
+ findBin,
7
+ generateDashboard,
8
+ loadConfig,
9
+ runCli,
10
+ writeCache
11
+ } from "./chunk-JI5NCJQ2.js";
5
12
  import {
6
13
  getStatusPath
7
14
  } from "./chunk-4ZVSCJCJ.js";
8
15
 
9
16
  // src/index.ts
10
- import { Command as Command5 } from "commander";
17
+ import { Command as Command10 } from "commander";
11
18
 
12
19
  // src/commands/delegate.ts
13
20
  import { Command } from "commander";
@@ -297,8 +304,9 @@ function createDoctorCommand() {
297
304
  import { Command as Command3 } from "commander";
298
305
  import { spawn as spawn3 } from "child_process";
299
306
  import { resolve as resolve2, dirname as dirname2, basename } from "path";
300
- import { existsSync as existsSync2, readFileSync } from "fs";
307
+ import { existsSync as existsSync2, readFileSync, writeFileSync } from "fs";
301
308
  import { fileURLToPath as fileURLToPath2 } from "url";
309
+ import { stringify } from "yaml";
302
310
  var __dirname2 = dirname2(fileURLToPath2(import.meta.url));
303
311
  var c2 = {
304
312
  reset: "\x1B[0m",
@@ -312,12 +320,6 @@ var c2 = {
312
320
  function colorize2(text, ...codes) {
313
321
  return codes.join("") + text + c2.reset;
314
322
  }
315
- function findBin3(name) {
316
- const pkgRoot = resolve2(__dirname2, "..");
317
- const localBin = resolve2(pkgRoot, "node_modules", ".bin", name);
318
- if (existsSync2(localBin)) return localBin;
319
- return name;
320
- }
321
323
  function detectProject(cwd) {
322
324
  let name = basename(cwd);
323
325
  const pkgPath = resolve2(cwd, "package.json");
@@ -348,9 +350,25 @@ function detectProject(cwd) {
348
350
  }
349
351
  return { name, stack: stacks.length > 0 ? stacks.join(", ") : "unknown" };
350
352
  }
351
- function runTool2(bin, args) {
353
+ function detectLicenseTemplate(cwd) {
354
+ const pkgPath = resolve2(cwd, "package.json");
355
+ if (existsSync2(pkgPath)) {
356
+ try {
357
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
358
+ const license = (pkg.license ?? "").toLowerCase();
359
+ if (["mit", "isc", "apache-2.0", "apache2", "bsd-2-clause", "bsd-3-clause"].some((l) => license.includes(l))) {
360
+ return "opensource";
361
+ }
362
+ if (license) return "commercial";
363
+ } catch {
364
+ }
365
+ }
366
+ return "opensource";
367
+ }
368
+ function runTool2(bin, args, cwd) {
352
369
  return new Promise((resolveP) => {
353
370
  const child = spawn3(bin, args, {
371
+ cwd,
354
372
  env: { ...process.env, FORCE_COLOR: "0", NO_COLOR: "1" }
355
373
  });
356
374
  let stdout = "";
@@ -365,8 +383,75 @@ function runTool2(bin, args) {
365
383
  child.on("close", (code) => resolveP({ stdout, stderr, exitCode: code ?? 1 }));
366
384
  });
367
385
  }
386
+ async function bootstrapConfigs(cwd) {
387
+ const results = [];
388
+ const hasEnv = existsSync2(resolve2(cwd, ".env"));
389
+ const hasEnvSchema = existsSync2(resolve2(cwd, ".env.schema"));
390
+ if (hasEnv && !hasEnvSchema) {
391
+ const { exitCode } = await runTool2(findBin("envalid"), ["init"], cwd);
392
+ if (exitCode === 0) {
393
+ results.push({ label: ".env.schema", action: "created", detail: "Created .env.schema from .env" });
394
+ } else if (exitCode === 127) {
395
+ results.push({ label: ".env.schema", action: "error", detail: "envalid not found" });
396
+ } else {
397
+ results.push({ label: ".env.schema", action: "error", detail: "envalid init failed" });
398
+ }
399
+ } else if (hasEnvSchema) {
400
+ results.push({ label: ".env.schema", action: "skipped", detail: "Skipped (already exists)" });
401
+ } else {
402
+ results.push({ label: ".env.schema", action: "skipped", detail: "Skipped (no .env found)" });
403
+ }
404
+ const hasVowConfig = existsSync2(resolve2(cwd, ".vow.json"));
405
+ if (!hasVowConfig) {
406
+ const template = detectLicenseTemplate(cwd);
407
+ const { exitCode } = await runTool2(findBin("vow"), ["init", "--template", template], cwd);
408
+ if (exitCode === 0) {
409
+ results.push({ label: ".vow.json", action: "created", detail: `Created .vow.json (template: ${template})` });
410
+ } else if (exitCode === 127) {
411
+ results.push({ label: ".vow.json", action: "error", detail: "vow not found" });
412
+ } else {
413
+ results.push({ label: ".vow.json", action: "error", detail: "vow init failed" });
414
+ }
415
+ } else {
416
+ results.push({ label: ".vow.json", action: "skipped", detail: "Skipped (already exists)" });
417
+ }
418
+ const hasStaleConfig = existsSync2(resolve2(cwd, ".stale.yml"));
419
+ let staleScanNeeded = false;
420
+ if (!hasStaleConfig) {
421
+ const { exitCode } = await runTool2(findBin("stale"), ["init"], cwd);
422
+ if (exitCode === 0) {
423
+ results.push({ label: ".stale.yml", action: "created", detail: "Created .stale.yml" });
424
+ staleScanNeeded = true;
425
+ } else if (exitCode === 127) {
426
+ results.push({ label: ".stale.yml", action: "error", detail: "stale not found" });
427
+ } else {
428
+ results.push({ label: ".stale.yml", action: "error", detail: "stale init failed" });
429
+ }
430
+ } else {
431
+ results.push({ label: ".stale.yml", action: "skipped", detail: "Skipped (already exists)" });
432
+ }
433
+ const { exitCode: berthCode } = await runTool2(findBin("berth"), ["register", "--yes", "--dir", cwd], cwd);
434
+ if (berthCode === 0) {
435
+ results.push({ label: "berth ports", action: "created", detail: "Registered project ports" });
436
+ } else if (berthCode === 127) {
437
+ results.push({ label: "berth ports", action: "error", detail: "berth not found" });
438
+ } else {
439
+ results.push({ label: "berth ports", action: "error", detail: "berth register failed" });
440
+ }
441
+ return { results, staleScanNeeded };
442
+ }
443
+ function bootstrapIcon(action) {
444
+ switch (action) {
445
+ case "created":
446
+ return colorize2("+", c2.green);
447
+ case "skipped":
448
+ return colorize2("-", c2.dim);
449
+ case "error":
450
+ return colorize2("!", c2.yellow);
451
+ }
452
+ }
368
453
  async function scanStale(cwd) {
369
- const { stdout, exitCode } = await runTool2(findBin3("stale"), ["scan", "--format", "json", "--path", cwd]);
454
+ const { stdout, exitCode } = await runTool2(findBin("stale"), ["scan", "--format", "json", "--path", cwd]);
370
455
  if (exitCode === 127) return { label: "Doc drift (stale)", status: "error", detail: "stale not found" };
371
456
  try {
372
457
  const json = JSON.parse(stdout);
@@ -377,7 +462,7 @@ async function scanStale(cwd) {
377
462
  }
378
463
  }
379
464
  async function scanEnvalid(cwd) {
380
- const { stdout, exitCode } = await runTool2(findBin3("envalid"), ["validate", "--format", "json"]);
465
+ const { stdout, exitCode } = await runTool2(findBin("envalid"), ["validate", "--format", "json"]);
381
466
  if (exitCode === 127) return { label: "Env validation (envalid)", status: "error", detail: "envalid not found" };
382
467
  if (exitCode === 2 || stdout.includes("not found")) return { label: "Env validation (envalid)", status: "skipped", detail: "No .env.schema \u2014 run `envalid init`" };
383
468
  try {
@@ -389,7 +474,7 @@ async function scanEnvalid(cwd) {
389
474
  }
390
475
  }
391
476
  async function scanBerth(cwd) {
392
- const { stdout, exitCode } = await runTool2(findBin3("berth"), ["check", cwd, "--json"]);
477
+ const { stdout, exitCode } = await runTool2(findBin("berth"), ["check", cwd, "--json"]);
393
478
  if (exitCode === 127) return { label: "Port conflicts (berth)", status: "error", detail: "berth not found" };
394
479
  try {
395
480
  const json = JSON.parse(stdout);
@@ -400,7 +485,7 @@ async function scanBerth(cwd) {
400
485
  }
401
486
  }
402
487
  async function scanVow(cwd) {
403
- const { stdout, exitCode } = await runTool2(findBin3("vow"), ["scan", "--format", "json", "--path", cwd]);
488
+ const { stdout, exitCode } = await runTool2(findBin("vow"), ["scan", "--format", "json", "--path", cwd]);
404
489
  if (exitCode === 127) return { label: "License scan (vow)", status: "error", detail: "vow not found" };
405
490
  const jsonStart = stdout.indexOf("{");
406
491
  const jsonStr = jsonStart >= 0 ? stdout.slice(jsonStart) : stdout;
@@ -418,12 +503,12 @@ async function scanVow(cwd) {
418
503
  async function scanAware(cwd) {
419
504
  const hasConfig = existsSync2(resolve2(cwd, ".aware.json"));
420
505
  if (!hasConfig) {
421
- const { exitCode: exitCode2 } = await runTool2(findBin3("aware"), ["init"]);
506
+ const { exitCode: exitCode2 } = await runTool2(findBin("aware"), ["init", "--force"], cwd);
422
507
  if (exitCode2 === 0) return { label: "AI context (aware)", status: "ok", detail: "Generated .aware.json and context files" };
423
508
  if (exitCode2 === 127) return { label: "AI context (aware)", status: "error", detail: "aware not found" };
424
509
  return { label: "AI context (aware)", status: "skipped", detail: "Could not generate \u2014 run `aware init` manually" };
425
510
  }
426
- const { stdout, stderr, exitCode } = await runTool2(findBin3("aware"), ["doctor"]);
511
+ const { stdout, stderr, exitCode } = await runTool2(findBin("aware"), ["doctor"], cwd);
427
512
  if (exitCode === 127) return { label: "AI context (aware)", status: "error", detail: "aware not found" };
428
513
  const combined = (stdout + stderr).trim();
429
514
  const warnings = combined.split("\n").filter((l) => l.includes("\u26A0") || /warn/i.test(l)).length;
@@ -443,7 +528,7 @@ function statusIcon2(status) {
443
528
  }
444
529
  function createInitCommand() {
445
530
  const cmd = new Command3("init");
446
- cmd.description("Interactive onboarding \u2014 detect stack, run all checks, suggest next steps");
531
+ cmd.description("Interactive onboarding \u2014 detect stack, bootstrap tool configs, run all checks");
447
532
  cmd.action(async () => {
448
533
  const cwd = process.cwd();
449
534
  console.log("");
@@ -454,6 +539,53 @@ function createInitCommand() {
454
539
  console.log(` Stack: ${colorize2(project.stack, c2.cyan)}`);
455
540
  console.log(` Path: ${colorize2(cwd, c2.dim)}`);
456
541
  console.log("");
542
+ process.stdout.write(colorize2(" Bootstrapping tool configs\u2026", c2.dim) + "\n");
543
+ const { results: bootstrapResults, staleScanNeeded } = await bootstrapConfigs(cwd);
544
+ process.stdout.write("\x1B[1A\x1B[2K");
545
+ console.log(colorize2(" Bootstrap", c2.bold));
546
+ console.log(colorize2(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500", c2.dim));
547
+ for (const r of bootstrapResults) {
548
+ const icon = bootstrapIcon(r.action);
549
+ const label = r.label.padEnd(20);
550
+ const detail = r.action === "created" ? colorize2(r.detail, c2.green) : r.action === "error" ? colorize2(r.detail, c2.yellow) : colorize2(r.detail, c2.dim);
551
+ console.log(` ${icon} ${label} ${detail}`);
552
+ }
553
+ const whenlabsConfigPath = resolve2(cwd, CONFIG_FILENAME);
554
+ if (!existsSync2(whenlabsConfigPath)) {
555
+ try {
556
+ const mergedConfig = {};
557
+ const staleConfigPath = resolve2(cwd, ".stale.yml");
558
+ if (existsSync2(staleConfigPath)) {
559
+ mergedConfig["stale"] = {};
560
+ }
561
+ const vowConfigPath = resolve2(cwd, ".vow.json");
562
+ if (existsSync2(vowConfigPath)) {
563
+ try {
564
+ const vowData = JSON.parse(readFileSync(vowConfigPath, "utf-8"));
565
+ mergedConfig["vow"] = {
566
+ ...typeof vowData.policy === "string" ? { policy: vowData.policy } : {},
567
+ ...typeof vowData.production_only === "boolean" ? { production_only: vowData.production_only } : {}
568
+ };
569
+ } catch {
570
+ mergedConfig["vow"] = {};
571
+ }
572
+ }
573
+ const envSchemaPath = resolve2(cwd, ".env.schema");
574
+ if (existsSync2(envSchemaPath)) {
575
+ mergedConfig["envalid"] = { schema: ".env.schema" };
576
+ }
577
+ mergedConfig["berth"] = {};
578
+ mergedConfig["aware"] = {};
579
+ mergedConfig["velocity"] = {};
580
+ writeFileSync(whenlabsConfigPath, stringify(mergedConfig, { lineWidth: 0 }), "utf-8");
581
+ console.log(` ${colorize2("+", c2.green)} ${colorize2(CONFIG_FILENAME, c2.bold)} ${colorize2("created", c2.green)}`);
582
+ } catch {
583
+ console.log(` ${colorize2("!", c2.yellow)} Could not generate ${colorize2(CONFIG_FILENAME, c2.bold)}`);
584
+ }
585
+ } else {
586
+ console.log(` ${colorize2("-", c2.dim)} ${colorize2(CONFIG_FILENAME, c2.bold)} ${colorize2("already exists", c2.dim)}`);
587
+ }
588
+ console.log("");
457
589
  process.stdout.write(colorize2(" Scanning project\u2026", c2.dim) + "\n");
458
590
  const results = await Promise.all([
459
591
  scanStale(cwd),
@@ -471,13 +603,32 @@ function createInitCommand() {
471
603
  const detail = r.status === "ok" ? colorize2(r.detail, c2.green) : r.status === "skipped" ? colorize2(r.detail, c2.dim) : r.status === "error" ? colorize2(r.detail, c2.yellow) : colorize2(r.detail, c2.red);
472
604
  console.log(` ${icon} ${label} ${detail}`);
473
605
  }
606
+ const staleResult = results.find((r) => r.label === "Doc drift (stale)");
607
+ if (staleResult?.status === "issues" || staleScanNeeded) {
608
+ process.stdout.write(colorize2(" Auto-fixing doc drift\u2026", c2.dim) + "\n");
609
+ const { exitCode: fixCode } = await runTool2(findBin("stale"), ["fix", "--apply"], cwd);
610
+ process.stdout.write("\x1B[1A\x1B[2K");
611
+ if (fixCode === 0) {
612
+ console.log(` ${colorize2("\u2713", c2.green)} ${colorize2("Doc drift auto-fixed", c2.green)}`);
613
+ } else if (fixCode === 127) {
614
+ console.log(` ${colorize2("!", c2.yellow)} ${colorize2("stale not found for auto-fix", c2.yellow)}`);
615
+ } else {
616
+ console.log(` ${colorize2("-", c2.dim)} ${colorize2("No high-confidence fixes available", c2.dim)}`);
617
+ }
618
+ }
474
619
  const issueCount = results.filter((r) => r.status === "issues").length;
475
620
  const errorCount = results.filter((r) => r.status === "error").length;
621
+ const bootstrapErrors = bootstrapResults.filter((r) => r.action === "error").length;
622
+ const bootstrapCreated = bootstrapResults.filter((r) => r.action === "created").length;
476
623
  console.log(colorize2(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500", c2.dim));
477
- if (issueCount + errorCount === 0) {
624
+ if (issueCount + errorCount + bootstrapErrors === 0) {
478
625
  console.log(colorize2(" All clear \u2014 project looks healthy!", c2.green, c2.bold));
479
626
  } else {
480
- console.log(colorize2(` ${issueCount} tool(s) found issues, ${errorCount} could not run`, c2.yellow, c2.bold));
627
+ const parts = [];
628
+ if (bootstrapCreated > 0) parts.push(`${bootstrapCreated} config(s) created`);
629
+ if (issueCount > 0) parts.push(`${issueCount} scan(s) found issues`);
630
+ if (errorCount + bootstrapErrors > 0) parts.push(`${errorCount + bootstrapErrors} tool(s) could not run`);
631
+ console.log(colorize2(` ${parts.join(", ")}`, c2.yellow, c2.bold));
481
632
  }
482
633
  console.log("");
483
634
  console.log(colorize2(" Next steps:", c2.bold));
@@ -498,7 +649,7 @@ function createInitCommand() {
498
649
  import { Command as Command4 } from "commander";
499
650
  import { join } from "path";
500
651
  import { homedir } from "os";
501
- import { mkdirSync, writeFileSync } from "fs";
652
+ import { mkdirSync, writeFileSync as writeFileSync2 } from "fs";
502
653
  var STATUS_DIR = join(homedir(), ".whenlabs");
503
654
  function toolResultToStatus(r) {
504
655
  const count = r.issues + r.warnings;
@@ -535,10 +686,10 @@ function writeStatus(results) {
535
686
  },
536
687
  summary: buildSummary(results)
537
688
  };
538
- writeFileSync(getStatusPath(), JSON.stringify(status, null, 2) + "\n");
689
+ writeFileSync2(getStatusPath(), JSON.stringify(status, null, 2) + "\n");
539
690
  }
540
691
  function sleep(ms) {
541
- return new Promise((resolve4) => setTimeout(resolve4, ms));
692
+ return new Promise((resolve7) => setTimeout(resolve7, ms));
542
693
  }
543
694
  function createWatchCommand() {
544
695
  const cmd = new Command4("watch");
@@ -585,16 +736,541 @@ function createWatchCommand() {
585
736
  return cmd;
586
737
  }
587
738
 
588
- // src/index.ts
589
- import { readFileSync as readFileSync2 } from "fs";
590
- import { resolve as resolve3, dirname as dirname3 } from "path";
739
+ // src/commands/config.ts
740
+ import { Command as Command5 } from "commander";
741
+ import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync3 } from "fs";
742
+ import { resolve as resolve3 } from "path";
743
+ import { parse, stringify as stringify2 } from "yaml";
744
+ var c3 = {
745
+ reset: "\x1B[0m",
746
+ bold: "\x1B[1m",
747
+ green: "\x1B[32m",
748
+ yellow: "\x1B[33m",
749
+ red: "\x1B[31m",
750
+ cyan: "\x1B[36m",
751
+ dim: "\x1B[2m"
752
+ };
753
+ function colorize3(text, ...codes) {
754
+ return codes.join("") + text + c3.reset;
755
+ }
756
+ function readExistingToolConfigs(cwd) {
757
+ const config = {};
758
+ const stalePath = resolve3(cwd, ".stale.yml");
759
+ if (existsSync3(stalePath)) {
760
+ try {
761
+ const raw = readFileSync2(stalePath, "utf-8");
762
+ const parsed = parse(raw);
763
+ config.stale = {
764
+ ignore: Array.isArray(parsed?.ignore) ? parsed.ignore : void 0,
765
+ deep: typeof parsed?.deep === "boolean" ? parsed.deep : void 0
766
+ };
767
+ } catch {
768
+ config.stale = {};
769
+ }
770
+ }
771
+ const vowPath = resolve3(cwd, ".vow.json");
772
+ if (existsSync3(vowPath)) {
773
+ try {
774
+ const vow = JSON.parse(readFileSync2(vowPath, "utf-8"));
775
+ config.vow = {
776
+ policy: typeof vow.policy === "string" ? vow.policy : void 0,
777
+ production_only: typeof vow.production_only === "boolean" ? vow.production_only : void 0
778
+ };
779
+ } catch {
780
+ }
781
+ }
782
+ const schemaPath = resolve3(cwd, ".env.schema");
783
+ if (existsSync3(schemaPath)) {
784
+ config.envalid = { schema: ".env.schema" };
785
+ }
786
+ return config;
787
+ }
788
+ function generateDefaultConfig(cwd) {
789
+ const base = readExistingToolConfigs(cwd);
790
+ return {
791
+ stale: base.stale ?? {},
792
+ envalid: base.envalid ?? {},
793
+ vow: base.vow ?? {},
794
+ berth: {},
795
+ aware: {},
796
+ velocity: {}
797
+ };
798
+ }
799
+ function validateConfig(config) {
800
+ const errors = [];
801
+ if (config.stale !== void 0 && typeof config.stale !== "object") {
802
+ errors.push("stale: must be an object");
803
+ }
804
+ if (config.stale?.ignore !== void 0 && !Array.isArray(config.stale.ignore)) {
805
+ errors.push("stale.ignore: must be an array of strings");
806
+ }
807
+ if (config.stale?.deep !== void 0 && typeof config.stale.deep !== "boolean") {
808
+ errors.push("stale.deep: must be a boolean");
809
+ }
810
+ if (config.envalid !== void 0 && typeof config.envalid !== "object") {
811
+ errors.push("envalid: must be an object");
812
+ }
813
+ if (config.envalid?.schema !== void 0 && typeof config.envalid.schema !== "string") {
814
+ errors.push("envalid.schema: must be a string");
815
+ }
816
+ if (config.envalid?.environments !== void 0 && !Array.isArray(config.envalid.environments)) {
817
+ errors.push("envalid.environments: must be an array of strings");
818
+ }
819
+ if (config.vow !== void 0 && typeof config.vow !== "object") {
820
+ errors.push("vow: must be an object");
821
+ }
822
+ if (config.vow?.policy !== void 0 && typeof config.vow.policy !== "string") {
823
+ errors.push("vow.policy: must be a string");
824
+ }
825
+ if (config.vow?.production_only !== void 0 && typeof config.vow.production_only !== "boolean") {
826
+ errors.push("vow.production_only: must be a boolean");
827
+ }
828
+ if (config.berth !== void 0 && typeof config.berth !== "object") {
829
+ errors.push("berth: must be an object");
830
+ }
831
+ if (config.berth?.ports !== void 0) {
832
+ if (typeof config.berth.ports !== "object" || Array.isArray(config.berth.ports)) {
833
+ errors.push("berth.ports: must be a key/value map of port names to numbers");
834
+ } else {
835
+ for (const [k, v] of Object.entries(config.berth.ports)) {
836
+ if (typeof v !== "number") errors.push(`berth.ports.${k}: must be a number`);
837
+ }
838
+ }
839
+ }
840
+ if (config.aware !== void 0 && typeof config.aware !== "object") {
841
+ errors.push("aware: must be an object");
842
+ }
843
+ if (config.aware?.targets !== void 0 && !Array.isArray(config.aware.targets)) {
844
+ errors.push("aware.targets: must be an array of strings");
845
+ }
846
+ if (config.velocity !== void 0 && typeof config.velocity !== "object") {
847
+ errors.push("velocity: must be an object");
848
+ }
849
+ if (config.velocity?.project !== void 0 && typeof config.velocity.project !== "string") {
850
+ errors.push("velocity.project: must be a string");
851
+ }
852
+ return errors;
853
+ }
854
+ function createConfigCommand() {
855
+ const cmd = new Command5("config");
856
+ cmd.description("Manage unified .whenlabs.yml project config");
857
+ cmd.action(() => {
858
+ const cwd = process.cwd();
859
+ const configPath = resolve3(cwd, CONFIG_FILENAME);
860
+ console.log("");
861
+ console.log(colorize3(" when config", c3.bold, c3.cyan));
862
+ console.log(colorize3(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500", c3.dim));
863
+ if (!existsSync3(configPath)) {
864
+ console.log(` ${colorize3("-", c3.dim)} No ${colorize3(CONFIG_FILENAME, c3.bold)} found`);
865
+ console.log(` ${colorize3("\u2022", c3.dim)} Run ${colorize3("when config init", c3.bold)} to generate one`);
866
+ console.log("");
867
+ return;
868
+ }
869
+ const raw = readFileSync2(configPath, "utf-8");
870
+ console.log(` ${colorize3(configPath, c3.dim)}`);
871
+ console.log("");
872
+ for (const line of raw.split("\n")) {
873
+ console.log(` ${line}`);
874
+ }
875
+ });
876
+ const initCmd = new Command5("init");
877
+ initCmd.description(`Generate ${CONFIG_FILENAME} from existing tool configs`);
878
+ initCmd.option("--force", "Overwrite existing config");
879
+ initCmd.action((options) => {
880
+ const cwd = process.cwd();
881
+ const configPath = resolve3(cwd, CONFIG_FILENAME);
882
+ console.log("");
883
+ console.log(colorize3(" when config init", c3.bold, c3.cyan));
884
+ console.log(colorize3(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500", c3.dim));
885
+ if (existsSync3(configPath) && !options.force) {
886
+ console.log(` ${colorize3("-", c3.dim)} ${colorize3(CONFIG_FILENAME, c3.bold)} already exists \u2014 use ${colorize3("--force", c3.bold)} to overwrite`);
887
+ console.log("");
888
+ return;
889
+ }
890
+ const config = generateDefaultConfig(cwd);
891
+ const yaml = stringify2(config, { lineWidth: 0 });
892
+ writeFileSync3(configPath, yaml, "utf-8");
893
+ console.log(` ${colorize3("+", c3.green)} Created ${colorize3(CONFIG_FILENAME, c3.bold)}`);
894
+ console.log("");
895
+ for (const line of yaml.split("\n")) {
896
+ if (line.trim()) console.log(` ${colorize3(line, c3.dim)}`);
897
+ }
898
+ console.log("");
899
+ });
900
+ const validateCmd = new Command5("validate");
901
+ validateCmd.description(`Validate ${CONFIG_FILENAME} structure`);
902
+ validateCmd.action(() => {
903
+ const cwd = process.cwd();
904
+ const configPath = resolve3(cwd, CONFIG_FILENAME);
905
+ console.log("");
906
+ console.log(colorize3(" when config validate", c3.bold, c3.cyan));
907
+ console.log(colorize3(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500", c3.dim));
908
+ if (!existsSync3(configPath)) {
909
+ console.log(` ${colorize3("-", c3.dim)} No ${colorize3(CONFIG_FILENAME, c3.bold)} found \u2014 nothing to validate`);
910
+ console.log("");
911
+ return;
912
+ }
913
+ const config = loadConfig(cwd);
914
+ if (!config) {
915
+ console.log(` ${colorize3("!", c3.yellow)} Could not parse ${colorize3(CONFIG_FILENAME, c3.bold)} \u2014 invalid YAML`);
916
+ console.log("");
917
+ process.exitCode = 1;
918
+ return;
919
+ }
920
+ const errors = validateConfig(config);
921
+ if (errors.length === 0) {
922
+ console.log(` ${colorize3("\u2713", c3.green)} ${colorize3(CONFIG_FILENAME, c3.bold)} is valid`);
923
+ } else {
924
+ for (const err of errors) {
925
+ console.log(` ${colorize3("\u2717", c3.red)} ${err}`);
926
+ }
927
+ process.exitCode = 1;
928
+ }
929
+ console.log("");
930
+ });
931
+ cmd.addCommand(initCmd);
932
+ cmd.addCommand(validateCmd);
933
+ return cmd;
934
+ }
935
+
936
+ // src/commands/upgrade.ts
937
+ import { Command as Command6 } from "commander";
938
+ import { execSync } from "child_process";
939
+ import { readFileSync as readFileSync3 } from "fs";
940
+ import { resolve as resolve4, dirname as dirname3 } from "path";
591
941
  import { fileURLToPath as fileURLToPath3 } from "url";
592
942
  var __dirname3 = dirname3(fileURLToPath3(import.meta.url));
593
- var { version } = JSON.parse(readFileSync2(resolve3(__dirname3, "..", "package.json"), "utf8"));
594
- var program = new Command5();
943
+ var c4 = {
944
+ reset: "\x1B[0m",
945
+ bold: "\x1B[1m",
946
+ green: "\x1B[32m",
947
+ yellow: "\x1B[33m",
948
+ red: "\x1B[31m",
949
+ cyan: "\x1B[36m",
950
+ dim: "\x1B[2m"
951
+ };
952
+ function colorize4(text, ...codes) {
953
+ return codes.join("") + text + c4.reset;
954
+ }
955
+ function parseVersion(v) {
956
+ return v.trim().split(".").map(Number);
957
+ }
958
+ function versionGte(a, b) {
959
+ const pa = parseVersion(a);
960
+ const pb = parseVersion(b);
961
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
962
+ const na = pa[i] ?? 0;
963
+ const nb = pb[i] ?? 0;
964
+ if (na > nb) return true;
965
+ if (na < nb) return false;
966
+ }
967
+ return true;
968
+ }
969
+ function createUpgradeCommand() {
970
+ const cmd = new Command6("upgrade");
971
+ cmd.description("Upgrade @whenlabs/when to the latest version");
972
+ cmd.action(async () => {
973
+ console.log("");
974
+ console.log(colorize4(" when upgrade", c4.bold, c4.cyan));
975
+ console.log(colorize4(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500", c4.dim));
976
+ const pkgPath = resolve4(__dirname3, "..", "..", "package.json");
977
+ let current;
978
+ try {
979
+ const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
980
+ current = pkg.version;
981
+ } catch {
982
+ console.log(` ${colorize4("!", c4.red)} Could not read current version`);
983
+ console.log("");
984
+ process.exitCode = 1;
985
+ return;
986
+ }
987
+ console.log(` ${colorize4("current", c4.dim)} ${colorize4(current, c4.bold)}`);
988
+ let latest;
989
+ try {
990
+ latest = execSync("npm view @whenlabs/when version", { encoding: "utf-8" }).trim();
991
+ } catch {
992
+ console.log(` ${colorize4("!", c4.yellow)} Could not reach npm registry \u2014 check your network connection`);
993
+ console.log("");
994
+ process.exitCode = 1;
995
+ return;
996
+ }
997
+ console.log(` ${colorize4("latest", c4.dim)} ${colorize4(latest, c4.bold)}`);
998
+ console.log("");
999
+ if (versionGte(current, latest)) {
1000
+ console.log(` ${colorize4("\u2713", c4.green)} Already up to date`);
1001
+ console.log("");
1002
+ return;
1003
+ }
1004
+ console.log(` ${colorize4("\u2191", c4.yellow)} Upgrade available: ${colorize4(current, c4.dim)} \u2192 ${colorize4(latest, c4.green + c4.bold)}`);
1005
+ console.log(` ${colorize4("\u2022", c4.dim)} Running: ${colorize4("npm install -g @whenlabs/when@latest", c4.bold)}`);
1006
+ console.log("");
1007
+ try {
1008
+ execSync("npm install -g @whenlabs/when@latest", { stdio: "inherit" });
1009
+ console.log("");
1010
+ console.log(` ${colorize4("\u2713", c4.green)} Upgraded to ${colorize4(latest, c4.bold)}`);
1011
+ } catch {
1012
+ console.log(` ${colorize4("\u2717", c4.red)} Install failed \u2014 try running with sudo or check npm permissions`);
1013
+ process.exitCode = 1;
1014
+ }
1015
+ console.log("");
1016
+ });
1017
+ return cmd;
1018
+ }
1019
+
1020
+ // src/commands/eject.ts
1021
+ import { Command as Command7 } from "commander";
1022
+ import { existsSync as existsSync4, writeFileSync as writeFileSync4, copyFileSync } from "fs";
1023
+ import { resolve as resolve5 } from "path";
1024
+ import { stringify as stringify3 } from "yaml";
1025
+ var c5 = {
1026
+ reset: "\x1B[0m",
1027
+ bold: "\x1B[1m",
1028
+ green: "\x1B[32m",
1029
+ yellow: "\x1B[33m",
1030
+ red: "\x1B[31m",
1031
+ cyan: "\x1B[36m",
1032
+ dim: "\x1B[2m"
1033
+ };
1034
+ function colorize5(text, ...codes) {
1035
+ return codes.join("") + text + c5.reset;
1036
+ }
1037
+ function createEjectCommand() {
1038
+ const cmd = new Command7("eject");
1039
+ cmd.description("Write each tool section of .whenlabs.yml back to its native config format");
1040
+ cmd.option("--force", "Overwrite existing files without prompting");
1041
+ cmd.action((options) => {
1042
+ const cwd = process.cwd();
1043
+ console.log("");
1044
+ console.log(colorize5(" when eject", c5.bold, c5.cyan));
1045
+ console.log(colorize5(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500", c5.dim));
1046
+ const config = loadConfig(cwd);
1047
+ if (!config) {
1048
+ console.log(` ${colorize5("!", c5.yellow)} No ${colorize5(".whenlabs.yml", c5.bold)} found \u2014 nothing to eject`);
1049
+ console.log(` ${colorize5("\u2022", c5.dim)} Run ${colorize5("when config init", c5.bold)} to generate one first`);
1050
+ console.log("");
1051
+ return;
1052
+ }
1053
+ let ejected = 0;
1054
+ let skipped = 0;
1055
+ if (config.stale && Object.keys(config.stale).length > 0) {
1056
+ const dest = resolve5(cwd, ".stale.yml");
1057
+ if (existsSync4(dest) && !options.force) {
1058
+ console.log(` ${colorize5("!", c5.yellow)} ${colorize5(".stale.yml", c5.bold)} already exists \u2014 use ${colorize5("--force", c5.bold)} to overwrite`);
1059
+ skipped++;
1060
+ } else {
1061
+ const yaml = stringify3(config.stale, { lineWidth: 0 });
1062
+ writeFileSync4(dest, yaml, "utf-8");
1063
+ console.log(` ${colorize5("+", c5.green)} Wrote ${colorize5(".stale.yml", c5.bold)}`);
1064
+ ejected++;
1065
+ }
1066
+ } else if (config.stale !== void 0) {
1067
+ console.log(` ${colorize5("-", c5.dim)} stale: empty config \u2014 skipping .stale.yml`);
1068
+ }
1069
+ if (config.vow && Object.keys(config.vow).length > 0) {
1070
+ const dest = resolve5(cwd, ".vow.json");
1071
+ if (existsSync4(dest) && !options.force) {
1072
+ console.log(` ${colorize5("!", c5.yellow)} ${colorize5(".vow.json", c5.bold)} already exists \u2014 use ${colorize5("--force", c5.bold)} to overwrite`);
1073
+ skipped++;
1074
+ } else {
1075
+ writeFileSync4(dest, JSON.stringify(config.vow, null, 2) + "\n", "utf-8");
1076
+ console.log(` ${colorize5("+", c5.green)} Wrote ${colorize5(".vow.json", c5.bold)}`);
1077
+ ejected++;
1078
+ }
1079
+ } else if (config.vow !== void 0) {
1080
+ console.log(` ${colorize5("-", c5.dim)} vow: empty config \u2014 skipping .vow.json`);
1081
+ }
1082
+ if (config.envalid?.schema) {
1083
+ const src = resolve5(cwd, config.envalid.schema);
1084
+ const dest = resolve5(cwd, ".env.schema");
1085
+ const isSamePath = resolve5(src) === resolve5(dest);
1086
+ if (isSamePath) {
1087
+ console.log(` ${colorize5("-", c5.dim)} envalid.schema already points to ${colorize5(".env.schema", c5.bold)}`);
1088
+ } else if (!existsSync4(src)) {
1089
+ console.log(` ${colorize5("!", c5.yellow)} envalid.schema source ${colorize5(config.envalid.schema, c5.bold)} not found \u2014 skipping`);
1090
+ skipped++;
1091
+ } else if (existsSync4(dest) && !options.force) {
1092
+ console.log(` ${colorize5("!", c5.yellow)} ${colorize5(".env.schema", c5.bold)} already exists \u2014 use ${colorize5("--force", c5.bold)} to overwrite`);
1093
+ skipped++;
1094
+ } else {
1095
+ copyFileSync(src, dest);
1096
+ console.log(` ${colorize5("+", c5.green)} Copied ${colorize5(config.envalid.schema, c5.bold)} \u2192 ${colorize5(".env.schema", c5.bold)}`);
1097
+ ejected++;
1098
+ }
1099
+ }
1100
+ if (config.berth !== void 0) {
1101
+ const portCount = config.berth.ports ? Object.keys(config.berth.ports).length : 0;
1102
+ if (portCount > 0) {
1103
+ console.log(` ${colorize5("\u2022", c5.dim)} berth: ${portCount} port(s) configured \u2014 berth has no standalone config file, managed via ${colorize5(".whenlabs.yml", c5.bold)}`);
1104
+ } else {
1105
+ console.log(` ${colorize5("-", c5.dim)} berth: no standalone config file`);
1106
+ }
1107
+ }
1108
+ console.log("");
1109
+ if (ejected > 0) {
1110
+ console.log(` ${colorize5("\u2713", c5.green)} Ejected ${colorize5(String(ejected), c5.bold)} file(s)`);
1111
+ }
1112
+ if (skipped > 0) {
1113
+ console.log(` ${colorize5("!", c5.yellow)} Skipped ${colorize5(String(skipped), c5.bold)} file(s) \u2014 run with ${colorize5("--force", c5.bold)} to overwrite`);
1114
+ }
1115
+ if (ejected === 0 && skipped === 0) {
1116
+ console.log(` ${colorize5("-", c5.dim)} Nothing to eject`);
1117
+ }
1118
+ console.log("");
1119
+ });
1120
+ return cmd;
1121
+ }
1122
+
1123
+ // src/commands/diff.ts
1124
+ import { Command as Command8 } from "commander";
1125
+ import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
1126
+ import { join as join2 } from "path";
1127
+ var c6 = {
1128
+ reset: "\x1B[0m",
1129
+ bold: "\x1B[1m",
1130
+ green: "\x1B[32m",
1131
+ yellow: "\x1B[33m",
1132
+ red: "\x1B[31m",
1133
+ cyan: "\x1B[36m",
1134
+ dim: "\x1B[2m"
1135
+ };
1136
+ function colorize6(text, ...codes) {
1137
+ return codes.join("") + text + c6.reset;
1138
+ }
1139
+ function readCache(tool, project) {
1140
+ const file = join2(CACHE_DIR, `${tool}_${project}.json`);
1141
+ if (!existsSync5(file)) return null;
1142
+ try {
1143
+ return JSON.parse(readFileSync5(file, "utf-8"));
1144
+ } catch {
1145
+ return null;
1146
+ }
1147
+ }
1148
+ function diffLines(oldOutput, newOutput) {
1149
+ const oldLines = new Set(oldOutput.split("\n").map((l) => l.trim()).filter(Boolean));
1150
+ const newLines = new Set(newOutput.split("\n").map((l) => l.trim()).filter(Boolean));
1151
+ const added = [];
1152
+ const removed = [];
1153
+ const unchanged = [];
1154
+ for (const line of newLines) {
1155
+ if (oldLines.has(line)) {
1156
+ unchanged.push(line);
1157
+ } else {
1158
+ added.push(line);
1159
+ }
1160
+ }
1161
+ for (const line of oldLines) {
1162
+ if (!newLines.has(line)) {
1163
+ removed.push(line);
1164
+ }
1165
+ }
1166
+ return { added, removed, unchanged };
1167
+ }
1168
+ var TOOLS = [
1169
+ { bin: "stale", args: ["scan"], label: "stale" },
1170
+ { bin: "envalid", args: ["validate"], label: "envalid" },
1171
+ { bin: "berth", args: ["status"], label: "berth" },
1172
+ { bin: "vow", args: ["scan"], label: "vow" },
1173
+ { bin: "aware", args: ["doctor"], label: "aware" }
1174
+ ];
1175
+ function createDiffCommand() {
1176
+ const cmd = new Command8("diff");
1177
+ cmd.description("Compare cached tool results to fresh runs and show what changed");
1178
+ cmd.action(async () => {
1179
+ const cwd = process.cwd();
1180
+ const project = deriveProject(cwd);
1181
+ console.log("");
1182
+ console.log(colorize6(" when diff", c6.bold, c6.cyan));
1183
+ console.log(colorize6(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500", c6.dim));
1184
+ console.log(` ${colorize6("project", c6.dim)} ${colorize6(project, c6.bold)}`);
1185
+ console.log("");
1186
+ let anyChanges = false;
1187
+ for (const tool of TOOLS) {
1188
+ const cached = readCache(tool.label, project);
1189
+ const fresh = await runCli(tool.bin, tool.args, cwd);
1190
+ const freshOutput = fresh.stdout.trim() || fresh.stderr.trim() || "";
1191
+ if (!cached) {
1192
+ console.log(` ${colorize6(tool.label, c6.bold, c6.cyan)}`);
1193
+ if (freshOutput) {
1194
+ for (const line of freshOutput.split("\n").slice(0, 5)) {
1195
+ if (line.trim()) console.log(` ${colorize6(line, c6.dim)}`);
1196
+ }
1197
+ const total = freshOutput.split("\n").filter(Boolean).length;
1198
+ if (total > 5) console.log(` ${colorize6(`\u2026 ${total - 5} more lines`, c6.dim)}`);
1199
+ } else {
1200
+ console.log(` ${colorize6("no output", c6.dim)}`);
1201
+ }
1202
+ console.log(` ${colorize6("(no prior cache \u2014 this is now the baseline)", c6.dim)}`);
1203
+ } else {
1204
+ const oldOutput = cached.output.trim();
1205
+ const { added, removed, unchanged } = diffLines(oldOutput, freshOutput);
1206
+ const hasChanges = added.length > 0 || removed.length > 0;
1207
+ if (hasChanges) anyChanges = true;
1208
+ console.log(` ${colorize6(tool.label, c6.bold, c6.cyan)}`);
1209
+ if (!hasChanges) {
1210
+ console.log(` ${colorize6("\u2713", c6.dim)} ${colorize6("no changes", c6.dim)} ${colorize6(`(${unchanged.length} line(s))`, c6.dim)}`);
1211
+ } else {
1212
+ for (const line of added) {
1213
+ console.log(` ${colorize6("+", c6.green)} ${colorize6(line, c6.green)}`);
1214
+ }
1215
+ for (const line of removed) {
1216
+ console.log(` ${colorize6("-", c6.red)} ${colorize6(line, c6.red)}`);
1217
+ }
1218
+ if (unchanged.length > 0) {
1219
+ console.log(` ${colorize6("\xB7", c6.dim)} ${colorize6(`${unchanged.length} line(s) unchanged`, c6.dim)}`);
1220
+ }
1221
+ }
1222
+ }
1223
+ writeCache(tool.label, project, freshOutput, fresh.code);
1224
+ console.log("");
1225
+ }
1226
+ if (!anyChanges) {
1227
+ console.log(` ${colorize6("\u2713", c6.green)} All tools unchanged since last run`);
1228
+ } else {
1229
+ console.log(` ${colorize6("\u2022", c6.dim)} Cache updated with latest results`);
1230
+ }
1231
+ console.log("");
1232
+ });
1233
+ return cmd;
1234
+ }
1235
+
1236
+ // src/commands/dashboard.ts
1237
+ import { Command as Command9 } from "commander";
1238
+ import { execSync as execSync2 } from "child_process";
1239
+ function createDashboardCommand() {
1240
+ const cmd = new Command9("dashboard");
1241
+ cmd.description("Generate an HTML velocity dashboard and open it in the browser");
1242
+ cmd.option("--no-open", "Write the HTML file without opening the browser");
1243
+ cmd.action(async (options) => {
1244
+ const { path, summary } = await generateDashboard();
1245
+ console.log(summary);
1246
+ if (options.open !== false) {
1247
+ const platform = process.platform;
1248
+ try {
1249
+ if (platform === "darwin") {
1250
+ execSync2(`open "${path}"`, { stdio: "ignore" });
1251
+ } else if (platform === "linux") {
1252
+ execSync2(`xdg-open "${path}"`, { stdio: "ignore" });
1253
+ } else {
1254
+ console.log(`Dashboard written to: ${path}`);
1255
+ }
1256
+ } catch {
1257
+ console.log(`Dashboard written to: ${path}`);
1258
+ }
1259
+ }
1260
+ });
1261
+ return cmd;
1262
+ }
1263
+
1264
+ // src/index.ts
1265
+ import { readFileSync as readFileSync6 } from "fs";
1266
+ import { resolve as resolve6, dirname as dirname4 } from "path";
1267
+ import { fileURLToPath as fileURLToPath4 } from "url";
1268
+ var __dirname4 = dirname4(fileURLToPath4(import.meta.url));
1269
+ var { version } = JSON.parse(readFileSync6(resolve6(__dirname4, "..", "package.json"), "utf8"));
1270
+ var program = new Command10();
595
1271
  program.name("when").version(version).description("The WhenLabs developer toolkit \u2014 6 tools, one install");
596
1272
  program.command("install").description("Install all WhenLabs tools globally (MCP server + CLAUDE.md instructions)").option("--cursor", "Install MCP servers into Cursor (~/.cursor/mcp.json)").option("--vscode", "Install MCP servers into VS Code (settings.json)").option("--windsurf", "Install MCP servers into Windsurf (~/.codeium/windsurf/mcp_config.json)").option("--all", "Install MCP servers into all supported editors").action(async (options) => {
597
- const { install } = await import("./install-V24JHOA2.js");
1273
+ const { install } = await import("./install-33GE3HKA.js");
598
1274
  await install(options);
599
1275
  });
600
1276
  program.command("uninstall").description("Remove all WhenLabs tools").option("--cursor", "Remove MCP servers from Cursor").option("--vscode", "Remove MCP servers from VS Code").option("--windsurf", "Remove MCP servers from Windsurf").option("--all", "Remove MCP servers from all supported editors").action(async (options) => {
@@ -612,10 +1288,16 @@ program.command("ci").description("Run stale, envalid, and vow checks \u2014 exi
612
1288
  program.addCommand(createInitCommand());
613
1289
  program.addCommand(createDoctorCommand());
614
1290
  program.addCommand(createWatchCommand());
1291
+ program.addCommand(createConfigCommand());
1292
+ program.addCommand(createUpgradeCommand());
1293
+ program.addCommand(createEjectCommand());
1294
+ program.addCommand(createDiffCommand());
615
1295
  program.addCommand(createDelegateCommand("stale", "Detect documentation drift in your codebase"));
616
1296
  program.addCommand(createDelegateCommand("envalid", "Validate .env files against a type-safe schema"));
617
1297
  program.addCommand(createDelegateCommand("berth", "Detect and resolve port conflicts"));
618
1298
  program.addCommand(createDelegateCommand("aware", "Auto-detect your stack and generate AI context files"));
619
1299
  program.addCommand(createDelegateCommand("vow", "Scan dependency licenses and validate against policies"));
620
- program.addCommand(createDelegateCommand("velocity", "velocity-mcp task timing server", "velocity-mcp"));
1300
+ var velocityCmd = createDelegateCommand("velocity", "velocity-mcp task timing server", "velocity-mcp");
1301
+ velocityCmd.addCommand(createDashboardCommand());
1302
+ program.addCommand(velocityCmd);
621
1303
  program.parse();