@whenlabs/when 0.8.1 → 0.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.
package/README.md CHANGED
@@ -16,10 +16,9 @@ This is a one-time setup. After install, all six tools are available in every pr
16
16
 
17
17
  Running `npx @whenlabs/when install` will:
18
18
 
19
- 1. Register **two MCP servers** in your Claude Code configuration:
20
- - `velocity-mcp` — task timing and estimation tools
21
- - `whenlabs` — stale, envalid, berth, aware, and vow tools
19
+ 1. Register a **single MCP server** (`whenlabs`) in your Claude Code configuration — all six tools, including velocity, are served from one server
22
20
  2. Inject **CLAUDE.md instructions** so Claude knows when to use each tool automatically — and prefers them over shell commands
21
+ 3. Clean up any legacy `velocity-mcp` registrations (velocity is now bundled)
23
22
 
24
23
  Once connected, Claude can call any tool directly without you asking. For example, after a refactor Claude might run `stale_scan` to check for doc drift, or before a release it might run `vow_check` to validate licenses.
25
24
 
@@ -46,17 +45,21 @@ These tools are available to Claude in every session after install:
46
45
  | `velocity_start_task` | Start timing a coding task |
47
46
  | `velocity_end_task` | End timing and record results |
48
47
  | `velocity_estimate` | Estimate time for a planned task |
49
- | `velocity_stats` | Show aggregate performance stats |
48
+ | `velocity_stats` | Show aggregate performance stats with insights |
50
49
  | `velocity_history` | Show task history |
51
50
  | `stale_scan` | Detect documentation drift |
51
+ | `stale_fix` | Auto-fix documentation drift (wrong paths, dead links, phantom env vars) |
52
52
  | `envalid_validate` | Validate .env files against schemas |
53
53
  | `envalid_detect` | Find undocumented env vars in codebase |
54
+ | `envalid_generate_schema` | Generate .env.schema from code analysis |
54
55
  | `berth_status` | Show active ports and conflicts |
55
56
  | `berth_check` | Scan project for port conflicts |
57
+ | `berth_resolve` | Auto-resolve port conflicts (kill or reassign) |
56
58
  | `aware_init` | Auto-detect stack, generate AI context files |
57
59
  | `aware_doctor` | Diagnose project health and config issues |
58
60
  | `vow_scan` | Scan and summarize dependency licenses |
59
61
  | `vow_check` | Validate licenses against policy |
62
+ | `vow_hook_install` | Install pre-commit license check hook |
60
63
 
61
64
  ## Multi-Editor Support
62
65
 
@@ -78,12 +81,18 @@ You can also run tools directly from the command line:
78
81
  ```bash
79
82
  when init # Onboard a project — detect stack, run all tools
80
83
  when stale scan
84
+ when stale fix # Auto-fix documentation drift
81
85
  when envalid validate
86
+ when envalid detect --generate # Generate schema from code
82
87
  when berth status
88
+ when berth resolve # Auto-resolve port conflicts
83
89
  when aware init
84
90
  when vow scan
91
+ when vow hook install # Install pre-commit license hook
85
92
  when status # Show installation status
86
93
  when doctor # Run all tools, show unified health report
94
+ when doctor --watch # Continuous monitoring dashboard
95
+ when watch # Background daemon for status line
87
96
  when ci # Run checks for CI (exits 1 on issues)
88
97
  ```
89
98
 
@@ -93,7 +102,11 @@ One command to onboard any project. Auto-detects your stack, runs all 5 tools in
93
102
 
94
103
  ### `when doctor`
95
104
 
96
- Runs all 5 CLI tools against the current project and displays a unified health report card. Supports `--json` for machine-readable output.
105
+ Runs all 5 CLI tools against the current project and displays a unified health report card. Supports `--json` for machine-readable output and `--watch` for continuous monitoring with a live dashboard.
106
+
107
+ ### `when watch`
108
+
109
+ Background daemon that runs all 5 tools on intervals and writes results to `~/.whenlabs/status.json`. Powers the Claude Code status line integration. Use `--once` for a single scan or `--interval <seconds>` to customize the schedule.
97
110
 
98
111
  ### `when ci`
99
112
 
@@ -118,7 +131,7 @@ when ci --json # Machine-readable JSON output
118
131
  npx @whenlabs/when uninstall
119
132
  ```
120
133
 
121
- Removes both MCP servers and cleans up CLAUDE.md instructions.
134
+ Removes the MCP server and cleans up CLAUDE.md instructions.
122
135
 
123
136
  ## License
124
137
 
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/utils/status-provider.ts
4
+ import { join } from "path";
5
+ import { homedir } from "os";
6
+ import { existsSync, readFileSync, statSync } from "fs";
7
+ var STATUS_PATH = join(homedir(), ".whenlabs", "status.json");
8
+ function getStatusPath() {
9
+ return STATUS_PATH;
10
+ }
11
+ function readStatus() {
12
+ if (!existsSync(STATUS_PATH)) return null;
13
+ try {
14
+ const raw = readFileSync(STATUS_PATH, "utf-8");
15
+ return JSON.parse(raw);
16
+ } catch {
17
+ return null;
18
+ }
19
+ }
20
+ function formatStatusLine() {
21
+ const data = readStatus();
22
+ if (!data) return null;
23
+ const tools = data.tools;
24
+ const parts = [];
25
+ for (const [key, info] of Object.entries(tools)) {
26
+ const label = key === "envalid" ? "env" : key === "vow" ? "lic" : key;
27
+ if (info.status === "ok") {
28
+ parts.push(`\u2713${label}`);
29
+ } else if (info.status === "issues") {
30
+ parts.push(`\u2717${label}:${info.count}`);
31
+ } else {
32
+ parts.push(`!${label}`);
33
+ }
34
+ }
35
+ return parts.join(" ");
36
+ }
37
+ function isStale(maxAgeMs = 120 * 60 * 1e3) {
38
+ if (!existsSync(STATUS_PATH)) return true;
39
+ try {
40
+ const stat = statSync(STATUS_PATH);
41
+ return Date.now() - stat.mtimeMs > maxAgeMs;
42
+ } catch {
43
+ return true;
44
+ }
45
+ }
46
+
47
+ export {
48
+ getStatusPath,
49
+ readStatus,
50
+ formatStatusLine,
51
+ isStale
52
+ };
package/dist/index.js CHANGED
@@ -1,7 +1,10 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ getStatusPath
4
+ } from "./chunk-4ZVSCJCJ.js";
2
5
 
3
6
  // src/index.ts
4
- import { Command as Command4 } from "commander";
7
+ import { Command as Command5 } from "commander";
5
8
 
6
9
  // src/commands/delegate.ts
7
10
  import { Command } from "commander";
@@ -47,25 +50,15 @@ function createDelegateCommand(name, description, binName) {
47
50
 
48
51
  // src/commands/doctor.ts
49
52
  import { Command as Command2 } from "commander";
53
+
54
+ // src/utils/tool-runner.ts
50
55
  import { spawn as spawn2 } from "child_process";
51
56
  import { resolve as resolve2, dirname as dirname2 } from "path";
52
57
  import { existsSync as existsSync2 } from "fs";
53
58
  import { fileURLToPath as fileURLToPath2 } from "url";
54
59
  var __dirname2 = dirname2(fileURLToPath2(import.meta.url));
55
- var c = {
56
- reset: "\x1B[0m",
57
- bold: "\x1B[1m",
58
- green: "\x1B[32m",
59
- yellow: "\x1B[33m",
60
- red: "\x1B[31m",
61
- cyan: "\x1B[36m",
62
- dim: "\x1B[2m"
63
- };
64
- function colorize(text, ...codes) {
65
- return codes.join("") + text + c.reset;
66
- }
67
60
  function findBin2(name) {
68
- const pkgRoot = resolve2(__dirname2, "..");
61
+ const pkgRoot = resolve2(__dirname2, "..", "..");
69
62
  const localBin = resolve2(pkgRoot, "node_modules", ".bin", name);
70
63
  if (existsSync2(localBin)) return localBin;
71
64
  return name;
@@ -215,6 +208,29 @@ async function checkAware() {
215
208
  exitCode
216
209
  };
217
210
  }
211
+ async function runAllChecks(cwd) {
212
+ return Promise.all([
213
+ checkStale(cwd),
214
+ checkEnvalid(cwd),
215
+ checkBerth(cwd),
216
+ checkVow(cwd),
217
+ checkAware()
218
+ ]);
219
+ }
220
+
221
+ // src/commands/doctor.ts
222
+ var c = {
223
+ reset: "\x1B[0m",
224
+ bold: "\x1B[1m",
225
+ green: "\x1B[32m",
226
+ yellow: "\x1B[33m",
227
+ red: "\x1B[31m",
228
+ cyan: "\x1B[36m",
229
+ dim: "\x1B[2m"
230
+ };
231
+ function colorize(text, ...codes) {
232
+ return codes.join("") + text + c.reset;
233
+ }
218
234
  function statusIcon(result) {
219
235
  switch (result.status) {
220
236
  case "ok":
@@ -257,13 +273,7 @@ function createDoctorCommand() {
257
273
  if (!options.json) {
258
274
  process.stdout.write(colorize(" Running health checks\u2026", c.dim) + "\n");
259
275
  }
260
- const results = await Promise.all([
261
- checkStale(cwd),
262
- checkEnvalid(cwd),
263
- checkBerth(cwd),
264
- checkVow(cwd),
265
- checkAware()
266
- ]);
276
+ const results = await runAllChecks(cwd);
267
277
  const hasIssues = results.some((r) => r.status === "issues" || r.status === "error");
268
278
  if (options.json) {
269
279
  const output = {
@@ -491,8 +501,99 @@ function createInitCommand() {
491
501
  return cmd;
492
502
  }
493
503
 
504
+ // src/commands/watch.ts
505
+ import { Command as Command4 } from "commander";
506
+ import { join } from "path";
507
+ import { homedir } from "os";
508
+ import { mkdirSync, writeFileSync } from "fs";
509
+ var STATUS_DIR = join(homedir(), ".whenlabs");
510
+ function toolResultToStatus(r) {
511
+ const count = r.issues + r.warnings;
512
+ if (r.status === "error") {
513
+ return { status: "error", count, detail: r.detail };
514
+ }
515
+ if (r.status === "issues") {
516
+ return { status: "issues", count, detail: r.detail };
517
+ }
518
+ return { status: "ok", count: 0, detail: r.detail };
519
+ }
520
+ function buildSummary(results) {
521
+ const map = {};
522
+ for (const r of results) map[r.name] = r;
523
+ const stalePart = `stale:${map["stale"]?.issues ?? 0}`;
524
+ const envPart = `env:${(map["envalid"]?.issues ?? 0) + (map["envalid"]?.warnings ?? 0)}`;
525
+ const portsPart = `ports:${map["berth"]?.issues ?? 0}`;
526
+ const licPart = `lic:${(map["vow"]?.issues ?? 0) + (map["vow"]?.warnings ?? 0)}`;
527
+ const awarePart = `aware:${map["aware"]?.status === "ok" || map["aware"]?.status === "skipped" ? "ok" : "stale"}`;
528
+ return `${stalePart} ${envPart} ${portsPart} ${licPart} ${awarePart}`;
529
+ }
530
+ function writeStatus(results) {
531
+ mkdirSync(STATUS_DIR, { recursive: true });
532
+ const toolsMap = {};
533
+ for (const r of results) toolsMap[r.name] = r;
534
+ const status = {
535
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
536
+ tools: {
537
+ stale: toolResultToStatus(toolsMap["stale"]),
538
+ envalid: toolResultToStatus(toolsMap["envalid"]),
539
+ berth: toolResultToStatus(toolsMap["berth"]),
540
+ vow: toolResultToStatus(toolsMap["vow"]),
541
+ aware: toolResultToStatus(toolsMap["aware"])
542
+ },
543
+ summary: buildSummary(results)
544
+ };
545
+ writeFileSync(getStatusPath(), JSON.stringify(status, null, 2) + "\n");
546
+ }
547
+ function sleep(ms) {
548
+ return new Promise((resolve4) => setTimeout(resolve4, ms));
549
+ }
550
+ function createWatchCommand() {
551
+ const cmd = new Command4("watch");
552
+ cmd.description("Run all 5 WhenLabs tools on a schedule and write results to ~/.whenlabs/status.json");
553
+ cmd.option("--once", "Run a single scan and exit");
554
+ cmd.option("--interval <seconds>", "Override the default scan interval (seconds)", "60");
555
+ cmd.action(async (options) => {
556
+ const cwd = process.cwd();
557
+ const intervalSec = Math.max(10, parseInt(options.interval ?? "60", 10));
558
+ let stopped = false;
559
+ const shutdown = () => {
560
+ stopped = true;
561
+ process.stderr.write("\nwatch: shutting down gracefully\n");
562
+ process.exit(0);
563
+ };
564
+ process.on("SIGINT", shutdown);
565
+ process.on("SIGTERM", shutdown);
566
+ const runScan = async () => {
567
+ const start = Date.now();
568
+ process.stderr.write(`watch: scanning... `);
569
+ const results = await runAllChecks(cwd);
570
+ writeStatus(results);
571
+ const elapsed = ((Date.now() - start) / 1e3).toFixed(1);
572
+ const summary = buildSummary(results);
573
+ process.stderr.write(`done in ${elapsed}s [${summary}]
574
+ `);
575
+ const hasIssues = results.some((r) => r.status === "issues" || r.status === "error");
576
+ return hasIssues;
577
+ };
578
+ if (options.once) {
579
+ const hasIssues = await runScan();
580
+ process.exit(hasIssues ? 1 : 0);
581
+ return;
582
+ }
583
+ process.stderr.write(`watch: started (interval=${intervalSec}s, status=${getStatusPath()})
584
+ `);
585
+ while (!stopped) {
586
+ await runScan();
587
+ for (let i = 0; i < intervalSec && !stopped; i++) {
588
+ await sleep(1e3);
589
+ }
590
+ }
591
+ });
592
+ return cmd;
593
+ }
594
+
494
595
  // src/index.ts
495
- var program = new Command4();
596
+ var program = new Command5();
496
597
  program.name("when").version("0.1.0").description("The WhenLabs developer toolkit \u2014 6 tools, one install");
497
598
  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) => {
498
599
  const { install } = await import("./install-HPF26YW2.js");
@@ -503,7 +604,7 @@ program.command("uninstall").description("Remove all WhenLabs tools").option("--
503
604
  await uninstall(options);
504
605
  });
505
606
  program.command("status").description("Show installation status and velocity stats").action(async () => {
506
- const { status } = await import("./status-LOZGVOA3.js");
607
+ const { status } = await import("./status-QWAHXHNA.js");
507
608
  await status();
508
609
  });
509
610
  program.command("ci").description("Run stale, envalid, and vow checks \u2014 exits 1 if any tool finds issues").option("--ci", "Output GitHub Actions annotations (::error file=X::message)").option("--json", "Machine-readable JSON output").action(async (options) => {
@@ -512,6 +613,7 @@ program.command("ci").description("Run stale, envalid, and vow checks \u2014 exi
512
613
  });
513
614
  program.addCommand(createInitCommand());
514
615
  program.addCommand(createDoctorCommand());
616
+ program.addCommand(createWatchCommand());
515
617
  program.addCommand(createDelegateCommand("stale", "Detect documentation drift in your codebase"));
516
618
  program.addCommand(createDelegateCommand("envalid", "Validate .env files against a type-safe schema"));
517
619
  program.addCommand(createDelegateCommand("berth", "Detect and resolve port conflicts"));
package/dist/mcp.js CHANGED
@@ -100,7 +100,7 @@ Note: Conflicts found in project "${projectName}".`);
100
100
  }
101
101
  var server = new McpServer({
102
102
  name: "whenlabs",
103
- version: "0.2.0"
103
+ version: "0.3.0"
104
104
  });
105
105
  var velocityDb = initDb();
106
106
  var velocityQueries = new TaskQueries(velocityDb);
@@ -137,6 +137,25 @@ server.tool(
137
137
  return { content: [{ type: "text", text: output + extras.join("") }] };
138
138
  }
139
139
  );
140
+ server.tool(
141
+ "stale_fix",
142
+ "Auto-fix documentation drift \u2014 generate fixes for wrong file paths, dead links, phantom env vars, outdated scripts",
143
+ {
144
+ path: z.string().optional().describe("Project directory to scan (defaults to cwd)"),
145
+ format: z.enum(["terminal", "diff"]).optional().describe("Output format (default: terminal)"),
146
+ apply: z.coerce.boolean().optional().describe("Apply high-confidence fixes directly"),
147
+ dryRun: z.coerce.boolean().optional().describe("Show what --apply would do without writing")
148
+ },
149
+ async ({ path, format, apply, dryRun }) => {
150
+ const args = ["fix"];
151
+ if (format) args.push("--format", format);
152
+ if (apply) args.push("--apply");
153
+ if (dryRun) args.push("--dry-run");
154
+ const result = await runCli("stale", args, path);
155
+ const output = formatOutput(result);
156
+ return { content: [{ type: "text", text: output }] };
157
+ }
158
+ );
140
159
  server.tool(
141
160
  "stale_init",
142
161
  "Generate a .stale.yml config file for customizing documentation drift detection",
@@ -263,6 +282,21 @@ server.tool(
263
282
  return { content: [{ type: "text", text: output }] };
264
283
  }
265
284
  );
285
+ server.tool(
286
+ "envalid_generate_schema",
287
+ "Generate .env.schema from code analysis \u2014 infer types, required-ness, and sensitivity from usage patterns",
288
+ {
289
+ path: z.string().optional().describe("Project directory (defaults to cwd)"),
290
+ output: z.string().optional().describe("Output file path (default: .env.schema)")
291
+ },
292
+ async ({ path, output }) => {
293
+ const args = ["detect", "--generate"];
294
+ if (output) args.push("-o", output);
295
+ const result = await runCli("envalid", args, path);
296
+ const outputText = formatOutput(result);
297
+ return { content: [{ type: "text", text: outputText }] };
298
+ }
299
+ );
266
300
  server.tool(
267
301
  "envalid_hook_status",
268
302
  "Check if the envalid pre-commit git hook is installed",
@@ -379,6 +413,25 @@ server.tool(
379
413
  return { content: [{ type: "text", text: output }] };
380
414
  }
381
415
  );
416
+ server.tool(
417
+ "berth_resolve",
418
+ "Auto-resolve port conflicts \u2014 detect conflicts and fix via kill or reassign strategy",
419
+ {
420
+ path: z.string().optional().describe("Project directory (defaults to cwd)"),
421
+ strategy: z.enum(["kill", "reassign", "auto"]).optional().describe("Resolution strategy (default: auto)"),
422
+ kill: z.coerce.boolean().optional().describe("Allow killing processes (required for kill/auto strategies)"),
423
+ dryRun: z.coerce.boolean().optional().describe("Show what would be done without making changes")
424
+ },
425
+ async ({ path, strategy, kill, dryRun }) => {
426
+ const args = ["resolve"];
427
+ if (strategy) args.push("--strategy", strategy);
428
+ if (kill) args.push("--kill");
429
+ if (dryRun) args.push("--dry-run");
430
+ const result = await runCli("berth", args, path);
431
+ const output = formatOutput(result);
432
+ return { content: [{ type: "text", text: output }] };
433
+ }
434
+ );
382
435
  server.tool(
383
436
  "berth_predict",
384
437
  "Predict port conflicts from project config files before starting \u2014 dry-run conflict check",
@@ -427,9 +480,14 @@ server.tool(
427
480
  server.tool(
428
481
  "aware_diff",
429
482
  "Show project changes since last sync \u2014 see what drifted in your codebase",
430
- { path: z.string().optional().describe("Project directory (defaults to cwd)") },
431
- async ({ path }) => {
432
- const result = await runCli("aware", ["diff"], path);
483
+ {
484
+ path: z.string().optional().describe("Project directory (defaults to cwd)"),
485
+ exitCode: z.coerce.boolean().optional().describe("Return exit code 1 if changes detected (useful for CI)")
486
+ },
487
+ async ({ path, exitCode }) => {
488
+ const args = ["diff"];
489
+ if (exitCode) args.push("--exit-code");
490
+ const result = await runCli("aware", args, path);
433
491
  const output = formatOutput(result);
434
492
  return { content: [{ type: "text", text: output }] };
435
493
  }
@@ -579,6 +637,42 @@ server.tool(
579
637
  return { content: [{ type: "text", text: outputText }] };
580
638
  }
581
639
  );
640
+ server.tool(
641
+ "vow_hook_install",
642
+ "Install a pre-commit git hook that checks dependency licenses before each commit",
643
+ {
644
+ path: z.string().optional().describe("Project directory (defaults to cwd)")
645
+ },
646
+ async ({ path }) => {
647
+ const result = await runCli("vow", ["hook", "install"], path);
648
+ const output = formatOutput(result);
649
+ return { content: [{ type: "text", text: output }] };
650
+ }
651
+ );
652
+ server.tool(
653
+ "vow_hook_uninstall",
654
+ "Remove the vow pre-commit license check hook",
655
+ {
656
+ path: z.string().optional().describe("Project directory (defaults to cwd)")
657
+ },
658
+ async ({ path }) => {
659
+ const result = await runCli("vow", ["hook", "uninstall"], path);
660
+ const output = formatOutput(result);
661
+ return { content: [{ type: "text", text: output }] };
662
+ }
663
+ );
664
+ server.tool(
665
+ "vow_hook_status",
666
+ "Check if the vow pre-commit license check hook is installed",
667
+ {
668
+ path: z.string().optional().describe("Project directory (defaults to cwd)")
669
+ },
670
+ async ({ path }) => {
671
+ const result = await runCli("vow", ["hook", "status"], path);
672
+ const output = formatOutput(result);
673
+ return { content: [{ type: "text", text: output }] };
674
+ }
675
+ );
582
676
  server.tool(
583
677
  "vow_attribution",
584
678
  "Generate THIRD_PARTY_LICENSES.md \u2014 list all dependencies with their licenses for compliance",
@@ -1,4 +1,9 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ formatStatusLine,
4
+ isStale,
5
+ readStatus
6
+ } from "./chunk-4ZVSCJCJ.js";
2
7
  import {
3
8
  hasBlock
4
9
  } from "./chunk-NYUYV3UL.js";
@@ -36,6 +41,16 @@ async function status() {
36
41
  ` CLAUDE.md instructions: ${claudeMdInstalled ? "\u2713 installed" : "\u2717 not installed"}`
37
42
  );
38
43
  console.log(` CLAUDE.md path: ${CLAUDE_MD_PATH}`);
44
+ const watchData = readStatus();
45
+ if (watchData) {
46
+ const line = formatStatusLine();
47
+ const stale = isStale();
48
+ const age = stale ? " (stale)" : "";
49
+ console.log(`
50
+ Watch results${age}: ${line}`);
51
+ console.log(` Last scan: ${watchData.timestamp}`);
52
+ console.log(` Summary: ${watchData.summary}`);
53
+ }
39
54
  const allGood = mcpRegistered && claudeMdInstalled;
40
55
  if (allGood) {
41
56
  console.log("\n Everything is set up. Run `when --help` to see available tools.\n");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@whenlabs/when",
3
- "version": "0.8.1",
3
+ "version": "0.9.0",
4
4
  "description": "The WhenLabs developer toolkit — 6 tools, one install",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -29,12 +29,12 @@
29
29
  },
30
30
  "dependencies": {
31
31
  "@modelcontextprotocol/sdk": "^1.29.0",
32
- "@whenlabs/aware": "^0.1.2",
33
- "@whenlabs/berth": "^0.1.0",
34
- "@whenlabs/envalid": "^0.1.2",
35
- "@whenlabs/stale": "^0.1.0",
36
- "@whenlabs/velocity-mcp": "^0.1.2",
37
- "@whenlabs/vow": "^0.1.3",
32
+ "@whenlabs/aware": "^0.1.3",
33
+ "@whenlabs/berth": "^0.1.3",
34
+ "@whenlabs/envalid": "^0.1.3",
35
+ "@whenlabs/stale": "^0.1.3",
36
+ "@whenlabs/velocity-mcp": "^0.1.3",
37
+ "@whenlabs/vow": "^0.1.4",
38
38
  "commander": "^12.0.0",
39
39
  "zod": "^4.3.6"
40
40
  },