@lovrozagar/grunt 0.5.2 → 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 -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 +39 -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 +56 -0
- package/CLAUDE.md +1 -49
- package/README.md +88 -80
- package/cli/grunt.mjs +18 -2
- package/cli/grunt.test.ts +33 -0
- package/cli/init.mjs +103 -10
- package/cli/init.test.ts +147 -35
- package/cli/prompt.mjs +8 -0
- package/cli/prompt.test.ts +14 -0
- package/package.json +7 -2
- 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/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/cli/init.test.ts
CHANGED
|
@@ -15,6 +15,12 @@ import {
|
|
|
15
15
|
guardedMarkdownDrift,
|
|
16
16
|
healGuardedRootFile,
|
|
17
17
|
init,
|
|
18
|
+
pruneRetired,
|
|
19
|
+
RETIRED_SKILLS,
|
|
20
|
+
RETIRED_AGENTS,
|
|
21
|
+
RETIRED_SCRIPTS,
|
|
22
|
+
RETIRED_PATHS,
|
|
23
|
+
RESERVED_SKILLS,
|
|
18
24
|
LAUNCH_SCRIPTS,
|
|
19
25
|
mergeClaudeSettings,
|
|
20
26
|
mergeGitignore,
|
|
@@ -30,7 +36,6 @@ import {
|
|
|
30
36
|
writeMergedGuardedFile,
|
|
31
37
|
} from "./init.mjs";
|
|
32
38
|
import { runGuardedRoots } from "../scripts/guarded-roots.mjs";
|
|
33
|
-
import { stripJsonc } from "../scripts/grunt-config.mjs";
|
|
34
39
|
|
|
35
40
|
const tmpDirs: string[] = [];
|
|
36
41
|
afterEach(() => {
|
|
@@ -46,12 +51,6 @@ function tmp(prefix: string) {
|
|
|
46
51
|
}
|
|
47
52
|
|
|
48
53
|
const COPY_DIRS = [".rulesync", ".grok", ".codex", ".claude", ".agents"];
|
|
49
|
-
const GRUNT_CONFIG_REL = path.join(".rulesync", "grunt.config.jsonc");
|
|
50
|
-
const GRUNT_CONFIG_DEFAULTS = {
|
|
51
|
-
version: 1,
|
|
52
|
-
leftoverGate: "ask",
|
|
53
|
-
spawnMode: "cascade",
|
|
54
|
-
};
|
|
55
54
|
const GUARDED_MD_FILES = ["AGENTS.md", "CLAUDE.md"];
|
|
56
55
|
const PRODUCT_FILES = [
|
|
57
56
|
"check-globals.mjs",
|
|
@@ -64,9 +63,9 @@ const PRODUCT_FILES = [
|
|
|
64
63
|
"hooks-union.mjs",
|
|
65
64
|
"pipeline.mjs",
|
|
66
65
|
"grunt-job.mjs",
|
|
67
|
-
"grunt-config.mjs",
|
|
68
66
|
"parse-need.mjs",
|
|
69
67
|
"persist-handoff.mjs",
|
|
68
|
+
"persist-implementation.mjs",
|
|
70
69
|
"persist-tmp.mjs",
|
|
71
70
|
"persist-plan.mjs",
|
|
72
71
|
"purge-global-mcps.mjs",
|
|
@@ -74,6 +73,10 @@ const PRODUCT_FILES = [
|
|
|
74
73
|
"scrub-text-lib.mjs",
|
|
75
74
|
"sync-global-settings.mjs",
|
|
76
75
|
"browser.mjs",
|
|
76
|
+
"speak.mjs",
|
|
77
|
+
"listen.mjs",
|
|
78
|
+
"google-workspace.mjs",
|
|
79
|
+
"setup.mjs",
|
|
77
80
|
"doctor.mjs",
|
|
78
81
|
"skill-conflicts.mjs",
|
|
79
82
|
];
|
|
@@ -118,12 +121,6 @@ const SRC_CLAUDE_SETTINGS = {
|
|
|
118
121
|
enabledMcpjsonServers: [],
|
|
119
122
|
};
|
|
120
123
|
|
|
121
|
-
function readGruntConfig(root: string) {
|
|
122
|
-
const p = path.join(root, GRUNT_CONFIG_REL);
|
|
123
|
-
expect(fs.existsSync(p)).toBe(true);
|
|
124
|
-
return JSON.parse(stripJsonc(fs.readFileSync(p, "utf8")));
|
|
125
|
-
}
|
|
126
|
-
|
|
127
124
|
function stubPkgRoot(pkg: Record<string, unknown> = {
|
|
128
125
|
name: "fixture-pkg",
|
|
129
126
|
scripts: {
|
|
@@ -143,10 +140,6 @@ function stubPkgRoot(pkg: Record<string, unknown> = {
|
|
|
143
140
|
fs.mkdirSync(path.join(root, d));
|
|
144
141
|
fs.writeFileSync(path.join(root, d, "marker"), d);
|
|
145
142
|
}
|
|
146
|
-
fs.writeFileSync(
|
|
147
|
-
path.join(root, GRUNT_CONFIG_REL),
|
|
148
|
-
`${JSON.stringify(GRUNT_CONFIG_DEFAULTS, null, 2)}\n`,
|
|
149
|
-
);
|
|
150
143
|
fs.writeFileSync(
|
|
151
144
|
path.join(root, ".claude", "settings.json"),
|
|
152
145
|
`${JSON.stringify(SRC_CLAUDE_SETTINGS, null, 2)}\n`,
|
|
@@ -179,18 +172,22 @@ describe("samePath", () => {
|
|
|
179
172
|
});
|
|
180
173
|
});
|
|
181
174
|
|
|
175
|
+
const GI_TMP = ".tmp/\n";
|
|
176
|
+
|
|
182
177
|
describe("mergeGitignore", () => {
|
|
183
178
|
it("creates .tmp/ when missing", () => {
|
|
184
179
|
const dest = tmp("gi-miss-");
|
|
185
180
|
mergeGitignore(dest);
|
|
186
|
-
expect(fs.readFileSync(path.join(dest, ".gitignore"), "utf8")).toBe(
|
|
181
|
+
expect(fs.readFileSync(path.join(dest, ".gitignore"), "utf8")).toBe(GI_TMP);
|
|
187
182
|
});
|
|
188
183
|
|
|
189
|
-
it("noop when .tmp/ present", () => {
|
|
184
|
+
it("noop when .tmp/ already present", () => {
|
|
190
185
|
const dest = tmp("gi-slash-");
|
|
191
186
|
fs.writeFileSync(path.join(dest, ".gitignore"), "foo\n.tmp/\nbar\n");
|
|
192
187
|
mergeGitignore(dest);
|
|
193
|
-
expect(fs.readFileSync(path.join(dest, ".gitignore"), "utf8")).toBe(
|
|
188
|
+
expect(fs.readFileSync(path.join(dest, ".gitignore"), "utf8")).toBe(
|
|
189
|
+
"foo\n.tmp/\nbar\n",
|
|
190
|
+
);
|
|
194
191
|
});
|
|
195
192
|
|
|
196
193
|
it("noop when .tmp present", () => {
|
|
@@ -200,12 +197,21 @@ describe("mergeGitignore", () => {
|
|
|
200
197
|
expect(fs.readFileSync(path.join(dest, ".gitignore"), "utf8")).toBe(".tmp\n");
|
|
201
198
|
});
|
|
202
199
|
|
|
200
|
+
it("noop when .tmp/ present in mixed file", () => {
|
|
201
|
+
const dest = tmp("gi-both-");
|
|
202
|
+
fs.writeFileSync(path.join(dest, ".gitignore"), `foo\n${GI_TMP}bar\n`);
|
|
203
|
+
mergeGitignore(dest);
|
|
204
|
+
expect(fs.readFileSync(path.join(dest, ".gitignore"), "utf8")).toBe(
|
|
205
|
+
`foo\n${GI_TMP}bar\n`,
|
|
206
|
+
);
|
|
207
|
+
});
|
|
208
|
+
|
|
203
209
|
it("appends after trailing newline", () => {
|
|
204
210
|
const dest = tmp("gi-nl-");
|
|
205
211
|
fs.writeFileSync(path.join(dest, ".gitignore"), "node_modules/\n");
|
|
206
212
|
mergeGitignore(dest);
|
|
207
213
|
expect(fs.readFileSync(path.join(dest, ".gitignore"), "utf8")).toBe(
|
|
208
|
-
|
|
214
|
+
`node_modules/\n${GI_TMP}`,
|
|
209
215
|
);
|
|
210
216
|
});
|
|
211
217
|
|
|
@@ -214,7 +220,7 @@ describe("mergeGitignore", () => {
|
|
|
214
220
|
fs.writeFileSync(path.join(dest, ".gitignore"), "node_modules/");
|
|
215
221
|
mergeGitignore(dest);
|
|
216
222
|
expect(fs.readFileSync(path.join(dest, ".gitignore"), "utf8")).toBe(
|
|
217
|
-
|
|
223
|
+
`node_modules/\n${GI_TMP}`,
|
|
218
224
|
);
|
|
219
225
|
});
|
|
220
226
|
|
|
@@ -222,7 +228,7 @@ describe("mergeGitignore", () => {
|
|
|
222
228
|
const dest = tmp("gi-empty-");
|
|
223
229
|
fs.writeFileSync(path.join(dest, ".gitignore"), "");
|
|
224
230
|
mergeGitignore(dest);
|
|
225
|
-
expect(fs.readFileSync(path.join(dest, ".gitignore"), "utf8")).toBe(
|
|
231
|
+
expect(fs.readFileSync(path.join(dest, ".gitignore"), "utf8")).toBe(GI_TMP);
|
|
226
232
|
});
|
|
227
233
|
});
|
|
228
234
|
|
|
@@ -1550,9 +1556,9 @@ describe("init", () => {
|
|
|
1550
1556
|
if (d === ".claude") continue;
|
|
1551
1557
|
expect(fs.readFileSync(path.join(dest, d, "marker"), "utf8")).toBe(d);
|
|
1552
1558
|
}
|
|
1553
|
-
expect(
|
|
1554
|
-
expect(PRODUCT_FILES).toContain("grunt-config.mjs");
|
|
1555
|
-
expect(fs.
|
|
1559
|
+
expect(fs.existsSync(path.join(dest, ".rulesync", "grunt.config.jsonc"))).toBe(false);
|
|
1560
|
+
expect(PRODUCT_FILES).not.toContain("grunt-config.mjs");
|
|
1561
|
+
expect(fs.existsSync(path.join(dest, "scripts", "grunt-config.mjs"))).toBe(false);
|
|
1556
1562
|
expect(fs.readFileSync(path.join(dest, ".claude", "marker"), "utf8")).toBe(".claude");
|
|
1557
1563
|
const settings = JSON.parse(
|
|
1558
1564
|
fs.readFileSync(path.join(dest, ".claude", "settings.json"), "utf8"),
|
|
@@ -1626,6 +1632,109 @@ describe("init", () => {
|
|
|
1626
1632
|
expect(fs.readFileSync(path.join(pkgRoot, ".gitignore"), "utf8")).toBe(".tmp/\n");
|
|
1627
1633
|
});
|
|
1628
1634
|
|
|
1635
|
+
it("prunes retired 0.5 skills and agents on dest", () => {
|
|
1636
|
+
const pkgRoot = stubPkgRoot();
|
|
1637
|
+
const dest = tmp("grunt-prune-");
|
|
1638
|
+
fs.mkdirSync(path.join(dest, ".rulesync", "skills", "solo"), { recursive: true });
|
|
1639
|
+
fs.writeFileSync(path.join(dest, ".rulesync", "skills", "solo", "SKILL.md"), "old");
|
|
1640
|
+
fs.mkdirSync(path.join(dest, ".rulesync", "skills", "parent"), { recursive: true });
|
|
1641
|
+
fs.mkdirSync(path.join(dest, ".rulesync", "skills", "cascade"), { recursive: true });
|
|
1642
|
+
fs.mkdirSync(path.join(dest, ".claude", "agents"), { recursive: true });
|
|
1643
|
+
fs.writeFileSync(path.join(dest, ".claude", "agents", "implementer.md"), "old");
|
|
1644
|
+
fs.writeFileSync(path.join(dest, ".claude", "agents", "thinker.md"), "old");
|
|
1645
|
+
expect(RETIRED_SKILLS).toEqual(["parent", "solo", "cascade"]);
|
|
1646
|
+
expect(RETIRED_AGENTS).toEqual(["implementer", "thinker"]);
|
|
1647
|
+
expect(RETIRED_SCRIPTS).toEqual(["telemetry.mjs", "grunt-config.mjs"]);
|
|
1648
|
+
expect(RETIRED_PATHS).toEqual([
|
|
1649
|
+
".grok/parent.md",
|
|
1650
|
+
".grok/skills/shared",
|
|
1651
|
+
".rulesync/grunt.config.jsonc",
|
|
1652
|
+
".rulesync/grunt.config.local.jsonc",
|
|
1653
|
+
".rulesync/grunt.config.local.jsonc.example",
|
|
1654
|
+
]);
|
|
1655
|
+
expect(RESERVED_SKILLS).toEqual([
|
|
1656
|
+
"ask",
|
|
1657
|
+
"auto",
|
|
1658
|
+
"browser",
|
|
1659
|
+
"clasp",
|
|
1660
|
+
"commit",
|
|
1661
|
+
"commit-and-push",
|
|
1662
|
+
"commit-push",
|
|
1663
|
+
"commit-push-deploy",
|
|
1664
|
+
"commit-push-release",
|
|
1665
|
+
"explain",
|
|
1666
|
+
"google-workspace",
|
|
1667
|
+
"handoff",
|
|
1668
|
+
"implement-plan",
|
|
1669
|
+
"listen",
|
|
1670
|
+
"pickup",
|
|
1671
|
+
"speak",
|
|
1672
|
+
"tmp",
|
|
1673
|
+
"write-plan",
|
|
1674
|
+
]);
|
|
1675
|
+
expect(RESERVED_SKILLS).not.toEqual(expect.arrayContaining(RETIRED_SKILLS));
|
|
1676
|
+
init(dest, { pkgRoot, execFileSync: vi.fn() });
|
|
1677
|
+
expect(fs.existsSync(path.join(dest, ".rulesync", "skills", "solo"))).toBe(false);
|
|
1678
|
+
expect(fs.existsSync(path.join(dest, ".rulesync", "skills", "parent"))).toBe(false);
|
|
1679
|
+
expect(fs.existsSync(path.join(dest, ".rulesync", "skills", "cascade"))).toBe(false);
|
|
1680
|
+
expect(fs.existsSync(path.join(dest, ".claude", "agents", "implementer.md"))).toBe(
|
|
1681
|
+
false,
|
|
1682
|
+
);
|
|
1683
|
+
expect(fs.existsSync(path.join(dest, ".claude", "agents", "thinker.md"))).toBe(false);
|
|
1684
|
+
pruneRetired(dest, { pkgRoot });
|
|
1685
|
+
});
|
|
1686
|
+
|
|
1687
|
+
it("prunes retired scripts/paths, reserved-not-packaged skills, and grok roles; keeps consumer extras", () => {
|
|
1688
|
+
const pkgRoot = stubPkgRoot();
|
|
1689
|
+
fs.mkdirSync(path.join(pkgRoot, ".rulesync", "skills", "tmp"), { recursive: true });
|
|
1690
|
+
fs.writeFileSync(path.join(pkgRoot, ".rulesync", "skills", "tmp", "SKILL.md"), "pkg tmp");
|
|
1691
|
+
const dest = tmp("grunt-prune-extra-");
|
|
1692
|
+
fs.mkdirSync(path.join(dest, ".rulesync", "skills", "tmp"), { recursive: true });
|
|
1693
|
+
fs.writeFileSync(path.join(dest, ".rulesync", "skills", "tmp", "SKILL.md"), "old tmp");
|
|
1694
|
+
fs.mkdirSync(path.join(dest, ".rulesync", "skills", "browser"), { recursive: true });
|
|
1695
|
+
fs.writeFileSync(path.join(dest, ".rulesync", "skills", "browser", "SKILL.md"), "stale reserved");
|
|
1696
|
+
fs.mkdirSync(path.join(dest, ".rulesync", "skills", "acme"), { recursive: true });
|
|
1697
|
+
fs.writeFileSync(path.join(dest, ".rulesync", "skills", "acme", "SKILL.md"), "consumer");
|
|
1698
|
+
fs.mkdirSync(path.join(dest, ".grok", "roles"), { recursive: true });
|
|
1699
|
+
fs.writeFileSync(path.join(dest, ".grok", "roles", "implementer.toml"), "old");
|
|
1700
|
+
fs.writeFileSync(path.join(dest, ".grok", "roles", "thinker.toml"), "old");
|
|
1701
|
+
fs.writeFileSync(path.join(dest, ".grok", "parent.md"), "pointer");
|
|
1702
|
+
fs.mkdirSync(path.join(dest, ".grok", "skills", "shared"), { recursive: true });
|
|
1703
|
+
fs.mkdirSync(path.join(dest, "scripts"), { recursive: true });
|
|
1704
|
+
fs.writeFileSync(path.join(dest, "scripts", "telemetry.mjs"), "old telemetry");
|
|
1705
|
+
fs.writeFileSync(path.join(dest, "scripts", "grunt-config.mjs"), "old config");
|
|
1706
|
+
fs.writeFileSync(path.join(dest, "scripts", "mine.mjs"), "keep");
|
|
1707
|
+
fs.writeFileSync(path.join(dest, ".rulesync", "grunt.config.jsonc"), '{"version":1}\n');
|
|
1708
|
+
fs.writeFileSync(path.join(dest, ".rulesync", "grunt.config.local.jsonc"), '{"sessionGate":"ask"}\n');
|
|
1709
|
+
fs.writeFileSync(
|
|
1710
|
+
path.join(dest, ".rulesync", "grunt.config.local.jsonc.example"),
|
|
1711
|
+
'{"sessionGate":"ask"}\n',
|
|
1712
|
+
);
|
|
1713
|
+
fs.mkdirSync(path.join(dest, ".gemini", "agents", "implementer"), { recursive: true });
|
|
1714
|
+
fs.writeFileSync(path.join(dest, ".gemini", "agents", "implementer", "agent.md"), "old");
|
|
1715
|
+
init(dest, { pkgRoot, execFileSync: vi.fn() });
|
|
1716
|
+
expect(fs.readFileSync(path.join(dest, ".rulesync", "skills", "tmp", "SKILL.md"), "utf8")).toBe(
|
|
1717
|
+
"pkg tmp",
|
|
1718
|
+
);
|
|
1719
|
+
expect(fs.existsSync(path.join(dest, ".rulesync", "skills", "browser"))).toBe(false);
|
|
1720
|
+
expect(fs.readFileSync(path.join(dest, ".rulesync", "skills", "acme", "SKILL.md"), "utf8")).toBe(
|
|
1721
|
+
"consumer",
|
|
1722
|
+
);
|
|
1723
|
+
expect(fs.existsSync(path.join(dest, ".grok", "roles", "implementer.toml"))).toBe(false);
|
|
1724
|
+
expect(fs.existsSync(path.join(dest, ".grok", "roles", "thinker.toml"))).toBe(false);
|
|
1725
|
+
expect(fs.existsSync(path.join(dest, ".grok", "parent.md"))).toBe(false);
|
|
1726
|
+
expect(fs.existsSync(path.join(dest, ".grok", "skills", "shared"))).toBe(false);
|
|
1727
|
+
expect(fs.existsSync(path.join(dest, "scripts", "telemetry.mjs"))).toBe(false);
|
|
1728
|
+
expect(fs.existsSync(path.join(dest, "scripts", "grunt-config.mjs"))).toBe(false);
|
|
1729
|
+
expect(fs.existsSync(path.join(dest, ".rulesync", "grunt.config.jsonc"))).toBe(false);
|
|
1730
|
+
expect(fs.existsSync(path.join(dest, ".rulesync", "grunt.config.local.jsonc"))).toBe(false);
|
|
1731
|
+
expect(
|
|
1732
|
+
fs.existsSync(path.join(dest, ".rulesync", "grunt.config.local.jsonc.example")),
|
|
1733
|
+
).toBe(false);
|
|
1734
|
+
expect(fs.readFileSync(path.join(dest, "scripts", "mine.mjs"), "utf8")).toBe("keep");
|
|
1735
|
+
expect(fs.existsSync(path.join(dest, ".gemini", "agents", "implementer"))).toBe(false);
|
|
1736
|
+
});
|
|
1737
|
+
|
|
1629
1738
|
it("omitted pkgRoot uses built-in PKG_ROOT; exec mocked", () => {
|
|
1630
1739
|
const dest = tmp("grunt-realroot-");
|
|
1631
1740
|
const exec = vi.fn();
|
|
@@ -1638,9 +1747,12 @@ describe("init", () => {
|
|
|
1638
1747
|
expect(claude.split("<!-- grunt:begin -->")).toHaveLength(2);
|
|
1639
1748
|
expect(claude.split("<!-- grunt:end -->")).toHaveLength(2);
|
|
1640
1749
|
expect(fs.existsSync(path.join(dest, "scripts", "scrub-text"))).toBe(true);
|
|
1641
|
-
expect(fs.existsSync(path.join(dest, "scripts", "grunt-config.mjs"))).toBe(
|
|
1750
|
+
expect(fs.existsSync(path.join(dest, "scripts", "grunt-config.mjs"))).toBe(false);
|
|
1751
|
+
expect(fs.existsSync(path.join(dest, "scripts", "speak.mjs"))).toBe(true);
|
|
1752
|
+
expect(fs.existsSync(path.join(dest, "scripts", "listen.mjs"))).toBe(true);
|
|
1753
|
+
expect(fs.existsSync(path.join(dest, "scripts", "google-workspace.mjs"))).toBe(true);
|
|
1642
1754
|
expect(fs.existsSync(path.join(dest, ".claude", "settings.json"))).toBe(true);
|
|
1643
|
-
expect(
|
|
1755
|
+
expect(fs.existsSync(path.join(dest, ".rulesync", "grunt.config.jsonc"))).toBe(false);
|
|
1644
1756
|
expect(exec.mock.calls.map((c) => c[1])).toEqual([
|
|
1645
1757
|
["install"],
|
|
1646
1758
|
["run", "grunt:rulesync:generate"],
|
|
@@ -1829,25 +1941,25 @@ describe("init", () => {
|
|
|
1829
1941
|
|
|
1830
1942
|
it("warns when dest skill differs from packaged then force-overwrites; keeps extras", () => {
|
|
1831
1943
|
const pkgRoot = stubPkgRoot();
|
|
1832
|
-
const pkgSkill = path.join(pkgRoot, ".rulesync", "skills", "
|
|
1944
|
+
const pkgSkill = path.join(pkgRoot, ".rulesync", "skills", "explain");
|
|
1833
1945
|
fs.mkdirSync(pkgSkill, { recursive: true });
|
|
1834
|
-
fs.writeFileSync(path.join(pkgSkill, "SKILL.md"), "grunt-
|
|
1946
|
+
fs.writeFileSync(path.join(pkgSkill, "SKILL.md"), "grunt-explain\n");
|
|
1835
1947
|
const dest = tmp("grunt-sk-conflict-");
|
|
1836
|
-
const destSkill = path.join(dest, ".rulesync", "skills", "
|
|
1948
|
+
const destSkill = path.join(dest, ".rulesync", "skills", "explain");
|
|
1837
1949
|
const destExtra = path.join(dest, ".rulesync", "skills", "my-extra");
|
|
1838
1950
|
fs.mkdirSync(destSkill, { recursive: true });
|
|
1839
1951
|
fs.mkdirSync(destExtra, { recursive: true });
|
|
1840
|
-
fs.writeFileSync(path.join(destSkill, "SKILL.md"), "custom-
|
|
1952
|
+
fs.writeFileSync(path.join(destSkill, "SKILL.md"), "custom-explain\n");
|
|
1841
1953
|
fs.writeFileSync(path.join(destExtra, "SKILL.md"), "keep-me\n");
|
|
1842
1954
|
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
1843
1955
|
init(dest, { pkgRoot, execFileSync: vi.fn(), skipGlobals: true });
|
|
1844
1956
|
expect(
|
|
1845
1957
|
warnSpy.mock.calls.some(
|
|
1846
|
-
(c) => String(c[0]).includes("
|
|
1958
|
+
(c) => String(c[0]).includes("explain") && String(c[0]).includes("re-init overwrites"),
|
|
1847
1959
|
),
|
|
1848
1960
|
).toBe(true);
|
|
1849
1961
|
warnSpy.mockRestore();
|
|
1850
|
-
expect(fs.readFileSync(path.join(destSkill, "SKILL.md"), "utf8")).toBe("grunt-
|
|
1962
|
+
expect(fs.readFileSync(path.join(destSkill, "SKILL.md"), "utf8")).toBe("grunt-explain\n");
|
|
1851
1963
|
expect(fs.readFileSync(path.join(destExtra, "SKILL.md"), "utf8")).toBe("keep-me\n");
|
|
1852
1964
|
});
|
|
1853
1965
|
});
|
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",
|
|
@@ -34,9 +34,9 @@
|
|
|
34
34
|
"scripts/hooks-union.mjs",
|
|
35
35
|
"scripts/pipeline.mjs",
|
|
36
36
|
"scripts/grunt-job.mjs",
|
|
37
|
-
"scripts/grunt-config.mjs",
|
|
38
37
|
"scripts/parse-need.mjs",
|
|
39
38
|
"scripts/persist-handoff.mjs",
|
|
39
|
+
"scripts/persist-implementation.mjs",
|
|
40
40
|
"scripts/persist-tmp.mjs",
|
|
41
41
|
"scripts/persist-plan.mjs",
|
|
42
42
|
"scripts/purge-global-mcps.mjs",
|
|
@@ -44,6 +44,10 @@
|
|
|
44
44
|
"scripts/scrub-text-lib.mjs",
|
|
45
45
|
"scripts/sync-global-settings.mjs",
|
|
46
46
|
"scripts/browser.mjs",
|
|
47
|
+
"scripts/speak.mjs",
|
|
48
|
+
"scripts/listen.mjs",
|
|
49
|
+
"scripts/google-workspace.mjs",
|
|
50
|
+
"scripts/setup.mjs",
|
|
47
51
|
"scripts/doctor.mjs",
|
|
48
52
|
"scripts/skill-conflicts.mjs",
|
|
49
53
|
"scripts/scrub-text",
|
|
@@ -73,6 +77,7 @@
|
|
|
73
77
|
"hooks:union": "node scripts/hooks-union.mjs",
|
|
74
78
|
"hooks:check": "node scripts/hooks-union.mjs --check",
|
|
75
79
|
"doctor": "node ./scripts/doctor.mjs",
|
|
80
|
+
"setup": "node ./scripts/setup.mjs",
|
|
76
81
|
"rulesync:doctor": "rulesync doctor",
|
|
77
82
|
"sync:globals": "node scripts/sync-global-settings.mjs",
|
|
78
83
|
"sync:globals:check": "node scripts/check-globals.mjs",
|
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,
|