@lovrozagar/grunt 0.5.0 → 0.6.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 +2 -23
- package/.agents/agents/orchestrator.md +5 -45
- package/.agents/skills/ask/SKILL.md +11 -0
- package/.agents/skills/auto/SKILL.md +13 -0
- package/.agents/skills/browser/SKILL.md +4 -7
- 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 -108
- 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 +54 -0
- package/.agents/skills/write-plan/SKILL.md +5 -74
- package/.claude/agents/grunt.md +2 -25
- package/.claude/agents/orchestrator.md +12 -45
- package/.claude/skills/ask/SKILL.md +11 -0
- package/.claude/skills/auto/SKILL.md +13 -0
- package/.claude/skills/browser/SKILL.md +4 -7
- 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 -108
- 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 +54 -0
- package/.claude/skills/write-plan/SKILL.md +5 -74
- package/.codex/agents/grunt.toml +2 -25
- package/.codex/agents/orchestrator.toml +4 -45
- package/.grok/agents/grunt.md +2 -23
- package/.grok/agents/orchestrator.md +4 -43
- package/.grok/global-settings.toml +1 -3
- package/.grok/hooks/orchestrate-parent.js +278 -228
- package/.grok/skills/ask/SKILL.md +11 -0
- package/.grok/skills/auto/SKILL.md +13 -0
- package/.grok/skills/browser/SKILL.md +4 -7
- 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 -108
- 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 +54 -0
- package/.grok/skills/write-plan/SKILL.md +5 -74
- package/.rulesync/hooks.jsonc +1 -1
- package/.rulesync/mcp-policy.jsonc +0 -2
- package/.rulesync/reference/INDEX.md +30 -17
- package/.rulesync/reference/browser.md +13 -6
- package/.rulesync/reference/cascade.md +6 -84
- package/.rulesync/reference/clasp.md +21 -0
- package/.rulesync/reference/google-workspace.md +79 -0
- package/.rulesync/reference/hooks.md +7 -9
- 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 +39 -31
- package/.rulesync/reference/output.md +3 -41
- package/.rulesync/reference/plan-format.md +14 -12
- 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 -9
- package/.rulesync/reference/speak.md +59 -0
- package/.rulesync/rules/CLAUDE.md +3 -44
- package/.rulesync/rules/overview.md +9 -42
- package/.rulesync/skills/ask/SKILL.md +11 -0
- package/.rulesync/skills/auto/SKILL.md +11 -0
- 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 -116
- 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 +54 -0
- package/.rulesync/skills/write-plan/SKILL.md +5 -80
- package/.rulesync/subagents/grunt.md +2 -23
- package/.rulesync/subagents/orchestrator.md +14 -54
- package/AGENTS.md +9 -42
- package/CHANGELOG.md +89 -0
- package/CLAUDE.md +1 -44
- package/README.md +97 -88
- package/cli/grunt.mjs +29 -13
- package/cli/grunt.test.ts +62 -20
- package/cli/init.mjs +218 -31
- package/cli/init.test.ts +669 -46
- package/cli/prompt.mjs +8 -0
- package/cli/prompt.test.ts +14 -0
- package/package.json +13 -1
- package/scripts/browser.mjs +98 -5
- package/scripts/doctor.mjs +82 -1
- package/scripts/emit-gemini.mjs +1 -1
- 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/listen.mjs +829 -0
- package/scripts/parse-need.mjs +21 -2
- package/scripts/persist-implementation.mjs +278 -0
- package/scripts/persist-plan.mjs +17 -2
- package/scripts/persist-tmp.mjs +123 -0
- package/scripts/scrub-spawn-prompt.mjs +4 -51
- 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 -28
- package/.agents/skills/cascade/SKILL.md +0 -26
- package/.agents/skills/parent/SKILL.md +0 -6
- package/.agents/skills/solo/SKILL.md +0 -23
- package/.claude/agents/implementer.md +0 -37
- package/.claude/agents/thinker.md +0 -35
- package/.claude/skills/cascade/SKILL.md +0 -26
- package/.claude/skills/parent/SKILL.md +0 -6
- package/.claude/skills/solo/SKILL.md +0 -23
- package/.codex/agents/implementer.toml +0 -26
- package/.codex/agents/thinker.toml +0 -25
- package/.grok/agents/implementer.md +0 -27
- package/.grok/agents/thinker.md +0 -26
- 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 -26
- package/.grok/skills/parent/SKILL.md +0 -6
- package/.grok/skills/solo/SKILL.md +0 -23
- package/.rulesync/skills/cascade/SKILL.md +0 -26
- package/.rulesync/skills/parent/SKILL.md +0 -6
- package/.rulesync/skills/solo/SKILL.md +0 -23
- package/.rulesync/subagents/implementer.md +0 -49
- package/.rulesync/subagents/thinker.md +0 -48
package/cli/prompt.mjs
CHANGED
|
@@ -31,6 +31,14 @@ export async function confirm(opts) {
|
|
|
31
31
|
return bailIfCancel(await clack.confirm(opts))
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
export async function text(opts) {
|
|
35
|
+
return bailIfCancel(await clack.text(opts))
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function password(opts) {
|
|
39
|
+
return bailIfCancel(await clack.password(opts))
|
|
40
|
+
}
|
|
41
|
+
|
|
34
42
|
export function spinner() {
|
|
35
43
|
return clack.spinner()
|
|
36
44
|
}
|
package/cli/prompt.test.ts
CHANGED
|
@@ -2,6 +2,8 @@ import { afterEach, describe, expect, it, vi } from "vitest";
|
|
|
2
2
|
|
|
3
3
|
const clackSelect = vi.hoisted(() => vi.fn());
|
|
4
4
|
const clackConfirm = vi.hoisted(() => vi.fn());
|
|
5
|
+
const clackText = vi.hoisted(() => vi.fn());
|
|
6
|
+
const clackPassword = vi.hoisted(() => vi.fn());
|
|
5
7
|
const clackSpinner = vi.hoisted(() => vi.fn());
|
|
6
8
|
const clackIsCancel = vi.hoisted(() => vi.fn());
|
|
7
9
|
const clackCancel = vi.hoisted(() => vi.fn());
|
|
@@ -9,6 +11,8 @@ const clackCancel = vi.hoisted(() => vi.fn());
|
|
|
9
11
|
vi.mock("@clack/prompts", () => ({
|
|
10
12
|
select: clackSelect,
|
|
11
13
|
confirm: clackConfirm,
|
|
14
|
+
text: clackText,
|
|
15
|
+
password: clackPassword,
|
|
12
16
|
spinner: clackSpinner,
|
|
13
17
|
isCancel: clackIsCancel,
|
|
14
18
|
cancel: clackCancel,
|
|
@@ -18,8 +22,10 @@ import {
|
|
|
18
22
|
bailIfCancel,
|
|
19
23
|
confirm,
|
|
20
24
|
isInteractive,
|
|
25
|
+
password,
|
|
21
26
|
select,
|
|
22
27
|
spinner,
|
|
28
|
+
text,
|
|
23
29
|
} from "./prompt.mjs";
|
|
24
30
|
|
|
25
31
|
function tty(isTTY: boolean) {
|
|
@@ -196,4 +202,12 @@ describe("clack wrappers", () => {
|
|
|
196
202
|
expect(spinner()).toBe(s);
|
|
197
203
|
expect(clackSpinner).toHaveBeenCalledOnce();
|
|
198
204
|
});
|
|
205
|
+
|
|
206
|
+
it("text and password bail then return", async () => {
|
|
207
|
+
clackText.mockResolvedValue("proj");
|
|
208
|
+
clackPassword.mockResolvedValue("sk_x");
|
|
209
|
+
clackIsCancel.mockReturnValue(false);
|
|
210
|
+
await expect(text({ message: "Project ID" })).resolves.toBe("proj");
|
|
211
|
+
await expect(password({ message: "API key" })).resolves.toBe("sk_x");
|
|
212
|
+
});
|
|
199
213
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lovrozagar/grunt",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Multi-agent orchestrator SoT — deep-merge rulesync trees, product scripts, and generate pipeline into a consumer repo",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agents",
|
|
@@ -36,12 +36,18 @@
|
|
|
36
36
|
"scripts/grunt-job.mjs",
|
|
37
37
|
"scripts/parse-need.mjs",
|
|
38
38
|
"scripts/persist-handoff.mjs",
|
|
39
|
+
"scripts/persist-implementation.mjs",
|
|
40
|
+
"scripts/persist-tmp.mjs",
|
|
39
41
|
"scripts/persist-plan.mjs",
|
|
40
42
|
"scripts/purge-global-mcps.mjs",
|
|
41
43
|
"scripts/scrub-spawn-prompt.mjs",
|
|
42
44
|
"scripts/scrub-text-lib.mjs",
|
|
43
45
|
"scripts/sync-global-settings.mjs",
|
|
44
46
|
"scripts/browser.mjs",
|
|
47
|
+
"scripts/speak.mjs",
|
|
48
|
+
"scripts/listen.mjs",
|
|
49
|
+
"scripts/google-workspace.mjs",
|
|
50
|
+
"scripts/setup.mjs",
|
|
45
51
|
"scripts/doctor.mjs",
|
|
46
52
|
"scripts/skill-conflicts.mjs",
|
|
47
53
|
"scripts/scrub-text",
|
|
@@ -71,12 +77,18 @@
|
|
|
71
77
|
"hooks:union": "node scripts/hooks-union.mjs",
|
|
72
78
|
"hooks:check": "node scripts/hooks-union.mjs --check",
|
|
73
79
|
"doctor": "node ./scripts/doctor.mjs",
|
|
80
|
+
"setup": "node ./scripts/setup.mjs",
|
|
74
81
|
"rulesync:doctor": "rulesync doctor",
|
|
75
82
|
"sync:globals": "node scripts/sync-global-settings.mjs",
|
|
76
83
|
"sync:globals:check": "node scripts/check-globals.mjs",
|
|
77
84
|
"sync:globals:apply": "node scripts/sync-global-settings.mjs --apply",
|
|
78
85
|
"purge:global-mcps": "node scripts/purge-global-mcps.mjs",
|
|
79
86
|
"purge:global-mcps:apply": "node scripts/purge-global-mcps.mjs --apply",
|
|
87
|
+
"antigravity": "antigravity --dangerously-skip-permissions",
|
|
88
|
+
"claude": "claude --dangerously-skip-permissions",
|
|
89
|
+
"codex": "codex --dangerously-bypass-approvals-and-sandbox",
|
|
90
|
+
"gemini": "gemini --yolo",
|
|
91
|
+
"grok": "grok --yolo",
|
|
80
92
|
"test": "vitest run --coverage"
|
|
81
93
|
},
|
|
82
94
|
"devDependencies": {
|
package/scripts/browser.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
/** Session browser rail: nav|snap|click|fill|shot|pdf|stop|doctor|ensure. Lightpanda default. */
|
|
2
|
+
/** Session browser rail: nav|snap|click|fill|scroll|wait|hover|select|shot|pdf|stop|doctor|ensure. Lightpanda default. */
|
|
3
3
|
import { spawn } from "node:child_process";
|
|
4
4
|
import crypto from "node:crypto";
|
|
5
5
|
import fs from "node:fs";
|
|
@@ -7,6 +7,7 @@ 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 = "";
|
|
@@ -348,8 +361,10 @@ async function handleCdp(state, method, params = {}) {
|
|
|
348
361
|
state.markdown = `# ${state.url}\n\nhello`;
|
|
349
362
|
return { frameId: "f1" };
|
|
350
363
|
case "LP.getMarkdown":
|
|
364
|
+
if (state.thinSnap) return { markdown: `# ${state.url || ""}\n` };
|
|
351
365
|
return { markdown: state.markdown || `# ${state.url || ""}\n\nhello` };
|
|
352
366
|
case "Accessibility.getFullAXTree":
|
|
367
|
+
if (state.thinSnap) return { nodes: [] };
|
|
353
368
|
return { nodes: state.axNodes || defaultAx(state.url) };
|
|
354
369
|
case "DOM.resolveNode": {
|
|
355
370
|
const id = params.backendNodeId;
|
|
@@ -378,6 +393,7 @@ export async function serveCdp(state = {}, { port = 0 } = {}) {
|
|
|
378
393
|
url: "about:blank",
|
|
379
394
|
markdown: "",
|
|
380
395
|
failProbe: false,
|
|
396
|
+
thinSnap: false,
|
|
381
397
|
engine: "lightpanda",
|
|
382
398
|
...state,
|
|
383
399
|
};
|
|
@@ -606,7 +622,7 @@ export async function runBrowser(argv, opts = {}) {
|
|
|
606
622
|
const verb = String(argv[0] || "").toLowerCase();
|
|
607
623
|
try {
|
|
608
624
|
if (!verb || verb === "help" || verb === "-h" || verb === "--help") {
|
|
609
|
-
return ok("nav|snap|click|fill|shot|pdf|stop|doctor|ensure");
|
|
625
|
+
return ok("nav|snap|click|fill|scroll|wait|hover|select|shot|pdf|stop|doctor|ensure");
|
|
610
626
|
}
|
|
611
627
|
if (verb === "doctor" || verb === "ensure") {
|
|
612
628
|
return runDoctor({ pathEnv: env.PATH, platform, cwd });
|
|
@@ -650,16 +666,79 @@ export async function runBrowser(argv, opts = {}) {
|
|
|
650
666
|
return ok(`engine: ${session.engine}\nurl: ${session.lastURL}`);
|
|
651
667
|
}
|
|
652
668
|
if (verb === "snap") {
|
|
653
|
-
|
|
669
|
+
let session = readSession(cwd);
|
|
654
670
|
if (!session || !alive(session.pid)) return fail("no browser session; run nav");
|
|
655
|
-
|
|
671
|
+
let { markdown, lines } = await snapshot(session);
|
|
672
|
+
if (
|
|
673
|
+
session.engine === "lightpanda" &&
|
|
674
|
+
(session.swapCount || 0) < 1 &&
|
|
675
|
+
bins.chromium &&
|
|
676
|
+
isBlockedSnap(markdown, lines)
|
|
677
|
+
) {
|
|
678
|
+
session = await swapToChromium(session, ctx);
|
|
679
|
+
({ markdown, lines } = await snapshot(session));
|
|
680
|
+
}
|
|
656
681
|
writeSession(cwd, session);
|
|
682
|
+
const raw = [markdown.trim(), "", ...lines].join("\n");
|
|
683
|
+
const headings = String(markdown || "")
|
|
684
|
+
.split(/\n/)
|
|
685
|
+
.filter((l) => /^#{1,6}\s/.test(l));
|
|
686
|
+
const shown = [...headings.slice(0, 6), ...lines.slice(0, 24)];
|
|
687
|
+
if (raw.length > 32 * 1024 || lines.length > 6) {
|
|
688
|
+
const stash = writeStash(cwd, "snap", raw);
|
|
689
|
+
return ok(
|
|
690
|
+
`engine: ${session.engine}\nurl: ${session.lastURL}\n${shown.length} shown. stash=${stash}\n${shown.join("\n")}`,
|
|
691
|
+
);
|
|
692
|
+
}
|
|
657
693
|
const body = [markdown.trim(), "", ...lines].filter((x, i, a) => x !== "" || a[i - 1] !== "").join("\n");
|
|
658
694
|
return ok(`engine: ${session.engine}\nurl: ${session.lastURL}\n\n${body}`);
|
|
659
695
|
}
|
|
660
|
-
if (verb === "
|
|
696
|
+
if (verb === "wait") {
|
|
661
697
|
const session = readSession(cwd);
|
|
662
698
|
if (!session || !alive(session.pid)) return fail("no browser session; run nav");
|
|
699
|
+
const raw = String(argv[1] || "1000");
|
|
700
|
+
const ms = Math.min(15000, Math.max(0, Number(raw) || 0));
|
|
701
|
+
await sleep(ms);
|
|
702
|
+
return ok(`wait: ${ms}`);
|
|
703
|
+
}
|
|
704
|
+
if (verb === "scroll") {
|
|
705
|
+
let session = readSession(cwd);
|
|
706
|
+
if (!session || !alive(session.pid)) return fail("no browser session; run nav");
|
|
707
|
+
const arg = String(argv[1] || "down");
|
|
708
|
+
const refs = session.lastRefs || {};
|
|
709
|
+
const rec = refs[arg];
|
|
710
|
+
const act = async (sess) =>
|
|
711
|
+
withCdp(sess, async (cdp) => {
|
|
712
|
+
if (rec) {
|
|
713
|
+
const resolved = await cdp.send("DOM.resolveNode", { backendNodeId: rec.backendNodeId });
|
|
714
|
+
const objectId = resolved.object && resolved.object.objectId;
|
|
715
|
+
if (!objectId) throw new Error("stale");
|
|
716
|
+
await cdp.send("Runtime.callFunctionOn", {
|
|
717
|
+
objectId,
|
|
718
|
+
functionDeclaration: "function() { this.scrollIntoView({ block: 'center' }); }",
|
|
719
|
+
});
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
let dy = 600;
|
|
723
|
+
if (arg === "up") dy = -600;
|
|
724
|
+
else if (/^-?\d+$/.test(arg)) dy = Number(arg);
|
|
725
|
+
await cdp.send("Runtime.evaluate", {
|
|
726
|
+
expression: `window.scrollBy(0, ${dy})`,
|
|
727
|
+
});
|
|
728
|
+
});
|
|
729
|
+
try {
|
|
730
|
+
await act(session);
|
|
731
|
+
} catch (err) {
|
|
732
|
+
const msg = String(err && err.message ? err.message : err);
|
|
733
|
+
if (/stale|missing/i.test(msg)) return fail(`stale ref: ${arg}`);
|
|
734
|
+
return fail(msg);
|
|
735
|
+
}
|
|
736
|
+
writeSession(cwd, session);
|
|
737
|
+
return ok(`scroll: ${arg}`);
|
|
738
|
+
}
|
|
739
|
+
if (verb === "click" || verb === "fill" || verb === "hover" || verb === "select") {
|
|
740
|
+
let session = readSession(cwd);
|
|
741
|
+
if (!session || !alive(session.pid)) return fail("no browser session; run nav");
|
|
663
742
|
const refs = session.lastRefs || {};
|
|
664
743
|
if (!Object.keys(refs).length) return fail("no snap refs; run snap");
|
|
665
744
|
const refId = String(argv[1] || "");
|
|
@@ -667,6 +746,7 @@ export async function runBrowser(argv, opts = {}) {
|
|
|
667
746
|
if (!rec) return fail(`missing ref: ${refId}`);
|
|
668
747
|
const text = argv.slice(2).join(" ");
|
|
669
748
|
if (verb === "fill" && !text) return fail("fill <ref> <text>");
|
|
749
|
+
if (verb === "select" && !text) return fail("select <ref> <value>");
|
|
670
750
|
const act = async (sess) =>
|
|
671
751
|
withCdp(sess, async (cdp) => {
|
|
672
752
|
const resolved = await cdp.send("DOM.resolveNode", { backendNodeId: rec.backendNodeId });
|
|
@@ -677,6 +757,19 @@ export async function runBrowser(argv, opts = {}) {
|
|
|
677
757
|
objectId,
|
|
678
758
|
functionDeclaration: "function() { this.click(); }",
|
|
679
759
|
});
|
|
760
|
+
} else if (verb === "hover") {
|
|
761
|
+
await cdp.send("Runtime.callFunctionOn", {
|
|
762
|
+
objectId,
|
|
763
|
+
functionDeclaration:
|
|
764
|
+
"function() { this.dispatchEvent(new MouseEvent('mouseover', { bubbles: true })); }",
|
|
765
|
+
});
|
|
766
|
+
} else if (verb === "select") {
|
|
767
|
+
await cdp.send("Runtime.callFunctionOn", {
|
|
768
|
+
objectId,
|
|
769
|
+
arguments: [{ value: text }],
|
|
770
|
+
functionDeclaration:
|
|
771
|
+
"function(v) { this.value = v; this.dispatchEvent(new Event('change', { bubbles: true })); }",
|
|
772
|
+
});
|
|
680
773
|
} else {
|
|
681
774
|
await cdp.send("Runtime.callFunctionOn", {
|
|
682
775
|
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 {
|
|
@@ -166,11 +167,72 @@ export function installHints(id, platform = process.platform) {
|
|
|
166
167
|
}
|
|
167
168
|
return ["sudo apt install chromium"];
|
|
168
169
|
}
|
|
170
|
+
if (id === "ffmpeg") {
|
|
171
|
+
if (platform === "win32") return ["winget install Gyan.FFmpeg"];
|
|
172
|
+
if (platform === "darwin") return ["brew install ffmpeg"];
|
|
173
|
+
return ["sudo apt install ffmpeg"];
|
|
174
|
+
}
|
|
175
|
+
if (id === "whisper-cli") {
|
|
176
|
+
if (platform === "win32") {
|
|
177
|
+
return ["whisper.cpp release: whisper-cli.exe on PATH", "https://github.com/ggml-org/whisper.cpp"];
|
|
178
|
+
}
|
|
179
|
+
if (platform === "darwin") return ["brew install whisper-cpp"];
|
|
180
|
+
return ["https://github.com/ggml-org/whisper.cpp"];
|
|
181
|
+
}
|
|
169
182
|
return [];
|
|
170
183
|
}
|
|
171
184
|
|
|
185
|
+
function isFile(abs) {
|
|
186
|
+
try {
|
|
187
|
+
return fs.statSync(abs).isFile();
|
|
188
|
+
} catch {
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function speakDoctorStatus({ env = process.env, home = os.homedir() } = {}) {
|
|
194
|
+
const names = [];
|
|
195
|
+
if (String(env.ELEVENLABS_API_KEY || "").trim()) names.push("elevenlabs");
|
|
196
|
+
if (String(env.OPENAI_API_KEY || "").trim()) names.push("openai");
|
|
197
|
+
const hasFile = isFile(path.join(home, ".grunt", "speak.json"));
|
|
198
|
+
if (!names.length && !hasFile) return { ok: false, extra: "" };
|
|
199
|
+
return { ok: true, extra: names.length ? names.join(",") : "config" };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const WORKSPACE_ACCOUNT_RE = /^[a-zA-Z0-9_-]{1,40}$/;
|
|
203
|
+
const WORKSPACE_KIND_ORDER = ["oauth", "tokens", "adc", "clasprc"];
|
|
204
|
+
|
|
205
|
+
export function workspaceDoctorStatus({ home = os.homedir() } = {}) {
|
|
206
|
+
const grunt = path.join(home, ".grunt");
|
|
207
|
+
const kinds = new Set();
|
|
208
|
+
const scanAccountDir = (dir) => {
|
|
209
|
+
if (isFile(path.join(dir, "google-oauth.json"))) kinds.add("oauth");
|
|
210
|
+
if (isFile(path.join(dir, "tokens.json"))) kinds.add("tokens");
|
|
211
|
+
};
|
|
212
|
+
if (isFile(path.join(grunt, "google-oauth.json"))) kinds.add("oauth");
|
|
213
|
+
if (isFile(path.join(grunt, "workspace-tokens.json"))) kinds.add("tokens");
|
|
214
|
+
scanAccountDir(path.join(grunt, "workspace", "default"));
|
|
215
|
+
const root = path.join(grunt, "workspace");
|
|
216
|
+
try {
|
|
217
|
+
for (const ent of fs.readdirSync(root, { withFileTypes: true })) {
|
|
218
|
+
if (!ent.isDirectory() || !WORKSPACE_ACCOUNT_RE.test(ent.name)) continue;
|
|
219
|
+
scanAccountDir(path.join(root, ent.name));
|
|
220
|
+
}
|
|
221
|
+
} catch {
|
|
222
|
+
/* none */
|
|
223
|
+
}
|
|
224
|
+
if (
|
|
225
|
+
isFile(path.join(home, ".config", "gcloud", "application_default_credentials.json"))
|
|
226
|
+
) {
|
|
227
|
+
kinds.add("adc");
|
|
228
|
+
}
|
|
229
|
+
if (isFile(path.join(home, ".clasprc.json"))) kinds.add("clasprc");
|
|
230
|
+
if (!kinds.size) return { ok: false, extra: "" };
|
|
231
|
+
return { ok: true, extra: WORKSPACE_KIND_ORDER.filter((k) => kinds.has(k)).join(",") };
|
|
232
|
+
}
|
|
233
|
+
|
|
172
234
|
function row(name, status, extra = "") {
|
|
173
|
-
const a = String(name).padEnd(
|
|
235
|
+
const a = String(name).padEnd(18);
|
|
174
236
|
const b = String(status).padEnd(18);
|
|
175
237
|
const c = extra ? String(extra).trim() : "";
|
|
176
238
|
return c ? `${a} ${b} ${c}` : `${a} ${b}`;
|
|
@@ -205,6 +267,8 @@ export function runDoctor({
|
|
|
205
267
|
platform = process.platform,
|
|
206
268
|
execPath = process.execPath,
|
|
207
269
|
nodeVersion = process.version,
|
|
270
|
+
env = process.env,
|
|
271
|
+
home = os.homedir(),
|
|
208
272
|
} = {}) {
|
|
209
273
|
const nmPath = withNmBin(cwd, pathEnv, platform);
|
|
210
274
|
const pathNode = whichBin("node", pathEnv, platform);
|
|
@@ -226,6 +290,10 @@ export function runDoctor({
|
|
|
226
290
|
const lightpanda = whichBin("lightpanda", pathEnv, platform);
|
|
227
291
|
const chromium = lookupChromium(pathEnv, platform);
|
|
228
292
|
const gh = whichBin("gh", pathEnv, platform);
|
|
293
|
+
const clasp = whichBin("clasp", pathEnv, platform);
|
|
294
|
+
const ffmpeg = whichBin("ffmpeg", pathEnv, platform);
|
|
295
|
+
const whisperCli =
|
|
296
|
+
whichBin("whisper-cli", pathEnv, platform) || whichBin("whisper-cpp", pathEnv, platform);
|
|
229
297
|
|
|
230
298
|
const found = {
|
|
231
299
|
node: nodeOk ? nodeBin : "",
|
|
@@ -252,6 +320,19 @@ export function runDoctor({
|
|
|
252
320
|
lines.push(lightpanda ? row("lightpanda", "ok", lightpanda) : row("lightpanda", "missing"));
|
|
253
321
|
lines.push(chromium ? row("chromium", "ok", chromium) : row("chromium", "missing"));
|
|
254
322
|
lines.push(gh ? row("gh", "ok", gh) : row("gh", "missing (optional)"));
|
|
323
|
+
lines.push(clasp ? row("clasp", "ok", clasp) : row("clasp", "missing (optional)"));
|
|
324
|
+
const workspace = workspaceDoctorStatus({ home });
|
|
325
|
+
lines.push(
|
|
326
|
+
workspace.ok
|
|
327
|
+
? row("google-workspace", "ok", workspace.extra)
|
|
328
|
+
: row("google-workspace", "missing (optional)"),
|
|
329
|
+
);
|
|
330
|
+
lines.push(ffmpeg ? row("ffmpeg", "ok", ffmpeg) : row("ffmpeg", "missing (optional)"));
|
|
331
|
+
lines.push(
|
|
332
|
+
whisperCli ? row("whisper-cli", "ok", whisperCli) : row("whisper-cli", "missing (optional)"),
|
|
333
|
+
);
|
|
334
|
+
const speak = speakDoctorStatus({ env, home });
|
|
335
|
+
lines.push(speak.ok ? row("speak", "ok", speak.extra) : row("speak", "missing (optional)"));
|
|
255
336
|
|
|
256
337
|
const missingRequired = REQUIRED.filter((k) => (k === "node" ? !nodeOk : !found[k]));
|
|
257
338
|
if (missingRequired.length) {
|
package/scripts/emit-gemini.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import path from "node:path";
|
|
|
5
5
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
6
6
|
import { guardedMarkdownDrift, writeMergedGuardedFile } from "../cli/init.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 =
|