pluidr 0.7.6 → 0.8.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.
Files changed (88) hide show
  1. package/LICENSE +20 -20
  2. package/README.md +409 -399
  3. package/bin/pluidr.js +3 -3
  4. package/package.json +1 -1
  5. package/src/cli/commands/doctor.js +137 -134
  6. package/src/cli/commands/init.js +146 -47
  7. package/src/cli/commands/launch.js +101 -50
  8. package/src/cli/commands/theme.js +91 -0
  9. package/src/cli/commands/theme.test.js +28 -0
  10. package/src/cli/commands/uninstall.js +109 -90
  11. package/src/cli/commands/update.js +9 -9
  12. package/src/cli/index.js +63 -57
  13. package/src/cli/wizard/selectModelTier.js +40 -40
  14. package/src/core/agentPromptWriter.js +49 -32
  15. package/src/core/agentPromptWriter.test.js +56 -56
  16. package/src/core/animation.js +84 -0
  17. package/src/core/animation.test.js +118 -0
  18. package/src/core/backup.js +46 -46
  19. package/src/core/commandsWriter.js +26 -0
  20. package/src/core/commandsWriter.test.js +29 -0
  21. package/src/core/configBuilder.js +32 -32
  22. package/src/core/configBuilder.test.js +93 -93
  23. package/src/core/configWriter.js +10 -10
  24. package/src/core/configWriter.test.js +1 -1
  25. package/src/core/identityHeader.js +8 -8
  26. package/src/core/paths.js +13 -11
  27. package/src/core/paths.test.js +33 -29
  28. package/src/core/pluginWriter.js +43 -29
  29. package/src/core/skillsWriter.js +21 -0
  30. package/src/core/skillsWriter.test.js +30 -0
  31. package/src/core/squeezeInstaller.js +158 -158
  32. package/src/core/squeezeInstaller.test.js +79 -79
  33. package/src/core/themeWriter.js +18 -4
  34. package/src/core/themeWriter.test.js +2 -2
  35. package/src/core/tuiConfigWriter.js +22 -3
  36. package/src/core/tuiConfigWriter.test.js +11 -5
  37. package/src/core/version.js +8 -8
  38. package/src/core/versionCheck.js +44 -44
  39. package/src/plugins/README.md +57 -68
  40. package/src/plugins/pluidr-squeeze.js +77 -77
  41. package/src/templates/agent-prompts/analyze.txt +49 -0
  42. package/src/templates/agent-prompts/builder.txt +10 -0
  43. package/src/templates/agent-prompts/compose.txt +50 -0
  44. package/src/templates/agent-prompts/debug.txt +49 -0
  45. package/src/templates/agent-prompts/explorer.txt +10 -0
  46. package/src/templates/agent-prompts/hierarchy.txt +101 -95
  47. package/src/templates/agent-prompts/reporter.txt +10 -0
  48. package/src/templates/agent-prompts/verifier.txt +10 -0
  49. package/src/templates/commands/squeeze-dashboard.md +5 -0
  50. package/src/templates/commands/squeeze-health.md +10 -0
  51. package/src/templates/commands/squeeze-quick.md +10 -0
  52. package/src/templates/model-defaults.json +59 -73
  53. package/src/templates/opencode.config.json +243 -572
  54. package/src/templates/skills/brooks-lint-rca/SKILL.md +48 -0
  55. package/src/templates/skills/codebase-fact-finding/SKILL.md +39 -0
  56. package/src/templates/skills/diff-review/SKILL.md +42 -0
  57. package/src/templates/skills/general-coding/SKILL.md +44 -0
  58. package/src/templates/skills/minimal-fixing/SKILL.md +25 -0
  59. package/src/templates/skills/plan-checking/SKILL.md +33 -0
  60. package/src/templates/{agent-prompts/patcher.txt → skills/ponytail-patching/SKILL.md} +20 -20
  61. package/src/templates/skills/prd-formatting/SKILL.md +45 -0
  62. package/src/templates/skills/refactoring-patterns/SKILL.md +37 -0
  63. package/src/templates/skills/security-auditing/SKILL.md +35 -0
  64. package/src/templates/skills/security-remediation/SKILL.md +37 -0
  65. package/src/templates/skills/summary-reporting/SKILL.md +83 -0
  66. package/src/templates/skills/test-assurance/SKILL.md +48 -0
  67. package/src/templates/skills/test-mocking-strategy/SKILL.md +18 -0
  68. package/src/templates/skills/ui-design-audit/SKILL.md +23 -0
  69. package/src/templates/skills/ui-design-system/SKILL.md +37 -0
  70. package/src/templates/{agent-prompts/tracer.txt → skills/wstg-recon/SKILL.md} +33 -33
  71. package/src/templates/themes/pluidr-colorful.json +241 -0
  72. package/src/templates/themes/{pluidr-contrast.json → pluidr-dark.json} +68 -68
  73. package/src/templates/themes/pluidr-light.json +241 -0
  74. package/src/templates/agent-prompts/auditor.txt +0 -20
  75. package/src/templates/agent-prompts/coder.txt +0 -88
  76. package/src/templates/agent-prompts/compose-reporter.txt +0 -55
  77. package/src/templates/agent-prompts/composer.txt +0 -414
  78. package/src/templates/agent-prompts/debug-reporter.txt +0 -65
  79. package/src/templates/agent-prompts/debugger.txt +0 -149
  80. package/src/templates/agent-prompts/fixer.txt +0 -66
  81. package/src/templates/agent-prompts/inspector.txt +0 -79
  82. package/src/templates/agent-prompts/plan-checker.txt +0 -45
  83. package/src/templates/agent-prompts/plan-writer.txt +0 -57
  84. package/src/templates/agent-prompts/probe-reporter.txt +0 -62
  85. package/src/templates/agent-prompts/prober.txt +0 -93
  86. package/src/templates/agent-prompts/researcher.txt +0 -48
  87. package/src/templates/agent-prompts/reviewer.txt +0 -57
  88. package/src/templates/agent-prompts/tester.txt +0 -66
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,6 +1,6 @@
1
1
  {
2
2
  "name": "pluidr",
3
- "version": "0.7.6",
3
+ "version": "0.8.1",
4
4
  "description": "Opinionated Engineering Workflow for OpenCode — Plan, Build, Review.",
5
5
  "keywords": [
6
6
  "opencode",
@@ -1,134 +1,137 @@
1
- import { existsSync, readFileSync, readdirSync } from "node:fs"
2
- import { join } from "node:path"
3
- import { getConfigDir, getConfigPath, getPromptsDir, getThemesDir, getTuiConfigPath } 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. Theme configuration verification
51
- const themeExists = existsSync(join(themesDir, "pluidr-contrast.json"))
52
- const tuiConfigPath = getTuiConfigPath()
53
- let tuiConfigValid = false
54
- let tuiDetail = "missing mapping"
55
- if (existsSync(tuiConfigPath)) {
56
- try {
57
- const content = readFileSync(tuiConfigPath, "utf-8")
58
- const parsed = JSON.parse(content)
59
- if (parsed.theme === "pluidr-contrast") {
60
- tuiConfigValid = true
61
- tuiDetail = ""
62
- } else {
63
- tuiDetail = `theme: ${parsed.theme || "none"}`
64
- }
65
- } catch {
66
- tuiDetail = "corrupted tui.json"
67
- }
68
- }
69
- checks.push({
70
- component: themeExists && tuiConfigValid ? "pluidr-contrast theme configured" : "pluidr-contrast theme not configured",
71
- pass: themeExists && tuiConfigValid,
72
- detail: !themeExists ? "missing theme file" : tuiDetail
73
- })
74
-
75
- // 4. package.json with @opencode-ai/plugin dependency
76
- let pkgValid = false
77
- if (existsSync(packageJsonPath)) {
78
- try {
79
- const pkg = JSON.parse(readFileSync(packageJsonPath, "utf-8"))
80
- pkgValid = !!(pkg.dependencies && pkg.dependencies["@opencode-ai/plugin"])
81
- } catch {
82
- pkgValid = false
83
- }
84
- }
85
- checks.push({ component: "package.json + plugin dep", pass: pkgValid })
86
-
87
- // 5. Squeeze binary exists
88
- let squeezeFound = false
89
- const squeezeOnPath = findSqueezePath()
90
- if (squeezeOnPath) {
91
- squeezeFound = true
92
- } else {
93
- const binName = process.platform === "win32" ? "squeeze.exe" : "squeeze"
94
- squeezeFound = existsSync(join(binDir, binName))
95
- }
96
- checks.push({ component: "squeeze binary tested", pass: squeezeFound })
97
-
98
- // 6. Config is valid JSON/JSONC
99
- let configValid = false
100
- if (configExists) {
101
- try {
102
- const content = readFileSync(configPath, "utf-8")
103
- const cleaned = cleanJsonc(content)
104
- JSON.parse(cleaned)
105
- configValid = true
106
- } catch {
107
- configValid = false
108
- }
109
- }
110
- checks.push({ component: "config JSON validity", pass: configValid })
111
-
112
- return checks
113
- }
114
-
115
- export async function runDoctor() {
116
- const checks = collectChecks()
117
- const allPass = checks.every((c) => c.pass)
118
-
119
- console.log("\nPluidr Doctor Installation Health Check\n")
120
- for (const c of checks) {
121
- const icon = c.pass ? "\u2713" : "\u2717"
122
- const detail = c.detail ? ` (${c.detail})` : ""
123
- console.log(` ${icon} ${c.component}${detail}`)
124
- }
125
- console.log("")
126
-
127
- if (allPass) {
128
- console.log("All checks passed.")
129
- } else {
130
- console.log("Some checks failed. Re-run `pluidr init` to repair.")
131
- }
132
-
133
- process.exit(allPass ? 0 : 1)
134
- }
1
+ import { existsSync, readFileSync, readdirSync } from "node:fs"
2
+ import { join } from "node:path"
3
+ import { getConfigDir, getConfigPath, getPromptsDir, getThemesDir, getTuiConfigPath, getCommandsDir } from "../../core/paths.js"
4
+
5
+ const EXPECTED_PROMPT_COUNT = 8
6
+ const PLUGIN_FILES = ["pluidr-flow.js", "pluidr-squeeze.js"]
7
+ const EXPECTED_COMMAND_FILES = ["squeeze-quick.md", "squeeze-health.md", "squeeze-dashboard.md"]
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 8 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: "8 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
+ // 3a. Slash command files exist
51
+ let commandsFound = 0
52
+ const commandsDir = getCommandsDir()
53
+ for (const f of EXPECTED_COMMAND_FILES) {
54
+ if (existsSync(join(commandsDir, f))) commandsFound++
55
+ }
56
+ checks.push({ component: "slash command files", pass: commandsFound === EXPECTED_COMMAND_FILES.length, detail: `${commandsFound}/${EXPECTED_COMMAND_FILES.length}` })
57
+
58
+ // 3b. Theme configuration verification (accepts any valid pluidr theme)
59
+ const ALLOWED_THEMES = new Set(["pluidr-dark", "pluidr-light", "pluidr-colorful"])
60
+ const tuiConfigPath = getTuiConfigPath()
61
+ let activeTheme = "pluidr-dark" // default fallback
62
+ let tuiConfigValid = false
63
+ let tuiDetail = "missing mapping"
64
+
65
+ if (existsSync(tuiConfigPath)) {
66
+ try {
67
+ const content = readFileSync(tuiConfigPath, "utf-8")
68
+ const parsed = JSON.parse(content)
69
+ if (parsed.theme && ALLOWED_THEMES.has(parsed.theme)) {
70
+ activeTheme = parsed.theme
71
+ tuiConfigValid = true
72
+ tuiDetail = ""
73
+ } else {
74
+ tuiDetail = `theme: ${parsed.theme || "none"}`
75
+ }
76
+ } catch {
77
+ tuiDetail = "corrupted tui.json"
78
+ }
79
+ }
80
+
81
+ const themeExists = existsSync(join(themesDir, `${activeTheme}.json`))
82
+ checks.push({
83
+ component: themeExists && tuiConfigValid ? `${activeTheme} theme configured` : "theme not configured",
84
+ pass: themeExists && tuiConfigValid,
85
+ detail: !themeExists ? `missing theme file ${activeTheme}.json` : tuiDetail
86
+ })
87
+
88
+ // 4. package.json with @opencode-ai/plugin dependency
89
+ let pkgValid = false
90
+ if (existsSync(packageJsonPath)) {
91
+ try {
92
+ const pkg = JSON.parse(readFileSync(packageJsonPath, "utf-8"))
93
+ pkgValid = !!(pkg.dependencies && pkg.dependencies["@opencode-ai/plugin"])
94
+ } catch {
95
+ pkgValid = false
96
+ }
97
+ }
98
+ checks.push({ component: "package.json + plugin dep", pass: pkgValid })
99
+
100
+
101
+ // 6. Config is valid JSON/JSONC
102
+ let configValid = false
103
+ if (configExists) {
104
+ try {
105
+ const content = readFileSync(configPath, "utf-8")
106
+ const cleaned = cleanJsonc(content)
107
+ JSON.parse(cleaned)
108
+ configValid = true
109
+ } catch {
110
+ configValid = false
111
+ }
112
+ }
113
+ checks.push({ component: "config JSON validity", pass: configValid })
114
+
115
+ return checks
116
+ }
117
+
118
+ export async function runDoctor() {
119
+ const checks = collectChecks()
120
+ const allPass = checks.every((c) => c.pass)
121
+
122
+ console.log("\nPluidr Doctor Installation Health Check\n")
123
+ for (const c of checks) {
124
+ const icon = c.pass ? "\u2713" : "\u2717"
125
+ const detail = c.detail ? ` (${c.detail})` : ""
126
+ console.log(` ${icon} ${c.component}${detail}`)
127
+ }
128
+ console.log("")
129
+
130
+ if (allPass) {
131
+ console.log("All checks passed.")
132
+ } else {
133
+ console.log("Some checks failed. Re-run `pluidr init` to repair.")
134
+ }
135
+
136
+ process.exit(allPass ? 0 : 1)
137
+ }
@@ -1,47 +1,146 @@
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
+ import { readFileSync, writeFileSync } from "node:fs"
2
+ import { resolve, dirname } from "node:path"
3
+ import { fileURLToPath } from "node:url"
4
+ import { select, 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 { writeSkills } from "../../core/skillsWriter.js"
14
+ import { writeCommands } from "../../core/commandsWriter.js"
15
+ import { version } from "../../core/version.js"
16
+ import { getConfigPath } from "../../core/paths.js"
17
+ import { getAnimationType, getLogoLine, ASCII_LOGO_LINES } from "../../core/animation.js"
18
+
19
+ const __dirname = dirname(fileURLToPath(import.meta.url))
20
+ const TEMPLATES_DIR = resolve(__dirname, "../../templates")
21
+
22
+ export async function runInit() {
23
+ console.log("\x1b[1;36m┌ pluidr init\x1b[0m")
24
+
25
+ // Print initial blank lines so the animation loop can safely clear them
26
+ for (let i = 0; i < 9; i++) console.log("│")
27
+
28
+ const animType = getAnimationType()
29
+ let frame = 0
30
+ const animInterval = setInterval(() => {
31
+ // Clear logo lines (9 rows)
32
+ process.stdout.write("\x1b[9A\x1b[0J")
33
+
34
+ for (let r = 0; r < 5; r++) {
35
+ const { text, color } = getLogoLine(ASCII_LOGO_LINES[r], r, frame, animType)
36
+ console.log("│ " + color + text + "\x1b[0m")
37
+ }
38
+ console.log("");
39
+ console.log(`│ Spex-driven development, made fluid. • v${version} (Stable)`);
40
+ console.log("│ \x1b[90mPress any key to begin setup...\x1b[0m");
41
+ console.log("│");
42
+
43
+ frame++
44
+ }, 100)
45
+
46
+ // Loop until user presses any key (or resolve immediately if not running in TTY)
47
+ if (process.stdin.isTTY) {
48
+ await new Promise((resolve) => {
49
+ const onData = (key) => {
50
+ if (key === "\u0003") {
51
+ process.exit(0);
52
+ }
53
+ clearInterval(animInterval);
54
+ process.stdin.setRawMode(false);
55
+ process.stdin.pause();
56
+ process.stdin.off("data", onData);
57
+ resolve();
58
+ };
59
+
60
+ process.stdin.setRawMode(true);
61
+ process.stdin.resume();
62
+ process.stdin.setEncoding("utf8");
63
+ process.stdin.on("data", onData);
64
+ });
65
+ } else {
66
+ clearInterval(animInterval);
67
+ }
68
+
69
+ // Overwrite "Press any key to begin setup..." with "Starting setup wizard..." in place
70
+ process.stdout.write("\x1b[2A\r│ \x1b[32m✓\x1b[0m Starting setup wizard...\x1b[0K\n│\n\n");
71
+
72
+ const defaultsPath = resolve(TEMPLATES_DIR, "model-defaults.json")
73
+ const modelDefaults = JSON.parse(readFileSync(defaultsPath, "utf-8"))
74
+
75
+ // 1. Model Tier Selection Prompts (Directly separated by reasoning, precision, fast)
76
+ const tierChoices = await selectModelTier(modelDefaults)
77
+
78
+ // 2. Theme Selection Prompt
79
+ const selectedTheme = await select({
80
+ message: "Select a Default Theme:",
81
+ options: [
82
+ { value: "pluidr-dark", label: "pluidr-dark - Dark High Contrast (Recommended)" },
83
+ { value: "pluidr-light", label: "pluidr-light - Light High Contrast" },
84
+ { value: "pluidr-colorful", label: "pluidr-colorful - Pastel Palette" }
85
+ ]
86
+ })
87
+
88
+ if (isCancel(selectedTheme)) {
89
+ console.log("Operation cancelled.")
90
+ process.exit(0)
91
+ }
92
+
93
+ // 3. Execution steps
94
+ backupExistingConfig()
95
+
96
+ const configObject = buildConfig(tierChoices, TEMPLATES_DIR)
97
+ const orderedConfig = {}
98
+ if ("$schema" in configObject) {
99
+ orderedConfig["$schema"] = configObject["$schema"]
100
+ }
101
+ if (selectedTheme) {
102
+ orderedConfig.theme = selectedTheme
103
+ }
104
+ for (const [k, v] of Object.entries(configObject)) {
105
+ if (k !== "$schema" && k !== "theme") {
106
+ orderedConfig[k] = v
107
+ }
108
+ }
109
+ writeConfig(orderedConfig)
110
+ writeAgentPrompts(TEMPLATES_DIR)
111
+ console.log("│ Writing agent prompts... \x1b[32m✓\x1b[0m Wrote 8 files to ~/.config/opencode/prompts/")
112
+
113
+ writeThemes(TEMPLATES_DIR)
114
+ console.log("│ Writing themes... \x1b[32m✓\x1b[0m Installed 3 themes")
115
+
116
+ writeSkills(TEMPLATES_DIR)
117
+ console.log("│ Writing skills... \x1b[32m✓\x1b[0m Installed 17 developer skill playbooks")
118
+
119
+ writeCommands(TEMPLATES_DIR)
120
+ console.log("│ Writing commands... \x1b[32m✓\x1b[0m Installed 3 custom slash commands")
121
+
122
+ writeTuiConfig(selectedTheme, undefined, "wave")
123
+ console.log("│ Writing configuration... \x1b[32m✓\x1b[0m Configured opencode.jsonc and tui.json")
124
+
125
+ writePluginBundle()
126
+ writePluginPackageJson()
127
+ console.log("│ Installing plugins... \x1b[32m✓\x1b[0m Installed bundled plugins (pluidr-flow, pluidr-squeeze)");
128
+
129
+ const path = getConfigPath().replace(/\\/g, "/").replace(/^\//, "")
130
+ console.log(`
131
+ ======================================================
132
+ 🚀 Pluidr Setup Complete!
133
+ ======================================================
134
+
135
+ You can view the full list of available commands using:
136
+ $ pluidr --help
137
+
138
+ To customize your agent model settings, open your config:
139
+ 👉 \x1b]8;;file:///${path}\x1b\\opencode.jsonc\x1b]8;;\x1b\\
140
+
141
+ To launch OpenCode with your Pluidr pipeline, simply run:
142
+ $ pluidr
143
+ ======================================================
144
+ `)
145
+ console.log("\x1b[1;36m└ Setup finished!\x1b[0m")
146
+ }
@@ -1,50 +1,101 @@
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
- const updated = await checkAndPromptUpdate(version)
11
- if (updated) {
12
- console.log("Update successful. Please run pluidr again to start the new version.")
13
- process.exit(0)
14
- }
15
-
16
- // 2. Doctor check
17
- process.stdout.write("Running doctor... ")
18
- const checks = collectChecks()
19
- const allPass = checks.every((c) => c.pass)
20
-
21
- if (allPass) {
22
- console.log("✓ All checks passed")
23
- } else {
24
- const failed = checks.filter((c) => !c.pass)
25
- console.log(`✗ ${failed.length} check${failed.length > 1 ? "s" : ""} failed`)
26
- for (const c of failed) {
27
- const detail = c.detail ? ` (${c.detail})` : ""
28
- console.log(` ✗ ${c.component}${detail}`)
29
- }
30
-
31
- const answer = await confirm({ message: "Repair with pluidr init?", initialValue: true })
32
- if (!isCancel(answer) && answer) {
33
- await runInit()
34
- }
35
- }
36
-
37
- // 3. Launch opencode
38
- console.log("Launching pluidr...")
39
- const child = process.platform === "win32"
40
- ? spawn("opencode", { stdio: "inherit", shell: true })
41
- : spawn("opencode", [], { stdio: "inherit" })
42
- child.on("error", (err) => {
43
- if (err.code === "ENOENT") {
44
- console.error("Error: opencode not found on PATH. Install it from https://opencode.ai")
45
- } else {
46
- console.error(`Error launching opencode: ${err.message}`)
47
- }
48
- process.exit(1)
49
- })
50
- }
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
+ import { getAnimationType, getLogoLine, ASCII_LOGO_LINES } from "../../core/animation.js"
9
+
10
+ export async function runLaunch() {
11
+ // 1. Start loading animation
12
+ for (let i = 0; i < 9; i++) console.log("");
13
+
14
+ const animType = getAnimationType();
15
+ let frame = 0;
16
+ let statusMessage = "Loading Pluidr...";
17
+
18
+ const animInterval = setInterval(() => {
19
+ // Clear logo lines (9 rows)
20
+ process.stdout.write("\x1b[9A\x1b[0J");
21
+
22
+ for (let r = 0; r < 5; r++) {
23
+ const { text, color } = getLogoLine(ASCII_LOGO_LINES[r], r, frame, animType);
24
+ console.log(color + text + "\x1b[0m");
25
+ }
26
+ console.log("");
27
+ console.log(` Spex-driven development, made fluid. v${version} (Stable)`);
28
+ console.log("");
29
+
30
+ // Draw spinning wave indicator
31
+ const spinner = ["∿", "≋", "≁", "≋"][frame % 4];
32
+ console.log(` ${spinner} [ \x1b[36m🌊\x1b[0m ] ${statusMessage}\r`);
33
+
34
+ frame++;
35
+ }, 100);
36
+
37
+ // 2. Perform async checks (enforce minimum 1.5s animation duration for visual quality)
38
+ const startTime = Date.now();
39
+
40
+ statusMessage = "Checking environment and version...";
41
+ const updated = await checkAndPromptUpdate(version);
42
+ if (updated) {
43
+ clearInterval(animInterval);
44
+ console.log("Update successful. Please run pluidr again to start the new version.");
45
+ process.exit(0);
46
+ }
47
+
48
+ statusMessage = "Diagnosing installation integrity...";
49
+ const checks = collectChecks();
50
+ const allPass = checks.every((c) => c.pass);
51
+
52
+ // Wait for remainder of minimum duration
53
+ const elapsed = Date.now() - startTime;
54
+ if (elapsed < 1500) {
55
+ await new Promise(resolve => setTimeout(resolve, 1500 - elapsed));
56
+ }
57
+
58
+ clearInterval(animInterval);
59
+
60
+ // 3. Handle checks outcomes
61
+ if (allPass) {
62
+ // Overwrite the spinner line with the success checkmark in place
63
+ process.stdout.write("\x1b[1A\r \x1b[32m✓\x1b[0m All checks passed! Spawning OpenCode...\x1b[0K\n\n");
64
+ } else {
65
+ // Overwrite the spinner line with the failure message in place
66
+ process.stdout.write("\x1b[1A\r \x1b[31m✗\x1b[0m Diagnostics check failed.\x1b[0K\n\n");
67
+ const failed = checks.filter((c) => !c.pass);
68
+ console.log(`✗ Pluidr diagnostics found ${failed.length} failure${failed.length > 1 ? "s" : ""}:`);
69
+ for (const c of failed) {
70
+ const detail = c.detail ? ` (${c.detail})` : "";
71
+ console.log(` ✗ ${c.component}${detail}`);
72
+ }
73
+
74
+ console.log("\nIt looks like Pluidr is not fully initialized yet.");
75
+ console.log("We highly recommend running the initialization command to set up templates, themes, and configs:");
76
+ console.log(" $ pluidr init");
77
+ console.log("\nYou can view all available commands using:");
78
+ console.log(" $ pluidr --help\n");
79
+
80
+ const answer = await confirm({ message: "Run pluidr init now to complete setup?", initialValue: true });
81
+ if (!isCancel(answer) && answer) {
82
+ await runInit();
83
+ } else {
84
+ process.exit(1);
85
+ }
86
+ }
87
+
88
+ // 4. Launch opencode
89
+ console.log("Launching pluidr...");
90
+ const child = process.platform === "win32"
91
+ ? spawn("opencode", { stdio: "inherit", shell: true })
92
+ : spawn("opencode", [], { stdio: "inherit" })
93
+ child.on("error", (err) => {
94
+ if (err.code === "ENOENT") {
95
+ console.error("Error: opencode not found on PATH. Install it from https://opencode.ai");
96
+ } else {
97
+ console.error(`Error launching opencode: ${err.message}`);
98
+ }
99
+ process.exit(1);
100
+ })
101
+ }