@plainconceptsplatform/agent-harness 2.0.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 -0
- package/README.md +419 -0
- package/package.json +68 -0
- package/src/commands/join.js +244 -0
- package/src/commands/shared.js +27 -0
- package/src/commands/single.js +79 -0
- package/src/commands/update.js +109 -0
- package/src/commands/wizard.js +134 -0
- package/src/content/.agents/skills/browser-automation/SKILL.md +66 -0
- package/src/content/.agents/skills/pc-guardrails-generic/SKILL.md +68 -0
- package/src/content/.agents/skills/pc-guardrails-project/SKILL.md +8 -0
- package/src/content/.agents/skills/pc-make-architecture/SKILL.md +51 -0
- package/src/content/.agents/skills/pc-make-architecture/structure-template.md +38 -0
- package/src/content/.agents/skills/pc-make-design/SKILL.md +68 -0
- package/src/content/.agents/skills/pc-make-engineer/SKILL.md +219 -0
- package/src/content/.agents/skills/pc-make-engineer/signal-mapping.md +68 -0
- package/src/content/.agents/skills/pc-make-engineer/template.md +81 -0
- package/src/content/.agents/skills/pc-make-evidence-scaffold/SKILL.md +18 -0
- package/src/content/.agents/skills/pc-make-evidence-scaffold/evidence-contract.md +29 -0
- package/src/content/.agents/skills/pc-make-guardrails/SKILL.md +74 -0
- package/src/content/.agents/skills/pc-make-guardrails/category-reference.md +68 -0
- package/src/content/.agents/skills/pc-make-merge-risk-assess/SKILL.md +70 -0
- package/src/content/.agents/skills/pc-make-merge-risk-assess/category-reference.md +98 -0
- package/src/content/.agents/skills/pc-make-user-model/SKILL.md +66 -0
- package/src/content/.agents/skills/pc-ops-evidence/SKILL.md +127 -0
- package/src/content/.agents/skills/pc-ops-ship/SKILL.md +18 -0
- package/src/content/.agents/skills/pc-plan-apply/SKILL.md +83 -0
- package/src/content/.agents/skills/pc-plan-apply/simple-mode.md +21 -0
- package/src/content/.agents/skills/pc-plan-archive/SKILL.md +63 -0
- package/src/content/.agents/skills/pc-plan-explore/SKILL.md +9 -0
- package/src/content/.agents/skills/pc-plan-goal/SKILL.md +94 -0
- package/src/content/.agents/skills/pc-plan-goal/branching.md +30 -0
- package/src/content/.agents/skills/pc-plan-goal/failure-policy.md +30 -0
- package/src/content/.agents/skills/pc-plan-goal/output-mode.md +9 -0
- package/src/content/.agents/skills/pc-plan-goal/output.md +68 -0
- package/src/content/.agents/skills/pc-plan-propose/SKILL.md +125 -0
- package/src/content/.agents/skills/pc-plan-propose/task-annotation.md +39 -0
- package/src/content/.agents/skills/pc-plan-quick/SKILL.md +62 -0
- package/src/content/.agents/skills/pc-plan-story/SKILL.md +146 -0
- package/src/content/.agents/skills/pc-repo-audit/SKILL.md +44 -0
- package/src/content/.agents/skills/pc-repo-help/SKILL.md +91 -0
- package/src/content/.agents/skills/pc-repo-initialize/SKILL.md +130 -0
- package/src/content/.agents/skills/pc-repo-onboard/SKILL.md +87 -0
- package/src/content/.agents/skills/pc-repo-verify/SKILL.md +34 -0
- package/src/content/.agents/skills/pc-userstory-az/SKILL.md +157 -0
- package/src/content/.agents/skills/pc-userstory-browser/SKILL.md +132 -0
- package/src/content/.agents/skills/pc-userstory-gh/SKILL.md +120 -0
- package/src/content/.agents/skills/pc-userstory-jira/SKILL.md +131 -0
- package/src/content/.opencode/_gitignore +7 -0
- package/src/content/.opencode/commands/init.md +5 -0
- package/src/content/.opencode/commands/make-architecture.md +5 -0
- package/src/content/.opencode/commands/make-design.md +5 -0
- package/src/content/.opencode/commands/make-engineer.md +5 -0
- package/src/content/.opencode/commands/make-evidence-scaffold.md +5 -0
- package/src/content/.opencode/commands/make-guardrails.md +5 -0
- package/src/content/.opencode/commands/make-user-model.md +5 -0
- package/src/content/.opencode/commands/ops-backlog.md +10 -0
- package/src/content/.opencode/commands/ops-evidence.md +9 -0
- package/src/content/.opencode/commands/ops-review.md +8 -0
- package/src/content/.opencode/commands/ops-ship.md +9 -0
- package/src/content/.opencode/commands/plan-apply.md +9 -0
- package/src/content/.opencode/commands/plan-archive.md +5 -0
- package/src/content/.opencode/commands/plan-explore.md +9 -0
- package/src/content/.opencode/commands/plan-goal.md +5 -0
- package/src/content/.opencode/commands/plan-propose.md +9 -0
- package/src/content/.opencode/commands/plan-quick.md +5 -0
- package/src/content/.opencode/commands/plan-story.md +9 -0
- package/src/content/.opencode/commands/repo-audit.md +5 -0
- package/src/content/.opencode/commands/repo-help.md +5 -0
- package/src/content/.opencode/commands/repo-initialize.md +5 -0
- package/src/content/.opencode/commands/repo-onboard.md +5 -0
- package/src/content/.opencode/commands/repo-verify.md +5 -0
- package/src/content/.opencode/package.json +10 -0
- package/src/content/.opencode/plugins/pc-subagent-monitor.js +139 -0
- package/src/content/.opencode/plugins/pc-subagent-tiers.js +179 -0
- package/src/content/.opencode/plugins/pc-system-reminders.js +96 -0
- package/src/content/.opencode/plugins/pc-system-reminders.test.js +35 -0
- package/src/content/.opencode/tui/pc-subagents.tsx +98 -0
- package/src/content/.opencode/tui.json +6 -0
- package/src/content/AGENTS.md +71 -0
- package/src/content/ARCHITECTURE.md +16 -0
- package/src/content/DESIGN.md +16 -0
- package/src/content/opencode.jsonc +31 -0
- package/src/content/openspec/changes/archive/.gitkeep +0 -0
- package/src/content/openspec/config.yaml +20 -0
- package/src/content/openspec/specs/.gitkeep +0 -0
- package/src/content/skills-lock.json +17 -0
- package/src/fragments/archive/az.md +95 -0
- package/src/fragments/archive/gh.md +94 -0
- package/src/fragments/archive/gl.md +94 -0
- package/src/fragments/archive/none.md +73 -0
- package/src/fragments/guardrails/codegraph.md +7 -0
- package/src/fragments/guardrails/humanizer.md +4 -0
- package/src/fragments/guardrails/memory.md +4 -0
- package/src/fragments/guardrails/rtk.md +3 -0
- package/src/fragments/guardrails/simple-english.md +4 -0
- package/src/fragments/ops-backlog/az.md +29 -0
- package/src/fragments/ops-backlog/gh.md +30 -0
- package/src/fragments/ops-backlog/jira.md +29 -0
- package/src/fragments/ops-evidence/az.md +41 -0
- package/src/fragments/ops-evidence/gh.md +53 -0
- package/src/fragments/ops-evidence/jira.md +38 -0
- package/src/fragments/ops-review/az.md +63 -0
- package/src/fragments/ops-review/gh.md +53 -0
- package/src/fragments/ops-review/gl.md +57 -0
- package/src/fragments/ops-ship/az.md +81 -0
- package/src/fragments/ops-ship/gh.md +69 -0
- package/src/fragments/ops-ship/gl.md +86 -0
- package/src/index.js +107 -0
- package/src/presets/agents-content.json +53 -0
- package/src/presets/browser.json +22 -0
- package/src/presets/clean.json +21 -0
- package/src/presets/models.json +68 -0
- package/src/presets/openspec.json +1 -0
- package/src/presets/optimization.json +37 -0
- package/src/presets/platforms.json +76 -0
- package/src/presets/quota.json +16 -0
- package/src/presets/source.json +23 -0
- package/src/steps/browser/index.js +91 -0
- package/src/steps/clean/index.js +120 -0
- package/src/steps/copy/agents.js +118 -0
- package/src/steps/copy/commands.js +91 -0
- package/src/steps/copy/fullstack-engineer.js +83 -0
- package/src/steps/copy/index.js +88 -0
- package/src/steps/copy/opencode-json.js +129 -0
- package/src/steps/copy/skills.js +196 -0
- package/src/steps/metadata/index.js +108 -0
- package/src/steps/models/format.js +88 -0
- package/src/steps/models/index.js +64 -0
- package/src/steps/models/write.js +34 -0
- package/src/steps/openspec/index.js +136 -0
- package/src/steps/optimization/codegraph.js +127 -0
- package/src/steps/optimization/humanizer.js +17 -0
- package/src/steps/optimization/index.js +163 -0
- package/src/steps/optimization/memory.js +88 -0
- package/src/steps/optimization/patch-guardrails.js +108 -0
- package/src/steps/optimization/quota.js +119 -0
- package/src/steps/optimization/simple-english.js +17 -0
- package/src/steps/optimization/skills-lock.js +30 -0
- package/src/steps/platform/index.js +109 -0
- package/src/steps/source/index.js +123 -0
- package/src/utils/copy.js +108 -0
- package/src/utils/exec-spinner.js +47 -0
- package/src/utils/exec.js +134 -0
- package/src/utils/legacy-check.js +30 -0
- package/src/utils/models-cache.js +58 -0
- package/src/utils/models-pricing.js +42 -0
- package/src/utils/paths.js +64 -0
- package/src/utils/process.js +3 -0
- package/src/utils/terminal.js +6 -0
- package/src/utils/update-manifest.js +49 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import chalk from 'chalk'
|
|
2
|
+
import { execa } from 'execa'
|
|
3
|
+
import fse from 'fs-extra'
|
|
4
|
+
import path from 'node:path'
|
|
5
|
+
import { parse as parseJsonc } from 'jsonc-parser'
|
|
6
|
+
import { installBrowser } from '../steps/browser/index.js'
|
|
7
|
+
import { checkRtk } from '../steps/optimization/index.js'
|
|
8
|
+
import { checkPlatform } from '../steps/platform/index.js'
|
|
9
|
+
import { commandExists, header, info, loading, success, warn } from '../utils/exec.js'
|
|
10
|
+
import { IGNORED_ENTRIES, USER_CONFIG_FILE } from '../utils/paths.js'
|
|
11
|
+
import { readHarnessConfig } from './shared.js'
|
|
12
|
+
|
|
13
|
+
export async function runJoin() {
|
|
14
|
+
const logo = chalk.hex('#fe3d57')
|
|
15
|
+
console.log()
|
|
16
|
+
console.log(logo(' 🤝 agent-harness join'))
|
|
17
|
+
console.log(chalk.dim(' New team member setup: checks & local installs only.'))
|
|
18
|
+
console.log(chalk.dim(' Does not modify committed project files.'))
|
|
19
|
+
console.log()
|
|
20
|
+
|
|
21
|
+
const saved = await readHarnessConfig()
|
|
22
|
+
|
|
23
|
+
if (!saved) {
|
|
24
|
+
warn('No .opencode/harness.json found.')
|
|
25
|
+
warn('This project may not have been onboarded yet. Run `npx @plainconceptsplatform/agent-harness` first.')
|
|
26
|
+
return
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const backlogPlatform = saved?.platform?.backlog
|
|
30
|
+
const repoPlatform = saved?.platform?.repo
|
|
31
|
+
const installScope = 'local'
|
|
32
|
+
const teamModels = saved?.models ?? {}
|
|
33
|
+
|
|
34
|
+
const opencodeDir = path.join(process.cwd(), '.opencode')
|
|
35
|
+
const opencodeJsonPath = path.join(process.cwd(), 'opencode.jsonc')
|
|
36
|
+
|
|
37
|
+
// Step 1: Platform CLI check
|
|
38
|
+
header('Step 1, Platform CLI check')
|
|
39
|
+
const platformsToCheck = [...new Set([backlogPlatform, repoPlatform])].filter(p => p && p !== 'none')
|
|
40
|
+
if (platformsToCheck.length === 0) {
|
|
41
|
+
info('No platform integration selected, skipping CLI checks.')
|
|
42
|
+
} else {
|
|
43
|
+
for (const p of platformsToCheck) {
|
|
44
|
+
const display = p === 'github' ? 'GitHub' : p === 'azure' ? 'Azure DevOps' : p
|
|
45
|
+
info(`Checking platform: ${display}`)
|
|
46
|
+
await checkPlatform(p)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Step 2: Install OpenCode plugins
|
|
51
|
+
header('Step 2, Installing OpenCode plugins')
|
|
52
|
+
const pkgPath = path.join(opencodeDir, 'package.json')
|
|
53
|
+
if (await fse.pathExists(pkgPath)) {
|
|
54
|
+
try {
|
|
55
|
+
await execa('npm', ['install'], { cwd: opencodeDir, reject: false, stdio: 'pipe' })
|
|
56
|
+
success('OpenCode plugins installed')
|
|
57
|
+
} catch {
|
|
58
|
+
warn('npm install failed in .opencode/: plugins may not load correctly')
|
|
59
|
+
}
|
|
60
|
+
} else {
|
|
61
|
+
warn('No .opencode/package.json found: skipping plugin install')
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Step 3: Install/update skills
|
|
65
|
+
header('Step 3, Installing skills')
|
|
66
|
+
try {
|
|
67
|
+
await execa('npx', ['skills'], { cwd: process.cwd(), reject: false, stdio: 'pipe' })
|
|
68
|
+
success('Skills installed/updated')
|
|
69
|
+
} catch {
|
|
70
|
+
warn('npx skills failed: skills may be missing')
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Step 4: OpenSpec CLI check
|
|
74
|
+
header('Step 4, Checking OpenSpec')
|
|
75
|
+
const openspecAvailable = await commandExists('openspec')
|
|
76
|
+
if (openspecAvailable) {
|
|
77
|
+
success('OpenSpec CLI is available')
|
|
78
|
+
} else {
|
|
79
|
+
info('OpenSpec not found on PATH: installing...')
|
|
80
|
+
try {
|
|
81
|
+
const result = await execa('npm', ['install', '@fission-ai/openspec', '--global'], {
|
|
82
|
+
cwd: process.cwd(),
|
|
83
|
+
reject: false,
|
|
84
|
+
stdio: 'pipe',
|
|
85
|
+
})
|
|
86
|
+
if (result.exitCode === 0) {
|
|
87
|
+
success('OpenSpec installed')
|
|
88
|
+
} else {
|
|
89
|
+
warn('OpenSpec install failed: run `npm install -g @fission-ai/openspec` manually')
|
|
90
|
+
}
|
|
91
|
+
} catch {
|
|
92
|
+
warn('OpenSpec install failed: run `npm install -g @fission-ai/openspec` manually')
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Step 5: agentmemory (if project uses it)
|
|
97
|
+
header('Step 5, Checking agentmemory')
|
|
98
|
+
let cfg = {}
|
|
99
|
+
if (await fse.pathExists(opencodeJsonPath)) {
|
|
100
|
+
try {
|
|
101
|
+
const errors = []
|
|
102
|
+
cfg = parseJsonc(await fse.readFile(opencodeJsonPath, 'utf-8'), errors)
|
|
103
|
+
if (errors.length > 0) cfg = {}
|
|
104
|
+
} catch {
|
|
105
|
+
// ignore config read errors
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (cfg?.mcp?.['agentmemory']) {
|
|
110
|
+
info('Project uses agentmemory MCP')
|
|
111
|
+
loading('checking agentmemory server...')
|
|
112
|
+
|
|
113
|
+
// Check if agentmemory is installed
|
|
114
|
+
const agentmemoryAvailable = await commandExists('agentmemory')
|
|
115
|
+
if (agentmemoryAvailable) {
|
|
116
|
+
success('agentmemory CLI is available')
|
|
117
|
+
} else {
|
|
118
|
+
info('agentmemory not found on PATH, installing...')
|
|
119
|
+
try {
|
|
120
|
+
const installResult = await execa('npm', ['install', '-g', '@agentmemory/agentmemory'], {
|
|
121
|
+
reject: false,
|
|
122
|
+
timeout: 300000,
|
|
123
|
+
stdio: 'pipe',
|
|
124
|
+
})
|
|
125
|
+
if (installResult.exitCode === 0) {
|
|
126
|
+
success('agentmemory installed')
|
|
127
|
+
} else {
|
|
128
|
+
warn('agentmemory install failed, run `npm install -g @agentmemory/agentmemory` manually')
|
|
129
|
+
}
|
|
130
|
+
} catch (err) {
|
|
131
|
+
warn(`agentmemory install failed: ${err.message}`)
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Health-check the server
|
|
136
|
+
try {
|
|
137
|
+
const healthResult = await execa('curl', ['-s', '-o', '/dev/null', '-w', '%{http_code}', 'http://localhost:3111/agentmemory/health'], {
|
|
138
|
+
reject: false,
|
|
139
|
+
timeout: 5000,
|
|
140
|
+
})
|
|
141
|
+
if (healthResult.stdout.trim() === '200') {
|
|
142
|
+
success('agentmemory server is running')
|
|
143
|
+
} else {
|
|
144
|
+
info('agentmemory server not running, start it with `agentmemory` in a separate terminal')
|
|
145
|
+
}
|
|
146
|
+
} catch {
|
|
147
|
+
info('agentmemory server not running, start it with `agentmemory` in a separate terminal')
|
|
148
|
+
}
|
|
149
|
+
} else {
|
|
150
|
+
info('agentmemory not configured for this project, skipping')
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Step 6: Codegraph (if project uses it)
|
|
154
|
+
header('Step 6, Checking codegraph')
|
|
155
|
+
if (cfg?.mcp?.['codegraph']) {
|
|
156
|
+
info('Project uses codegraph MCP: building index...')
|
|
157
|
+
try {
|
|
158
|
+
const result = await execa('npx', ['--yes', '@colbymchenry/codegraph', 'init', '-i'], {
|
|
159
|
+
cwd: process.cwd(),
|
|
160
|
+
reject: false,
|
|
161
|
+
stdio: 'pipe',
|
|
162
|
+
timeout: 120000,
|
|
163
|
+
})
|
|
164
|
+
if (result.exitCode === 0) {
|
|
165
|
+
success('codegraph index initialized')
|
|
166
|
+
} else {
|
|
167
|
+
warn('codegraph init failed: codegraph_search may be slow or unavailable')
|
|
168
|
+
}
|
|
169
|
+
} catch {
|
|
170
|
+
warn('codegraph init failed: codegraph_search may be slow or unavailable')
|
|
171
|
+
}
|
|
172
|
+
} else {
|
|
173
|
+
info('codegraph not configured for this project: skipping')
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Step 7: RTK check
|
|
177
|
+
header('Step 7, Checking rtk')
|
|
178
|
+
await checkRtk({ skipHeader: true, skipPrompt: true })
|
|
179
|
+
|
|
180
|
+
// Step 8: Browser extension
|
|
181
|
+
header('Step 8, Installing opencode-browser')
|
|
182
|
+
await installBrowser({ installScope })
|
|
183
|
+
|
|
184
|
+
// Step 9: Model tier guidance
|
|
185
|
+
header('Step 9, Model tiers')
|
|
186
|
+
const userConfigPath = path.join(opencodeDir, USER_CONFIG_FILE)
|
|
187
|
+
const hasUserOverride = await fse.pathExists(userConfigPath)
|
|
188
|
+
|
|
189
|
+
if (teamModels && Object.keys(teamModels).length > 0) {
|
|
190
|
+
info('Team model configuration:')
|
|
191
|
+
for (const [tier, model] of Object.entries(teamModels)) {
|
|
192
|
+
info(` ${tier}: ${model}`)
|
|
193
|
+
}
|
|
194
|
+
console.log()
|
|
195
|
+
|
|
196
|
+
if (hasUserOverride) {
|
|
197
|
+
info('You have a local model override (harness.user.json).')
|
|
198
|
+
info('To change: /make-user-model user <tier> <model>')
|
|
199
|
+
} else {
|
|
200
|
+
info('No local override: using team defaults.')
|
|
201
|
+
info('To override a tier for your machine: /make-user-model user <tier> <model>')
|
|
202
|
+
info(' e.g. /make-user-model user build current')
|
|
203
|
+
}
|
|
204
|
+
console.log()
|
|
205
|
+
info('The pc-subagent-tiers plugin will generate *-engineer.<tier>.md variants')
|
|
206
|
+
info('on opencode startup from these model configs.')
|
|
207
|
+
} else {
|
|
208
|
+
warn('No model tiers configured in harness.json.')
|
|
209
|
+
warn('Run /make-user-model <tier> <model> for plan, build, and fast.')
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Step 10: Ensure .opencode/.gitignore exists
|
|
213
|
+
header('Step 10, Checking .opencode/.gitignore')
|
|
214
|
+
const gitignorePath = path.join(opencodeDir, '.gitignore')
|
|
215
|
+
const requiredEntries = IGNORED_ENTRIES
|
|
216
|
+
|
|
217
|
+
if (await fse.pathExists(gitignorePath)) {
|
|
218
|
+
const content = await fse.readFile(gitignorePath, 'utf-8')
|
|
219
|
+
const existing = content.split('\n').map(l => l.trim()).filter(Boolean)
|
|
220
|
+
const missing = requiredEntries.filter(e => !existing.includes(e))
|
|
221
|
+
if (missing.length > 0) {
|
|
222
|
+
const merged = `${[...existing, ...missing].join('\n')}\n`
|
|
223
|
+
await fse.writeFile(gitignorePath, merged, 'utf-8')
|
|
224
|
+
success(`Merged ${missing.length} missing .gitignore entries`)
|
|
225
|
+
} else {
|
|
226
|
+
success('.gitignore is up to date')
|
|
227
|
+
}
|
|
228
|
+
} else {
|
|
229
|
+
await fse.writeFile(gitignorePath, `${requiredEntries.join('\n')}\n`, 'utf-8')
|
|
230
|
+
success('Created .opencode/.gitignore')
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
console.log()
|
|
234
|
+
console.log(chalk.bold.green('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'))
|
|
235
|
+
console.log(chalk.bold.green(' Join setup complete!'))
|
|
236
|
+
console.log(chalk.bold.green('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'))
|
|
237
|
+
console.log()
|
|
238
|
+
console.log(' Your local environment is ready.')
|
|
239
|
+
if (teamModels && Object.keys(teamModels).length > 0 && !hasUserOverride) {
|
|
240
|
+
console.log(chalk.dim(' Tip: Run /make-user-model user <tier> current to override team models locally.'))
|
|
241
|
+
}
|
|
242
|
+
console.log(chalk.dim(' Restart opencode to pick up tier agent variants.'))
|
|
243
|
+
console.log()
|
|
244
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { execa } from 'execa'
|
|
2
|
+
import fse from 'fs-extra'
|
|
3
|
+
import { configPath } from '../utils/paths.js'
|
|
4
|
+
|
|
5
|
+
export async function readHarnessConfig() {
|
|
6
|
+
const cfgPath = configPath()
|
|
7
|
+
if (!await fse.pathExists(cfgPath)) return null
|
|
8
|
+
try {
|
|
9
|
+
return await fse.readJson(cfgPath)
|
|
10
|
+
} catch {
|
|
11
|
+
return null
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function ensureGitLongpaths(cwd = process.cwd()) {
|
|
16
|
+
// core.longpaths only matters on Windows; don't touch repo config elsewhere.
|
|
17
|
+
if (process.platform !== 'win32') return false
|
|
18
|
+
try {
|
|
19
|
+
const repoCheck = await execa('git', ['rev-parse', '--is-inside-work-tree'], { cwd, reject: false })
|
|
20
|
+
if (repoCheck.exitCode !== 0 || repoCheck.stdout.trim() !== 'true') return false
|
|
21
|
+
|
|
22
|
+
const configResult = await execa('git', ['config', 'core.longpaths', 'true'], { cwd, reject: false })
|
|
23
|
+
return configResult.exitCode === 0
|
|
24
|
+
} catch {
|
|
25
|
+
return false
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import fse from 'fs-extra'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import { fileURLToPath } from 'url'
|
|
4
|
+
import { cleanAiFiles } from '../steps/clean/index.js'
|
|
5
|
+
import { copyContentStep } from '../steps/copy/index.js'
|
|
6
|
+
import { chooseModels } from '../steps/models/index.js'
|
|
7
|
+
import { initOpenspec } from '../steps/openspec/index.js'
|
|
8
|
+
import { tokenOptimizationStep } from '../steps/optimization/index.js'
|
|
9
|
+
import { choosePlatform } from '../steps/platform/index.js'
|
|
10
|
+
import { installBrowser } from '../steps/browser/index.js'
|
|
11
|
+
import { writeHarnessConfig } from '../steps/metadata/index.js'
|
|
12
|
+
import { readHarnessConfig } from './shared.js'
|
|
13
|
+
|
|
14
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
15
|
+
const platformsPreset = await fse.readJson(path.resolve(__dirname, '../presets/platforms.json'))
|
|
16
|
+
// platforms.json is the single source of truth for valid platform values.
|
|
17
|
+
// Hardcoding a subset here silently rewrote jira/gitlab/browser projects to github.
|
|
18
|
+
const VALID_PLATFORMS = new Set(platformsPreset.map(p => p.value))
|
|
19
|
+
|
|
20
|
+
export function resolvePlatform(value, fallback = 'github') {
|
|
21
|
+
return VALID_PLATFORMS.has(value) ? value : fallback
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function runSingleCommand(command) {
|
|
25
|
+
const saved = await readHarnessConfig()
|
|
26
|
+
const ctx = {
|
|
27
|
+
hasDesign: !!saved?.preexisting?.design,
|
|
28
|
+
hasArchitecture: !!saved?.preexisting?.architecture,
|
|
29
|
+
hasOpenspec: !!saved?.preexisting?.openspec,
|
|
30
|
+
sourceMode: saved?.source?.mode ?? 'current',
|
|
31
|
+
sourceRoots: Array.isArray(saved?.source?.roots) ? saved.source.roots : [],
|
|
32
|
+
maxConcurrentAgents: saved?.agents?.maxConcurrent ?? 3,
|
|
33
|
+
}
|
|
34
|
+
const backlogPlatform = saved?.platform?.backlog
|
|
35
|
+
const repoPlatform = saved?.platform?.repo
|
|
36
|
+
const resolvedBacklog = resolvePlatform(backlogPlatform)
|
|
37
|
+
const resolvedRepo = resolvePlatform(repoPlatform)
|
|
38
|
+
|
|
39
|
+
const handlers = {
|
|
40
|
+
clean: async () => {
|
|
41
|
+
await cleanAiFiles()
|
|
42
|
+
},
|
|
43
|
+
platform: async () => {
|
|
44
|
+
await choosePlatform()
|
|
45
|
+
},
|
|
46
|
+
copy: async () => {
|
|
47
|
+
await copyContentStep({ backlogPlatform: resolvedBacklog, repoPlatform: resolvedRepo }, ctx)
|
|
48
|
+
},
|
|
49
|
+
openspec: async () => {
|
|
50
|
+
await initOpenspec()
|
|
51
|
+
},
|
|
52
|
+
models: async () => {
|
|
53
|
+
await chooseModels()
|
|
54
|
+
},
|
|
55
|
+
optimization: async () => {
|
|
56
|
+
await tokenOptimizationStep({ ctx })
|
|
57
|
+
},
|
|
58
|
+
browser: async () => {
|
|
59
|
+
await installBrowser()
|
|
60
|
+
},
|
|
61
|
+
metadata: async () => {
|
|
62
|
+
await writeHarnessConfig({
|
|
63
|
+
...ctx,
|
|
64
|
+
backlogPlatform: resolvedBacklog,
|
|
65
|
+
repoPlatform: resolvedRepo,
|
|
66
|
+
maxConcurrentAgents: saved?.agents?.maxConcurrent ?? 3,
|
|
67
|
+
planModel: saved?.models?.plan ?? null,
|
|
68
|
+
buildModel: saved?.models?.build ?? null,
|
|
69
|
+
fastModel: saved?.models?.fast ?? null,
|
|
70
|
+
optionalTools: saved?.tools ?? null,
|
|
71
|
+
})
|
|
72
|
+
},
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const handler = handlers[command]
|
|
76
|
+
if (!handler) return false
|
|
77
|
+
await handler()
|
|
78
|
+
return true
|
|
79
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import chalk from 'chalk'
|
|
2
|
+
import { execa } from 'execa'
|
|
3
|
+
import fse from 'fs-extra'
|
|
4
|
+
import path from 'node:path'
|
|
5
|
+
import { fileURLToPath } from 'url'
|
|
6
|
+
import { readHarnessConfig } from './shared.js'
|
|
7
|
+
import { copyContentStep } from '../steps/copy/index.js'
|
|
8
|
+
import { writeModelsToConfigs } from '../steps/models/write.js'
|
|
9
|
+
import { patchGuardrails } from '../steps/optimization/patch-guardrails.js'
|
|
10
|
+
import { writeHarnessConfig } from '../steps/metadata/index.js'
|
|
11
|
+
import { exit } from '../utils/process.js'
|
|
12
|
+
|
|
13
|
+
const SIMPLE_ENGLISH_ENTRY = {
|
|
14
|
+
source: 'AminBlg/SimpleEnglish',
|
|
15
|
+
sourceType: 'github',
|
|
16
|
+
skillPath: 'skills/simple-english/SKILL.md',
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
20
|
+
const platformsPreset = await fse.readJson(path.resolve(__dirname, '../presets/platforms.json'))
|
|
21
|
+
const VALID_PLATFORMS = new Set(platformsPreset.map(p => p.value))
|
|
22
|
+
|
|
23
|
+
function resolvePlatform(value, fallback = 'github') {
|
|
24
|
+
return VALID_PLATFORMS.has(value) ? value : fallback
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function runUpdate() {
|
|
28
|
+
const saved = await readHarnessConfig()
|
|
29
|
+
if (!saved?.platform) {
|
|
30
|
+
console.log(chalk.red('No agent-harness config found. Run the wizard first.'))
|
|
31
|
+
exit(1)
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const backlogPlatform = resolvePlatform(saved.platform.backlog)
|
|
36
|
+
const repoPlatform = resolvePlatform(saved.platform.repo)
|
|
37
|
+
|
|
38
|
+
const ctx = {
|
|
39
|
+
hasDesign: !!saved.preexisting?.design,
|
|
40
|
+
hasArchitecture: !!saved.preexisting?.architecture,
|
|
41
|
+
hasOpenspec: !!saved.preexisting?.openspec,
|
|
42
|
+
sourceMode: saved.source?.mode ?? 'current',
|
|
43
|
+
sourceRoots: Array.isArray(saved.source?.roots) ? saved.source.roots : [],
|
|
44
|
+
maxConcurrentAgents: saved.agents?.maxConcurrent ?? 3,
|
|
45
|
+
installScope: 'local',
|
|
46
|
+
skipSkills: false,
|
|
47
|
+
updateMode: true,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
console.log()
|
|
51
|
+
console.log(chalk.bold('Updating project from saved config'))
|
|
52
|
+
console.log(chalk.dim(` backlog: ${backlogPlatform} repo: ${repoPlatform} agents: ${ctx.maxConcurrentAgents}`))
|
|
53
|
+
console.log()
|
|
54
|
+
|
|
55
|
+
await copyContentStep({ backlogPlatform, repoPlatform }, ctx)
|
|
56
|
+
|
|
57
|
+
if (saved.models) {
|
|
58
|
+
await writeModelsToConfigs({ buildModel: saved.models.build })
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const tools = saved.tools ?? {}
|
|
62
|
+
await migrateCavemanSkill(tools)
|
|
63
|
+
await patchGuardrails({
|
|
64
|
+
rtk: !!tools.rtk,
|
|
65
|
+
simpleEnglish: !!(tools.simpleEnglish || tools.caveman),
|
|
66
|
+
codegraph: !!tools.codegraph,
|
|
67
|
+
memory: !!tools.memory,
|
|
68
|
+
humanizer: !!tools.humanizer,
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
await writeHarnessConfig({
|
|
72
|
+
...ctx,
|
|
73
|
+
backlogPlatform,
|
|
74
|
+
repoPlatform,
|
|
75
|
+
maxConcurrentAgents: ctx.maxConcurrentAgents,
|
|
76
|
+
planModel: saved.models?.plan ?? null,
|
|
77
|
+
buildModel: saved.models?.build ?? null,
|
|
78
|
+
fastModel: saved.models?.fast ?? null,
|
|
79
|
+
optionalTools: tools,
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
console.log()
|
|
83
|
+
console.log(chalk.bold.green('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'))
|
|
84
|
+
console.log(chalk.bold.green(' Update complete!'))
|
|
85
|
+
console.log(chalk.bold.green('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'))
|
|
86
|
+
console.log()
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function migrateCavemanSkill(tools) {
|
|
90
|
+
if (!tools.caveman || tools.simpleEnglish) return
|
|
91
|
+
|
|
92
|
+
const lockPath = path.join(process.cwd(), 'skills-lock.json')
|
|
93
|
+
const lock = await fse.readJson(lockPath).catch(() => ({ version: 1, skills: {} }))
|
|
94
|
+
lock.skills ??= {}
|
|
95
|
+
delete lock.skills.caveman
|
|
96
|
+
lock.skills['simple-english'] = SIMPLE_ENGLISH_ENTRY
|
|
97
|
+
await fse.writeJson(lockPath, lock, { spaces: 2 })
|
|
98
|
+
await fse.remove(path.join(process.cwd(), '.agents', 'skills', 'caveman'))
|
|
99
|
+
|
|
100
|
+
const result = await execa('npx', ['skills', 'experimental_install', '--yes'], {
|
|
101
|
+
cwd: process.cwd(),
|
|
102
|
+
reject: false,
|
|
103
|
+
stdio: 'pipe',
|
|
104
|
+
timeout: 600000,
|
|
105
|
+
})
|
|
106
|
+
if (result.exitCode !== 0) {
|
|
107
|
+
console.log(chalk.yellow('Simple English was added to skills-lock.json but could not be installed. Run npx skills experimental_install --yes.'))
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { select as wizardSelect } from '@inquirer/prompts'
|
|
2
|
+
import chalk from 'chalk'
|
|
3
|
+
import { ensureGitLongpaths } from './shared.js'
|
|
4
|
+
import { chooseSourceScope } from '../steps/source/index.js'
|
|
5
|
+
import { cleanAiFiles } from '../steps/clean/index.js'
|
|
6
|
+
import { choosePlatform } from '../steps/platform/index.js'
|
|
7
|
+
import { copyContentStep } from '../steps/copy/index.js'
|
|
8
|
+
import { initOpenspec } from '../steps/openspec/index.js'
|
|
9
|
+
import { chooseModels } from '../steps/models/index.js'
|
|
10
|
+
import { tokenOptimizationStep } from '../steps/optimization/index.js'
|
|
11
|
+
import { installBrowser } from '../steps/browser/index.js'
|
|
12
|
+
import { writeHarnessConfig } from '../steps/metadata/index.js'
|
|
13
|
+
|
|
14
|
+
export async function runWizard(version) {
|
|
15
|
+
const logo = chalk.hex('#fe3d57')
|
|
16
|
+
const bannerLines = [
|
|
17
|
+
logo(' '),
|
|
18
|
+
logo(' ▒▒▒▒▒▒▒▒▒▒▒▒▒ '),
|
|
19
|
+
logo(' ▒▒▓ ▓▒▓ '),
|
|
20
|
+
logo(' ▒▒▒▒▒▒▓▒▒▒▒▒▒▒▒▒▓▓▒▒▒▒▒ '),
|
|
21
|
+
logo(' ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓ '),
|
|
22
|
+
logo(' ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓ '),
|
|
23
|
+
logo(' ▓▒▒▒▒░░░▒▒▒▒▒▒▒▒▒▒▒░░░▒▒▒▓▓ '),
|
|
24
|
+
logo(' ▓▓▓▓▒▒▒▓▓▓▓▓▓▓▓▓▓▓▒▒▒▓▓▓▓ '),
|
|
25
|
+
logo(' ▓▓▒▒▒▒▒▒░▒▒▒▒▒▒▒░▒▒▒▒▒▒▓▓ '),
|
|
26
|
+
logo(' ▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓ '),
|
|
27
|
+
logo(' ▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓ '),
|
|
28
|
+
logo(' ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ '),
|
|
29
|
+
'',
|
|
30
|
+
chalk.bold(' 🧰 agent-harness') + chalk.dim(` v${version}`),
|
|
31
|
+
chalk.dim(' Prepare your codebase for AI agents'),
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
for (const line of bannerLines) console.log(line)
|
|
35
|
+
console.log()
|
|
36
|
+
console.log(' This tool will set up your project with a team of AI agents,')
|
|
37
|
+
console.log(' install skills, select models, and configure OpenCode.')
|
|
38
|
+
console.log()
|
|
39
|
+
|
|
40
|
+
// Only wait for Enter in a real interactive TTY
|
|
41
|
+
if (process.stdin.isTTY) {
|
|
42
|
+
console.log(chalk.bold(' Press Enter to begin...'))
|
|
43
|
+
console.log()
|
|
44
|
+
await new Promise(resolve => {
|
|
45
|
+
process.stdin.resume()
|
|
46
|
+
process.stdin.once('data', () => {
|
|
47
|
+
process.stdin.pause()
|
|
48
|
+
resolve()
|
|
49
|
+
})
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const longpathsConfigured = await ensureGitLongpaths()
|
|
54
|
+
if (longpathsConfigured) console.log(chalk.dim(' Git long paths enabled for this repository.'))
|
|
55
|
+
|
|
56
|
+
const scope = await chooseSourceScope()
|
|
57
|
+
|
|
58
|
+
const maxConcurrentAgents = await wizardSelect({
|
|
59
|
+
message: 'Max concurrent agents:',
|
|
60
|
+
default: 3,
|
|
61
|
+
choices: [
|
|
62
|
+
{ name: '1', value: 1, description: 'Serial: one agent at a time' },
|
|
63
|
+
{ name: '2', value: 2, description: 'Conservative: lower resource usage' },
|
|
64
|
+
{ name: '3 (default)', value: 3, description: 'Recommended: balances speed vs MCP load' },
|
|
65
|
+
{ name: '4', value: 4, description: 'Higher parallelism' },
|
|
66
|
+
{ name: '5', value: 5, description: 'Maximum: needs more resources' },
|
|
67
|
+
],
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
const installScope = await wizardSelect({
|
|
71
|
+
message: 'Install dependencies:',
|
|
72
|
+
default: 'local',
|
|
73
|
+
choices: [
|
|
74
|
+
{ name: 'Locally (default)', value: 'local', description: 'Install tools project-locally where possible' },
|
|
75
|
+
{ name: 'Globally', value: 'global', description: 'Install tools globally (affects all projects on this machine)' },
|
|
76
|
+
],
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
const preserve = await cleanAiFiles({ removeLegacyConfig: true })
|
|
80
|
+
const ctx = { ...preserve, ...scope, maxConcurrentAgents, installScope }
|
|
81
|
+
|
|
82
|
+
const { backlogPlatform, repoPlatform } = await choosePlatform()
|
|
83
|
+
|
|
84
|
+
await copyContentStep({ backlogPlatform, repoPlatform }, ctx)
|
|
85
|
+
|
|
86
|
+
const openspec = await initOpenspec()
|
|
87
|
+
|
|
88
|
+
const selectedModels = await chooseModels()
|
|
89
|
+
|
|
90
|
+
const tokenOpt = await tokenOptimizationStep({ ctx })
|
|
91
|
+
const { rtk, quota, simpleEnglish, codegraph, memory, humanizer } = tokenOpt
|
|
92
|
+
|
|
93
|
+
await installBrowser(ctx)
|
|
94
|
+
|
|
95
|
+
await writeHarnessConfig({
|
|
96
|
+
...ctx,
|
|
97
|
+
backlogPlatform,
|
|
98
|
+
repoPlatform,
|
|
99
|
+
openspec,
|
|
100
|
+
maxConcurrentAgents,
|
|
101
|
+
installScope,
|
|
102
|
+
additionalSkillsProvider: 'npx-skills',
|
|
103
|
+
...selectedModels,
|
|
104
|
+
optionalTools: { rtk, quota, simpleEnglish, codegraph, memory, humanizer },
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
const toGenerate = [
|
|
108
|
+
!ctx.hasDesign && 'DESIGN.md',
|
|
109
|
+
!ctx.hasArchitecture && 'ARCHITECTURE.md',
|
|
110
|
+
].filter(Boolean)
|
|
111
|
+
|
|
112
|
+
console.log()
|
|
113
|
+
console.log(chalk.bold.green('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'))
|
|
114
|
+
console.log(chalk.bold.green(' Onboarding complete!'))
|
|
115
|
+
console.log(chalk.bold.green('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'))
|
|
116
|
+
console.log()
|
|
117
|
+
console.log(' Open this project in OpenCode and type:')
|
|
118
|
+
console.log(chalk.bold(' "/init"'))
|
|
119
|
+
console.log(' Or run the following command in your terminal:')
|
|
120
|
+
console.log(chalk.bold(' opencode --prompt "/init" --model "opencode/big-pickle" '))
|
|
121
|
+
console.log()
|
|
122
|
+
console.log(chalk.dim(' Note: the first time you open OpenCode in this project,'))
|
|
123
|
+
console.log(chalk.dim(' it will install plugin dependencies. You may see a black'))
|
|
124
|
+
console.log(chalk.dim(' screen for a moment: this is normal. Wait for it to load.'))
|
|
125
|
+
console.log()
|
|
126
|
+
if (toGenerate.length > 0) {
|
|
127
|
+
console.log(` OpenCode will generate ${toGenerate.join(' and ')}`)
|
|
128
|
+
console.log(' from your actual codebase, then activate the agent team.')
|
|
129
|
+
} else {
|
|
130
|
+
console.log(' OpenCode will activate the agent team.')
|
|
131
|
+
}
|
|
132
|
+
console.log(` Source scope: ${ctx.sourceMode === 'parent-selected' ? ctx.sourceRoots.map(p => `../${p.split(/[/\\]/).pop()}`).join(', ') : 'current folder'}`)
|
|
133
|
+
console.log()
|
|
134
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: browser-automation
|
|
3
|
+
description: Reliable, composable browser automation using OpenCode Browser primitives. Use when capturing screenshots of a locally running app, clicking UI elements, reading page content, or automating browser interactions on localhost.
|
|
4
|
+
license: MIT
|
|
5
|
+
compatibility: Requires opencode-browser extension installed and running.
|
|
6
|
+
metadata:
|
|
7
|
+
author: copilots
|
|
8
|
+
version: "1.0"
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
This skill operates on `localhost` URLs only. Screenshots, clicks, typing, scrolling, and queries on locally running apps are in scope. External services (github.com, dev.azure.com, npmjs.com, etc.) are out of scope for browser tools.
|
|
12
|
+
|
|
13
|
+
The only exception: when the user selects "Others (Browser)" as their backlog platform during onboarding, the `pc-userstory` skill may navigate to work item URLs the user explicitly provides. That is the sole case where external navigation is permitted, and only to URLs the user explicitly gives you.
|
|
14
|
+
|
|
15
|
+
## Best-practice workflow
|
|
16
|
+
|
|
17
|
+
1. Inspect tabs with `browser_get_tabs`
|
|
18
|
+
2. Open new tabs with `browser_open_tab` when needed
|
|
19
|
+
3. Navigate with `browser_navigate` if needed
|
|
20
|
+
4. Wait for UI using `browser_query` with `timeoutMs`
|
|
21
|
+
5. Discover candidates using `browser_query` with `mode=list`
|
|
22
|
+
6. Click, type, or select using `index`
|
|
23
|
+
7. Confirm using `browser_query` or `browser_snapshot`
|
|
24
|
+
|
|
25
|
+
## CLI-first debugging
|
|
26
|
+
|
|
27
|
+
- List all available tools: `npx @different-ai/opencode-browser@4.6.1 tools`
|
|
28
|
+
- Run one tool directly: `npx @different-ai/opencode-browser@4.6.1 tool browser_status`
|
|
29
|
+
- Pass JSON args: `npx @different-ai/opencode-browser@4.6.1 tool browser_query --args '{"mode":"page_text"}'`
|
|
30
|
+
- Run smoke test: `npx @different-ai/opencode-browser@4.6.1 self-test`
|
|
31
|
+
- After `update`, reload the unpacked extension in `chrome://extensions`
|
|
32
|
+
|
|
33
|
+
## Selecting options
|
|
34
|
+
|
|
35
|
+
- Use `browser_select` for native `<select>` elements
|
|
36
|
+
- Prefer `value` or `label`; use `optionIndex` when needed
|
|
37
|
+
- Example: `browser_select({ selector: "select", value: "plugin" })`
|
|
38
|
+
|
|
39
|
+
## Query modes
|
|
40
|
+
|
|
41
|
+
- `text`: read visible text from a matched element
|
|
42
|
+
- `value`: read input values
|
|
43
|
+
- `list`: list many matches with text/metadata
|
|
44
|
+
- `exists`: check presence and count
|
|
45
|
+
- `page_text`: extract visible page text
|
|
46
|
+
|
|
47
|
+
## Opening tabs
|
|
48
|
+
|
|
49
|
+
- Use `browser_open_tab` to create a new tab, optionally with `url` and `active`
|
|
50
|
+
- Example: `browser_open_tab({ url: "https://example.com", active: false })`
|
|
51
|
+
|
|
52
|
+
## Troubleshooting
|
|
53
|
+
|
|
54
|
+
- If a selector fails, run `browser_query` with `mode=page_text` to confirm the content exists
|
|
55
|
+
- Use `mode=list` on broad selectors (`button`, `a`, `*[role="button"]`, `*[role="listitem"]`) and choose by index
|
|
56
|
+
- For inbox/chat panes, try text selectors first (`text:Subject line`) then verify selection with `browser_query`
|
|
57
|
+
- For scrollable containers, pass both `selector` and `x`/`y` to `browser_scroll` and then verify `scrollTop`
|
|
58
|
+
|
|
59
|
+
## Scope
|
|
60
|
+
|
|
61
|
+
- Screenshots of locally running app on `localhost` URLs: in scope
|
|
62
|
+
- Click, type, scroll, query on `localhost` pages: in scope
|
|
63
|
+
- Navigate to work item URLs the user explicitly provides (when backlog platform is "Others (Browser)"): in scope
|
|
64
|
+
- Navigate to external services for non-work-item purposes: out of scope
|
|
65
|
+
- DevOps or GitHub CLI operations via browser tools: out of scope
|
|
66
|
+
- Reading or modifying production systems via browser tools: out of scope
|