pluidr 0.7.0 → 0.7.2
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 +113 -101
- package/src/cli/commands/init.js +47 -43
- package/src/cli/commands/launch.js +46 -44
- package/src/cli/commands/uninstall.js +90 -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 +46 -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 +11 -9
- package/src/core/paths.test.js +29 -21
- package/src/core/pluginWriter.js +29 -29
- package/src/core/squeezeInstaller.js +158 -161
- package/src/core/squeezeInstaller.test.js +79 -75
- package/src/core/themeWriter.js +12 -0
- package/src/core/themeWriter.test.js +29 -0
- package/src/core/tuiConfigWriter.js +22 -0
- package/src/core/tuiConfigWriter.test.js +38 -0
- 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 +429 -430
- package/src/templates/agent-prompts/debug-reporter.txt +65 -65
- package/src/templates/agent-prompts/debugger.txt +152 -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 +94 -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 +482 -481
- package/src/templates/themes/pluidr-contrast.json +177 -0
package/bin/pluidr.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { run } from "../src/cli/index.js"
|
|
3
|
-
|
|
4
|
-
run(process.argv)
|
|
2
|
+
import { run } from "../src/cli/index.js"
|
|
3
|
+
|
|
4
|
+
run(process.argv)
|
package/package.json
CHANGED
|
@@ -1,45 +1,45 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "pluidr",
|
|
3
|
-
"version": "0.7.
|
|
4
|
-
"description": "Opinionated Engineering Workflow for OpenCode — Explore, Plan, Build, Debug, and Security-Audit.",
|
|
5
|
-
"keywords": [
|
|
6
|
-
"opencode",
|
|
7
|
-
"workflow",
|
|
8
|
-
"engineering",
|
|
9
|
-
"pipeline",
|
|
10
|
-
"ai-agents",
|
|
11
|
-
"code-review",
|
|
12
|
-
"debugging",
|
|
13
|
-
"security-audit",
|
|
14
|
-
"security",
|
|
15
|
-
"planner",
|
|
16
|
-
"builder",
|
|
17
|
-
"cli"
|
|
18
|
-
],
|
|
19
|
-
"license": "MIT",
|
|
20
|
-
"type": "module",
|
|
21
|
-
"scripts": {
|
|
22
|
-
"test": "node --test"
|
|
23
|
-
},
|
|
24
|
-
"bin": {
|
|
25
|
-
"pluidr": "bin/pluidr.js"
|
|
26
|
-
},
|
|
27
|
-
"repository": {
|
|
28
|
-
"type": "git",
|
|
29
|
-
"url": "git+https://github.com/funara/pluidr.git"
|
|
30
|
-
},
|
|
31
|
-
"homepage": "https://github.com/funara/pluidr#readme",
|
|
32
|
-
"bugs": {
|
|
33
|
-
"url": "https://github.com/funara/pluidr/issues"
|
|
34
|
-
},
|
|
35
|
-
"engines": {
|
|
36
|
-
"node": ">=18"
|
|
37
|
-
},
|
|
38
|
-
"files": [
|
|
39
|
-
"bin",
|
|
40
|
-
"src"
|
|
41
|
-
],
|
|
42
|
-
"dependencies": {
|
|
43
|
-
"@clack/prompts": "^0.9"
|
|
44
|
-
}
|
|
45
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "pluidr",
|
|
3
|
+
"version": "0.7.2",
|
|
4
|
+
"description": "Opinionated Engineering Workflow for OpenCode — Explore, Plan, Build, Debug, and Security-Audit.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"opencode",
|
|
7
|
+
"workflow",
|
|
8
|
+
"engineering",
|
|
9
|
+
"pipeline",
|
|
10
|
+
"ai-agents",
|
|
11
|
+
"code-review",
|
|
12
|
+
"debugging",
|
|
13
|
+
"security-audit",
|
|
14
|
+
"security",
|
|
15
|
+
"planner",
|
|
16
|
+
"builder",
|
|
17
|
+
"cli"
|
|
18
|
+
],
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"type": "module",
|
|
21
|
+
"scripts": {
|
|
22
|
+
"test": "node --test"
|
|
23
|
+
},
|
|
24
|
+
"bin": {
|
|
25
|
+
"pluidr": "bin/pluidr.js"
|
|
26
|
+
},
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "git+https://github.com/funara/pluidr.git"
|
|
30
|
+
},
|
|
31
|
+
"homepage": "https://github.com/funara/pluidr#readme",
|
|
32
|
+
"bugs": {
|
|
33
|
+
"url": "https://github.com/funara/pluidr/issues"
|
|
34
|
+
},
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=18"
|
|
37
|
+
},
|
|
38
|
+
"files": [
|
|
39
|
+
"bin",
|
|
40
|
+
"src"
|
|
41
|
+
],
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@clack/prompts": "^0.9"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -1,101 +1,113 @@
|
|
|
1
|
-
import { existsSync, readFileSync, readdirSync } from "node:fs"
|
|
2
|
-
import { join } from "node:path"
|
|
3
|
-
import { getConfigDir, getConfigPath, getPromptsDir } from "../../core/paths.js"
|
|
4
|
-
import { findSqueezePath } from "../../core/squeezeInstaller.js"
|
|
5
|
-
|
|
6
|
-
const EXPECTED_PROMPT_COUNT = 18
|
|
7
|
-
const PLUGIN_FILES = ["pluidr-flow.js", "pluidr-squeeze.js"]
|
|
8
|
-
|
|
9
|
-
export function
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
checks.push({ component: "
|
|
53
|
-
|
|
54
|
-
//
|
|
55
|
-
let
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
1
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs"
|
|
2
|
+
import { join } from "node:path"
|
|
3
|
+
import { getConfigDir, getConfigPath, getPromptsDir, getThemesDir } from "../../core/paths.js"
|
|
4
|
+
import { findSqueezePath } from "../../core/squeezeInstaller.js"
|
|
5
|
+
|
|
6
|
+
const EXPECTED_PROMPT_COUNT = 18
|
|
7
|
+
const PLUGIN_FILES = ["pluidr-flow.js", "pluidr-squeeze.js"]
|
|
8
|
+
|
|
9
|
+
export function cleanJsonc(content) {
|
|
10
|
+
return content.replace(
|
|
11
|
+
/\\"|"(?:\\"|[^"])*"|(\/\/.*|\/\*[\s\S]*?\*\/)|(,\s*(?=[\]}]))/g,
|
|
12
|
+
(m, g1, g2) => g1 ? "" : g2 ? "" : m
|
|
13
|
+
)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function collectChecks() {
|
|
17
|
+
const configDir = getConfigDir()
|
|
18
|
+
const configPath = getConfigPath()
|
|
19
|
+
const promptsDir = getPromptsDir()
|
|
20
|
+
const pluginsDir = join(configDir, "plugins")
|
|
21
|
+
const packageJsonPath = join(configDir, "package.json")
|
|
22
|
+
const binDir = join(configDir, "bin")
|
|
23
|
+
const themesDir = getThemesDir()
|
|
24
|
+
|
|
25
|
+
const checks = []
|
|
26
|
+
|
|
27
|
+
// 1. opencode.jsonc exists
|
|
28
|
+
const configExists = existsSync(configPath)
|
|
29
|
+
checks.push({ component: "opencode.jsonc", pass: configExists })
|
|
30
|
+
|
|
31
|
+
// 2. All 18 prompt files exist
|
|
32
|
+
let promptCount = 0
|
|
33
|
+
if (existsSync(promptsDir)) {
|
|
34
|
+
try {
|
|
35
|
+
const files = readdirSync(promptsDir).filter((f) => f.endsWith(".txt"))
|
|
36
|
+
promptCount = files.length
|
|
37
|
+
} catch {
|
|
38
|
+
promptCount = 0
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
checks.push({ component: "18 prompt files", pass: promptCount === EXPECTED_PROMPT_COUNT, detail: `${promptCount}/${EXPECTED_PROMPT_COUNT}` })
|
|
42
|
+
|
|
43
|
+
// 3. Both plugin files exist
|
|
44
|
+
let pluginsFound = 0
|
|
45
|
+
for (const f of PLUGIN_FILES) {
|
|
46
|
+
if (existsSync(join(pluginsDir, f))) pluginsFound++
|
|
47
|
+
}
|
|
48
|
+
checks.push({ component: "plugin files", pass: pluginsFound === PLUGIN_FILES.length, detail: `${pluginsFound}/${PLUGIN_FILES.length}` })
|
|
49
|
+
|
|
50
|
+
// 3b. Custom theme exists
|
|
51
|
+
const themeExists = existsSync(join(themesDir, "pluidr-contrast.json"))
|
|
52
|
+
checks.push({ component: "pluidr-contrast theme file", pass: themeExists })
|
|
53
|
+
|
|
54
|
+
// 4. package.json with @opencode-ai/plugin dependency
|
|
55
|
+
let pkgValid = false
|
|
56
|
+
if (existsSync(packageJsonPath)) {
|
|
57
|
+
try {
|
|
58
|
+
const pkg = JSON.parse(readFileSync(packageJsonPath, "utf-8"))
|
|
59
|
+
pkgValid = !!(pkg.dependencies && pkg.dependencies["@opencode-ai/plugin"])
|
|
60
|
+
} catch {
|
|
61
|
+
pkgValid = false
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
checks.push({ component: "package.json + plugin dep", pass: pkgValid })
|
|
65
|
+
|
|
66
|
+
// 5. Squeeze binary exists
|
|
67
|
+
let squeezeFound = false
|
|
68
|
+
const squeezeOnPath = findSqueezePath()
|
|
69
|
+
if (squeezeOnPath) {
|
|
70
|
+
squeezeFound = true
|
|
71
|
+
} else {
|
|
72
|
+
const binName = process.platform === "win32" ? "squeeze.exe" : "squeeze"
|
|
73
|
+
squeezeFound = existsSync(join(binDir, binName))
|
|
74
|
+
}
|
|
75
|
+
checks.push({ component: "squeeze binary tested", pass: squeezeFound })
|
|
76
|
+
|
|
77
|
+
// 6. Config is valid JSON/JSONC
|
|
78
|
+
let configValid = false
|
|
79
|
+
if (configExists) {
|
|
80
|
+
try {
|
|
81
|
+
const content = readFileSync(configPath, "utf-8")
|
|
82
|
+
const cleaned = cleanJsonc(content)
|
|
83
|
+
JSON.parse(cleaned)
|
|
84
|
+
configValid = true
|
|
85
|
+
} catch {
|
|
86
|
+
configValid = false
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
checks.push({ component: "config JSON validity", pass: configValid })
|
|
90
|
+
|
|
91
|
+
return checks
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export async function runDoctor() {
|
|
95
|
+
const checks = collectChecks()
|
|
96
|
+
const allPass = checks.every((c) => c.pass)
|
|
97
|
+
|
|
98
|
+
console.log("\nPluidr Doctor — Installation Health Check\n")
|
|
99
|
+
for (const c of checks) {
|
|
100
|
+
const icon = c.pass ? "\u2713" : "\u2717"
|
|
101
|
+
const detail = c.detail ? ` (${c.detail})` : ""
|
|
102
|
+
console.log(` ${icon} ${c.component}${detail}`)
|
|
103
|
+
}
|
|
104
|
+
console.log("")
|
|
105
|
+
|
|
106
|
+
if (allPass) {
|
|
107
|
+
console.log("All checks passed.")
|
|
108
|
+
} else {
|
|
109
|
+
console.log("Some checks failed. Re-run `pluidr init` to repair.")
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
process.exit(allPass ? 0 : 1)
|
|
113
|
+
}
|
package/src/cli/commands/init.js
CHANGED
|
@@ -1,43 +1,47 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs"
|
|
2
|
-
import { resolve, dirname } from "node:path"
|
|
3
|
-
import { fileURLToPath } from "node:url"
|
|
4
|
-
import { confirm, isCancel } from "@clack/prompts"
|
|
5
|
-
import { selectModelTier } from "../wizard/selectModelTier.js"
|
|
6
|
-
import { backupExistingConfig } from "../../core/backup.js"
|
|
7
|
-
import { buildConfig } from "../../core/configBuilder.js"
|
|
8
|
-
import { writeConfig } from "../../core/configWriter.js"
|
|
9
|
-
import { writeAgentPrompts } from "../../core/agentPromptWriter.js"
|
|
10
|
-
import { writePluginBundle, writePluginPackageJson } from "../../core/pluginWriter.js"
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
1
|
+
import { readFileSync, writeFileSync } from "node:fs"
|
|
2
|
+
import { resolve, dirname } from "node:path"
|
|
3
|
+
import { fileURLToPath } from "node:url"
|
|
4
|
+
import { confirm, isCancel } from "@clack/prompts"
|
|
5
|
+
import { selectModelTier } from "../wizard/selectModelTier.js"
|
|
6
|
+
import { backupExistingConfig } from "../../core/backup.js"
|
|
7
|
+
import { buildConfig } from "../../core/configBuilder.js"
|
|
8
|
+
import { writeConfig } from "../../core/configWriter.js"
|
|
9
|
+
import { writeAgentPrompts } from "../../core/agentPromptWriter.js"
|
|
10
|
+
import { writePluginBundle, writePluginPackageJson } from "../../core/pluginWriter.js"
|
|
11
|
+
import { writeThemes } from "../../core/themeWriter.js"
|
|
12
|
+
import { writeTuiConfig } from "../../core/tuiConfigWriter.js"
|
|
13
|
+
import { installSqueeze } from "../../core/squeezeInstaller.js"
|
|
14
|
+
import { getConfigPath, getConfigDir } from "../../core/paths.js"
|
|
15
|
+
|
|
16
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
17
|
+
const TEMPLATES_DIR = resolve(__dirname, "../../templates")
|
|
18
|
+
|
|
19
|
+
export async function runInit() {
|
|
20
|
+
const defaultsPath = resolve(TEMPLATES_DIR, "model-defaults.json")
|
|
21
|
+
const modelDefaults = JSON.parse(readFileSync(defaultsPath, "utf-8"))
|
|
22
|
+
|
|
23
|
+
const tierChoices = await selectModelTier(modelDefaults)
|
|
24
|
+
|
|
25
|
+
const squeezeAnswer = await confirm({
|
|
26
|
+
message: "Install pluidr-squeeze plugin? Reduces bash output tokens by 60-90%.",
|
|
27
|
+
initialValue: true,
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
const installSqueezePlugin = isCancel(squeezeAnswer) ? false : squeezeAnswer
|
|
31
|
+
|
|
32
|
+
backupExistingConfig()
|
|
33
|
+
|
|
34
|
+
const configObject = buildConfig(tierChoices, TEMPLATES_DIR)
|
|
35
|
+
writeConfig(configObject)
|
|
36
|
+
writeAgentPrompts(TEMPLATES_DIR)
|
|
37
|
+
writeThemes(TEMPLATES_DIR)
|
|
38
|
+
writeTuiConfig("pluidr-contrast")
|
|
39
|
+
writePluginBundle()
|
|
40
|
+
writePluginPackageJson()
|
|
41
|
+
if (installSqueezePlugin) {
|
|
42
|
+
await installSqueeze()
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const path = getConfigPath().replace(/\\/g, "/").replace(/^\//, "")
|
|
46
|
+
console.log(`Pluidr setup complete!\nYou can update your agent model settings later in \x1b]8;;file:///${path}\x1b\\opencode.jsonc\x1b]8;;\x1b\\`)
|
|
47
|
+
}
|
|
@@ -1,44 +1,46 @@
|
|
|
1
|
-
import { spawn } from "node:child_process"
|
|
2
|
-
import { confirm, isCancel } from "@clack/prompts"
|
|
3
|
-
import { checkAndPromptUpdate } from "../../core/versionCheck.js"
|
|
4
|
-
import { version } from "../../core/version.js"
|
|
5
|
-
import { collectChecks } from "./doctor.js"
|
|
6
|
-
import { runInit } from "./init.js"
|
|
7
|
-
|
|
8
|
-
export async function runLaunch() {
|
|
9
|
-
// 1. Update check
|
|
10
|
-
await checkAndPromptUpdate(version)
|
|
11
|
-
|
|
12
|
-
// 2. Doctor check
|
|
13
|
-
process.stdout.write("Running doctor... ")
|
|
14
|
-
const checks = collectChecks()
|
|
15
|
-
const allPass = checks.every((c) => c.pass)
|
|
16
|
-
|
|
17
|
-
if (allPass) {
|
|
18
|
-
console.log("✓ All checks passed")
|
|
19
|
-
} else {
|
|
20
|
-
const failed = checks.filter((c) => !c.pass)
|
|
21
|
-
console.log(`✗ ${failed.length} check${failed.length > 1 ? "s" : ""} failed`)
|
|
22
|
-
for (const c of failed) {
|
|
23
|
-
const detail = c.detail ? ` (${c.detail})` : ""
|
|
24
|
-
console.log(` ✗ ${c.component}${detail}`)
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const answer = await confirm({ message: "Repair with pluidr init?", initialValue: true })
|
|
28
|
-
if (!isCancel(answer) && answer) {
|
|
29
|
-
await runInit()
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// 3. Launch opencode
|
|
34
|
-
console.log("Launching
|
|
35
|
-
const child =
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
console.error(
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
1
|
+
import { spawn } from "node:child_process"
|
|
2
|
+
import { confirm, isCancel } from "@clack/prompts"
|
|
3
|
+
import { checkAndPromptUpdate } from "../../core/versionCheck.js"
|
|
4
|
+
import { version } from "../../core/version.js"
|
|
5
|
+
import { collectChecks } from "./doctor.js"
|
|
6
|
+
import { runInit } from "./init.js"
|
|
7
|
+
|
|
8
|
+
export async function runLaunch() {
|
|
9
|
+
// 1. Update check
|
|
10
|
+
await checkAndPromptUpdate(version)
|
|
11
|
+
|
|
12
|
+
// 2. Doctor check
|
|
13
|
+
process.stdout.write("Running doctor... ")
|
|
14
|
+
const checks = collectChecks()
|
|
15
|
+
const allPass = checks.every((c) => c.pass)
|
|
16
|
+
|
|
17
|
+
if (allPass) {
|
|
18
|
+
console.log("✓ All checks passed")
|
|
19
|
+
} else {
|
|
20
|
+
const failed = checks.filter((c) => !c.pass)
|
|
21
|
+
console.log(`✗ ${failed.length} check${failed.length > 1 ? "s" : ""} failed`)
|
|
22
|
+
for (const c of failed) {
|
|
23
|
+
const detail = c.detail ? ` (${c.detail})` : ""
|
|
24
|
+
console.log(` ✗ ${c.component}${detail}`)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const answer = await confirm({ message: "Repair with pluidr init?", initialValue: true })
|
|
28
|
+
if (!isCancel(answer) && answer) {
|
|
29
|
+
await runInit()
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// 3. Launch opencode
|
|
34
|
+
console.log("Launching pluidr...")
|
|
35
|
+
const child = process.platform === "win32"
|
|
36
|
+
? spawn("opencode", { stdio: "inherit", shell: true })
|
|
37
|
+
: spawn("opencode", [], { stdio: "inherit" })
|
|
38
|
+
child.on("error", (err) => {
|
|
39
|
+
if (err.code === "ENOENT") {
|
|
40
|
+
console.error("Error: opencode not found on PATH. Install it from https://opencode.ai")
|
|
41
|
+
} else {
|
|
42
|
+
console.error(`Error launching opencode: ${err.message}`)
|
|
43
|
+
}
|
|
44
|
+
process.exit(1)
|
|
45
|
+
})
|
|
46
|
+
}
|