@lovrozagar/grunt 0.3.11 → 0.4.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/.agents/agents/grunt.md +1 -0
- package/.agents/agents/implementer.md +1 -0
- package/.agents/agents/orchestrator.md +2 -1
- package/.agents/skills/browser/SKILL.md +17 -0
- package/.agents/skills/handoff/SKILL.md +2 -2
- package/.agents/skills/pickup/SKILL.md +64 -0
- package/.agents/skills/solo/SKILL.md +1 -1
- package/.claude/agents/grunt.md +1 -0
- package/.claude/agents/implementer.md +1 -0
- package/.claude/agents/orchestrator.md +2 -1
- package/.claude/skills/browser/SKILL.md +17 -0
- package/.claude/skills/handoff/SKILL.md +2 -2
- package/.claude/skills/pickup/SKILL.md +64 -0
- package/.claude/skills/solo/SKILL.md +1 -1
- package/.codex/agents/grunt.toml +1 -0
- package/.codex/agents/implementer.toml +1 -0
- package/.codex/agents/orchestrator.toml +2 -1
- package/.grok/agents/grunt.md +1 -0
- package/.grok/agents/implementer.md +1 -0
- package/.grok/agents/orchestrator.md +2 -1
- package/.grok/hooks/orchestrate-parent.js +1 -0
- package/.grok/skills/browser/SKILL.md +17 -0
- package/.grok/skills/handoff/SKILL.md +2 -2
- package/.grok/skills/pickup/SKILL.md +64 -0
- package/.grok/skills/solo/SKILL.md +1 -1
- package/.rulesync/reference/browser.md +59 -0
- package/.rulesync/reference/cascade.md +1 -0
- package/.rulesync/reference/hooks.md +1 -1
- package/.rulesync/reference/map.md +6 -3
- package/.rulesync/reference/rtk.md +4 -1
- package/.rulesync/rules/CLAUDE.md +2 -1
- package/.rulesync/rules/overview.md +2 -1
- package/.rulesync/skills/browser/SKILL.md +19 -0
- package/.rulesync/skills/handoff/SKILL.md +2 -2
- package/.rulesync/skills/pickup/SKILL.md +64 -0
- package/.rulesync/skills/solo/SKILL.md +1 -1
- package/.rulesync/subagents/grunt.md +1 -0
- package/.rulesync/subagents/implementer.md +1 -0
- package/.rulesync/subagents/orchestrator.md +2 -1
- package/AGENTS.md +2 -1
- package/CLAUDE.md +2 -1
- package/README.md +35 -5
- package/cli/grunt.mjs +2 -2
- package/cli/grunt.test.ts +3 -3
- package/cli/init.mjs +2 -0
- package/cli/init.test.ts +2 -0
- package/package.json +5 -2
- package/scripts/browser.mjs +764 -0
- package/scripts/doctor.mjs +252 -0
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/** Unified prereq doctor. Print-only install hints. Never runs installs. */
|
|
3
|
+
import { execFileSync } from "node:child_process";
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
7
|
+
|
|
8
|
+
export const CHROMIUM_BINS = [
|
|
9
|
+
"chromium",
|
|
10
|
+
"chromium-browser",
|
|
11
|
+
"google-chrome",
|
|
12
|
+
"google-chrome-stable",
|
|
13
|
+
"chrome",
|
|
14
|
+
"msedge",
|
|
15
|
+
"microsoft-edge",
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
export const LIGHTPANDA_REPO = "https://github.com/lightpanda-io/browser";
|
|
19
|
+
const NODE_URL = "https://nodejs.org";
|
|
20
|
+
const RTK_CURL =
|
|
21
|
+
"curl -fsSL https://raw.githubusercontent.com/rtk-ai/rtk/master/install.sh | sh";
|
|
22
|
+
const RTK_DOCS = "https://www.rtk-ai.app/docs/getting-started/installation/";
|
|
23
|
+
const LP_INSTALL_SH = "curl -fsSL https://pkg.lightpanda.io/install.sh | bash";
|
|
24
|
+
const RULESYNC_SCHEMA = "rulesync schema doctor: npm run rulesync:doctor";
|
|
25
|
+
const REQUIRED = ["node", "npm", "git", "rtk", "rulesync", "lightpanda", "chromium"];
|
|
26
|
+
|
|
27
|
+
export function nodeMajor(version) {
|
|
28
|
+
const m = String(version || "").match(/(\d+)/);
|
|
29
|
+
return m ? Number(m[1]) : 0;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function whichBin(name, pathEnv = process.env.PATH, platform = process.platform) {
|
|
33
|
+
if (!name) return "";
|
|
34
|
+
const delim =
|
|
35
|
+
platform === "win32" && String(pathEnv || "").includes(";") ? ";" : path.delimiter;
|
|
36
|
+
const dirs = String(pathEnv || "").split(delim);
|
|
37
|
+
const names = [name];
|
|
38
|
+
if (platform === "win32" && !path.extname(name)) {
|
|
39
|
+
const pathext = String(process.env.PATHEXT || ".EXE;.CMD;.BAT;.COM");
|
|
40
|
+
for (const raw of pathext.split(";")) {
|
|
41
|
+
if (!raw) continue;
|
|
42
|
+
names.push(name + raw);
|
|
43
|
+
names.push(name + raw.toLowerCase());
|
|
44
|
+
}
|
|
45
|
+
names.push(`${name}.exe`);
|
|
46
|
+
}
|
|
47
|
+
const uniq = [...new Set(names)];
|
|
48
|
+
for (const dir of dirs) {
|
|
49
|
+
if (!dir) continue;
|
|
50
|
+
for (const n of uniq) {
|
|
51
|
+
const candidate = path.join(dir, n);
|
|
52
|
+
try {
|
|
53
|
+
fs.accessSync(candidate, fs.constants.X_OK);
|
|
54
|
+
return candidate;
|
|
55
|
+
} catch {
|
|
56
|
+
if (platform === "win32") {
|
|
57
|
+
try {
|
|
58
|
+
fs.accessSync(candidate, fs.constants.F_OK);
|
|
59
|
+
return candidate;
|
|
60
|
+
} catch {
|
|
61
|
+
/* try next */
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return "";
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function delimOf(pathEnv, platform) {
|
|
71
|
+
return platform === "win32" && String(pathEnv || "").includes(";") ? ";" : path.delimiter;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function withNmBin(cwd, pathEnv, platform) {
|
|
75
|
+
const nm = path.join(cwd || "", "node_modules", ".bin");
|
|
76
|
+
const d = delimOf(pathEnv, platform);
|
|
77
|
+
return `${nm}${d}${pathEnv || ""}`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function readNodeVersion(bin) {
|
|
81
|
+
try {
|
|
82
|
+
return String(
|
|
83
|
+
execFileSync(bin, ["-v"], {
|
|
84
|
+
encoding: "utf8",
|
|
85
|
+
timeout: 8000,
|
|
86
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
87
|
+
}),
|
|
88
|
+
).trim();
|
|
89
|
+
} catch {
|
|
90
|
+
return "";
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function lookupChromium(pathEnv, platform) {
|
|
95
|
+
for (const n of CHROMIUM_BINS) {
|
|
96
|
+
const hit = whichBin(n, pathEnv, platform);
|
|
97
|
+
if (hit) return hit;
|
|
98
|
+
}
|
|
99
|
+
return "";
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function installHints(id, platform = process.platform) {
|
|
103
|
+
if (id === "all") {
|
|
104
|
+
const keys = ["node", "npm", "git", "rtk", "rulesync", "lightpanda", "chromium"];
|
|
105
|
+
return keys.flatMap((k) => installHints(k, platform));
|
|
106
|
+
}
|
|
107
|
+
if (id === "node") {
|
|
108
|
+
if (platform === "win32") {
|
|
109
|
+
return [`${NODE_URL} (≥22)`, "nvm / OS pkg", "winget install OpenJS.NodeJS.LTS"];
|
|
110
|
+
}
|
|
111
|
+
return [`${NODE_URL} (≥22)`, "nvm / OS pkg"];
|
|
112
|
+
}
|
|
113
|
+
if (id === "npm") {
|
|
114
|
+
return ["npm ships with Node ≥22", NODE_URL];
|
|
115
|
+
}
|
|
116
|
+
if (id === "git") {
|
|
117
|
+
if (platform === "win32") return ["winget install Git.Git"];
|
|
118
|
+
if (platform === "darwin") return ["brew install git"];
|
|
119
|
+
return ["sudo apt install git"];
|
|
120
|
+
}
|
|
121
|
+
if (id === "rtk") {
|
|
122
|
+
if (platform === "win32") {
|
|
123
|
+
return ["rtk.exe on PATH (release zip) or WSL curl", RTK_DOCS];
|
|
124
|
+
}
|
|
125
|
+
return [RTK_CURL, "brew install rtk"];
|
|
126
|
+
}
|
|
127
|
+
if (id === "rulesync") {
|
|
128
|
+
return ["npm i -D rulesync", "npx"];
|
|
129
|
+
}
|
|
130
|
+
if (id === "lightpanda") {
|
|
131
|
+
if (platform === "win32") {
|
|
132
|
+
return [
|
|
133
|
+
"Windows: native Lightpanda not required; WSL2 only",
|
|
134
|
+
"wsl --install",
|
|
135
|
+
`in WSL: ${LP_INSTALL_SH}`,
|
|
136
|
+
LIGHTPANDA_REPO,
|
|
137
|
+
];
|
|
138
|
+
}
|
|
139
|
+
if (platform === "darwin") {
|
|
140
|
+
return [LP_INSTALL_SH, "brew tap lightpanda-io/browser", "brew install lightpanda-io/browser/lightpanda", LIGHTPANDA_REPO];
|
|
141
|
+
}
|
|
142
|
+
return [LP_INSTALL_SH, LIGHTPANDA_REPO];
|
|
143
|
+
}
|
|
144
|
+
if (id === "chromium") {
|
|
145
|
+
if (platform === "win32") {
|
|
146
|
+
return [
|
|
147
|
+
"winget install Google.Chrome",
|
|
148
|
+
"winget install Microsoft.Edge",
|
|
149
|
+
"add chrome.exe or msedge.exe to PATH or shim",
|
|
150
|
+
];
|
|
151
|
+
}
|
|
152
|
+
if (platform === "darwin") {
|
|
153
|
+
return ["brew install --cask chromium", "brew install --cask google-chrome"];
|
|
154
|
+
}
|
|
155
|
+
return ["sudo apt install chromium"];
|
|
156
|
+
}
|
|
157
|
+
return [];
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function row(name, status, extra = "") {
|
|
161
|
+
const a = String(name).padEnd(10);
|
|
162
|
+
const b = String(status).padEnd(18);
|
|
163
|
+
const c = extra ? String(extra).trim() : "";
|
|
164
|
+
return c ? `${a} ${b} ${c}` : `${a} ${b}`;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function runDoctor({
|
|
168
|
+
cwd = process.cwd(),
|
|
169
|
+
pathEnv = process.env.PATH,
|
|
170
|
+
platform = process.platform,
|
|
171
|
+
execPath = process.execPath,
|
|
172
|
+
nodeVersion = process.version,
|
|
173
|
+
} = {}) {
|
|
174
|
+
const nmPath = withNmBin(cwd, pathEnv, platform);
|
|
175
|
+
const pathNode = whichBin("node", pathEnv, platform);
|
|
176
|
+
const nodeBin = pathNode || execPath || "";
|
|
177
|
+
let ver = "";
|
|
178
|
+
if (nodeBin) {
|
|
179
|
+
ver =
|
|
180
|
+
execPath && nodeBin === execPath
|
|
181
|
+
? String(nodeVersion || process.version)
|
|
182
|
+
: readNodeVersion(nodeBin);
|
|
183
|
+
}
|
|
184
|
+
const major = nodeMajor(ver);
|
|
185
|
+
const nodeOk = Boolean(nodeBin) && major >= 22;
|
|
186
|
+
|
|
187
|
+
const npm = whichBin("npm", pathEnv, platform);
|
|
188
|
+
const git = whichBin("git", pathEnv, platform);
|
|
189
|
+
const rtk = whichBin("rtk", nmPath, platform);
|
|
190
|
+
const rulesync = whichBin("rulesync", nmPath, platform);
|
|
191
|
+
const lightpanda = whichBin("lightpanda", pathEnv, platform);
|
|
192
|
+
const chromium = lookupChromium(pathEnv, platform);
|
|
193
|
+
const gh = whichBin("gh", pathEnv, platform);
|
|
194
|
+
|
|
195
|
+
const found = {
|
|
196
|
+
node: nodeOk ? nodeBin : "",
|
|
197
|
+
npm,
|
|
198
|
+
git,
|
|
199
|
+
rtk,
|
|
200
|
+
rulesync,
|
|
201
|
+
lightpanda,
|
|
202
|
+
chromium,
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const lines = [];
|
|
206
|
+
if (nodeBin && !nodeOk) {
|
|
207
|
+
lines.push(row("node", "need ≥22", `${ver} (${nodeBin})`));
|
|
208
|
+
} else if (nodeOk) {
|
|
209
|
+
lines.push(row("node", "ok", `${ver} (${nodeBin})`));
|
|
210
|
+
} else {
|
|
211
|
+
lines.push(row("node", "missing"));
|
|
212
|
+
}
|
|
213
|
+
lines.push(npm ? row("npm", "ok", npm) : row("npm", "missing"));
|
|
214
|
+
lines.push(git ? row("git", "ok", git) : row("git", "missing"));
|
|
215
|
+
lines.push(rtk ? row("rtk", "ok", rtk) : row("rtk", "missing"));
|
|
216
|
+
lines.push(rulesync ? row("rulesync", "ok", rulesync) : row("rulesync", "missing"));
|
|
217
|
+
lines.push(lightpanda ? row("lightpanda", "ok", lightpanda) : row("lightpanda", "missing"));
|
|
218
|
+
lines.push(chromium ? row("chromium", "ok", chromium) : row("chromium", "missing"));
|
|
219
|
+
lines.push(gh ? row("gh", "ok", gh) : row("gh", "missing (optional)"));
|
|
220
|
+
|
|
221
|
+
const missingRequired = REQUIRED.filter((k) => (k === "node" ? !nodeOk : !found[k]));
|
|
222
|
+
if (missingRequired.length) {
|
|
223
|
+
lines.push("");
|
|
224
|
+
lines.push("install (print-only; not run):");
|
|
225
|
+
const seen = new Set();
|
|
226
|
+
for (const k of missingRequired) {
|
|
227
|
+
for (const h of installHints(k, platform)) {
|
|
228
|
+
if (seen.has(h)) continue;
|
|
229
|
+
seen.add(h);
|
|
230
|
+
lines.push(h);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
lines.push("");
|
|
236
|
+
lines.push(RULESYNC_SCHEMA);
|
|
237
|
+
const stdout = `${lines.join("\n")}\n`;
|
|
238
|
+
return { code: missingRequired.length ? 1 : 0, stdout, stderr: "" };
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function main() {
|
|
242
|
+
const r = runDoctor();
|
|
243
|
+
if (r.stdout) process.stdout.write(r.stdout);
|
|
244
|
+
if (r.stderr) process.stderr.write(r.stderr);
|
|
245
|
+
process.exit(r.code);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const thisFile = fileURLToPath(import.meta.url);
|
|
249
|
+
const invoked = process.argv[1] ? path.resolve(process.argv[1]) : "";
|
|
250
|
+
if (invoked === thisFile || import.meta.url === pathToFileURL(invoked).href) {
|
|
251
|
+
main();
|
|
252
|
+
}
|