@plainconceptsplatform/agent-harness 3.8.2 → 3.9.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/LICENSE +21 -21
- package/README.md +551 -549
- package/cli/commands/join.js +259 -259
- package/cli/commands/migrate.js +310 -310
- package/cli/commands/shared.js +27 -27
- package/cli/commands/single.js +79 -79
- package/cli/commands/update.js +121 -121
- package/cli/commands/wizard.js +134 -134
- package/cli/fragments/archive/az.md +97 -97
- package/cli/fragments/archive/gh.md +96 -96
- package/cli/fragments/archive/gl.md +96 -96
- package/cli/fragments/archive/none.md +75 -75
- package/cli/fragments/guardrails/codegraph.md +5 -5
- package/cli/fragments/guardrails/humanizer.md +4 -4
- package/cli/fragments/guardrails/memory.md +4 -4
- package/cli/fragments/guardrails/rtk.md +3 -3
- package/cli/fragments/guardrails/simple-english.md +4 -4
- package/cli/fragments/ops-backlog/az.md +28 -28
- package/cli/fragments/ops-backlog/gh.md +28 -28
- package/cli/fragments/ops-backlog/jira.md +28 -28
- package/cli/fragments/ops-review/az.md +62 -62
- package/cli/fragments/ops-review/gh.md +51 -51
- package/cli/fragments/ops-review/gl.md +55 -55
- package/cli/fragments/ops-ship/az.md +81 -81
- package/cli/fragments/ops-ship/gh.md +67 -67
- package/cli/fragments/ops-ship/gl.md +84 -84
- package/cli/index.js +116 -116
- package/cli/presets/agents-content.json +34 -34
- package/cli/presets/browser.json +10 -10
- package/cli/presets/clean.json +21 -21
- package/cli/presets/models.json +68 -68
- package/cli/presets/optimization.json +32 -32
- package/cli/presets/platforms.json +76 -76
- package/cli/presets/source.json +23 -23
- package/cli/steps/browser/index.js +48 -48
- package/cli/steps/clean/index.js +120 -120
- package/cli/steps/copy/agents.js +176 -160
- package/cli/steps/copy/commands.js +79 -79
- package/cli/steps/copy/fullstack-engineer.js +81 -89
- package/cli/steps/copy/index.js +88 -88
- package/cli/steps/copy/opencode-json.js +324 -324
- package/cli/steps/copy/skills.js +316 -316
- package/cli/steps/metadata/index.js +115 -115
- package/cli/steps/models/format.js +88 -88
- package/cli/steps/models/index.js +64 -64
- package/cli/steps/models/write.js +38 -38
- package/cli/steps/openspec/index.js +136 -136
- package/cli/steps/optimization/codegraph.js +141 -141
- package/cli/steps/optimization/detect.js +66 -66
- package/cli/steps/optimization/humanizer.js +17 -17
- package/cli/steps/optimization/index.js +158 -158
- package/cli/steps/optimization/memory.js +91 -91
- package/cli/steps/optimization/patch-guardrails.js +111 -111
- package/cli/steps/optimization/simple-english.js +17 -17
- package/cli/steps/optimization/skills-lock.js +30 -30
- package/cli/steps/platform/index.js +109 -109
- package/cli/steps/source/index.js +123 -123
- package/cli/utils/agent-color.js +111 -111
- package/cli/utils/copy.js +134 -134
- package/cli/utils/exec-spinner.js +47 -47
- package/cli/utils/exec.js +134 -134
- package/cli/utils/legacy-check.js +30 -30
- package/cli/utils/models-cache.js +58 -58
- package/cli/utils/models-pricing.js +42 -42
- package/cli/utils/paths.js +82 -82
- package/cli/utils/process.js +3 -3
- package/cli/utils/terminal.js +6 -6
- package/cli/utils/update-manifest.js +75 -75
- package/harness/.opencode/_gitignore +12 -10
- package/harness/.opencode/commands/init.md +5 -5
- package/harness/.opencode/commands/make-architecture.md +5 -5
- package/harness/.opencode/commands/make-design.md +5 -5
- package/harness/.opencode/commands/make-engineer.md +5 -5
- package/harness/.opencode/commands/make-guardrails.md +5 -5
- package/harness/.opencode/commands/make-user-model.md +5 -5
- package/harness/.opencode/commands/ops-backlog.md +10 -10
- package/harness/.opencode/commands/ops-review.md +10 -10
- package/harness/.opencode/commands/ops-ship.md +9 -9
- package/harness/.opencode/commands/plan-apply.md +9 -9
- package/harness/.opencode/commands/plan-archive.md +7 -7
- package/harness/.opencode/commands/plan-explore.md +11 -11
- package/harness/.opencode/commands/plan-goal.md +5 -5
- package/harness/.opencode/commands/plan-propose.md +11 -11
- package/harness/.opencode/commands/plan-quick.md +7 -7
- package/harness/.opencode/commands/plan-story.md +11 -11
- package/harness/.opencode/commands/repo-audit.md +7 -7
- package/harness/.opencode/commands/repo-help.md +5 -5
- package/harness/.opencode/commands/repo-initialize.md +5 -5
- package/harness/.opencode/commands/repo-onboard.md +5 -5
- package/harness/.opencode/commands/repo-verify.md +5 -5
- package/harness/.opencode/package.json +2 -1
- package/harness/.opencode/plugins/pc-commit-coauthor.js +41 -41
- package/harness/.opencode/plugins/pc-subagent-monitor.js +184 -184
- package/harness/.opencode/plugins/pc-subagent-tiers-core.js +337 -0
- package/harness/.opencode/plugins/pc-subagent-tiers.js +34 -345
- package/harness/.opencode/plugins/pc-system-reminders.js +424 -424
- package/harness/.opencode/scripts/check-diff.mjs +305 -305
- package/harness/.opencode/scripts/code-rules.mjs +288 -288
- package/harness/.opencode/scripts/harness-doctor.mjs +52 -0
- package/harness/.opencode/scripts/harness-doctor.test.mjs +58 -0
- package/harness/.opencode/scripts/platform-contract.mjs +366 -366
- package/harness/.opencode/scripts/sync-agents.mjs +29 -0
- package/harness/AGENTS.md +55 -49
- package/harness/ARCHITECTURE.md +13 -13
- package/harness/DESIGN.md +13 -13
- package/harness/opencode.jsonc +36 -36
- package/harness/openspec/config.yaml +20 -20
- package/harness/skills-lock.json +17 -17
- package/package.json +12 -14
- package/skills/README.md +18 -18
- package/skills/agent-harness-cli/SKILL.md +86 -86
- package/skills/browser-automation/SKILL.md +72 -72
- package/skills/pc-guardrails-generic/SKILL.md +53 -53
- package/skills/pc-guardrails-project/SKILL.md +8 -8
- package/skills/pc-make-architecture/SKILL.md +33 -33
- package/skills/pc-make-architecture/structure-template.md +64 -64
- package/skills/pc-make-design/SKILL.md +45 -45
- package/skills/pc-make-engineer/SKILL.md +59 -59
- package/skills/pc-make-engineer/signal-mapping.md +53 -53
- package/skills/pc-make-engineer/template.md +36 -36
- package/skills/pc-make-guardrails/SKILL.md +44 -44
- package/skills/pc-make-guardrails/category-reference.md +112 -112
- package/skills/pc-make-merge-risk-assess/SKILL.md +70 -70
- package/skills/pc-make-merge-risk-assess/category-reference.md +114 -114
- package/skills/pc-make-user-model/SKILL.md +56 -56
- package/skills/pc-ops-ship/SKILL.md +22 -22
- package/skills/pc-plan-apply/SKILL.md +90 -90
- package/skills/pc-plan-apply/simple-mode.md +21 -21
- package/skills/pc-plan-archive/SKILL.md +66 -66
- package/skills/pc-plan-explore/SKILL.md +21 -21
- package/skills/pc-plan-goal/SKILL.md +91 -91
- package/skills/pc-plan-goal/branching.md +39 -39
- package/skills/pc-plan-goal/failure-policy.md +30 -30
- package/skills/pc-plan-goal/output-mode.md +10 -10
- package/skills/pc-plan-goal/output.md +71 -71
- package/skills/pc-plan-propose/SKILL.md +127 -127
- package/skills/pc-plan-propose/task-annotation.md +39 -39
- package/skills/pc-plan-quick/SKILL.md +46 -46
- package/skills/pc-plan-story/SKILL.md +93 -93
- package/skills/pc-repo-audit/SKILL.md +44 -44
- package/skills/pc-repo-help/SKILL.md +87 -87
- package/skills/pc-repo-initialize/SKILL.md +119 -119
- package/skills/pc-repo-onboard/SKILL.md +32 -32
- package/skills/pc-repo-verify/SKILL.md +150 -150
- package/skills/pc-userstory-az/SKILL.md +71 -71
- package/skills/pc-userstory-browser/SKILL.md +64 -64
- package/skills/pc-userstory-gh/SKILL.md +63 -63
- package/skills/pc-userstory-jira/SKILL.md +74 -74
package/cli/utils/exec.js
CHANGED
|
@@ -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,30 +1,30 @@
|
|
|
1
|
-
import fse from 'fs-extra'
|
|
2
|
-
import path from 'node:path'
|
|
3
|
-
import { CONFIG_FILE, LEGACY_CONFIG_FILES, OPENCODE_DIR } from './paths.js'
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Detect a repo onboarded by opencode-onboard v1.x.
|
|
7
|
-
*
|
|
8
|
-
* v2 renamed both the state files and the `ob-` skill prefix, and it does not
|
|
9
|
-
* migrate: see the rename plan. Running against a v1 repo would otherwise fail
|
|
10
|
-
* silently in two places, because `patchFile` returns without logging when its
|
|
11
|
-
* markers are absent and the skill sync only removes stale directories under
|
|
12
|
-
* `forceOverwrite && !updateMode`. The result is a half-patched harness that
|
|
13
|
-
* still reports success, so refuse the run instead.
|
|
14
|
-
*/
|
|
15
|
-
export async function findLegacyInstall(cwd = process.cwd()) {
|
|
16
|
-
const opencodeDir = path.join(cwd, OPENCODE_DIR)
|
|
17
|
-
if (await fse.pathExists(path.join(opencodeDir, CONFIG_FILE))) return null
|
|
18
|
-
|
|
19
|
-
const found = []
|
|
20
|
-
for (const file of LEGACY_CONFIG_FILES) {
|
|
21
|
-
if (await fse.pathExists(path.join(opencodeDir, file))) found.push(`${OPENCODE_DIR}/${file}`)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const skillsDir = path.join(cwd, '.agents', 'skills')
|
|
25
|
-
const legacySkills = (await fse.readdir(skillsDir).catch(() => []))
|
|
26
|
-
.filter(entry => entry.startsWith('ob-'))
|
|
27
|
-
|
|
28
|
-
if (found.length === 0 && legacySkills.length === 0) return null
|
|
29
|
-
return { files: found, skills: legacySkills }
|
|
30
|
-
}
|
|
1
|
+
import fse from 'fs-extra'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import { CONFIG_FILE, LEGACY_CONFIG_FILES, OPENCODE_DIR } from './paths.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Detect a repo onboarded by opencode-onboard v1.x.
|
|
7
|
+
*
|
|
8
|
+
* v2 renamed both the state files and the `ob-` skill prefix, and it does not
|
|
9
|
+
* migrate: see the rename plan. Running against a v1 repo would otherwise fail
|
|
10
|
+
* silently in two places, because `patchFile` returns without logging when its
|
|
11
|
+
* markers are absent and the skill sync only removes stale directories under
|
|
12
|
+
* `forceOverwrite && !updateMode`. The result is a half-patched harness that
|
|
13
|
+
* still reports success, so refuse the run instead.
|
|
14
|
+
*/
|
|
15
|
+
export async function findLegacyInstall(cwd = process.cwd()) {
|
|
16
|
+
const opencodeDir = path.join(cwd, OPENCODE_DIR)
|
|
17
|
+
if (await fse.pathExists(path.join(opencodeDir, CONFIG_FILE))) return null
|
|
18
|
+
|
|
19
|
+
const found = []
|
|
20
|
+
for (const file of LEGACY_CONFIG_FILES) {
|
|
21
|
+
if (await fse.pathExists(path.join(opencodeDir, file))) found.push(`${OPENCODE_DIR}/${file}`)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const skillsDir = path.join(cwd, '.agents', 'skills')
|
|
25
|
+
const legacySkills = (await fse.readdir(skillsDir).catch(() => []))
|
|
26
|
+
.filter(entry => entry.startsWith('ob-'))
|
|
27
|
+
|
|
28
|
+
if (found.length === 0 && legacySkills.length === 0) return null
|
|
29
|
+
return { files: found, skills: legacySkills }
|
|
30
|
+
}
|
|
@@ -1,58 +1,58 @@
|
|
|
1
|
-
import fse from 'fs-extra'
|
|
2
|
-
import path from 'path'
|
|
3
|
-
import { parseModels } from './models-pricing.js'
|
|
4
|
-
import { modelsCacheDir } from './paths.js'
|
|
5
|
-
|
|
6
|
-
const CACHE_DIR = modelsCacheDir();
|
|
7
|
-
const CACHE_FILE = path.join(CACHE_DIR, 'models-cache.json');
|
|
8
|
-
const CACHE_TTL_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
|
|
9
|
-
const MODELS_URL = 'https://models.dev/api.json';
|
|
10
|
-
|
|
11
|
-
async function loadCache() {
|
|
12
|
-
try {
|
|
13
|
-
if (!await fse.pathExists(CACHE_FILE)) return null;
|
|
14
|
-
const cache = await fse.readJson(CACHE_FILE);
|
|
15
|
-
if (!cache.timestamp || !cache.models) return null;
|
|
16
|
-
const age = Date.now() - cache.timestamp;
|
|
17
|
-
if (age > CACHE_TTL_MS) return null; // expired
|
|
18
|
-
return cache.models;
|
|
19
|
-
} catch {
|
|
20
|
-
return null;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
async function saveCache(models) {
|
|
25
|
-
try {
|
|
26
|
-
await fse.ensureDir(CACHE_DIR);
|
|
27
|
-
await fse.writeJson(CACHE_FILE, { timestamp: Date.now(), models });
|
|
28
|
-
} catch {
|
|
29
|
-
// cache write failure is non-fatal
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export async function fetchModels() {
|
|
34
|
-
// 1. Try cache first (fresh)
|
|
35
|
-
const cached = await loadCache();
|
|
36
|
-
if (cached) return { models: cached, source: 'cache' };
|
|
37
|
-
|
|
38
|
-
// 2. Try network
|
|
39
|
-
try {
|
|
40
|
-
const res = await fetch(MODELS_URL, { signal: AbortSignal.timeout(8000) });
|
|
41
|
-
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
42
|
-
const data = await res.json();
|
|
43
|
-
const models = parseModels(data);
|
|
44
|
-
await saveCache(models);
|
|
45
|
-
return { models, source: 'network' };
|
|
46
|
-
} catch {
|
|
47
|
-
// 3. Network failed, fall back to stale cache if available
|
|
48
|
-
try {
|
|
49
|
-
if (await fse.pathExists(CACHE_FILE)) {
|
|
50
|
-
const cache = await fse.readJson(CACHE_FILE);
|
|
51
|
-
if (cache.models?.length) return { models: cache.models, source: 'stale-cache' };
|
|
52
|
-
}
|
|
53
|
-
} catch {
|
|
54
|
-
// ignore
|
|
55
|
-
}
|
|
56
|
-
return { models: null, source: 'unavailable' };
|
|
57
|
-
}
|
|
58
|
-
}
|
|
1
|
+
import fse from 'fs-extra'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import { parseModels } from './models-pricing.js'
|
|
4
|
+
import { modelsCacheDir } from './paths.js'
|
|
5
|
+
|
|
6
|
+
const CACHE_DIR = modelsCacheDir();
|
|
7
|
+
const CACHE_FILE = path.join(CACHE_DIR, 'models-cache.json');
|
|
8
|
+
const CACHE_TTL_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
|
|
9
|
+
const MODELS_URL = 'https://models.dev/api.json';
|
|
10
|
+
|
|
11
|
+
async function loadCache() {
|
|
12
|
+
try {
|
|
13
|
+
if (!await fse.pathExists(CACHE_FILE)) return null;
|
|
14
|
+
const cache = await fse.readJson(CACHE_FILE);
|
|
15
|
+
if (!cache.timestamp || !cache.models) return null;
|
|
16
|
+
const age = Date.now() - cache.timestamp;
|
|
17
|
+
if (age > CACHE_TTL_MS) return null; // expired
|
|
18
|
+
return cache.models;
|
|
19
|
+
} catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function saveCache(models) {
|
|
25
|
+
try {
|
|
26
|
+
await fse.ensureDir(CACHE_DIR);
|
|
27
|
+
await fse.writeJson(CACHE_FILE, { timestamp: Date.now(), models });
|
|
28
|
+
} catch {
|
|
29
|
+
// cache write failure is non-fatal
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function fetchModels() {
|
|
34
|
+
// 1. Try cache first (fresh)
|
|
35
|
+
const cached = await loadCache();
|
|
36
|
+
if (cached) return { models: cached, source: 'cache' };
|
|
37
|
+
|
|
38
|
+
// 2. Try network
|
|
39
|
+
try {
|
|
40
|
+
const res = await fetch(MODELS_URL, { signal: AbortSignal.timeout(8000) });
|
|
41
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
42
|
+
const data = await res.json();
|
|
43
|
+
const models = parseModels(data);
|
|
44
|
+
await saveCache(models);
|
|
45
|
+
return { models, source: 'network' };
|
|
46
|
+
} catch {
|
|
47
|
+
// 3. Network failed, fall back to stale cache if available
|
|
48
|
+
try {
|
|
49
|
+
if (await fse.pathExists(CACHE_FILE)) {
|
|
50
|
+
const cache = await fse.readJson(CACHE_FILE);
|
|
51
|
+
if (cache.models?.length) return { models: cache.models, source: 'stale-cache' };
|
|
52
|
+
}
|
|
53
|
+
} catch {
|
|
54
|
+
// ignore
|
|
55
|
+
}
|
|
56
|
+
return { models: null, source: 'unavailable' };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -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
|
+
}
|