@plainconceptsplatform/agent-harness 2.3.0 → 2.4.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 (155) hide show
  1. package/README.md +23 -12
  2. package/{src → cli}/commands/join.js +3 -4
  3. package/{src → cli}/commands/migrate.js +1 -1
  4. package/{src → cli}/commands/wizard.js +1 -1
  5. package/{src → cli}/index.js +1 -1
  6. package/{src → cli}/presets/agents-content.json +4 -4
  7. package/cli/presets/browser.json +10 -0
  8. package/{src → cli}/presets/clean.json +21 -21
  9. package/{src → cli}/presets/optimization.json +37 -37
  10. package/{src → cli}/presets/platforms.json +76 -76
  11. package/{src → cli}/presets/quota.json +16 -16
  12. package/{src → cli}/presets/source.json +23 -23
  13. package/cli/steps/browser/index.js +48 -0
  14. package/{src → cli}/steps/clean/index.js +120 -120
  15. package/{src → cli}/steps/copy/agents.js +119 -119
  16. package/{src → cli}/steps/copy/commands.js +95 -95
  17. package/{src → cli}/steps/copy/fullstack-engineer.js +89 -88
  18. package/{src → cli}/steps/copy/index.js +1 -1
  19. package/{src → cli}/steps/copy/opencode-json.js +196 -161
  20. package/{src → cli}/steps/copy/skills.js +2 -2
  21. package/{src → cli}/steps/models/format.js +88 -88
  22. package/{src → cli}/steps/models/index.js +64 -64
  23. package/{src → cli}/steps/openspec/index.js +136 -136
  24. package/{src → cli}/steps/optimization/codegraph.js +127 -127
  25. package/{src → cli}/steps/optimization/detect.js +66 -66
  26. package/{src → cli}/steps/optimization/humanizer.js +17 -17
  27. package/{src → cli}/steps/optimization/index.js +163 -163
  28. package/{src → cli}/steps/optimization/memory.js +88 -88
  29. package/{src → cli}/steps/optimization/patch-guardrails.js +109 -109
  30. package/{src → cli}/steps/optimization/quota.js +119 -119
  31. package/{src → cli}/steps/optimization/simple-english.js +17 -17
  32. package/{src → cli}/steps/optimization/skills-lock.js +30 -30
  33. package/{src → cli}/steps/platform/index.js +109 -109
  34. package/{src → cli}/steps/source/index.js +123 -123
  35. package/{src → cli}/utils/agent-color.js +111 -83
  36. package/{src → cli}/utils/exec-spinner.js +47 -47
  37. package/{src → cli}/utils/exec.js +134 -134
  38. package/{src → cli}/utils/models-pricing.js +42 -42
  39. package/{src → cli}/utils/paths.js +1 -1
  40. package/{src → cli}/utils/process.js +3 -3
  41. package/{src → cli}/utils/terminal.js +6 -6
  42. package/harness/.agents/skills/browser-automation/SKILL.md +72 -0
  43. package/{src/content → harness}/.agents/skills/pc-make-engineer/SKILL.md +219 -219
  44. package/{src/content → harness}/.agents/skills/pc-make-engineer/template.md +80 -80
  45. package/{src/content → harness}/.agents/skills/pc-plan-archive/SKILL.md +3 -0
  46. package/{src/content → harness}/.agents/skills/pc-plan-explore/SKILL.md +3 -0
  47. package/{src/content → harness}/.agents/skills/pc-plan-goal/SKILL.md +5 -16
  48. package/{src/content → harness}/.agents/skills/pc-plan-goal/branching.md +1 -1
  49. package/{src/content → harness}/.agents/skills/pc-plan-goal/output.md +5 -8
  50. package/{src/content → harness}/.agents/skills/pc-plan-story/SKILL.md +3 -0
  51. package/{src/content → harness}/.agents/skills/pc-userstory-browser/SKILL.md +136 -132
  52. package/{src/content → harness}/.opencode/package.json +9 -10
  53. package/{src/content → harness}/.opencode/plugins/pc-subagent-tiers.js +368 -337
  54. package/{src/content → harness}/ARCHITECTURE.md +16 -16
  55. package/{src/content → harness}/DESIGN.md +16 -16
  56. package/{src/content → harness}/opencode.jsonc +47 -41
  57. package/{src/content → harness}/openspec/config.yaml +20 -20
  58. package/{src/content → harness}/skills-lock.json +17 -17
  59. package/package.json +7 -7
  60. package/src/content/.agents/skills/browser-automation/SKILL.md +0 -66
  61. package/src/presets/browser.json +0 -22
  62. package/src/steps/browser/index.js +0 -91
  63. /package/{src → cli}/commands/shared.js +0 -0
  64. /package/{src → cli}/commands/single.js +0 -0
  65. /package/{src → cli}/commands/update.js +0 -0
  66. /package/{src → cli}/fragments/archive/az.md +0 -0
  67. /package/{src → cli}/fragments/archive/gh.md +0 -0
  68. /package/{src → cli}/fragments/archive/gl.md +0 -0
  69. /package/{src → cli}/fragments/archive/none.md +0 -0
  70. /package/{src → cli}/fragments/guardrails/codegraph.md +0 -0
  71. /package/{src → cli}/fragments/guardrails/humanizer.md +0 -0
  72. /package/{src → cli}/fragments/guardrails/memory.md +0 -0
  73. /package/{src → cli}/fragments/guardrails/rtk.md +0 -0
  74. /package/{src → cli}/fragments/guardrails/simple-english.md +0 -0
  75. /package/{src → cli}/fragments/ops-backlog/az.md +0 -0
  76. /package/{src → cli}/fragments/ops-backlog/gh.md +0 -0
  77. /package/{src → cli}/fragments/ops-backlog/jira.md +0 -0
  78. /package/{src → cli}/fragments/ops-evidence/az.md +0 -0
  79. /package/{src → cli}/fragments/ops-evidence/gh.md +0 -0
  80. /package/{src → cli}/fragments/ops-evidence/jira.md +0 -0
  81. /package/{src → cli}/fragments/ops-review/az.md +0 -0
  82. /package/{src → cli}/fragments/ops-review/gh.md +0 -0
  83. /package/{src → cli}/fragments/ops-review/gl.md +0 -0
  84. /package/{src → cli}/fragments/ops-ship/az.md +0 -0
  85. /package/{src → cli}/fragments/ops-ship/gh.md +0 -0
  86. /package/{src → cli}/fragments/ops-ship/gl.md +0 -0
  87. /package/{src → cli}/presets/models.json +0 -0
  88. /package/{src → cli}/presets/openspec.json +0 -0
  89. /package/{src → cli}/steps/metadata/index.js +0 -0
  90. /package/{src → cli}/steps/models/write.js +0 -0
  91. /package/{src → cli}/utils/copy.js +0 -0
  92. /package/{src → cli}/utils/legacy-check.js +0 -0
  93. /package/{src → cli}/utils/models-cache.js +0 -0
  94. /package/{src → cli}/utils/update-manifest.js +0 -0
  95. /package/{src/content → harness}/.agents/skills/pc-guardrails-generic/SKILL.md +0 -0
  96. /package/{src/content → harness}/.agents/skills/pc-guardrails-project/SKILL.md +0 -0
  97. /package/{src/content → harness}/.agents/skills/pc-make-architecture/SKILL.md +0 -0
  98. /package/{src/content → harness}/.agents/skills/pc-make-architecture/structure-template.md +0 -0
  99. /package/{src/content → harness}/.agents/skills/pc-make-design/SKILL.md +0 -0
  100. /package/{src/content → harness}/.agents/skills/pc-make-engineer/signal-mapping.md +0 -0
  101. /package/{src/content → harness}/.agents/skills/pc-make-evidence-scaffold/SKILL.md +0 -0
  102. /package/{src/content → harness}/.agents/skills/pc-make-evidence-scaffold/evidence-contract.md +0 -0
  103. /package/{src/content → harness}/.agents/skills/pc-make-guardrails/SKILL.md +0 -0
  104. /package/{src/content → harness}/.agents/skills/pc-make-guardrails/category-reference.md +0 -0
  105. /package/{src/content → harness}/.agents/skills/pc-make-merge-risk-assess/SKILL.md +0 -0
  106. /package/{src/content → harness}/.agents/skills/pc-make-merge-risk-assess/category-reference.md +0 -0
  107. /package/{src/content → harness}/.agents/skills/pc-make-user-model/SKILL.md +0 -0
  108. /package/{src/content → harness}/.agents/skills/pc-ops-evidence/SKILL.md +0 -0
  109. /package/{src/content → harness}/.agents/skills/pc-ops-ship/SKILL.md +0 -0
  110. /package/{src/content → harness}/.agents/skills/pc-plan-apply/SKILL.md +0 -0
  111. /package/{src/content → harness}/.agents/skills/pc-plan-apply/simple-mode.md +0 -0
  112. /package/{src/content → harness}/.agents/skills/pc-plan-goal/failure-policy.md +0 -0
  113. /package/{src/content → harness}/.agents/skills/pc-plan-goal/output-mode.md +0 -0
  114. /package/{src/content → harness}/.agents/skills/pc-plan-propose/SKILL.md +0 -0
  115. /package/{src/content → harness}/.agents/skills/pc-plan-propose/task-annotation.md +0 -0
  116. /package/{src/content → harness}/.agents/skills/pc-plan-quick/SKILL.md +0 -0
  117. /package/{src/content → harness}/.agents/skills/pc-repo-audit/SKILL.md +0 -0
  118. /package/{src/content → harness}/.agents/skills/pc-repo-help/SKILL.md +0 -0
  119. /package/{src/content → harness}/.agents/skills/pc-repo-initialize/SKILL.md +0 -0
  120. /package/{src/content → harness}/.agents/skills/pc-repo-onboard/SKILL.md +0 -0
  121. /package/{src/content → harness}/.agents/skills/pc-repo-verify/SKILL.md +0 -0
  122. /package/{src/content → harness}/.agents/skills/pc-userstory-az/SKILL.md +0 -0
  123. /package/{src/content → harness}/.agents/skills/pc-userstory-gh/SKILL.md +0 -0
  124. /package/{src/content → harness}/.agents/skills/pc-userstory-jira/SKILL.md +0 -0
  125. /package/{src/content → harness}/.opencode/_gitignore +0 -0
  126. /package/{src/content → harness}/.opencode/commands/init.md +0 -0
  127. /package/{src/content → harness}/.opencode/commands/make-architecture.md +0 -0
  128. /package/{src/content → harness}/.opencode/commands/make-design.md +0 -0
  129. /package/{src/content → harness}/.opencode/commands/make-engineer.md +0 -0
  130. /package/{src/content → harness}/.opencode/commands/make-evidence-scaffold.md +0 -0
  131. /package/{src/content → harness}/.opencode/commands/make-guardrails.md +0 -0
  132. /package/{src/content → harness}/.opencode/commands/make-user-model.md +0 -0
  133. /package/{src/content → harness}/.opencode/commands/ops-backlog.md +0 -0
  134. /package/{src/content → harness}/.opencode/commands/ops-evidence.md +0 -0
  135. /package/{src/content → harness}/.opencode/commands/ops-review.md +0 -0
  136. /package/{src/content → harness}/.opencode/commands/ops-ship.md +0 -0
  137. /package/{src/content → harness}/.opencode/commands/plan-apply.md +0 -0
  138. /package/{src/content → harness}/.opencode/commands/plan-archive.md +0 -0
  139. /package/{src/content → harness}/.opencode/commands/plan-explore.md +0 -0
  140. /package/{src/content → harness}/.opencode/commands/plan-goal.md +0 -0
  141. /package/{src/content → harness}/.opencode/commands/plan-propose.md +0 -0
  142. /package/{src/content → harness}/.opencode/commands/plan-quick.md +0 -0
  143. /package/{src/content → harness}/.opencode/commands/plan-story.md +0 -0
  144. /package/{src/content → harness}/.opencode/commands/repo-audit.md +0 -0
  145. /package/{src/content → harness}/.opencode/commands/repo-help.md +0 -0
  146. /package/{src/content → harness}/.opencode/commands/repo-initialize.md +0 -0
  147. /package/{src/content → harness}/.opencode/commands/repo-onboard.md +0 -0
  148. /package/{src/content → harness}/.opencode/commands/repo-verify.md +0 -0
  149. /package/{src/content → harness}/.opencode/plugins/pc-subagent-monitor.js +0 -0
  150. /package/{src/content → harness}/.opencode/plugins/pc-system-reminders.js +0 -0
  151. /package/{src/content → harness}/.opencode/tui/pc-subagents.tsx +0 -0
  152. /package/{src/content → harness}/.opencode/tui.json +0 -0
  153. /package/{src/content → harness}/AGENTS.md +0 -0
  154. /package/{src/content → harness}/openspec/changes/archive/.gitkeep +0 -0
  155. /package/{src/content → harness}/openspec/specs/.gitkeep +0 -0
@@ -1,83 +1,111 @@
1
- /**
2
- * Deterministic colour for an agent, derived from its name.
3
- *
4
- * opencode accepts either a hex value or one of its theme keywords. Two of
5
- * those keywords are reserved for the primaries so they stay recognisable
6
- * across every project: build is `primary`, plan is `warning`. Every other
7
- * agent gets a hex derived from its name, so a `butterfly-engineer` is the
8
- * same colour in every repository that has one, with no central registry and
9
- * no chance of two agents in one project colliding by accident.
10
- *
11
- * Saturation and lightness are fixed rather than hashed. That is what keeps
12
- * the result usable: hashing them too would eventually produce near-grey,
13
- * near-black or near-white agents that are invisible against one theme or the
14
- * other. Only the hue varies.
15
- */
16
-
17
- /** opencode's theme keywords. Treated as unset so a name-derived hex wins. */
18
- export const THEME_COLORS = new Set([
19
- 'primary',
20
- 'secondary',
21
- 'accent',
22
- 'success',
23
- 'warning',
24
- 'error',
25
- 'info',
26
- ])
27
-
28
- /** Reserved: the two primaries, which never take a derived colour. */
29
- export const RESERVED_COLORS = { build: 'primary', plan: 'warning' }
30
-
31
- const SATURATION = 68
32
- const LIGHTNESS = 52
33
-
34
- /**
35
- * FNV-1a, 32 bit. Chosen because it is short enough to duplicate verbatim in
36
- * the shipped plugin and produces the same hue everywhere: the same name must
37
- * resolve to the same colour on every machine and in every project.
38
- */
39
- function hash(value) {
40
- let h = 0x811c9dc5
41
- for (let i = 0; i < value.length; i++) {
42
- h ^= value.charCodeAt(i)
43
- h = Math.imul(h, 0x01000193) >>> 0
44
- }
45
- return h >>> 0
46
- }
47
-
48
- function hslToHex(h, s, l) {
49
- const sat = s / 100
50
- const light = l / 100
51
- const c = (1 - Math.abs(2 * light - 1)) * sat
52
- const x = c * (1 - Math.abs(((h / 60) % 2) - 1))
53
- const m = light - c / 2
54
-
55
- const [r, g, b] =
56
- h < 60 ? [c, x, 0] :
57
- h < 120 ? [x, c, 0] :
58
- h < 180 ? [0, c, x] :
59
- h < 240 ? [0, x, c] :
60
- h < 300 ? [x, 0, c] :
61
- [c, 0, x]
62
-
63
- const toHex = v => Math.round((v + m) * 255).toString(16).padStart(2, '0')
64
- return `#${toHex(r)}${toHex(g)}${toHex(b)}`.toUpperCase()
65
- }
66
-
67
- /** The colour for an agent, honouring the two reserved primaries. */
68
- export function agentColor(name) {
69
- if (name in RESERVED_COLORS) return RESERVED_COLORS[name]
70
- return hslToHex(hash(name) % 360, SATURATION, LIGHTNESS)
71
- }
72
-
73
- /**
74
- * Whether a frontmatter colour should be replaced by the derived one.
75
- *
76
- * A theme keyword is replaced: those came from the old template, which asked
77
- * whoever ran /make-engineer to pick one and avoid collisions by hand. An
78
- * explicit hex is left alone, because someone chose it deliberately.
79
- */
80
- export function shouldDeriveColor(current) {
81
- if (!current) return true
82
- return THEME_COLORS.has(current.trim().toLowerCase())
83
- }
1
+ /**
2
+ * Deterministic colour for an agent, derived from its name.
3
+ *
4
+ * opencode accepts either a hex value or one of its theme keywords. Two of
5
+ * those keywords are reserved for the primaries so they stay recognisable
6
+ * across every project: build is `primary`, plan is `warning`. Every other
7
+ * agent gets a hex derived from its name, so a `butterfly-engineer` is the
8
+ * same colour in every repository that has one, with no central registry and
9
+ * no chance of two agents in one project colliding by accident.
10
+ *
11
+ * Saturation and lightness are fixed rather than hashed. That is what keeps
12
+ * the result usable: hashing them too would eventually produce near-grey,
13
+ * near-black or near-white agents that are invisible against one theme or the
14
+ * other. Only the hue varies.
15
+ */
16
+
17
+ /** opencode's theme keywords. Treated as unset so a name-derived hex wins. */
18
+ export const THEME_COLORS = new Set([
19
+ 'primary',
20
+ 'secondary',
21
+ 'accent',
22
+ 'success',
23
+ 'warning',
24
+ 'error',
25
+ 'info',
26
+ ])
27
+
28
+ /** Reserved: the two primaries, which never take a derived colour. */
29
+ export const RESERVED_COLORS = { build: 'primary', plan: 'warning' }
30
+
31
+ const SATURATION = 68
32
+ const LIGHTNESS = 52
33
+
34
+ /**
35
+ * FNV-1a, 32 bit. Chosen because it is short enough to duplicate verbatim in
36
+ * the shipped plugin and produces the same hue everywhere: the same name must
37
+ * resolve to the same colour on every machine and in every project.
38
+ */
39
+ function hash(value) {
40
+ let h = 0x811c9dc5
41
+ for (let i = 0; i < value.length; i++) {
42
+ h ^= value.charCodeAt(i)
43
+ h = Math.imul(h, 0x01000193) >>> 0
44
+ }
45
+ return h >>> 0
46
+ }
47
+
48
+ function hslToHex(h, s, l) {
49
+ const sat = s / 100
50
+ const light = l / 100
51
+ const c = (1 - Math.abs(2 * light - 1)) * sat
52
+ const x = c * (1 - Math.abs(((h / 60) % 2) - 1))
53
+ const m = light - c / 2
54
+
55
+ const [r, g, b] =
56
+ h < 60 ? [c, x, 0] :
57
+ h < 120 ? [x, c, 0] :
58
+ h < 180 ? [0, c, x] :
59
+ h < 240 ? [0, x, c] :
60
+ h < 300 ? [x, 0, c] :
61
+ [c, 0, x]
62
+
63
+ const toHex = v => Math.round((v + m) * 255).toString(16).padStart(2, '0')
64
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`.toUpperCase()
65
+ }
66
+
67
+ /** The colour for an agent, honouring the two reserved primaries. */
68
+ export function agentColor(name) {
69
+ if (name in RESERVED_COLORS) return RESERVED_COLORS[name]
70
+ return hslToHex(hash(name) % 360, SATURATION, LIGHTNESS)
71
+ }
72
+
73
+ /**
74
+ * Render a colour for YAML frontmatter.
75
+ *
76
+ * A hex has to be quoted. `#` opens a comment in YAML, so `color: #D83155`
77
+ * parses as null and opencode rejects the agent outright with "Invalid input
78
+ * color". Theme keywords need no quoting and are left bare.
79
+ */
80
+ export function yamlColor(value) {
81
+ return value.startsWith('#') ? `"${value}"` : value
82
+ }
83
+
84
+ /** The value of a frontmatter colour, with any quoting removed. */
85
+ export function unquoteColor(raw) {
86
+ return (raw ?? '').trim().replace(/^["']/, '').replace(/["']$/, '')
87
+ }
88
+
89
+ /**
90
+ * The frontmatter colour line an agent should have, or null when the current
91
+ * one is already correct. Covers both jobs: choosing the colour, and quoting it
92
+ * so the file actually parses.
93
+ */
94
+ export function colorLineFor(name, rawCurrent) {
95
+ const current = unquoteColor(rawCurrent)
96
+ const target = shouldDeriveColor(current) ? agentColor(name) : current
97
+ const rendered = yamlColor(target)
98
+ return (rawCurrent ?? '').trim() === rendered ? null : `color: ${rendered}`
99
+ }
100
+
101
+ /**
102
+ * Whether a frontmatter colour should be replaced by the derived one.
103
+ *
104
+ * A theme keyword is replaced: those came from the old template, which asked
105
+ * whoever ran /make-engineer to pick one and avoid collisions by hand. An
106
+ * explicit hex is left alone, because someone chose it deliberately.
107
+ */
108
+ export function shouldDeriveColor(current) {
109
+ if (!current) return true
110
+ return THEME_COLORS.has(current.trim().toLowerCase())
111
+ }
@@ -1,47 +1,47 @@
1
- import chalk from 'chalk'
2
- import ora from 'ora'
3
-
4
- // ── Screen / step state ──────────────────────────────────────────────────────
5
-
6
- const previousSteps = []; // up to 2 completed steps, each is an array of lines
7
- let currentStepLines = []; // lines accumulated in the current step
8
- let stepSpinner = null; // ora spinner shown while step is working
9
-
10
- export function appendLine(line) {
11
- currentStepLines.push(line);
12
- }
13
-
14
- export function stopSpinner() {
15
- if (stepSpinner) {
16
- stepSpinner.stop();
17
- stepSpinner = null;
18
- }
19
- }
20
-
21
- export function startSpinner(text = 'working...') {
22
- stopSpinner();
23
- stepSpinner = ora({ text: chalk.dim(text), color: 'red' }).start();
24
- }
25
-
26
- export function redraw() {
27
- if (process.stdout.isTTY) console.clear();
28
-
29
- // Show up to 2 previous steps dimmed
30
- for (const stepLines of previousSteps) {
31
- for (const line of stepLines) {
32
- process.stdout.write(`${chalk.dim(line)}\n`);
33
- }
34
- process.stdout.write('\n');
35
- }
36
-
37
- // Current step output
38
- for (const line of currentStepLines) {
39
- process.stdout.write(`${line}\n`);
40
- }
41
- }
42
-
43
- export function rotateStep() {
44
- previousSteps.push(currentStepLines);
45
- if (previousSteps.length > 2) previousSteps.shift();
46
- currentStepLines = [];
47
- }
1
+ import chalk from 'chalk'
2
+ import ora from 'ora'
3
+
4
+ // ── Screen / step state ──────────────────────────────────────────────────────
5
+
6
+ const previousSteps = []; // up to 2 completed steps, each is an array of lines
7
+ let currentStepLines = []; // lines accumulated in the current step
8
+ let stepSpinner = null; // ora spinner shown while step is working
9
+
10
+ export function appendLine(line) {
11
+ currentStepLines.push(line);
12
+ }
13
+
14
+ export function stopSpinner() {
15
+ if (stepSpinner) {
16
+ stepSpinner.stop();
17
+ stepSpinner = null;
18
+ }
19
+ }
20
+
21
+ export function startSpinner(text = 'working...') {
22
+ stopSpinner();
23
+ stepSpinner = ora({ text: chalk.dim(text), color: 'red' }).start();
24
+ }
25
+
26
+ export function redraw() {
27
+ if (process.stdout.isTTY) console.clear();
28
+
29
+ // Show up to 2 previous steps dimmed
30
+ for (const stepLines of previousSteps) {
31
+ for (const line of stepLines) {
32
+ process.stdout.write(`${chalk.dim(line)}\n`);
33
+ }
34
+ process.stdout.write('\n');
35
+ }
36
+
37
+ // Current step output
38
+ for (const line of currentStepLines) {
39
+ process.stdout.write(`${line}\n`);
40
+ }
41
+ }
42
+
43
+ export function rotateStep() {
44
+ previousSteps.push(currentStepLines);
45
+ if (previousSteps.length > 2) previousSteps.shift();
46
+ currentStepLines = [];
47
+ }
@@ -1,134 +1,134 @@
1
- import chalk from 'chalk'
2
- import { execa } from 'execa'
3
- import ora from 'ora'
4
- import { appendLine, redraw, rotateStep, startSpinner, stopSpinner } from './exec-spinner.js'
5
- import { MARKERS } from './terminal.js';
6
-
7
- // ── Public API ───────────────────────────────────────────────────────────────
8
-
9
- /**
10
- * Run a shell command with a spinner.
11
- * Returns { success, stdout, stderr }
12
- */
13
- export async function run(command, args = [], { label, cwd = process.cwd() } = {}) {
14
- const spinner = ora(label ?? `${command} ${args.join(' ')}`).start();
15
- try {
16
- const result = await execa(command, args, { cwd, reject: false });
17
- if (result.exitCode === 0) {
18
- spinner.succeed();
19
- } else {
20
- spinner.fail();
21
- }
22
- return { success: result.exitCode === 0, stdout: result.stdout, stderr: result.stderr };
23
- } catch (err) {
24
- spinner.fail();
25
- return { success: false, stdout: '', stderr: err.message };
26
- }
27
- }
28
-
29
- /**
30
- * Check if a command is available on PATH.
31
- * Returns true/false.
32
- */
33
- export async function commandExists(command) {
34
- try {
35
- const result = await execa(command, ['--version'], { reject: false });
36
- return result.exitCode === 0;
37
- } catch {
38
- return false;
39
- }
40
- }
41
-
42
- /**
43
- * Print a section header, clears screen, shows previous step dimmed, starts new step.
44
- */
45
- export function header(text) {
46
- rotateStep();
47
-
48
- const line1 = '';
49
- const line2 = chalk.bold.hex('#fe3d57')(`━━ ${text}`);
50
- const line3 = '';
51
-
52
- appendLine(line1);
53
- appendLine(line2);
54
- appendLine(line3);
55
-
56
- redraw();
57
-
58
- startSpinner('working...');
59
- }
60
-
61
- /**
62
- * Restart the step spinner after prompts or logs.
63
- */
64
- export function loading(text = 'working...') {
65
- startSpinner(text);
66
- }
67
-
68
- /**
69
- * Print a success line.
70
- */
71
- export function success(text) {
72
- stopSpinner();
73
- const line = chalk.green(`${MARKERS.OK_PREFIX}${text}`);
74
- appendLine(line);
75
- console.log(line);
76
- }
77
-
78
- /**
79
- * Print a warning line.
80
- */
81
- export function warn(text) {
82
- stopSpinner();
83
- const line = chalk.yellow(`${MARKERS.WARN_PREFIX}${text}`);
84
- appendLine(line);
85
- console.log(line);
86
- }
87
-
88
- /**
89
- * Print an error line.
90
- */
91
- export function error(text) {
92
- stopSpinner();
93
- const line = chalk.red(`${MARKERS.ERROR_PREFIX}${text}`);
94
- appendLine(line);
95
- console.log(line);
96
- }
97
-
98
- /**
99
- * Print an info line.
100
- */
101
- export function info(text) {
102
- stopSpinner();
103
- const line = chalk.dim(`${MARKERS.EMPTY}${text}`);
104
- appendLine(line);
105
- console.log(line);
106
- }
107
-
108
- /**
109
- * Print an action prompt line (white bold, requires user interaction).
110
- */
111
- export function prompt(text) {
112
- stopSpinner();
113
- const line = chalk.bold(`${MARKERS.EMPTY}${text}`);
114
- appendLine(line);
115
- console.log(line);
116
- }
117
-
118
- /**
119
- * Print a code block.
120
- */
121
- export function code(lines) {
122
- stopSpinner();
123
- appendLine('');
124
- console.log();
125
- for (const line of lines) {
126
- const formatted = chalk.bgGray.white(
127
- `${MARKERS.EMPTY}${line}${MARKERS.EMPTY}`,
128
- );
129
- appendLine(formatted);
130
- console.log(formatted);
131
- }
132
- appendLine('');
133
- console.log();
134
- }
1
+ import chalk from 'chalk'
2
+ import { execa } from 'execa'
3
+ import ora from 'ora'
4
+ import { appendLine, redraw, rotateStep, startSpinner, stopSpinner } from './exec-spinner.js'
5
+ import { MARKERS } from './terminal.js';
6
+
7
+ // ── Public API ───────────────────────────────────────────────────────────────
8
+
9
+ /**
10
+ * Run a shell command with a spinner.
11
+ * Returns { success, stdout, stderr }
12
+ */
13
+ export async function run(command, args = [], { label, cwd = process.cwd() } = {}) {
14
+ const spinner = ora(label ?? `${command} ${args.join(' ')}`).start();
15
+ try {
16
+ const result = await execa(command, args, { cwd, reject: false });
17
+ if (result.exitCode === 0) {
18
+ spinner.succeed();
19
+ } else {
20
+ spinner.fail();
21
+ }
22
+ return { success: result.exitCode === 0, stdout: result.stdout, stderr: result.stderr };
23
+ } catch (err) {
24
+ spinner.fail();
25
+ return { success: false, stdout: '', stderr: err.message };
26
+ }
27
+ }
28
+
29
+ /**
30
+ * Check if a command is available on PATH.
31
+ * Returns true/false.
32
+ */
33
+ export async function commandExists(command) {
34
+ try {
35
+ const result = await execa(command, ['--version'], { reject: false });
36
+ return result.exitCode === 0;
37
+ } catch {
38
+ return false;
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Print a section header, clears screen, shows previous step dimmed, starts new step.
44
+ */
45
+ export function header(text) {
46
+ rotateStep();
47
+
48
+ const line1 = '';
49
+ const line2 = chalk.bold.hex('#fe3d57')(`━━ ${text}`);
50
+ const line3 = '';
51
+
52
+ appendLine(line1);
53
+ appendLine(line2);
54
+ appendLine(line3);
55
+
56
+ redraw();
57
+
58
+ startSpinner('working...');
59
+ }
60
+
61
+ /**
62
+ * Restart the step spinner after prompts or logs.
63
+ */
64
+ export function loading(text = 'working...') {
65
+ startSpinner(text);
66
+ }
67
+
68
+ /**
69
+ * Print a success line.
70
+ */
71
+ export function success(text) {
72
+ stopSpinner();
73
+ const line = chalk.green(`${MARKERS.OK_PREFIX}${text}`);
74
+ appendLine(line);
75
+ console.log(line);
76
+ }
77
+
78
+ /**
79
+ * Print a warning line.
80
+ */
81
+ export function warn(text) {
82
+ stopSpinner();
83
+ const line = chalk.yellow(`${MARKERS.WARN_PREFIX}${text}`);
84
+ appendLine(line);
85
+ console.log(line);
86
+ }
87
+
88
+ /**
89
+ * Print an error line.
90
+ */
91
+ export function error(text) {
92
+ stopSpinner();
93
+ const line = chalk.red(`${MARKERS.ERROR_PREFIX}${text}`);
94
+ appendLine(line);
95
+ console.log(line);
96
+ }
97
+
98
+ /**
99
+ * Print an info line.
100
+ */
101
+ export function info(text) {
102
+ stopSpinner();
103
+ const line = chalk.dim(`${MARKERS.EMPTY}${text}`);
104
+ appendLine(line);
105
+ console.log(line);
106
+ }
107
+
108
+ /**
109
+ * Print an action prompt line (white bold, requires user interaction).
110
+ */
111
+ export function prompt(text) {
112
+ stopSpinner();
113
+ const line = chalk.bold(`${MARKERS.EMPTY}${text}`);
114
+ appendLine(line);
115
+ console.log(line);
116
+ }
117
+
118
+ /**
119
+ * Print a code block.
120
+ */
121
+ export function code(lines) {
122
+ stopSpinner();
123
+ appendLine('');
124
+ console.log();
125
+ for (const line of lines) {
126
+ const formatted = chalk.bgGray.white(
127
+ `${MARKERS.EMPTY}${line}${MARKERS.EMPTY}`,
128
+ );
129
+ appendLine(formatted);
130
+ console.log(formatted);
131
+ }
132
+ appendLine('');
133
+ console.log();
134
+ }
@@ -1,42 +1,42 @@
1
- // Providers considered "canonical" for reference pricing, in priority order.
2
- // When a model's own provider has no cost (e.g. github-copilot shows $0),
3
- // we look up the same model name in these providers and attach canonicalCost.
4
- export const CANONICAL_PROVIDERS = ['anthropic', 'openai', 'google', 'mistral', 'meta', 'cohere'];
5
-
6
- export function parseModels(data) {
7
- // Build name → canonical cost lookup from authoritative providers first
8
- const canonicalCostByName = new Map();
9
- for (const providerId of CANONICAL_PROVIDERS) {
10
- const provider = data[providerId];
11
- if (!provider?.models) continue;
12
- for (const model of Object.values(provider.models)) {
13
- if (!model.tool_call) continue;
14
- const name = model.name;
15
- if (name && model.cost?.input !== undefined && !canonicalCostByName.has(name)) {
16
- canonicalCostByName.set(name, model.cost.input);
17
- }
18
- }
19
- }
20
-
21
- const models = [];
22
- for (const [providerId, provider] of Object.entries(data)) {
23
- if (!provider.models) continue;
24
- for (const [modelId, model] of Object.entries(provider.models)) {
25
- if (!model.tool_call) continue;
26
- const name = model.name || modelId;
27
- const cost = model.cost?.input;
28
- const canonicalCost = canonicalCostByName.get(name);
29
- models.push({
30
- id: `${providerId}/${modelId}`,
31
- name,
32
- cost,
33
- // canonicalCost: cost from the authoritative provider for this model name.
34
- // Defined when cost !== canonicalCost (different provider, reseller, or $0 subscription).
35
- canonicalCost: canonicalCost !== undefined && canonicalCost !== cost ? canonicalCost : undefined,
36
- context: model.limit?.context,
37
- });
38
- }
39
- }
40
- models.sort((a, b) => (a.cost ?? Infinity) - (b.cost ?? Infinity));
41
- return models;
42
- }
1
+ // Providers considered "canonical" for reference pricing, in priority order.
2
+ // When a model's own provider has no cost (e.g. github-copilot shows $0),
3
+ // we look up the same model name in these providers and attach canonicalCost.
4
+ export const CANONICAL_PROVIDERS = ['anthropic', 'openai', 'google', 'mistral', 'meta', 'cohere'];
5
+
6
+ export function parseModels(data) {
7
+ // Build name → canonical cost lookup from authoritative providers first
8
+ const canonicalCostByName = new Map();
9
+ for (const providerId of CANONICAL_PROVIDERS) {
10
+ const provider = data[providerId];
11
+ if (!provider?.models) continue;
12
+ for (const model of Object.values(provider.models)) {
13
+ if (!model.tool_call) continue;
14
+ const name = model.name;
15
+ if (name && model.cost?.input !== undefined && !canonicalCostByName.has(name)) {
16
+ canonicalCostByName.set(name, model.cost.input);
17
+ }
18
+ }
19
+ }
20
+
21
+ const models = [];
22
+ for (const [providerId, provider] of Object.entries(data)) {
23
+ if (!provider.models) continue;
24
+ for (const [modelId, model] of Object.entries(provider.models)) {
25
+ if (!model.tool_call) continue;
26
+ const name = model.name || modelId;
27
+ const cost = model.cost?.input;
28
+ const canonicalCost = canonicalCostByName.get(name);
29
+ models.push({
30
+ id: `${providerId}/${modelId}`,
31
+ name,
32
+ cost,
33
+ // canonicalCost: cost from the authoritative provider for this model name.
34
+ // Defined when cost !== canonicalCost (different provider, reseller, or $0 subscription).
35
+ canonicalCost: canonicalCost !== undefined && canonicalCost !== cost ? canonicalCost : undefined,
36
+ context: model.limit?.context,
37
+ });
38
+ }
39
+ }
40
+ models.sort((a, b) => (a.cost ?? Infinity) - (b.cost ?? Infinity));
41
+ return models;
42
+ }
@@ -6,7 +6,7 @@ import path from 'node:path'
6
6
  *
7
7
  * These filenames are a contract with every repo the harness has been
8
8
  * installed into: the CLI writes them, and the opencode plugins shipped in
9
- * src/content/.opencode/plugins/ read them back at runtime. Changing a name
9
+ * harness/.opencode/plugins/ read them back at runtime. Changing a name
10
10
  * here means changing it in that payload too, or the plugins go blind.
11
11
  *
12
12
  * They are deliberately named for the harness rather than for the CLI, so a
@@ -1,3 +1,3 @@
1
- export function exit(code = 0) {
2
- process.exit(code)
3
- }
1
+ export function exit(code = 0) {
2
+ process.exit(code)
3
+ }