@sous-io/sous 0.1.1 → 0.2.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/README.md +115 -35
- package/bin/run.js +10 -1
- package/docs/markdown/README.md +27 -0
- package/docs/markdown/_sidebar.md +18 -0
- package/docs/markdown/commands.md +308 -0
- package/docs/markdown/config-discovery.md +74 -0
- package/docs/markdown/config-inspection.md +69 -0
- package/docs/markdown/config-layers.md +92 -0
- package/docs/markdown/config-variables.md +79 -0
- package/docs/markdown/configuration.md +71 -0
- package/docs/markdown/design-principles.md +59 -0
- package/docs/markdown/repositories-authoring.md +408 -0
- package/docs/markdown/repositories-consuming.md +580 -0
- package/docs/markdown/repositories-file-formats.md +1084 -0
- package/docs/markdown/repositories-variables.md +387 -0
- package/docs/markdown/repositories.md +303 -0
- package/docs/markdown/skill-categories.md +58 -0
- package/package.json +72 -8
- package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/about-agent-skills/SKILL.tpl.md +20 -20
- package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/about-agent-skills/examples/about-something.md +2 -2
- package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/about-agent-skills/examples/do-something.md +1 -1
- package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/about-agent-skills/references/advanced-patterns.md +6 -6
- package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/about-agent-skills/references/commands.md +5 -5
- package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/about-agent-skills/references/frontmatter.md +3 -3
- package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/about-liquid-templates/SKILL.tpl.md +40 -25
- package/recipes/core/sous-skills/skills/about-sous/SKILL.tpl.md +70 -0
- package/recipes/core/sous-skills/skills/about-sous-configuration/SKILL.tpl.md +75 -0
- package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/create-skill/SKILL.tpl.md +8 -9
- package/recipes/core/sous-skills/sous.recipe.yaml +45 -0
- package/sous.config.schema.json +337 -0
- package/src/base-command.ts +220 -67
- package/src/commands/build.ts +150 -73
- package/src/commands/clear.ts +23 -15
- package/src/commands/compile.ts +74 -16
- package/src/commands/config/get.ts +110 -0
- package/src/commands/config/show.ts +32 -0
- package/src/commands/config/validate.ts +53 -0
- package/src/commands/help.ts +46 -0
- package/src/commands/launch.ts +36 -14
- package/src/commands/lock/rebuild.ts +241 -0
- package/src/commands/lock/show.ts +115 -0
- package/src/commands/namespace/list.ts +117 -0
- package/src/commands/namespace/show.ts +110 -0
- package/src/commands/prune.ts +3 -11
- package/src/commands/recipe/list.ts +95 -0
- package/src/commands/recipe/show.ts +301 -0
- package/src/commands/repo/add.ts +145 -0
- package/src/commands/repo/gc.ts +172 -0
- package/src/commands/repo/init.ts +136 -0
- package/src/commands/repo/link.ts +500 -0
- package/src/commands/repo/list.ts +179 -0
- package/src/commands/repo/release.ts +619 -0
- package/src/commands/repo/remove.ts +193 -0
- package/src/commands/repo/search.ts +189 -0
- package/src/commands/repo/submit.ts +133 -0
- package/src/commands/repo/unlink.ts +147 -0
- package/src/commands/subscription/add.ts +285 -0
- package/src/commands/subscription/list.ts +129 -0
- package/src/commands/subscription/remove.ts +181 -0
- package/src/commands/vars/ask.ts +374 -0
- package/src/commands/vars/index.ts +79 -0
- package/src/commands/vars/list.ts +67 -0
- package/src/commands/vars/show.ts +77 -0
- package/src/config-command.ts +30 -0
- package/src/lib/build-service.ts +206 -54
- package/src/lib/config-discovery.ts +220 -27
- package/src/lib/config-inspect.ts +145 -0
- package/src/lib/config-kernel.mjs +377 -0
- package/src/lib/config-schema.ts +361 -0
- package/src/lib/env-file.ts +328 -0
- package/src/lib/env-local.ts +18 -1
- package/src/lib/errors.ts +32 -0
- package/src/lib/include-resolver.ts +108 -15
- package/src/lib/interactive.ts +165 -0
- package/src/lib/markdown-compiler.ts +118 -37
- package/src/lib/package-info.ts +25 -0
- package/src/lib/pid-service.ts +32 -21
- package/src/lib/refs/find.ts +589 -0
- package/src/lib/refs/index.ts +12 -0
- package/src/lib/refs/pick.ts +147 -0
- package/src/lib/refs/scopes.ts +61 -0
- package/src/lib/repos/catalog-display.ts +116 -0
- package/src/lib/repos/catalog-inputs.ts +160 -0
- package/src/lib/repos/catalog.ts +722 -0
- package/src/lib/repos/core-recipe.ts +105 -0
- package/src/lib/repos/defaults.ts +175 -0
- package/src/lib/repos/formats/common.ts +389 -0
- package/src/lib/repos/formats/index-file.ts +215 -0
- package/src/lib/repos/formats/links-map.ts +96 -0
- package/src/lib/repos/formats/lockfile.ts +167 -0
- package/src/lib/repos/formats/patterns.ts +57 -0
- package/src/lib/repos/formats/recipe-manifest.ts +395 -0
- package/src/lib/repos/formats/repo-manifest.ts +88 -0
- package/src/lib/repos/formats/store-entry.ts +84 -0
- package/src/lib/repos/freshness.ts +208 -0
- package/src/lib/repos/git-clone.ts +312 -0
- package/src/lib/repos/identity.ts +89 -0
- package/src/lib/repos/index.ts +58 -0
- package/src/lib/repos/links.ts +353 -0
- package/src/lib/repos/load-manifest.ts +236 -0
- package/src/lib/repos/lock-service.ts +453 -0
- package/src/lib/repos/locked-namespace-resolver.ts +90 -0
- package/src/lib/repos/locked-recipes.ts +254 -0
- package/src/lib/repos/managed-layer.ts +422 -0
- package/src/lib/repos/namespace-resolver.ts +370 -0
- package/src/lib/repos/providers/base.ts +206 -0
- package/src/lib/repos/providers/git.ts +233 -0
- package/src/lib/repos/providers/github.ts +294 -0
- package/src/lib/repos/providers/gitlab.ts +263 -0
- package/src/lib/repos/providers/http.ts +102 -0
- package/src/lib/repos/providers/index-cache.ts +382 -0
- package/src/lib/repos/providers/index.ts +106 -0
- package/src/lib/repos/providers/local.ts +391 -0
- package/src/lib/repos/providers/provider.ts +401 -0
- package/src/lib/repos/recipe-config-layers.ts +287 -0
- package/src/lib/repos/recipe-targets.ts +223 -0
- package/src/lib/repos/ref-search.ts +46 -0
- package/src/lib/repos/ref.ts +513 -0
- package/src/lib/repos/reference-report.ts +122 -0
- package/src/lib/repos/release/bump.ts +161 -0
- package/src/lib/repos/release/git-state.ts +305 -0
- package/src/lib/repos/release/index-builder.ts +635 -0
- package/src/lib/repos/release/index.ts +16 -0
- package/src/lib/repos/release/plan.ts +512 -0
- package/src/lib/repos/release/submit-service.ts +496 -0
- package/src/lib/repos/release/tags.ts +243 -0
- package/src/lib/repos/release/validate.ts +463 -0
- package/src/lib/repos/resolver.ts +789 -0
- package/src/lib/repos/scaffold/index.ts +238 -0
- package/src/lib/repos/scaffold/templates.ts +413 -0
- package/src/lib/repos/seed.ts +414 -0
- package/src/lib/repos/store/contract.ts +64 -0
- package/src/lib/repos/store/hash.ts +114 -0
- package/src/lib/repos/store/recipe-store.ts +599 -0
- package/src/lib/repos/store/settings.ts +58 -0
- package/src/lib/repos/subscription-service.ts +2678 -0
- package/src/lib/repos/trust.ts +447 -0
- package/src/lib/settings.ts +546 -189
- package/src/lib/sous-home.ts +104 -0
- package/src/lib/state.ts +52 -20
- package/src/lib/vars/ask.ts +1152 -0
- package/src/lib/vars/definition-source.ts +252 -0
- package/src/lib/vars/display.ts +233 -0
- package/src/lib/vars/index.ts +18 -0
- package/src/lib/vars/ladder.ts +282 -0
- package/src/lib/vars/mappings.ts +265 -0
- package/src/lib/vars/names.ts +94 -0
- package/src/lib/vars/preanswers.ts +395 -0
- package/src/lib/vars/question-plan.ts +218 -0
- package/src/lib/vars/report.ts +228 -0
- package/src/lib/vars/safe-regex.ts +235 -0
- package/src/lib/vars/validate.ts +312 -0
- package/src/lib/watch-loop.ts +148 -0
- package/src/templating/init-liquid-engine.ts +58 -16
- package/src/utils/choice-prompt.ts +143 -0
- package/src/utils/command-errors.ts +186 -0
- package/src/utils/command-help.ts +45 -0
- package/src/utils/confirm-prompt.ts +110 -0
- package/src/utils/flags.ts +153 -0
- package/src/utils/formatting.ts +540 -55
- package/src/utils/prompts.ts +35 -1
- package/src/utils/sous-directory.ts +245 -0
- package/src/utils/table.ts +603 -0
- package/src/utils/value-prompt.ts +119 -0
- package/shared-prompts/_partials/resume-task.md +0 -51
- package/shared-prompts/_partials/sub-agent-delegation.md +0 -32
- package/shared-prompts/_partials/update-task-file.md +0 -52
- package/shared-prompts/memories/automated-browser-tasks/INDEX.tpl.md +0 -52
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/SKILL.tpl.md +0 -102
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/examples/auth-failure-handling.mjs +0 -81
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/examples/chained-workflow.mjs +0 -126
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/examples/simple-fetch.mjs +0 -92
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/references/architecture.md +0 -61
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/references/auth-and-sessions.md +0 -65
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/references/ctx-api.md +0 -96
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/references/installation.md +0 -104
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/references/script-conventions.md +0 -243
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/chrome-state.mjs +0 -148
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/debug.mjs +0 -383
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/debug.spec.mjs +0 -267
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/eslint.config.mjs +0 -56
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/harness.mjs +0 -169
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/keyring.mjs +0 -59
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/logger.mjs +0 -25
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/params.mjs +0 -140
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/run.mjs +0 -140
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/settings.tpl.mjs +0 -1
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/utils.mjs +0 -185
- package/shared-prompts/skills/automated-browser-tasks/create-automated-browser-task/SKILL.tpl.md +0 -52
- package/shared-prompts/skills/automated-browser-tasks/running-automated-browser-tasks/SKILL.tpl.md +0 -59
- package/shared-prompts/skills/automated-browser-tasks/update-automated-browser-task/SKILL.tpl.md +0 -47
- package/shared-prompts/skills/control-flow/approve/SKILL.tpl.md +0 -26
- package/shared-prompts/skills/control-flow/opine/SKILL.tpl.md +0 -58
- package/shared-prompts/skills/control-flow/repeat/SKILL.tpl.md +0 -27
- package/shared-prompts/skills/control-flow/research/SKILL.tpl.md +0 -34
- package/shared-prompts/skills/sous-skills/about-sous/SKILL.tpl.md +0 -51
- package/shared-prompts/skills/task-files/about-task-files/SKILL.tpl.md +0 -122
- package/shared-prompts/skills/task-files/continue-task-in-new-branch/SKILL.tpl.md +0 -80
- package/shared-prompts/skills/task-files/go/SKILL.tpl.md +0 -14
- package/shared-prompts/skills/task-files/resume-task/SKILL.tpl.md +0 -13
- package/shared-prompts/skills/task-files/start-task/SKILL.tpl.md +0 -93
- package/shared-prompts/skills/task-files/update/SKILL.tpl.md +0 -14
- package/shared-prompts/skills/task-files/update-task-file/SKILL.tpl.md +0 -13
- /package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/about-agent-skills/references/substitutions.md +0 -0
- /package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/about-liquid-templates/references/liquid-filters.md +0 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The value question sous asks for a recipe variable.
|
|
3
|
+
*
|
|
4
|
+
* It behaves like the stock `input` prompt (typing, backspace, Enter alone
|
|
5
|
+
* accepting the default, a validation message that re-asks) with one addition:
|
|
6
|
+
* Tab resolves the question with `{ kind: "advanced" }` instead of inserting a
|
|
7
|
+
* tab or inlining the default, so the caller can show the advanced view and
|
|
8
|
+
* then ask the same question again. A secret passes `mask`, which hides what is
|
|
9
|
+
* typed. Built on the public `@inquirer/core` hooks only.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
createPrompt,
|
|
14
|
+
isEnterKey,
|
|
15
|
+
isTabKey,
|
|
16
|
+
useKeypress,
|
|
17
|
+
usePrefix,
|
|
18
|
+
useState,
|
|
19
|
+
type Status,
|
|
20
|
+
} from "@inquirer/core";
|
|
21
|
+
import { promptBottom } from "./formatting.js";
|
|
22
|
+
|
|
23
|
+
/** What the value question needs in order to ask itself. */
|
|
24
|
+
export interface ValuePromptConfig {
|
|
25
|
+
/** The one-line question. */
|
|
26
|
+
message: string;
|
|
27
|
+
/** The value Enter alone accepts. */
|
|
28
|
+
default?: string;
|
|
29
|
+
/** A line under the question: the key legend, in the stock prompts' style. */
|
|
30
|
+
hint?: string;
|
|
31
|
+
/** Whether to hide what is typed, for a secret. */
|
|
32
|
+
mask?: boolean;
|
|
33
|
+
/** Checks an answer, returning true or the reason it was refused. */
|
|
34
|
+
validate?: (value: string) => boolean | string | Promise<boolean | string>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** How the question ended: with an answer, or with a request for the advanced view. */
|
|
38
|
+
export type ValuePromptResult = { kind: "value"; value: string } | { kind: "advanced" };
|
|
39
|
+
|
|
40
|
+
/** Everything the renderer draws, so it can be tested without a terminal. */
|
|
41
|
+
export interface ValuePromptView extends Omit<ValuePromptConfig, "validate"> {
|
|
42
|
+
/** The prompt prefix (the question mark, or the check mark once answered). */
|
|
43
|
+
prefix: string;
|
|
44
|
+
/** What has been typed so far. */
|
|
45
|
+
value: string;
|
|
46
|
+
/** The validation message, when the last answer was refused. */
|
|
47
|
+
error?: string;
|
|
48
|
+
/** Whether the question has been answered, which drops the legend and the padding. */
|
|
49
|
+
answered?: boolean;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Draws the question: prefix, message, the default in parentheses and what has
|
|
54
|
+
* been typed, plus the block underneath carrying the validation message when
|
|
55
|
+
* there is one and the key legend otherwise, and the padding that keeps the
|
|
56
|
+
* question off the terminal's last row.
|
|
57
|
+
*
|
|
58
|
+
* @param view - The prompt's state.
|
|
59
|
+
* @returns The question line and the block under it.
|
|
60
|
+
*/
|
|
61
|
+
export function renderValuePrompt(view: ValuePromptView): [string, string] {
|
|
62
|
+
const shown = view.mask === true ? "*".repeat(view.value.length) : view.value;
|
|
63
|
+
const suffix = view.default !== undefined && view.default !== "" ? ` (${view.default})` : "";
|
|
64
|
+
const line = `${view.prefix} ${view.message}${suffix}: ${shown}`.trimEnd();
|
|
65
|
+
|
|
66
|
+
if (view.answered === true) return [line, ""];
|
|
67
|
+
return [line, promptBottom(view.error ?? view.hint ?? "")];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Asks for one value, with Tab bound to the advanced view. */
|
|
71
|
+
export const valuePrompt = createPrompt<ValuePromptResult, ValuePromptConfig>((config, done) => {
|
|
72
|
+
const [status, setStatus] = useState<Status>("idle");
|
|
73
|
+
const [value, setValue] = useState("");
|
|
74
|
+
const [error, setError] = useState<string | undefined>(undefined);
|
|
75
|
+
const prefix = usePrefix({ status });
|
|
76
|
+
|
|
77
|
+
useKeypress(async (key, rl) => {
|
|
78
|
+
if (status !== "idle") return;
|
|
79
|
+
|
|
80
|
+
if (isTabKey(key)) {
|
|
81
|
+
// Readline has already put a literal tab in the buffer; take it back out.
|
|
82
|
+
rl.clearLine(0);
|
|
83
|
+
rl.write(value);
|
|
84
|
+
setStatus("done");
|
|
85
|
+
done({ kind: "advanced" });
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (isEnterKey(key)) {
|
|
90
|
+
const answer = value === "" ? (config.default ?? "") : value;
|
|
91
|
+
setStatus("loading");
|
|
92
|
+
const checked = config.validate === undefined ? true : await config.validate(answer);
|
|
93
|
+
if (checked === true) {
|
|
94
|
+
setValue(answer);
|
|
95
|
+
setStatus("done");
|
|
96
|
+
done({ kind: "value", value: answer });
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
rl.write(value);
|
|
100
|
+
setError(typeof checked === "string" ? checked : "That answer is not valid.");
|
|
101
|
+
setStatus("idle");
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
setValue(rl.line);
|
|
106
|
+
setError(undefined);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
return renderValuePrompt({
|
|
110
|
+
prefix,
|
|
111
|
+
message: config.message,
|
|
112
|
+
value,
|
|
113
|
+
...(status === "done" ? { answered: true } : {}),
|
|
114
|
+
...(config.default === undefined ? {} : { default: config.default }),
|
|
115
|
+
...(config.hint === undefined ? {} : { hint: config.hint }),
|
|
116
|
+
...(config.mask === undefined ? {} : { mask: config.mask }),
|
|
117
|
+
...(error === undefined ? {} : { error }),
|
|
118
|
+
});
|
|
119
|
+
});
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
The agent performing this work MUST load `about-task-files`.
|
|
2
|
-
|
|
3
|
-
## Delegation
|
|
4
|
-
|
|
5
|
-
Per the sub-agent delegation pattern (`~sous-shared/_partials/sub-agent-delegation.md`), the
|
|
6
|
-
orchestrator delegates steps 1 and 2 (find and read the task file, extract status) to a background
|
|
7
|
-
sub-agent, which reports back a concise summary. Step 3 is orchestrator-only.
|
|
8
|
-
|
|
9
|
-
## Steps
|
|
10
|
-
|
|
11
|
-
### 1. Find the Task File
|
|
12
|
-
|
|
13
|
-
Run `git status` to get the current branch name. Look for the task file at:
|
|
14
|
-
```
|
|
15
|
-
{{ taskFileRoot }}/[branch-name].md
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
If not found, run a fresh `git status` to confirm the branch before concluding the file doesn't exist. If it truly doesn't exist, report that back; the orchestrator asks the user whether to create one (load `start-task`).
|
|
19
|
-
|
|
20
|
-
### 2. Read and Analyze
|
|
21
|
-
|
|
22
|
-
Read the entire task file. Identify:
|
|
23
|
-
- Overall objective
|
|
24
|
-
- What has been completed (checked items, status markers)
|
|
25
|
-
- What remains (unchecked items, TODOs)
|
|
26
|
-
- Any blockers or pending issues
|
|
27
|
-
- Recent decisions and learnings
|
|
28
|
-
|
|
29
|
-
### 3. Present Status and Next Steps
|
|
30
|
-
|
|
31
|
-
Orchestrator-only; sub-agents cannot talk to the user. A sub-agent doing steps 1 and 2 returns its
|
|
32
|
-
summary to the orchestrator, which presents:
|
|
33
|
-
|
|
34
|
-
```
|
|
35
|
-
Based on the task file, here's where we are:
|
|
36
|
-
|
|
37
|
-
**Current Status:**
|
|
38
|
-
[Brief summary of completed phases and where work stopped]
|
|
39
|
-
|
|
40
|
-
**Recommended Next Steps:**
|
|
41
|
-
1. [First thing to do]
|
|
42
|
-
2. [Second thing]
|
|
43
|
-
3. [etc.]
|
|
44
|
-
|
|
45
|
-
Would you like me to:
|
|
46
|
-
- A) Proceed with these next steps?
|
|
47
|
-
- B) Focus on something specific?
|
|
48
|
-
- C) Something else?
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
**Do not begin work until the user confirms.** If the task file has contradictions or outdated information, point them out before proceeding.
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
# Sub-Agent Delegation
|
|
2
|
-
|
|
3
|
-
Several shared skills say work is "delegated" and cite the sub-agent delegation pattern. This is
|
|
4
|
-
that pattern. Include this partial in a project's core memory (`@~sous-shared/_partials/sub-agent-delegation.md`)
|
|
5
|
-
if the project wants the agent to follow it by default.
|
|
6
|
-
|
|
7
|
-
The main chat session is an **orchestrator**: it does the reasoning, the decisions, and all
|
|
8
|
-
interaction with the user, and it delegates execution to sub-agents. The orchestrator usually runs
|
|
9
|
-
an expensive model, so pushing execution down to cheaper sub-agents cuts cost and, because
|
|
10
|
-
sub-agents run in parallel, finishes sooner.
|
|
11
|
-
|
|
12
|
-
**Orchestrator-only:** planning, decisions, anything that needs the user (questions, approvals,
|
|
13
|
-
drafts), and very small actions where delegating costs more than doing it (a quick file read, a
|
|
14
|
-
one-line edit).
|
|
15
|
-
|
|
16
|
-
**Delegated:** anything with real work in it, including research, code changes, doc and task file
|
|
17
|
-
writing, and multi-step lookups.
|
|
18
|
-
|
|
19
|
-
**Rules:**
|
|
20
|
-
|
|
21
|
-
- Run sub-agents in the background and in parallel by default; batch independent dispatches into one
|
|
22
|
-
message. Go synchronous only when the result blocks the very next step.
|
|
23
|
-
- Prompts must be self-contained. Sub-agents start with fresh context and cannot see the
|
|
24
|
-
conversation, so state every fact, path, ID, and decision they need, and name the skills they
|
|
25
|
-
should load. Anything the sub-agent can gather itself (branch name, commits, test output) should
|
|
26
|
-
be left to it.
|
|
27
|
-
- Sub-agents cannot talk to the user. Questions and user-facing messages go back to the orchestrator
|
|
28
|
-
to relay.
|
|
29
|
-
- Every sub-agent reports a concise summary of what it did or found, not a file dump.
|
|
30
|
-
- The orchestrator spot-checks load-bearing results (task files, code diffs, outward-facing writes)
|
|
31
|
-
in a cheap, targeted way; it does not re-read everything.
|
|
32
|
-
- Never fabricate or predict a pending sub-agent's result. Wait for it.
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
The agent performing this work MUST load `about-task-files`.
|
|
2
|
-
|
|
3
|
-
**CRITICAL: Do NOTHING ELSE. Update the task file IMMEDIATELY. Use as few edits as possible.**
|
|
4
|
-
|
|
5
|
-
## Delegation
|
|
6
|
-
|
|
7
|
-
Per the sub-agent delegation pattern (`~sous-shared/_partials/sub-agent-delegation.md`), writing the
|
|
8
|
-
task file is delegated work. The orchestrator does not edit the file itself; it hands an Opus
|
|
9
|
-
sub-agent everything below in one self-contained prompt:
|
|
10
|
-
|
|
11
|
-
- The task file path (branch name), or instructions to derive it from `git status`.
|
|
12
|
-
- The facts and decisions to record. Compiling these is orchestrator-only; sub-agents cannot see
|
|
13
|
-
this conversation, so anything known only from the conversation must be stated in the prompt.
|
|
14
|
-
- Anything the sub-agent can gather itself (commit list, changed files, test/build output), which it
|
|
15
|
-
should collect rather than have the orchestrator paste in.
|
|
16
|
-
|
|
17
|
-
The sub-agent loads `about-task-files`, edits the file, and reports back a concise summary of what
|
|
18
|
-
it wrote. The orchestrator spot-checks with a targeted diff, not a full re-read.
|
|
19
|
-
|
|
20
|
-
## What to Include
|
|
21
|
-
|
|
22
|
-
Update the task file with all of the following:
|
|
23
|
-
|
|
24
|
-
1. **Progress since last update** — what was completed, what is in progress, current status
|
|
25
|
-
2. **Decisions made** — technical approach chosen, alternatives considered and rejected
|
|
26
|
-
3. **Remaining work** — everything still needed, prioritized next steps, known dependencies
|
|
27
|
-
4. **Pending issues** — unresolved problems, blockers, questions needing answers
|
|
28
|
-
5. **Learnings** — insights, patterns discovered, things to remember
|
|
29
|
-
6. **Commits made** — commit IDs and brief description of each
|
|
30
|
-
7. **Testing URLs** — web URLs currently in use, database query results referenced
|
|
31
|
-
8. **Files changed** — absolute paths with line numbers, what was done and why
|
|
32
|
-
9. **Unresolved errors** — test failures, build errors, TypeScript errors — with paths and line numbers
|
|
33
|
-
|
|
34
|
-
## Checklist Management
|
|
35
|
-
|
|
36
|
-
- Mark completed items `[x]`
|
|
37
|
-
- Add `[ ]` for newly identified tasks
|
|
38
|
-
- Remove or condense sections no longer relevant
|
|
39
|
-
|
|
40
|
-
## Consistency Check
|
|
41
|
-
|
|
42
|
-
Scan the existing file for contradictions or outdated information. Correct before saving.
|
|
43
|
-
|
|
44
|
-
## After Update
|
|
45
|
-
|
|
46
|
-
**STOP immediately.** Do not:
|
|
47
|
-
- Continue working on the task
|
|
48
|
-
- Start new work
|
|
49
|
-
- Make additional changes
|
|
50
|
-
|
|
51
|
-
A sub-agent reports what it wrote and stops. The orchestrator then waits; the user will end the
|
|
52
|
-
session and start a new one.
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
# Automated Browser Tasks
|
|
2
|
-
|
|
3
|
-
This project can drive a real web browser to accomplish tasks — logging in as
|
|
4
|
-
you (using your existing Chrome session), navigating, and extracting data —
|
|
5
|
-
**headlessly**, without opening a visible browser or interrupting your work.
|
|
6
|
-
|
|
7
|
-
**When a task needs a browser and no API or MCP server can do it, this is the
|
|
8
|
-
default path.** Do not tell the user to click through a browser manually if it
|
|
9
|
-
can be scripted.
|
|
10
|
-
|
|
11
|
-
**Running a non-mutating task is normal workflow — do it without asking.** A task
|
|
12
|
-
that only navigates and reads (extracts data; submits nothing, changes no state,
|
|
13
|
-
sends nothing outward) is investigation, the same category as reading a file or
|
|
14
|
-
running a query. Running an existing read-only task is not a "manual action" and
|
|
15
|
-
not "outward-facing," so it does not need the user's permission — especially while
|
|
16
|
-
investigating. Just run it and report what it found; pausing to ask first only
|
|
17
|
-
wastes a round trip on a no-risk action. (Creating or modifying a task script, or
|
|
18
|
-
running a task that mutates external state, is a separate case — apply normal
|
|
19
|
-
judgment there.)
|
|
20
|
-
|
|
21
|
-
The agent performing the work MUST load the `about-automated-browser-tasks` skill
|
|
22
|
-
before writing, running, or modifying any of these tasks. It explains the `ctx`
|
|
23
|
-
API, the auth model, and the scriptwriting conventions. The action skills
|
|
24
|
-
`create-automated-browser-task`, `update-automated-browser-task`, and
|
|
25
|
-
`running-automated-browser-tasks` cover those specific operations (including
|
|
26
|
-
exact runner invocation).
|
|
27
|
-
|
|
28
|
-
Browser runs are slow and multi-step, so they are delegated to a background
|
|
29
|
-
sub-agent, per the sub-agent delegation pattern
|
|
30
|
-
(`~sous-shared/_partials/sub-agent-delegation.md`). The sub-agent loads the skills
|
|
31
|
-
itself and reports the result plus any link or actionable message.
|
|
32
|
-
|
|
33
|
-
## Available Tasks
|
|
34
|
-
|
|
35
|
-
{% getFiles browserTasks root=browserAutomationScriptsDir include="*.mjs" import="meta" -%}
|
|
36
|
-
{% if browserTasks.size > 0 -%}
|
|
37
|
-
{%- for t in browserTasks %}
|
|
38
|
-
### {{ t.meta.name }}
|
|
39
|
-
|
|
40
|
-
{{ t.meta.description }}
|
|
41
|
-
|
|
42
|
-
- Script: `{{ t.path }}`
|
|
43
|
-
{%- if t.meta.params %}
|
|
44
|
-
- Parameters:
|
|
45
|
-
{%- for p in t.meta.params %}
|
|
46
|
-
- `{{ p[0] }}` ({% if p[1].required %}required{% else %}optional{% if p[1].default %}, default: `{{ p[1].default }}`{% endif %}{% endif %}) — {{ p[1].description | split: ". " | first }}.
|
|
47
|
-
{%- endfor %}
|
|
48
|
-
{%- endif %}
|
|
49
|
-
{% endfor -%}
|
|
50
|
-
{% else -%}
|
|
51
|
-
_No automation tasks exist yet. Load `create-automated-browser-task` to add one._
|
|
52
|
-
{% endif %}
|
package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/SKILL.tpl.md
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: about-automated-browser-tasks
|
|
3
|
-
description: >
|
|
4
|
-
YOU MUST load this skill when the user asks you to do anything that requires
|
|
5
|
-
interacting with a web browser, or when no MCP server or API can accomplish a
|
|
6
|
-
task and the only path forward is browser-based actions. Before telling the
|
|
7
|
-
user to do something manually in a browser, check for an existing automation
|
|
8
|
-
script and offer to run or write one. When browser work is needed, look for an
|
|
9
|
-
existing script first; create one only if none exists.
|
|
10
|
-
user-invocable: false
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
# Automated Browser Tasks
|
|
14
|
-
|
|
15
|
-
This system lets you write, run, and reuse headless browser automation scripts
|
|
16
|
-
(Playwright) that authenticate as the user by borrowing their existing Chrome
|
|
17
|
-
session — no manual login, no visible browser, Chrome stays open.
|
|
18
|
-
|
|
19
|
-
When a task needs a browser and no API/MCP server can do it, this is the default
|
|
20
|
-
path. Do not instruct the user to click through a browser manually if the task
|
|
21
|
-
can be scripted.
|
|
22
|
-
|
|
23
|
-
## How It Works (cold-start orientation)
|
|
24
|
-
|
|
25
|
-
1. **Auth is automatic.** A harness reads cookies from the user's Chrome profile,
|
|
26
|
-
decrypts them locally (via the OS keyring), and injects them into a fresh
|
|
27
|
-
headless browser context. Scripts "just work" as the logged-in user.
|
|
28
|
-
2. **Scripts are small modules.** Each script file exports `meta` (name,
|
|
29
|
-
description, params) and `execute(ctx)`. The harness resolves + validates
|
|
30
|
-
params, builds `ctx`, and runs `execute`.
|
|
31
|
-
3. **You run scripts by absolute path** through the runner:
|
|
32
|
-
```bash
|
|
33
|
-
node <scriptsDir>/run.mjs <absolute-path-to-script.mjs> [--param=value ...]
|
|
34
|
-
```
|
|
35
|
-
4. **Scripts return a result object**; the runner prints it and writes any
|
|
36
|
-
`outputFile`. Auth failures are detected and reported with an actionable
|
|
37
|
-
message telling the user to log in, then you retry.
|
|
38
|
-
|
|
39
|
-
The framework code (harness, chrome-state, keyring, logger, utils, params,
|
|
40
|
-
run) lives in this skill's `scripts/` directory. User scripts live in the
|
|
41
|
-
project's configured scripts directory and are invoked by path.
|
|
42
|
-
|
|
43
|
-
## The `ctx` Object
|
|
44
|
-
|
|
45
|
-
Every script's `execute(ctx)` receives:
|
|
46
|
-
|
|
47
|
-
- `ctx.page` — Playwright `Page`
|
|
48
|
-
- `ctx.context` / `ctx.browser` — Playwright context / browser
|
|
49
|
-
- `ctx.params` — fully resolved + validated params
|
|
50
|
-
- `ctx.settings` — compiled project settings (from `settings.mjs`)
|
|
51
|
-
- `ctx.timeout` — overall timeout (ms)
|
|
52
|
-
- `ctx.logger` — prefixed logger; `ctx.logger.child('section')` for sections
|
|
53
|
-
- `ctx.utils` — generic action helpers (modal dismissal, waits, text extraction, …)
|
|
54
|
-
- `ctx.debug` — exploration & debugging tools (dump, describe, findText, watch, …)
|
|
55
|
-
- `ctx.checkAuth()` — throws `AuthError` if redirected to a login page
|
|
56
|
-
- `ctx.runChild(script, params)` — run another script in the same browser context
|
|
57
|
-
|
|
58
|
-
Full details: [references/ctx-api.md](references/ctx-api.md).
|
|
59
|
-
|
|
60
|
-
## Writing Scripts — Non-Negotiable Conventions
|
|
61
|
-
|
|
62
|
-
- `execute()` is a thin orchestrator; logic lives in small named step functions.
|
|
63
|
-
- EVERY function (including `execute`) has a full JSDoc block (`@param`/`@returns`).
|
|
64
|
-
- Destructure what you need off `ctx`/`params` at the top of each function.
|
|
65
|
-
- Use `ctx.logger`, never `console.log`.
|
|
66
|
-
- Do NOT validate params in the script — declare rules in `meta.params`.
|
|
67
|
-
- Fail fast; do not catch-and-continue. Fix root causes, not symptoms.
|
|
68
|
-
|
|
69
|
-
Full conventions: [references/script-conventions.md](references/script-conventions.md).
|
|
70
|
-
|
|
71
|
-
## Reference Files
|
|
72
|
-
|
|
73
|
-
- [references/architecture.md](references/architecture.md) — components and data flow
|
|
74
|
-
- [references/ctx-api.md](references/ctx-api.md) — complete `ctx` and `ctx.utils` API
|
|
75
|
-
- [references/auth-and-sessions.md](references/auth-and-sessions.md) — how auth works, auth failures, retries
|
|
76
|
-
- [references/script-conventions.md](references/script-conventions.md) — full scriptwriting rules
|
|
77
|
-
- [references/installation.md](references/installation.md) — dependencies and setup
|
|
78
|
-
|
|
79
|
-
## Examples
|
|
80
|
-
|
|
81
|
-
- [examples/simple-fetch.mjs](examples/simple-fetch.mjs) — single-page data extraction
|
|
82
|
-
- [examples/chained-workflow.mjs](examples/chained-workflow.mjs) — a script calling another via `ctx.runChild`
|
|
83
|
-
- [examples/auth-failure-handling.mjs](examples/auth-failure-handling.mjs) — the auth-failure + retry pattern
|
|
84
|
-
|
|
85
|
-
# Other Skills
|
|
86
|
-
|
|
87
|
-
The agent performing the work MUST load `create-automated-browser-task` when
|
|
88
|
-
creating a new automation script, `update-automated-browser-task` when modifying an
|
|
89
|
-
existing script, and `running-automated-browser-tasks` when running a script.
|
|
90
|
-
|
|
91
|
-
Per the sub-agent delegation pattern
|
|
92
|
-
(`~sous-shared/_partials/sub-agent-delegation.md`), all three are delegated to a background
|
|
93
|
-
sub-agent, which loads the skills itself and reports back. Auth failures and other
|
|
94
|
-
user-facing messages go to the orchestrator to relay; sub-agents cannot talk to the
|
|
95
|
-
user.
|
|
96
|
-
|
|
97
|
-
## Source for this Skill
|
|
98
|
-
|
|
99
|
-
This skill was pulled from the `sous` project's "shared skills" library. It was compiled from a template and
|
|
100
|
-
the output file should not be edited directly.
|
|
101
|
-
|
|
102
|
-
- Source Path: {{ sousTemplatePath }}
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* auth-failure-handling
|
|
3
|
-
*
|
|
4
|
-
* Demonstrates the auth-failure contract. Scripts do NOT log in and do NOT
|
|
5
|
-
* recover mid-run: they call `ctx.checkAuth()` after navigation and let the
|
|
6
|
-
* harness convert a login-page redirect into a structured auth error. The agent
|
|
7
|
-
* relays the message, the user logs in, and the SAME command is re-run.
|
|
8
|
-
*
|
|
9
|
-
* This script targets a page that requires authentication and simply reports
|
|
10
|
-
* whether it reached real content — illustrating where checkAuth belongs.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
export const meta = {
|
|
14
|
-
name: 'auth-failure-handling',
|
|
15
|
-
description: 'Visit an authenticated page and confirm we are logged in (auth-pattern demo).',
|
|
16
|
-
params: {
|
|
17
|
-
url: {
|
|
18
|
-
required: true,
|
|
19
|
-
description:
|
|
20
|
-
'Absolute URL of a page that requires authentication. If the Chrome ' +
|
|
21
|
-
'session is expired, the harness will surface an actionable auth error.',
|
|
22
|
-
validate: /^https?:\/\/.+/,
|
|
23
|
-
invalidMessage: 'url must be an absolute http(s) URL.',
|
|
24
|
-
},
|
|
25
|
-
readySelector: {
|
|
26
|
-
required: true,
|
|
27
|
-
description:
|
|
28
|
-
'CSS selector for an element that only appears once the authenticated ' +
|
|
29
|
-
'content has loaded (e.g. a user avatar or app shell). Used to confirm we ' +
|
|
30
|
-
'are past any login wall.',
|
|
31
|
-
},
|
|
32
|
-
},
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Entry point. Navigates, checks auth, then confirms authed content rendered.
|
|
37
|
-
*
|
|
38
|
-
* @param {object} ctx - The harness context.
|
|
39
|
-
* @returns {Promise<object>} `{ found, url, message }`.
|
|
40
|
-
*/
|
|
41
|
-
export async function execute(ctx) {
|
|
42
|
-
const { page, params } = ctx;
|
|
43
|
-
const { url, readySelector } = params;
|
|
44
|
-
|
|
45
|
-
await openAuthedPage(ctx, url, readySelector);
|
|
46
|
-
|
|
47
|
-
return {
|
|
48
|
-
found: true,
|
|
49
|
-
url: page.url(),
|
|
50
|
-
message: 'Reached authenticated content successfully.',
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Navigate to an authenticated page and confirm we reached it — the canonical
|
|
56
|
-
* auth-handling pattern. Many SPAs resolve auth client-side a beat AFTER load and
|
|
57
|
-
* redirect to a login page, so we do NOT check the URL immediately. Instead we
|
|
58
|
-
* wait for the success signal (`readySelector`, an element that only renders for
|
|
59
|
-
* authenticated users). Only if that wait times out do we call `checkAuth()`: by
|
|
60
|
-
* then the URL has settled, so a login page becomes a clear AuthError (which the
|
|
61
|
-
* harness reports as `{ error: 'auth', message }`), while anything else re-throws
|
|
62
|
-
* as a genuine render timeout. The script never tries to log in itself.
|
|
63
|
-
*
|
|
64
|
-
* @param {object} ctx - The harness context.
|
|
65
|
-
* @param {string} url - The authenticated URL to load.
|
|
66
|
-
* @param {string} readySelector - Element proving authed content rendered.
|
|
67
|
-
* @returns {Promise<void>}
|
|
68
|
-
*/
|
|
69
|
-
async function openAuthedPage(ctx, url, readySelector) {
|
|
70
|
-
const { page, logger, timeout, checkAuth } = ctx;
|
|
71
|
-
const log = logger.child('navigate');
|
|
72
|
-
log.info(`Loading authenticated page ${url}`);
|
|
73
|
-
await page.goto(url, { waitUntil: 'domcontentloaded', timeout });
|
|
74
|
-
try {
|
|
75
|
-
await page.locator(readySelector).first().waitFor({ state: 'visible', timeout });
|
|
76
|
-
log.info('Authenticated content is present');
|
|
77
|
-
} catch (renderTimeout) {
|
|
78
|
-
await checkAuth(); // throws AuthError if the settled URL is a login page
|
|
79
|
-
throw renderTimeout;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* chained-workflow
|
|
3
|
-
*
|
|
4
|
-
* Demonstrates composing scripts: one script invokes another via
|
|
5
|
-
* `ctx.runChild`, reusing the same authenticated browser context. The child
|
|
6
|
-
* runs in-process and returns its result object directly.
|
|
7
|
-
*
|
|
8
|
-
* Here the parent collects a list of item URLs from an index page, then runs a
|
|
9
|
-
* child "fetch" script against each one. In a real project the child would be a
|
|
10
|
-
* separate script file imported at the top; it is inlined here so the example
|
|
11
|
-
* is self-contained.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
export const meta = {
|
|
15
|
-
name: 'chained-workflow',
|
|
16
|
-
description: 'Collect links from an index page, then fetch each via a child script.',
|
|
17
|
-
params: {
|
|
18
|
-
indexUrl: {
|
|
19
|
-
required: true,
|
|
20
|
-
description:
|
|
21
|
-
'Absolute URL of the index/listing page to scrape links from. Loaded as ' +
|
|
22
|
-
'the authenticated user.',
|
|
23
|
-
validate: /^https?:\/\/.+/,
|
|
24
|
-
invalidMessage: 'indexUrl must be an absolute http(s) URL.',
|
|
25
|
-
},
|
|
26
|
-
linkSelector: {
|
|
27
|
-
required: false,
|
|
28
|
-
default: 'a[href]',
|
|
29
|
-
description:
|
|
30
|
-
'CSS selector matching the links to follow on the index page. Defaults ' +
|
|
31
|
-
'to all anchors with an href.',
|
|
32
|
-
},
|
|
33
|
-
limit: {
|
|
34
|
-
required: false,
|
|
35
|
-
default: 3,
|
|
36
|
-
description:
|
|
37
|
-
'Maximum number of links to follow, to keep runs bounded. Provided as a ' +
|
|
38
|
-
'string on the CLI and coerced to a number.',
|
|
39
|
-
validate: (v) => Number(v) > 0 || 'limit must be a positive number',
|
|
40
|
-
},
|
|
41
|
-
},
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
/** A small child script run once per collected link. Normally its own file. */
|
|
45
|
-
const fetchTitle = {
|
|
46
|
-
meta: {
|
|
47
|
-
name: 'fetch-title',
|
|
48
|
-
params: {
|
|
49
|
-
url: { required: true, validate: /^https?:\/\/.+/, invalidMessage: 'url must be absolute http(s)' },
|
|
50
|
-
},
|
|
51
|
-
},
|
|
52
|
-
/**
|
|
53
|
-
* Load a URL and return its document title.
|
|
54
|
-
*
|
|
55
|
-
* @param {object} ctx - The harness context (shares the parent's browser).
|
|
56
|
-
* @returns {Promise<object>} `{ url, title }`.
|
|
57
|
-
*/
|
|
58
|
-
async execute(ctx) {
|
|
59
|
-
const { page, params, logger, timeout, checkAuth } = ctx;
|
|
60
|
-
logger.child('navigate').info(`Fetching ${params.url}`);
|
|
61
|
-
// domcontentloaded is enough here: the <title> is in the initial document.
|
|
62
|
-
await page.goto(params.url, { waitUntil: 'domcontentloaded', timeout });
|
|
63
|
-
await checkAuth();
|
|
64
|
-
return { url: page.url(), title: await page.title() };
|
|
65
|
-
},
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Entry point. Collects links from the index, then fetches each via a child.
|
|
70
|
-
*
|
|
71
|
-
* @param {object} ctx - The harness context.
|
|
72
|
-
* @returns {Promise<object>} `{ found, count, results }`.
|
|
73
|
-
*/
|
|
74
|
-
export async function execute(ctx) {
|
|
75
|
-
const { params } = ctx;
|
|
76
|
-
const { indexUrl, linkSelector, limit } = params;
|
|
77
|
-
|
|
78
|
-
const links = await collectLinks(ctx, indexUrl, linkSelector, Number(limit));
|
|
79
|
-
const results = await fetchEach(ctx, links);
|
|
80
|
-
|
|
81
|
-
return { found: results.length > 0, count: results.length, results };
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Load the index page and collect up to `limit` link URLs.
|
|
86
|
-
*
|
|
87
|
-
* @param {object} ctx - The harness context.
|
|
88
|
-
* @param {string} indexUrl - The listing page URL.
|
|
89
|
-
* @param {string} selector - Selector matching links to follow.
|
|
90
|
-
* @param {number} limit - Max links to return.
|
|
91
|
-
* @returns {Promise<string[]>} Absolute link URLs.
|
|
92
|
-
*/
|
|
93
|
-
async function collectLinks(ctx, indexUrl, selector, limit) {
|
|
94
|
-
const { page, logger, timeout, checkAuth } = ctx;
|
|
95
|
-
const log = logger.child('collect');
|
|
96
|
-
log.info(`Loading index ${indexUrl}`);
|
|
97
|
-
await page.goto(indexUrl, { waitUntil: 'domcontentloaded', timeout });
|
|
98
|
-
// Success signal: the links we intend to read are present. If they never
|
|
99
|
-
// appear, check auth (login redirects resolve late) before failing.
|
|
100
|
-
try {
|
|
101
|
-
await page.locator(selector).first().waitFor({ state: 'visible', timeout });
|
|
102
|
-
} catch (renderTimeout) {
|
|
103
|
-
await checkAuth();
|
|
104
|
-
throw renderTimeout;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const hrefs = await page.$$eval(selector, (els) => els.map((el) => el.href));
|
|
108
|
-
const unique = [...new Set(hrefs.filter(Boolean))].slice(0, limit);
|
|
109
|
-
log.info(`Collected ${unique.length} link(s)`);
|
|
110
|
-
return unique;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Run the child fetch script against each link, in sequence.
|
|
115
|
-
*
|
|
116
|
-
* @param {object} ctx - The harness context.
|
|
117
|
-
* @param {string[]} links - URLs to fetch.
|
|
118
|
-
* @returns {Promise<object[]>} One child result per link.
|
|
119
|
-
*/
|
|
120
|
-
async function fetchEach(ctx, links) {
|
|
121
|
-
const results = [];
|
|
122
|
-
for (const url of links) {
|
|
123
|
-
results.push(await ctx.runChild(fetchTitle, { url }));
|
|
124
|
-
}
|
|
125
|
-
return results;
|
|
126
|
-
}
|