pluidr 0.7.0 → 0.7.1
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 +20 -20
- package/README.md +365 -365
- package/bin/pluidr.js +3 -3
- package/package.json +45 -45
- package/src/cli/commands/doctor.js +101 -101
- package/src/cli/commands/init.js +43 -43
- package/src/cli/commands/launch.js +46 -44
- package/src/cli/commands/uninstall.js +63 -63
- package/src/cli/commands/update.js +6 -6
- package/src/cli/index.js +57 -57
- package/src/cli/wizard/selectModelTier.js +40 -40
- package/src/core/agentPromptWriter.js +32 -32
- package/src/core/agentPromptWriter.test.js +56 -56
- package/src/core/backup.js +40 -40
- package/src/core/configBuilder.js +32 -32
- package/src/core/configBuilder.test.js +93 -93
- package/src/core/configWriter.js +10 -10
- package/src/core/identityHeader.js +8 -8
- package/src/core/paths.js +9 -9
- package/src/core/paths.test.js +21 -21
- package/src/core/pluginWriter.js +29 -29
- package/src/core/squeezeInstaller.js +161 -161
- package/src/core/squeezeInstaller.test.js +75 -75
- package/src/core/version.js +8 -8
- package/src/core/versionCheck.js +44 -35
- package/src/core/versionCheck.test.js +12 -2
- package/src/plugins/README.md +68 -68
- package/src/plugins/pluidr-squeeze.js +77 -77
- package/src/templates/agent-prompts/auditor.txt +20 -20
- package/src/templates/agent-prompts/coder.txt +87 -87
- package/src/templates/agent-prompts/compose-reporter.txt +55 -55
- package/src/templates/agent-prompts/composer.txt +430 -430
- package/src/templates/agent-prompts/debug-reporter.txt +65 -65
- package/src/templates/agent-prompts/debugger.txt +151 -151
- package/src/templates/agent-prompts/fixer.txt +66 -66
- package/src/templates/agent-prompts/hierarchy.txt +96 -96
- package/src/templates/agent-prompts/inspector.txt +79 -79
- package/src/templates/agent-prompts/patcher.txt +20 -20
- package/src/templates/agent-prompts/plan-checker.txt +45 -45
- package/src/templates/agent-prompts/plan-writer.txt +57 -57
- package/src/templates/agent-prompts/probe-reporter.txt +62 -62
- package/src/templates/agent-prompts/prober.txt +90 -90
- package/src/templates/agent-prompts/researcher.txt +48 -48
- package/src/templates/agent-prompts/reviewer.txt +57 -57
- package/src/templates/agent-prompts/tester.txt +66 -66
- package/src/templates/agent-prompts/tracer.txt +33 -33
- package/src/templates/model-defaults.json +73 -73
- package/src/templates/opencode.config.json +481 -481
package/src/cli/index.js
CHANGED
|
@@ -1,57 +1,57 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs"
|
|
2
|
-
import { resolve, dirname } from "node:path"
|
|
3
|
-
import { fileURLToPath } from "node:url"
|
|
4
|
-
import { runInit } from "./commands/init.js"
|
|
5
|
-
import { runUninstall } from "./commands/uninstall.js"
|
|
6
|
-
import { runUpdate } from "./commands/update.js"
|
|
7
|
-
import { runDoctor } from "./commands/doctor.js"
|
|
8
|
-
import { runLaunch } from "./commands/launch.js"
|
|
9
|
-
import { version } from "../core/version.js"
|
|
10
|
-
|
|
11
|
-
const HELP = `Usage: pluidr [command]
|
|
12
|
-
|
|
13
|
-
pluidr
|
|
14
|
-
pluidr init
|
|
15
|
-
pluidr update Check
|
|
16
|
-
pluidr doctor
|
|
17
|
-
pluidr uninstall Remove Pluidr artifacts and restore previous config
|
|
18
|
-
--version, -v Print version number
|
|
19
|
-
--help, -h Show this help message`
|
|
20
|
-
|
|
21
|
-
export function run(argv) {
|
|
22
|
-
const cmd = argv[2]
|
|
23
|
-
|
|
24
|
-
if (!cmd) {
|
|
25
|
-
return runLaunch()
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
if (cmd === "--help" || cmd === "-h") {
|
|
29
|
-
console.log(HELP)
|
|
30
|
-
return
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (cmd === "--version" || cmd === "-v") {
|
|
34
|
-
console.log(version)
|
|
35
|
-
return
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
if (cmd === "init") {
|
|
39
|
-
return runInit()
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
if (cmd === "uninstall") {
|
|
43
|
-
return runUninstall()
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (cmd === "update") {
|
|
47
|
-
return runUpdate()
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
if (cmd === "doctor") {
|
|
51
|
-
return runDoctor()
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
console.error(`Unknown command: ${cmd}\n`)
|
|
55
|
-
console.log(HELP)
|
|
56
|
-
process.exit(1)
|
|
57
|
-
}
|
|
1
|
+
import { readFileSync } from "node:fs"
|
|
2
|
+
import { resolve, dirname } from "node:path"
|
|
3
|
+
import { fileURLToPath } from "node:url"
|
|
4
|
+
import { runInit } from "./commands/init.js"
|
|
5
|
+
import { runUninstall } from "./commands/uninstall.js"
|
|
6
|
+
import { runUpdate } from "./commands/update.js"
|
|
7
|
+
import { runDoctor } from "./commands/doctor.js"
|
|
8
|
+
import { runLaunch } from "./commands/launch.js"
|
|
9
|
+
import { version } from "../core/version.js"
|
|
10
|
+
|
|
11
|
+
const HELP = `Usage: pluidr [command]
|
|
12
|
+
|
|
13
|
+
pluidr Launch OpenCode powered by Pluidr agents
|
|
14
|
+
pluidr init Install and configure the Pluidr agent pipeline
|
|
15
|
+
pluidr update Check and update the Pluidr system
|
|
16
|
+
pluidr doctor Diagnose the integrity of the current installation
|
|
17
|
+
pluidr uninstall Remove Pluidr artifacts and restore the previous config
|
|
18
|
+
--version, -v Print the current version number
|
|
19
|
+
--help, -h Show this help message`
|
|
20
|
+
|
|
21
|
+
export function run(argv) {
|
|
22
|
+
const cmd = argv[2]
|
|
23
|
+
|
|
24
|
+
if (!cmd) {
|
|
25
|
+
return runLaunch()
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (cmd === "--help" || cmd === "-h") {
|
|
29
|
+
console.log(HELP)
|
|
30
|
+
return
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (cmd === "--version" || cmd === "-v") {
|
|
34
|
+
console.log(version)
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (cmd === "init") {
|
|
39
|
+
return runInit()
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (cmd === "uninstall") {
|
|
43
|
+
return runUninstall()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (cmd === "update") {
|
|
47
|
+
return runUpdate()
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (cmd === "doctor") {
|
|
51
|
+
return runDoctor()
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
console.error(`Unknown command: ${cmd}\n`)
|
|
55
|
+
console.log(HELP)
|
|
56
|
+
process.exit(1)
|
|
57
|
+
}
|
|
@@ -1,40 +1,40 @@
|
|
|
1
|
-
import { select, text, isCancel } from "@clack/prompts"
|
|
2
|
-
|
|
3
|
-
const TIER_LABELS = {
|
|
4
|
-
reasoning: "Model for reasoning task (orchestrators + deep analysis)",
|
|
5
|
-
precision: "Model for precision task (quality gates + verification)",
|
|
6
|
-
fast: "Model for fast/cheap task (builders + formatters)",
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export async function selectModelTier(modelDefaults) {
|
|
10
|
-
const choices = {}
|
|
11
|
-
for (const [tierKey, tierInfo] of Object.entries(modelDefaults)) {
|
|
12
|
-
const defaultModel = `${tierInfo.provider}/${tierInfo.model}`
|
|
13
|
-
const label = TIER_LABELS[tierKey] ?? tierKey
|
|
14
|
-
|
|
15
|
-
const recommendedOptions = (tierInfo.recommended || []).map((r) => ({
|
|
16
|
-
value: r.value,
|
|
17
|
-
label: r.label,
|
|
18
|
-
}))
|
|
19
|
-
|
|
20
|
-
const options = [
|
|
21
|
-
...recommendedOptions,
|
|
22
|
-
{ value: "__custom__", label: "custom (enter provider/model)" },
|
|
23
|
-
]
|
|
24
|
-
|
|
25
|
-
const answer = await select({
|
|
26
|
-
message: label,
|
|
27
|
-
options,
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
if (isCancel(answer)) {
|
|
31
|
-
choices[tierKey] = defaultModel
|
|
32
|
-
} else if (answer === "__custom__") {
|
|
33
|
-
const customModel = await text({ message: "Enter model:" })
|
|
34
|
-
choices[tierKey] = isCancel(customModel) ? defaultModel : (customModel || defaultModel)
|
|
35
|
-
} else {
|
|
36
|
-
choices[tierKey] = answer
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
return choices
|
|
40
|
-
}
|
|
1
|
+
import { select, text, isCancel } from "@clack/prompts"
|
|
2
|
+
|
|
3
|
+
const TIER_LABELS = {
|
|
4
|
+
reasoning: "Model for reasoning task (orchestrators + deep analysis)",
|
|
5
|
+
precision: "Model for precision task (quality gates + verification)",
|
|
6
|
+
fast: "Model for fast/cheap task (builders + formatters)",
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export async function selectModelTier(modelDefaults) {
|
|
10
|
+
const choices = {}
|
|
11
|
+
for (const [tierKey, tierInfo] of Object.entries(modelDefaults)) {
|
|
12
|
+
const defaultModel = `${tierInfo.provider}/${tierInfo.model}`
|
|
13
|
+
const label = TIER_LABELS[tierKey] ?? tierKey
|
|
14
|
+
|
|
15
|
+
const recommendedOptions = (tierInfo.recommended || []).map((r) => ({
|
|
16
|
+
value: r.value,
|
|
17
|
+
label: r.label,
|
|
18
|
+
}))
|
|
19
|
+
|
|
20
|
+
const options = [
|
|
21
|
+
...recommendedOptions,
|
|
22
|
+
{ value: "__custom__", label: "custom (enter provider/model)" },
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
const answer = await select({
|
|
26
|
+
message: label,
|
|
27
|
+
options,
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
if (isCancel(answer)) {
|
|
31
|
+
choices[tierKey] = defaultModel
|
|
32
|
+
} else if (answer === "__custom__") {
|
|
33
|
+
const customModel = await text({ message: "Enter model:" })
|
|
34
|
+
choices[tierKey] = isCancel(customModel) ? defaultModel : (customModel || defaultModel)
|
|
35
|
+
} else {
|
|
36
|
+
choices[tierKey] = answer
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return choices
|
|
40
|
+
}
|
|
@@ -1,32 +1,32 @@
|
|
|
1
|
-
import { mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs"
|
|
2
|
-
import { basename, extname, resolve } from "node:path"
|
|
3
|
-
import { getPromptsDir } from "./paths.js"
|
|
4
|
-
import IDENTITY_HEADER from "./identityHeader.js"
|
|
5
|
-
|
|
6
|
-
// hierarchy.txt is a global reference document (referenced from
|
|
7
|
-
// opencode.config.json's `instructions`), not an agent prompt. It must be
|
|
8
|
-
// copied verbatim — no agent-identity header.
|
|
9
|
-
const REFERENCE_FILES = new Set(["hierarchy"])
|
|
10
|
-
|
|
11
|
-
export function writeAgentPrompts(templatesDir, destDir) {
|
|
12
|
-
const sourceDir = resolve(templatesDir, "agent-prompts")
|
|
13
|
-
if (!destDir) destDir = getPromptsDir()
|
|
14
|
-
|
|
15
|
-
mkdirSync(destDir, { recursive: true })
|
|
16
|
-
|
|
17
|
-
for (const file of readdirSync(sourceDir)) {
|
|
18
|
-
if (extname(file) !== ".txt") continue
|
|
19
|
-
|
|
20
|
-
const sourcePath = resolve(sourceDir, file)
|
|
21
|
-
const destPath = resolve(destDir, file)
|
|
22
|
-
const content = readFileSync(sourcePath, "utf-8")
|
|
23
|
-
|
|
24
|
-
const agentName = basename(file, ".txt")
|
|
25
|
-
if (REFERENCE_FILES.has(agentName)) {
|
|
26
|
-
writeFileSync(destPath, content, "utf-8")
|
|
27
|
-
continue
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
writeFileSync(destPath, IDENTITY_HEADER(agentName) + content, "utf-8")
|
|
31
|
-
}
|
|
32
|
-
}
|
|
1
|
+
import { mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs"
|
|
2
|
+
import { basename, extname, resolve } from "node:path"
|
|
3
|
+
import { getPromptsDir } from "./paths.js"
|
|
4
|
+
import IDENTITY_HEADER from "./identityHeader.js"
|
|
5
|
+
|
|
6
|
+
// hierarchy.txt is a global reference document (referenced from
|
|
7
|
+
// opencode.config.json's `instructions`), not an agent prompt. It must be
|
|
8
|
+
// copied verbatim — no agent-identity header.
|
|
9
|
+
const REFERENCE_FILES = new Set(["hierarchy"])
|
|
10
|
+
|
|
11
|
+
export function writeAgentPrompts(templatesDir, destDir) {
|
|
12
|
+
const sourceDir = resolve(templatesDir, "agent-prompts")
|
|
13
|
+
if (!destDir) destDir = getPromptsDir()
|
|
14
|
+
|
|
15
|
+
mkdirSync(destDir, { recursive: true })
|
|
16
|
+
|
|
17
|
+
for (const file of readdirSync(sourceDir)) {
|
|
18
|
+
if (extname(file) !== ".txt") continue
|
|
19
|
+
|
|
20
|
+
const sourcePath = resolve(sourceDir, file)
|
|
21
|
+
const destPath = resolve(destDir, file)
|
|
22
|
+
const content = readFileSync(sourcePath, "utf-8")
|
|
23
|
+
|
|
24
|
+
const agentName = basename(file, ".txt")
|
|
25
|
+
if (REFERENCE_FILES.has(agentName)) {
|
|
26
|
+
writeFileSync(destPath, content, "utf-8")
|
|
27
|
+
continue
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
writeFileSync(destPath, IDENTITY_HEADER(agentName) + content, "utf-8")
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -1,56 +1,56 @@
|
|
|
1
|
-
import { describe, it, after } from "node:test"
|
|
2
|
-
import assert from "node:assert"
|
|
3
|
-
import { existsSync, mkdtempSync, writeFileSync, readFileSync, rmSync, mkdirSync } from "node:fs"
|
|
4
|
-
import { join } from "node:path"
|
|
5
|
-
import { tmpdir } from "node:os"
|
|
6
|
-
import { writeAgentPrompts } from "./agentPromptWriter.js"
|
|
7
|
-
|
|
8
|
-
describe("agentPromptWriter", () => {
|
|
9
|
-
const destDir = mkdtempSync(join(tmpdir(), "pluidr-apw-dest-"))
|
|
10
|
-
|
|
11
|
-
after(() => {
|
|
12
|
-
// Clean up any files written during tests
|
|
13
|
-
if (existsSync(destDir)) rmSync(destDir, { recursive: true })
|
|
14
|
-
})
|
|
15
|
-
|
|
16
|
-
it("prepends identity header to agent prompts", () => {
|
|
17
|
-
const tmpDir = mkdtempSync(join(tmpdir(), "apw-source-"))
|
|
18
|
-
const agentPromptsDir = join(tmpDir, "agent-prompts")
|
|
19
|
-
mkdirSync(agentPromptsDir, { recursive: true })
|
|
20
|
-
|
|
21
|
-
writeFileSync(join(agentPromptsDir, "coder.txt"), "test instructions", "utf-8")
|
|
22
|
-
writeFileSync(join(agentPromptsDir, "compose-reporter.txt"), "write docs", "utf-8")
|
|
23
|
-
|
|
24
|
-
writeAgentPrompts(tmpDir, destDir)
|
|
25
|
-
|
|
26
|
-
const coderContent = readFileSync(join(destDir, "coder.txt"), "utf-8")
|
|
27
|
-
const composeReporterContent = readFileSync(join(destDir, "compose-reporter.txt"), "utf-8")
|
|
28
|
-
|
|
29
|
-
assert.ok(coderContent.startsWith('You are the "coder" agent.\n\n'),
|
|
30
|
-
"coder should have identity header")
|
|
31
|
-
assert.ok(composeReporterContent.startsWith('You are the "compose-reporter" agent.\n\n'),
|
|
32
|
-
"compose-reporter should have identity header")
|
|
33
|
-
assert.ok(coderContent.includes("test instructions"),
|
|
34
|
-
"original content preserved in coder")
|
|
35
|
-
assert.ok(composeReporterContent.includes("write docs"),
|
|
36
|
-
"original content preserved in compose-reporter")
|
|
37
|
-
|
|
38
|
-
rmSync(tmpDir, { recursive: true })
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
it("copies hierarchy.txt verbatim without identity header", () => {
|
|
42
|
-
const tmpDir = mkdtempSync(join(tmpdir(), "apw-source2-"))
|
|
43
|
-
const agentPromptsDir = join(tmpDir, "agent-prompts")
|
|
44
|
-
mkdirSync(agentPromptsDir, { recursive: true })
|
|
45
|
-
|
|
46
|
-
writeFileSync(join(agentPromptsDir, "hierarchy.txt"), "global reference rules", "utf-8")
|
|
47
|
-
|
|
48
|
-
writeAgentPrompts(tmpDir, destDir)
|
|
49
|
-
|
|
50
|
-
const hierarchyContent = readFileSync(join(destDir, "hierarchy.txt"), "utf-8")
|
|
51
|
-
assert.strictEqual(hierarchyContent, "global reference rules",
|
|
52
|
-
"hierarchy.txt should be copied verbatim without header")
|
|
53
|
-
|
|
54
|
-
rmSync(tmpDir, { recursive: true })
|
|
55
|
-
})
|
|
56
|
-
})
|
|
1
|
+
import { describe, it, after } from "node:test"
|
|
2
|
+
import assert from "node:assert"
|
|
3
|
+
import { existsSync, mkdtempSync, writeFileSync, readFileSync, rmSync, mkdirSync } from "node:fs"
|
|
4
|
+
import { join } from "node:path"
|
|
5
|
+
import { tmpdir } from "node:os"
|
|
6
|
+
import { writeAgentPrompts } from "./agentPromptWriter.js"
|
|
7
|
+
|
|
8
|
+
describe("agentPromptWriter", () => {
|
|
9
|
+
const destDir = mkdtempSync(join(tmpdir(), "pluidr-apw-dest-"))
|
|
10
|
+
|
|
11
|
+
after(() => {
|
|
12
|
+
// Clean up any files written during tests
|
|
13
|
+
if (existsSync(destDir)) rmSync(destDir, { recursive: true })
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it("prepends identity header to agent prompts", () => {
|
|
17
|
+
const tmpDir = mkdtempSync(join(tmpdir(), "apw-source-"))
|
|
18
|
+
const agentPromptsDir = join(tmpDir, "agent-prompts")
|
|
19
|
+
mkdirSync(agentPromptsDir, { recursive: true })
|
|
20
|
+
|
|
21
|
+
writeFileSync(join(agentPromptsDir, "coder.txt"), "test instructions", "utf-8")
|
|
22
|
+
writeFileSync(join(agentPromptsDir, "compose-reporter.txt"), "write docs", "utf-8")
|
|
23
|
+
|
|
24
|
+
writeAgentPrompts(tmpDir, destDir)
|
|
25
|
+
|
|
26
|
+
const coderContent = readFileSync(join(destDir, "coder.txt"), "utf-8")
|
|
27
|
+
const composeReporterContent = readFileSync(join(destDir, "compose-reporter.txt"), "utf-8")
|
|
28
|
+
|
|
29
|
+
assert.ok(coderContent.startsWith('You are the "coder" agent.\n\n'),
|
|
30
|
+
"coder should have identity header")
|
|
31
|
+
assert.ok(composeReporterContent.startsWith('You are the "compose-reporter" agent.\n\n'),
|
|
32
|
+
"compose-reporter should have identity header")
|
|
33
|
+
assert.ok(coderContent.includes("test instructions"),
|
|
34
|
+
"original content preserved in coder")
|
|
35
|
+
assert.ok(composeReporterContent.includes("write docs"),
|
|
36
|
+
"original content preserved in compose-reporter")
|
|
37
|
+
|
|
38
|
+
rmSync(tmpDir, { recursive: true })
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it("copies hierarchy.txt verbatim without identity header", () => {
|
|
42
|
+
const tmpDir = mkdtempSync(join(tmpdir(), "apw-source2-"))
|
|
43
|
+
const agentPromptsDir = join(tmpDir, "agent-prompts")
|
|
44
|
+
mkdirSync(agentPromptsDir, { recursive: true })
|
|
45
|
+
|
|
46
|
+
writeFileSync(join(agentPromptsDir, "hierarchy.txt"), "global reference rules", "utf-8")
|
|
47
|
+
|
|
48
|
+
writeAgentPrompts(tmpDir, destDir)
|
|
49
|
+
|
|
50
|
+
const hierarchyContent = readFileSync(join(destDir, "hierarchy.txt"), "utf-8")
|
|
51
|
+
assert.strictEqual(hierarchyContent, "global reference rules",
|
|
52
|
+
"hierarchy.txt should be copied verbatim without header")
|
|
53
|
+
|
|
54
|
+
rmSync(tmpDir, { recursive: true })
|
|
55
|
+
})
|
|
56
|
+
})
|
package/src/core/backup.js
CHANGED
|
@@ -1,40 +1,40 @@
|
|
|
1
|
-
import { existsSync, copyFileSync, readdirSync, unlinkSync } from "node:fs"
|
|
2
|
-
import { join, dirname } from "node:path"
|
|
3
|
-
import { getConfigPath } from "./paths.js"
|
|
4
|
-
|
|
5
|
-
const MAX_BACKUPS = 5
|
|
6
|
-
|
|
7
|
-
function timestamp() {
|
|
8
|
-
// ponytail: simplified formatting using native Date serialization
|
|
9
|
-
const s = new Date().toISOString().replace(/[-:]/g, "")
|
|
10
|
-
return `${s.slice(0, 8)}_${s.slice(9, 15)}`
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function rotateBackups(configPath) {
|
|
14
|
-
const dir = dirname(configPath)
|
|
15
|
-
const baseName = "opencode.jsonc.bak"
|
|
16
|
-
let backups = []
|
|
17
|
-
|
|
18
|
-
try {
|
|
19
|
-
backups = readdirSync(dir)
|
|
20
|
-
.filter((f) => f.startsWith(baseName + "."))
|
|
21
|
-
.map((f) => join(dir, f))
|
|
22
|
-
.sort()
|
|
23
|
-
} catch (err) {
|
|
24
|
-
if (err.code !== "ENOENT") throw err
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
while (backups.length >= MAX_BACKUPS) {
|
|
28
|
-
const oldest = backups.shift()
|
|
29
|
-
try { unlinkSync(oldest) } catch {}
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function backupExistingConfig(configPath) {
|
|
34
|
-
const resolvedPath = configPath || getConfigPath()
|
|
35
|
-
if (!existsSync(resolvedPath)) return
|
|
36
|
-
|
|
37
|
-
rotateBackups(resolvedPath)
|
|
38
|
-
const backupPath = `${resolvedPath}.bak.${timestamp()}`
|
|
39
|
-
copyFileSync(resolvedPath, backupPath)
|
|
40
|
-
}
|
|
1
|
+
import { existsSync, copyFileSync, readdirSync, unlinkSync } from "node:fs"
|
|
2
|
+
import { join, dirname } from "node:path"
|
|
3
|
+
import { getConfigPath } from "./paths.js"
|
|
4
|
+
|
|
5
|
+
const MAX_BACKUPS = 5
|
|
6
|
+
|
|
7
|
+
function timestamp() {
|
|
8
|
+
// ponytail: simplified formatting using native Date serialization
|
|
9
|
+
const s = new Date().toISOString().replace(/[-:]/g, "")
|
|
10
|
+
return `${s.slice(0, 8)}_${s.slice(9, 15)}`
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function rotateBackups(configPath) {
|
|
14
|
+
const dir = dirname(configPath)
|
|
15
|
+
const baseName = "opencode.jsonc.bak"
|
|
16
|
+
let backups = []
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
backups = readdirSync(dir)
|
|
20
|
+
.filter((f) => f.startsWith(baseName + "."))
|
|
21
|
+
.map((f) => join(dir, f))
|
|
22
|
+
.sort()
|
|
23
|
+
} catch (err) {
|
|
24
|
+
if (err.code !== "ENOENT") throw err
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
while (backups.length >= MAX_BACKUPS) {
|
|
28
|
+
const oldest = backups.shift()
|
|
29
|
+
try { unlinkSync(oldest) } catch {}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function backupExistingConfig(configPath) {
|
|
34
|
+
const resolvedPath = configPath || getConfigPath()
|
|
35
|
+
if (!existsSync(resolvedPath)) return
|
|
36
|
+
|
|
37
|
+
rotateBackups(resolvedPath)
|
|
38
|
+
const backupPath = `${resolvedPath}.bak.${timestamp()}`
|
|
39
|
+
copyFileSync(resolvedPath, backupPath)
|
|
40
|
+
}
|
|
@@ -1,32 +1,32 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs"
|
|
2
|
-
import { resolve } from "node:path"
|
|
3
|
-
|
|
4
|
-
export function buildConfig(tierChoices, templatesDir) {
|
|
5
|
-
const configPath = resolve(templatesDir, "opencode.config.json")
|
|
6
|
-
const defaultsPath = resolve(templatesDir, "model-defaults.json")
|
|
7
|
-
|
|
8
|
-
const config = JSON.parse(readFileSync(configPath, "utf-8"))
|
|
9
|
-
const modelDefaults = JSON.parse(readFileSync(defaultsPath, "utf-8"))
|
|
10
|
-
|
|
11
|
-
// Validate: every agent in model-defaults must have a key in config.agent
|
|
12
|
-
const defaultedAgents = new Set(
|
|
13
|
-
Object.values(modelDefaults).flatMap((tier) => tier.agents),
|
|
14
|
-
)
|
|
15
|
-
const missingAgents = [...defaultedAgents].filter(
|
|
16
|
-
(name) => !(name in config.agent),
|
|
17
|
-
)
|
|
18
|
-
if (missingAgents.length > 0) {
|
|
19
|
-
throw new Error(
|
|
20
|
-
`Template validation failed: agent(s) not found in opencode.config.json: ${missingAgents.join(", ")}`,
|
|
21
|
-
)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
for (const [tierKey, tier] of Object.entries(modelDefaults)) {
|
|
25
|
-
const model = tierChoices[tierKey]
|
|
26
|
-
for (const agentName of tier.agents) {
|
|
27
|
-
config.agent[agentName].model = model
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return config
|
|
32
|
-
}
|
|
1
|
+
import { readFileSync } from "node:fs"
|
|
2
|
+
import { resolve } from "node:path"
|
|
3
|
+
|
|
4
|
+
export function buildConfig(tierChoices, templatesDir) {
|
|
5
|
+
const configPath = resolve(templatesDir, "opencode.config.json")
|
|
6
|
+
const defaultsPath = resolve(templatesDir, "model-defaults.json")
|
|
7
|
+
|
|
8
|
+
const config = JSON.parse(readFileSync(configPath, "utf-8"))
|
|
9
|
+
const modelDefaults = JSON.parse(readFileSync(defaultsPath, "utf-8"))
|
|
10
|
+
|
|
11
|
+
// Validate: every agent in model-defaults must have a key in config.agent
|
|
12
|
+
const defaultedAgents = new Set(
|
|
13
|
+
Object.values(modelDefaults).flatMap((tier) => tier.agents),
|
|
14
|
+
)
|
|
15
|
+
const missingAgents = [...defaultedAgents].filter(
|
|
16
|
+
(name) => !(name in config.agent),
|
|
17
|
+
)
|
|
18
|
+
if (missingAgents.length > 0) {
|
|
19
|
+
throw new Error(
|
|
20
|
+
`Template validation failed: agent(s) not found in opencode.config.json: ${missingAgents.join(", ")}`,
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
for (const [tierKey, tier] of Object.entries(modelDefaults)) {
|
|
25
|
+
const model = tierChoices[tierKey]
|
|
26
|
+
for (const agentName of tier.agents) {
|
|
27
|
+
config.agent[agentName].model = model
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return config
|
|
32
|
+
}
|