@mrclrchtr/supi-extras 1.8.0 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,3 +1,5 @@
1
+ ![SuPi](assets/logo.png)
2
+
1
3
  # @mrclrchtr/supi-extras
2
4
 
3
5
  Adds a bundle of small quality-of-life features to the [pi coding agent](https://github.com/earendil-works/pi).
@@ -14,7 +16,7 @@ For local development:
14
16
  pi install ./packages/supi-extras
15
17
  ```
16
18
 
17
- After editing the source, run `/reload`.
19
+ ![Stash picker overlay](https://raw.githubusercontent.com/mrclrchtr/supi/main/screenshots/supi-extras-stash.png)
18
20
 
19
21
  ## What you get
20
22
 
@@ -1,29 +1,20 @@
1
+ ![SuPi](assets/logo.png)
2
+
1
3
  # @mrclrchtr/supi-core
2
4
 
3
5
  Shared infrastructure for SuPi extensions.
4
6
 
5
- This package is mainly for extension authors. It gives you a common config system, settings plumbing, context helpers, registries, and a small extension surface that registers `/supi-settings`.
7
+ This is a **pure library** it does not register any pi commands or tools. The `/supi-settings` command is now available through `@mrclrchtr/supi-settings`.
6
8
 
7
9
  ## Install
8
10
 
9
- ### As a dependency for another extension
10
-
11
11
  ```bash
12
12
  pnpm add @mrclrchtr/supi-core
13
13
  ```
14
14
 
15
- ### As a pi package
16
-
17
- ```bash
18
- pi install npm:@mrclrchtr/supi-core
19
- ```
20
-
21
- Installing it as a pi package adds the minimal `/supi-settings` extension surface.
22
-
23
15
  ## Package surfaces
24
16
 
25
17
  - `@mrclrchtr/supi-core/api` — reusable helpers for other packages and extensions
26
- - `@mrclrchtr/supi-core/extension` — minimal pi extension that registers `/supi-settings`
27
18
 
28
19
  ## What you get from the API
29
20
 
@@ -101,7 +92,6 @@ const message = wrapExtensionContext("my-extension", "hello", {
101
92
  ## Source
102
93
 
103
94
  - `src/api.ts` — exported library surface
104
- - `src/extension.ts` — minimal `/supi-settings` entrypoint
105
95
  - `src/config.ts` — shared config loading and writing
106
96
  - `src/config-settings.ts` — config-backed settings registration helper
107
97
  - `src/settings-ui.ts` — shared settings overlay
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-core",
3
- "version": "1.8.0",
3
+ "version": "1.9.0",
4
4
  "description": "SuPi core — shared infrastructure for SuPi extensions (XML context tags, config system)",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -40,7 +40,6 @@
40
40
  "./config": "./src/config.ts",
41
41
  "./context": "./src/context.ts",
42
42
  "./debug": "./src/debug-registry.ts",
43
- "./extension": "./src/extension.ts",
44
43
  "./package.json": "./package.json",
45
44
  "./path": "./src/path.ts",
46
45
  "./project": "./src/project.ts",
@@ -50,10 +49,5 @@
50
49
  "./terminal": "./src/terminal.ts",
51
50
  "./tool-framework": "./src/tool-framework.ts",
52
51
  "./types": "./src/types.ts"
53
- },
54
- "pi": {
55
- "extensions": [
56
- "./src/extension.ts"
57
- ]
58
52
  }
59
53
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-extras",
3
- "version": "1.8.0",
3
+ "version": "1.9.0",
4
4
  "description": "SuPi extras — command aliases, skill shorthand, tab spinner, /supi-stash prompt stash with TUI overlay, and other small utilities",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -21,7 +21,7 @@
21
21
  ],
22
22
  "dependencies": {
23
23
  "clipboardy": "^5.3.1",
24
- "@mrclrchtr/supi-core": "1.8.0"
24
+ "@mrclrchtr/supi-core": "1.9.0"
25
25
  },
26
26
  "bundledDependencies": [
27
27
  "@mrclrchtr/supi-core"
@@ -41,9 +41,9 @@
41
41
  },
42
42
  "pi": {
43
43
  "extensions": [
44
- "./src/extension.ts",
45
- "node_modules/@mrclrchtr/supi-core/src/extension.ts"
46
- ]
44
+ "./src/extension.ts"
45
+ ],
46
+ "image": "https://raw.githubusercontent.com/mrclrchtr/supi/main/packages/supi-extras/assets/logo.png"
47
47
  },
48
48
  "exports": {
49
49
  "./api": "./src/api.ts",
@@ -15,6 +15,18 @@ import { formatTitle, signalDone } from "@mrclrchtr/supi-core/terminal";
15
15
  const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
16
16
  const AGENT_END_SETTLE_MS = 200;
17
17
 
18
+ /** Icons shown in the terminal title when a review phase completes. */
19
+ const BRIEF_DONE_ICON = "\u{1F4CB}"; // 📋 clipboard
20
+ const REVIEW_DONE_ICON = "\u{1F50D}"; // 🔍 magnifying glass
21
+ const REVIEW_FAILED_ICON = "\u2717"; // ✗ ballot x
22
+
23
+ type ReviewTitleState =
24
+ | { kind: "none" }
25
+ | { kind: "brief-done" }
26
+ | { kind: "brief-failed" }
27
+ | { kind: "review-done" }
28
+ | { kind: "review-failed" };
29
+
18
30
  // biome-ignore lint/complexity/noExcessiveLinesPerFunction: spinner state and event wiring are intentionally colocated
19
31
  export default function tabSpinner(pi: ExtensionAPI) {
20
32
  let timer: ReturnType<typeof setInterval> | null = null;
@@ -24,6 +36,7 @@ export default function tabSpinner(pi: ExtensionAPI) {
24
36
  let hasActiveAgent = false;
25
37
  let pendingAgentEnd = false;
26
38
  let askUserActive = 0;
39
+ let reviewTitleState: ReviewTitleState = { kind: "none" };
27
40
  let currentCtx: ExtensionContext | undefined;
28
41
  let cachedSessionName: string | undefined;
29
42
  let cachedCwd: string | undefined;
@@ -34,6 +47,39 @@ export default function tabSpinner(pi: ExtensionAPI) {
34
47
  return formatTitle(getSessionNameSafe(), cachedCwd);
35
48
  }
36
49
 
50
+ /** Set the terminal title to the review-phase icon if one is active. */
51
+ function applyReviewTitle(): boolean {
52
+ if (timer) return false; // spinner is active, don't override
53
+
54
+ const baseTitle = title();
55
+ const icon = reviewTitleIcon();
56
+ if (!icon) return false;
57
+
58
+ safelySetTitle(`${icon} ${baseTitle}`);
59
+ return true;
60
+ }
61
+
62
+ /** Return the appropriate review title icon, or empty string when idle. */
63
+ function reviewTitleIcon(): string {
64
+ switch (reviewTitleState.kind) {
65
+ case "brief-done":
66
+ return BRIEF_DONE_ICON;
67
+ case "brief-failed":
68
+ return REVIEW_FAILED_ICON;
69
+ case "review-done":
70
+ return REVIEW_DONE_ICON;
71
+ case "review-failed":
72
+ return REVIEW_FAILED_ICON;
73
+ case "none":
74
+ return "";
75
+ }
76
+ }
77
+
78
+ /** Clear any active review title state and let normal title logic resume. */
79
+ function clearReviewTitle() {
80
+ reviewTitleState = { kind: "none" };
81
+ }
82
+
37
83
  function clearPendingAgentEnd() {
38
84
  pendingAgentEnd = false;
39
85
  if (pendingAgentEndTimer) {
@@ -85,24 +131,24 @@ export default function tabSpinner(pi: ExtensionAPI) {
85
131
  }
86
132
  }
87
133
 
88
- /** Restore the base title immediately. */
134
+ /** Restore the base title, or show the review icon if one is active. */
89
135
  function stop() {
90
136
  clearPendingAgentEnd();
91
137
  clearSpinnerTimer();
92
138
  frame = 0;
93
- const baseTitle = title();
94
- safelySetTitle(baseTitle);
139
+ if (applyReviewTitle()) return;
140
+ safelySetTitle(title());
95
141
  }
96
142
 
97
- /** Show the ✓ done symbol in the title and play the terminal bell. */
143
+ /** Show the ✓ done symbol, or the review icon if one is active. */
98
144
  function showDone() {
99
145
  clearPendingAgentEnd();
100
146
  clearSpinnerTimer();
101
147
  frame = 0;
102
- const baseTitle = title();
103
148
  if (!currentCtx) return;
149
+ if (applyReviewTitle()) return;
104
150
  try {
105
- signalDone(currentCtx, baseTitle);
151
+ signalDone(currentCtx, title());
106
152
  } catch {
107
153
  handleStaleContext();
108
154
  }
@@ -172,6 +218,7 @@ export default function tabSpinner(pi: ExtensionAPI) {
172
218
  });
173
219
 
174
220
  pi.on("agent_start", async (_event, ctx) => {
221
+ clearReviewTitle();
175
222
  if (pendingAgentEnd) {
176
223
  resumePendingAgent(ctx);
177
224
  return;
@@ -191,6 +238,7 @@ export default function tabSpinner(pi: ExtensionAPI) {
191
238
 
192
239
  pi.on("session_shutdown", async (_event, ctx) => {
193
240
  unregisterEvents();
241
+ clearReviewTitle();
194
242
  activeCount = 0;
195
243
  rememberContext(ctx);
196
244
  stop();
@@ -217,4 +265,22 @@ export default function tabSpinner(pi: ExtensionAPI) {
217
265
  if (askUserActive === 0 && activeCount > 0) start();
218
266
  }),
219
267
  );
268
+
269
+ unregisterBusHandlers.push(
270
+ pi.events.on("supi:review:brief-done", (payload: unknown) => {
271
+ const data = payload as { kind: string };
272
+ reviewTitleState =
273
+ data.kind === "success" ? { kind: "brief-done" } : { kind: "brief-failed" };
274
+ applyReviewTitle();
275
+ }),
276
+ );
277
+
278
+ unregisterBusHandlers.push(
279
+ pi.events.on("supi:review:review-done", (payload: unknown) => {
280
+ const data = payload as { kind: string };
281
+ reviewTitleState =
282
+ data.kind === "success" ? { kind: "review-done" } : { kind: "review-failed" };
283
+ applyReviewTitle();
284
+ }),
285
+ );
220
286
  }
@@ -1 +0,0 @@
1
- export { registerSettingsCommand as default } from "./settings/settings-command.ts";