@cleocode/cleo-os 2026.4.36 → 2026.4.38

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.
@@ -7,19 +7,10 @@
7
7
  * Loaded by: Pi via `--extension <path>` injected by CleoOS cli.ts
8
8
  *
9
9
  * On `session_start`, displays a branded CleoOS welcome panel with:
10
- * - ASCII art CLEO logo
11
10
  * - Project name and CLEO task summary (pending / active / done)
12
- * - Last 3 brain decisions ("what we decided recently")
13
- * - Memory bridge summary (total entries, % verified, top 3 cited)
14
- * - Quick action hints based on current state
15
11
  * - Current focused task (if any)
16
12
  * - Last session handoff note from the memory bridge
17
13
  *
18
- * Also registers:
19
- * - `/cleo:status` — on-demand project status refresh
20
- * - `/cleo:focus <task-id>` — focus on a task
21
- * - `/cleo:end-session [note]` — end the current session
22
- *
23
14
  * All data is fetched via the `cleo` CLI (best-effort). If any call
24
15
  * fails, the banner degrades gracefully — Pi is never crashed.
25
16
  *
@@ -118,36 +109,6 @@ interface CurrentTask {
118
109
  status: string;
119
110
  }
120
111
 
121
- /**
122
- * A single decision entry from `cleo memory decision-find --json`.
123
- */
124
- interface DecisionEntry {
125
- /** Short decision ID. */
126
- id: string;
127
- /** Decision title or summary text. */
128
- title: string;
129
- /** ISO date string. */
130
- date: string;
131
- }
132
-
133
- /**
134
- * Memory bridge statistics parsed from `.cleo/memory-bridge.md`.
135
- */
136
- interface MemoryBridgeStats {
137
- /** Total entry count found in memory bridge sections. */
138
- totalEntries: number;
139
- /** Top 3 cited items (label lines from the bridge). */
140
- topThree: string[];
141
- }
142
-
143
- /**
144
- * Quick-action hints built from task/session state.
145
- */
146
- interface QuickHints {
147
- /** Array of short hint strings to display. */
148
- hints: string[];
149
- }
150
-
151
112
  // ============================================================================
152
113
  // Parsers — best-effort, always return a typed default on failure
153
114
  // ============================================================================
@@ -268,116 +229,6 @@ export function parseCurrentTask(stdout: string): CurrentTask | null {
268
229
  }
269
230
  }
270
231
 
271
- /**
272
- * Parse the last 3 decisions from `cleo memory decision-find --json` output.
273
- *
274
- * @param stdout - Raw stdout from the CLI call.
275
- * @returns Array of up to 3 decision entries.
276
- */
277
- export function parseRecentDecisions(stdout: string): DecisionEntry[] {
278
- try {
279
- const parsed = JSON.parse(stdout) as Record<string, unknown>;
280
- const data = (parsed["data"] ?? parsed) as Record<string, unknown>;
281
- const rawDecisions = data["decisions"];
282
- if (!Array.isArray(rawDecisions)) return [];
283
-
284
- return rawDecisions
285
- .slice(0, 3)
286
- .map((d) => {
287
- const entry = d as Record<string, unknown>;
288
- const id = typeof entry["id"] === "string" ? entry["id"] : "";
289
- const title =
290
- typeof entry["title"] === "string"
291
- ? entry["title"]
292
- : typeof entry["summary"] === "string"
293
- ? entry["summary"]
294
- : "";
295
- const date =
296
- typeof entry["date"] === "string"
297
- ? entry["date"].slice(0, 10)
298
- : typeof entry["createdAt"] === "string"
299
- ? entry["createdAt"].slice(0, 10)
300
- : "";
301
- return { id, title, date };
302
- })
303
- .filter((d) => d.title.length > 0);
304
- } catch {
305
- return [];
306
- }
307
- }
308
-
309
- /**
310
- * Parse memory bridge statistics from `.cleo/memory-bridge.md`.
311
- *
312
- * Counts total list items across all sections and extracts the
313
- * top 3 cited items (items appearing in the "Recent Decisions" or
314
- * "Key Learnings" sections).
315
- *
316
- * @param content - Raw memory-bridge.md content.
317
- * @returns Memory bridge statistics.
318
- */
319
- export function parseMemoryBridgeStats(content: string): MemoryBridgeStats {
320
- if (!content || content.length === 0) {
321
- return { totalEntries: 0, topThree: [] };
322
- }
323
-
324
- // Count all list items (lines starting with "- " or "* ")
325
- const allItems = content.match(/^[-*]\s+\[.+?\].+/gm) ?? [];
326
- const totalEntries = allItems.length;
327
-
328
- // Extract top 3 from "Key Learnings" or "Recent Decisions" sections
329
- const learningsMatch = content.match(/## Key Learnings\n([\s\S]*?)(?=\n##|\s*$)/m);
330
- const decisionsMatch = content.match(/## Recent Decisions\n([\s\S]*?)(?=\n##|\s*$)/m);
331
-
332
- const topSection = (learningsMatch?.[1] ?? decisionsMatch?.[1] ?? "");
333
- const topItems = (topSection.match(/^[-*]\s+\[.+?\]\s+(.+)/gm) ?? [])
334
- .slice(0, 3)
335
- .map((line) => {
336
- // Strip the "- [ID] " prefix, keep the description
337
- return line.replace(/^[-*]\s+\[.+?\]\s+/, "").trim();
338
- })
339
- .filter((s) => s.length > 0);
340
-
341
- return { totalEntries, topThree: topItems };
342
- }
343
-
344
- /**
345
- * Build quick-action hints based on current project state.
346
- *
347
- * Generates context-sensitive suggestions so the operator knows
348
- * immediately what to do next without running extra commands.
349
- *
350
- * @param tasks - Task summary counts.
351
- * @param session - Current session state.
352
- * @param currentTask - Currently focused task, or null.
353
- * @returns Quick-action hints.
354
- */
355
- export function buildQuickHints(
356
- tasks: TaskSummary,
357
- session: SessionInfo,
358
- currentTask: CurrentTask | null,
359
- ): QuickHints {
360
- const hints: string[] = [];
361
-
362
- if (!session.active) {
363
- hints.push("No active session — run `cleo session start` to begin");
364
- } else if (!currentTask && tasks.pending > 0) {
365
- hints.push(`${tasks.pending} ready tasks — run \`cleo next\` to pick one`);
366
- } else if (currentTask) {
367
- hints.push(`Working on [${currentTask.id}] — run \`cleo complete ${currentTask.id}\` when done`);
368
- }
369
-
370
- if (tasks.blocked > 0) {
371
- hints.push(`${tasks.blocked} blocked task(s) — run \`cleo blockers\` to review`);
372
- }
373
-
374
- if (hints.length === 0 && tasks.total === 0) {
375
- hints.push("No tasks yet — run `cleo add \"<task title>\"` to start");
376
- }
377
-
378
- return { hints };
379
- }
380
-
381
232
  /**
382
233
  * Read the last session handoff note from `.cleo/memory-bridge.md`.
383
234
  *
@@ -455,30 +306,12 @@ function padBannerLine(
455
306
  );
456
307
  }
457
308
 
458
- /**
459
- * ASCII art CLEO logo lines (no ANSI — caller applies color).
460
- *
461
- * Rendered in a compact 3-row form that fits within the 54-char banner width.
462
- */
463
- const CLEO_ASCII_LOGO: readonly string[] = [
464
- " ██████╗██╗ ███████╗ ██████╗",
465
- " ██╔════╝██║ ██╔════╝██╔═══██╗",
466
- " ██║ ██║ █████╗ ██║ ██║",
467
- " ██║ ██║ ██╔══╝ ██║ ██║",
468
- " ╚██████╗███████╗███████╗╚██████╔╝",
469
- " ╚═════╝╚══════╝╚══════╝ ╚═════╝",
470
- ] as const;
471
-
472
309
  /**
473
310
  * Build the full CleoOS startup banner.
474
311
  *
475
312
  * Renders a box-drawing widget with:
476
- * - ASCII art CLEO logo
477
313
  * - Branded header with forge icon
478
314
  * - Task counts (active / pending / done / blocked)
479
- * - Last 3 brain decisions
480
- * - Memory bridge summary
481
- * - Quick action hints
482
315
  * - Current task title (if any)
483
316
  * - Session info (name + ID)
484
317
  * - Last session handoff note (from memory-bridge.md or session data)
@@ -488,9 +321,6 @@ const CLEO_ASCII_LOGO: readonly string[] = [
488
321
  * @param currentTask - Currently focused task, or null.
489
322
  * @param handoffNote - Last session handoff note, or null.
490
323
  * @param projectName - Project display name.
491
- * @param decisions - Recent brain decisions (up to 3).
492
- * @param memStats - Memory bridge statistics.
493
- * @param hints - Quick-action hints.
494
324
  * @returns Array of ANSI-styled banner lines.
495
325
  */
496
326
  export function buildStartupBanner(
@@ -499,9 +329,6 @@ export function buildStartupBanner(
499
329
  currentTask: CurrentTask | null,
500
330
  handoffNote: string | null,
501
331
  projectName: string,
502
- decisions: DecisionEntry[] = [],
503
- memStats: MemoryBridgeStats = { totalEntries: 0, topThree: [] },
504
- hints: QuickHints = { hints: [] },
505
332
  ): string[] {
506
333
  // Inner width: characters between the two vertical border chars
507
334
  // (not counting the leading space + BOX_VERTICAL or trailing BOX_VERTICAL)
@@ -513,20 +340,24 @@ export function buildStartupBanner(
513
340
  // ── Top border ────────────────────────────────────────────────────────
514
341
  lines.push(accentPrimary(BOX_TOP_LEFT + hBar + BOX_TOP_RIGHT));
515
342
 
516
- // ── ASCII Logo ────────────────────────────────────────────────────────
517
- for (const logoLine of CLEO_ASCII_LOGO) {
518
- // Pad to INNER width so border chars align
519
- lines.push(padBannerLine(logoLine + " ", accentPrimary(logoLine), INNER));
520
- }
521
-
522
343
  // ── Header row ────────────────────────────────────────────────────────
523
- const headerRaw = ` ${ICON_FORGE} Agentic Dev Forge ${ICON_FORGE} ${truncate(projectName, 18)}`;
344
+ const headerRaw = ` ${ICON_FORGE} C L E O O S ${ICON_FORGE} ${truncate(projectName, 24)}`;
524
345
  lines.push(
525
346
  padBannerLine(
526
347
  " " + headerRaw + " ",
527
- " " + bold(accentPrimary(`${ICON_FORGE} Agentic Dev Forge ${ICON_FORGE}`)) +
528
- accentPrimary(" ") +
529
- bold(textSecondary(truncate(projectName, 18))),
348
+ " " + bold(accentPrimary(`${ICON_FORGE} C L E O O S ${ICON_FORGE}`)) +
349
+ accentPrimary(" ") +
350
+ bold(textSecondary(truncate(projectName, 24))),
351
+ INNER,
352
+ ),
353
+ );
354
+
355
+ // ── Subtitle ──────────────────────────────────────────────────────────
356
+ const subtitleRaw = " The Agentic Development Forge";
357
+ lines.push(
358
+ padBannerLine(
359
+ " " + subtitleRaw + " ",
360
+ " " + textSecondary("The Agentic Development Forge"),
530
361
  INNER,
531
362
  ),
532
363
  );
@@ -588,63 +419,6 @@ export function buildStartupBanner(
588
419
  lines.push(padBannerLine(" " + sessionRaw + " ", sessionStyled, INNER));
589
420
  }
590
421
 
591
- // ── Memory bridge stats ───────────────────────────────────────────────
592
- if (memStats.totalEntries > 0) {
593
- lines.push(accentPrimary(BOX_LEFT_T + hBar + BOX_RIGHT_T));
594
- const memRaw = ` Memory: ${memStats.totalEntries} entries`;
595
- const memStyled =
596
- ` ${textSecondary("Memory:")} ` + accentSuccess(String(memStats.totalEntries)) +
597
- textSecondary(" entries");
598
- lines.push(padBannerLine(" " + memRaw + " ", memStyled, INNER));
599
-
600
- for (const item of memStats.topThree) {
601
- const truncItem = truncate(item, INNER - 6);
602
- lines.push(
603
- padBannerLine(
604
- ` * ${truncItem} `,
605
- ` ${textTertiary("*")} ${textSecondary(truncItem)}`,
606
- INNER,
607
- ),
608
- );
609
- }
610
- }
611
-
612
- // ── Recent decisions ──────────────────────────────────────────────────
613
- if (decisions.length > 0) {
614
- lines.push(accentPrimary(BOX_LEFT_T + hBar + BOX_RIGHT_T));
615
- const decHeaderRaw = " What we decided recently:";
616
- lines.push(
617
- padBannerLine(
618
- " " + decHeaderRaw + " ",
619
- " " + bold(textSecondary("What we decided recently:")),
620
- INNER,
621
- ),
622
- );
623
- for (const dec of decisions) {
624
- const label = dec.date ? `[${dec.date}]` : `[${dec.id}]`;
625
- const decRaw = ` ${label} ${truncate(dec.title, INNER - label.length - 5)}`;
626
- const decStyled =
627
- ` ${textTertiary(label)} ` +
628
- textSecondary(truncate(dec.title, INNER - label.length - 5));
629
- lines.push(padBannerLine(" " + decRaw + " ", decStyled, INNER));
630
- }
631
- }
632
-
633
- // ── Quick action hints ────────────────────────────────────────────────
634
- if (hints.hints.length > 0) {
635
- lines.push(accentPrimary(BOX_LEFT_T + hBar + BOX_RIGHT_T));
636
- for (const hint of hints.hints) {
637
- const truncHint = truncate(hint, INNER - 5);
638
- lines.push(
639
- padBannerLine(
640
- ` > ${truncHint} `,
641
- ` ${accentWarning(">")} ${accentWarning(truncHint)}`,
642
- INNER,
643
- ),
644
- );
645
- }
646
- }
647
-
648
422
  // ── Handoff note ──────────────────────────────────────────────────────
649
423
  const note = handoffNote ?? session.handoffNote;
650
424
  if (note) {
@@ -738,143 +512,65 @@ export function detectProjectName(projectDir: string): string {
738
512
  // Pi extension factory
739
513
  // ============================================================================
740
514
 
741
- // ============================================================================
742
- // Shared fetch helper (used by both session_start and /cleo:status)
743
- // ============================================================================
744
-
745
515
  /**
746
- * Fetch all data needed to render the startup banner.
516
+ * Pi extension factory for the CleoOS branded startup experience.
517
+ *
518
+ * Registers a `session_start` handler that:
519
+ * 1. Fetches project, task, and session data via `cleo` CLI in parallel
520
+ * 2. Reads the memory-bridge.md handoff note
521
+ * 3. Renders the branded startup banner as a Pi UI widget
747
522
  *
748
- * All CLI calls are issued in parallel and are best-effort no failure
749
- * will throw or block. Returns a fully-populated data bundle.
523
+ * All operations are best-effort failures are silently swallowed so Pi
524
+ * is never blocked by CLEO unavailability.
750
525
  *
751
- * @param cwd - Project root directory.
752
- * @returns All banner data.
526
+ * @param pi - The Pi extension API instance.
753
527
  */
754
- async function fetchBannerData(cwd: string): Promise<{
755
- tasks: TaskSummary;
756
- session: SessionInfo;
757
- currentTask: CurrentTask | null;
758
- handoffNote: string | null;
759
- projectName: string;
760
- decisions: DecisionEntry[];
761
- memStats: MemoryBridgeStats;
762
- hints: QuickHints;
763
- }> {
764
- const [dashResult, sessionResult, currentResult, decisionsResult] =
765
- await Promise.allSettled([
766
- execFileAsync("cleo", ["dash", "--json"], { timeout: 8_000, cwd }),
767
- execFileAsync("cleo", ["session", "status", "--json"], { timeout: 8_000, cwd }),
768
- execFileAsync("cleo", ["current", "--json"], { timeout: 8_000, cwd }),
769
- execFileAsync("cleo", ["memory", "decision-find", "--limit", "3", "--json"], {
528
+ export default function (pi: ExtensionAPI): void {
529
+ pi.on("session_start", async (_event: unknown, ctx: ExtensionContext) => {
530
+ // Fetch dash + session status in parallel (best-effort)
531
+ const [dashResult, sessionResult, currentResult] = await Promise.allSettled([
532
+ execFileAsync("cleo", ["dash", "--json"], {
770
533
  timeout: 8_000,
771
- cwd,
534
+ cwd: ctx.cwd,
535
+ }),
536
+ execFileAsync("cleo", ["session", "status", "--json"], {
537
+ timeout: 8_000,
538
+ cwd: ctx.cwd,
539
+ }),
540
+ execFileAsync("cleo", ["current", "--json"], {
541
+ timeout: 8_000,
542
+ cwd: ctx.cwd,
772
543
  }),
773
544
  ]);
774
545
 
775
- const tasks =
776
- dashResult.status === "fulfilled"
777
- ? parseDashSummary(dashResult.value.stdout)
778
- : { active: 0, pending: 0, done: 0, total: 0, blocked: 0 };
779
-
780
- const session =
781
- sessionResult.status === "fulfilled"
782
- ? parseSessionInfo(sessionResult.value.stdout)
783
- : { active: false, id: "", name: "", currentTaskId: null, handoffNote: null };
784
-
785
- const currentTask =
786
- currentResult.status === "fulfilled"
787
- ? parseCurrentTask(currentResult.value.stdout)
788
- : null;
546
+ const tasks =
547
+ dashResult.status === "fulfilled"
548
+ ? parseDashSummary(dashResult.value.stdout)
549
+ : { active: 0, pending: 0, done: 0, total: 0, blocked: 0 };
789
550
 
790
- const decisions =
791
- decisionsResult.status === "fulfilled"
792
- ? parseRecentDecisions(decisionsResult.value.stdout)
793
- : [];
551
+ const session =
552
+ sessionResult.status === "fulfilled"
553
+ ? parseSessionInfo(sessionResult.value.stdout)
554
+ : { active: false, id: "", name: "", currentTaskId: null, handoffNote: null };
794
555
 
795
- // Memory bridge stats — synchronous read from filesystem
796
- const bridgeContent = readMemoryBridgeContent(cwd);
797
- const memStats = bridgeContent
798
- ? parseMemoryBridgeStats(bridgeContent)
799
- : { totalEntries: 0, topThree: [] };
800
-
801
- const handoffNote = bridgeContent ? extractNoteFromBridgeContent(bridgeContent) : null;
802
- const projectName = detectProjectName(cwd);
803
- const hints = buildQuickHints(tasks, session, currentTask);
804
-
805
- return { tasks, session, currentTask, handoffNote, projectName, decisions, memStats, hints };
806
- }
807
-
808
- /**
809
- * Read raw `.cleo/memory-bridge.md` content.
810
- *
811
- * Returns null when the file does not exist or cannot be read.
812
- *
813
- * @param projectDir - The project root directory.
814
- * @returns The raw file content, or null.
815
- */
816
- function readMemoryBridgeContent(projectDir: string): string | null {
817
- try {
818
- const bridgePath = join(projectDir, ".cleo", "memory-bridge.md");
819
- if (!existsSync(bridgePath)) return null;
820
- const content = readFileSync(bridgePath, "utf-8");
821
- return content.length > 0 ? content : null;
822
- } catch {
823
- return null;
824
- }
825
- }
556
+ const currentTask =
557
+ currentResult.status === "fulfilled"
558
+ ? parseCurrentTask(currentResult.value.stdout)
559
+ : null;
826
560
 
827
- /**
828
- * Extract the last session handoff note from raw memory-bridge.md content.
829
- *
830
- * Prefers the `## Last Session` section's `Note:` line.
831
- *
832
- * @param content - Raw memory-bridge.md content.
833
- * @returns The last session note, or null.
834
- */
835
- function extractNoteFromBridgeContent(content: string): string | null {
836
- try {
837
- const noteMatch = content.match(/[-*]\s+\*\*Note\*\*:\s*(.+)/m);
838
- if (noteMatch?.[1]) return noteMatch[1].trim();
839
- const altMatch = content.match(/\*\*Note\*\*[:\s]+(.+)/m);
840
- if (altMatch?.[1]) return altMatch[1].trim();
841
- return null;
842
- } catch {
843
- return null;
844
- }
845
- }
561
+ // Read handoff note from memory-bridge.md (synchronous, fast)
562
+ const handoffNote = readMemoryBridgeNote(ctx.cwd);
846
563
 
847
- // ============================================================================
848
- // Pi extension factory
849
- // ============================================================================
850
-
851
- /**
852
- * Pi extension factory for the CleoOS branded startup experience.
853
- *
854
- * Registers:
855
- * - `session_start` — fetches all data, renders the branded startup banner
856
- * - `/cleo:status` — on-demand project status refresh
857
- * - `/cleo:focus <task-id>` — focus on a task from inside Pi
858
- * - `/cleo:end-session [note]` — end the current session from inside Pi
859
- *
860
- * All operations are best-effort — failures are silently swallowed so Pi
861
- * is never blocked by CLEO unavailability.
862
- *
863
- * @param pi - The Pi extension API instance.
864
- */
865
- export default function (pi: ExtensionAPI): void {
866
- pi.on("session_start", async (_event: unknown, ctx: ExtensionContext) => {
867
- const data = await fetchBannerData(ctx.cwd);
564
+ // Detect project name from filesystem
565
+ const projectName = detectProjectName(ctx.cwd);
868
566
 
567
+ // Build and display the banner
869
568
  const bannerLines = buildStartupBanner(
870
- data.tasks,
871
- data.session,
872
- data.currentTask,
873
- data.handoffNote,
874
- data.projectName,
875
- data.decisions,
876
- data.memStats,
877
- data.hints,
569
+ tasks,
570
+ session,
571
+ currentTask,
572
+ handoffNote,
573
+ projectName,
878
574
  );
879
575
 
880
576
  if (ctx.hasUI) {
@@ -882,11 +578,11 @@ export default function (pi: ExtensionAPI): void {
882
578
  placement: "aboveEditor",
883
579
  });
884
580
 
885
- // Compact status bar entry
886
- const taskSummary = `${data.tasks.active}a ${data.tasks.pending}p ${data.tasks.done}d`;
581
+ // Also set a compact status bar entry
582
+ const taskSummary = `${tasks.active}a ${tasks.pending}p ${tasks.done}d`;
887
583
  ctx.ui.setStatus(
888
584
  "cleo-startup",
889
- `${ICON_FORGE} ${data.projectName.split("/").pop() ?? data.projectName} [${taskSummary}]`,
585
+ `${ICON_FORGE} ${projectName.split("/").pop() ?? projectName} [${taskSummary}]`,
890
586
  );
891
587
  } else {
892
588
  // No UI — print to stderr as a text summary (visible in TTY mode)
@@ -898,29 +594,40 @@ export default function (pi: ExtensionAPI): void {
898
594
  // Command: /cleo:status — on-demand project status refresh
899
595
  // -------------------------------------------------------------------------
900
596
  pi.registerCommand("cleo:status", {
901
- description: "Show CleoOS project status: tasks, session, decisions, and hints",
597
+ description: "Show CleoOS project status: tasks, session, and last handoff",
902
598
  handler: async (_args: string, ctx: ExtensionContext) => {
903
- const data = await fetchBannerData(ctx.cwd);
599
+ const [dashResult, sessionResult, currentResult] = await Promise.allSettled([
600
+ execFileAsync("cleo", ["dash", "--json"], { timeout: 8_000, cwd: ctx.cwd }),
601
+ execFileAsync("cleo", ["session", "status", "--json"], { timeout: 8_000, cwd: ctx.cwd }),
602
+ execFileAsync("cleo", ["current", "--json"], { timeout: 8_000, cwd: ctx.cwd }),
603
+ ]);
604
+
605
+ const tasks =
606
+ dashResult.status === "fulfilled"
607
+ ? parseDashSummary(dashResult.value.stdout)
608
+ : { active: 0, pending: 0, done: 0, total: 0, blocked: 0 };
609
+
610
+ const session =
611
+ sessionResult.status === "fulfilled"
612
+ ? parseSessionInfo(sessionResult.value.stdout)
613
+ : { active: false, id: "", name: "", currentTaskId: null, handoffNote: null };
614
+
615
+ const currentTask =
616
+ currentResult.status === "fulfilled"
617
+ ? parseCurrentTask(currentResult.value.stdout)
618
+ : null;
619
+
620
+ const handoffNote = readMemoryBridgeNote(ctx.cwd);
621
+ const projectName = detectProjectName(ctx.cwd);
904
622
 
905
623
  const bannerLines = buildStartupBanner(
906
- data.tasks,
907
- data.session,
908
- data.currentTask,
909
- data.handoffNote,
910
- data.projectName,
911
- data.decisions,
912
- data.memStats,
913
- data.hints,
624
+ tasks,
625
+ session,
626
+ currentTask,
627
+ handoffNote,
628
+ projectName,
914
629
  );
915
630
 
916
- // Refresh the widget too
917
- if (ctx.hasUI) {
918
- ctx.ui.setWidget("cleo-startup-banner", bannerLines, {
919
- placement: "aboveEditor",
920
- });
921
- ctx.ui.notify("CleoOS status refreshed", "info");
922
- }
923
-
924
631
  pi.sendMessage(
925
632
  {
926
633
  customType: "cleo-status",
@@ -929,91 +636,15 @@ export default function (pi: ExtensionAPI): void {
929
636
  },
930
637
  { triggerTurn: false },
931
638
  );
932
- },
933
- });
934
-
935
- // -------------------------------------------------------------------------
936
- // Command: /cleo:focus <task-id> — focus on a task
937
- // -------------------------------------------------------------------------
938
- pi.registerCommand("cleo:focus", {
939
- description: "Focus on a CLEO task: /cleo:focus <task-id>",
940
- handler: async (args: string, ctx: ExtensionContext) => {
941
- const taskId = args.trim();
942
- if (!taskId) {
943
- pi.sendMessage(
944
- {
945
- customType: "cleo-focus",
946
- content: "Usage: /cleo:focus <task-id>",
947
- display: true,
948
- },
949
- { triggerTurn: false },
950
- );
951
- return;
952
- }
953
-
954
- let resultText: string;
955
- try {
956
- const { stdout } = await execFileAsync(
957
- "cleo",
958
- ["start", taskId],
959
- { timeout: 8_000, cwd: ctx.cwd },
960
- );
961
- resultText = stdout.trim() || `Focused on task ${taskId}`;
962
- } catch (err: unknown) {
963
- resultText = `Failed to focus on ${taskId}: ${err instanceof Error ? err.message : String(err)}`;
964
- }
965
-
966
- pi.sendMessage(
967
- {
968
- customType: "cleo-focus",
969
- content: resultText,
970
- display: true,
971
- },
972
- { triggerTurn: false },
973
- );
974
-
975
- if (ctx.hasUI) {
976
- ctx.ui.notify(`Focused: ${taskId}`, "info");
977
- }
978
- },
979
- });
980
-
981
- // -------------------------------------------------------------------------
982
- // Command: /cleo:end-session [note] — end the current CLEO session
983
- // -------------------------------------------------------------------------
984
- pi.registerCommand("cleo:end-session", {
985
- description: "End the current CLEO session with an optional handoff note",
986
- handler: async (args: string, ctx: ExtensionContext) => {
987
- const note = args.trim() || "Session ended from CleoOS Hearth";
988
- let resultText: string;
989
- try {
990
- const { stdout } = await execFileAsync(
991
- "cleo",
992
- ["session", "end", "--note", note],
993
- { timeout: 15_000, cwd: ctx.cwd },
994
- );
995
- resultText = stdout.trim() || "Session ended";
996
- } catch (err: unknown) {
997
- resultText = `Failed to end session: ${err instanceof Error ? err.message : String(err)}`;
998
- }
999
-
1000
- pi.sendMessage(
1001
- {
1002
- customType: "cleo-end-session",
1003
- content: resultText,
1004
- display: true,
1005
- },
1006
- { triggerTurn: false },
1007
- );
1008
639
 
1009
640
  if (ctx.hasUI) {
1010
- ctx.ui.notify("CLEO session ended", "info");
641
+ ctx.ui.notify("CleoOS status refreshed", "info");
1011
642
  }
1012
643
  },
1013
644
  });
1014
645
 
1015
- // session_shutdown: no cleanup needed Pi clears widgets on shutdown
646
+ // session_shutdown: remove the startup banner widget
1016
647
  pi.on("session_shutdown", async () => {
1017
- // Intentional no-op
648
+ // No cleanup needed — Pi clears widgets on shutdown
1018
649
  });
1019
650
  }