@whenlabs/when 0.9.0 → 0.9.1
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 +1 -1
- package/dist/install-TFEGFWJ5.js +447 -0
- package/package.json +1 -1
- package/dist/install-HPF26YW2.js +0 -124
package/dist/index.js
CHANGED
|
@@ -596,7 +596,7 @@ function createWatchCommand() {
|
|
|
596
596
|
var program = new Command5();
|
|
597
597
|
program.name("when").version("0.1.0").description("The WhenLabs developer toolkit \u2014 6 tools, one install");
|
|
598
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) => {
|
|
599
|
-
const { install } = await import("./install-
|
|
599
|
+
const { install } = await import("./install-TFEGFWJ5.js");
|
|
600
600
|
await install(options);
|
|
601
601
|
});
|
|
602
602
|
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) => {
|
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
ALL_EDITORS,
|
|
4
|
+
installForEditor,
|
|
5
|
+
registerMcpServer
|
|
6
|
+
} from "./chunk-3PDLNC63.js";
|
|
7
|
+
import {
|
|
8
|
+
injectBlock
|
|
9
|
+
} from "./chunk-NYUYV3UL.js";
|
|
10
|
+
|
|
11
|
+
// src/commands/install.ts
|
|
12
|
+
import { join } from "path";
|
|
13
|
+
import { homedir } from "os";
|
|
14
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync } from "fs";
|
|
15
|
+
var CLAUDE_MD_PATH = join(homedir(), ".claude", "CLAUDE.md");
|
|
16
|
+
var SCRIPTS_DIR = join(homedir(), ".claude", "scripts");
|
|
17
|
+
var STATUSLINE_PATH = join(SCRIPTS_DIR, "statusline.py");
|
|
18
|
+
var SETTINGS_PATH = join(homedir(), ".claude", "settings.json");
|
|
19
|
+
var OLD_START_MARKER = "<!-- velocity-mcp:start -->";
|
|
20
|
+
var OLD_END_MARKER = "<!-- velocity-mcp:end -->";
|
|
21
|
+
var CLAUDE_MD_CONTENT = `# WhenLabs Toolkit
|
|
22
|
+
|
|
23
|
+
## Task Timing (velocity-mcp)
|
|
24
|
+
|
|
25
|
+
Velocity tools are part of the unified \`whenlabs\` MCP server. Follow these rules for EVERY session:
|
|
26
|
+
|
|
27
|
+
1. **Before starting any discrete coding task**, call \`velocity_start_task\` with:
|
|
28
|
+
- Appropriate \`category\` (scaffold, implement, refactor, debug, test, config, docs, deploy)
|
|
29
|
+
- Relevant \`tags\` (e.g. typescript, react, sqlite, api)
|
|
30
|
+
- Clear \`description\` of what you're about to do
|
|
31
|
+
- \`estimated_files\` if you know how many files you'll touch
|
|
32
|
+
- \`project\` set to the current project name (auto-detected from git remote or directory name)
|
|
33
|
+
|
|
34
|
+
2. **After completing each task**, call \`velocity_end_task\` with:
|
|
35
|
+
- The \`task_id\` from the start call
|
|
36
|
+
- \`status\`: completed, failed, or abandoned
|
|
37
|
+
- \`actual_files\`: how many files were actually modified
|
|
38
|
+
- \`notes\`: any useful context about what happened
|
|
39
|
+
|
|
40
|
+
3. **When creating a multi-step plan**, call \`velocity_estimate\` to provide the user with a time estimate before starting work.
|
|
41
|
+
|
|
42
|
+
4. **If the user asks about speed or performance**, call \`velocity_stats\` to show aggregate data.
|
|
43
|
+
|
|
44
|
+
### Guidelines
|
|
45
|
+
- Every discrete unit of work should be tracked \u2014 don't batch multiple unrelated changes into one task
|
|
46
|
+
- If a task is abandoned or fails, still call \`velocity_end_task\` with the appropriate status
|
|
47
|
+
- Use consistent tags across sessions so the similarity matching can find comparable historical tasks
|
|
48
|
+
- Keep descriptions concise but specific enough to be useful for future matching
|
|
49
|
+
|
|
50
|
+
## WhenLabs MCP Tools (ALWAYS prefer these over shell commands)
|
|
51
|
+
|
|
52
|
+
All six tools (including velocity) are available through the unified \`whenlabs\` MCP server. **ALWAYS use these MCP tools instead of running shell commands like lsof, grep, or manual checks.** These tools are purpose-built and give better results:
|
|
53
|
+
|
|
54
|
+
| When to use | Call this tool | NOT this |
|
|
55
|
+
|-------------|---------------|----------|
|
|
56
|
+
| Check ports or port conflicts | \`berth_status\` or \`berth_check\` | \`lsof\`, \`netstat\`, \`ss\` |
|
|
57
|
+
| Scan dependency licenses | \`vow_scan\` or \`vow_check\` | manual \`npm ls\`, \`license-checker\` |
|
|
58
|
+
| Check if docs are stale | \`stale_scan\` | manual file comparison |
|
|
59
|
+
| Validate .env files | \`envalid_validate\` or \`envalid_detect\` | manual .env inspection |
|
|
60
|
+
| Generate AI context files | \`aware_init\` or \`aware_doctor\` | manual CLAUDE.md creation |
|
|
61
|
+
|
|
62
|
+
### Tool Reference
|
|
63
|
+
- \`berth_status\` \u2014 Show all active ports, Docker ports, and configured ports
|
|
64
|
+
- \`berth_check\` \u2014 Scan a project directory for port conflicts
|
|
65
|
+
- \`stale_scan\` \u2014 Detect documentation drift in the codebase
|
|
66
|
+
- \`envalid_validate\` \u2014 Validate .env files against their schema
|
|
67
|
+
- \`envalid_detect\` \u2014 Find undocumented env vars in codebase
|
|
68
|
+
- \`aware_init\` \u2014 Auto-detect stack and generate AI context files
|
|
69
|
+
- \`aware_doctor\` \u2014 Diagnose project health and config issues
|
|
70
|
+
- \`vow_scan\` \u2014 Scan and summarize all dependency licenses
|
|
71
|
+
- \`vow_check\` \u2014 Validate licenses against a policy file
|
|
72
|
+
|
|
73
|
+
### Proactive Background Scans
|
|
74
|
+
WhenLabs tools run automatically in the background on a schedule. The status line shows findings:
|
|
75
|
+
- \`stale:N\` \u2014 N docs have drifted from code. Run \`stale_scan\` and fix the drift.
|
|
76
|
+
- \`env:N\` \u2014 N .env issues found. Run \`envalid_validate\` and help the user fix them.
|
|
77
|
+
- \`ports:N\` \u2014 N port conflicts. Run \`berth_status\` and suggest resolution.
|
|
78
|
+
- \`lic:N?\` \u2014 N packages with unknown licenses. Run \`vow_scan\` for details.
|
|
79
|
+
- \`aware:stale\` \u2014 AI context files are outdated. Run \`aware_init\` to regenerate.
|
|
80
|
+
|
|
81
|
+
**When you see any of these in the status line, proactively tell the user and offer to fix the issue.** Do not wait for the user to ask.`;
|
|
82
|
+
var STATUSLINE_SCRIPT = `#!/usr/bin/env python3
|
|
83
|
+
"""WhenLabs status line for Claude Code \u2014 with proactive background tool scans."""
|
|
84
|
+
|
|
85
|
+
import json
|
|
86
|
+
import os
|
|
87
|
+
import subprocess
|
|
88
|
+
import sys
|
|
89
|
+
import time
|
|
90
|
+
from pathlib import Path
|
|
91
|
+
|
|
92
|
+
CACHE_DIR = Path.home() / ".whenlabs" / "cache"
|
|
93
|
+
CACHE_DIR.mkdir(parents=True, exist_ok=True)
|
|
94
|
+
|
|
95
|
+
# Scan intervals in seconds
|
|
96
|
+
INTERVALS = {
|
|
97
|
+
"berth": 900, # 15 min
|
|
98
|
+
"stale": 1800, # 30 min
|
|
99
|
+
"envalid": 1800, # 30 min
|
|
100
|
+
"vow": 3600, # 60 min
|
|
101
|
+
"aware": 3600, # 60 min
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class C:
|
|
106
|
+
AMBER = "\\033[38;2;196;106;26m"
|
|
107
|
+
BLUE = "\\033[38;2;59;130;246m"
|
|
108
|
+
CYAN = "\\033[38;2;34;211;238m"
|
|
109
|
+
GREEN = "\\033[38;2;34;197;94m"
|
|
110
|
+
RED = "\\033[38;2;239;68;68m"
|
|
111
|
+
YELLOW = "\\033[38;2;234;179;8m"
|
|
112
|
+
GRAY = "\\033[38;2;156;163;175m"
|
|
113
|
+
DIM = "\\033[2m"
|
|
114
|
+
RESET = "\\033[0m"
|
|
115
|
+
SEP = f"\\033[38;2;107;114;128m \\u30fb \\033[0m"
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def git_info(cwd):
|
|
119
|
+
try:
|
|
120
|
+
subprocess.run(["git", "rev-parse", "--git-dir"], cwd=cwd, capture_output=True, check=True, timeout=1)
|
|
121
|
+
branch = subprocess.run(["git", "rev-parse", "--abbrev-ref", "HEAD"], cwd=cwd, capture_output=True, text=True, timeout=1).stdout.strip()
|
|
122
|
+
status = subprocess.run(["git", "status", "--porcelain"], cwd=cwd, capture_output=True, text=True, timeout=1).stdout
|
|
123
|
+
clean = len([l for l in status.strip().split("\\n") if l]) == 0
|
|
124
|
+
color = C.GREEN if clean else C.YELLOW
|
|
125
|
+
icon = "\\u2713" if clean else "\\u00b1"
|
|
126
|
+
return f"{color}{branch} {icon}{C.RESET}"
|
|
127
|
+
except Exception:
|
|
128
|
+
return None
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def mcp_servers(data):
|
|
132
|
+
servers = []
|
|
133
|
+
try:
|
|
134
|
+
cfg = Path.home() / ".claude.json"
|
|
135
|
+
if cfg.exists():
|
|
136
|
+
with open(cfg) as f:
|
|
137
|
+
servers.extend(json.load(f).get("mcpServers", {}).keys())
|
|
138
|
+
except Exception:
|
|
139
|
+
pass
|
|
140
|
+
cwd = data.get("workspace", {}).get("current_dir", "")
|
|
141
|
+
if cwd:
|
|
142
|
+
for p in [Path(cwd) / ".mcp.json", Path(cwd) / ".claude" / ".mcp.json"]:
|
|
143
|
+
try:
|
|
144
|
+
if p.exists():
|
|
145
|
+
with open(p) as f:
|
|
146
|
+
servers.extend(json.load(f).get("mcpServers", {}).keys())
|
|
147
|
+
except Exception:
|
|
148
|
+
pass
|
|
149
|
+
seen = set()
|
|
150
|
+
return [s for s in servers if not (s in seen or seen.add(s))]
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def context_pct(data):
|
|
154
|
+
try:
|
|
155
|
+
cw = data["context_window"]
|
|
156
|
+
size = cw["context_window_size"]
|
|
157
|
+
usage = cw.get("current_usage", {})
|
|
158
|
+
tokens = usage.get("input_tokens", 0) + usage.get("cache_creation_input_tokens", 0) + usage.get("cache_read_input_tokens", 0)
|
|
159
|
+
pct = (tokens * 100) // size
|
|
160
|
+
color = C.GREEN if pct < 40 else C.YELLOW if pct < 70 else C.RED
|
|
161
|
+
return f"{C.DIM}{color}{pct}%{C.RESET}"
|
|
162
|
+
except Exception:
|
|
163
|
+
return None
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def cost_info(data):
|
|
167
|
+
try:
|
|
168
|
+
cfg = Path.home() / ".claude.json"
|
|
169
|
+
if cfg.exists():
|
|
170
|
+
with open(cfg) as f:
|
|
171
|
+
if json.load(f).get("oauthAccount", {}).get("accountUuid"):
|
|
172
|
+
return None
|
|
173
|
+
except Exception:
|
|
174
|
+
pass
|
|
175
|
+
try:
|
|
176
|
+
cost = data.get("cost", {}).get("total_cost_usd")
|
|
177
|
+
if cost:
|
|
178
|
+
color = C.GREEN if cost < 1 else C.YELLOW if cost < 5 else C.RED
|
|
179
|
+
return f"{color}\${cost:.2f}{C.RESET}"
|
|
180
|
+
except Exception:
|
|
181
|
+
pass
|
|
182
|
+
return None
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
# --- Background tool scanning ---
|
|
186
|
+
|
|
187
|
+
def cache_path(tool, cwd):
|
|
188
|
+
project = Path(cwd).name if cwd else "global"
|
|
189
|
+
return CACHE_DIR / f"{tool}_{project}.json"
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def should_run(tool, cwd):
|
|
193
|
+
cp = cache_path(tool, cwd)
|
|
194
|
+
if not cp.exists():
|
|
195
|
+
return True
|
|
196
|
+
try:
|
|
197
|
+
cached = json.loads(cp.read_text())
|
|
198
|
+
return (time.time() - cached.get("timestamp", 0)) > INTERVALS[tool]
|
|
199
|
+
except Exception:
|
|
200
|
+
return True
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def run_bg(tool, args, cwd):
|
|
204
|
+
cp = cache_path(tool, cwd)
|
|
205
|
+
snippet = (
|
|
206
|
+
"import subprocess, json, time, os; "
|
|
207
|
+
f"args = {args!r}; "
|
|
208
|
+
f"cwd = {cwd!r}; "
|
|
209
|
+
f"out_path = {str(cp)!r}; "
|
|
210
|
+
"env = {**os.environ, 'FORCE_COLOR': '0', 'NO_COLOR': '1'}; "
|
|
211
|
+
"r = subprocess.run(args, cwd=cwd, capture_output=True, text=True, env=env, timeout=60); "
|
|
212
|
+
"cache = {'timestamp': time.time(), 'output': r.stdout + r.stderr, 'code': r.returncode}; "
|
|
213
|
+
"open(out_path, 'w').write(json.dumps(cache))"
|
|
214
|
+
)
|
|
215
|
+
try:
|
|
216
|
+
subprocess.Popen(
|
|
217
|
+
[sys.executable, "-c", snippet],
|
|
218
|
+
stdout=subprocess.DEVNULL,
|
|
219
|
+
stderr=subprocess.DEVNULL,
|
|
220
|
+
start_new_session=True,
|
|
221
|
+
)
|
|
222
|
+
except Exception:
|
|
223
|
+
pass
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def read_cache(tool, cwd):
|
|
227
|
+
cp = cache_path(tool, cwd)
|
|
228
|
+
if not cp.exists():
|
|
229
|
+
return None
|
|
230
|
+
try:
|
|
231
|
+
return json.loads(cp.read_text())
|
|
232
|
+
except Exception:
|
|
233
|
+
return None
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def parse_stale(cached):
|
|
237
|
+
if not cached or cached["code"] != 0:
|
|
238
|
+
return None
|
|
239
|
+
out = cached["output"]
|
|
240
|
+
drifted = out.count("\\u2717")
|
|
241
|
+
if drifted > 0:
|
|
242
|
+
return f"{C.RED}stale:{drifted}{C.RESET}"
|
|
243
|
+
if "\\u2713" in out or "No drift" in out.lower() or "clean" in out.lower():
|
|
244
|
+
return f"{C.GREEN}stale:ok{C.RESET}"
|
|
245
|
+
return None
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def parse_envalid(cached):
|
|
249
|
+
if not cached:
|
|
250
|
+
return None
|
|
251
|
+
out = cached["output"]
|
|
252
|
+
if cached["code"] != 0:
|
|
253
|
+
errors = sum(1 for line in out.split("\\n") if "\\u2717" in line or "error" in line.lower() or "missing" in line.lower())
|
|
254
|
+
if errors > 0:
|
|
255
|
+
return f"{C.RED}env:{errors}{C.RESET}"
|
|
256
|
+
return f"{C.YELLOW}env:?{C.RESET}"
|
|
257
|
+
return f"{C.GREEN}env:ok{C.RESET}"
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def parse_berth(cached):
|
|
261
|
+
if not cached:
|
|
262
|
+
return None
|
|
263
|
+
out = cached["output"]
|
|
264
|
+
conflicts = out.lower().count("conflict")
|
|
265
|
+
if conflicts > 0:
|
|
266
|
+
return f"{C.RED}ports:{conflicts}{C.RESET}"
|
|
267
|
+
return None
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def parse_vow(cached):
|
|
271
|
+
if not cached:
|
|
272
|
+
return None
|
|
273
|
+
out = cached["output"]
|
|
274
|
+
unknown = 0
|
|
275
|
+
for line in out.split("\\n"):
|
|
276
|
+
low = line.lower()
|
|
277
|
+
if "unknown" in low or "unlicensed" in low:
|
|
278
|
+
for word in line.split():
|
|
279
|
+
if word.isdigit():
|
|
280
|
+
unknown += int(word)
|
|
281
|
+
break
|
|
282
|
+
else:
|
|
283
|
+
unknown += 1
|
|
284
|
+
if unknown > 0:
|
|
285
|
+
return f"{C.YELLOW}lic:{unknown}?{C.RESET}"
|
|
286
|
+
return None
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def parse_aware(cached):
|
|
290
|
+
if not cached:
|
|
291
|
+
return None
|
|
292
|
+
out = cached["output"]
|
|
293
|
+
if cached["code"] != 0 or "stale" in out.lower() or "outdated" in out.lower() or "drift" in out.lower():
|
|
294
|
+
return f"{C.YELLOW}aware:stale{C.RESET}"
|
|
295
|
+
return None
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def run_scans(cwd):
|
|
299
|
+
if not cwd:
|
|
300
|
+
return []
|
|
301
|
+
|
|
302
|
+
scans = {
|
|
303
|
+
"stale": (["npx", "--yes", "@whenlabs/stale", "scan"], parse_stale),
|
|
304
|
+
"envalid": (["npx", "--yes", "@whenlabs/envalid", "validate"], parse_envalid),
|
|
305
|
+
"berth": (["npx", "--yes", "@whenlabs/berth", "check", "."], parse_berth),
|
|
306
|
+
"vow": (["npx", "--yes", "@whenlabs/vow", "scan"], parse_vow),
|
|
307
|
+
"aware": (["npx", "--yes", "@whenlabs/aware", "doctor"], parse_aware),
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
for tool, (args, _) in scans.items():
|
|
311
|
+
if should_run(tool, cwd):
|
|
312
|
+
run_bg(tool, args, cwd)
|
|
313
|
+
break
|
|
314
|
+
|
|
315
|
+
results = []
|
|
316
|
+
for tool, (_, parser) in scans.items():
|
|
317
|
+
cached = read_cache(tool, cwd)
|
|
318
|
+
if cached:
|
|
319
|
+
parsed = parser(cached)
|
|
320
|
+
if parsed:
|
|
321
|
+
results.append(parsed)
|
|
322
|
+
|
|
323
|
+
return results
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def main():
|
|
327
|
+
try:
|
|
328
|
+
data = json.loads(sys.stdin.read())
|
|
329
|
+
except Exception:
|
|
330
|
+
return
|
|
331
|
+
|
|
332
|
+
parts = []
|
|
333
|
+
|
|
334
|
+
cwd = data.get("workspace", {}).get("current_dir", "")
|
|
335
|
+
if cwd:
|
|
336
|
+
parts.append(f"{C.BLUE}{Path(cwd).name}{C.RESET}")
|
|
337
|
+
|
|
338
|
+
if cwd:
|
|
339
|
+
g = git_info(cwd)
|
|
340
|
+
if g:
|
|
341
|
+
parts.append(g)
|
|
342
|
+
|
|
343
|
+
servers = mcp_servers(data)
|
|
344
|
+
if servers:
|
|
345
|
+
names = " ".join(servers)
|
|
346
|
+
parts.append(f"{C.AMBER}{C.DIM}{names}{C.RESET}")
|
|
347
|
+
|
|
348
|
+
c = cost_info(data)
|
|
349
|
+
if c:
|
|
350
|
+
parts.append(c)
|
|
351
|
+
|
|
352
|
+
model = data.get("model", {}).get("display_name", "")
|
|
353
|
+
if model:
|
|
354
|
+
short = "".join(ch for ch in model if ch.isalpha()).lower()
|
|
355
|
+
parts.append(f"{C.GRAY}{short}{C.RESET}")
|
|
356
|
+
|
|
357
|
+
ctx = context_pct(data)
|
|
358
|
+
if ctx:
|
|
359
|
+
parts.append(ctx)
|
|
360
|
+
|
|
361
|
+
ver = data.get("version")
|
|
362
|
+
if ver:
|
|
363
|
+
parts.append(f"{C.DIM}{C.GRAY}v{ver}{C.RESET}")
|
|
364
|
+
|
|
365
|
+
scan_results = run_scans(cwd)
|
|
366
|
+
|
|
367
|
+
print(C.SEP.join(parts))
|
|
368
|
+
|
|
369
|
+
if scan_results:
|
|
370
|
+
print(f"{C.DIM}{C.GRAY} tools:{C.RESET} {f' {C.DIM}|{C.RESET} '.join(scan_results)}")
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
if __name__ == "__main__":
|
|
374
|
+
main()
|
|
375
|
+
`;
|
|
376
|
+
function installStatusLine() {
|
|
377
|
+
try {
|
|
378
|
+
mkdirSync(SCRIPTS_DIR, { recursive: true });
|
|
379
|
+
writeFileSync(STATUSLINE_PATH, STATUSLINE_SCRIPT, "utf-8");
|
|
380
|
+
chmodSync(STATUSLINE_PATH, 493);
|
|
381
|
+
let settings = {};
|
|
382
|
+
if (existsSync(SETTINGS_PATH)) {
|
|
383
|
+
try {
|
|
384
|
+
settings = JSON.parse(readFileSync(SETTINGS_PATH, "utf-8"));
|
|
385
|
+
} catch {
|
|
386
|
+
settings = {};
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
const statuslineCmd = `python3 ${STATUSLINE_PATH}`;
|
|
390
|
+
const currentCmd = settings.statusLine?.command;
|
|
391
|
+
if (currentCmd !== statuslineCmd) {
|
|
392
|
+
settings.statusLine = { type: "command", command: statuslineCmd };
|
|
393
|
+
writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
394
|
+
}
|
|
395
|
+
return { installed: true, message: "Status line installed (proactive background scans)" };
|
|
396
|
+
} catch (err) {
|
|
397
|
+
return { installed: false, message: `Status line install failed: ${err.message}` };
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
function escapeRegex(str) {
|
|
401
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
402
|
+
}
|
|
403
|
+
function hasOldBlock(filePath) {
|
|
404
|
+
if (!existsSync(filePath)) return false;
|
|
405
|
+
const content = readFileSync(filePath, "utf-8");
|
|
406
|
+
return content.includes(OLD_START_MARKER) && content.includes(OLD_END_MARKER);
|
|
407
|
+
}
|
|
408
|
+
function removeOldBlock(filePath) {
|
|
409
|
+
if (!existsSync(filePath)) return;
|
|
410
|
+
let content = readFileSync(filePath, "utf-8");
|
|
411
|
+
const pattern = new RegExp(
|
|
412
|
+
`\\n?${escapeRegex(OLD_START_MARKER)}[\\s\\S]*?${escapeRegex(OLD_END_MARKER)}\\n?`,
|
|
413
|
+
"g"
|
|
414
|
+
);
|
|
415
|
+
content = content.replace(pattern, "\n").replace(/\n{3,}/g, "\n\n").trimEnd() + "\n";
|
|
416
|
+
writeFileSync(filePath, content, "utf-8");
|
|
417
|
+
}
|
|
418
|
+
async function install(options = {}) {
|
|
419
|
+
console.log("\n\u{1F527} WhenLabs toolkit installer\n");
|
|
420
|
+
const editorFlags = options.all ? ALL_EDITORS : [
|
|
421
|
+
options.cursor && "cursor",
|
|
422
|
+
options.vscode && "vscode",
|
|
423
|
+
options.windsurf && "windsurf"
|
|
424
|
+
].filter(Boolean);
|
|
425
|
+
const claudeOnly = editorFlags.length === 0;
|
|
426
|
+
if (claudeOnly) {
|
|
427
|
+
const mcpResult = registerMcpServer();
|
|
428
|
+
console.log(mcpResult.success ? ` \u2713 ${mcpResult.message}` : ` \u2717 ${mcpResult.message}`);
|
|
429
|
+
injectBlock(CLAUDE_MD_PATH, CLAUDE_MD_CONTENT);
|
|
430
|
+
console.log(` \u2713 CLAUDE.md instructions written to ${CLAUDE_MD_PATH}`);
|
|
431
|
+
const slResult = installStatusLine();
|
|
432
|
+
console.log(slResult.installed ? ` \u2713 ${slResult.message}` : ` \u2717 ${slResult.message}`);
|
|
433
|
+
if (hasOldBlock(CLAUDE_MD_PATH)) {
|
|
434
|
+
removeOldBlock(CLAUDE_MD_PATH);
|
|
435
|
+
console.log(" \u2713 Removed legacy velocity-mcp markers (migrated to whenlabs block)");
|
|
436
|
+
}
|
|
437
|
+
} else {
|
|
438
|
+
for (const editor of editorFlags) {
|
|
439
|
+
const result = installForEditor(editor);
|
|
440
|
+
console.log(result.success ? ` \u2713 ${result.message}` : ` \u2717 ${result.message}`);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
console.log("\nInstallation complete. Run `when status` to verify.\n");
|
|
444
|
+
}
|
|
445
|
+
export {
|
|
446
|
+
install
|
|
447
|
+
};
|
package/package.json
CHANGED
package/dist/install-HPF26YW2.js
DELETED
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
ALL_EDITORS,
|
|
4
|
-
installForEditor,
|
|
5
|
-
registerMcpServer
|
|
6
|
-
} from "./chunk-3PDLNC63.js";
|
|
7
|
-
import {
|
|
8
|
-
injectBlock
|
|
9
|
-
} from "./chunk-NYUYV3UL.js";
|
|
10
|
-
|
|
11
|
-
// src/commands/install.ts
|
|
12
|
-
import { join } from "path";
|
|
13
|
-
import { homedir } from "os";
|
|
14
|
-
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
15
|
-
var CLAUDE_MD_PATH = join(homedir(), ".claude", "CLAUDE.md");
|
|
16
|
-
var OLD_START_MARKER = "<!-- velocity-mcp:start -->";
|
|
17
|
-
var OLD_END_MARKER = "<!-- velocity-mcp:end -->";
|
|
18
|
-
var CLAUDE_MD_CONTENT = `# WhenLabs Toolkit
|
|
19
|
-
|
|
20
|
-
## Task Timing (velocity-mcp)
|
|
21
|
-
|
|
22
|
-
Velocity tools are part of the unified \`whenlabs\` MCP server. Follow these rules for EVERY session:
|
|
23
|
-
|
|
24
|
-
1. **Before starting any discrete coding task**, call \`velocity_start_task\` with:
|
|
25
|
-
- Appropriate \`category\` (scaffold, implement, refactor, debug, test, config, docs, deploy)
|
|
26
|
-
- Relevant \`tags\` (e.g. typescript, react, sqlite, api)
|
|
27
|
-
- Clear \`description\` of what you're about to do
|
|
28
|
-
- \`estimated_files\` if you know how many files you'll touch
|
|
29
|
-
- \`project\` set to the current project name (auto-detected from git remote or directory name)
|
|
30
|
-
|
|
31
|
-
2. **After completing each task**, call \`velocity_end_task\` with:
|
|
32
|
-
- The \`task_id\` from the start call
|
|
33
|
-
- \`status\`: completed, failed, or abandoned
|
|
34
|
-
- \`actual_files\`: how many files were actually modified
|
|
35
|
-
- \`notes\`: any useful context about what happened
|
|
36
|
-
|
|
37
|
-
3. **When creating a multi-step plan**, call \`velocity_estimate\` to provide the user with a time estimate before starting work.
|
|
38
|
-
|
|
39
|
-
4. **If the user asks about speed or performance**, call \`velocity_stats\` to show aggregate data.
|
|
40
|
-
|
|
41
|
-
### Guidelines
|
|
42
|
-
- Every discrete unit of work should be tracked \u2014 don't batch multiple unrelated changes into one task
|
|
43
|
-
- If a task is abandoned or fails, still call \`velocity_end_task\` with the appropriate status
|
|
44
|
-
- Use consistent tags across sessions so the similarity matching can find comparable historical tasks
|
|
45
|
-
- Keep descriptions concise but specific enough to be useful for future matching
|
|
46
|
-
|
|
47
|
-
## WhenLabs MCP Tools (ALWAYS prefer these over shell commands)
|
|
48
|
-
|
|
49
|
-
All six tools (including velocity) are available through the unified \`whenlabs\` MCP server. **ALWAYS use these MCP tools instead of running shell commands like lsof, grep, or manual checks.** These tools are purpose-built and give better results:
|
|
50
|
-
|
|
51
|
-
| When to use | Call this tool | NOT this |
|
|
52
|
-
|-------------|---------------|----------|
|
|
53
|
-
| Check ports or port conflicts | \`berth_status\` or \`berth_check\` | \`lsof\`, \`netstat\`, \`ss\` |
|
|
54
|
-
| Scan dependency licenses | \`vow_scan\` or \`vow_check\` | manual \`npm ls\`, \`license-checker\` |
|
|
55
|
-
| Check if docs are stale | \`stale_scan\` | manual file comparison |
|
|
56
|
-
| Validate .env files | \`envalid_validate\` or \`envalid_detect\` | manual .env inspection |
|
|
57
|
-
| Generate AI context files | \`aware_init\` or \`aware_doctor\` | manual CLAUDE.md creation |
|
|
58
|
-
|
|
59
|
-
### Tool Reference
|
|
60
|
-
- \`berth_status\` \u2014 Show all active ports, Docker ports, and configured ports
|
|
61
|
-
- \`berth_check\` \u2014 Scan a project directory for port conflicts
|
|
62
|
-
- \`stale_scan\` \u2014 Detect documentation drift in the codebase
|
|
63
|
-
- \`envalid_validate\` \u2014 Validate .env files against their schema
|
|
64
|
-
- \`envalid_detect\` \u2014 Find undocumented env vars in codebase
|
|
65
|
-
- \`aware_init\` \u2014 Auto-detect stack and generate AI context files
|
|
66
|
-
- \`aware_doctor\` \u2014 Diagnose project health and config issues
|
|
67
|
-
- \`vow_scan\` \u2014 Scan and summarize all dependency licenses
|
|
68
|
-
- \`vow_check\` \u2014 Validate licenses against a policy file
|
|
69
|
-
|
|
70
|
-
### Proactive Background Scans
|
|
71
|
-
WhenLabs tools run automatically in the background on a schedule. The status line shows findings:
|
|
72
|
-
- \`stale:N\` \u2014 N docs have drifted from code. Run \`stale_scan\` and fix the drift.
|
|
73
|
-
- \`env:N\` \u2014 N .env issues found. Run \`envalid_validate\` and help the user fix them.
|
|
74
|
-
- \`ports:N\` \u2014 N port conflicts. Run \`berth_status\` and suggest resolution.
|
|
75
|
-
- \`lic:N?\` \u2014 N packages with unknown licenses. Run \`vow_scan\` for details.
|
|
76
|
-
- \`aware:stale\` \u2014 AI context files are outdated. Run \`aware_init\` to regenerate.
|
|
77
|
-
|
|
78
|
-
**When you see any of these in the status line, proactively tell the user and offer to fix the issue.** Do not wait for the user to ask.`;
|
|
79
|
-
function escapeRegex(str) {
|
|
80
|
-
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
81
|
-
}
|
|
82
|
-
function hasOldBlock(filePath) {
|
|
83
|
-
if (!existsSync(filePath)) return false;
|
|
84
|
-
const content = readFileSync(filePath, "utf-8");
|
|
85
|
-
return content.includes(OLD_START_MARKER) && content.includes(OLD_END_MARKER);
|
|
86
|
-
}
|
|
87
|
-
function removeOldBlock(filePath) {
|
|
88
|
-
if (!existsSync(filePath)) return;
|
|
89
|
-
let content = readFileSync(filePath, "utf-8");
|
|
90
|
-
const pattern = new RegExp(
|
|
91
|
-
`\\n?${escapeRegex(OLD_START_MARKER)}[\\s\\S]*?${escapeRegex(OLD_END_MARKER)}\\n?`,
|
|
92
|
-
"g"
|
|
93
|
-
);
|
|
94
|
-
content = content.replace(pattern, "\n").replace(/\n{3,}/g, "\n\n").trimEnd() + "\n";
|
|
95
|
-
writeFileSync(filePath, content, "utf-8");
|
|
96
|
-
}
|
|
97
|
-
async function install(options = {}) {
|
|
98
|
-
console.log("\n\u{1F527} WhenLabs toolkit installer\n");
|
|
99
|
-
const editorFlags = options.all ? ALL_EDITORS : [
|
|
100
|
-
options.cursor && "cursor",
|
|
101
|
-
options.vscode && "vscode",
|
|
102
|
-
options.windsurf && "windsurf"
|
|
103
|
-
].filter(Boolean);
|
|
104
|
-
const claudeOnly = editorFlags.length === 0;
|
|
105
|
-
if (claudeOnly) {
|
|
106
|
-
const mcpResult = registerMcpServer();
|
|
107
|
-
console.log(mcpResult.success ? ` \u2713 ${mcpResult.message}` : ` \u2717 ${mcpResult.message}`);
|
|
108
|
-
injectBlock(CLAUDE_MD_PATH, CLAUDE_MD_CONTENT);
|
|
109
|
-
console.log(` \u2713 CLAUDE.md instructions written to ${CLAUDE_MD_PATH}`);
|
|
110
|
-
if (hasOldBlock(CLAUDE_MD_PATH)) {
|
|
111
|
-
removeOldBlock(CLAUDE_MD_PATH);
|
|
112
|
-
console.log(" \u2713 Removed legacy velocity-mcp markers (migrated to whenlabs block)");
|
|
113
|
-
}
|
|
114
|
-
} else {
|
|
115
|
-
for (const editor of editorFlags) {
|
|
116
|
-
const result = installForEditor(editor);
|
|
117
|
-
console.log(result.success ? ` \u2713 ${result.message}` : ` \u2717 ${result.message}`);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
console.log("\nInstallation complete. Run `when status` to verify.\n");
|
|
121
|
-
}
|
|
122
|
-
export {
|
|
123
|
-
install
|
|
124
|
-
};
|