opencode-codeops 1.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.
- package/CHANGELOG.md +179 -0
- package/LICENSE +21 -0
- package/README.md +171 -0
- package/_shared/auto-design.md +129 -0
- package/_shared/layout-convention.md +198 -0
- package/_shared/quality-profile.md +134 -0
- package/_shared/recommendation-hardening.md +166 -0
- package/_shared/scope-expansion-control.md +176 -0
- package/_shared/spec-first-ordering.md +79 -0
- package/_shared/zero-ambiguity-gate.md +311 -0
- package/agent-templates/codebase-scout.md +17 -0
- package/agent-templates/concurrency-auditor.md +5 -0
- package/agent-templates/design-challenger.md +26 -0
- package/agent-templates/financial-integrity-auditor.md +5 -0
- package/agent-templates/perf-auditor.md +23 -0
- package/agent-templates/phase-reviewer.md +54 -0
- package/agent-templates/plan-task-executor-opus.md +46 -0
- package/agent-templates/plan-task-executor.md +43 -0
- package/agent-templates/preflight-auditor.md +45 -0
- package/agent-templates/security-auditor.md +42 -0
- package/agent-templates/semantics-reviewer.md +5 -0
- package/agent-templates/spec-test-author.md +29 -0
- package/agents/concurrency-auditor.md +15 -0
- package/agents/correctness-reviewer.md +66 -0
- package/agents/demanding-executor.md +58 -0
- package/agents/design-challenger.md +38 -0
- package/agents/executor.md +55 -0
- package/agents/explorer.md +29 -0
- package/agents/financial-integrity-auditor.md +15 -0
- package/agents/performance-auditor.md +35 -0
- package/agents/preflight-auditor.md +57 -0
- package/agents/security-auditor.md +54 -0
- package/agents/semantics-reviewer.md +15 -0
- package/agents/spec-test-author.md +41 -0
- package/bin/codeops-worktree +244 -0
- package/bin/index.mjs +106 -0
- package/bin/install-agents.mjs +453 -0
- package/bin/install-skills.mjs +466 -0
- package/bin/lib/opencode-install.mjs +185 -0
- package/install.sh +55 -0
- package/package.json +73 -0
- package/plugin/index.ts +181 -0
- package/references/domains/compiler-and-language.md +28 -0
- package/references/domains/data-and-migration.md +22 -0
- package/references/domains/distributed-and-concurrent.md +26 -0
- package/references/domains/financial-system.md +28 -0
- package/references/domains/selection.md +19 -0
- package/references/domains/web-application.md +23 -0
- package/schemas/codeops-config.schema.json +56 -0
- package/scripts/check-version.mjs +163 -0
- package/scripts/codeops-migrate.sh +355 -0
- package/scripts/codeops-roadmap-compact.sh +232 -0
- package/scripts/codeops-roadmap-sync.sh +275 -0
- package/scripts/codeops_outcomes.py +155 -0
- package/scripts/codeops_plan.py +239 -0
- package/scripts/codeops_plan_migrate.py +318 -0
- package/scripts/codeops_worktree_snapshot.py +99 -0
- package/scripts/install_agents.py +288 -0
- package/scripts/release.mjs +533 -0
- package/skills/analyze-project/SKILL.md +28 -0
- package/skills/clean-comments/SKILL.md +22 -0
- package/skills/exec-plan/SKILL.md +267 -0
- package/skills/exec-plan/commit-modes.md +113 -0
- package/skills/exec-plan/execution-protocol.md +471 -0
- package/skills/git-commit/SKILL.md +35 -0
- package/skills/github-issues/SKILL.md +38 -0
- package/skills/grill-me/SKILL.md +342 -0
- package/skills/make-plan/SKILL.md +282 -0
- package/skills/make-plan/quality-checklist.md +96 -0
- package/skills/make-plan/templates.md +535 -0
- package/skills/make-plan/zero-ambiguity-gate.md +19 -0
- package/skills/make-requirements/SKILL.md +268 -0
- package/skills/make-requirements/discovery-phases.md +255 -0
- package/skills/make-requirements/review-and-add.md +73 -0
- package/skills/make-requirements/templates.md +296 -0
- package/skills/make-requirements/zero-ambiguity-gate.md +18 -0
- package/skills/outcome-review/SKILL.md +34 -0
- package/skills/preflight/SKILL.md +310 -0
- package/skills/preflight/dimensions.md +181 -0
- package/skills/preflight/report-format.md +300 -0
- package/skills/retro-requirements/SKILL.md +218 -0
- package/skills/retro-requirements/confidence-classification.md +45 -0
- package/skills/retro-requirements/phases.md +609 -0
- package/skills/retro-requirements/triage-gate.md +135 -0
- package/skills/roadmap/SKILL.md +381 -0
- package/skills/roadmap/stage-hooks.md +80 -0
- package/skills/roadmap/template.md +200 -0
- package/skills/setup-codeops/SKILL.md +94 -0
- package/skills/setup-codeops/migration.md +106 -0
- package/skills/setup-codeops/scaffold.md +99 -0
- package/skills/setup-routing/SKILL.md +102 -0
- package/skills/setup-routing/routing.md +44 -0
- package/skills/techdocs/SKILL.md +199 -0
- package/skills/techdocs/authoring-and-update.md +178 -0
- package/skills/techdocs/templates.md +655 -0
- package/skills/techdocs/vitepress-setup.md +143 -0
- package/skills/upgrade-plan/SKILL.md +75 -0
- package/skills/upgrade-plan/content-quality-gate.md +35 -0
- package/skills/upgrade-plan/upgrade-checklists.md +107 -0
- package/standards/coding-standards-full.md +124 -0
- package/standards/coding-standards.md +64 -0
- package/standards/output-style.md +17 -0
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Install, inspect, and remove the CodeOps subagents for OpenCode.
|
|
4
|
+
*
|
|
5
|
+
* OpenCode discovers subagents from the filesystem: `.opencode/agents/` in a
|
|
6
|
+
* project, or `~/.config/opencode/agents/` globally. This script copies the
|
|
7
|
+
* packaged agent definitions (one `.md` file per role) into one of those
|
|
8
|
+
* locations.
|
|
9
|
+
*
|
|
10
|
+
* The shipped agent files are generated from `agent-templates/` by
|
|
11
|
+
* `scripts/install_agents.py`, which can also apply per-role model and
|
|
12
|
+
* permission overrides from `codeops/codeops.json`. This installer copies the
|
|
13
|
+
* packaged defaults; running the Python generator afterward re-applies any
|
|
14
|
+
* routing overrides a project has configured.
|
|
15
|
+
*
|
|
16
|
+
* Ownership and atomic-replace rules match the skills installer: only files the
|
|
17
|
+
* marker records are replaced, so hand-authored agents survive, and a re-run
|
|
18
|
+
* upgrades in place.
|
|
19
|
+
*
|
|
20
|
+
* Usage:
|
|
21
|
+
* opencode-codeops install-agents [options] Install or upgrade (default)
|
|
22
|
+
* opencode-codeops agents-status [options] Show the installed version
|
|
23
|
+
* opencode-codeops agents-uninstall [options] Remove the managed agents
|
|
24
|
+
*
|
|
25
|
+
* @module install-agents
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { existsSync, readFileSync, readdirSync, realpathSync, rmSync } from "node:fs"
|
|
29
|
+
import { dirname, join, resolve } from "node:path"
|
|
30
|
+
import { fileURLToPath } from "node:url"
|
|
31
|
+
|
|
32
|
+
import {
|
|
33
|
+
MARKER_FILE,
|
|
34
|
+
atomicReplace,
|
|
35
|
+
cleanStaleArtifacts,
|
|
36
|
+
ensureDir,
|
|
37
|
+
entryExists,
|
|
38
|
+
isSymlink,
|
|
39
|
+
linkEntry,
|
|
40
|
+
readMarker,
|
|
41
|
+
resolveTarget,
|
|
42
|
+
writeMarkerFile,
|
|
43
|
+
} from "./lib/opencode-install.mjs"
|
|
44
|
+
|
|
45
|
+
// This file lives in <package root>/bin/, so the parent of its directory is the
|
|
46
|
+
// package root that contains agents/ and package.json.
|
|
47
|
+
const PACKAGE_ROOT = dirname(dirname(fileURLToPath(import.meta.url)))
|
|
48
|
+
|
|
49
|
+
export { MARKER_FILE, readMarker }
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Reads the marker only when it is an agents marker.
|
|
53
|
+
*
|
|
54
|
+
* Both installers use the same marker file name, so a marker written by the
|
|
55
|
+
* skills installer must not be mistaken for one that lists agents. Treating it
|
|
56
|
+
* as absent is safe: it makes the install behave like a first run instead of
|
|
57
|
+
* silently skipping every packaged agent.
|
|
58
|
+
*
|
|
59
|
+
* @param targetDir - Agents directory that may hold a marker
|
|
60
|
+
* @returns The parsed agents marker, or `undefined`
|
|
61
|
+
*/
|
|
62
|
+
function readAgentsMarker(targetDir) {
|
|
63
|
+
const marker = readMarker(targetDir)
|
|
64
|
+
return marker && Array.isArray(marker.agents) ? marker : undefined
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Reads this package's own version, recorded in the install marker.
|
|
69
|
+
*
|
|
70
|
+
* @returns The package version, or `"0.0.0"` when package.json cannot be read
|
|
71
|
+
*/
|
|
72
|
+
export function readPackageVersion() {
|
|
73
|
+
try {
|
|
74
|
+
const manifest = JSON.parse(readFileSync(join(PACKAGE_ROOT, "package.json"), "utf-8"))
|
|
75
|
+
return typeof manifest.version === "string" ? manifest.version : "0.0.0"
|
|
76
|
+
} catch {
|
|
77
|
+
return "0.0.0"
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Resolves the directory that holds the packaged agent definitions.
|
|
83
|
+
*
|
|
84
|
+
* The `CODEOPS_PLUGIN_ROOT` environment variable overrides the default, which
|
|
85
|
+
* lets a checkout run the installer before the package is published.
|
|
86
|
+
*
|
|
87
|
+
* @param override - Optional explicit agents directory
|
|
88
|
+
* @returns Absolute path to the agents directory
|
|
89
|
+
*/
|
|
90
|
+
export function resolveSourceDir(override) {
|
|
91
|
+
if (override) return resolve(override)
|
|
92
|
+
|
|
93
|
+
const envRoot = process.env.CODEOPS_PLUGIN_ROOT
|
|
94
|
+
if (envRoot && existsSync(join(envRoot, "agents"))) {
|
|
95
|
+
return join(envRoot, "agents")
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return join(PACKAGE_ROOT, "agents")
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Lists the agent definition files shipped in a directory.
|
|
103
|
+
*
|
|
104
|
+
* @param sourceDir - Directory to scan
|
|
105
|
+
* @returns Sorted file names ending in `.md`
|
|
106
|
+
*/
|
|
107
|
+
export function listAgents(sourceDir) {
|
|
108
|
+
if (!existsSync(sourceDir)) return []
|
|
109
|
+
|
|
110
|
+
return readdirSync(sourceDir, { withFileTypes: true })
|
|
111
|
+
.filter((entry) => entry.isFile() && entry.name.endsWith(".md"))
|
|
112
|
+
.map((entry) => entry.name)
|
|
113
|
+
.sort()
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Writes the install marker that records ownership and the installed version.
|
|
118
|
+
*
|
|
119
|
+
* @param targetDir - Agents directory to mark
|
|
120
|
+
* @param details - Marker contents
|
|
121
|
+
* @param details.version - Installed package version
|
|
122
|
+
* @param details.agents - Agent file names the package owns
|
|
123
|
+
*/
|
|
124
|
+
export function writeMarker(targetDir, { version, agents }) {
|
|
125
|
+
writeMarkerFile(targetDir, {
|
|
126
|
+
schema: 1,
|
|
127
|
+
source: "opencode-codeops",
|
|
128
|
+
version,
|
|
129
|
+
installedAt: new Date().toISOString(),
|
|
130
|
+
agents,
|
|
131
|
+
})
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Installs one agent file by replacing it atomically, or by linking to it when
|
|
136
|
+
* `link` is set (used by development checkouts).
|
|
137
|
+
*
|
|
138
|
+
* @param details - Install inputs
|
|
139
|
+
* @param details.sourceDir - Agents directory holding the packaged file
|
|
140
|
+
* @param details.targetDir - Agents directory to install into
|
|
141
|
+
* @param details.name - Agent file name
|
|
142
|
+
* @param details.dryRun - Report only, write nothing
|
|
143
|
+
* @param details.link - Symlink to the source instead of copying
|
|
144
|
+
* @returns Whether a previous file existed
|
|
145
|
+
*/
|
|
146
|
+
function installOneAgent({ sourceDir, targetDir, name, dryRun, link }) {
|
|
147
|
+
const from = join(sourceDir, name)
|
|
148
|
+
const dest = join(targetDir, name)
|
|
149
|
+
const existed = entryExists(dest)
|
|
150
|
+
|
|
151
|
+
if (dryRun) return { existed }
|
|
152
|
+
|
|
153
|
+
if (link) {
|
|
154
|
+
ensureDir(targetDir)
|
|
155
|
+
linkEntry({ from, dest, type: "file" })
|
|
156
|
+
return { existed }
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return atomicReplace({ from, targetDir, name, recursive: false })
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Installs or upgrades every packaged agent into a target directory.
|
|
164
|
+
*
|
|
165
|
+
* Agent files named by an existing marker, plus every packaged file on a first
|
|
166
|
+
* run, are replaced. A same-named file the marker does not own is skipped
|
|
167
|
+
* unless `force` is set, so a hand-authored or routing-generated agent is never
|
|
168
|
+
* overwritten by accident.
|
|
169
|
+
*
|
|
170
|
+
* @param details - Install inputs
|
|
171
|
+
* @param details.sourceDir - Agents directory holding the packaged files
|
|
172
|
+
* @param details.targetDir - Agents directory to install into
|
|
173
|
+
* @param details.version - Version stored in the marker
|
|
174
|
+
* @param details.force - Replace same-named files the marker does not own
|
|
175
|
+
* @param details.dryRun - Report only, write nothing
|
|
176
|
+
* @param details.link - Symlink to the source instead of copying
|
|
177
|
+
* @returns Counts plus the agent names recorded in the marker
|
|
178
|
+
*/
|
|
179
|
+
export function installAgents({
|
|
180
|
+
sourceDir,
|
|
181
|
+
targetDir,
|
|
182
|
+
version,
|
|
183
|
+
force = false,
|
|
184
|
+
dryRun = false,
|
|
185
|
+
link = false,
|
|
186
|
+
}) {
|
|
187
|
+
const names = listAgents(sourceDir)
|
|
188
|
+
const marker = readAgentsMarker(targetDir)
|
|
189
|
+
const managed = marker ? new Set(marker.agents ?? []) : null
|
|
190
|
+
const counts = { agents: names.length, installed: 0, replaced: 0, skipped: 0 }
|
|
191
|
+
const owned = []
|
|
192
|
+
const prefix = dryRun ? "[dry-run] would " : ""
|
|
193
|
+
|
|
194
|
+
if (!dryRun) {
|
|
195
|
+
ensureDir(targetDir)
|
|
196
|
+
cleanStaleArtifacts(targetDir)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
for (const name of names) {
|
|
200
|
+
const dest = join(targetDir, name)
|
|
201
|
+
|
|
202
|
+
if (entryExists(dest) && managed && !managed.has(name) && !force) {
|
|
203
|
+
console.log(`conflict, skipped (not managed by opencode-codeops; use --force): ${dest}`)
|
|
204
|
+
counts.skipped += 1
|
|
205
|
+
continue
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const { existed } = installOneAgent({ sourceDir, targetDir, name, dryRun, link })
|
|
209
|
+
owned.push(name)
|
|
210
|
+
if (existed) counts.replaced += 1
|
|
211
|
+
else counts.installed += 1
|
|
212
|
+
console.log(`${prefix}${existed ? "replace" : "install"}: ${dest}`)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (!dryRun) writeMarker(targetDir, { version, agents: owned })
|
|
216
|
+
|
|
217
|
+
return { ...counts, owned }
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Removes only the agent files recorded as owned by this package.
|
|
222
|
+
*
|
|
223
|
+
* @param details - Uninstall inputs
|
|
224
|
+
* @param details.targetDir - Agents directory to clean
|
|
225
|
+
* @param details.dryRun - Report only, write nothing
|
|
226
|
+
* @returns Removed file names plus the marker outcome, or a reason it refused
|
|
227
|
+
*/
|
|
228
|
+
export function uninstallAgents({ targetDir, dryRun = false }) {
|
|
229
|
+
const marker = readAgentsMarker(targetDir)
|
|
230
|
+
|
|
231
|
+
if (!marker) {
|
|
232
|
+
return { removed: [], markerRemoved: false, error: "no opencode-codeops marker found" }
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const removed = []
|
|
236
|
+
|
|
237
|
+
for (const name of marker.agents ?? []) {
|
|
238
|
+
const dest = join(targetDir, name)
|
|
239
|
+
if (!entryExists(dest)) continue
|
|
240
|
+
|
|
241
|
+
if (!dryRun) rmSync(dest, { force: true })
|
|
242
|
+
removed.push(name)
|
|
243
|
+
console.log(`${dryRun ? "would remove" : "removed"}: ${dest}`)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (!dryRun) rmSync(join(targetDir, MARKER_FILE), { force: true })
|
|
247
|
+
|
|
248
|
+
return { removed, markerRemoved: true }
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/** Prints command usage. */
|
|
252
|
+
function printUsage() {
|
|
253
|
+
console.log(`Install the CodeOps subagents so OpenCode can discover them.
|
|
254
|
+
|
|
255
|
+
Usage:
|
|
256
|
+
opencode-codeops install-agents [options] Install or upgrade (default)
|
|
257
|
+
opencode-codeops agents-status [options] Show the installed version
|
|
258
|
+
opencode-codeops agents-uninstall [options] Remove the managed agents
|
|
259
|
+
|
|
260
|
+
Options:
|
|
261
|
+
--global Use ~/.config/opencode/agents (default)
|
|
262
|
+
--project Use ./.opencode/agents
|
|
263
|
+
--target <dir> Use a custom agents directory
|
|
264
|
+
--source <dir> Override the packaged agents directory
|
|
265
|
+
--link Symlink to the source instead of copying (development)
|
|
266
|
+
--dry-run Show what would happen without writing files
|
|
267
|
+
--force Replace same-named files this package does not own
|
|
268
|
+
-h, --help Show this help`)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Parses argv into a subcommand and options.
|
|
273
|
+
*
|
|
274
|
+
* The subcommand is optional and defaults to `install`. The dispatcher passes
|
|
275
|
+
* `agents-status` and `agents-uninstall`; the short forms are accepted too.
|
|
276
|
+
*
|
|
277
|
+
* @param argv - Arguments after the executable
|
|
278
|
+
* @returns The command, parsed options, and an error message when invalid
|
|
279
|
+
*/
|
|
280
|
+
export function parseArgs(argv) {
|
|
281
|
+
const options = {
|
|
282
|
+
project: false,
|
|
283
|
+
target: null,
|
|
284
|
+
source: null,
|
|
285
|
+
dryRun: false,
|
|
286
|
+
force: false,
|
|
287
|
+
link: false,
|
|
288
|
+
help: false,
|
|
289
|
+
}
|
|
290
|
+
let command = "install"
|
|
291
|
+
let index = 0
|
|
292
|
+
|
|
293
|
+
const first = argv[0]
|
|
294
|
+
if (first && !first.startsWith("-")) {
|
|
295
|
+
if (first === "install" || first === "install-agents") command = "install"
|
|
296
|
+
else if (first === "status" || first === "agents-status") command = "status"
|
|
297
|
+
else if (first === "uninstall" || first === "agents-uninstall") command = "uninstall"
|
|
298
|
+
else if (first === "help") command = "help"
|
|
299
|
+
else return { command, options, error: `unknown command '${first}'` }
|
|
300
|
+
index = 1
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
for (; index < argv.length; index += 1) {
|
|
304
|
+
const arg = argv[index]
|
|
305
|
+
|
|
306
|
+
if (arg === "--global") options.project = false
|
|
307
|
+
else if (arg === "--project") options.project = true
|
|
308
|
+
else if (arg === "--dry-run") options.dryRun = true
|
|
309
|
+
else if (arg === "--force") options.force = true
|
|
310
|
+
else if (arg === "--link") options.link = true
|
|
311
|
+
else if (arg === "--help" || arg === "-h") options.help = true
|
|
312
|
+
else if (arg === "--target" || arg === "--source") {
|
|
313
|
+
const value = argv[index + 1]
|
|
314
|
+
if (!value || value.startsWith("--")) {
|
|
315
|
+
return { command, options, error: `${arg} requires a directory argument` }
|
|
316
|
+
}
|
|
317
|
+
if (arg === "--target") options.target = value
|
|
318
|
+
else options.source = value
|
|
319
|
+
index += 1
|
|
320
|
+
} else {
|
|
321
|
+
return { command, options, error: `unknown argument '${arg}'` }
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return { command, options }
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/** Prints the installed version and health for one target directory. */
|
|
329
|
+
function printStatus({ targetDir, sourceVersion }) {
|
|
330
|
+
const marker = readAgentsMarker(targetDir)
|
|
331
|
+
|
|
332
|
+
if (marker) {
|
|
333
|
+
const owned = marker.agents ?? []
|
|
334
|
+
console.log(`agents: installed: v${marker.version} (${owned.length} agents)`)
|
|
335
|
+
if (sourceVersion && marker.version !== sourceVersion) {
|
|
336
|
+
console.log(`agents: update available: v${sourceVersion} — run install-agents to upgrade`)
|
|
337
|
+
}
|
|
338
|
+
const missing = owned.filter((name) => !entryExists(join(targetDir, name)))
|
|
339
|
+
if (missing.length > 0) {
|
|
340
|
+
console.log(`agents: warning: missing managed agents: ${missing.join(", ")}`)
|
|
341
|
+
}
|
|
342
|
+
return
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
console.log("agents: not installed")
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Runs the installer CLI.
|
|
350
|
+
*
|
|
351
|
+
* @param argv - Arguments after the executable
|
|
352
|
+
* @param io - Injectable environment for tests
|
|
353
|
+
* @param io.cwd - Project root used for `--project`
|
|
354
|
+
* @param io.source - Override the packaged agents directory
|
|
355
|
+
* @param io.version - Override the version recorded in the marker
|
|
356
|
+
* @returns Process exit code
|
|
357
|
+
*/
|
|
358
|
+
export function main(argv, io = {}) {
|
|
359
|
+
if (argv.length === 0) {
|
|
360
|
+
printUsage()
|
|
361
|
+
return 0
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const { command, options, error } = parseArgs(argv)
|
|
365
|
+
if (error) {
|
|
366
|
+
console.error(`error: ${error}`)
|
|
367
|
+
return 2
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (options.help || command === "help") {
|
|
371
|
+
printUsage()
|
|
372
|
+
return 0
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const cwd = io.cwd ?? process.cwd()
|
|
376
|
+
const version = io.version ?? readPackageVersion()
|
|
377
|
+
const sourceDir = io.source ?? resolveSourceDir(options.source)
|
|
378
|
+
const targetDir = resolveTarget(options, cwd, "agents")
|
|
379
|
+
|
|
380
|
+
if (!existsSync(sourceDir)) {
|
|
381
|
+
console.error(`error: agents directory not found: ${sourceDir}`)
|
|
382
|
+
return 1
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
console.log(`Source: ${sourceDir}`)
|
|
386
|
+
console.log(`Target: ${targetDir}`)
|
|
387
|
+
|
|
388
|
+
// Writing through a symlinked target would mutate whatever the link points
|
|
389
|
+
// at. Refuse unless the user explicitly overrides with --force. Status is
|
|
390
|
+
// read-only and always allowed.
|
|
391
|
+
if (command !== "status" && !options.force && isSymlink(targetDir)) {
|
|
392
|
+
console.error(
|
|
393
|
+
`error: target agents directory is a symlink to ${realpathSync(targetDir)}; ` +
|
|
394
|
+
"install/uninstall would write through it. Pass --force to proceed."
|
|
395
|
+
)
|
|
396
|
+
return 2
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
if (command === "status") {
|
|
400
|
+
printStatus({ targetDir, sourceVersion: version })
|
|
401
|
+
return 0
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
if (command === "uninstall") {
|
|
405
|
+
const result = uninstallAgents({ targetDir, dryRun: options.dryRun })
|
|
406
|
+
if (result.error) {
|
|
407
|
+
console.error(`error: ${result.error} at ${targetDir}`)
|
|
408
|
+
return 1
|
|
409
|
+
}
|
|
410
|
+
console.log(
|
|
411
|
+
`${options.dryRun ? "would remove" : "removed"} ${result.removed.length} agent(s); ` +
|
|
412
|
+
`marker ${options.dryRun ? "would be removed" : "removed"}`
|
|
413
|
+
)
|
|
414
|
+
return 0
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const counts = installAgents({
|
|
418
|
+
sourceDir,
|
|
419
|
+
targetDir,
|
|
420
|
+
version,
|
|
421
|
+
force: options.force,
|
|
422
|
+
dryRun: options.dryRun,
|
|
423
|
+
link: options.link,
|
|
424
|
+
})
|
|
425
|
+
const mode = options.dryRun ? " (dry-run, nothing written)" : ""
|
|
426
|
+
console.log(
|
|
427
|
+
`Done${mode}: ${counts.agents} agents | ` +
|
|
428
|
+
`installed ${counts.installed}, replaced ${counts.replaced}, ${counts.skipped} skipped`
|
|
429
|
+
)
|
|
430
|
+
if (counts.skipped > 0) {
|
|
431
|
+
console.log("Re-run with --force to replace skipped files.")
|
|
432
|
+
}
|
|
433
|
+
return 0
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* True when this module is the process entry point.
|
|
438
|
+
*
|
|
439
|
+
* @returns True when this file is the entry point
|
|
440
|
+
*/
|
|
441
|
+
function isMainModule() {
|
|
442
|
+
if (!process.argv[1]) return false
|
|
443
|
+
|
|
444
|
+
try {
|
|
445
|
+
return fileURLToPath(import.meta.url) === realpathSync(process.argv[1])
|
|
446
|
+
} catch {
|
|
447
|
+
return false
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
if (isMainModule()) {
|
|
452
|
+
process.exitCode = main(process.argv.slice(2))
|
|
453
|
+
}
|