@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.
- package/LICENSE +21 -0
- package/README.md +364 -0
- package/cli/audit.mjs +144 -0
- package/cli/banner.mjs +41 -0
- package/cli/bin.mjs +186 -0
- package/cli/copy.mjs +508 -0
- package/cli/export.mjs +87 -0
- package/cli/init.mjs +147 -0
- package/cli/install.mjs +390 -0
- package/cli/plan-templates.mjs +523 -0
- package/cli/plan.mjs +2087 -0
- package/cli/prompts.mjs +163 -0
- package/cli/update.mjs +273 -0
- package/cli/utils.mjs +153 -0
- package/config/AGENTS.md +282 -0
- package/config/agents/baldr.md +148 -0
- package/config/agents/forseti.md +112 -0
- package/config/agents/frigg.md +101 -0
- package/config/agents/heimdall.md +157 -0
- package/config/agents/hermod.md +144 -0
- package/config/agents/mimir.md +115 -0
- package/config/agents/odin.md +309 -0
- package/config/agents/quick.md +78 -0
- package/config/agents/semble-search.md +44 -0
- package/config/agents/thor.md +97 -0
- package/config/agents/tyr.md +96 -0
- package/config/agents/vidarr.md +100 -0
- package/config/agents/vor.md +140 -0
- package/config/commands/audit.md +1 -0
- package/config/commands/explain.md +1 -0
- package/config/commands/init.md +1 -0
- package/config/commands/learn.md +1 -0
- package/config/commands/pr-review.md +1 -0
- package/config/commands/tailscale-serve.md +96 -0
- package/config/hooks/README.md +29 -0
- package/config/hooks/post-tool-use.md +16 -0
- package/config/hooks/pre-tool-use.md +16 -0
- package/config/opencode.json +52 -0
- package/config/opencode.json.template +52 -0
- package/config/rules/general.md +8 -0
- package/config/rules/git.md +11 -0
- package/config/rules/javascript.md +10 -0
- package/config/rules/python.md +10 -0
- package/config/rules/testing.md +10 -0
- package/config/skills/bizar/README.md +9 -0
- package/config/skills/bizar/SKILL.md +187 -0
- package/config/skills/cpp-coding-standards/README.md +28 -0
- package/config/skills/cpp-coding-standards/SKILL.md +634 -0
- package/config/skills/cpp-coding-standards/agents/openai.yaml +4 -0
- package/config/skills/cpp-coding-standards/references/concurrency.md +320 -0
- package/config/skills/cpp-coding-standards/references/error-handling.md +229 -0
- package/config/skills/cpp-coding-standards/references/memory-safety.md +216 -0
- package/config/skills/cpp-coding-standards/references/modern-idioms.md +282 -0
- package/config/skills/cpp-coding-standards/references/review-checklist.md +96 -0
- package/config/skills/cpp-testing/README.md +28 -0
- package/config/skills/cpp-testing/SKILL.md +304 -0
- package/config/skills/cpp-testing/agents/openai.yaml +4 -0
- package/config/skills/cpp-testing/references/coverage.md +370 -0
- package/config/skills/cpp-testing/references/framework-compare.md +175 -0
- package/config/skills/cpp-testing/references/host-test-for-embedded.md +499 -0
- package/config/skills/cpp-testing/references/mocking.md +364 -0
- package/config/skills/cpp-testing/references/tdd-workflow.md +308 -0
- package/config/skills/embedded-esp-idf/README.md +41 -0
- package/config/skills/embedded-esp-idf/SKILL.md +439 -0
- package/config/skills/embedded-esp-idf/agents/openai.yaml +4 -0
- package/config/skills/embedded-esp-idf/references/freertos-patterns.md +214 -0
- package/config/skills/embedded-esp-idf/references/host-tests.md +164 -0
- package/config/skills/embedded-esp-idf/references/idf-py-commands.md +157 -0
- package/config/skills/embedded-esp-idf/references/kconfig.md +159 -0
- package/config/skills/embedded-esp-idf/references/logging-discipline.md +118 -0
- package/config/skills/embedded-esp-idf/references/memory-and-iram.md +137 -0
- package/config/skills/embedded-esp-idf/references/nvs.md +121 -0
- package/config/skills/embedded-esp-idf/references/packed-structs.md +192 -0
- package/config/skills/embedded-esp-idf/scripts/idf_env.sh +47 -0
- package/config/skills/embedded-esp-idf/scripts/size_check.sh +77 -0
- package/config/skills/self-improvement/SKILL.md +64 -0
- package/package.json +47 -0
- package/templates/plan/htmx.min.js +1 -0
- package/templates/plan/library/bug-investigation.mdx +79 -0
- package/templates/plan/library/decision-record.mdx +71 -0
- package/templates/plan/library/feature-design.mdx +92 -0
- package/templates/plan/meta.json.template +8 -0
- package/templates/plan/plan.canvas.template +1711 -0
- package/templates/plan/plan.html.template +937 -0
- 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
|
+
}
|
package/cli/install.mjs
ADDED
|
@@ -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
|
+
}
|