@polderlabs/bizar 4.5.2 → 4.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.
Files changed (71) hide show
  1. package/bizar-dash/dist/assets/main-DGGq-iZI.js +361 -0
  2. package/bizar-dash/dist/assets/main-DGGq-iZI.js.map +1 -0
  3. package/bizar-dash/dist/assets/{mobile-lbH6szyX.js → mobile-CWqPoGaT.js} +18 -18
  4. package/bizar-dash/dist/assets/mobile-CWqPoGaT.js.map +1 -0
  5. package/bizar-dash/dist/assets/{mobile-BRhoDOUz.js → mobile-i4Uv9eW8.js} +1 -1
  6. package/bizar-dash/dist/assets/{mobile-BRhoDOUz.js.map → mobile-i4Uv9eW8.js.map} +1 -1
  7. package/bizar-dash/dist/index.html +2 -2
  8. package/bizar-dash/dist/mobile.html +2 -2
  9. package/bizar-dash/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  10. package/bizar-dash/src/server/logger.mjs +71 -0
  11. package/bizar-dash/src/server/memory-lightrag.mjs +45 -6
  12. package/bizar-dash/src/server/metrics.mjs +193 -0
  13. package/bizar-dash/src/server/routes/chat.mjs +6 -3
  14. package/bizar-dash/src/server/routes/lightrag.mjs +3 -2
  15. package/bizar-dash/src/server/routes/memory.mjs +5 -4
  16. package/bizar-dash/src/server/routes/misc.mjs +2 -1
  17. package/bizar-dash/src/server/routes/overview.mjs +2 -1
  18. package/bizar-dash/src/server/server.mjs +62 -0
  19. package/bizar-dash/src/web/components/SearchModal.tsx +3 -2
  20. package/bizar-dash/src/web/components/VirtualList.tsx +53 -0
  21. package/bizar-dash/src/web/components/chat/ChatThread.tsx +17 -11
  22. package/bizar-dash/src/web/hooks/useI18n.ts +13 -0
  23. package/bizar-dash/src/web/lib/i18n.ts +25 -0
  24. package/bizar-dash/src/web/locales/en.json +52 -0
  25. package/bizar-dash/src/web/main.tsx +5 -0
  26. package/bizar-dash/src/web/views/Activity.tsx +24 -17
  27. package/bizar-dash/src/web/views/History.tsx +90 -75
  28. package/bizar-dash/src/web/views/Overview.tsx +11 -8
  29. package/bizar-dash/src/web/views/Settings.tsx +7 -1
  30. package/bizar-dash/tests/cli-bugfixes.test.mjs +4 -4
  31. package/bizar-dash/tests/cli-refactor.test.mjs +184 -0
  32. package/bizar-dash/tests/components/Button.test.tsx +41 -0
  33. package/bizar-dash/tests/components/Card.test.tsx +42 -0
  34. package/bizar-dash/tests/components/Modal.test.tsx +104 -0
  35. package/bizar-dash/tests/components/Spinner.test.tsx +32 -0
  36. package/bizar-dash/tests/components/StatusBadge.test.tsx +35 -0
  37. package/bizar-dash/tests/components/Toast.test.tsx +108 -0
  38. package/bizar-dash/tests/hooks/useModal.test.tsx +84 -0
  39. package/bizar-dash/tests/hooks/useToast.test.tsx +50 -0
  40. package/bizar-dash/tests/lib/i18n.test.ts +46 -0
  41. package/bizar-dash/tests/lib/utils.test.ts +194 -0
  42. package/bizar-dash/tests/logger.test.mjs +207 -0
  43. package/bizar-dash/tests/metrics.test.mjs +183 -0
  44. package/bizar-dash/tests/server-bugfixes.test.mjs +2 -2
  45. package/bizar-dash/tests/setup.ts +7 -0
  46. package/bizar-dash/vitest.config.ts +13 -0
  47. package/cli/artifact-cli.mjs +605 -0
  48. package/cli/artifact-render.mjs +621 -0
  49. package/cli/artifact-server.mjs +847 -0
  50. package/cli/artifact.mjs +38 -2096
  51. package/cli/bg.mjs +5 -13
  52. package/cli/bin.mjs +149 -1361
  53. package/cli/commands/artifact.mjs +20 -0
  54. package/cli/commands/dash.mjs +160 -0
  55. package/cli/commands/headroom.mjs +204 -0
  56. package/cli/commands/install.mjs +169 -0
  57. package/cli/commands/memory.mjs +25 -0
  58. package/cli/commands/minimax.mjs +265 -0
  59. package/cli/commands/mod.mjs +185 -0
  60. package/cli/commands/service.mjs +65 -0
  61. package/cli/commands/usage.mjs +109 -0
  62. package/cli/commands/util.mjs +306 -0
  63. package/cli/doctor.mjs +1 -13
  64. package/cli/provision.mjs +2 -13
  65. package/cli/service-controller.mjs +1 -11
  66. package/cli/service.mjs +1 -11
  67. package/cli/utils.mjs +41 -1
  68. package/package.json +6 -1
  69. package/bizar-dash/dist/assets/main-B4OfGAwz.js +0 -361
  70. package/bizar-dash/dist/assets/main-B4OfGAwz.js.map +0 -1
  71. package/bizar-dash/dist/assets/mobile-lbH6szyX.js.map +0 -1
@@ -0,0 +1,605 @@
1
+ /**
2
+ * cli/artifact-cli.mjs
3
+ *
4
+ * Artifact CLI dispatch and flag parsing.
5
+ * Extracted from artifact.mjs to separate CLI from server and rendering logic.
6
+ */
7
+ import {
8
+ readFileSync,
9
+ existsSync,
10
+ mkdirSync,
11
+ rmSync,
12
+ } from 'node:fs';
13
+ import { join, resolve, dirname, basename, isAbsolute } from 'node:path';
14
+ import { fileURLToPath } from 'node:url';
15
+ import { spawn } from 'node:child_process';
16
+
17
+ import {
18
+ getTemplate,
19
+ getTemplateNames,
20
+ listTemplates,
21
+ printTemplates,
22
+ substitute,
23
+ buildVars,
24
+ saveTemplate as saveTemplateToLibrary,
25
+ deleteTemplate as deleteLibraryTemplate,
26
+ } from './artifact-templates.mjs';
27
+
28
+ import {
29
+ CANVAS_SCHEMA_VERSION,
30
+ emptyCanvas,
31
+ readCanvasFile,
32
+ writeCanvasFile,
33
+ loadOrMigrateCanvas,
34
+ canvasToMarkdown,
35
+ makeElementId,
36
+ makeConnectionId,
37
+ makeCommentId,
38
+ makeReplyId,
39
+ readPlanMeta,
40
+ renderElementHTML,
41
+ renderConnectionHTML,
42
+ renderCommentPinHTML,
43
+ renderCommentThreadHTML,
44
+ renderReplyHTML,
45
+ escapeHtml,
46
+ formatDate,
47
+ replaceTemplate,
48
+ readTemplate,
49
+ atomicWriteText,
50
+ atomicWriteJson,
51
+ openBrowser,
52
+ } from './artifact-render.mjs';
53
+
54
+ import {
55
+ startServer,
56
+ } from './artifact-server.mjs';
57
+
58
+ const __dirname = dirname(fileURLToPath(import.meta.url));
59
+ const PROJECT_ROOT = resolve(__dirname, '..');
60
+ const TEMPLATES_DIR = join(PROJECT_ROOT, 'templates', 'artifact');
61
+ const PLANS_DIR = join(PROJECT_ROOT, 'artifacts');
62
+ const MAX_REQUEST_BODY_BYTES = 5 * 1024 * 1024;
63
+
64
+ // ─── Slug validation ─────────────────────────────────────────────────────────
65
+
66
+ const SLUG_REGEX = /^[a-z0-9][a-z0-9-]{0,63}$/;
67
+
68
+ function validateSlug(slug) {
69
+ if (!slug || !SLUG_REGEX.test(slug)) {
70
+ console.error(
71
+ ` ✗ Invalid slug "${slug}". Slug must be lowercase, may contain hyphens,\n` +
72
+ ` must start with an alphanumeric character, and be 1–64 characters.`
73
+ );
74
+ return false;
75
+ }
76
+ return true;
77
+ }
78
+
79
+ // ─── Title case ──────────────────────────────────────────────────────────────
80
+
81
+ function titleCase(str) {
82
+ return str
83
+ .split('-')
84
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
85
+ .join(' ');
86
+ }
87
+
88
+ // ─── Template resolution ─────────────────────────────────────────────────────
89
+
90
+ /**
91
+ * Resolve the content for a new artifact from the template system.
92
+ */
93
+ async function resolveTemplateContent(template, vars) {
94
+ if (template == null || template === '' || template === 'blank') {
95
+ const tpl = await readTemplate('artifact.mdx.template');
96
+ return { content: replaceTemplate(tpl, vars), templateName: 'blank', source: 'built-in' };
97
+ }
98
+
99
+ // Absolute path to a user file
100
+ if (isAbsolute(template) || template.endsWith('.mdx') || template.startsWith('.')) {
101
+ let p = template;
102
+ if (!isAbsolute(p)) p = resolve(p);
103
+ if (existsSync(p)) {
104
+ const fileContent = readFileSync(p, 'utf-8');
105
+ return {
106
+ content: substitute(fileContent, vars),
107
+ templateName: basename(p).replace(/\.mdx$/, ''),
108
+ source: 'file',
109
+ };
110
+ }
111
+ }
112
+
113
+ // Built-in name
114
+ const tpl = getTemplate(template);
115
+ if (!tpl) {
116
+ const available = getTemplateNames().join(', ');
117
+ throw new Error(
118
+ `Unknown template "${template}".\n` +
119
+ ` Built-in templates: ${available}.\n` +
120
+ ` Or pass an absolute path to a .mdx file.`
121
+ );
122
+ }
123
+
124
+ if (tpl.content == null) {
125
+ const blankTpl = await readTemplate('artifact.mdx.template');
126
+ return { content: replaceTemplate(blankTpl, vars), templateName: 'blank', source: 'built-in' };
127
+ }
128
+
129
+ return { content: substitute(tpl.content, vars), templateName: tpl.name, source: tpl.source };
130
+ }
131
+
132
+ // ─── Artifact operations ───────────────────────────────────────────────────────
133
+
134
+ async function createArtifact(slug, { template = null } = {}) {
135
+ const planDir = join(PLANS_DIR, slug);
136
+
137
+ // Step 1: validate
138
+ if (!validateSlug(slug)) return false;
139
+
140
+ // Step 2: check existence
141
+ if (existsSync(planDir)) {
142
+ console.error(` ✗ Artifact "${slug}" already exists at ${planDir}`);
143
+ console.error(` Use "bizar artifact open ${slug}" to open it.`);
144
+ return false;
145
+ }
146
+
147
+ // Step 3: create directory
148
+ mkdirSync(planDir, { recursive: true });
149
+
150
+ // Step 4: generate values
151
+ const now = new Date().toISOString();
152
+ const title = titleCase(slug);
153
+ const author = process.env.USER || 'unknown';
154
+ const vars = {
155
+ title,
156
+ slug,
157
+ author,
158
+ created: now,
159
+ lastEdited: now,
160
+ };
161
+
162
+ // Step 5: artifact.mdx (from template if --template was given)
163
+ let mdxContent;
164
+ let templateName = 'blank';
165
+ try {
166
+ const resolved = await resolveTemplateContent(template, vars);
167
+ mdxContent = resolved.content;
168
+ templateName = resolved.templateName;
169
+ } catch (err) {
170
+ console.error(` ✗ ${err.message}`);
171
+ rmSync(planDir, { recursive: true, force: true });
172
+ return false;
173
+ }
174
+ writePlanFile(slug, 'artifact.mdx', mdxContent);
175
+
176
+ // Step 6: meta.json
177
+ const metaTemplate = await readTemplate('meta.json.template');
178
+ const metaContent = replaceTemplate(metaTemplate, vars);
179
+ writePlanFile(slug, 'meta.json', metaContent);
180
+
181
+ // Step 7: comments.json (stored as empty array for comments)
182
+ writePlanFile(slug, 'comments.json', JSON.stringify([], null, 2));
183
+
184
+ // Step 8: artifact.html (regenerate from template)
185
+ await regenerateHtml(slug);
186
+
187
+ console.log(` ✓ Created artifact "${title}" (slug: ${slug})`);
188
+ if (templateName !== 'blank') {
189
+ console.log(` Template: ${templateName}`);
190
+ }
191
+ console.log(` → ${planDir}`);
192
+
193
+ return true;
194
+ }
195
+
196
+ export async function regenerateHtml(slug) {
197
+ const planDir = join(PLANS_DIR, slug);
198
+ if (!existsSync(planDir)) return;
199
+
200
+ // Prefer the v2 canvas template. Fall back to the v1 template.
201
+ let htmlTemplate;
202
+ let templateName = 'artifact.html.template';
203
+ try {
204
+ htmlTemplate = await readTemplate('artifact.canvas.template');
205
+ templateName = 'artifact.canvas.template';
206
+ } catch {
207
+ try {
208
+ htmlTemplate = await readTemplate('artifact.html.template');
209
+ templateName = 'artifact.html.template';
210
+ } catch {
211
+ return;
212
+ }
213
+ }
214
+
215
+ const planMdx = readFileSync(join(planDir, 'artifact.mdx'), 'utf-8');
216
+ const metaJson = readFileSync(join(planDir, 'meta.json'), 'utf-8');
217
+ const commentsJson = readFileSync(join(planDir, 'comments.json'), 'utf-8');
218
+ const meta = JSON.parse(metaJson);
219
+ const planJson = JSON.stringify(planMdx);
220
+
221
+ let canvasJson = 'null';
222
+ try {
223
+ const canvas = loadOrMigrateCanvas(planDir, meta.title || slug);
224
+ canvasJson = JSON.stringify(canvas);
225
+ } catch {
226
+ // ignore
227
+ }
228
+
229
+ const vars = {
230
+ title: meta.title || slug,
231
+ slug: meta.slug || slug,
232
+ status: meta.status || 'draft',
233
+ created: meta.created || new Date().toISOString(),
234
+ lastEdited: meta.lastEdited || new Date().toISOString(),
235
+ author: meta.author || 'unknown',
236
+ planJson,
237
+ commentsJson,
238
+ metaJson,
239
+ canvasJson,
240
+ templateName,
241
+ };
242
+
243
+ const htmlContent = replaceTemplate(htmlTemplate, vars);
244
+ atomicWriteText(join(planDir, 'artifact.html'), htmlContent);
245
+ }
246
+
247
+ async function openArtifact(slug) {
248
+ if (!validateSlug(slug)) return false;
249
+
250
+ const planDir = join(PLANS_DIR, slug);
251
+ if (!existsSync(planDir)) {
252
+ console.error(` ✗ Artifact "${slug}" not found at ${planDir}`);
253
+ console.error(` Use "bizar artifact new ${slug}" to create it.`);
254
+ return false;
255
+ }
256
+
257
+ // Regenerate HTML to bake in current state
258
+ await regenerateHtml(slug);
259
+
260
+ // Start server
261
+ const { port, close } = await startServer(slug, planDir);
262
+
263
+ const url = `http://localhost:${port}/${slug}/`;
264
+ console.log(` ✓ Opening artifact "${slug}" at ${url}`);
265
+ openBrowser(url);
266
+
267
+ // Keep process alive until Ctrl-C
268
+ await waitForSignal(close);
269
+
270
+ return true;
271
+ }
272
+
273
+ async function listPlans() {
274
+ if (!existsSync(PLANS_DIR)) {
275
+ console.log(' No artifacts found. Run `bizar artifact new <slug>` to create one.');
276
+ return true;
277
+ }
278
+
279
+ const { readdirSync } = await import('node:fs');
280
+ const dirs = readdirSync(PLANS_DIR).filter((d) => {
281
+ return existsSync(join(PLANS_DIR, d, 'meta.json'));
282
+ });
283
+
284
+ if (dirs.length === 0) {
285
+ console.log(' No artifacts found. Run `bizar artifact new <slug>` to create one.');
286
+ return true;
287
+ }
288
+
289
+ // Read meta for each
290
+ const artifacts = [];
291
+ for (const dir of dirs) {
292
+ try {
293
+ const meta = JSON.parse(readFileSync(join(PLANS_DIR, dir, 'meta.json'), 'utf-8'));
294
+ artifacts.push(meta);
295
+ } catch {
296
+ // Skip invalid meta.json
297
+ }
298
+ }
299
+
300
+ // Sort by lastEdited newest first
301
+ artifacts.sort((a, b) => new Date(b.lastEdited) - new Date(a.lastEdited));
302
+
303
+ // Print table
304
+ console.log(' Slug Title Status Last Edited Author');
305
+ console.log(' ────────────── ────────────────────── ──────── ──────────────────────── ───────────────');
306
+
307
+ for (const artifact of artifacts) {
308
+ const slug = (artifact.slug || '').padEnd(15);
309
+ const title = (artifact.title || '').substring(0, 23).padEnd(23);
310
+ const status = (artifact.status || 'draft').padEnd(9);
311
+ const lastEdited = (artifact.lastEdited || '').substring(0, 27).padEnd(27);
312
+ const author = (artifact.author || 'unknown').substring(0, 20);
313
+ console.log(` ${slug} ${title} ${status} ${lastEdited} ${author}`);
314
+ }
315
+ console.log();
316
+
317
+ return true;
318
+ }
319
+
320
+ async function deleteArtifact(slug) {
321
+ if (!validateSlug(slug)) return false;
322
+
323
+ const planDir = join(PLANS_DIR, slug);
324
+ if (!existsSync(planDir)) {
325
+ console.error(` ✗ Artifact "${slug}" not found.`);
326
+ return false;
327
+ }
328
+
329
+ let title = slug;
330
+ try {
331
+ const meta = JSON.parse(readFileSync(join(planDir, 'meta.json'), 'utf-8'));
332
+ title = meta.title || slug;
333
+ } catch { /* use slug as fallback */ }
334
+
335
+ // Read confirm from stdin
336
+ const answer = await question(` Delete artifact "${title}" (slug: ${slug})? This is irreversible. [y/N] `);
337
+
338
+ if (answer.trim().toLowerCase() === 'y') {
339
+ rmSync(planDir, { recursive: true });
340
+ console.log(` ✓ Deleted artifact "${slug}".`);
341
+ return true;
342
+ } else {
343
+ console.log(' Cancelled.');
344
+ return true;
345
+ }
346
+ }
347
+
348
+ async function exportArtifact(slug) {
349
+ if (!validateSlug(slug)) return false;
350
+
351
+ const planDir = join(PLANS_DIR, slug);
352
+ if (!existsSync(planDir)) {
353
+ console.error(` ✗ Artifact "${slug}" not found.`);
354
+ return false;
355
+ }
356
+
357
+ // Prefer the v2 canvas-derived markdown when artifact.json exists.
358
+ const canvasFile = join(planDir, 'artifact.json');
359
+ if (existsSync(canvasFile)) {
360
+ try {
361
+ const canvas = JSON.parse(readFileSync(canvasFile, 'utf-8'));
362
+ process.stdout.write(canvasToMarkdown(canvas));
363
+ return true;
364
+ } catch {
365
+ // fall through to mdx export
366
+ }
367
+ }
368
+
369
+ const planFile = join(planDir, 'artifact.mdx');
370
+ if (!existsSync(planFile)) {
371
+ console.error(` ✗ Artifact "${slug}" not found.`);
372
+ return false;
373
+ }
374
+
375
+ const content = readFileSync(planFile, 'utf-8');
376
+ process.stdout.write(content);
377
+ return true;
378
+ }
379
+
380
+ // ─── Helpers ─────────────────────────────────────────────────────────────────
381
+
382
+ function question(prompt) {
383
+ return new Promise((resolve) => {
384
+ process.stdout.write(prompt);
385
+ process.stdin.once('data', (data) => resolve(data.toString()));
386
+ });
387
+ }
388
+
389
+ function waitForSignal(closeFn) {
390
+ return new Promise((resolve) => {
391
+ const finish = async () => {
392
+ await closeFn();
393
+ process.off('SIGINT', finish);
394
+ process.off('SIGTERM', finish);
395
+ resolve();
396
+ };
397
+ process.on('SIGINT', finish);
398
+ process.on('SIGTERM', finish);
399
+ });
400
+ }
401
+
402
+ function writePlanFile(slug, filename, content) {
403
+ const dir = join(PLANS_DIR, slug);
404
+ mkdirSync(dir, { recursive: true });
405
+ atomicWriteText(join(dir, filename), content);
406
+ }
407
+
408
+ // ─── Help ────────────────────────────────────────────────────────────────────
409
+
410
+ export function showHelp() {
411
+ console.log(`
412
+ bizar artifact <subcommand> [options]
413
+
414
+ Subcommands:
415
+ new <slug> [--template <name>] Create a new artifact (default: blank template)
416
+ open <slug> Open an existing artifact in the browser
417
+ list List all artifacts
418
+ delete <slug> Delete a artifact (with confirmation)
419
+ export <slug> Export artifact.mdx to stdout
420
+ templates List available artifact templates
421
+ template save <name> <artifact-slug> Save a artifact as a library template
422
+ template delete <name> Delete a user-added library template
423
+ help Show this help
424
+
425
+ Plans are stored in artifacts/<slug>/ as:
426
+ - artifact.mdx source content (in git)
427
+ - artifact.html viewer/editor (gitignored)
428
+ - comments.json comments array (gitignored)
429
+ - meta.json metadata (in git)
430
+
431
+ Built-in templates:
432
+ blank Empty starter (the v1 default)
433
+ feature-design For designing a new feature
434
+ bug-investigation For investigating a bug
435
+ decision-record Architecture Decision Record (ADR)
436
+
437
+ Examples:
438
+ bizar artifact new my-feature
439
+ bizar artifact new auth-v2 --template feature-design
440
+ bizar artifact new oops --template bug-investigation
441
+ bizar artifact templates
442
+ bizar artifact open my-feature
443
+ bizar artifact list
444
+ bizar artifact export my-feature > my-feature.mdx
445
+ `);
446
+ }
447
+
448
+ // ─── Main dispatcher ──────────────────────────────────────────────────────────
449
+
450
+ /**
451
+ * Parse a string of CLI args into { positional, flags }.
452
+ * Supports --flag value and --flag=value styles. Booleans (no value)
453
+ * are stored as true.
454
+ */
455
+ function parseArgs(argv) {
456
+ const positional = [];
457
+ const flags = {};
458
+ for (let i = 0; i < argv.length; i++) {
459
+ const a = argv[i];
460
+ if (a.startsWith('--')) {
461
+ const eq = a.indexOf('=');
462
+ if (eq !== -1) {
463
+ flags[a.slice(2, eq)] = a.slice(eq + 1);
464
+ } else {
465
+ const key = a.slice(2);
466
+ const next = argv[i + 1];
467
+ if (next != null && !next.startsWith('--')) {
468
+ flags[key] = next;
469
+ i++;
470
+ } else {
471
+ flags[key] = true;
472
+ }
473
+ }
474
+ } else {
475
+ positional.push(a);
476
+ }
477
+ }
478
+ return { positional, flags };
479
+ }
480
+
481
+ /**
482
+ * Main entry point for `bizar artifact`.
483
+ * Subcommands: new, open, list, delete, export, templates,
484
+ * template save <name> <artifact-slug>, template delete <name>, help
485
+ */
486
+ export async function runArtifact(argsOrPositional, legacyFlags) {
487
+ let positional;
488
+ let flags;
489
+ if (Array.isArray(argsOrPositional)) {
490
+ const parsed = parseArgs(argsOrPositional);
491
+ positional = parsed.positional;
492
+ flags = { ...(legacyFlags || {}), ...parsed.flags };
493
+ } else if (argsOrPositional && Array.isArray(argsOrPositional.positional)) {
494
+ positional = argsOrPositional.positional;
495
+ flags = { ...(legacyFlags || {}), ...(argsOrPositional.flags || {}) };
496
+ } else {
497
+ positional = [];
498
+ flags = legacyFlags || {};
499
+ }
500
+
501
+ // Special-case: "template save <name> <artifact-slug>" / "template delete <name>"
502
+ if (positional[0] === 'template') {
503
+ const action = positional[1];
504
+ if (action === 'save') {
505
+ const name = positional[2];
506
+ const planSlug = positional[3];
507
+ if (!name || !planSlug) {
508
+ console.error(' ✗ Usage: bizar artifact template save <name> <artifact-slug>');
509
+ return false;
510
+ }
511
+ try {
512
+ const target = saveTemplateToLibrary(name, planSlug);
513
+ console.log(` ✓ Saved template "${name}" → ${target}`);
514
+ return true;
515
+ } catch (err) {
516
+ console.error(` ✗ ${err.message}`);
517
+ return false;
518
+ }
519
+ }
520
+ if (action === 'delete' || action === 'rm') {
521
+ const name = positional[2];
522
+ if (!name) {
523
+ console.error(' ✗ Usage: bizar artifact template delete <name>');
524
+ return false;
525
+ }
526
+ try {
527
+ const removed = deleteLibraryTemplate(name);
528
+ console.log(` ✓ Removed template "${name}" from library (${removed})`);
529
+ return true;
530
+ } catch (err) {
531
+ console.error(` ✗ ${err.message}`);
532
+ return false;
533
+ }
534
+ }
535
+ if (action === 'list' || action === undefined) {
536
+ printTemplates();
537
+ return true;
538
+ }
539
+ console.error(` ✗ Unknown template action: "${action}"`);
540
+ console.error(' Use: save <name> <artifact-slug>, delete <name>, or list');
541
+ return false;
542
+ }
543
+
544
+ const [subcommand, slug] = positional;
545
+
546
+ switch (subcommand) {
547
+ case 'new': {
548
+ if (!slug) {
549
+ console.error(' ✗ Usage: bizar artifact new <slug> [--template <name>]');
550
+ return false;
551
+ }
552
+ const created = await createArtifact(slug, { template: flags.template || null });
553
+ if (!created) return false;
554
+ return await openArtifact(slug);
555
+ }
556
+
557
+ case 'open': {
558
+ if (!slug) {
559
+ console.error(' ✗ Usage: bizar artifact open <slug>');
560
+ return false;
561
+ }
562
+ return await openArtifact(slug);
563
+ }
564
+
565
+ case 'list': {
566
+ return await listPlans();
567
+ }
568
+
569
+ case 'delete': {
570
+ if (!slug) {
571
+ console.error(' ✗ Usage: bizar artifact delete <slug>');
572
+ return false;
573
+ }
574
+ return await deleteArtifact(slug);
575
+ }
576
+
577
+ case 'export': {
578
+ if (!slug) {
579
+ console.error(' ✗ Usage: bizar artifact export <slug>');
580
+ return false;
581
+ }
582
+ return await exportArtifact(slug);
583
+ }
584
+
585
+ case 'templates': {
586
+ printTemplates();
587
+ return true;
588
+ }
589
+
590
+ case 'help':
591
+ case undefined: {
592
+ showHelp();
593
+ return true;
594
+ }
595
+
596
+ default: {
597
+ console.error(` ✗ Unknown subcommand: "${subcommand}"`);
598
+ console.error(` Run "bizar artifact help" for usage.`);
599
+ return false;
600
+ }
601
+ }
602
+ }
603
+
604
+ // Default export for CLI entrypoint
605
+ export default runArtifact;