@polderlabs/bizar 3.5.4 → 3.7.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/cli/audit.mjs +3 -5
- package/cli/banner.mjs +1 -1
- package/cli/bin.mjs +165 -70
- package/cli/copy.mjs +23 -6
- package/cli/export.mjs +15 -7
- package/cli/init.mjs +25 -17
- package/cli/install.mjs +6 -5
- package/cli/plan.mjs +62 -35
- package/cli/prompts.mjs +10 -5
- package/cli/service.mjs +32 -9
- package/cli/update.mjs +18 -2
- package/cli/utils.mjs +28 -29
- package/config/AGENTS.md +100 -0
- package/config/agents/baldr.md +57 -1
- package/config/agents/forseti.md +56 -0
- package/config/agents/frigg.md +55 -1
- package/config/agents/heimdall.md +57 -1
- package/config/agents/hermod.md +56 -0
- package/config/agents/mimir.md +57 -1
- package/config/agents/odin.md +58 -0
- package/config/agents/quick.md +56 -0
- package/config/agents/semble-search.md +59 -0
- package/config/agents/thor.md +56 -0
- package/config/agents/tyr.md +56 -0
- package/config/agents/vidarr.md +56 -0
- package/config/agents/vor.md +55 -1
- package/config/opencode.json +52 -24
- package/config/opencode.json.template +310 -0
- package/package.json +3 -18
package/cli/audit.mjs
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { readFileSync, existsSync, readdirSync } from 'node:fs';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
|
+
import { opencodeConfigDir } from './utils.mjs';
|
|
4
5
|
|
|
5
|
-
const
|
|
6
|
-
const CONFIG_DIR = process.env.XDG_CONFIG_HOME
|
|
7
|
-
? join(process.env.XDG_CONFIG_HOME, 'opencode')
|
|
8
|
-
: join(HOME, '.config/opencode');
|
|
6
|
+
const CONFIG_DIR = opencodeConfigDir();
|
|
9
7
|
|
|
10
8
|
export async function runAudit() {
|
|
11
9
|
console.log(chalk.bold.hex('#ef4444')('\n ⚔ BIZARHARNESS AUDIT ⚔\n'));
|
|
@@ -141,4 +139,4 @@ export async function runAudit() {
|
|
|
141
139
|
console.log(chalk.bold(` Security Score: ${scoreColor(`${score}/100`)}\n`));
|
|
142
140
|
|
|
143
141
|
return { issues, warnings, score, totalIssues, totalWarnings };
|
|
144
|
-
}
|
|
142
|
+
}
|
package/cli/banner.mjs
CHANGED
|
@@ -25,7 +25,7 @@ export function showBanner() {
|
|
|
25
25
|
console.log(chalk.hex('#6366f1').bold(RUNE_HELM));
|
|
26
26
|
console.log(chalk.hex('#a855f7')(' Norse Pantheon Agent System for opencode'));
|
|
27
27
|
console.log();
|
|
28
|
-
console.log(chalk.dim('
|
|
28
|
+
console.log(chalk.dim(' 13 agents · 4 cost tiers · per-project Hindsight memory · RTK · Semble · Skills CLI'));
|
|
29
29
|
console.log();
|
|
30
30
|
}
|
|
31
31
|
|
package/cli/bin.mjs
CHANGED
|
@@ -18,9 +18,11 @@
|
|
|
18
18
|
* --web / --no-web / --web-only / --bg / --detach
|
|
19
19
|
*/
|
|
20
20
|
import { existsSync } from 'node:fs';
|
|
21
|
+
import { readFileSync } from 'node:fs';
|
|
21
22
|
import { join } from 'node:path';
|
|
23
|
+
import { fileURLToPath } from 'node:url';
|
|
22
24
|
import chalk from 'chalk';
|
|
23
|
-
import { runInstaller
|
|
25
|
+
import { runInstaller } from './install.mjs';
|
|
24
26
|
import { runAudit } from './audit.mjs';
|
|
25
27
|
import { runInit } from './init.mjs';
|
|
26
28
|
import { runExport } from './export.mjs';
|
|
@@ -29,20 +31,46 @@ import { runUpdate } from './update.mjs';
|
|
|
29
31
|
import { ensureSetup, checkSetupStatus } from './bootstrap.mjs';
|
|
30
32
|
|
|
31
33
|
const args = process.argv.slice(2);
|
|
34
|
+
const isHelpRequest = args.includes('--help') || args.includes('-h');
|
|
35
|
+
const isVersionRequest = args.includes('--version') || args.includes('-v');
|
|
32
36
|
|
|
33
37
|
// ── Bootstrap ─────────────────────────────────────────────────────────────────
|
|
34
38
|
// Every bin command checks setup status on first invocation.
|
|
35
39
|
// Skip only when: --postinstall (manual trigger), --check (status only),
|
|
36
|
-
// BIZAR_SKIP_INSTALL=1 (disabled),
|
|
40
|
+
// --help / --version (informational), BIZAR_SKIP_INSTALL=1 (disabled),
|
|
41
|
+
// or already handled via npm script.
|
|
37
42
|
if (
|
|
38
43
|
!args.includes('--postinstall') &&
|
|
39
44
|
!args.includes('--check') &&
|
|
45
|
+
!isHelpRequest &&
|
|
46
|
+
!isVersionRequest &&
|
|
40
47
|
!process.env.BIZAR_SKIP_INSTALL
|
|
41
48
|
) {
|
|
42
49
|
await ensureSetup({ silent: true });
|
|
43
50
|
}
|
|
44
51
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
45
52
|
|
|
53
|
+
function readCliVersion() {
|
|
54
|
+
try {
|
|
55
|
+
const packageJsonPath = fileURLToPath(new URL('../package.json', import.meta.url));
|
|
56
|
+
const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
|
|
57
|
+
return pkg.version || 'unknown';
|
|
58
|
+
} catch {
|
|
59
|
+
return 'unknown';
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function getBizarConfigDir() {
|
|
64
|
+
if (process.platform === 'win32') {
|
|
65
|
+
return process.env.APPDATA
|
|
66
|
+
? join(process.env.APPDATA, 'bizar')
|
|
67
|
+
: join(process.env.HOME || process.cwd(), '.config', 'bizar');
|
|
68
|
+
}
|
|
69
|
+
return process.env.XDG_CONFIG_HOME
|
|
70
|
+
? join(process.env.XDG_CONFIG_HOME, 'bizar')
|
|
71
|
+
: join(process.env.HOME || process.cwd(), '.config', 'bizar');
|
|
72
|
+
}
|
|
73
|
+
|
|
46
74
|
function showHelp() {
|
|
47
75
|
console.log(`
|
|
48
76
|
Bizar — Norse Pantheon Agent System for opencode
|
|
@@ -64,6 +92,7 @@ function showHelp() {
|
|
|
64
92
|
bizar dashboard Launch the web dashboard (uses bizar)
|
|
65
93
|
bizar --setup Re-run setup manually (agents, plugin, RTK, Semble, Skills CLI)
|
|
66
94
|
bizar --check Print setup status as JSON, exit 1 if setup needed
|
|
95
|
+
bizar --version Show package version
|
|
67
96
|
bizar --help Show this help
|
|
68
97
|
|
|
69
98
|
Install:
|
|
@@ -91,12 +120,55 @@ function showAuditHelp() {
|
|
|
91
120
|
function showInitHelp() {
|
|
92
121
|
console.log(`
|
|
93
122
|
bizar init — Initialize .bizar/ in current project
|
|
123
|
+
|
|
124
|
+
Usage:
|
|
125
|
+
bizar init
|
|
126
|
+
|
|
127
|
+
Description:
|
|
128
|
+
Detects the project stack, creates .bizar/PROJECT.md and
|
|
129
|
+
.bizar/AGENTS_SELF_IMPROVEMENT.md, and installs relevant skills.
|
|
94
130
|
`);
|
|
95
131
|
}
|
|
96
132
|
|
|
97
133
|
function showExportHelp() {
|
|
98
134
|
console.log(`
|
|
99
135
|
bizar export — Export agents/rules to another harness
|
|
136
|
+
|
|
137
|
+
Usage:
|
|
138
|
+
bizar export [claude|cursor|opencode]
|
|
139
|
+
|
|
140
|
+
Description:
|
|
141
|
+
Copies installed Bizar agents and rules into another harness format.
|
|
142
|
+
`);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function showInstallHelp() {
|
|
146
|
+
console.log(`
|
|
147
|
+
bizar install — Run the interactive installer
|
|
148
|
+
|
|
149
|
+
Usage:
|
|
150
|
+
bizar install
|
|
151
|
+
|
|
152
|
+
Description:
|
|
153
|
+
Installs agents, rules, commands, plugin support, RTK, Semble,
|
|
154
|
+
and the Skills CLI.
|
|
155
|
+
`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function showUpdateHelp() {
|
|
159
|
+
console.log(`
|
|
160
|
+
bizar update — Update opencode, bizar, and/or bizar-plugin
|
|
161
|
+
|
|
162
|
+
Usage:
|
|
163
|
+
bizar update
|
|
164
|
+
bizar update --all
|
|
165
|
+
bizar update opencode
|
|
166
|
+
bizar update bizar
|
|
167
|
+
bizar update plugin
|
|
168
|
+
|
|
169
|
+
Description:
|
|
170
|
+
Prompts interactively by default. Use --all for non-interactive
|
|
171
|
+
updates, or pass explicit component names.
|
|
100
172
|
`);
|
|
101
173
|
}
|
|
102
174
|
|
|
@@ -107,6 +179,7 @@ function showTestGateHelp() {
|
|
|
107
179
|
}
|
|
108
180
|
|
|
109
181
|
function showServiceHelp() {
|
|
182
|
+
const bizarConfigDir = getBizarConfigDir();
|
|
110
183
|
console.log(`
|
|
111
184
|
bizar service — Manage the background service daemon
|
|
112
185
|
|
|
@@ -115,12 +188,13 @@ function showServiceHelp() {
|
|
|
115
188
|
bizar service stop Stop the running service
|
|
116
189
|
bizar service status Show whether the service is running
|
|
117
190
|
bizar service logs Tail the service log
|
|
191
|
+
bizar service follow Follow the service log until Ctrl-C
|
|
118
192
|
|
|
119
193
|
Description:
|
|
120
194
|
The service watches per-project schedules (cron / interval / once)
|
|
121
195
|
and runs them at the right time. It logs to
|
|
122
|
-
|
|
123
|
-
|
|
196
|
+
${bizarConfigDir}/service.log and writes its PID to
|
|
197
|
+
${bizarConfigDir}/service.pid.
|
|
124
198
|
`);
|
|
125
199
|
}
|
|
126
200
|
|
|
@@ -155,8 +229,7 @@ async function findBizarDash() {
|
|
|
155
229
|
/* fall through */
|
|
156
230
|
}
|
|
157
231
|
// Local fallback — node_modules of this package
|
|
158
|
-
const
|
|
159
|
-
const localPath = join(here.pathname, '..', 'node_modules', '@polderlabs', 'bizar-dash', 'src', 'cli.mjs');
|
|
232
|
+
const localPath = fileURLToPath(new URL('../node_modules/@polderlabs/bizar-dash/src/cli.mjs', import.meta.url));
|
|
160
233
|
if (existsSync(localPath)) return localPath;
|
|
161
234
|
return null;
|
|
162
235
|
}
|
|
@@ -179,7 +252,17 @@ async function delegateToDash(argsForDash) {
|
|
|
179
252
|
env: process.env,
|
|
180
253
|
});
|
|
181
254
|
await new Promise((resolve, reject) => {
|
|
182
|
-
child.on('exit', (code) =>
|
|
255
|
+
child.on('exit', (code, signal) => {
|
|
256
|
+
if (code === 0) {
|
|
257
|
+
resolve();
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
if (signal) {
|
|
261
|
+
reject(new Error(`dashboard exited via signal ${signal}`));
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
reject(new Error(`dashboard exited with code ${code}`));
|
|
265
|
+
});
|
|
183
266
|
child.on('error', reject);
|
|
184
267
|
});
|
|
185
268
|
}
|
|
@@ -195,7 +278,14 @@ async function readAutoLaunchWeb() {
|
|
|
195
278
|
const fs = await import('node:fs');
|
|
196
279
|
const os = await import('node:os');
|
|
197
280
|
const path = await import('node:path');
|
|
198
|
-
const
|
|
281
|
+
const bizarConfigDir = process.platform === 'win32'
|
|
282
|
+
? (process.env.APPDATA
|
|
283
|
+
? path.join(process.env.APPDATA, 'bizar')
|
|
284
|
+
: path.join(os.homedir(), '.config', 'bizar'))
|
|
285
|
+
: (process.env.XDG_CONFIG_HOME
|
|
286
|
+
? path.join(process.env.XDG_CONFIG_HOME, 'bizar')
|
|
287
|
+
: path.join(os.homedir(), '.config', 'bizar'));
|
|
288
|
+
const file = path.join(bizarConfigDir, 'settings.json');
|
|
199
289
|
if (!fs.existsSync(file)) return true;
|
|
200
290
|
const parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
201
291
|
if (parsed && parsed.dashboard && typeof parsed.dashboard.autoLaunchWeb === 'boolean') {
|
|
@@ -244,68 +334,73 @@ async function runServiceCommand(sub) {
|
|
|
244
334
|
await runService(sub || 'status', args.slice(2));
|
|
245
335
|
}
|
|
246
336
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
} else if (args[0] === '
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
} else if (args[0] === '
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
} else if (args[0] === '
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
} else if (args[0] === '
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
} else {
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
} else if (args[0] === 'plan') {
|
|
277
|
-
|
|
278
|
-
} else if (args[0] === 'install') {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
if (args
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
if (args
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
// Default: launch the TUI dashboard. The TUI lives in bizar-dash.
|
|
295
|
-
// We try to use the bizar-dash package's TUI, but we also support a
|
|
296
|
-
// bundled fallback that imports from this package's local copy.
|
|
297
|
-
const dashPath = await findBizarDash();
|
|
298
|
-
if (dashPath) {
|
|
299
|
-
const skipWeb = args.includes('--no-web');
|
|
300
|
-
const forceWeb = args.includes('--web');
|
|
301
|
-
const settingAuto = await readAutoLaunchWeb();
|
|
302
|
-
const launchWeb = !skipWeb && (forceWeb || settingAuto);
|
|
303
|
-
await delegateToDash(['tui', ...(launchWeb ? [] : ['--no-web'])]);
|
|
337
|
+
async function main() {
|
|
338
|
+
if (args.includes('--check')) {
|
|
339
|
+
const status = checkSetupStatus();
|
|
340
|
+
console.log(JSON.stringify(status, null, 2));
|
|
341
|
+
process.exit(status.needed ? 1 : 0);
|
|
342
|
+
} else if (args.includes('--setup')) {
|
|
343
|
+
await ensureSetup({ silent: false });
|
|
344
|
+
process.exit(0);
|
|
345
|
+
} else if (args.includes('--postinstall')) {
|
|
346
|
+
// Legacy manual trigger — now an alias for --setup
|
|
347
|
+
await ensureSetup({ silent: false });
|
|
348
|
+
process.exit(0);
|
|
349
|
+
} else if (isVersionRequest) {
|
|
350
|
+
console.log(readCliVersion());
|
|
351
|
+
} else if (args[0] === 'audit') {
|
|
352
|
+
if (isHelpRequest) showAuditHelp();
|
|
353
|
+
else await runAudit();
|
|
354
|
+
} else if (args[0] === 'init') {
|
|
355
|
+
if (isHelpRequest) showInitHelp();
|
|
356
|
+
else await runInit(process.cwd());
|
|
357
|
+
} else if (args[0] === 'export') {
|
|
358
|
+
if (isHelpRequest) showExportHelp();
|
|
359
|
+
else await runExport(parseFlag('--target'));
|
|
360
|
+
} else if (args[0] === 'test-gate') {
|
|
361
|
+
if (isHelpRequest) showTestGateHelp();
|
|
362
|
+
else await runTestGate();
|
|
363
|
+
} else if (args[0] === 'update') {
|
|
364
|
+
if (isHelpRequest) showUpdateHelp();
|
|
365
|
+
else await runUpdate(args.slice(1));
|
|
366
|
+
} else if (args[0] === 'plan') {
|
|
367
|
+
await runPlan(args.slice(1), {});
|
|
368
|
+
} else if (args[0] === 'install') {
|
|
369
|
+
if (isHelpRequest) showInstallHelp();
|
|
370
|
+
else await runInstaller();
|
|
371
|
+
} else if (args[0] === 'service') {
|
|
372
|
+
if (isHelpRequest) showServiceHelp();
|
|
373
|
+
else await runServiceCommand(args[1]);
|
|
374
|
+
} else if (args[0] === 'dashboard' || args[0] === 'start' || args[0] === 'stop' || args[0] === 'status' || args[0] === 'tui') {
|
|
375
|
+
if (isHelpRequest) showDashboardHelp();
|
|
376
|
+
else await delegateToDash(args.slice(1));
|
|
377
|
+
} else if (args.includes('--bg') || args.includes('--detach')) {
|
|
378
|
+
// Delegate to bizar-dash
|
|
379
|
+
await delegateToDash(['--bg']);
|
|
380
|
+
} else if (args.includes('--web-only')) {
|
|
381
|
+
await delegateToDash(['--web-only']);
|
|
382
|
+
} else if (isHelpRequest) {
|
|
383
|
+
showHelp();
|
|
304
384
|
} else {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
385
|
+
// Default: launch the TUI dashboard. The TUI lives in bizar-dash.
|
|
386
|
+
const dashPath = await findBizarDash();
|
|
387
|
+
if (dashPath) {
|
|
388
|
+
const skipWeb = args.includes('--no-web');
|
|
389
|
+
const forceWeb = args.includes('--web');
|
|
390
|
+
const settingAuto = await readAutoLaunchWeb();
|
|
391
|
+
const launchWeb = !skipWeb && (forceWeb || settingAuto);
|
|
392
|
+
await delegateToDash(['tui', ...(launchWeb ? [] : ['--no-web'])]);
|
|
393
|
+
} else {
|
|
394
|
+
console.log('The Bizar dashboard is in a separate package:');
|
|
395
|
+
console.log(chalk.cyan(' npm install -g @polderlabs/bizar-dash'));
|
|
396
|
+
console.log('');
|
|
397
|
+
console.log('Or run the installer to set up everything:');
|
|
398
|
+
console.log(chalk.cyan(' npx -y @polderlabs/bizar install'));
|
|
399
|
+
}
|
|
310
400
|
}
|
|
311
401
|
}
|
|
402
|
+
|
|
403
|
+
await main().catch((err) => {
|
|
404
|
+
console.error(chalk.red(`bizar: ${err && err.message ? err.message : String(err)}`));
|
|
405
|
+
process.exit(1);
|
|
406
|
+
});
|
package/cli/copy.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { mkdir, writeFile, readFile, copyFile, access, constants } from 'node:fs/promises';
|
|
2
|
-
import { join, dirname
|
|
1
|
+
import { mkdir, writeFile, readFile, copyFile, access, constants, rename, unlink } from 'node:fs/promises';
|
|
2
|
+
import { join, dirname } from 'node:path';
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
4
|
import ora from 'ora';
|
|
5
5
|
import chalk from 'chalk';
|
|
@@ -14,6 +14,22 @@ async function fileExists(path) {
|
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
async function atomicWriteText(filePath, content) {
|
|
18
|
+
const tmpPath = `${filePath}.tmp.${process.pid}.${Date.now()}`;
|
|
19
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
20
|
+
try {
|
|
21
|
+
await writeFile(tmpPath, content, 'utf-8');
|
|
22
|
+
await rename(tmpPath, filePath);
|
|
23
|
+
} catch (error) {
|
|
24
|
+
try {
|
|
25
|
+
await unlink(tmpPath);
|
|
26
|
+
} catch {
|
|
27
|
+
// ignore cleanup failure
|
|
28
|
+
}
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
17
33
|
export async function installAgents(agentFiles, mode) {
|
|
18
34
|
const spinner = ora({ text: 'Installing agent definitions...', color: 'magenta' }).start();
|
|
19
35
|
const destDir = opencodeAgentsDir();
|
|
@@ -112,7 +128,7 @@ export async function installOpencodeJson(mode) {
|
|
|
112
128
|
}
|
|
113
129
|
|
|
114
130
|
if (mode === 'fresh' || !(await fileExists(dest))) {
|
|
115
|
-
await
|
|
131
|
+
await atomicWriteText(dest, JSON.stringify(templateObj, null, 2) + '\n');
|
|
116
132
|
spinner.succeed(chalk.green('opencode.json configured'));
|
|
117
133
|
return true;
|
|
118
134
|
}
|
|
@@ -122,14 +138,14 @@ export async function installOpencodeJson(mode) {
|
|
|
122
138
|
const existingRaw = await readFile(dest, 'utf-8');
|
|
123
139
|
const existing = JSON.parse(existingRaw);
|
|
124
140
|
const merged = deepMerge(existing, templateObj);
|
|
125
|
-
await
|
|
141
|
+
await atomicWriteText(dest, JSON.stringify(merged, null, 2) + '\n');
|
|
126
142
|
spinner.succeed(chalk.green('opencode.json merged (existing keys preserved)'));
|
|
127
143
|
return true;
|
|
128
144
|
} catch {
|
|
129
145
|
// If existing is invalid JSON, backup and overwrite
|
|
130
146
|
const backup = dest + '.bak';
|
|
131
147
|
await copyFile(dest, backup);
|
|
132
|
-
await
|
|
148
|
+
await atomicWriteText(dest, JSON.stringify(templateObj, null, 2) + '\n');
|
|
133
149
|
spinner.succeed(chalk.green('opencode.json written (backup at opencode.json.bak)'));
|
|
134
150
|
return true;
|
|
135
151
|
}
|
|
@@ -181,7 +197,7 @@ export async function mergeToolsIntoUserConfig() {
|
|
|
181
197
|
}
|
|
182
198
|
|
|
183
199
|
const merged = { ...existing, tools };
|
|
184
|
-
await
|
|
200
|
+
await atomicWriteText(dest, JSON.stringify(merged, null, 2) + '\n');
|
|
185
201
|
|
|
186
202
|
console.log(chalk.dim(` [tools] added: ${added.join(', ')}`));
|
|
187
203
|
return { merged: true, added };
|
|
@@ -220,6 +236,7 @@ export async function installBizarFolder() {
|
|
|
220
236
|
for (const file of files) {
|
|
221
237
|
const src = join(srcDir, file);
|
|
222
238
|
const dst = join(destDir, file);
|
|
239
|
+
await mkdir(dirname(dst), { recursive: true });
|
|
223
240
|
await copyFile(src, dst);
|
|
224
241
|
}
|
|
225
242
|
|
package/cli/export.mjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { existsSync, readFileSync, readdirSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
5
|
+
import { opencodeConfigDir } from './utils.mjs';
|
|
4
6
|
|
|
5
|
-
const HOME =
|
|
6
|
-
const CONFIG_DIR =
|
|
7
|
-
? join(process.env.XDG_CONFIG_HOME, 'opencode')
|
|
8
|
-
: join(HOME, '.config/opencode');
|
|
7
|
+
const HOME = homedir();
|
|
8
|
+
const CONFIG_DIR = opencodeConfigDir();
|
|
9
9
|
|
|
10
10
|
export async function runExport(target) {
|
|
11
11
|
const validTargets = ['claude', 'cursor', 'opencode'];
|
|
@@ -18,12 +18,20 @@ export async function runExport(target) {
|
|
|
18
18
|
|
|
19
19
|
for (const t of targets) {
|
|
20
20
|
console.log(chalk.bold.hex('#6366f1')(`\n Exporting to ${t}...\n`));
|
|
21
|
-
|
|
21
|
+
try {
|
|
22
|
+
await exportTo(t);
|
|
23
|
+
} catch (error) {
|
|
24
|
+
console.log(chalk.red(` Export failed: ${error.message}`));
|
|
25
|
+
}
|
|
22
26
|
}
|
|
23
27
|
}
|
|
24
28
|
|
|
25
29
|
async function exportTo(target) {
|
|
26
|
-
const
|
|
30
|
+
const agentsDir = join(CONFIG_DIR, 'agents');
|
|
31
|
+
if (!existsSync(agentsDir)) {
|
|
32
|
+
throw new Error(`No installed agents found at ${agentsDir}. Run \`bizar install\` first.`);
|
|
33
|
+
}
|
|
34
|
+
const agentFiles = readdirSync(agentsDir).filter(f => f.endsWith('.md'));
|
|
27
35
|
|
|
28
36
|
switch (target) {
|
|
29
37
|
case 'claude': {
|
|
@@ -84,4 +92,4 @@ ${content}
|
|
|
84
92
|
break;
|
|
85
93
|
}
|
|
86
94
|
}
|
|
87
|
-
}
|
|
95
|
+
}
|
package/cli/init.mjs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { existsSync, readFileSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
3
|
-
import { join } from 'node:path';
|
|
4
|
-
import {
|
|
3
|
+
import { join, basename } from 'node:path';
|
|
4
|
+
import { spawnSync } from 'node:child_process';
|
|
5
|
+
import { detectSkillsCli } from './utils.mjs';
|
|
5
6
|
|
|
6
7
|
function detectStack(cwd) {
|
|
7
8
|
const stack = { language: null, framework: null, database: null, tools: [], build: null, test: null, runner: null };
|
|
@@ -34,7 +35,7 @@ function detectStack(cwd) {
|
|
|
34
35
|
stack.language = 'Python';
|
|
35
36
|
const content = readFileSync(join(cwd, 'pyproject.toml'), 'utf-8');
|
|
36
37
|
if (content.includes('django')) stack.framework = 'Django';
|
|
37
|
-
else if (content.includes('fastapi')
|
|
38
|
+
else if (content.includes('fastapi')) stack.framework = 'FastAPI';
|
|
38
39
|
else if (content.includes('flask')) stack.framework = 'Flask';
|
|
39
40
|
}
|
|
40
41
|
|
|
@@ -72,33 +73,40 @@ export async function runInit(cwd) {
|
|
|
72
73
|
|
|
73
74
|
// Install relevant skills via Skills CLI
|
|
74
75
|
console.log(chalk.dim(' Installing relevant skills...'));
|
|
75
|
-
const
|
|
76
|
+
const skillCommands = [];
|
|
76
77
|
if (stack.language?.toLowerCase().includes('typescript') || stack.framework?.toLowerCase().includes('next') || stack.framework?.toLowerCase().includes('react')) {
|
|
77
|
-
|
|
78
|
-
|
|
78
|
+
skillCommands.push(['add', 'vercel-labs/agent-skills', '--all', '-y']);
|
|
79
|
+
skillCommands.push(['add', 'shadcn/ui', '--all', '-y']);
|
|
79
80
|
}
|
|
80
81
|
if (stack.framework?.toLowerCase().includes('django') || stack.framework?.toLowerCase().includes('fastapi') || stack.framework?.toLowerCase().includes('flask')) {
|
|
81
|
-
|
|
82
|
+
skillCommands.push(['add', 'supabase/agent-skills', '--all', '-y']);
|
|
82
83
|
}
|
|
83
84
|
if (stack.language?.toLowerCase().includes('python')) {
|
|
84
|
-
|
|
85
|
+
skillCommands.push(['add', 'mattpocock/skills', '-y']);
|
|
85
86
|
}
|
|
86
87
|
if (stack.language?.toLowerCase().includes('rust')) {
|
|
87
|
-
|
|
88
|
+
skillCommands.push(['add', 'supabase/agent-skills', '--all', '-y']);
|
|
88
89
|
}
|
|
89
90
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
91
|
+
if (!(await detectSkillsCli())) {
|
|
92
|
+
console.log(chalk.dim(' - skills CLI not detected (skipped)'));
|
|
93
|
+
} else {
|
|
94
|
+
for (const args of skillCommands) {
|
|
95
|
+
const result = spawnSync('skills', args, {
|
|
96
|
+
stdio: 'pipe',
|
|
97
|
+
timeout: 30000,
|
|
98
|
+
});
|
|
99
|
+
if (result.status === 0) {
|
|
100
|
+
console.log(chalk.green(` ✓ skills ${args.join(' ')}`));
|
|
101
|
+
} else {
|
|
102
|
+
console.log(chalk.dim(` - skills ${args.join(' ')} (skipped)`));
|
|
103
|
+
}
|
|
96
104
|
}
|
|
97
105
|
}
|
|
98
106
|
console.log();
|
|
99
107
|
|
|
100
108
|
// Generate PROJECT.md
|
|
101
|
-
const projectName = cwd
|
|
109
|
+
const projectName = basename(cwd) || 'my-project';
|
|
102
110
|
const projectMd = `# ${projectName}
|
|
103
111
|
|
|
104
112
|
${stack.framework ? `${stack.framework} ` : ''}${stack.language || ''} project.
|
|
@@ -144,4 +152,4 @@ ${stack.runner ? `- Dev: \`${stack.runner}\`` : ''}
|
|
|
144
152
|
|
|
145
153
|
console.log(chalk.dim('\n Project initialized. Run `@frigg` to ask questions about the codebase.\n'));
|
|
146
154
|
return true;
|
|
147
|
-
}
|
|
155
|
+
}
|
package/cli/install.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import { join } from 'node:path';
|
|
|
5
5
|
|
|
6
6
|
import { showBanner, showPantheon, sectionHeading } from './banner.mjs';
|
|
7
7
|
import { promptComponents, promptInstallMode, promptAgents, promptSkillPacks, promptApiKeys, promptConfirmInstall, promptRestartOpenCode } from './prompts.mjs';
|
|
8
|
-
import { detectOpenCode, detectRtk, detectSemble, detectSkillsCli, buildSummary, opencodeAgentsDir, opencodeConfigDir, repoPath } from './utils.mjs';
|
|
8
|
+
import { detectOpenCode, detectRtk, detectSemble, detectSkillsCli, detectUv, buildSummary, opencodeAgentsDir, opencodeConfigDir, repoPath } from './utils.mjs';
|
|
9
9
|
import { installAgents, installAgentsMd, installSkill, installOpencodeJson, installBizarFolder, installPluginBizar, installRtk, installSemble, installSkillsCli, installCuratedSkills, installRules, installHooks, installCommands, installCommandsBizar, mergeToolsIntoUserConfig } from './copy.mjs';
|
|
10
10
|
|
|
11
11
|
const AGENT_FILES = [
|
|
@@ -212,7 +212,9 @@ export async function runInstaller() {
|
|
|
212
212
|
|
|
213
213
|
// Also try to install the plugin from the separate global npm package
|
|
214
214
|
// `@polderlabs/bizar-plugin` (preferred path going forward).
|
|
215
|
-
|
|
215
|
+
if (components.includes('plugin-bizar')) {
|
|
216
|
+
await installPluginFromGlobal();
|
|
217
|
+
}
|
|
216
218
|
|
|
217
219
|
// ── Rules, hooks, commands (optional components) ──
|
|
218
220
|
if (components.includes('rules')) {
|
|
@@ -325,8 +327,7 @@ async function promptYesNo(question, defaultYes = true) {
|
|
|
325
327
|
async function isPackageInstalled(name) {
|
|
326
328
|
try {
|
|
327
329
|
const globalRoot = execSync('npm root -g', { encoding: 'utf8' }).trim();
|
|
328
|
-
|
|
329
|
-
return true;
|
|
330
|
+
return existsSync(join(globalRoot, ...name.split('/'), 'package.json'));
|
|
330
331
|
} catch {
|
|
331
332
|
return false;
|
|
332
333
|
}
|
|
@@ -409,7 +410,7 @@ export async function runPostInstall() {
|
|
|
409
410
|
const env = await detectOpenCode();
|
|
410
411
|
if (!env.exists) {
|
|
411
412
|
mkdirSync(opencodeConfigDir(), { recursive: true });
|
|
412
|
-
console.log(
|
|
413
|
+
console.log(`BizarHarness: created ${opencodeConfigDir()}/`);
|
|
413
414
|
}
|
|
414
415
|
|
|
415
416
|
mkdirSync(opencodeAgentsDir(), { recursive: true });
|