@sous-io/sous 0.1.0 → 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.
Files changed (206) hide show
  1. package/README.md +121 -35
  2. package/bin/run.js +10 -1
  3. package/docs/markdown/README.md +27 -0
  4. package/docs/markdown/_sidebar.md +18 -0
  5. package/docs/markdown/commands.md +308 -0
  6. package/docs/markdown/config-discovery.md +74 -0
  7. package/docs/markdown/config-inspection.md +69 -0
  8. package/docs/markdown/config-layers.md +92 -0
  9. package/docs/markdown/config-variables.md +79 -0
  10. package/docs/markdown/configuration.md +71 -0
  11. package/docs/markdown/design-principles.md +59 -0
  12. package/docs/markdown/repositories-authoring.md +408 -0
  13. package/docs/markdown/repositories-consuming.md +580 -0
  14. package/docs/markdown/repositories-file-formats.md +1084 -0
  15. package/docs/markdown/repositories-variables.md +387 -0
  16. package/docs/markdown/repositories.md +303 -0
  17. package/docs/markdown/skill-categories.md +58 -0
  18. package/package.json +73 -9
  19. package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/about-agent-skills/SKILL.tpl.md +20 -20
  20. package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/about-agent-skills/examples/about-something.md +2 -2
  21. package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/about-agent-skills/examples/do-something.md +1 -1
  22. package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/about-agent-skills/references/advanced-patterns.md +6 -6
  23. package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/about-agent-skills/references/commands.md +5 -5
  24. package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/about-agent-skills/references/frontmatter.md +3 -3
  25. package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/about-liquid-templates/SKILL.tpl.md +40 -25
  26. package/recipes/core/sous-skills/skills/about-sous/SKILL.tpl.md +70 -0
  27. package/recipes/core/sous-skills/skills/about-sous-configuration/SKILL.tpl.md +75 -0
  28. package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/create-skill/SKILL.tpl.md +8 -9
  29. package/recipes/core/sous-skills/sous.recipe.yaml +45 -0
  30. package/sous.config.schema.json +337 -0
  31. package/src/base-command.ts +220 -67
  32. package/src/commands/build.ts +150 -73
  33. package/src/commands/clear.ts +23 -15
  34. package/src/commands/compile.ts +74 -16
  35. package/src/commands/config/get.ts +110 -0
  36. package/src/commands/config/show.ts +32 -0
  37. package/src/commands/config/validate.ts +53 -0
  38. package/src/commands/help.ts +46 -0
  39. package/src/commands/launch.ts +36 -14
  40. package/src/commands/lock/rebuild.ts +241 -0
  41. package/src/commands/lock/show.ts +115 -0
  42. package/src/commands/namespace/list.ts +117 -0
  43. package/src/commands/namespace/show.ts +110 -0
  44. package/src/commands/prune.ts +3 -11
  45. package/src/commands/recipe/list.ts +95 -0
  46. package/src/commands/recipe/show.ts +301 -0
  47. package/src/commands/repo/add.ts +145 -0
  48. package/src/commands/repo/gc.ts +172 -0
  49. package/src/commands/repo/init.ts +136 -0
  50. package/src/commands/repo/link.ts +500 -0
  51. package/src/commands/repo/list.ts +179 -0
  52. package/src/commands/repo/release.ts +619 -0
  53. package/src/commands/repo/remove.ts +193 -0
  54. package/src/commands/repo/search.ts +189 -0
  55. package/src/commands/repo/submit.ts +133 -0
  56. package/src/commands/repo/unlink.ts +147 -0
  57. package/src/commands/subscription/add.ts +285 -0
  58. package/src/commands/subscription/list.ts +129 -0
  59. package/src/commands/subscription/remove.ts +181 -0
  60. package/src/commands/vars/ask.ts +374 -0
  61. package/src/commands/vars/index.ts +79 -0
  62. package/src/commands/vars/list.ts +67 -0
  63. package/src/commands/vars/show.ts +77 -0
  64. package/src/config-command.ts +30 -0
  65. package/src/lib/build-service.ts +206 -54
  66. package/src/lib/config-discovery.ts +220 -27
  67. package/src/lib/config-inspect.ts +145 -0
  68. package/src/lib/config-kernel.mjs +377 -0
  69. package/src/lib/config-schema.ts +361 -0
  70. package/src/lib/env-file.ts +328 -0
  71. package/src/lib/env-local.ts +18 -1
  72. package/src/lib/errors.ts +32 -0
  73. package/src/lib/include-resolver.ts +108 -15
  74. package/src/lib/interactive.ts +165 -0
  75. package/src/lib/markdown-compiler.ts +118 -37
  76. package/src/lib/package-info.ts +25 -0
  77. package/src/lib/pid-service.ts +32 -21
  78. package/src/lib/refs/find.ts +589 -0
  79. package/src/lib/refs/index.ts +12 -0
  80. package/src/lib/refs/pick.ts +147 -0
  81. package/src/lib/refs/scopes.ts +61 -0
  82. package/src/lib/repos/catalog-display.ts +116 -0
  83. package/src/lib/repos/catalog-inputs.ts +160 -0
  84. package/src/lib/repos/catalog.ts +722 -0
  85. package/src/lib/repos/core-recipe.ts +105 -0
  86. package/src/lib/repos/defaults.ts +175 -0
  87. package/src/lib/repos/formats/common.ts +389 -0
  88. package/src/lib/repos/formats/index-file.ts +215 -0
  89. package/src/lib/repos/formats/links-map.ts +96 -0
  90. package/src/lib/repos/formats/lockfile.ts +167 -0
  91. package/src/lib/repos/formats/patterns.ts +57 -0
  92. package/src/lib/repos/formats/recipe-manifest.ts +395 -0
  93. package/src/lib/repos/formats/repo-manifest.ts +88 -0
  94. package/src/lib/repos/formats/store-entry.ts +84 -0
  95. package/src/lib/repos/freshness.ts +208 -0
  96. package/src/lib/repos/git-clone.ts +312 -0
  97. package/src/lib/repos/identity.ts +89 -0
  98. package/src/lib/repos/index.ts +58 -0
  99. package/src/lib/repos/links.ts +353 -0
  100. package/src/lib/repos/load-manifest.ts +236 -0
  101. package/src/lib/repos/lock-service.ts +453 -0
  102. package/src/lib/repos/locked-namespace-resolver.ts +90 -0
  103. package/src/lib/repos/locked-recipes.ts +254 -0
  104. package/src/lib/repos/managed-layer.ts +422 -0
  105. package/src/lib/repos/namespace-resolver.ts +370 -0
  106. package/src/lib/repos/providers/base.ts +206 -0
  107. package/src/lib/repos/providers/git.ts +233 -0
  108. package/src/lib/repos/providers/github.ts +294 -0
  109. package/src/lib/repos/providers/gitlab.ts +263 -0
  110. package/src/lib/repos/providers/http.ts +102 -0
  111. package/src/lib/repos/providers/index-cache.ts +382 -0
  112. package/src/lib/repos/providers/index.ts +106 -0
  113. package/src/lib/repos/providers/local.ts +391 -0
  114. package/src/lib/repos/providers/provider.ts +401 -0
  115. package/src/lib/repos/recipe-config-layers.ts +287 -0
  116. package/src/lib/repos/recipe-targets.ts +223 -0
  117. package/src/lib/repos/ref-search.ts +46 -0
  118. package/src/lib/repos/ref.ts +513 -0
  119. package/src/lib/repos/reference-report.ts +122 -0
  120. package/src/lib/repos/release/bump.ts +161 -0
  121. package/src/lib/repos/release/git-state.ts +305 -0
  122. package/src/lib/repos/release/index-builder.ts +635 -0
  123. package/src/lib/repos/release/index.ts +16 -0
  124. package/src/lib/repos/release/plan.ts +512 -0
  125. package/src/lib/repos/release/submit-service.ts +496 -0
  126. package/src/lib/repos/release/tags.ts +243 -0
  127. package/src/lib/repos/release/validate.ts +463 -0
  128. package/src/lib/repos/resolver.ts +789 -0
  129. package/src/lib/repos/scaffold/index.ts +238 -0
  130. package/src/lib/repos/scaffold/templates.ts +413 -0
  131. package/src/lib/repos/seed.ts +414 -0
  132. package/src/lib/repos/store/contract.ts +64 -0
  133. package/src/lib/repos/store/hash.ts +114 -0
  134. package/src/lib/repos/store/recipe-store.ts +599 -0
  135. package/src/lib/repos/store/settings.ts +58 -0
  136. package/src/lib/repos/subscription-service.ts +2678 -0
  137. package/src/lib/repos/trust.ts +447 -0
  138. package/src/lib/settings.ts +546 -189
  139. package/src/lib/sous-home.ts +104 -0
  140. package/src/lib/state.ts +52 -20
  141. package/src/lib/vars/ask.ts +1152 -0
  142. package/src/lib/vars/definition-source.ts +252 -0
  143. package/src/lib/vars/display.ts +233 -0
  144. package/src/lib/vars/index.ts +18 -0
  145. package/src/lib/vars/ladder.ts +282 -0
  146. package/src/lib/vars/mappings.ts +265 -0
  147. package/src/lib/vars/names.ts +94 -0
  148. package/src/lib/vars/preanswers.ts +395 -0
  149. package/src/lib/vars/question-plan.ts +218 -0
  150. package/src/lib/vars/report.ts +228 -0
  151. package/src/lib/vars/safe-regex.ts +235 -0
  152. package/src/lib/vars/validate.ts +312 -0
  153. package/src/lib/watch-loop.ts +148 -0
  154. package/src/templating/init-liquid-engine.ts +58 -16
  155. package/src/utils/choice-prompt.ts +143 -0
  156. package/src/utils/command-errors.ts +186 -0
  157. package/src/utils/command-help.ts +45 -0
  158. package/src/utils/confirm-prompt.ts +110 -0
  159. package/src/utils/flags.ts +153 -0
  160. package/src/utils/formatting.ts +540 -55
  161. package/src/utils/prompts.ts +35 -1
  162. package/src/utils/sous-directory.ts +245 -0
  163. package/src/utils/table.ts +603 -0
  164. package/src/utils/value-prompt.ts +119 -0
  165. package/bin/xcv +0 -5
  166. package/shared-prompts/_partials/resume-task.md +0 -51
  167. package/shared-prompts/_partials/sub-agent-delegation.md +0 -32
  168. package/shared-prompts/_partials/update-task-file.md +0 -52
  169. package/shared-prompts/memories/automated-browser-tasks/INDEX.tpl.md +0 -52
  170. package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/SKILL.tpl.md +0 -102
  171. package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/examples/auth-failure-handling.mjs +0 -81
  172. package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/examples/chained-workflow.mjs +0 -126
  173. package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/examples/simple-fetch.mjs +0 -92
  174. package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/references/architecture.md +0 -61
  175. package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/references/auth-and-sessions.md +0 -65
  176. package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/references/ctx-api.md +0 -96
  177. package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/references/installation.md +0 -104
  178. package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/references/script-conventions.md +0 -243
  179. package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/chrome-state.mjs +0 -148
  180. package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/debug.mjs +0 -383
  181. package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/debug.spec.mjs +0 -267
  182. package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/eslint.config.mjs +0 -56
  183. package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/harness.mjs +0 -169
  184. package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/keyring.mjs +0 -59
  185. package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/logger.mjs +0 -25
  186. package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/params.mjs +0 -140
  187. package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/run.mjs +0 -140
  188. package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/settings.tpl.mjs +0 -1
  189. package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/utils.mjs +0 -185
  190. package/shared-prompts/skills/automated-browser-tasks/create-automated-browser-task/SKILL.tpl.md +0 -52
  191. package/shared-prompts/skills/automated-browser-tasks/running-automated-browser-tasks/SKILL.tpl.md +0 -59
  192. package/shared-prompts/skills/automated-browser-tasks/update-automated-browser-task/SKILL.tpl.md +0 -47
  193. package/shared-prompts/skills/control-flow/approve/SKILL.tpl.md +0 -26
  194. package/shared-prompts/skills/control-flow/opine/SKILL.tpl.md +0 -58
  195. package/shared-prompts/skills/control-flow/repeat/SKILL.tpl.md +0 -27
  196. package/shared-prompts/skills/control-flow/research/SKILL.tpl.md +0 -34
  197. package/shared-prompts/skills/sous-skills/about-sous/SKILL.tpl.md +0 -51
  198. package/shared-prompts/skills/task-files/about-task-files/SKILL.tpl.md +0 -122
  199. package/shared-prompts/skills/task-files/continue-task-in-new-branch/SKILL.tpl.md +0 -80
  200. package/shared-prompts/skills/task-files/go/SKILL.tpl.md +0 -14
  201. package/shared-prompts/skills/task-files/resume-task/SKILL.tpl.md +0 -13
  202. package/shared-prompts/skills/task-files/start-task/SKILL.tpl.md +0 -93
  203. package/shared-prompts/skills/task-files/update/SKILL.tpl.md +0 -14
  204. package/shared-prompts/skills/task-files/update-task-file/SKILL.tpl.md +0 -13
  205. /package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/about-agent-skills/references/substitutions.md +0 -0
  206. /package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/about-liquid-templates/references/liquid-filters.md +0 -0
@@ -1,140 +0,0 @@
1
- /**
2
- * Param resolution and validation.
3
- *
4
- * Scripts declare their params in `meta.params`. The framework resolves and
5
- * validates them BEFORE `execute()` runs, so a script never validates its own
6
- * input.
7
- *
8
- * Resolution order (lowest to highest priority):
9
- * ctx.settings < meta.params[x].default < explicit (CLI) params
10
- *
11
- * A param spec supports:
12
- * required {boolean} - error if no value resolves
13
- * default {*} - fallback value
14
- * description {string} - shown in CLI listings and error messages
15
- * validate {RegExp|Function}
16
- * RegExp - the resolved value (coerced to string) must match
17
- * Function - (value, resolvedParams) => true | false | string
18
- * true = valid
19
- * string = invalid; the string is used as the error message
20
- * false = invalid; `invalidMessage` (or a default) is used
21
- * invalidMessage {string} - error message for a failing RegExp, or for a
22
- * validate function that returns `false`
23
- */
24
-
25
- export class ParamError extends Error {
26
- constructor(message) {
27
- super(message);
28
- this.name = 'ParamError';
29
- }
30
- }
31
-
32
- /**
33
- * Resolve and validate params for a script.
34
- *
35
- * @param {object} meta - The script's `meta` export (may have `.params`).
36
- * @param {object} explicit - Params explicitly provided (e.g. from CLI).
37
- * @param {object} settings - Compiled project settings (ctx.settings).
38
- * @returns {object} The fully-resolved params object.
39
- * @throws {ParamError} If a required param is missing or a value fails validation.
40
- */
41
- export function resolveParams(meta, explicit = {}, settings = {}) {
42
- const spec = meta?.params || {};
43
- const scriptName = meta?.name || 'script';
44
- const resolved = {};
45
- const missing = [];
46
-
47
- for (const [key, def] of Object.entries(spec)) {
48
- let value;
49
- if (key in explicit) value = explicit[key];
50
- else if (def.default !== undefined) value = def.default;
51
- else if (key in settings) value = settings[key];
52
-
53
- if (value === undefined) {
54
- if (def.required) missing.push(key);
55
- continue;
56
- }
57
- resolved[key] = value;
58
- }
59
-
60
- // Pass through any explicit params not declared in meta (escape hatch).
61
- for (const [key, value] of Object.entries(explicit)) {
62
- if (!(key in resolved)) resolved[key] = value;
63
- }
64
-
65
- if (missing.length) {
66
- throw new ParamError(
67
- `Missing required param(s) for "${scriptName}":\n${missing
68
- .map((k) => describeParam(k, spec[k]))
69
- .join('\n')}`
70
- );
71
- }
72
-
73
- const invalid = validateParams(spec, resolved);
74
- if (invalid.length) {
75
- throw new ParamError(
76
- `Invalid param(s) for "${scriptName}":\n${invalid
77
- .map(({ key, message }) => ` - ${key}: ${message}`)
78
- .join('\n')}`
79
- );
80
- }
81
-
82
- return resolved;
83
- }
84
-
85
- /**
86
- * Run each declared param's `validate` rule against its resolved value.
87
- *
88
- * @param {object} spec - The `meta.params` spec object.
89
- * @param {object} resolved - The resolved param values.
90
- * @returns {Array<{key: string, message: string}>} One entry per failure.
91
- */
92
- function validateParams(spec, resolved) {
93
- const failures = [];
94
-
95
- for (const [key, def] of Object.entries(spec)) {
96
- if (!(key in resolved) || !def?.validate) continue;
97
- const message = runValidator(def, resolved[key], resolved);
98
- if (message) failures.push({ key, message });
99
- }
100
-
101
- return failures;
102
- }
103
-
104
- /**
105
- * Apply a single param's validator to a value.
106
- *
107
- * @param {object} def - The param spec (has `validate` and optional `invalidMessage`).
108
- * @param {*} value - The resolved value to check.
109
- * @param {object} resolved - All resolved params (passed to function validators).
110
- * @returns {string|null} An error message if invalid, otherwise null.
111
- */
112
- function runValidator(def, value, resolved) {
113
- const { validate, invalidMessage } = def;
114
-
115
- if (validate instanceof RegExp) {
116
- return validate.test(String(value))
117
- ? null
118
- : invalidMessage || `value "${value}" does not match ${validate}`;
119
- }
120
-
121
- if (typeof validate === 'function') {
122
- const result = validate(value, resolved);
123
- if (result === true) return null;
124
- if (typeof result === 'string') return result;
125
- return invalidMessage || `value "${value}" is invalid`;
126
- }
127
-
128
- return null;
129
- }
130
-
131
- /**
132
- * Format a param for inclusion in a "missing required" error message.
133
- *
134
- * @param {string} key - The param name.
135
- * @param {object} [def] - The param spec (may carry a description).
136
- * @returns {string} A single indented line.
137
- */
138
- function describeParam(key, def) {
139
- return def?.description ? ` - ${key}: ${def.description}` : ` - ${key}`;
140
- }
@@ -1,140 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * Runner — executes an automation script by absolute path with given params.
5
- *
6
- * Usage:
7
- * node run.mjs <absolute-path-to-script.mjs> [--param=value ...]
8
- *
9
- * Harness options are passed the same way and intercepted before params:
10
- * --profileName=<name> Chrome profile to pull cookies from
11
- * --timeout=<ms> Overall script timeout
12
- * --headless=false (debug) run headed — rarely useful for this system
13
- *
14
- * Everything else (--foo=bar) becomes a script param. The harness resolves
15
- * defaults + settings and validates required params before execute() runs.
16
- *
17
- * Project settings compiled by sous (settings.mjs, a sibling of this file) are
18
- * loaded automatically and passed to the harness as ctx.settings.
19
- */
20
-
21
- import { resolve, dirname, join } from 'path';
22
- import { existsSync, writeFileSync } from 'fs';
23
- import { fileURLToPath, pathToFileURL } from 'url';
24
-
25
- const HARNESS_OPTIONS = ['headless', 'profileName', 'timeout'];
26
-
27
- const args = process.argv.slice(2);
28
- if (args.length === 0 || args[0] === '--help') {
29
- console.log('Usage: node run.mjs <absolute-path-to-script.mjs> [--param=value ...]');
30
- process.exit(args.length === 0 ? 1 : 0);
31
- }
32
-
33
- const scriptPath = resolve(args[0]);
34
- if (!existsSync(scriptPath)) {
35
- console.error(`Script not found: ${scriptPath}`);
36
- process.exit(1);
37
- }
38
-
39
- const { params, options } = parseArgs(args.slice(1));
40
- const settings = await loadSettings();
41
- const script = await import(pathToFileURL(scriptPath).href);
42
- const meta = script.meta || {};
43
-
44
- printRunHeader(meta, params, options, settings);
45
-
46
- // Imported lazily so --help and arg validation don't require playwright et al.
47
- const { runScript } = await import('./harness.mjs');
48
- const result = await runScript(script, params, { ...options, settings });
49
-
50
- reportResult(result);
51
- process.exit(result.success ? 0 : 1);
52
-
53
- /**
54
- * Split `--key=value` args into harness options and script params.
55
- *
56
- * @param {string[]} rest - CLI args after the script path.
57
- * @returns {{params: object, options: object}}
58
- */
59
- function parseArgs(rest) {
60
- const params = {};
61
- const options = {};
62
- for (const arg of rest) {
63
- const match = arg.match(/^--([\w-]+)=(.*)$/s);
64
- if (!match) continue;
65
- const [, key, value] = match;
66
- if (!HARNESS_OPTIONS.includes(key)) {
67
- params[key] = value;
68
- } else if (key === 'headless') {
69
- options.headless = value !== 'false';
70
- } else if (key === 'timeout') {
71
- options.timeout = parseInt(value, 10);
72
- } else {
73
- options[key] = value;
74
- }
75
- }
76
- return { params, options };
77
- }
78
-
79
- /**
80
- * Load compiled project settings (settings.mjs) sitting beside this runner.
81
- *
82
- * @returns {Promise<object>} The settings object, or {} if none is present.
83
- */
84
- async function loadSettings() {
85
- const here = dirname(fileURLToPath(import.meta.url));
86
- const settingsPath = join(here, 'settings.mjs');
87
- if (!existsSync(settingsPath)) return {};
88
- const mod = await import(pathToFileURL(settingsPath).href);
89
- return mod.default || {};
90
- }
91
-
92
- /**
93
- * Print the resolved params (with defaults/settings marked) before running.
94
- *
95
- * @param {object} meta - The script's meta export.
96
- * @param {object} params - Explicit CLI params.
97
- * @param {object} options - Harness options.
98
- * @param {object} settings - Compiled project settings.
99
- * @returns {void}
100
- */
101
- function printRunHeader(meta, params, options, settings) {
102
- console.log(`\n▶ Running: ${meta.name || '(unnamed script)'}`);
103
- console.log(' Params:');
104
- const spec = meta.params || {};
105
- const keys = new Set([...Object.keys(spec), ...Object.keys(params)]);
106
- for (const key of keys) {
107
- let value, origin;
108
- if (key in params) { value = params[key]; origin = ''; }
109
- else if (spec[key]?.default !== undefined) { value = spec[key].default; origin = ' (default)'; }
110
- else if (key in settings) { value = settings[key]; origin = ' (settings)'; }
111
- else continue;
112
- console.log(` ${key}: ${value}${origin}`);
113
- }
114
- if (Object.keys(options).length) console.log(` Options: ${JSON.stringify(options)}`);
115
- console.log('');
116
- }
117
-
118
- /**
119
- * Print the run outcome and, on success, write any requested output file.
120
- *
121
- * @param {object} result - The harness result envelope.
122
- * @returns {void}
123
- */
124
- function reportResult(result) {
125
- console.log('\n' + '─'.repeat(60));
126
- if (result.success) {
127
- console.log('✓ Script completed successfully\n');
128
- if (result.data?.outputFile && result.data?.content) {
129
- writeFileSync(result.data.outputFile, result.data.content);
130
- console.log(`Output written to: ${result.data.outputFile}`);
131
- } else {
132
- console.log(JSON.stringify(result.data, null, 2));
133
- }
134
- } else {
135
- console.log(`✗ Script failed (${result.error})\n`);
136
- console.log(result.message);
137
- if (result.stack && result.error === 'script') console.log(`\nStack:\n${result.stack}`);
138
- if (result.debugDir) console.log(`\nDebug snapshot (screenshot, HTML, text): ${result.debugDir}`);
139
- }
140
- }
@@ -1,185 +0,0 @@
1
- /**
2
- * ctx.utils — generic, site-agnostic helpers for automation scripts.
3
- *
4
- * These are the patterns proven during the POC, promoted out of any single
5
- * script. Site-specific logic (dismissing a particular app's modals, parsing a
6
- * particular tool's output) stays in the script as step functions.
7
- *
8
- * Built per-run and bound to the live `page`/`logger`, so scripts call them as
9
- * `ctx.utils.foo(...)` without threading `page` through every call.
10
- */
11
-
12
- /**
13
- * Build the utils object for a run.
14
- *
15
- * @param {import('playwright').Page} page
16
- * @param {object} logger - The script's logger (a child is created per helper)
17
- * @returns {object} the ctx.utils surface
18
- */
19
- export function createUtils(page, logger) {
20
- const log = logger.child('utils');
21
-
22
- return {
23
- /**
24
- * RECOMMENDED way to handle modals/banners that appear at an unpredictable
25
- * time and would block later actions. Registers a Playwright locator handler:
26
- * whenever `triggerLocator` is present and blocks an action, `dismiss` runs.
27
- * This is fully timing-independent — no sleeps, no races. Prefer this over
28
- * `dismissModals` for anything that may appear asynchronously.
29
- *
30
- * @param {import('playwright').Locator} triggerLocator - The modal/overlay.
31
- * @param {(locator) => Promise<void>} dismiss - How to dismiss it.
32
- * @returns {Promise<void>}
33
- */
34
- async autoDismiss(triggerLocator, dismiss) {
35
- await page.addLocatorHandler(triggerLocator, dismiss);
36
- log.info('registered auto-dismiss handler');
37
- },
38
-
39
- /**
40
- * Best-effort one-shot dismissal of modals KNOWN to already be present. Clicks
41
- * the first of each candidate selector that currently exists; failures are
42
- * swallowed. Does NOT wait for modals that may appear later — use
43
- * `autoDismiss` for those. No fixed delays.
44
- *
45
- * @param {string[]} selectors
46
- * @param {object} [opts] - { timeout }
47
- * @returns {Promise<number>} count of elements clicked
48
- */
49
- async dismissModals(selectors, opts = {}) {
50
- const { timeout = 2000 } = opts;
51
- let dismissed = 0;
52
- for (const sel of selectors) {
53
- const el = await page.$(sel);
54
- if (!el) continue;
55
- try {
56
- await el.click({ timeout });
57
- dismissed++;
58
- } catch {
59
- // best-effort; modal may have already closed or be non-actionable
60
- }
61
- }
62
- if (dismissed) log.info(`dismissed ${dismissed} modal element(s)`);
63
- return dismissed;
64
- },
65
-
66
- /**
67
- * Click an element located by visible text (regex or string). Defaults to a
68
- * NON-forced click so Playwright auto-waits for the element to be actionable
69
- * (visible, stable, not covered) — the resilient default. Pass `force: true`
70
- * only for the rare element a component library (e.g. Blueprint.js) wrongly
71
- * reports as disabled, and verify the outcome afterward.
72
- *
73
- * @param {RegExp|string} text
74
- * @param {object} [opts] - { force, timeout }
75
- */
76
- async clickByText(text, opts = {}) {
77
- const { force = false, timeout = 15000 } = opts;
78
- const locator = page.getByText(text).first();
79
- await locator.waitFor({ timeout });
80
- await locator.click({ force });
81
- },
82
-
83
- /**
84
- * Wait for any element matching `text` (regex or string) to appear, without
85
- * clicking. Useful for asserting a list/view has rendered.
86
- *
87
- * @param {RegExp|string} text
88
- * @param {object} [opts] - { timeout }
89
- */
90
- async waitForText(text, opts = {}) {
91
- const { timeout = 15000 } = opts;
92
- await page.getByText(text).first().waitFor({ timeout });
93
- },
94
-
95
- /**
96
- * Try each selector in order; return the longest text content found among
97
- * all matches, or null if nothing exceeds `minLength`. Built for "find the
98
- * log/output blob on the page" cases where the exact container is unknown.
99
- *
100
- * @param {string[]} selectors
101
- * @param {object} [opts] - { minLength }
102
- * @returns {Promise<string|null>}
103
- */
104
- async extractLongestText(selectors, opts = {}) {
105
- const { minLength = 50 } = opts;
106
- for (const sel of selectors) {
107
- const els = await page.$$(sel);
108
- if (!els.length) continue;
109
- const texts = await Promise.all(els.map((el) => el.textContent()));
110
- const longest = texts
111
- .filter(Boolean)
112
- .map((t) => t.trim())
113
- .sort((a, b) => b.length - a.length)[0];
114
- if (longest && longest.length >= minLength) return longest;
115
- }
116
- return null;
117
- },
118
-
119
- /**
120
- * Extract the first match of a regex from page text (or supplied text).
121
- * Returns the trimmed match string, or null.
122
- *
123
- * @param {RegExp} pattern
124
- * @param {object} [opts] - { source: string, group: number }
125
- * @returns {Promise<string|null>}
126
- */
127
- async extractTextByPattern(pattern, opts = {}) {
128
- const { source = null, group = 0 } = opts;
129
- const haystack = source ?? (await page.textContent('body')) ?? '';
130
- const m = haystack.match(pattern);
131
- return m ? m[group].trim() : null;
132
- },
133
-
134
- /**
135
- * Capture a screenshot for debugging. No-op-safe: failures are logged, not
136
- * thrown, so a debug aid never breaks a run.
137
- *
138
- * @param {string} path - Absolute file path for the PNG
139
- * @param {object} [opts] - { fullPage }
140
- */
141
- async screenshot(path, opts = {}) {
142
- const { fullPage = true } = opts;
143
- try {
144
- await page.screenshot({ path, fullPage });
145
- log.info(`screenshot saved: ${path}`);
146
- } catch (err) {
147
- log.warn(`screenshot failed: ${err.message}`);
148
- }
149
- },
150
-
151
- /**
152
- * Scroll an element matching `selector` into view. Returns true if found.
153
- *
154
- * @param {string} selector
155
- */
156
- async scrollIntoView(selector) {
157
- const el = await page.$(selector);
158
- if (!el) return false;
159
- await el.scrollIntoViewIfNeeded().catch(() => {});
160
- return true;
161
- },
162
-
163
- /**
164
- * Retry an async function until it succeeds or attempts are exhausted.
165
- * Throws the last error on final failure.
166
- *
167
- * @param {Function} fn - async () => result
168
- * @param {object} [opts] - { attempts, delay, label }
169
- */
170
- async retry(fn, opts = {}) {
171
- const { attempts = 3, delay = 1000, label = 'operation' } = opts;
172
- let lastErr;
173
- for (let i = 1; i <= attempts; i++) {
174
- try {
175
- return await fn();
176
- } catch (err) {
177
- lastErr = err;
178
- log.warn(`${label} failed (attempt ${i}/${attempts}): ${err.message}`);
179
- if (i < attempts) await page.waitForTimeout(delay);
180
- }
181
- }
182
- throw lastErr;
183
- },
184
- };
185
- }
@@ -1,52 +0,0 @@
1
- ---
2
- name: create-automated-browser-task
3
- description: >
4
- YOU MUST load this skill when you need to create a new headless browser
5
- automation script — i.e. a browser task is required and no existing script
6
- performs it. Produces a convention-compliant script in the project's
7
- automation scripts directory.
8
- ---
9
-
10
- # Create an Automated Browser Task
11
-
12
- A thin action skill. The architecture, `ctx` API, and full conventions live in
13
- the parent topic skill; the agent performing this work MUST load
14
- `about-automated-browser-tasks`.
15
-
16
- ## Delegation
17
-
18
- Per the sub-agent delegation pattern
19
- (`~sous-shared/_partials/sub-agent-delegation.md`), writing and iterating on a script is
20
- delegated work: one Opus sub-agent writes it, runs it, and iterates against the
21
- real page until it works, then reports the script path and result. Give it the
22
- target URL, the data to extract, and the params wanted.
23
-
24
- ## Steps
25
-
26
- 1. **Confirm none exists.** Search the project's automation scripts directory for
27
- a script that already does this. If one is close, prefer
28
- `update-automated-browser-task` instead.
29
- 2. **Name the file** `verb-noun-qualifier.mjs` (e.g. `get-repo-ci-error.mjs`).
30
- 3. **Declare `meta`** — `name`, a descriptive `description`, and `params`. For each
31
- param set `required`, `default`, a genuinely descriptive `description`, and a
32
- `validate` rule (RegExp or function) with an `invalidMessage`. Pull shared
33
- values (URLs, resource IDs, profile) from `ctx.settings` rather than hardcoding.
34
- 4. **Write `execute(ctx)`** as a thin orchestrator that calls small, named step
35
- functions. Destructure off `ctx`/`params` at the top. Use `ctx.logger`, never
36
- `console.log`. Use `ctx.utils` for generic patterns; keep site-specific logic
37
- in step functions. Give EVERY function a full JSDoc block.
38
- 5. **Return a result object** (`found`, `content`, `outputFile`, a URL, `message`).
39
- 6. **Run it** to verify — see `running-automated-browser-tasks`. Iterate on the
40
- real page; do not guess selectors.
41
-
42
- ## Reference
43
-
44
- The exact param spec, `ctx.utils` surface, return shape, and worked examples are
45
- in the topic skill's references and `examples/`. Read them — do not improvise.
46
-
47
- ## Source for this Skill
48
-
49
- This skill was pulled from the `sous` project's "shared skills" library. It was compiled from a template and
50
- the output file should not be edited directly.
51
-
52
- - Source Path: {{ sousTemplatePath }}
@@ -1,59 +0,0 @@
1
- ---
2
- name: running-automated-browser-tasks
3
- description: >
4
- YOU MUST load this skill when you need to run an existing headless browser
5
- automation script for the user — to execute a task, fetch data from a site, or
6
- verify a script you just wrote or changed.
7
- ---
8
-
9
- # Run an Automated Browser Task
10
-
11
- A thin action skill. The agent performing this work MUST load
12
- `about-automated-browser-tasks` for the architecture and the meaning of `ctx`,
13
- auth handling, and result shapes.
14
-
15
- ## Delegation
16
-
17
- Per the sub-agent delegation pattern
18
- (`~sous-shared/_partials/sub-agent-delegation.md`), a browser run is a slow, multi-step
19
- execution: delegate it to a background sub-agent, which runs the command,
20
- interprets the outcome below, and reports the result plus any link or actionable
21
- message. Run a one-off script inline only when its result blocks the very next
22
- step.
23
-
24
- ## Invocation
25
-
26
- Run a script by its absolute path through the runner in this system's `scripts/`
27
- directory:
28
-
29
- ```bash
30
- node <scriptsDir>/run.mjs <absolute-path-to-script.mjs> [--param=value ...]
31
- ```
32
-
33
- - Pass script params as `--paramName=value`.
34
- - Harness options: `--profileName=<name>` (Chrome profile), `--timeout=<ms>`.
35
- - The runner loads compiled project `settings.mjs` automatically and prints the
36
- resolved params (marking which came from defaults/settings) before running.
37
-
38
- ## Reading the outcome
39
-
40
- - **Success** → `✓` and the result is printed; if the script returned an
41
- `outputFile` + `content`, the runner writes the file and reports the path.
42
- - **`error: params`** → a required param was missing or failed validation. Fix the
43
- invocation; do not edit the script to bypass validation.
44
- - **`error: auth`** → the session was redirected to a login page. The user must log
45
- in to the site in their Chrome profile, then the SAME command is re-run; no
46
- script change needed. A sub-agent cannot ask for this: return the actionable
47
- message and the link to the orchestrator, which relays them to the user and
48
- re-dispatches the run afterward.
49
- - **`error: script`** → an unexpected failure with a stack. Inspect, then use
50
- `update-automated-browser-task` to fix the root cause and re-run.
51
-
52
- Never silently swallow a failure or fake a result. Report what happened.
53
-
54
- ## Source for this Skill
55
-
56
- This skill was pulled from the `sous` project's "shared skills" library. It was compiled from a template and
57
- the output file should not be edited directly.
58
-
59
- - Source Path: {{ sousTemplatePath }}
@@ -1,47 +0,0 @@
1
- ---
2
- name: update-automated-browser-task
3
- description: >
4
- YOU MUST load this skill when you need to modify, fix, or extend an existing
5
- headless browser automation script — for example when a script breaks because
6
- a site's markup changed, or a new param/behavior is needed.
7
- ---
8
-
9
- # Update an Automated Browser Task
10
-
11
- A thin action skill. The agent performing this work MUST load
12
- `about-automated-browser-tasks` for the architecture, `ctx` API, and full
13
- conventions.
14
-
15
- ## Delegation
16
-
17
- Per the sub-agent delegation pattern
18
- (`~sous-shared/_partials/sub-agent-delegation.md`), the fix-and-re-run loop below
19
- is delegated to one Opus sub-agent, which reports what broke, what it changed, and the verified
20
- result. Give it the script path and the failing run output.
21
-
22
- ## Steps
23
-
24
- 1. **Read the script** and the run output. Identify the failing step from the
25
- logger prefixes (`[script:section]`) in the output.
26
- 2. **Reproduce** by running it (see `running-automated-browser-tasks`). For
27
- selector/markup failures, inspect the live page — use `ctx.utils.screenshot`
28
- or widen selectors; do not guess.
29
- 3. **Make the smallest correct change.** Fix the root cause, not the symptom. Keep
30
- the existing decomposition: edit the relevant step function, add a new one if a
31
- new discrete action is needed.
32
- 4. **Preserve conventions.** Maintain JSDoc blocks, `ctx`/`params` destructuring,
33
- `ctx.logger` usage, and `meta.params` validation. If you add a param, give it a
34
- description and a `validate` rule.
35
- 5. **Re-run to verify** the fix end-to-end.
36
-
37
- ## When NOT to update
38
-
39
- If the change amounts to a different task, create a new script instead
40
- (`create-automated-browser-task`). Keep each script focused on one job.
41
-
42
- ## Source for this Skill
43
-
44
- This skill was pulled from the `sous` project's "shared skills" library. It was compiled from a template and
45
- the output file should not be edited directly.
46
-
47
- - Source Path: {{ sousTemplatePath }}
@@ -1,26 +0,0 @@
1
- ---
2
- name: approve
3
- description: Approve the current plan and instruct Claude to proceed with maximum parallelism.
4
- disable-model-invocation: true
5
- ---
6
-
7
- Your plan looks good, and I approve.
8
-
9
- While executing the plan, maximize parallelism:
10
- - Delegate the steps that have any complexity to background sub-agents, per the sub-agent
11
- delegation pattern (`~sous-shared/_partials/sub-agent-delegation.md`), batching independent
12
- dispatches into one message.
13
- - Batch independent investigations (searches, file reads, greps, listings) into concurrent tool
14
- calls whenever practical.
15
- - Keep dependent steps sequential (especially edits, formatting/linting, and tests that rely on
16
- prior changes).
17
-
18
- Use the best-fit tools for the job. If you can't parallelize a step, say why briefly and proceed
19
- sequentially.
20
-
21
- ## Source for this Skill
22
-
23
- This skill was pulled from the `sous` project's "shared skills" library. It was compiled from a
24
- template and the output file should not be edited directly.
25
-
26
- - Source Path: {{ sousTemplatePath }}