@polderlabs/bizar 2.3.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.
Files changed (85) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +364 -0
  3. package/cli/audit.mjs +144 -0
  4. package/cli/banner.mjs +41 -0
  5. package/cli/bin.mjs +186 -0
  6. package/cli/copy.mjs +508 -0
  7. package/cli/export.mjs +87 -0
  8. package/cli/init.mjs +147 -0
  9. package/cli/install.mjs +390 -0
  10. package/cli/plan-templates.mjs +523 -0
  11. package/cli/plan.mjs +2087 -0
  12. package/cli/prompts.mjs +163 -0
  13. package/cli/update.mjs +273 -0
  14. package/cli/utils.mjs +153 -0
  15. package/config/AGENTS.md +282 -0
  16. package/config/agents/baldr.md +148 -0
  17. package/config/agents/forseti.md +112 -0
  18. package/config/agents/frigg.md +101 -0
  19. package/config/agents/heimdall.md +157 -0
  20. package/config/agents/hermod.md +144 -0
  21. package/config/agents/mimir.md +115 -0
  22. package/config/agents/odin.md +309 -0
  23. package/config/agents/quick.md +78 -0
  24. package/config/agents/semble-search.md +44 -0
  25. package/config/agents/thor.md +97 -0
  26. package/config/agents/tyr.md +96 -0
  27. package/config/agents/vidarr.md +100 -0
  28. package/config/agents/vor.md +140 -0
  29. package/config/commands/audit.md +1 -0
  30. package/config/commands/explain.md +1 -0
  31. package/config/commands/init.md +1 -0
  32. package/config/commands/learn.md +1 -0
  33. package/config/commands/pr-review.md +1 -0
  34. package/config/commands/tailscale-serve.md +96 -0
  35. package/config/hooks/README.md +29 -0
  36. package/config/hooks/post-tool-use.md +16 -0
  37. package/config/hooks/pre-tool-use.md +16 -0
  38. package/config/opencode.json +52 -0
  39. package/config/opencode.json.template +52 -0
  40. package/config/rules/general.md +8 -0
  41. package/config/rules/git.md +11 -0
  42. package/config/rules/javascript.md +10 -0
  43. package/config/rules/python.md +10 -0
  44. package/config/rules/testing.md +10 -0
  45. package/config/skills/bizar/README.md +9 -0
  46. package/config/skills/bizar/SKILL.md +187 -0
  47. package/config/skills/cpp-coding-standards/README.md +28 -0
  48. package/config/skills/cpp-coding-standards/SKILL.md +634 -0
  49. package/config/skills/cpp-coding-standards/agents/openai.yaml +4 -0
  50. package/config/skills/cpp-coding-standards/references/concurrency.md +320 -0
  51. package/config/skills/cpp-coding-standards/references/error-handling.md +229 -0
  52. package/config/skills/cpp-coding-standards/references/memory-safety.md +216 -0
  53. package/config/skills/cpp-coding-standards/references/modern-idioms.md +282 -0
  54. package/config/skills/cpp-coding-standards/references/review-checklist.md +96 -0
  55. package/config/skills/cpp-testing/README.md +28 -0
  56. package/config/skills/cpp-testing/SKILL.md +304 -0
  57. package/config/skills/cpp-testing/agents/openai.yaml +4 -0
  58. package/config/skills/cpp-testing/references/coverage.md +370 -0
  59. package/config/skills/cpp-testing/references/framework-compare.md +175 -0
  60. package/config/skills/cpp-testing/references/host-test-for-embedded.md +499 -0
  61. package/config/skills/cpp-testing/references/mocking.md +364 -0
  62. package/config/skills/cpp-testing/references/tdd-workflow.md +308 -0
  63. package/config/skills/embedded-esp-idf/README.md +41 -0
  64. package/config/skills/embedded-esp-idf/SKILL.md +439 -0
  65. package/config/skills/embedded-esp-idf/agents/openai.yaml +4 -0
  66. package/config/skills/embedded-esp-idf/references/freertos-patterns.md +214 -0
  67. package/config/skills/embedded-esp-idf/references/host-tests.md +164 -0
  68. package/config/skills/embedded-esp-idf/references/idf-py-commands.md +157 -0
  69. package/config/skills/embedded-esp-idf/references/kconfig.md +159 -0
  70. package/config/skills/embedded-esp-idf/references/logging-discipline.md +118 -0
  71. package/config/skills/embedded-esp-idf/references/memory-and-iram.md +137 -0
  72. package/config/skills/embedded-esp-idf/references/nvs.md +121 -0
  73. package/config/skills/embedded-esp-idf/references/packed-structs.md +192 -0
  74. package/config/skills/embedded-esp-idf/scripts/idf_env.sh +47 -0
  75. package/config/skills/embedded-esp-idf/scripts/size_check.sh +77 -0
  76. package/config/skills/self-improvement/SKILL.md +64 -0
  77. package/package.json +47 -0
  78. package/templates/plan/htmx.min.js +1 -0
  79. package/templates/plan/library/bug-investigation.mdx +79 -0
  80. package/templates/plan/library/decision-record.mdx +71 -0
  81. package/templates/plan/library/feature-design.mdx +92 -0
  82. package/templates/plan/meta.json.template +8 -0
  83. package/templates/plan/plan.canvas.template +1711 -0
  84. package/templates/plan/plan.html.template +937 -0
  85. package/templates/plan/plan.mdx.template +46 -0
package/cli/export.mjs ADDED
@@ -0,0 +1,87 @@
1
+ import chalk from 'chalk';
2
+ import { existsSync, readFileSync, readdirSync, mkdirSync, writeFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+
5
+ const HOME = process.env.HOME || '/home/drb0rk';
6
+ const CONFIG_DIR = process.env.XDG_CONFIG_HOME
7
+ ? join(process.env.XDG_CONFIG_HOME, 'opencode')
8
+ : join(HOME, '.config/opencode');
9
+
10
+ export async function runExport(target) {
11
+ const validTargets = ['claude', 'cursor', 'opencode'];
12
+ if (target && !validTargets.includes(target)) {
13
+ console.log(chalk.red(` Unknown target "${target}". Valid: ${validTargets.join(', ')}`));
14
+ return;
15
+ }
16
+
17
+ const targets = target ? [target] : validTargets;
18
+
19
+ for (const t of targets) {
20
+ console.log(chalk.bold.hex('#6366f1')(`\n Exporting to ${t}...\n`));
21
+ await exportTo(t);
22
+ }
23
+ }
24
+
25
+ async function exportTo(target) {
26
+ const agentFiles = readdirSync(join(CONFIG_DIR, 'agents')).filter(f => f.endsWith('.md'));
27
+
28
+ switch (target) {
29
+ case 'claude': {
30
+ const claudeDir = join(HOME, '.claude');
31
+ mkdirSync(claudeDir, { recursive: true });
32
+
33
+ // Copy agents to .claude/agents/
34
+ const claudeAgentDir = join(claudeDir, 'agents');
35
+ mkdirSync(claudeAgentDir, { recursive: true });
36
+ for (const file of agentFiles) {
37
+ const content = readFileSync(join(CONFIG_DIR, 'agents', file), 'utf-8');
38
+ writeFileSync(join(claudeAgentDir, file), content);
39
+ }
40
+ console.log(chalk.green(` ✓ ${agentFiles.length} agents → ${claudeAgentDir}`));
41
+
42
+ // Copy rules
43
+ const rulesDir = join(CONFIG_DIR, 'rules');
44
+ if (existsSync(rulesDir)) {
45
+ const claudeRulesDir = join(claudeDir, 'rules');
46
+ mkdirSync(claudeRulesDir, { recursive: true });
47
+ const rules = readdirSync(rulesDir).filter(f => f.endsWith('.md'));
48
+ for (const file of rules) {
49
+ const content = readFileSync(join(rulesDir, file), 'utf-8');
50
+ writeFileSync(join(claudeRulesDir, file), content);
51
+ }
52
+ console.log(chalk.green(` ✓ ${rules.length} rules → ${claudeRulesDir}`));
53
+ }
54
+
55
+ break;
56
+ }
57
+
58
+ case 'cursor': {
59
+ const cursorDir = join(HOME, '.cursor');
60
+
61
+ // Convert agents to .cursor/rules/ format
62
+ const cursorRulesDir = join(cursorDir, 'rules');
63
+ mkdirSync(cursorRulesDir, { recursive: true });
64
+
65
+ for (const file of agentFiles) {
66
+ const content = readFileSync(join(CONFIG_DIR, 'agents', file), 'utf-8');
67
+ // Cursor uses YAML frontmatter .mdc files
68
+ const name = file.replace('.md', '');
69
+ const cursorContent = `---
70
+ description: ${name} agent from BizarHarness
71
+ globs: **/*
72
+ ---
73
+ ${content}
74
+ `;
75
+ writeFileSync(join(cursorRulesDir, `${name}.mdc`), cursorContent);
76
+ }
77
+ console.log(chalk.green(` ✓ ${agentFiles.length} agents → ${cursorRulesDir} (as .mdc rules)`));
78
+
79
+ break;
80
+ }
81
+
82
+ case 'opencode': {
83
+ console.log(chalk.dim(' Already configured for opencode. Run `bizar` for interactive setup.'));
84
+ break;
85
+ }
86
+ }
87
+ }
package/cli/init.mjs ADDED
@@ -0,0 +1,147 @@
1
+ import chalk from 'chalk';
2
+ import { existsSync, readFileSync, mkdirSync, writeFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { execSync } from 'node:child_process';
5
+
6
+ function detectStack(cwd) {
7
+ const stack = { language: null, framework: null, database: null, tools: [], build: null, test: null, runner: null };
8
+
9
+ // Language detection
10
+ if (existsSync(join(cwd, 'package.json'))) {
11
+ stack.language = 'JavaScript/TypeScript';
12
+ try {
13
+ const pkg = JSON.parse(readFileSync(join(cwd, 'package.json'), 'utf-8'));
14
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
15
+ if (deps.next) stack.framework = 'Next.js';
16
+ else if (deps.react) stack.framework = 'React';
17
+ else if (deps.vue) stack.framework = 'Vue';
18
+ else if (deps.express) stack.framework = 'Express';
19
+ else if (deps.nest) stack.framework = 'NestJS';
20
+ if (deps.prisma) stack.database = 'PostgreSQL (Prisma)';
21
+ else if (deps['@prisma/client']) stack.database = 'PostgreSQL (Prisma)';
22
+ else if (deps.drizzle) stack.database = 'PostgreSQL (Drizzle)';
23
+ else if (deps.typeorm) stack.database = 'PostgreSQL (TypeORM)';
24
+ if (pkg.scripts) {
25
+ if (pkg.scripts.test) stack.test = pkg.scripts.test;
26
+ if (pkg.scripts.build) stack.build = pkg.scripts.build;
27
+ if (pkg.scripts.dev) stack.runner = pkg.scripts.dev;
28
+ }
29
+ if (existsSync(join(cwd, 'tsconfig.json'))) stack.language = 'TypeScript';
30
+ } catch { /* ignore parse errors */ }
31
+ }
32
+
33
+ if (existsSync(join(cwd, 'pyproject.toml'))) {
34
+ stack.language = 'Python';
35
+ const content = readFileSync(join(cwd, 'pyproject.toml'), 'utf-8');
36
+ if (content.includes('django')) stack.framework = 'Django';
37
+ else if (content.includes('fastapi') || content.includes('fastapi')) stack.framework = 'FastAPI';
38
+ else if (content.includes('flask')) stack.framework = 'Flask';
39
+ }
40
+
41
+ if (existsSync(join(cwd, 'Cargo.toml'))) {
42
+ stack.language = 'Rust';
43
+ }
44
+
45
+ if (existsSync(join(cwd, 'go.mod'))) {
46
+ stack.language = 'Go';
47
+ }
48
+
49
+ // Tool detection
50
+ if (existsSync(join(cwd, '.github/workflows'))) stack.tools.push('GitHub Actions');
51
+ if (existsSync(join(cwd, 'Dockerfile'))) stack.tools.push('Docker');
52
+ if (existsSync(join(cwd, 'docker-compose.yml')) || existsSync(join(cwd, 'docker-compose.yaml'))) stack.tools.push('Docker Compose');
53
+ if (existsSync(join(cwd, '.nvmrc'))) stack.tools.push('nvm');
54
+
55
+ return stack;
56
+ }
57
+
58
+ export async function runInit(cwd) {
59
+ console.log(chalk.bold.hex('#10b981')('\n ᛗ BIZARHARNESS INIT ᛗ\n'));
60
+
61
+ const bizarDir = join(cwd, '.bizar');
62
+ mkdirSync(bizarDir, { recursive: true });
63
+
64
+ // Detect stack
65
+ console.log(chalk.dim(' Detecting project stack...'));
66
+ const stack = detectStack(cwd);
67
+ console.log(` Language: ${stack.language || chalk.yellow('unknown')}`);
68
+ console.log(` Framework: ${stack.framework || chalk.yellow('none detected')}`);
69
+ console.log(` Database: ${stack.database || chalk.yellow('none detected')}`);
70
+ if (stack.tools.length > 0) console.log(` Tools: ${stack.tools.join(', ')}`);
71
+ console.log();
72
+
73
+ // Install relevant skills via Skills CLI
74
+ console.log(chalk.dim(' Installing relevant skills...'));
75
+ const skillRepos = [];
76
+ if (stack.language?.toLowerCase().includes('typescript') || stack.framework?.toLowerCase().includes('next') || stack.framework?.toLowerCase().includes('react')) {
77
+ skillRepos.push('skills add vercel-labs/agent-skills --all -y');
78
+ skillRepos.push('skills add shadcn/ui --all -y');
79
+ }
80
+ if (stack.framework?.toLowerCase().includes('django') || stack.framework?.toLowerCase().includes('fastapi') || stack.framework?.toLowerCase().includes('flask')) {
81
+ skillRepos.push('skills add supabase/agent-skills --all -y');
82
+ }
83
+ if (stack.language?.toLowerCase().includes('python')) {
84
+ skillRepos.push('skills add mattpocock/skills -y');
85
+ }
86
+ if (stack.language?.toLowerCase().includes('rust')) {
87
+ skillRepos.push('skills add supabase/agent-skills --all -y');
88
+ }
89
+
90
+ for (const cmd of skillRepos) {
91
+ try {
92
+ execSync(cmd, { stdio: 'pipe', timeout: 30000 });
93
+ console.log(chalk.green(` ✓ ${cmd}`));
94
+ } catch {
95
+ console.log(chalk.dim(` - ${cmd} (skipped)`));
96
+ }
97
+ }
98
+ console.log();
99
+
100
+ // Generate PROJECT.md
101
+ const projectName = cwd.split('/').pop() || 'my-project';
102
+ const projectMd = `# ${projectName}
103
+
104
+ ${stack.framework ? `${stack.framework} ` : ''}${stack.language || ''} project.
105
+
106
+ ## Stack
107
+ - Language: ${stack.language || 'unknown'}
108
+ ${stack.framework ? `- Framework: ${stack.framework}` : ''}
109
+ ${stack.database ? `- Database: ${stack.database}` : ''}
110
+ ${stack.tools.length > 0 ? `- Tools: ${stack.tools.join(', ')}` : ''}
111
+ ${stack.build ? `- Build: \`${stack.build}\`` : ''}
112
+ ${stack.test ? `- Test: \`${stack.test}\`` : ''}
113
+ ${stack.runner ? `- Dev: \`${stack.runner}\`` : ''}
114
+
115
+ ## Conventions
116
+ - Follow BizarHarness Always-On Rules (see \`rules/\`)
117
+ - Use TDD for all new features
118
+ - Conventional commits
119
+
120
+ ## Entry Points
121
+ - \`${stack.runner || 'npm run dev'}\` — development
122
+ - \`${stack.test || 'npm test'}\` — testing
123
+ - \`${stack.build || 'npm run build'}\` — build
124
+ `;
125
+
126
+ const projPath = join(bizarDir, 'PROJECT.md');
127
+ writeFileSync(projPath, projectMd);
128
+ console.log(chalk.green(` ✓ Created ${projPath}`));
129
+
130
+ // Generate AGENTS_SELF_IMPROVEMENT.md if not exists
131
+ const siPath = join(bizarDir, 'AGENTS_SELF_IMPROVEMENT.md');
132
+ if (!existsSync(siPath)) {
133
+ writeFileSync(siPath, `# Agents Self-Improvement Log
134
+
135
+ ## Active Rules
136
+ - Use BizarHarness Always-On Rules
137
+ - Cost-aware routing: prefer cheapest capable agent
138
+ - Always split implementation across 2+ parallel agents
139
+
140
+ ## Entries
141
+ `);
142
+ console.log(chalk.green(` ✓ Created ${siPath}`));
143
+ }
144
+
145
+ console.log(chalk.dim('\n Project initialized. Run `@frigg` to ask questions about the codebase.\n'));
146
+ return true;
147
+ }
@@ -0,0 +1,390 @@
1
+ import chalk from 'chalk';
2
+ import boxen from 'boxen';
3
+ import { existsSync } from 'node:fs';
4
+
5
+ import { showBanner, showPantheon, sectionHeading } from './banner.mjs';
6
+ import { promptComponents, promptInstallMode, promptAgents, promptSkillPacks, promptApiKeys, promptConfirmInstall, promptRestartOpenCode } from './prompts.mjs';
7
+ import { detectOpenCode, detectRtk, detectSemble, detectSkillsCli, buildSummary, opencodeAgentsDir, opencodeConfigDir, repoPath } from './utils.mjs';
8
+ import { installAgents, installAgentsMd, installSkill, installOpencodeJson, installBizarFolder, installPluginBizar, installRtk, installSemble, installSkillsCli, installCuratedSkills, installRules, installHooks, installCommands, mergeToolsIntoUserConfig } from './copy.mjs';
9
+
10
+ const AGENT_FILES = [
11
+ 'odin.md', 'vor.md', 'frigg.md', 'quick.md',
12
+ 'mimir.md', 'heimdall.md', 'hermod.md', 'thor.md', 'baldr.md',
13
+ 'tyr.md', 'vidarr.md', 'forseti.md',
14
+ 'semble-search.md',
15
+ ];
16
+
17
+ /**
18
+ * Install the Bizar opencode plugin from the separate global npm package
19
+ * `@polderlabs/bizar-plugin`. The main `@polderlabs/bizar` package
20
+ * no longer ships `plugins/bizar/` — the plugin lives in its own scoped package
21
+ * so it can be versioned and published independently.
22
+ *
23
+ * If the plugin package is globally installed, copies its contents into
24
+ * `~/.config/opencode/plugins/bizar/` (or the platform-equivalent path via
25
+ * `opencodeConfigDir()`). Otherwise, prints a hint directing the user to run
26
+ * `npm install -g @polderlabs/bizar-plugin`.
27
+ *
28
+ * Returns `true` if the plugin was installed, `false` otherwise. Never throws.
29
+ */
30
+ export async function installPluginFromGlobal() {
31
+ const { execSync } = await import('node:child_process');
32
+ const { mkdir, readdir, copyFile } = await import('node:fs/promises');
33
+ const { join } = await import('node:path');
34
+
35
+ let globalRoot;
36
+ try {
37
+ globalRoot = execSync('npm root -g', { stdio: ['ignore', 'pipe', 'ignore'] })
38
+ .toString()
39
+ .trim();
40
+ } catch {
41
+ console.log(chalk.yellow(' ⚠ Could not determine npm global root — skipping plugin install'));
42
+ return false;
43
+ }
44
+
45
+ const pluginPath = join(globalRoot, '@polderlabs', 'bizar-plugin');
46
+ if (!existsSync(pluginPath)) {
47
+ console.log(chalk.dim(' ℹ Bizar plugin not installed globally. To install it:'));
48
+ console.log(chalk.dim(' npm install -g @polderlabs/bizar-plugin'));
49
+ return false;
50
+ }
51
+
52
+ const destDir = join(opencodeConfigDir(), 'plugins', 'bizar');
53
+ await mkdir(destDir, { recursive: true });
54
+
55
+ async function copyRecursive(srcDir, dstDir) {
56
+ const entries = await readdir(srcDir, { withFileTypes: true });
57
+ for (const entry of entries) {
58
+ const src = join(srcDir, entry.name);
59
+ const dst = join(dstDir, entry.name);
60
+ if (entry.isDirectory()) {
61
+ if (entry.name === 'node_modules' || entry.name === 'dist') continue;
62
+ await mkdir(dst, { recursive: true });
63
+ await copyRecursive(src, dst);
64
+ } else {
65
+ if (entry.name === '.DS_Store' || entry.name.endsWith('.log')) continue;
66
+ await copyFile(src, dst);
67
+ }
68
+ }
69
+ }
70
+
71
+ try {
72
+ await copyRecursive(pluginPath, destDir);
73
+ console.log(chalk.green(` ✓ Bizar plugin installed from global package`));
74
+ console.log(chalk.dim(` ${pluginPath} → ${destDir}`));
75
+ return true;
76
+ } catch (err) {
77
+ console.log(chalk.yellow(` ⚠ Failed to copy Bizar plugin: ${err.message}`));
78
+ return false;
79
+ }
80
+ }
81
+
82
+ export async function runInstaller() {
83
+ showBanner();
84
+
85
+ // ── Detect opencode ──
86
+ sectionHeading('Pre-flight');
87
+ const env = await detectOpenCode();
88
+
89
+ if (!env.exists) {
90
+ console.log(chalk.yellow(' ⚠ opencode config directory not found.'));
91
+ console.log(chalk.dim(' The installer will create it at:'));
92
+ console.log(chalk.dim(` ${env.configDir}`));
93
+ } else {
94
+ console.log(chalk.green(` ✓ opencode detected at ${env.configDir}`));
95
+ if (env.version) console.log(chalk.dim(` version ${env.version}`));
96
+ }
97
+
98
+ const rtkInstalled = await detectRtk();
99
+ if (rtkInstalled) {
100
+ console.log(chalk.green(' ✓ RTK detected (token optimizer)'));
101
+ } else {
102
+ console.log(chalk.yellow(' ○ RTK not detected — will install'));
103
+ }
104
+
105
+ const sembleInstalled = await detectSemble();
106
+ if (sembleInstalled) {
107
+ console.log(chalk.green(' ✓ Semble detected (code search)'));
108
+ } else {
109
+ console.log(chalk.yellow(' ○ Semble not detected — will install'));
110
+ }
111
+
112
+ const skillsCliInstalled = await detectSkillsCli();
113
+ if (skillsCliInstalled) {
114
+ console.log(chalk.green(' ✓ Skills CLI detected (skill discovery)'));
115
+ } else {
116
+ console.log(chalk.yellow(' ○ Skills CLI not detected — will install'));
117
+ }
118
+ console.log();
119
+
120
+ // ── Step 1: Component selection ──
121
+ sectionHeading('Component Selection');
122
+ const components = await promptComponents();
123
+
124
+ // ── Step 2: Agent selection (if agents component chosen) ──
125
+ let selectedAgents = [];
126
+ if (components.includes('agents')) {
127
+ sectionHeading('Agent Selection');
128
+ selectedAgents = await promptAgents();
129
+ }
130
+
131
+ // ── Step 3: Install mode ──
132
+ sectionHeading('Installation Mode');
133
+ const mode = await promptInstallMode();
134
+
135
+ // ── Step 4: Skill packs (via skills.sh) ──
136
+ sectionHeading('Skills from skills.sh');
137
+ const skillPacks = await promptSkillPacks();
138
+
139
+ // ── Step 5: Build summary & confirm ──
140
+ const summary = buildSummary(components, selectedAgents, env.configDir, skillPacks);
141
+ showPantheon();
142
+ console.log();
143
+ console.log(chalk.dim(' Summary:'));
144
+ console.log(chalk.dim(` Components : ${summary.components}`));
145
+ console.log(chalk.dim(` Agents : ${summary.agents}`));
146
+ console.log(chalk.dim(` Target : ${summary.target}`));
147
+ console.log(chalk.dim(` Mode : ${mode}`));
148
+ console.log();
149
+
150
+ const confirmed = await promptConfirmInstall(summary);
151
+ if (!confirmed) {
152
+ console.log(chalk.yellow('\n Installation cancelled.\n'));
153
+ process.exit(0);
154
+ }
155
+
156
+ // ── Step 5: Install ──
157
+ console.log();
158
+ sectionHeading('Installing');
159
+
160
+ if (components.includes('agents') && selectedAgents.length > 0) {
161
+ await installAgents(selectedAgents, mode);
162
+ }
163
+
164
+ if (components.includes('agents-md')) {
165
+ await installAgentsMd(mode);
166
+ }
167
+
168
+ if (components.includes('skill-bizar')) {
169
+ await installSkill('bizar');
170
+ }
171
+
172
+ if (components.includes('skill-improve')) {
173
+ await installSkill('self-improvement');
174
+ }
175
+
176
+ if (components.includes('skill-cpp-std')) {
177
+ await installSkill('cpp-coding-standards');
178
+ }
179
+
180
+ if (components.includes('skill-cpp-test')) {
181
+ await installSkill('cpp-testing');
182
+ }
183
+
184
+ if (components.includes('skill-esp-idf')) {
185
+ await installSkill('embedded-esp-idf');
186
+ }
187
+
188
+ if (components.includes('opencode-json')) {
189
+ await installOpencodeJson(mode);
190
+ }
191
+
192
+ // Always attempt to merge the 7 BizarHarness tool keys idempotently.
193
+ // Safe to call even if opencode-json wasn't selected — skips silently.
194
+ {
195
+ const result = await mergeToolsIntoUserConfig();
196
+ if (result.merged) {
197
+ console.log(chalk.green(` ✓ ${result.added.length} BizarHarness tool key(s) merged into opencode.json`));
198
+ }
199
+ }
200
+
201
+ if (components.includes('bizar')) {
202
+ await installBizarFolder();
203
+ }
204
+
205
+ if (components.includes('plugin-bizar')) {
206
+ const result = await installPluginBizar();
207
+ if (result.errors.length > 0) {
208
+ console.log(chalk.yellow(` ⚠ Plugin install: ${result.errors.length} error(s)`));
209
+ }
210
+ }
211
+
212
+ // Also try to install the plugin from the separate global npm package
213
+ // `@polderlabs/bizar-plugin` (preferred path going forward).
214
+ await installPluginFromGlobal();
215
+
216
+ // ── Rules, hooks, commands (optional components) ──
217
+ if (components.includes('rules')) {
218
+ const n = await installRules();
219
+ console.log(chalk.green(` ✓ ${n} rules installed`));
220
+ }
221
+ if (components.includes('hooks')) {
222
+ const n = await installHooks();
223
+ console.log(chalk.green(` ✓ ${n} hooks installed`));
224
+ }
225
+ if (components.includes('commands')) {
226
+ const n = await installCommands();
227
+ console.log(chalk.green(` ✓ ${n} commands installed`));
228
+ }
229
+
230
+ // ── RTK (always installed — token optimization required) ──
231
+ await installRtk();
232
+
233
+ // ── Semble (always installed — code search required) ──
234
+ await installSemble();
235
+
236
+ // ── Skills CLI (always installed — skill discovery required) ──
237
+ await installSkillsCli();
238
+
239
+ // ── Skill packs (via skills.sh ecosystem) ──
240
+ for (const pack of skillPacks) {
241
+ await installCuratedSkills([pack]);
242
+ }
243
+
244
+ // ── Step 6: API keys ──
245
+ sectionHeading('API Keys');
246
+ console.log(chalk.dim(' You can configure API keys now or later via /connect in opencode.'));
247
+ const keys = await promptApiKeys();
248
+
249
+ if (keys.opencodeZen || keys.minimax || keys.openai || keys.hindsight) {
250
+ console.log(chalk.dim('\n Keys noted. Add them to your opencode.json or run /connect in opencode.\n'));
251
+ }
252
+
253
+ // ── Summary ──
254
+ console.log();
255
+ console.log(boxen(
256
+ chalk.bold.hex('#6366f1')(' ⚡ BIZARHARNESS INSTALLED ⚡\n') +
257
+ '\n' +
258
+ chalk.dim(' Norse Pantheon active.\n') +
259
+ '\n' +
260
+ chalk.green(` ✓ ${selectedAgents.length} agents installed`) + '\n' +
261
+ chalk.green(` ✓ ${summary.parts.length} components configured`) + '\n' +
262
+ '\n' +
263
+ chalk.green(' ✓ RTK ') + chalk.dim(`(${rtkInstalled ? 'already configured' : 'installed & configured'})`) + '\n' +
264
+ chalk.green(' ✓ Semble ') + chalk.dim(`(${sembleInstalled ? 'ready' : 'installed'})`) + '\n' +
265
+ chalk.green(' ✓ Skills CLI ') + chalk.dim(`(${skillsCliInstalled ? 'ready' : 'installed'})`) + '\n' +
266
+ (skillPacks.length > 0 ? chalk.green(` ✓ Skill packs: ${skillPacks.join(', ')}`) + '\n' : '') + '\n' +
267
+ chalk.hex('#a855f7')(' Next steps:') + '\n' +
268
+ chalk.hex('#a855f7')(' 1. Restart opencode') + '\n' +
269
+ chalk.hex('#a855f7')(' 2. Run /connect to add API keys') + '\n' +
270
+ chalk.hex('#a855f7')(' 3. Run /models to verify agents'),
271
+ {
272
+ padding: 1,
273
+ margin: 0,
274
+ borderStyle: 'round',
275
+ borderColor: '#6366f1',
276
+ },
277
+ ));
278
+ console.log();
279
+
280
+ // ── Restart prompt ──
281
+ const shouldRestart = await promptRestartOpenCode();
282
+ if (shouldRestart) {
283
+ console.log(chalk.dim(' Restarting opencode...'));
284
+ try {
285
+ const { execSync } = await import('node:child_process');
286
+ execSync('opencode', { stdio: 'inherit' });
287
+ } catch {
288
+ console.log(chalk.yellow(' Could not restart automatically. Restart opencode manually.'));
289
+ }
290
+ }
291
+
292
+ // ── Post-install ──
293
+ console.log(chalk.dim('\n Odin watches. The Pantheon awaits. ᛟ\n'));
294
+ }
295
+
296
+ export async function runPostInstall() {
297
+ const { mkdirSync, copyFileSync, existsSync } = await import('node:fs');
298
+ const { join } = await import('node:path');
299
+ const { execSync } = await import('node:child_process');
300
+
301
+ const env = await detectOpenCode();
302
+ if (!env.exists) {
303
+ console.log('BizarHarness: opencode not detected — skipping auto-install.');
304
+ return;
305
+ }
306
+
307
+ mkdirSync(opencodeAgentsDir(), { recursive: true });
308
+
309
+ for (const file of AGENT_FILES) {
310
+ const src = repoPath('config', 'agents', file);
311
+ const dest = join(opencodeAgentsDir(), file);
312
+ if (!existsSync(dest)) {
313
+ copyFileSync(src, dest);
314
+ }
315
+ }
316
+ console.log('BizarHarness: agents installed.');
317
+
318
+ // Install RTK
319
+ const rtkPresent = await detectRtk();
320
+ if (!rtkPresent) {
321
+ console.log('BizarHarness: installing RTK (token optimizer)...');
322
+ try {
323
+ execSync(
324
+ 'curl -fsSL https://raw.githubusercontent.com/rtk-ai/rtk/refs/heads/master/install.sh | sh',
325
+ { stdio: 'pipe', timeout: 60000 },
326
+ );
327
+ execSync('rtk init -g --opencode', { stdio: 'pipe' });
328
+ console.log('BizarHarness: RTK installed and configured.');
329
+ } catch {
330
+ console.log('BizarHarness: RTK install failed. Install manually from https://github.com/rtk-ai/rtk');
331
+ }
332
+ } else {
333
+ try {
334
+ execSync('rtk init -g --opencode', { stdio: 'pipe' });
335
+ console.log('BizarHarness: RTK configured for opencode.');
336
+ } catch {
337
+ console.log('BizarHarness: could not configure RTK. Run `rtk init -g --opencode` manually.');
338
+ }
339
+ }
340
+
341
+ // Install Semble
342
+ const semblePresent = await detectSemble();
343
+ if (!semblePresent) {
344
+ console.log('BizarHarness: installing Semble (code search)...');
345
+ try {
346
+ const hasUv = await detectUv();
347
+ if (!hasUv) {
348
+ execSync(
349
+ 'curl -LsSf https://astral.sh/uv/install.sh | sh',
350
+ { stdio: 'pipe', timeout: 60000 },
351
+ );
352
+ }
353
+ execSync('uv tool install "semble[mcp]"', { stdio: 'pipe', timeout: 60000 });
354
+ console.log('BizarHarness: Semble installed.');
355
+ } catch {
356
+ console.log('BizarHarness: Semble install failed. Run `uv tool install "semble[mcp]"` manually.');
357
+ }
358
+ } else {
359
+ console.log('BizarHarness: Semble ready.');
360
+ }
361
+
362
+ // Install Skills CLI
363
+ const skillsPresent = await detectSkillsCli();
364
+ if (!skillsPresent) {
365
+ console.log('BizarHarness: installing Skills CLI...');
366
+ try {
367
+ execSync('npm install -g skills', { stdio: 'pipe', timeout: 30000 });
368
+ console.log('BizarHarness: Skills CLI installed.');
369
+ } catch {
370
+ console.log('BizarHarness: Skills CLI install failed. Run `npm install -g skills` manually.');
371
+ }
372
+ } else {
373
+ console.log('BizarHarness: Skills CLI ready.');
374
+ }
375
+
376
+ // Install core skill pack
377
+ console.log('BizarHarness: installing core skills (find-skills, skill-creator)...');
378
+ try {
379
+ execSync('skills add vercel-labs/skills --all -y', { stdio: 'pipe', timeout: 60000 });
380
+ console.log('BizarHarness: core skills installed.');
381
+ } catch {
382
+ console.log('BizarHarness: core skill install skipped — run `skills add vercel-labs/skills --all -y` manually.');
383
+ }
384
+
385
+ // Try to install the Bizar plugin from the separate global npm package.
386
+ // Non-fatal: prints a hint if the package isn't installed globally yet.
387
+ await installPluginFromGlobal();
388
+
389
+ console.log('Run `bizar` for interactive setup.');
390
+ }