@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 +19 -6
- package/dist/chunk-4ZVSCJCJ.js +52 -0
- package/dist/index.js +125 -23
- package/dist/mcp.js +98 -4
- package/dist/{status-LOZGVOA3.js → status-QWAHXHNA.js} +15 -0
- package/package.json +7 -7
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 **
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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-
|
|
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.
|
|
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
|
-
{
|
|
431
|
-
|
|
432
|
-
|
|
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.
|
|
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.
|
|
33
|
-
"@whenlabs/berth": "^0.1.
|
|
34
|
-
"@whenlabs/envalid": "^0.1.
|
|
35
|
-
"@whenlabs/stale": "^0.1.
|
|
36
|
-
"@whenlabs/velocity-mcp": "^0.1.
|
|
37
|
-
"@whenlabs/vow": "^0.1.
|
|
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
|
},
|