@lovrozagar/grunt 0.5.2 → 0.6.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/.agents/agents/grunt.md +2 -24
- package/.agents/agents/orchestrator.md +5 -50
- package/.agents/skills/ask/SKILL.md +5 -12
- package/.agents/skills/auto/SKILL.md +7 -12
- package/.agents/skills/browser/SKILL.md +4 -8
- package/.agents/skills/clasp/SKILL.md +11 -0
- package/.agents/skills/commit/SKILL.md +3 -1
- package/.agents/skills/commit-push/SKILL.md +2 -0
- package/.agents/skills/commit-push-deploy/SKILL.md +2 -0
- package/.agents/skills/commit-push-release/SKILL.md +2 -0
- package/.agents/skills/explain/SKILL.md +7 -13
- package/.agents/skills/google-workspace/SKILL.md +13 -0
- package/.agents/skills/handoff/SKILL.md +7 -11
- package/.agents/skills/implement-plan/SKILL.md +5 -109
- package/.agents/skills/listen/SKILL.md +15 -0
- package/.agents/skills/pickup/SKILL.md +6 -12
- package/.agents/skills/speak/SKILL.md +13 -0
- package/.agents/skills/tmp/SKILL.md +6 -10
- package/.agents/skills/write-plan/SKILL.md +5 -77
- package/.claude/agents/grunt.md +2 -26
- package/.claude/agents/orchestrator.md +12 -50
- package/.claude/skills/ask/SKILL.md +5 -12
- package/.claude/skills/auto/SKILL.md +7 -12
- package/.claude/skills/browser/SKILL.md +4 -8
- package/.claude/skills/clasp/SKILL.md +11 -0
- package/.claude/skills/commit/SKILL.md +3 -1
- package/.claude/skills/commit-push/SKILL.md +2 -0
- package/.claude/skills/commit-push-deploy/SKILL.md +2 -0
- package/.claude/skills/commit-push-release/SKILL.md +2 -0
- package/.claude/skills/explain/SKILL.md +7 -13
- package/.claude/skills/google-workspace/SKILL.md +13 -0
- package/.claude/skills/handoff/SKILL.md +7 -11
- package/.claude/skills/implement-plan/SKILL.md +5 -109
- package/.claude/skills/listen/SKILL.md +15 -0
- package/.claude/skills/pickup/SKILL.md +6 -12
- package/.claude/skills/speak/SKILL.md +13 -0
- package/.claude/skills/tmp/SKILL.md +6 -10
- package/.claude/skills/write-plan/SKILL.md +5 -77
- package/.codex/agents/grunt.toml +2 -26
- package/.codex/agents/orchestrator.toml +4 -50
- package/.grok/agents/grunt.md +2 -24
- package/.grok/agents/orchestrator.md +4 -48
- package/.grok/global-settings.toml +1 -3
- package/.grok/hooks/orchestrate-parent.js +190 -358
- package/.grok/skills/ask/SKILL.md +5 -12
- package/.grok/skills/auto/SKILL.md +7 -12
- package/.grok/skills/browser/SKILL.md +4 -8
- package/.grok/skills/clasp/SKILL.md +11 -0
- package/.grok/skills/commit/SKILL.md +3 -1
- package/.grok/skills/commit-push/SKILL.md +2 -0
- package/.grok/skills/commit-push-deploy/SKILL.md +2 -0
- package/.grok/skills/commit-push-release/SKILL.md +2 -0
- package/.grok/skills/explain/SKILL.md +7 -13
- package/.grok/skills/google-workspace/SKILL.md +13 -0
- package/.grok/skills/handoff/SKILL.md +7 -11
- package/.grok/skills/implement-plan/SKILL.md +5 -109
- package/.grok/skills/listen/SKILL.md +15 -0
- package/.grok/skills/pickup/SKILL.md +6 -12
- package/.grok/skills/speak/SKILL.md +13 -0
- package/.grok/skills/tmp/SKILL.md +6 -10
- package/.grok/skills/write-plan/SKILL.md +5 -77
- package/.rulesync/hooks.jsonc +1 -1
- package/.rulesync/mcp-policy.jsonc +0 -2
- package/.rulesync/reference/INDEX.md +30 -20
- package/.rulesync/reference/browser.md +13 -6
- package/.rulesync/reference/cascade.md +6 -86
- package/.rulesync/reference/clasp.md +21 -0
- package/.rulesync/reference/google-workspace.md +79 -0
- package/.rulesync/reference/hooks.md +7 -11
- package/.rulesync/reference/implementation-format.md +113 -0
- package/.rulesync/reference/law.md +5 -3
- package/.rulesync/reference/listen.md +80 -0
- package/.rulesync/reference/map.md +40 -36
- package/.rulesync/reference/output.md +3 -51
- package/.rulesync/reference/plan-format.md +4 -2
- package/.rulesync/reference/refs-map.md +12 -5
- package/.rulesync/reference/rtk.md +3 -2
- package/.rulesync/reference/scope.md +37 -0
- package/.rulesync/reference/setup.md +27 -0
- package/.rulesync/reference/skills-map.md +13 -12
- package/.rulesync/reference/speak.md +59 -0
- package/.rulesync/rules/CLAUDE.md +3 -49
- package/.rulesync/rules/overview.md +9 -47
- package/.rulesync/skills/ask/SKILL.md +5 -12
- package/.rulesync/skills/auto/SKILL.md +5 -12
- package/.rulesync/skills/browser/SKILL.md +4 -8
- package/.rulesync/skills/clasp/SKILL.md +9 -0
- package/.rulesync/skills/commit/SKILL.md +3 -1
- package/.rulesync/skills/commit-push/SKILL.md +2 -0
- package/.rulesync/skills/commit-push-deploy/SKILL.md +2 -0
- package/.rulesync/skills/commit-push-release/SKILL.md +2 -0
- package/.rulesync/skills/explain/SKILL.md +7 -14
- package/.rulesync/skills/google-workspace/SKILL.md +11 -0
- package/.rulesync/skills/handoff/SKILL.md +7 -11
- package/.rulesync/skills/implement-plan/SKILL.md +5 -119
- package/.rulesync/skills/listen/SKILL.md +13 -0
- package/.rulesync/skills/pickup/SKILL.md +6 -12
- package/.rulesync/skills/speak/SKILL.md +13 -0
- package/.rulesync/skills/tmp/SKILL.md +6 -10
- package/.rulesync/skills/write-plan/SKILL.md +5 -84
- package/.rulesync/subagents/grunt.md +2 -24
- package/.rulesync/subagents/orchestrator.md +14 -59
- package/AGENTS.md +9 -47
- package/CHANGELOG.md +67 -0
- package/CLAUDE.md +1 -49
- package/README.md +89 -80
- package/cli/grunt.mjs +21 -3
- package/cli/grunt.test.ts +47 -3
- package/cli/init.mjs +134 -234
- package/cli/init.test.ts +241 -58
- package/cli/prompt.mjs +9 -36
- package/cli/prompt.test.ts +14 -0
- package/package.json +10 -2
- package/scripts/browser.mjs +128 -7
- package/scripts/doctor.mjs +104 -10
- package/scripts/emit-gemini.mjs +2 -2
- package/scripts/emit-mcp-policy.mjs +1 -1
- package/scripts/gate-fat-tools.mjs +223 -173
- package/scripts/google-workspace.mjs +713 -0
- package/scripts/grunt-job.mjs +453 -69
- package/scripts/guarded-md.mjs +230 -0
- package/scripts/guarded-roots.mjs +1 -1
- package/scripts/interactive.mjs +15 -0
- package/scripts/listen.mjs +829 -0
- package/scripts/parse-need.mjs +21 -2
- package/scripts/persist-implementation.mjs +278 -0
- package/scripts/prompt.mjs +32 -0
- package/scripts/scrub-spawn-prompt.mjs +3 -50
- package/scripts/setup.mjs +746 -0
- package/scripts/speak.mjs +291 -0
- package/.agents/agents/implementer.md +0 -32
- package/.agents/agents/thinker.md +0 -29
- package/.agents/skills/cascade/SKILL.md +0 -28
- package/.agents/skills/parent/SKILL.md +0 -6
- package/.agents/skills/solo/SKILL.md +0 -25
- package/.claude/agents/implementer.md +0 -37
- package/.claude/agents/thinker.md +0 -36
- package/.claude/skills/cascade/SKILL.md +0 -28
- package/.claude/skills/parent/SKILL.md +0 -6
- package/.claude/skills/solo/SKILL.md +0 -25
- package/.codex/agents/implementer.toml +0 -26
- package/.codex/agents/thinker.toml +0 -26
- package/.grok/agents/implementer.md +0 -27
- package/.grok/agents/thinker.md +0 -27
- package/.grok/parent.md +0 -1
- package/.grok/roles/implementer.toml +0 -5
- package/.grok/roles/thinker.toml +0 -5
- package/.grok/skills/cascade/SKILL.md +0 -28
- package/.grok/skills/parent/SKILL.md +0 -6
- package/.grok/skills/solo/SKILL.md +0 -25
- package/.rulesync/grunt.config.jsonc +0 -7
- package/.rulesync/skills/cascade/SKILL.md +0 -28
- package/.rulesync/skills/parent/SKILL.md +0 -6
- package/.rulesync/skills/solo/SKILL.md +0 -25
- package/.rulesync/subagents/implementer.md +0 -49
- package/.rulesync/subagents/thinker.md +0 -49
- package/scripts/grunt-config.mjs +0 -50
package/scripts/browser.mjs
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
/** Session browser rail: nav|snap|click|fill|shot|pdf|stop|doctor|ensure. Lightpanda default. */
|
|
3
|
-
import { spawn } from "node:child_process";
|
|
2
|
+
/** Session browser rail: nav|snap|click|fill|scroll|wait|hover|select|shot|pdf|stop|doctor|ensure. Lightpanda default. */
|
|
3
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
4
4
|
import crypto from "node:crypto";
|
|
5
5
|
import fs from "node:fs";
|
|
6
6
|
import http from "node:http";
|
|
7
7
|
import net from "node:net";
|
|
8
8
|
import path from "node:path";
|
|
9
9
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
10
|
+
import { writeStash } from "./grunt-job.mjs";
|
|
10
11
|
import {
|
|
11
12
|
CHROMIUM_BINS,
|
|
12
13
|
LIGHTPANDA_REPO as DOCTOR_LP_REPO,
|
|
@@ -64,9 +65,21 @@ export function isPaintHost(url) {
|
|
|
64
65
|
if ((h === "google.com" || h.endsWith(".google.com")) && u.pathname.startsWith("/earth")) {
|
|
65
66
|
return true;
|
|
66
67
|
}
|
|
68
|
+
if (/(^|\.)amazon\./.test(h)) return true;
|
|
67
69
|
return false;
|
|
68
70
|
}
|
|
69
71
|
|
|
72
|
+
const BLOCKED_SNAP_RE =
|
|
73
|
+
/enable javascript|javascript (is )?(required|disabled)|checking your browser|just a moment|captcha|access denied|sorry.*robot|continue shopping|attention required|cf-browser-verification|verify you are a human/i;
|
|
74
|
+
|
|
75
|
+
export function isBlockedSnap(markdown, lines) {
|
|
76
|
+
const refs = Array.isArray(lines) ? lines.length : 0;
|
|
77
|
+
const md = String(markdown || "").trim();
|
|
78
|
+
if (BLOCKED_SNAP_RE.test(md)) return true;
|
|
79
|
+
const body = md.replace(/^#\s+\S+\s*$/m, "").trim();
|
|
80
|
+
return refs === 0 && body.length < 80;
|
|
81
|
+
}
|
|
82
|
+
|
|
70
83
|
export function lookupBins(pathEnv, platform = process.platform) {
|
|
71
84
|
const lightpanda = whichBin("lightpanda", pathEnv, platform);
|
|
72
85
|
let chromium = "";
|
|
@@ -124,6 +137,21 @@ function alive(pid) {
|
|
|
124
137
|
|
|
125
138
|
function reap(pid) {
|
|
126
139
|
if (!pid) return;
|
|
140
|
+
if (process.platform === "win32") {
|
|
141
|
+
try {
|
|
142
|
+
process.kill(pid);
|
|
143
|
+
} catch {
|
|
144
|
+
/* ignore */
|
|
145
|
+
}
|
|
146
|
+
if (alive(pid)) {
|
|
147
|
+
spawnSync("taskkill", ["/PID", String(pid), "/T", "/F"], {
|
|
148
|
+
stdio: "ignore",
|
|
149
|
+
timeout: 1500,
|
|
150
|
+
windowsHide: true,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
127
155
|
try {
|
|
128
156
|
process.kill(-pid, "SIGTERM");
|
|
129
157
|
} catch {
|
|
@@ -348,8 +376,10 @@ async function handleCdp(state, method, params = {}) {
|
|
|
348
376
|
state.markdown = `# ${state.url}\n\nhello`;
|
|
349
377
|
return { frameId: "f1" };
|
|
350
378
|
case "LP.getMarkdown":
|
|
379
|
+
if (state.thinSnap) return { markdown: `# ${state.url || ""}\n` };
|
|
351
380
|
return { markdown: state.markdown || `# ${state.url || ""}\n\nhello` };
|
|
352
381
|
case "Accessibility.getFullAXTree":
|
|
382
|
+
if (state.thinSnap) return { nodes: [] };
|
|
353
383
|
return { nodes: state.axNodes || defaultAx(state.url) };
|
|
354
384
|
case "DOM.resolveNode": {
|
|
355
385
|
const id = params.backendNodeId;
|
|
@@ -378,6 +408,7 @@ export async function serveCdp(state = {}, { port = 0 } = {}) {
|
|
|
378
408
|
url: "about:blank",
|
|
379
409
|
markdown: "",
|
|
380
410
|
failProbe: false,
|
|
411
|
+
thinSnap: false,
|
|
381
412
|
engine: "lightpanda",
|
|
382
413
|
...state,
|
|
383
414
|
};
|
|
@@ -464,6 +495,19 @@ function engineBin(engine, bins) {
|
|
|
464
495
|
return bins.chromium;
|
|
465
496
|
}
|
|
466
497
|
|
|
498
|
+
function spawnEngine(bin, args, opts) {
|
|
499
|
+
if (process.platform === "win32" && /\.(cmd|bat)$/i.test(bin)) {
|
|
500
|
+
const mjs = `${String(bin).replace(/\.(cmd|bat)$/i, "")}.mjs`;
|
|
501
|
+
try {
|
|
502
|
+
fs.accessSync(mjs, fs.constants.F_OK);
|
|
503
|
+
return spawn(process.execPath, [mjs, ...args], opts);
|
|
504
|
+
} catch {
|
|
505
|
+
return spawn(bin, args, { ...opts, shell: true });
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
return spawn(bin, args, opts);
|
|
509
|
+
}
|
|
510
|
+
|
|
467
511
|
function spawnArgs(engine, port, profile) {
|
|
468
512
|
if (engine === "lightpanda") return ["serve", "--host", "127.0.0.1", "--port", String(port)];
|
|
469
513
|
return [
|
|
@@ -485,7 +529,7 @@ async function launchEngine(engine, ctx) {
|
|
|
485
529
|
const profile = path.join(dir, "profile");
|
|
486
530
|
fs.mkdirSync(profile, { recursive: true });
|
|
487
531
|
const port = await freePort();
|
|
488
|
-
const child =
|
|
532
|
+
const child = spawnEngine(bin, spawnArgs(engine, port, profile), {
|
|
489
533
|
detached: true,
|
|
490
534
|
stdio: ["ignore", "pipe", "pipe"],
|
|
491
535
|
env: ctx.env,
|
|
@@ -606,7 +650,7 @@ export async function runBrowser(argv, opts = {}) {
|
|
|
606
650
|
const verb = String(argv[0] || "").toLowerCase();
|
|
607
651
|
try {
|
|
608
652
|
if (!verb || verb === "help" || verb === "-h" || verb === "--help") {
|
|
609
|
-
return ok("nav|snap|click|fill|shot|pdf|stop|doctor|ensure");
|
|
653
|
+
return ok("nav|snap|click|fill|scroll|wait|hover|select|shot|pdf|stop|doctor|ensure");
|
|
610
654
|
}
|
|
611
655
|
if (verb === "doctor" || verb === "ensure") {
|
|
612
656
|
return runDoctor({ pathEnv: env.PATH, platform, cwd });
|
|
@@ -650,16 +694,79 @@ export async function runBrowser(argv, opts = {}) {
|
|
|
650
694
|
return ok(`engine: ${session.engine}\nurl: ${session.lastURL}`);
|
|
651
695
|
}
|
|
652
696
|
if (verb === "snap") {
|
|
653
|
-
|
|
697
|
+
let session = readSession(cwd);
|
|
654
698
|
if (!session || !alive(session.pid)) return fail("no browser session; run nav");
|
|
655
|
-
|
|
699
|
+
let { markdown, lines } = await snapshot(session);
|
|
700
|
+
if (
|
|
701
|
+
session.engine === "lightpanda" &&
|
|
702
|
+
(session.swapCount || 0) < 1 &&
|
|
703
|
+
bins.chromium &&
|
|
704
|
+
isBlockedSnap(markdown, lines)
|
|
705
|
+
) {
|
|
706
|
+
session = await swapToChromium(session, ctx);
|
|
707
|
+
({ markdown, lines } = await snapshot(session));
|
|
708
|
+
}
|
|
656
709
|
writeSession(cwd, session);
|
|
710
|
+
const raw = [markdown.trim(), "", ...lines].join("\n");
|
|
711
|
+
const headings = String(markdown || "")
|
|
712
|
+
.split(/\n/)
|
|
713
|
+
.filter((l) => /^#{1,6}\s/.test(l));
|
|
714
|
+
const shown = [...headings.slice(0, 6), ...lines.slice(0, 24)];
|
|
715
|
+
if (raw.length > 32 * 1024 || lines.length > 6) {
|
|
716
|
+
const stash = writeStash(cwd, "snap", raw);
|
|
717
|
+
return ok(
|
|
718
|
+
`engine: ${session.engine}\nurl: ${session.lastURL}\n${shown.length} shown. stash=${stash}\n${shown.join("\n")}`,
|
|
719
|
+
);
|
|
720
|
+
}
|
|
657
721
|
const body = [markdown.trim(), "", ...lines].filter((x, i, a) => x !== "" || a[i - 1] !== "").join("\n");
|
|
658
722
|
return ok(`engine: ${session.engine}\nurl: ${session.lastURL}\n\n${body}`);
|
|
659
723
|
}
|
|
660
|
-
if (verb === "
|
|
724
|
+
if (verb === "wait") {
|
|
661
725
|
const session = readSession(cwd);
|
|
662
726
|
if (!session || !alive(session.pid)) return fail("no browser session; run nav");
|
|
727
|
+
const raw = String(argv[1] || "1000");
|
|
728
|
+
const ms = Math.min(15000, Math.max(0, Number(raw) || 0));
|
|
729
|
+
await sleep(ms);
|
|
730
|
+
return ok(`wait: ${ms}`);
|
|
731
|
+
}
|
|
732
|
+
if (verb === "scroll") {
|
|
733
|
+
let session = readSession(cwd);
|
|
734
|
+
if (!session || !alive(session.pid)) return fail("no browser session; run nav");
|
|
735
|
+
const arg = String(argv[1] || "down");
|
|
736
|
+
const refs = session.lastRefs || {};
|
|
737
|
+
const rec = refs[arg];
|
|
738
|
+
const act = async (sess) =>
|
|
739
|
+
withCdp(sess, async (cdp) => {
|
|
740
|
+
if (rec) {
|
|
741
|
+
const resolved = await cdp.send("DOM.resolveNode", { backendNodeId: rec.backendNodeId });
|
|
742
|
+
const objectId = resolved.object && resolved.object.objectId;
|
|
743
|
+
if (!objectId) throw new Error("stale");
|
|
744
|
+
await cdp.send("Runtime.callFunctionOn", {
|
|
745
|
+
objectId,
|
|
746
|
+
functionDeclaration: "function() { this.scrollIntoView({ block: 'center' }); }",
|
|
747
|
+
});
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
let dy = 600;
|
|
751
|
+
if (arg === "up") dy = -600;
|
|
752
|
+
else if (/^-?\d+$/.test(arg)) dy = Number(arg);
|
|
753
|
+
await cdp.send("Runtime.evaluate", {
|
|
754
|
+
expression: `window.scrollBy(0, ${dy})`,
|
|
755
|
+
});
|
|
756
|
+
});
|
|
757
|
+
try {
|
|
758
|
+
await act(session);
|
|
759
|
+
} catch (err) {
|
|
760
|
+
const msg = String(err && err.message ? err.message : err);
|
|
761
|
+
if (/stale|missing/i.test(msg)) return fail(`stale ref: ${arg}`);
|
|
762
|
+
return fail(msg);
|
|
763
|
+
}
|
|
764
|
+
writeSession(cwd, session);
|
|
765
|
+
return ok(`scroll: ${arg}`);
|
|
766
|
+
}
|
|
767
|
+
if (verb === "click" || verb === "fill" || verb === "hover" || verb === "select") {
|
|
768
|
+
let session = readSession(cwd);
|
|
769
|
+
if (!session || !alive(session.pid)) return fail("no browser session; run nav");
|
|
663
770
|
const refs = session.lastRefs || {};
|
|
664
771
|
if (!Object.keys(refs).length) return fail("no snap refs; run snap");
|
|
665
772
|
const refId = String(argv[1] || "");
|
|
@@ -667,6 +774,7 @@ export async function runBrowser(argv, opts = {}) {
|
|
|
667
774
|
if (!rec) return fail(`missing ref: ${refId}`);
|
|
668
775
|
const text = argv.slice(2).join(" ");
|
|
669
776
|
if (verb === "fill" && !text) return fail("fill <ref> <text>");
|
|
777
|
+
if (verb === "select" && !text) return fail("select <ref> <value>");
|
|
670
778
|
const act = async (sess) =>
|
|
671
779
|
withCdp(sess, async (cdp) => {
|
|
672
780
|
const resolved = await cdp.send("DOM.resolveNode", { backendNodeId: rec.backendNodeId });
|
|
@@ -677,6 +785,19 @@ export async function runBrowser(argv, opts = {}) {
|
|
|
677
785
|
objectId,
|
|
678
786
|
functionDeclaration: "function() { this.click(); }",
|
|
679
787
|
});
|
|
788
|
+
} else if (verb === "hover") {
|
|
789
|
+
await cdp.send("Runtime.callFunctionOn", {
|
|
790
|
+
objectId,
|
|
791
|
+
functionDeclaration:
|
|
792
|
+
"function() { this.dispatchEvent(new MouseEvent('mouseover', { bubbles: true })); }",
|
|
793
|
+
});
|
|
794
|
+
} else if (verb === "select") {
|
|
795
|
+
await cdp.send("Runtime.callFunctionOn", {
|
|
796
|
+
objectId,
|
|
797
|
+
arguments: [{ value: text }],
|
|
798
|
+
functionDeclaration:
|
|
799
|
+
"function(v) { this.value = v; this.dispatchEvent(new Event('change', { bubbles: true })); }",
|
|
800
|
+
});
|
|
680
801
|
} else {
|
|
681
802
|
await cdp.send("Runtime.callFunctionOn", {
|
|
682
803
|
objectId,
|
package/scripts/doctor.mjs
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
/** Unified prereq doctor. Print-only install hints. Never runs installs. */
|
|
3
3
|
import { execFileSync } from "node:child_process";
|
|
4
4
|
import fs from "node:fs";
|
|
5
|
+
import os from "node:os";
|
|
5
6
|
import path from "node:path";
|
|
6
7
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
7
8
|
import {
|
|
@@ -41,20 +42,32 @@ export function nodeMajor(version) {
|
|
|
41
42
|
return m ? Number(m[1]) : 0;
|
|
42
43
|
}
|
|
43
44
|
|
|
45
|
+
function winShim(bin) {
|
|
46
|
+
return process.platform === "win32" && /\.(cmd|bat)$/i.test(String(bin || ""));
|
|
47
|
+
}
|
|
48
|
+
|
|
44
49
|
export function whichBin(name, pathEnv = process.env.PATH, platform = process.platform) {
|
|
45
50
|
if (!name) return "";
|
|
51
|
+
const hostWin = process.platform === "win32";
|
|
46
52
|
const delim =
|
|
47
|
-
platform === "win32" && String(pathEnv || "").includes(";")
|
|
53
|
+
(platform === "win32" || hostWin) && String(pathEnv || "").includes(";")
|
|
54
|
+
? ";"
|
|
55
|
+
: path.delimiter;
|
|
48
56
|
const dirs = String(pathEnv || "").split(delim);
|
|
49
|
-
const names = [
|
|
50
|
-
if (platform === "win32" && !path.extname(name)) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
57
|
+
const names = [];
|
|
58
|
+
if ((hostWin || platform === "win32") && !path.extname(name)) {
|
|
59
|
+
if (hostWin) {
|
|
60
|
+
const pathext = String(process.env.PATHEXT || ".EXE;.CMD;.BAT;.COM");
|
|
61
|
+
for (const raw of pathext.split(";")) {
|
|
62
|
+
if (!raw) continue;
|
|
63
|
+
names.push(name + raw.toLowerCase());
|
|
64
|
+
names.push(name + raw);
|
|
65
|
+
}
|
|
56
66
|
}
|
|
57
67
|
names.push(`${name}.exe`);
|
|
68
|
+
names.push(name);
|
|
69
|
+
} else {
|
|
70
|
+
names.push(name);
|
|
58
71
|
}
|
|
59
72
|
const uniq = [...new Set(names)];
|
|
60
73
|
for (const dir of dirs) {
|
|
@@ -65,7 +78,7 @@ export function whichBin(name, pathEnv = process.env.PATH, platform = process.pl
|
|
|
65
78
|
fs.accessSync(candidate, fs.constants.X_OK);
|
|
66
79
|
return candidate;
|
|
67
80
|
} catch {
|
|
68
|
-
if (
|
|
81
|
+
if (hostWin) {
|
|
69
82
|
try {
|
|
70
83
|
fs.accessSync(candidate, fs.constants.F_OK);
|
|
71
84
|
return candidate;
|
|
@@ -96,6 +109,7 @@ function readNodeVersion(bin) {
|
|
|
96
109
|
encoding: "utf8",
|
|
97
110
|
timeout: 8000,
|
|
98
111
|
stdio: ["ignore", "pipe", "pipe"],
|
|
112
|
+
shell: winShim(bin),
|
|
99
113
|
}),
|
|
100
114
|
).trim();
|
|
101
115
|
} catch {
|
|
@@ -166,11 +180,72 @@ export function installHints(id, platform = process.platform) {
|
|
|
166
180
|
}
|
|
167
181
|
return ["sudo apt install chromium"];
|
|
168
182
|
}
|
|
183
|
+
if (id === "ffmpeg") {
|
|
184
|
+
if (platform === "win32") return ["winget install Gyan.FFmpeg"];
|
|
185
|
+
if (platform === "darwin") return ["brew install ffmpeg"];
|
|
186
|
+
return ["sudo apt install ffmpeg"];
|
|
187
|
+
}
|
|
188
|
+
if (id === "whisper-cli") {
|
|
189
|
+
if (platform === "win32") {
|
|
190
|
+
return ["whisper.cpp release: whisper-cli.exe on PATH", "https://github.com/ggml-org/whisper.cpp"];
|
|
191
|
+
}
|
|
192
|
+
if (platform === "darwin") return ["brew install whisper-cpp"];
|
|
193
|
+
return ["https://github.com/ggml-org/whisper.cpp"];
|
|
194
|
+
}
|
|
169
195
|
return [];
|
|
170
196
|
}
|
|
171
197
|
|
|
198
|
+
function isFile(abs) {
|
|
199
|
+
try {
|
|
200
|
+
return fs.statSync(abs).isFile();
|
|
201
|
+
} catch {
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export function speakDoctorStatus({ env = process.env, home = os.homedir() } = {}) {
|
|
207
|
+
const names = [];
|
|
208
|
+
if (String(env.ELEVENLABS_API_KEY || "").trim()) names.push("elevenlabs");
|
|
209
|
+
if (String(env.OPENAI_API_KEY || "").trim()) names.push("openai");
|
|
210
|
+
const hasFile = isFile(path.join(home, ".grunt", "speak.json"));
|
|
211
|
+
if (!names.length && !hasFile) return { ok: false, extra: "" };
|
|
212
|
+
return { ok: true, extra: names.length ? names.join(",") : "config" };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const WORKSPACE_ACCOUNT_RE = /^[a-zA-Z0-9_-]{1,40}$/;
|
|
216
|
+
const WORKSPACE_KIND_ORDER = ["oauth", "tokens", "adc", "clasprc"];
|
|
217
|
+
|
|
218
|
+
export function workspaceDoctorStatus({ home = os.homedir() } = {}) {
|
|
219
|
+
const grunt = path.join(home, ".grunt");
|
|
220
|
+
const kinds = new Set();
|
|
221
|
+
const scanAccountDir = (dir) => {
|
|
222
|
+
if (isFile(path.join(dir, "google-oauth.json"))) kinds.add("oauth");
|
|
223
|
+
if (isFile(path.join(dir, "tokens.json"))) kinds.add("tokens");
|
|
224
|
+
};
|
|
225
|
+
if (isFile(path.join(grunt, "google-oauth.json"))) kinds.add("oauth");
|
|
226
|
+
if (isFile(path.join(grunt, "workspace-tokens.json"))) kinds.add("tokens");
|
|
227
|
+
scanAccountDir(path.join(grunt, "workspace", "default"));
|
|
228
|
+
const root = path.join(grunt, "workspace");
|
|
229
|
+
try {
|
|
230
|
+
for (const ent of fs.readdirSync(root, { withFileTypes: true })) {
|
|
231
|
+
if (!ent.isDirectory() || !WORKSPACE_ACCOUNT_RE.test(ent.name)) continue;
|
|
232
|
+
scanAccountDir(path.join(root, ent.name));
|
|
233
|
+
}
|
|
234
|
+
} catch {
|
|
235
|
+
/* none */
|
|
236
|
+
}
|
|
237
|
+
if (
|
|
238
|
+
isFile(path.join(home, ".config", "gcloud", "application_default_credentials.json"))
|
|
239
|
+
) {
|
|
240
|
+
kinds.add("adc");
|
|
241
|
+
}
|
|
242
|
+
if (isFile(path.join(home, ".clasprc.json"))) kinds.add("clasprc");
|
|
243
|
+
if (!kinds.size) return { ok: false, extra: "" };
|
|
244
|
+
return { ok: true, extra: WORKSPACE_KIND_ORDER.filter((k) => kinds.has(k)).join(",") };
|
|
245
|
+
}
|
|
246
|
+
|
|
172
247
|
function row(name, status, extra = "") {
|
|
173
|
-
const a = String(name).padEnd(
|
|
248
|
+
const a = String(name).padEnd(18);
|
|
174
249
|
const b = String(status).padEnd(18);
|
|
175
250
|
const c = extra ? String(extra).trim() : "";
|
|
176
251
|
return c ? `${a} ${b} ${c}` : `${a} ${b}`;
|
|
@@ -205,6 +280,8 @@ export function runDoctor({
|
|
|
205
280
|
platform = process.platform,
|
|
206
281
|
execPath = process.execPath,
|
|
207
282
|
nodeVersion = process.version,
|
|
283
|
+
env = process.env,
|
|
284
|
+
home = os.homedir(),
|
|
208
285
|
} = {}) {
|
|
209
286
|
const nmPath = withNmBin(cwd, pathEnv, platform);
|
|
210
287
|
const pathNode = whichBin("node", pathEnv, platform);
|
|
@@ -226,6 +303,10 @@ export function runDoctor({
|
|
|
226
303
|
const lightpanda = whichBin("lightpanda", pathEnv, platform);
|
|
227
304
|
const chromium = lookupChromium(pathEnv, platform);
|
|
228
305
|
const gh = whichBin("gh", pathEnv, platform);
|
|
306
|
+
const clasp = whichBin("clasp", pathEnv, platform);
|
|
307
|
+
const ffmpeg = whichBin("ffmpeg", pathEnv, platform);
|
|
308
|
+
const whisperCli =
|
|
309
|
+
whichBin("whisper-cli", pathEnv, platform) || whichBin("whisper-cpp", pathEnv, platform);
|
|
229
310
|
|
|
230
311
|
const found = {
|
|
231
312
|
node: nodeOk ? nodeBin : "",
|
|
@@ -252,6 +333,19 @@ export function runDoctor({
|
|
|
252
333
|
lines.push(lightpanda ? row("lightpanda", "ok", lightpanda) : row("lightpanda", "missing"));
|
|
253
334
|
lines.push(chromium ? row("chromium", "ok", chromium) : row("chromium", "missing"));
|
|
254
335
|
lines.push(gh ? row("gh", "ok", gh) : row("gh", "missing (optional)"));
|
|
336
|
+
lines.push(clasp ? row("clasp", "ok", clasp) : row("clasp", "missing (optional)"));
|
|
337
|
+
const workspace = workspaceDoctorStatus({ home });
|
|
338
|
+
lines.push(
|
|
339
|
+
workspace.ok
|
|
340
|
+
? row("google-workspace", "ok", workspace.extra)
|
|
341
|
+
: row("google-workspace", "missing (optional)"),
|
|
342
|
+
);
|
|
343
|
+
lines.push(ffmpeg ? row("ffmpeg", "ok", ffmpeg) : row("ffmpeg", "missing (optional)"));
|
|
344
|
+
lines.push(
|
|
345
|
+
whisperCli ? row("whisper-cli", "ok", whisperCli) : row("whisper-cli", "missing (optional)"),
|
|
346
|
+
);
|
|
347
|
+
const speak = speakDoctorStatus({ env, home });
|
|
348
|
+
lines.push(speak.ok ? row("speak", "ok", speak.extra) : row("speak", "missing (optional)"));
|
|
255
349
|
|
|
256
350
|
const missingRequired = REQUIRED.filter((k) => (k === "node" ? !nodeOk : !found[k]));
|
|
257
351
|
if (missingRequired.length) {
|
package/scripts/emit-gemini.mjs
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
6
|
-
import { guardedMarkdownDrift, writeMergedGuardedFile } from "
|
|
6
|
+
import { guardedMarkdownDrift, writeMergedGuardedFile } from "./guarded-md.mjs";
|
|
7
7
|
|
|
8
|
-
export const AGENT_IDS = ["orchestrator", "
|
|
8
|
+
export const AGENT_IDS = ["orchestrator", "grunt"];
|
|
9
9
|
const SSOT_REL = ".rulesync/subagents";
|
|
10
10
|
const GEMINI_MD_REL = "GEMINI.md";
|
|
11
11
|
const AGENTS_REL = ".gemini/agents";
|
|
@@ -12,7 +12,7 @@ const MCP_JSON_REL = ".mcp.json";
|
|
|
12
12
|
const CODEX_REL = ".codex/config.toml";
|
|
13
13
|
const AGENTS_MCP_REL = ".agents/mcp_config.json";
|
|
14
14
|
const GEMINI_SETTINGS_REL = ".gemini/settings.json";
|
|
15
|
-
const AGENT_IDS = ["orchestrator", "
|
|
15
|
+
const AGENT_IDS = ["orchestrator", "grunt"];
|
|
16
16
|
const MCP_DENY = "mcp__*";
|
|
17
17
|
const AGENT_ORCHESTRATOR_DENY = "Agent(orchestrator)";
|
|
18
18
|
const GROK_HEADER =
|