@polderlabs/bizar 4.4.6 → 4.4.8
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/bizar-dash/dist/assets/main-CEazNxxy.js +347 -0
- package/bizar-dash/dist/assets/main-CEazNxxy.js.map +1 -0
- package/bizar-dash/dist/assets/{mobile-CQTXbuHq.js → mobile-DSb-t42Y.js} +2 -2
- package/bizar-dash/dist/assets/{mobile-CQTXbuHq.js.map → mobile-DSb-t42Y.js.map} +1 -1
- package/bizar-dash/dist/assets/{mobile-CaZ0gEeJ.js → mobile-Dl1q7Cyq.js} +2 -2
- package/bizar-dash/dist/assets/{mobile-CaZ0gEeJ.js.map → mobile-Dl1q7Cyq.js.map} +1 -1
- package/bizar-dash/dist/index.html +2 -2
- package/bizar-dash/dist/mobile.html +2 -2
- package/bizar-dash/src/server/glyphs/mdx-compiler.mjs +13 -0
- package/bizar-dash/src/web/views/glyphs/GlyphRenderer.tsx +240 -9
- package/cli/bin.mjs +39 -30
- package/cli/install.mjs +13 -51
- package/cli/provision.mjs +967 -0
- package/cli/update.mjs +28 -878
- package/config/skills/glyph/SKILL.md +126 -25
- package/install.sh +226 -439
- package/package.json +1 -1
- package/bizar-dash/dist/assets/main-C4Dq4wTz.js +0 -347
- package/bizar-dash/dist/assets/main-C4Dq4wTz.js.map +0 -1
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
}
|
|
30
30
|
})();
|
|
31
31
|
</script>
|
|
32
|
-
<script type="module" crossorigin src="/assets/main-
|
|
33
|
-
<link rel="modulepreload" crossorigin href="/assets/mobile-
|
|
32
|
+
<script type="module" crossorigin src="/assets/main-CEazNxxy.js"></script>
|
|
33
|
+
<link rel="modulepreload" crossorigin href="/assets/mobile-Dl1q7Cyq.js">
|
|
34
34
|
<link rel="stylesheet" crossorigin href="/assets/mobile-CsZQAswA.css">
|
|
35
35
|
<link rel="stylesheet" crossorigin href="/assets/main-Nq8Dq3VR.css">
|
|
36
36
|
</head>
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
13
13
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
14
14
|
<link rel="stylesheet" href="https://rsms.me/inter/inter.css" />
|
|
15
|
-
<script type="module" crossorigin src="/assets/mobile-
|
|
16
|
-
<link rel="modulepreload" crossorigin href="/assets/mobile-
|
|
15
|
+
<script type="module" crossorigin src="/assets/mobile-DSb-t42Y.js"></script>
|
|
16
|
+
<link rel="modulepreload" crossorigin href="/assets/mobile-Dl1q7Cyq.js">
|
|
17
17
|
<link rel="stylesheet" crossorigin href="/assets/mobile-CsZQAswA.css">
|
|
18
18
|
</head>
|
|
19
19
|
<body>
|
|
@@ -199,6 +199,19 @@ function parseJsxValue(src) {
|
|
|
199
199
|
}
|
|
200
200
|
while (i < len) {
|
|
201
201
|
skipWs();
|
|
202
|
+
// v4.4.8 — After consuming a comma + whitespace, check for the
|
|
203
|
+
// closer before recursing. Without this, an array like `[a, b, ]`
|
|
204
|
+
// would call parseValue on `]`, which falls through to the
|
|
205
|
+
// bareword-identifier branch and returns `{__ident: ''}` as a
|
|
206
|
+
// phantom element. The trailing-comma-then-closer case was the
|
|
207
|
+
// user-visible bug in the v3-to-v4-consolidation glyph: the
|
|
208
|
+
// FileTree's last entry was the bareword phantom, which then
|
|
209
|
+
// crashed the React renderer (components.tsx:609 tried to read
|
|
210
|
+
// `t.bg` on undefined).
|
|
211
|
+
if (arr && src[i] === closer) {
|
|
212
|
+
i += 1;
|
|
213
|
+
return items;
|
|
214
|
+
}
|
|
202
215
|
if (arr) {
|
|
203
216
|
items.push(parseValue());
|
|
204
217
|
} else {
|
|
@@ -49,6 +49,142 @@ import {
|
|
|
49
49
|
Mockup,
|
|
50
50
|
} from './components';
|
|
51
51
|
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// BlockErrorBoundary — v4.4.8
|
|
54
|
+
//
|
|
55
|
+
// A single broken block (e.g. a FileTree whose `data.entries` ended up
|
|
56
|
+
// not being an array because the parser silently produced a phantom
|
|
57
|
+
// bareword entry) used to throw inside React, which propagated up and
|
|
58
|
+
// blanked the whole canvas. The boundary catches throws per-block and
|
|
59
|
+
// renders an inline error card instead. The boundary also exposes its
|
|
60
|
+
// errors via a callback so we can surface them in the top-of-page
|
|
61
|
+
// "render errors" banner.
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
|
|
64
|
+
interface BlockErrorBoundaryProps {
|
|
65
|
+
blockId: string;
|
|
66
|
+
blockType: string;
|
|
67
|
+
children: React.ReactNode;
|
|
68
|
+
onError: (blockId: string, blockType: string, err: Error, info: React.ErrorInfo) => void;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
interface BlockErrorBoundaryState {
|
|
72
|
+
err: Error | null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
class BlockErrorBoundary extends React.Component<BlockErrorBoundaryProps, BlockErrorBoundaryState> {
|
|
76
|
+
state: BlockErrorBoundaryState = { err: null };
|
|
77
|
+
static getDerivedStateFromError(err: Error): BlockErrorBoundaryState {
|
|
78
|
+
return { err };
|
|
79
|
+
}
|
|
80
|
+
componentDidCatch(err: Error, info: React.ErrorInfo): void {
|
|
81
|
+
try {
|
|
82
|
+
this.props.onError(this.props.blockId, this.props.blockType, err, info);
|
|
83
|
+
} catch {
|
|
84
|
+
/* ignore — the boundary itself must not throw */
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
render(): React.ReactNode {
|
|
88
|
+
if (this.state.err) {
|
|
89
|
+
return (
|
|
90
|
+
<div
|
|
91
|
+
className="glyph-block-error"
|
|
92
|
+
role="alert"
|
|
93
|
+
style={{
|
|
94
|
+
border: '1px solid var(--error, #f85149)',
|
|
95
|
+
background: 'rgba(248, 81, 73, 0.06)',
|
|
96
|
+
borderRadius: 8,
|
|
97
|
+
padding: '12px 14px',
|
|
98
|
+
margin: '12px 0',
|
|
99
|
+
color: 'var(--text)',
|
|
100
|
+
fontFamily: 'var(--font-mono, ui-monospace, monospace)',
|
|
101
|
+
fontSize: 12,
|
|
102
|
+
lineHeight: 1.55,
|
|
103
|
+
}}
|
|
104
|
+
>
|
|
105
|
+
<strong style={{ color: 'var(--error, #f85149)', fontFamily: 'var(--font-sans, system-ui, sans-serif)', display: 'block', marginBottom: 4 }}>
|
|
106
|
+
{this.props.blockType} block crashed
|
|
107
|
+
</strong>
|
|
108
|
+
<div style={{ color: 'var(--text-muted)', marginBottom: 6 }}>
|
|
109
|
+
block id: <code>{this.props.blockId}</code>
|
|
110
|
+
</div>
|
|
111
|
+
<pre style={{ margin: 0, whiteSpace: 'pre-wrap', wordBreak: 'break-word', color: 'var(--text)' }}>
|
|
112
|
+
{this.state.err.message}
|
|
113
|
+
</pre>
|
|
114
|
+
</div>
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
return this.props.children;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
// Block validation — v4.4.8
|
|
123
|
+
//
|
|
124
|
+
// Before handing block data to the component switch, verify that the
|
|
125
|
+
// shape matches the prop contract. Returns `{ ok: true }` when the
|
|
126
|
+
// block can be rendered, or `{ ok: false, error }` when a field is
|
|
127
|
+
// missing or wrong type. The renderer uses the error to skip the
|
|
128
|
+
// component and surface an inline message instead of throwing.
|
|
129
|
+
// ---------------------------------------------------------------------------
|
|
130
|
+
|
|
131
|
+
function validateBlock(b: Block): { ok: true } | { ok: false; error: string } {
|
|
132
|
+
const data = (b.data ?? {}) as Record<string, unknown>;
|
|
133
|
+
switch (b.type) {
|
|
134
|
+
case 'RichText':
|
|
135
|
+
return { ok: true };
|
|
136
|
+
case 'Callout':
|
|
137
|
+
return { ok: true };
|
|
138
|
+
case 'Checklist':
|
|
139
|
+
if (!Array.isArray(data.items)) return { ok: false, error: 'Checklist requires data.items to be an array' };
|
|
140
|
+
return { ok: true };
|
|
141
|
+
case 'Table':
|
|
142
|
+
if (!Array.isArray(data.columns)) return { ok: false, error: 'Table requires data.columns to be an array' };
|
|
143
|
+
if (!Array.isArray(data.rows)) return { ok: false, error: 'Table requires data.rows to be an array' };
|
|
144
|
+
if (data.rows.length > 0 && !Array.isArray(data.rows[0])) return { ok: false, error: 'Table.data.rows[0] must be an array (cell array)' };
|
|
145
|
+
return { ok: true };
|
|
146
|
+
case 'CodeTabs':
|
|
147
|
+
if (!Array.isArray(data.tabs)) return { ok: false, error: 'CodeTabs requires data.tabs to be an array' };
|
|
148
|
+
return { ok: true };
|
|
149
|
+
case 'Decision':
|
|
150
|
+
if (!Array.isArray(data.options)) return { ok: false, error: 'Decision requires data.options to be an array' };
|
|
151
|
+
return { ok: true };
|
|
152
|
+
case 'OpenQuestions':
|
|
153
|
+
if (!Array.isArray(data.questions)) return { ok: false, error: 'OpenQuestions requires data.questions to be an array' };
|
|
154
|
+
return { ok: true };
|
|
155
|
+
case 'FileTree':
|
|
156
|
+
if (!Array.isArray(data.entries)) return { ok: false, error: 'FileTree requires data.entries to be an array' };
|
|
157
|
+
// v4.4.8 — also catch the phantom bareword entry the parser used
|
|
158
|
+
// to emit (the v3-to-v4-consolidation glyph crash). An entry that
|
|
159
|
+
// is not an object means the parser miscounted braces.
|
|
160
|
+
for (const e of data.entries) {
|
|
161
|
+
if (e === null || typeof e !== 'object' || Array.isArray(e)) {
|
|
162
|
+
return { ok: false, error: `FileTree contains a malformed entry: ${JSON.stringify(e)}` };
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return { ok: true };
|
|
166
|
+
case 'Diff':
|
|
167
|
+
if (typeof data.before !== 'string') return { ok: false, error: 'Diff requires data.before to be a string' };
|
|
168
|
+
if (typeof data.after !== 'string') return { ok: false, error: 'Diff requires data.after to be a string' };
|
|
169
|
+
return { ok: true };
|
|
170
|
+
case 'Stat':
|
|
171
|
+
if (data.label === undefined || data.label === null) return { ok: false, error: 'Stat requires data.label' };
|
|
172
|
+
if (data.value === undefined || data.value === null) return { ok: false, error: 'Stat requires data.value' };
|
|
173
|
+
return { ok: true };
|
|
174
|
+
case 'Workflow':
|
|
175
|
+
if (!Array.isArray(data.steps)) return { ok: false, error: 'Workflow requires data.steps to be an array' };
|
|
176
|
+
return { ok: true };
|
|
177
|
+
case 'Mockup':
|
|
178
|
+
if (typeof data.html !== 'string') return { ok: false, error: 'Mockup requires data.html to be a string' };
|
|
179
|
+
return { ok: true };
|
|
180
|
+
case 'Diagram':
|
|
181
|
+
if (typeof data.dataHtml !== 'string') return { ok: false, error: 'Diagram requires data.dataHtml to be a string' };
|
|
182
|
+
return { ok: true };
|
|
183
|
+
default:
|
|
184
|
+
return { ok: false, error: `Unknown block type: ${(b as { type: string }).type}` };
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
52
188
|
// ---------------------------------------------------------------------------
|
|
53
189
|
// Types — match the server compiler output
|
|
54
190
|
// ---------------------------------------------------------------------------
|
|
@@ -72,7 +208,7 @@ interface CompiledGlyph {
|
|
|
72
208
|
slug: string;
|
|
73
209
|
frontmatter: Record<string, unknown>;
|
|
74
210
|
blocks: Block[];
|
|
75
|
-
errors: string
|
|
211
|
+
errors: Array<{ line?: number; message: string }>;
|
|
76
212
|
compiledAt: string;
|
|
77
213
|
}
|
|
78
214
|
|
|
@@ -162,6 +298,25 @@ export function GlyphRenderer({ slug, onClose, onCommentAdded }: Props) {
|
|
|
162
298
|
// Active pin (expanded thread view)
|
|
163
299
|
const [activePin, setActivePin] = useState<string | null>(null);
|
|
164
300
|
|
|
301
|
+
// v4.4.8 — Per-block render errors caught by BlockErrorBoundary. The
|
|
302
|
+
// boundary calls onError(err) which pushes into this array. We render
|
|
303
|
+
// the array at the top of the canvas in a prominent red banner so the
|
|
304
|
+
// user isn't left staring at a blank screen wondering what happened.
|
|
305
|
+
const [blockErrors, setBlockErrors] = useState<
|
|
306
|
+
Array<{ blockId: string; blockType: string; message: string }>
|
|
307
|
+
>([]);
|
|
308
|
+
const reportBlockError = React.useCallback(
|
|
309
|
+
(blockId: string, blockType: string, err: Error) => {
|
|
310
|
+
setBlockErrors((prev) => {
|
|
311
|
+
// dedupe by blockId so the same block crashing twice doesn't
|
|
312
|
+
// duplicate the entry
|
|
313
|
+
if (prev.some((e) => e.blockId === blockId)) return prev;
|
|
314
|
+
return [...prev, { blockId, blockType, message: err.message || String(err) }];
|
|
315
|
+
});
|
|
316
|
+
},
|
|
317
|
+
[],
|
|
318
|
+
);
|
|
319
|
+
|
|
165
320
|
const canvasRef = useRef<HTMLDivElement>(null);
|
|
166
321
|
|
|
167
322
|
// Section grouping — memoized so we don't re-walk the block array on every render
|
|
@@ -289,10 +444,58 @@ export function GlyphRenderer({ slug, onClose, onCommentAdded }: Props) {
|
|
|
289
444
|
setFullscreen((v) => !v);
|
|
290
445
|
}
|
|
291
446
|
|
|
292
|
-
// Render a single block
|
|
447
|
+
// Render a single block — wrapped in a BlockErrorBoundary so a single
|
|
448
|
+
// broken block (e.g. wrong data shape from a parser bug) shows an inline
|
|
449
|
+
// error card instead of blanking the whole canvas.
|
|
293
450
|
const renderBlock = (b: Block) => {
|
|
294
451
|
const id = b.id;
|
|
295
452
|
const data = (b.data ?? {}) as Record<string, unknown>;
|
|
453
|
+
// Validate data shape BEFORE handing to the component switch. A
|
|
454
|
+
// validation failure shows an inline error card immediately, no
|
|
455
|
+
// need for the boundary to catch it.
|
|
456
|
+
const validation = validateBlock(b);
|
|
457
|
+
if (!validation.ok) {
|
|
458
|
+
return (
|
|
459
|
+
<div
|
|
460
|
+
key={id}
|
|
461
|
+
id={id}
|
|
462
|
+
className="glyph-block-error"
|
|
463
|
+
role="alert"
|
|
464
|
+
style={{
|
|
465
|
+
border: '1px solid var(--error, #f85149)',
|
|
466
|
+
background: 'rgba(248, 81, 73, 0.06)',
|
|
467
|
+
borderRadius: 8,
|
|
468
|
+
padding: '12px 14px',
|
|
469
|
+
margin: '12px 0',
|
|
470
|
+
color: 'var(--text)',
|
|
471
|
+
fontFamily: 'var(--font-mono, ui-monospace, monospace)',
|
|
472
|
+
fontSize: 12,
|
|
473
|
+
lineHeight: 1.55,
|
|
474
|
+
}}
|
|
475
|
+
>
|
|
476
|
+
<strong style={{ color: 'var(--error, #f85149)', fontFamily: 'var(--font-sans, system-ui, sans-serif)', display: 'block', marginBottom: 4 }}>
|
|
477
|
+
{b.type} block invalid
|
|
478
|
+
</strong>
|
|
479
|
+
<div style={{ color: 'var(--text-muted)', marginBottom: 6 }}>
|
|
480
|
+
block id: <code>{id}</code>
|
|
481
|
+
</div>
|
|
482
|
+
<pre style={{ margin: 0, whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>{validation.error}</pre>
|
|
483
|
+
</div>
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
// Wrap the actual render in an error boundary so even runtime
|
|
487
|
+
// errors (bad component props, undefined data, etc.) don't crash
|
|
488
|
+
// the whole canvas.
|
|
489
|
+
return (
|
|
490
|
+
<BlockErrorBoundary key={id} blockId={id} blockType={b.type} onError={reportBlockError}>
|
|
491
|
+
{renderBlockInner(b, id, data)}
|
|
492
|
+
</BlockErrorBoundary>
|
|
493
|
+
);
|
|
494
|
+
};
|
|
495
|
+
|
|
496
|
+
// Inner renderer — split out so the outer renderBlock can validate +
|
|
497
|
+
// wrap in an error boundary without nesting the switch in the JSX tree.
|
|
498
|
+
const renderBlockInner = (b: Block, id: string, data: Record<string, unknown>) => {
|
|
296
499
|
switch (b.type) {
|
|
297
500
|
case 'RichText':
|
|
298
501
|
return <RichText key={id} id={id}>{b.childrenMarkdown ?? ''}</RichText>;
|
|
@@ -506,13 +709,41 @@ export function GlyphRenderer({ slug, onClose, onCommentAdded }: Props) {
|
|
|
506
709
|
</div>
|
|
507
710
|
</header>
|
|
508
711
|
|
|
509
|
-
{/*
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
712
|
+
{/* v4.4.8 — Render errors banner. Two layers:
|
|
713
|
+
1. Compiler warnings from the MDX parser (e.g. unknown
|
|
714
|
+
tag, unclosed block). Lightweight, yellow border.
|
|
715
|
+
2. Block render errors caught by BlockErrorBoundary. These
|
|
716
|
+
are the "one block crashed but the rest still rendered"
|
|
717
|
+
cases. Red border, prominent, listed first so the user
|
|
718
|
+
sees them before scrolling. */}
|
|
719
|
+
{(blockErrors.length > 0 || compiled.errors?.length > 0) && (
|
|
720
|
+
<div
|
|
721
|
+
className="glyph-render-errors"
|
|
722
|
+
role="alert"
|
|
723
|
+
style={{
|
|
724
|
+
border: '1px solid var(--error, #f85149)',
|
|
725
|
+
background: 'rgba(248, 81, 73, 0.08)',
|
|
726
|
+
borderRadius: 8,
|
|
727
|
+
padding: '12px 16px',
|
|
728
|
+
margin: '0 0 16px 0',
|
|
729
|
+
color: 'var(--text)',
|
|
730
|
+
}}
|
|
731
|
+
>
|
|
732
|
+
<strong style={{ color: 'var(--error, #f85149)', display: 'block', marginBottom: 8 }}>
|
|
733
|
+
{blockErrors.length > 0
|
|
734
|
+
? `${blockErrors.length} block${blockErrors.length === 1 ? '' : 's'} failed to render`
|
|
735
|
+
: 'Compiler warnings'}
|
|
736
|
+
</strong>
|
|
737
|
+
<ul style={{ margin: 0, paddingLeft: 20, fontFamily: 'var(--font-mono, ui-monospace, monospace)', fontSize: 12, lineHeight: 1.6 }}>
|
|
738
|
+
{blockErrors.map((e, i) => (
|
|
739
|
+
<li key={`be-${i}`}>
|
|
740
|
+
<strong>{e.blockType}</strong> (<code>{e.blockId}</code>): {e.message}
|
|
741
|
+
</li>
|
|
742
|
+
))}
|
|
743
|
+
{compiled.errors?.map((e, i) => (
|
|
744
|
+
<li key={`ce-${i}`}>
|
|
745
|
+
{e.line ? `line ${e.line}: ` : ''}{e.message}
|
|
746
|
+
</li>
|
|
516
747
|
))}
|
|
517
748
|
</ul>
|
|
518
749
|
</div>
|
package/cli/bin.mjs
CHANGED
|
@@ -153,59 +153,68 @@ function showExportHelp() {
|
|
|
153
153
|
|
|
154
154
|
function showInstallHelp() {
|
|
155
155
|
console.log(`
|
|
156
|
-
bizar install — Run the
|
|
156
|
+
bizar install — Run the unified BizarHarness installer
|
|
157
157
|
|
|
158
158
|
Usage:
|
|
159
|
-
bizar install
|
|
159
|
+
bizar install Install (or refresh) every component
|
|
160
|
+
bizar install --dry-run Print what would happen, change nothing
|
|
161
|
+
bizar install --force Overwrite existing files
|
|
162
|
+
bizar install --help Show this help
|
|
160
163
|
|
|
161
164
|
Description:
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
165
|
+
v4.4.7+ — unified installer. Same code path as 'bizar update'; the
|
|
166
|
+
difference is just mode=install vs mode=update. Every step is
|
|
167
|
+
idempotent — running this twice is safe.
|
|
168
|
+
|
|
169
|
+
1. Installs @polderlabs/bizar via npm (skipped if already current).
|
|
170
|
+
2. Shells to ./install.sh for platform-specific system deps (uv,
|
|
171
|
+
python3.12, jq, gh on Linux; brew on macOS) + service registration
|
|
172
|
+
(systemd / launchd / Task Scheduler).
|
|
173
|
+
3. Syncs agent files, slash commands, and bundled skills into
|
|
174
|
+
~/.config/opencode/.
|
|
175
|
+
4. Copies plugins/bizar/ from the npm install into
|
|
176
|
+
~/.config/opencode/plugins/bizar/ (preserves dev symlinks).
|
|
177
|
+
5. Patches ~/.config/opencode/opencode.json with the Bizar plugin
|
|
178
|
+
entry (skipped if already present).
|
|
179
|
+
6. Runs 'bizar doctor' as a post-install health check.
|
|
180
|
+
|
|
181
|
+
No API key collection, no interactive prompts.
|
|
172
182
|
`);
|
|
173
183
|
}
|
|
174
184
|
|
|
175
185
|
function showUpdateHelp() {
|
|
176
186
|
console.log(`
|
|
177
|
-
bizar update — Update opencode
|
|
187
|
+
bizar update — Update opencode + @polderlabs/bizar (which bundles the
|
|
188
|
+
plugin and dashboard). Detects what's installed and only touches what's
|
|
189
|
+
missing or out of date.
|
|
178
190
|
|
|
179
191
|
Usage:
|
|
180
192
|
bizar update Update EVERYTHING (default; auto-kills + restarts)
|
|
181
|
-
bizar update --pick Interactive picker (legacy per-component UI)
|
|
182
|
-
bizar update opencode bizar dash Update specific components
|
|
183
193
|
bizar update --no-restart Don't auto-restart the dashboard after update
|
|
184
194
|
bizar update --dry-run Print what would happen, change nothing
|
|
195
|
+
bizar update --force Override .bizar/PRE_PUSH_NOTES.md blockers
|
|
196
|
+
bizar update --yes Same as --force, but named for one-line scripts
|
|
185
197
|
bizar update --help Show this help
|
|
186
198
|
|
|
187
|
-
Components:
|
|
188
|
-
opencode the opencode CLI itself
|
|
189
|
-
|
|
190
|
-
dash @polderlabs/bizar-dash (web dashboard + TUI)
|
|
191
|
-
plugin @polderlabs/bizar-plugin (opencode plugin)
|
|
199
|
+
Components updated:
|
|
200
|
+
opencode-ai the opencode CLI itself
|
|
201
|
+
@polderlabs/bizar this CLI + dashboard + plugin (one package)
|
|
192
202
|
|
|
193
|
-
Behavior (
|
|
194
|
-
•
|
|
195
|
-
|
|
196
|
-
|
|
203
|
+
Behavior (v4.4.7+):
|
|
204
|
+
• Single unified provisioner. 'bizar install' and 'bizar update' are
|
|
205
|
+
the same code path with different mode flags. Every step is
|
|
206
|
+
idempotent — re-running is safe.
|
|
197
207
|
• Detects running Bizar instances (background service daemon, web
|
|
198
208
|
dashboard) by reading ~/.config/bizar/{service,dashboard}.pid and
|
|
199
209
|
cleans up any stale or empty PID files.
|
|
200
|
-
• Auto-kills running instances with a brief notice
|
|
201
|
-
be prompted first instead).
|
|
210
|
+
• Auto-kills running instances with a brief notice.
|
|
202
211
|
• Sends SIGTERM, waits up to 5s, escalates to SIGKILL if needed.
|
|
203
212
|
• Re-runs the install script so the deployed plugin source matches
|
|
204
213
|
the just-upgraded npm version (avoids the version-skew trap).
|
|
205
|
-
• If the dashboard was running and bizar
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
• Runs
|
|
214
|
+
• If the dashboard was running and bizar was updated, spawns a
|
|
215
|
+
fresh detached dashboard process with the new code (skipped with
|
|
216
|
+
--no-restart).
|
|
217
|
+
• Runs 'bizar doctor' after a successful update to catch config
|
|
209
218
|
regressions before opencode tries to start.
|
|
210
219
|
|
|
211
220
|
Examples:
|
package/cli/install.mjs
CHANGED
|
@@ -203,60 +203,22 @@ export async function installPluginFromGlobal(opts = {}) {
|
|
|
203
203
|
}
|
|
204
204
|
|
|
205
205
|
/**
|
|
206
|
-
* runInstaller —
|
|
206
|
+
* runInstaller — v4.4.7 thin wrapper around the unified provisioner.
|
|
207
207
|
*
|
|
208
|
-
*
|
|
209
|
-
* `
|
|
210
|
-
*
|
|
211
|
-
*
|
|
212
|
-
*
|
|
208
|
+
* `bizar install` and `bizar update` are now the SAME code path with
|
|
209
|
+
* different `mode` flags. Everything (deps, plugin copy, opencode.json
|
|
210
|
+
* patching, service registration, agent files, skills, doctor) is
|
|
211
|
+
* owned by `cli/provision.mjs:runProvision`. This file just parses the
|
|
212
|
+
* flags and forwards.
|
|
213
213
|
*
|
|
214
|
-
*
|
|
215
|
-
*
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
* guarantees the two will diverge.
|
|
219
|
-
* - No interactive prompts. The TUI asked for API keys, restart
|
|
220
|
-
* confirmation, and component selection — none of which belong in a
|
|
221
|
-
* `npm i -g @polderlabs/bizar` install (no TTY, secrets stay local).
|
|
222
|
-
* - Cross-platform. The bash script already detects Windows and falls
|
|
223
|
-
* back to the npm-based path; re-implementing that in Node is busywork.
|
|
224
|
-
*
|
|
225
|
-
* Returns the install.sh exit code (0 = success, 1 = partial failure).
|
|
226
|
-
* On platforms where bash isn't available (rare — Windows without WSL),
|
|
227
|
-
* falls back to running the npm-based path directly.
|
|
214
|
+
* Backward-compatible — the public exports (`runInstaller`,
|
|
215
|
+
* `runPostInstall`, `installPluginFromGlobal`) still exist so that any
|
|
216
|
+
* external callers (including the postinstall npm hook) keep working.
|
|
217
|
+
* New code should call `runProvision({ mode: 'install' })` directly.
|
|
228
218
|
*/
|
|
229
|
-
export async function runInstaller() {
|
|
230
|
-
const {
|
|
231
|
-
|
|
232
|
-
const { spawnSync } = await import('node:child_process');
|
|
233
|
-
|
|
234
|
-
// __dirname is defined at module scope (see top of file)
|
|
235
|
-
// cli/install.mjs → ../install.sh
|
|
236
|
-
const installSh = join(__dirname, '..', 'install.sh');
|
|
237
|
-
|
|
238
|
-
if (!existsSync(installSh)) {
|
|
239
|
-
console.error(chalk.red(' ✗ install.sh not found at ' + installSh));
|
|
240
|
-
console.error(chalk.dim(' Run `git clone https://github.com/DrB0rk/BizarHarness` first, then `cd BizarHarness && ./install.sh`.'));
|
|
241
|
-
process.exit(1);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
console.log(chalk.bold.hex('#6366f1')('\n BizarHarness installer (delegating to install.sh)\n'));
|
|
245
|
-
|
|
246
|
-
const useBash = process.platform !== 'win32' || process.env.WSL_DISTRO_NAME;
|
|
247
|
-
if (useBash) {
|
|
248
|
-
const r = spawnSync('bash', [installSh], { stdio: 'inherit' });
|
|
249
|
-
process.exit(r.status ?? 1);
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
// Windows without WSL — bash isn't available. The bash script has a
|
|
253
|
-
// Windows fallback that uses npm-based install paths. Run the npm path
|
|
254
|
-
// directly: install the BizarHarness npm packages + copy plugin from
|
|
255
|
-
// global to ~/.config/opencode/plugins/bizar/. This is the same code
|
|
256
|
-
// path install.sh uses for `platform == win32`.
|
|
257
|
-
console.log(chalk.dim(' bash not available — running npm-based installer (Windows path)'));
|
|
258
|
-
console.log('');
|
|
259
|
-
await runPostInstall();
|
|
219
|
+
export async function runInstaller(opts = {}) {
|
|
220
|
+
const { runProvision } = await import('./provision.mjs');
|
|
221
|
+
return runProvision({ ...opts, mode: 'install' });
|
|
260
222
|
}
|
|
261
223
|
|
|
262
224
|
// ── Interactive prompts for optional packages ─────────────────────────────────
|