@polderlabs/bizar-plugin 0.5.4 → 0.6.1

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/src/commands.ts CHANGED
@@ -60,12 +60,37 @@ export type SideEffect =
60
60
  | {
61
61
  kind: "list_plans";
62
62
  }
63
+ | {
64
+ kind: "launch_dashboard";
65
+ defaultPort: number;
66
+ }
63
67
  | {
64
68
  kind: "tool_invocation";
65
69
  toolName: string;
66
70
  args: unknown;
67
71
  };
68
72
 
73
+ /** Dialog component types that can be rendered in the dashboard. */
74
+ export type DialogComponent =
75
+ | "visual-plan"
76
+ | "plan-create"
77
+ | "plan-list"
78
+ | "help"
79
+ | "audit"
80
+ | "generic";
81
+
82
+ export interface DialogDescriptor {
83
+ id: string;
84
+ title: string;
85
+ command: string;
86
+ component: DialogComponent;
87
+ data?: Record<string, unknown>;
88
+ }
89
+
90
+ function generateId(): string {
91
+ return `dlg_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
92
+ }
93
+
69
94
  export interface SlashCommandResult {
70
95
  handled: true;
71
96
  /** Text shown to the user / LLM. */
@@ -74,6 +99,8 @@ export interface SlashCommandResult {
74
99
  settingsPatch?: Partial<PlanSettings>;
75
100
  /** Optional side-effect to perform (file I/O lives in the hook, not here). */
76
101
  sideEffect?: SideEffect;
102
+ /** Optional dialog to open in the dashboard instead of showing text. */
103
+ dialog?: DialogDescriptor;
77
104
  }
78
105
 
79
106
  /**
@@ -264,6 +291,8 @@ export function parseSlashCommand(
264
291
  return handleVisualPlan(rest, ctx);
265
292
  case "plan":
266
293
  return handlePlan(rest, ctx);
294
+ case "bizar":
295
+ return handleBizar(rest, ctx);
267
296
  case "help":
268
297
  case "commands":
269
298
  return helpResult();
@@ -279,43 +308,82 @@ export function parseSlashCommand(
279
308
 
280
309
  function handleVisualPlan(arg: string, ctx: ParseContext): SlashCommandResult {
281
310
  const lc = arg.toLowerCase();
311
+ const currentEnabled = ctx.currentSettings.visualPlanEnabled;
282
312
 
283
313
  if (lc === "") {
284
- // No argument — return current state
285
- const on = ctx.currentSettings.visualPlanEnabled ? "on" : "off";
314
+ // No argument — return current state as a dialog
286
315
  return {
287
316
  handled: true,
288
- response:
289
- `Visual plan mode is **${on}**.\n` +
290
- `Default template: ${ctx.currentSettings.defaultTemplate}\n` +
291
- (ctx.currentSettings.lastUsedSlug
292
- ? `Last used plan: ${ctx.currentSettings.lastUsedSlug}\n`
293
- : "") +
294
- `\nUsage: /visual-plan on | off`,
317
+ response: "",
318
+ dialog: {
319
+ id: generateId(),
320
+ title: "Visual Plan",
321
+ command: "/visual-plan",
322
+ component: "visual-plan",
323
+ data: {
324
+ enabled: currentEnabled,
325
+ previousEnabled: currentEnabled,
326
+ defaultTemplate: ctx.currentSettings.defaultTemplate,
327
+ lastUsedSlug: ctx.currentSettings.lastUsedSlug ?? null,
328
+ mode: "status",
329
+ },
330
+ },
295
331
  };
296
332
  }
297
333
 
298
334
  if (lc === "on" || lc === "true" || lc === "1" || lc === "enable") {
299
335
  return {
300
336
  handled: true,
301
- response: "Visual plan mode is now **on**. The agent will create a plan and wait for feedback on complex tasks.",
337
+ response: "",
302
338
  settingsPatch: { visualPlanEnabled: true },
339
+ dialog: {
340
+ id: generateId(),
341
+ title: "Visual Plan",
342
+ command: "/visual-plan on",
343
+ component: "visual-plan",
344
+ data: {
345
+ enabled: true,
346
+ previousEnabled: currentEnabled,
347
+ mode: "toggle",
348
+ },
349
+ },
303
350
  };
304
351
  }
305
352
 
306
353
  if (lc === "off" || lc === "false" || lc === "0" || lc === "disable") {
307
354
  return {
308
355
  handled: true,
309
- response: "Visual plan mode is now **off**.",
356
+ response: "",
310
357
  settingsPatch: { visualPlanEnabled: false },
358
+ dialog: {
359
+ id: generateId(),
360
+ title: "Visual Plan",
361
+ command: "/visual-plan off",
362
+ component: "visual-plan",
363
+ data: {
364
+ enabled: false,
365
+ previousEnabled: currentEnabled,
366
+ mode: "toggle",
367
+ },
368
+ },
311
369
  };
312
370
  }
313
371
 
314
372
  if (lc === "status" || lc === "state" || lc === "?") {
315
- const on = ctx.currentSettings.visualPlanEnabled ? "on" : "off";
316
373
  return {
317
374
  handled: true,
318
- response: `Visual plan mode: ${on}`,
375
+ response: "",
376
+ dialog: {
377
+ id: generateId(),
378
+ title: "Visual Plan",
379
+ command: "/visual-plan status",
380
+ component: "visual-plan",
381
+ data: {
382
+ enabled: currentEnabled,
383
+ previousEnabled: currentEnabled,
384
+ mode: "status",
385
+ },
386
+ },
319
387
  };
320
388
  }
321
389
 
@@ -376,21 +444,30 @@ function handlePlan(arg: string, ctx: ParseContext): SlashCommandResult {
376
444
  function helpPlan(): SlashCommandResult {
377
445
  return {
378
446
  handled: true,
379
- response:
380
- `Plan commands:\n` +
381
- ` /plan new <slug> [template] — Create a new plan\n` +
382
- ` /plan list — List all plans in the worktree\n` +
383
- ` /plan open <slug> — Return the URL for a plan\n` +
384
- ` /plan get <slug> — Fetch the full canvas\n` +
385
- ` /plan add <slug> --title T --type kind — Add an element to a plan\n` +
386
- ` /plan update <slug> <id> [--x N --y N …] — Patch an existing element\n` +
387
- ` /plan delete <slug> <id> — Remove an element\n` +
388
- ` /plan comment <slug> [id] "text" — Add a comment (canvas-pinned if no id)\n` +
389
- ` /plan comments <slug> [id] — Read comments on a plan\n` +
390
- ` /plan status <slug> <status> — Set the plan's status\n` +
391
- ` /plan wait <slug> [--timeout N] — Wait for feedback (deferred see note)\n` +
392
- `\nAvailable templates: ${KNOWN_TEMPLATES.join(", ")}\n` +
393
- `Available statuses: ${PLAN_STATUSES.join(", ")}`,
447
+ response: "",
448
+ dialog: {
449
+ id: generateId(),
450
+ title: "Plan Commands",
451
+ command: "/plan",
452
+ component: "help",
453
+ data: {
454
+ commands: [
455
+ { cmd: "/plan new <slug> [template]", desc: "Create a new plan" },
456
+ { cmd: "/plan list", desc: "List all plans in the worktree" },
457
+ { cmd: "/plan open <slug>", desc: "Open a plan in the viewer" },
458
+ { cmd: "/plan get <slug>", desc: "Fetch the full canvas" },
459
+ { cmd: "/plan add <slug> --title T --type kind", desc: "Add an element to a plan" },
460
+ { cmd: "/plan update <slug> <id> [--x N --y N …]", desc: "Patch an existing element" },
461
+ { cmd: "/plan delete <slug> <id>", desc: "Remove an element" },
462
+ { cmd: "/plan comment <slug> [id] \"text\"", desc: "Add a comment" },
463
+ { cmd: "/plan comments <slug> [id]", desc: "Read comments on a plan" },
464
+ { cmd: "/plan status <slug> <status>", desc: "Set the plan's status" },
465
+ { cmd: "/plan wait <slug> [--timeout N]", desc: "Wait for feedback (deferred)" },
466
+ ],
467
+ templates: KNOWN_TEMPLATES,
468
+ statuses: PLAN_STATUSES,
469
+ },
470
+ },
394
471
  };
395
472
  }
396
473
 
@@ -400,7 +477,18 @@ function handlePlanNew(args: string[], ctx: ParseContext): SlashCommandResult {
400
477
  if (args.length === 0 || args[0] === "") {
401
478
  return {
402
479
  handled: true,
403
- response: "Usage: /plan new <slug> [template]",
480
+ response: "",
481
+ dialog: {
482
+ id: generateId(),
483
+ title: "Create New Plan",
484
+ command: "/plan new",
485
+ component: "plan-create",
486
+ data: {
487
+ templates: [...KNOWN_TEMPLATES],
488
+ defaultTemplate: ctx.currentSettings.defaultTemplate,
489
+ suggestedSlug: "",
490
+ },
491
+ },
404
492
  };
405
493
  }
406
494
 
@@ -428,24 +516,27 @@ function handlePlanNew(args: string[], ctx: ParseContext): SlashCommandResult {
428
516
  template = candidate;
429
517
  }
430
518
 
431
- // The response surfaces the resolved template name (the user-supplied
432
- // argument, or the user's current default). The sideEffect carries
433
- // the explicit `null` so the executor knows to fall back to
434
- // `currentSettings.defaultTemplate` at write time.
435
519
  const resolvedTemplate = template ?? ctx.currentSettings.defaultTemplate;
436
520
 
437
521
  return {
438
522
  handled: true,
439
- response:
440
- `Plan "${titleCase(slug)}" (slug: ${slug}) will be created with the ` +
441
- `"${resolvedTemplate}" template.\n` +
442
- `After creation, use /plan open ${slug} to get the URL.`,
523
+ response: "",
443
524
  sideEffect: {
444
525
  kind: "create_plan",
445
526
  slug,
446
527
  template,
447
528
  },
448
529
  settingsPatch: { lastUsedSlug: slug },
530
+ dialog: {
531
+ id: generateId(),
532
+ title: "Plan Created",
533
+ command: `/plan new ${slug}`,
534
+ component: "generic",
535
+ data: {
536
+ message: `Plan "${titleCase(slug)}" created with the "${resolvedTemplate}" template.`,
537
+ detail: `Use /plan open ${slug} to open it.`,
538
+ },
539
+ },
449
540
  };
450
541
  }
451
542
 
@@ -453,19 +544,20 @@ function handlePlanNew(args: string[], ctx: ParseContext): SlashCommandResult {
453
544
 
454
545
  function handlePlanList(ctx: ParseContext): SlashCommandResult {
455
546
  const slugs = ctx.availablePlanSlugs ?? [];
456
- if (slugs.length === 0) {
457
- return {
458
- handled: true,
459
- response:
460
- "No plans found in this worktree. Use /plan new <slug> to create one.",
461
- sideEffect: { kind: "list_plans" },
462
- };
463
- }
464
- const lines = slugs.map((s) => ` - ${s}`);
465
547
  return {
466
548
  handled: true,
467
- response: `Plans in this worktree (${slugs.length}):\n${lines.join("\n")}`,
549
+ response: "",
468
550
  sideEffect: { kind: "list_plans" },
551
+ dialog: {
552
+ id: generateId(),
553
+ title: "Plans",
554
+ command: "/plan list",
555
+ component: "plan-list",
556
+ data: {
557
+ plans: slugs,
558
+ count: slugs.length,
559
+ },
560
+ },
469
561
  };
470
562
  }
471
563
 
@@ -492,16 +584,22 @@ function handlePlanOpen(args: string[], ctx: ParseContext): SlashCommandResult {
492
584
 
493
585
  return {
494
586
  handled: true,
495
- response:
496
- `Plan URL: ${url}\n` +
497
- `(v0.5.0 MVP — server startup is a future enhancement; the URL is ` +
498
- `informational. Use "bizar plan open ${slug}" in the terminal to ` +
499
- `start the local viewer.)`,
587
+ response: "",
500
588
  settingsPatch: { lastUsedSlug: slug },
501
589
  sideEffect: {
502
590
  kind: "open_plan_url",
503
591
  slug,
504
592
  },
593
+ dialog: {
594
+ id: generateId(),
595
+ title: "Opening Plan",
596
+ command: `/plan open ${slug}`,
597
+ component: "generic",
598
+ data: {
599
+ message: `Opening plan: ${slug}`,
600
+ url,
601
+ },
602
+ },
505
603
  };
506
604
  }
507
605
 
@@ -520,13 +618,22 @@ function handlePlanGet(args: string[]): SlashCommandResult {
520
618
  }
521
619
  return {
522
620
  handled: true,
523
- response: `Fetching canvas for plan "${slug}"…`,
621
+ response: "",
524
622
  sideEffect: {
525
623
  kind: "tool_invocation",
526
624
  toolName: "bizar_plan_action",
527
625
  args: { action: "get_canvas", planSlug: slug },
528
626
  },
529
627
  settingsPatch: { lastUsedSlug: slug },
628
+ dialog: {
629
+ id: generateId(),
630
+ title: "Plan Canvas",
631
+ command: `/plan get ${slug}`,
632
+ component: "generic",
633
+ data: {
634
+ message: `Fetching canvas for plan "${slug}"…`,
635
+ },
636
+ },
530
637
  };
531
638
  }
532
639
 
@@ -585,13 +692,22 @@ function handlePlanAdd(args: string[]): SlashCommandResult {
585
692
  }
586
693
  return {
587
694
  handled: true,
588
- response: `Adding element to plan "${slug}"…`,
695
+ response: "",
589
696
  sideEffect: {
590
697
  kind: "tool_invocation",
591
698
  toolName: "bizar_plan_action",
592
699
  args: { action: "add_element", planSlug: slug, element },
593
700
  },
594
701
  settingsPatch: { lastUsedSlug: slug },
702
+ dialog: {
703
+ id: generateId(),
704
+ title: "Element Added",
705
+ command: `/plan add ${slug}`,
706
+ component: "generic",
707
+ data: {
708
+ message: `Adding element to plan "${slug}"…`,
709
+ },
710
+ },
595
711
  };
596
712
  }
597
713
 
@@ -631,13 +747,22 @@ function handlePlanUpdate(args: string[]): SlashCommandResult {
631
747
  }
632
748
  return {
633
749
  handled: true,
634
- response: `Updating element ${elementId} in plan "${slug}"…`,
750
+ response: "",
635
751
  sideEffect: {
636
752
  kind: "tool_invocation",
637
753
  toolName: "bizar_plan_action",
638
754
  args: { action: "update_element", planSlug: slug, elementId, element },
639
755
  },
640
756
  settingsPatch: { lastUsedSlug: slug },
757
+ dialog: {
758
+ id: generateId(),
759
+ title: "Element Updated",
760
+ command: `/plan update ${slug}`,
761
+ component: "generic",
762
+ data: {
763
+ message: `Updating element ${elementId} in plan "${slug}"…`,
764
+ },
765
+ },
641
766
  };
642
767
  }
643
768
 
@@ -660,13 +785,22 @@ function handlePlanDelete(args: string[]): SlashCommandResult {
660
785
  }
661
786
  return {
662
787
  handled: true,
663
- response: `Deleting element ${elementId} from plan "${slug}"…`,
788
+ response: "",
664
789
  sideEffect: {
665
790
  kind: "tool_invocation",
666
791
  toolName: "bizar_plan_action",
667
792
  args: { action: "delete_element", planSlug: slug, elementId },
668
793
  },
669
794
  settingsPatch: { lastUsedSlug: slug },
795
+ dialog: {
796
+ id: generateId(),
797
+ title: "Element Deleted",
798
+ command: `/plan delete ${slug}`,
799
+ component: "generic",
800
+ data: {
801
+ message: `Deleting element ${elementId} from plan "${slug}"…`,
802
+ },
803
+ },
670
804
  };
671
805
  }
672
806
 
@@ -728,10 +862,7 @@ function commentSideEffect(
728
862
  ): SlashCommandResult {
729
863
  return {
730
864
  handled: true,
731
- response:
732
- elementId === null
733
- ? `Adding canvas-pinned comment to plan "${slug}"…`
734
- : `Adding comment to element ${elementId} on plan "${slug}"…`,
865
+ response: "",
735
866
  sideEffect: {
736
867
  kind: "tool_invocation",
737
868
  toolName: "bizar_plan_action",
@@ -746,6 +877,18 @@ function commentSideEffect(
746
877
  },
747
878
  },
748
879
  settingsPatch: { lastUsedSlug: slug },
880
+ dialog: {
881
+ id: generateId(),
882
+ title: "Comment Added",
883
+ command: `/plan comment ${slug}`,
884
+ component: "generic",
885
+ data: {
886
+ message:
887
+ elementId === null
888
+ ? `Adding canvas-pinned comment to plan "${slug}"…`
889
+ : `Adding comment to element ${elementId} on plan "${slug}"…`,
890
+ },
891
+ },
749
892
  };
750
893
  }
751
894
 
@@ -765,16 +908,25 @@ function handlePlanComments(args: string[]): SlashCommandResult {
765
908
  const elementId = args.length >= 2 ? args[1]! : undefined;
766
909
  return {
767
910
  handled: true,
768
- response:
769
- elementId === undefined
770
- ? `Reading comments on plan "${slug}"…`
771
- : `Reading comments on element ${elementId} of plan "${slug}"…`,
911
+ response: "",
772
912
  sideEffect: {
773
913
  kind: "tool_invocation",
774
914
  toolName: "bizar_get_plan_comments",
775
915
  args: elementId === undefined ? { planSlug: slug } : { planSlug: slug, elementId },
776
916
  },
777
917
  settingsPatch: { lastUsedSlug: slug },
918
+ dialog: {
919
+ id: generateId(),
920
+ title: "Comments",
921
+ command: `/plan comments ${slug}`,
922
+ component: "generic",
923
+ data: {
924
+ message:
925
+ elementId === undefined
926
+ ? `Reading comments on plan "${slug}"…`
927
+ : `Reading comments on element ${elementId} of plan "${slug}"…`,
928
+ },
929
+ },
778
930
  };
779
931
  }
780
932
 
@@ -803,13 +955,22 @@ function handlePlanStatus(args: string[]): SlashCommandResult {
803
955
  }
804
956
  return {
805
957
  handled: true,
806
- response: `Setting plan "${slug}" status to "${status}"…`,
958
+ response: "",
807
959
  sideEffect: {
808
960
  kind: "tool_invocation",
809
961
  toolName: "bizar_plan_action",
810
962
  args: { action: "set_status", planSlug: slug, status },
811
963
  },
812
964
  settingsPatch: { lastUsedSlug: slug },
965
+ dialog: {
966
+ id: generateId(),
967
+ title: "Status Updated",
968
+ command: `/plan status ${slug}`,
969
+ component: "generic",
970
+ data: {
971
+ message: `Setting plan "${slug}" status to "${status}"…`,
972
+ },
973
+ },
813
974
  };
814
975
  }
815
976
 
@@ -845,36 +1006,105 @@ function handlePlanWait(args: string[]): SlashCommandResult {
845
1006
  };
846
1007
  }
847
1008
 
1009
+ // --- /bizar --------------------------------------------------------------
1010
+
1011
+ /**
1012
+ * v2.5.0 — `/bizar [args]` launches the dashboard or routes a sub-request.
1013
+ *
1014
+ * Behavior:
1015
+ * - `/bizar` (no args) — emits a `launch_dashboard` side-effect. The
1016
+ * executor spawns `bizar dash start` as a detached child
1017
+ * process, then the host surfaces the URL in the response.
1018
+ * - `/bizar <args>` — passes the args to the menu command file. Today
1019
+ * the menu routes intent (`/explain`, `/plan`, `/audit`, etc.); the
1020
+ * response is the menu's natural-language routing advice.
1021
+ *
1022
+ * Note: the menu text lives in `config/commands/bizar.md` and is shipped
1023
+ * via the CLI package. The plugin only handles the no-arg case for the
1024
+ * side-effect; with args we return a short pointer so the user knows
1025
+ * where the routing table lives.
1026
+ */
1027
+ function handleBizar(arg: string, ctx: ParseContext): SlashCommandResult {
1028
+ const trimmed = arg.trim();
1029
+
1030
+ if (trimmed === "") {
1031
+ const port = ctx.defaultPort ?? 4321;
1032
+ return {
1033
+ handled: true,
1034
+ response: "",
1035
+ sideEffect: {
1036
+ kind: "launch_dashboard",
1037
+ defaultPort: port,
1038
+ },
1039
+ dialog: {
1040
+ id: generateId(),
1041
+ title: "Dashboard",
1042
+ command: "/bizar",
1043
+ component: "generic",
1044
+ data: {
1045
+ message: "Dashboard launching in the background…",
1046
+ url: `http://localhost:${port}/`,
1047
+ },
1048
+ },
1049
+ };
1050
+ }
1051
+
1052
+ // With args, defer to the menu command file shipped with the CLI.
1053
+ return {
1054
+ handled: true,
1055
+ response: "",
1056
+ dialog: {
1057
+ id: generateId(),
1058
+ title: "Bizar Commands",
1059
+ command: `/bizar ${trimmed}`,
1060
+ component: "help",
1061
+ data: {
1062
+ commands: [
1063
+ { cmd: "/bizar", desc: "Launch the Bizar dashboard" },
1064
+ { cmd: "/bizar explain <question>", desc: "Read-only code Q&A" },
1065
+ { cmd: "/bizar plan <args>", desc: "Manage plans" },
1066
+ { cmd: "/bizar audit", desc: "Run security audit" },
1067
+ { cmd: "/bizar learn", desc: "Extract patterns from session" },
1068
+ { cmd: "/bizar init", desc: "Initialize .bizar/ in this project" },
1069
+ { cmd: "/bizar pr-review", desc: "PR review" },
1070
+ ],
1071
+ },
1072
+ },
1073
+ };
1074
+ }
1075
+
848
1076
  // --- /help ----------------------------------------------------------------
849
1077
 
850
1078
  function helpResult(): SlashCommandResult {
851
1079
  return {
852
1080
  handled: true,
853
- response:
854
- `Available commands:\n` +
855
- ` /visual-plan on | off — Toggle visual plan mode\n` +
856
- ` /visual-plan — Show current visual plan state\n` +
857
- `\n` +
858
- ` /plan new <slug> [template] — Create a new plan\n` +
859
- ` /plan list — List all plans in the worktree\n` +
860
- ` /plan open <slug> — Return the URL for a plan\n` +
861
- `\n` +
862
- ` /plan get <slug> Fetch the full canvas (via bizar_plan_action)\n` +
863
- ` /plan add <slug> --title T --type kind [--x N --y N …]\n` +
864
- ` — Add an element to a plan (via bizar_plan_action)\n` +
865
- ` /plan update <slug> <id> [--x N --y N --title T --content C …]\n` +
866
- ` — Patch an existing element (via bizar_plan_action)\n` +
867
- ` /plan delete <slug> <id> Remove an element (via bizar_plan_action)\n` +
868
- ` /plan comment <slug> [id] "text"\n` +
869
- ` — Add a comment (via bizar_plan_action)\n` +
870
- ` /plan comments <slug> [id] Read comments (via bizar_get_plan_comments)\n` +
871
- ` /plan status <slug> <status> Set the plan's status (via bizar_plan_action)\n` +
872
- ` /plan wait <slug> [--timeout N]\n` +
873
- ` — Wait for feedback (deferred see /plan wait)\n` +
874
- `\n` +
875
- ` /help | /commands Show this help\n` +
876
- `\n` +
877
- `Available templates: ${KNOWN_TEMPLATES.join(", ")}\n` +
878
- `Available statuses: ${PLAN_STATUSES.join(", ")}`,
1081
+ response: "",
1082
+ dialog: {
1083
+ id: generateId(),
1084
+ title: "Bizar Commands",
1085
+ command: "/help",
1086
+ component: "help",
1087
+ data: {
1088
+ commands: [
1089
+ { cmd: "/visual-plan [on|off|status]", desc: "Toggle or view visual plan mode" },
1090
+ { cmd: "/plan new <slug> [template]", desc: "Create a new plan" },
1091
+ { cmd: "/plan list", desc: "List all plans in the worktree" },
1092
+ { cmd: "/plan open <slug>", desc: "Open a plan in the viewer" },
1093
+ { cmd: "/plan get <slug>", desc: "Fetch the full canvas" },
1094
+ { cmd: "/plan add <slug> --title T --type kind", desc: "Add an element to a plan" },
1095
+ { cmd: "/plan update <slug> <id> [flags]", desc: "Patch an existing element" },
1096
+ { cmd: "/plan delete <slug> <id>", desc: "Remove an element" },
1097
+ { cmd: "/plan comment <slug> [id] \"text\"", desc: "Add a comment" },
1098
+ { cmd: "/plan comments <slug> [id]", desc: "Read comments on a plan" },
1099
+ { cmd: "/plan status <slug> <status>", desc: "Set the plan's status" },
1100
+ { cmd: "/plan wait <slug> [--timeout N]", desc: "Wait for feedback (deferred)" },
1101
+ { cmd: "/bizar", desc: "Launch the Bizar dashboard" },
1102
+ { cmd: "/bizar <args>", desc: "Route a request via the menu" },
1103
+ { cmd: "/help | /commands", desc: "Show this help" },
1104
+ ],
1105
+ templates: [...KNOWN_TEMPLATES],
1106
+ statuses: [...PLAN_STATUSES],
1107
+ },
1108
+ },
879
1109
  };
880
1110
  }
package/src/plan-fs.ts CHANGED
@@ -43,7 +43,7 @@ import {
43
43
  rmSync,
44
44
  writeFileSync,
45
45
  } from "node:fs";
46
- import { join } from "node:path";
46
+ import { dirname, join } from "node:path";
47
47
 
48
48
  import type { Logger } from "./logger.js";
49
49
 
@@ -146,7 +146,7 @@ function writeJsonAtomic(
146
146
  ): { ok: true } | { ok: false; error: string } {
147
147
  const tmp = `${filePath}.tmp`;
148
148
  try {
149
- mkdirSync(join(filePath, ".."), { recursive: true });
149
+ mkdirSync(dirname(filePath), { recursive: true });
150
150
  writeFileSync(tmp, JSON.stringify(data, null, 2), "utf-8");
151
151
  renameSync(tmp, filePath);
152
152
  return { ok: true };