@marckrenn/pi-sub-bar 1.0.2 → 1.0.4

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 (63) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/README.md +9 -6
  3. package/index.ts +219 -23
  4. package/node_modules/@marckrenn/pi-sub-core/CHANGELOG.md +68 -0
  5. package/node_modules/@marckrenn/pi-sub-core/README.md +178 -0
  6. package/node_modules/@marckrenn/pi-sub-core/index.ts +395 -0
  7. package/node_modules/@marckrenn/pi-sub-core/package.json +35 -0
  8. package/node_modules/@marckrenn/pi-sub-core/src/cache.ts +487 -0
  9. package/node_modules/@marckrenn/pi-sub-core/src/config.ts +35 -0
  10. package/node_modules/@marckrenn/pi-sub-core/src/dependencies.ts +37 -0
  11. package/node_modules/@marckrenn/pi-sub-core/src/errors.ts +71 -0
  12. package/node_modules/@marckrenn/pi-sub-core/src/paths.ts +55 -0
  13. package/node_modules/@marckrenn/pi-sub-core/src/provider.ts +66 -0
  14. package/node_modules/@marckrenn/pi-sub-core/src/providers/detection.ts +51 -0
  15. package/node_modules/@marckrenn/pi-sub-core/src/providers/impl/anthropic.ts +170 -0
  16. package/node_modules/@marckrenn/pi-sub-core/src/providers/impl/antigravity.ts +205 -0
  17. package/node_modules/@marckrenn/pi-sub-core/src/providers/impl/codex.ts +139 -0
  18. package/node_modules/@marckrenn/pi-sub-core/src/providers/impl/copilot.ts +172 -0
  19. package/node_modules/@marckrenn/pi-sub-core/src/providers/impl/gemini.ts +121 -0
  20. package/node_modules/@marckrenn/pi-sub-core/src/providers/impl/kiro.ts +92 -0
  21. package/node_modules/@marckrenn/pi-sub-core/src/providers/impl/zai.ts +117 -0
  22. package/node_modules/@marckrenn/pi-sub-core/src/providers/index.ts +5 -0
  23. package/node_modules/@marckrenn/pi-sub-core/src/providers/metadata.ts +16 -0
  24. package/node_modules/@marckrenn/pi-sub-core/src/providers/registry.ts +54 -0
  25. package/node_modules/@marckrenn/pi-sub-core/src/providers/settings.ts +109 -0
  26. package/node_modules/@marckrenn/pi-sub-core/src/providers/status.ts +25 -0
  27. package/node_modules/@marckrenn/pi-sub-core/src/settings/behavior.ts +58 -0
  28. package/node_modules/@marckrenn/pi-sub-core/src/settings/menu.ts +83 -0
  29. package/node_modules/@marckrenn/pi-sub-core/src/settings/tools.ts +38 -0
  30. package/node_modules/@marckrenn/pi-sub-core/src/settings/ui.ts +450 -0
  31. package/node_modules/@marckrenn/pi-sub-core/src/settings-types.ts +95 -0
  32. package/node_modules/@marckrenn/pi-sub-core/src/settings-ui.ts +1 -0
  33. package/node_modules/@marckrenn/pi-sub-core/src/settings.ts +137 -0
  34. package/node_modules/@marckrenn/pi-sub-core/src/status.ts +123 -0
  35. package/node_modules/@marckrenn/pi-sub-core/src/storage/lock.ts +60 -0
  36. package/node_modules/@marckrenn/pi-sub-core/src/storage.ts +61 -0
  37. package/node_modules/@marckrenn/pi-sub-core/src/types.ts +33 -0
  38. package/node_modules/@marckrenn/pi-sub-core/src/ui/settings-list.ts +290 -0
  39. package/node_modules/@marckrenn/pi-sub-core/src/usage/controller.ts +235 -0
  40. package/node_modules/@marckrenn/pi-sub-core/src/usage/fetch.ts +215 -0
  41. package/node_modules/@marckrenn/pi-sub-core/src/usage/types.ts +5 -0
  42. package/node_modules/@marckrenn/pi-sub-core/src/utils.ts +122 -0
  43. package/node_modules/@marckrenn/pi-sub-core/test/all.test.ts +5 -0
  44. package/node_modules/@marckrenn/pi-sub-core/test/cache.test.ts +96 -0
  45. package/node_modules/@marckrenn/pi-sub-core/test/controller.test.ts +101 -0
  46. package/node_modules/@marckrenn/pi-sub-core/test/detection.test.ts +24 -0
  47. package/node_modules/@marckrenn/pi-sub-core/test/helpers.ts +48 -0
  48. package/node_modules/@marckrenn/pi-sub-core/test/lock.test.ts +42 -0
  49. package/node_modules/@marckrenn/pi-sub-core/test/providers.test.ts +222 -0
  50. package/node_modules/@marckrenn/pi-sub-core/tsconfig.json +5 -0
  51. package/node_modules/@marckrenn/pi-sub-shared/README.md +58 -0
  52. package/node_modules/@marckrenn/pi-sub-shared/index.ts +212 -0
  53. package/node_modules/@marckrenn/pi-sub-shared/package.json +23 -0
  54. package/package.json +5 -4
  55. package/src/paths.ts +7 -0
  56. package/src/settings/display.ts +20 -6
  57. package/src/settings/menu.ts +17 -11
  58. package/src/settings/themes.ts +33 -2
  59. package/src/settings/ui.ts +238 -22
  60. package/src/settings-types.ts +19 -5
  61. package/src/settings.ts +30 -18
  62. package/src/share.ts +24 -5
  63. package/test/settings.test.ts +12 -5
package/CHANGELOG.md CHANGED
@@ -1,5 +1,48 @@
1
1
  # @marckrenn/pi-sub-bar
2
2
 
3
+ ## 1.0.4
4
+
5
+ ### Patch Changes
6
+
7
+ - [#28](https://github.com/marckrenn/pi-sub/pull/28) [`2e35657`](https://github.com/marckrenn/pi-sub/commit/2e3565776a98ec133537fe0bacbc099cd4afadbe) Thanks [@marckrenn](https://github.com/marckrenn)! - Default Anthropic “Show Extra Window” provider setting to off.
8
+
9
+ - [#29](https://github.com/marckrenn/pi-sub/pull/29) [`7e6b7a0`](https://github.com/marckrenn/pi-sub/commit/7e6b7a08d69cf1cd456d2add406ec89c5c86f5df) Thanks [@marckrenn](https://github.com/marckrenn)! - Split display padding into left/right settings and migrate legacy paddingX values to both sides.
10
+
11
+ - [#22](https://github.com/marckrenn/pi-sub/pull/22) [`3e5a026`](https://github.com/marckrenn/pi-sub/commit/3e5a026ea3dc113561ff32466a8aa03b91c6d876) Thanks [@marckrenn](https://github.com/marckrenn)! - Store sub-core and sub-bar settings in agent-level JSON files so updates no longer overwrite user configuration. Legacy extension `settings.json` files are migrated into the new files and removed after a successful migration.
12
+
13
+ Manual migration (if you want to do it yourself before updating):
14
+
15
+ ```
16
+ cp ~/.pi/agent/extensions/sub-core/settings.json ~/.pi/agent/pi-sub-core-settings.json
17
+ cp ~/.pi/agent/extensions/sub-bar/settings.json ~/.pi/agent/pi-sub-bar-settings.json
18
+ ```
19
+
20
+ Existing users should move legacy settings from the extension folders to:
21
+ - `~/.pi/agent/pi-sub-core-settings.json`
22
+ - `~/.pi/agent/pi-sub-bar-settings.json`
23
+
24
+ - [#23](https://github.com/marckrenn/pi-sub/pull/23) [`9c324fc`](https://github.com/marckrenn/pi-sub/commit/9c324fc7daae2a874816f600ac1ea422f3799dd2) Thanks [@marckrenn](https://github.com/marckrenn)! - Auto-post theme share strings when saving, add a “Share theme” menu entry, allow share strings without a name when importing, and post raw share strings (without the `/sub-bar:import` prefix).
25
+
26
+ - [#25](https://github.com/marckrenn/pi-sub/pull/25) [`ab97c8f`](https://github.com/marckrenn/pi-sub/commit/ab97c8f13c567c32581bb82fe5b0406b3f2464ca) Thanks [@marckrenn](https://github.com/marckrenn)! - Refine the theme menu ordering/labels and add rename support for saved themes in the Manage & Load flow.
27
+
28
+ - [#27](https://github.com/marckrenn/pi-sub/pull/27) [`549c6fe`](https://github.com/marckrenn/pi-sub/commit/549c6fe3eb57374a54bfc69ad70c91862250a186) Thanks [@marckrenn](https://github.com/marckrenn)! - Move Themes to the root settings menu, rename Display Settings to Adv. Display Settings, and rename “Manage & Load themes” to “Load & Manage themes”.
29
+
30
+ - [#26](https://github.com/marckrenn/pi-sub/pull/26) [`af28d98`](https://github.com/marckrenn/pi-sub/commit/af28d9820f80bc1d045783644afbcc4d7cd114f1) Thanks [@marckrenn](https://github.com/marckrenn)! - Prompt to upload theme share strings as secret GitHub gists and post the gist URL when accepted.
31
+
32
+ - Updated dependencies [[`af0828a`](https://github.com/marckrenn/pi-sub/commit/af0828a8d2e529497a1acff95e388a0a3eabb90e), [`3e5a026`](https://github.com/marckrenn/pi-sub/commit/3e5a026ea3dc113561ff32466a8aa03b91c6d876), [`a6c0d33`](https://github.com/marckrenn/pi-sub/commit/a6c0d33c8d19d2876a4a8a1a0a69302a3c63f5e8), [`7da1e08`](https://github.com/marckrenn/pi-sub/commit/7da1e082e634f4e4dee2560b4d490527d1543ade), [`1f5e451`](https://github.com/marckrenn/pi-sub/commit/1f5e45173b9868b0d6645ae35a084142a0ac56a5), [`35eb185`](https://github.com/marckrenn/pi-sub/commit/35eb18590f369db4cda931b8e11099d0f3ddb4ec)]:
33
+ - @marckrenn/pi-sub-core@1.0.4
34
+ - @marckrenn/pi-sub-shared@1.0.4
35
+
36
+ ## 1.0.3
37
+
38
+ ### Patch Changes
39
+
40
+ - [`6fa2736`](https://github.com/marckrenn/pi-sub/commit/6fa27363573f34c38a372a6d7b8b74e756716724) Thanks [@marckrenn](https://github.com/marckrenn)! - Update extension tool execute signature order for compatibility with latest Pi API.
41
+
42
+ - Updated dependencies [[`6fa2736`](https://github.com/marckrenn/pi-sub/commit/6fa27363573f34c38a372a6d7b8b74e756716724)]:
43
+ - @marckrenn/pi-sub-core@1.0.3
44
+ - @marckrenn/pi-sub-shared@1.0.3
45
+
3
46
  ## 1.0.2
4
47
 
5
48
  ### Patch Changes
package/README.md CHANGED
@@ -51,7 +51,7 @@ https://github.com/user-attachments/assets/d61d82f6-afd0-45fc-82f3-69910543aa7a
51
51
 
52
52
  ## Installation
53
53
 
54
- Install via the pi package manager (recommended). `sub-bar` bundles `sub-core`, so you only need to install `sub-bar`:
54
+ Install via the pi package manager (recommended). `sub-bar` bundles `sub-core`, so you only need to install sub-bar:
55
55
 
56
56
  ```bash
57
57
  pi install npm:@marckrenn/pi-sub-bar
@@ -63,6 +63,8 @@ Use `-l` to install into project settings instead of global:
63
63
  pi install -l npm:@marckrenn/pi-sub-bar
64
64
  ```
65
65
 
66
+ If you previously installed `sub-core` separately, remove it from `~/.pi/agent/extensions` or `~/.pi/agent/settings.json` to avoid duplicate core instances.
67
+
66
68
  Manual install (local development):
67
69
 
68
70
  ```bash
@@ -73,9 +75,9 @@ npm install
73
75
  ln -s /path/to/pi-sub/packages/sub-bar ~/.pi/agent/extensions/sub-bar
74
76
  ```
75
77
 
76
- If you want to develop `sub-core` separately, also symlink `packages/sub-core` into `~/.pi/agent/extensions`.
78
+ For local development, also ensure sub-core is available (either link it separately or link it into `packages/sub-bar/node_modules`).
77
79
 
78
- Alternative (no symlink): add `sub-bar` to `~/.pi/agent/settings.json`:
80
+ Alternative (no symlink): add sub-bar to `~/.pi/agent/settings.json`:
79
81
 
80
82
  ```json
81
83
  {
@@ -96,7 +98,7 @@ The extension loads automatically. Use:
96
98
  - `Ctrl+Alt+R` - Toggle reset timer format (relative vs datetime)
97
99
 
98
100
  **Caching:**
99
- - Handled by sub-core at `cache.json` next to the sub-core extension entry
101
+ - Handled by sub-core at `~/.pi/agent/cache/sub-core/cache.json`
100
102
  - Cache TTL matches your auto-refresh interval setting
101
103
  - Lock file prevents race conditions between multiple pi windows
102
104
 
@@ -108,9 +110,9 @@ The extension loads automatically. Use:
108
110
 
109
111
  ## Settings
110
112
 
111
- Display and provider UI settings are persisted next to the extension entry (`settings.json` in the same folder as `index.ts`). Core settings are managed by sub-core, and the sub-bar settings menu includes a shortcut that points you to `sub-core:settings` for additional options.
113
+ Display and provider UI settings are stored in `~/.pi/agent/pi-sub-bar-settings.json` (migrated from the legacy extension `settings.json` when present; the legacy file is removed after a successful migration). Core settings are managed by sub-core, and the sub-bar settings menu includes a shortcut that points you to `sub-core:settings` for additional options.
112
114
 
113
- **Settings migrations:** settings are merged with defaults on load, but renames/removals are not migrated automatically. When adding new settings or changing schema, update the defaults/merge logic and provide a migration (or instruct users to reset `settings.json`).
115
+ **Settings migrations:** settings are merged with defaults on load, but renames/removals are not migrated automatically. When adding new settings or changing schema, update the defaults/merge logic and provide a migration (or instruct users to reset `pi-sub-bar-settings.json`).
114
116
 
115
117
  ### Provider UI Settings
116
118
 
@@ -142,6 +144,7 @@ Credentials are loaded by sub-core from:
142
144
  Pi packages use a `pi` field in `package.json` plus the `pi-package` keyword for discoverability. This repo already declares `pi.extensions`, so you can install via:
143
145
 
144
146
  ```bash
147
+ pi install npm:@marckrenn/pi-sub-core
145
148
  pi install npm:@marckrenn/pi-sub-bar
146
149
  ```
147
150
 
package/index.ts CHANGED
@@ -7,7 +7,7 @@
7
7
  import type { ExtensionAPI, ExtensionContext, Theme, ThemeColor } from "@mariozechner/pi-coding-agent";
8
8
  import { Container, Input, SelectList, Spacer, Text, truncateToWidth, wrapTextWithAnsi, visibleWidth } from "@mariozechner/pi-tui";
9
9
  import * as fs from "node:fs";
10
- import { homedir } from "node:os";
10
+ import { homedir, tmpdir } from "node:os";
11
11
  import { join } from "node:path";
12
12
  import type { ProviderName, ProviderUsageEntry, SubCoreAllState, SubCoreState, UsageSnapshot } from "./src/types.js";
13
13
  import type { Settings, BaseTextColor } from "./src/settings-types.js";
@@ -128,6 +128,59 @@ export default function createExtension(pi: ExtensionAPI) {
128
128
  let settingsSnapshot = "";
129
129
  let settingsMtimeMs = 0;
130
130
  let settingsWatchStarted = false;
131
+ let subCoreBootstrapAttempted = false;
132
+
133
+ async function probeSubCore(timeoutMs = 200): Promise<boolean> {
134
+ return new Promise((resolve) => {
135
+ let resolved = false;
136
+ const timer = setTimeout(() => {
137
+ if (!resolved) {
138
+ resolved = true;
139
+ resolve(false);
140
+ }
141
+ }, timeoutMs);
142
+
143
+ const request: SubCoreRequest = {
144
+ type: "current",
145
+ reply: () => {
146
+ if (resolved) return;
147
+ resolved = true;
148
+ clearTimeout(timer);
149
+ resolve(true);
150
+ },
151
+ };
152
+ pi.events.emit("sub-core:request", request);
153
+ });
154
+ }
155
+
156
+ async function ensureSubCoreLoaded(): Promise<void> {
157
+ if (subCoreBootstrapAttempted) return;
158
+ subCoreBootstrapAttempted = true;
159
+ const hasCore = await probeSubCore();
160
+ if (hasCore) return;
161
+ try {
162
+ const bundledUrl = new URL("./node_modules/@marckrenn/pi-sub-core/index.ts", import.meta.url);
163
+ const module = await import(bundledUrl.toString());
164
+ const createCore = module.default as undefined | ((api: ExtensionAPI) => void | Promise<void>);
165
+ if (typeof createCore === "function") {
166
+ void createCore(pi);
167
+ return;
168
+ }
169
+ } catch {
170
+ // Fall back to package resolution
171
+ }
172
+ try {
173
+ const module = await import("@marckrenn/pi-sub-core");
174
+ const createCore = module.default as undefined | ((api: ExtensionAPI) => void | Promise<void>);
175
+ if (typeof createCore === "function") {
176
+ void createCore(pi);
177
+ }
178
+ } catch (error) {
179
+ console.warn("Failed to auto-load sub-core:", error);
180
+ }
181
+ }
182
+
183
+ void ensureSubCoreLoaded();
131
184
 
132
185
  async function promptImportAction(ctx: ExtensionContext): Promise<"save-apply" | "save" | "cancel"> {
133
186
  return new Promise((resolve) => {
@@ -171,7 +224,7 @@ export default function createExtension(pi: ExtensionAPI) {
171
224
  resolve(undefined);
172
225
  };
173
226
  const container = new Container();
174
- container.addChild(new Text(theme.fg("muted", "Share string"), 1, 0));
227
+ container.addChild(new Text(theme.fg("muted", "Paste Theme Share string"), 1, 0));
175
228
  container.addChild(new Spacer(1));
176
229
  container.addChild(input);
177
230
  return {
@@ -183,6 +236,141 @@ export default function createExtension(pi: ExtensionAPI) {
183
236
  });
184
237
  }
185
238
 
239
+ async function promptImportName(ctx: ExtensionContext): Promise<string | undefined> {
240
+ while (true) {
241
+ const name = await ctx.ui.input("Theme name", "Theme");
242
+ if (name === undefined) return undefined;
243
+ const trimmed = name.trim();
244
+ if (trimmed) return trimmed;
245
+ ctx.ui.notify("Enter a theme name", "warning");
246
+ }
247
+ }
248
+
249
+ const THEME_GIST_FILE_BASE = "pi-sub-bar Theme";
250
+ const THEME_GIST_STATUS_KEY = "sub-bar:share";
251
+
252
+ function buildThemeGistFileName(name: string): string {
253
+ const trimmed = name.trim();
254
+ if (!trimmed) return THEME_GIST_FILE_BASE;
255
+ const safeName = trimmed.replace(/[\\/:*?"<>|]+/g, "-").trim();
256
+ return safeName ? `${THEME_GIST_FILE_BASE} ${safeName}` : THEME_GIST_FILE_BASE;
257
+ }
258
+
259
+ async function createThemeGist(ctx: ExtensionContext, name: string, shareString: string): Promise<string | null> {
260
+ const notify = (message: string, level: "info" | "warning" | "error") => {
261
+ if (ctx.hasUI) {
262
+ ctx.ui.notify(message, level);
263
+ return;
264
+ }
265
+ if (level === "error") {
266
+ console.error(message);
267
+ } else if (level === "warning") {
268
+ console.warn(message);
269
+ } else {
270
+ console.log(message);
271
+ }
272
+ };
273
+
274
+ try {
275
+ const authResult = await pi.exec("gh", ["auth", "status"]);
276
+ if (authResult.code !== 0) {
277
+ notify("GitHub CLI is not logged in. Run 'gh auth login' first.", "error");
278
+ return null;
279
+ }
280
+ } catch {
281
+ notify("GitHub CLI (gh) is not installed. Install it from https://cli.github.com/", "error");
282
+ return null;
283
+ }
284
+
285
+ const tempDir = fs.mkdtempSync(join(tmpdir(), "pi-sub-bar-"));
286
+ const fileName = buildThemeGistFileName(name);
287
+ const filePath = join(tempDir, fileName);
288
+ fs.writeFileSync(filePath, shareString, "utf-8");
289
+
290
+ if (ctx.hasUI) {
291
+ ctx.ui.setStatus(THEME_GIST_STATUS_KEY, "Creating gist...");
292
+ }
293
+
294
+ try {
295
+ const result = await pi.exec("gh", ["gist", "create", "--public=false", filePath]);
296
+ if (result.code !== 0) {
297
+ const errorMsg = result.stderr?.trim() || "Unknown error";
298
+ notify(`Failed to create gist: ${errorMsg}`, "error");
299
+ return null;
300
+ }
301
+ const gistUrl = result.stdout?.trim();
302
+ if (!gistUrl) {
303
+ notify("Failed to create gist: empty response", "error");
304
+ return null;
305
+ }
306
+ return gistUrl;
307
+ } catch (error) {
308
+ notify(`Failed to create gist: ${error instanceof Error ? error.message : "Unknown error"}`, "error");
309
+ return null;
310
+ } finally {
311
+ if (ctx.hasUI) {
312
+ ctx.ui.setStatus(THEME_GIST_STATUS_KEY, undefined);
313
+ }
314
+ try {
315
+ fs.rmSync(tempDir, { recursive: true, force: true });
316
+ } catch {
317
+ // Ignore cleanup errors
318
+ }
319
+ }
320
+ }
321
+
322
+ async function shareThemeString(
323
+ ctx: ExtensionContext,
324
+ name: string,
325
+ shareString: string,
326
+ mode: "prompt" | "gist" | "string" = "prompt",
327
+ ): Promise<void> {
328
+ const trimmedName = name.trim();
329
+ const notify = (message: string, level: "info" | "warning" | "error") => {
330
+ if (ctx.hasUI) {
331
+ ctx.ui.notify(message, level);
332
+ return;
333
+ }
334
+ if (level === "error") {
335
+ console.error(message);
336
+ } else if (level === "warning") {
337
+ console.warn(message);
338
+ } else {
339
+ console.log(message);
340
+ }
341
+ };
342
+ let resolvedMode = mode;
343
+ if (resolvedMode === "prompt") {
344
+ if (!ctx.hasUI) {
345
+ resolvedMode = "string";
346
+ } else {
347
+ const wantsGist = await ctx.ui.confirm("Share Theme", "Upload to a secret GitHub gist?");
348
+ resolvedMode = wantsGist ? "gist" : "string";
349
+ }
350
+ }
351
+
352
+ if (resolvedMode === "gist") {
353
+ const gistUrl = await createThemeGist(ctx, trimmedName, shareString);
354
+ if (gistUrl) {
355
+ pi.sendMessage({
356
+ customType: "sub-bar",
357
+ content: `Theme gist:\n${gistUrl}`,
358
+ display: true,
359
+ });
360
+ notify("Theme gist posted to chat", "info");
361
+ return;
362
+ }
363
+ notify("Posting share string instead.", "warning");
364
+ }
365
+
366
+ pi.sendMessage({
367
+ customType: "sub-bar",
368
+ content: `Theme share string:\n${shareString}`,
369
+ display: true,
370
+ });
371
+ notify("Theme share string posted to chat", "info");
372
+ }
373
+
186
374
  function readSettingsFile(): string | undefined {
187
375
  try {
188
376
  return fs.readFileSync(SETTINGS_PATH, "utf-8");
@@ -273,8 +461,9 @@ export default function createExtension(pi: ExtensionAPI) {
273
461
  (_tui: unknown, theme: Theme) => ({
274
462
  render(width: number) {
275
463
  const safeWidth = Math.max(1, width);
276
- const paddingX = settings.display.paddingX ?? 0;
277
- const contentWidth = Math.max(1, safeWidth - paddingX * 2);
464
+ const paddingLeft = settings.display.paddingLeft ?? 0;
465
+ const paddingRight = settings.display.paddingRight ?? 0;
466
+ const contentWidth = Math.max(1, safeWidth - paddingLeft - paddingRight);
278
467
  const showTopDivider = settings.display.showTopDivider ?? false;
279
468
  const showBottomDivider = settings.display.showBottomDivider ?? true;
280
469
  const dividerChar = settings.display.dividerCharacter ?? "•";
@@ -315,9 +504,10 @@ export default function createExtension(pi: ExtensionAPI) {
315
504
  lines = [trimmed];
316
505
  }
317
506
 
318
- if (paddingX > 0) {
319
- const pad = " ".repeat(paddingX);
320
- lines = lines.map((line) => pad + line + pad);
507
+ if (paddingLeft > 0 || paddingRight > 0) {
508
+ const leftPad = " ".repeat(paddingLeft);
509
+ const rightPad = " ".repeat(paddingRight);
510
+ lines = lines.map((line) => `${leftPad}${line}${rightPad}`);
321
511
  }
322
512
 
323
513
  if (showTopDivider) {
@@ -593,20 +783,14 @@ export default function createExtension(pi: ExtensionAPI) {
593
783
  onDisplayThemeApplied: (name, options) => {
594
784
  const content = options?.source === "manual"
595
785
  ? `sub-bar Theme ${name} loaded`
596
- : `sub-bar Theme ${name} loaded / applied / saved. Restore settings in /sub-bar:settings -> Display Settings -> Theme -> Manage themes`;
786
+ : `sub-bar Theme ${name} loaded / applied / saved. Restore settings in /sub-bar:settings -> Themes -> Load & Manage themes`;
597
787
  pi.sendMessage({
598
788
  customType: "sub-bar",
599
789
  content,
600
790
  display: true,
601
791
  });
602
792
  },
603
- onDisplayThemeShared: (_name, shareString) => {
604
- pi.sendMessage({
605
- customType: "sub-bar",
606
- content: `Theme share string:\n/sub-bar:import ${shareString}`,
607
- display: true,
608
- });
609
- },
793
+ onDisplayThemeShared: (name, shareString, mode) => shareThemeString(ctx, name, shareString, mode ?? "prompt"),
610
794
  });
611
795
  settings = newSettings;
612
796
  void ensurePinnedEntries(settings.pinnedProvider ?? null);
@@ -642,35 +826,47 @@ export default function createExtension(pi: ExtensionAPI) {
642
826
  }
643
827
 
644
828
  const action = await promptImportAction(ctx);
645
- const notifyImported = () => {
829
+ let resolvedName = decoded.name;
830
+ if ((action === "save-apply" || action === "save") && !decoded.hasName) {
831
+ const providedName = await promptImportName(ctx);
832
+ if (!providedName) {
833
+ settings.display = { ...backup };
834
+ if (lastContext) {
835
+ renderUsageWidget(lastContext, currentUsage);
836
+ }
837
+ return;
838
+ }
839
+ resolvedName = providedName;
840
+ }
841
+ const notifyImported = (name: string) => {
646
842
  const message = decoded.isNewerVersion
647
- ? `Imported ${decoded.name} (newer version, some fields may be ignored)`
648
- : `Imported ${decoded.name}`;
843
+ ? `Imported ${name} (newer version, some fields may be ignored)`
844
+ : `Imported ${name}`;
649
845
  ctx.ui.notify(message, decoded.isNewerVersion ? "warning" : "info");
650
846
  };
651
847
 
652
848
  if (action === "save-apply") {
653
849
  settings.displayUserTheme = { ...backup };
654
- settings = upsertDisplayTheme(settings, decoded.name, decoded.display, "imported");
850
+ settings = upsertDisplayTheme(settings, resolvedName, decoded.display, "imported");
655
851
  settings.display = { ...decoded.display };
656
852
  saveSettings(settings);
657
853
  if (lastContext) {
658
854
  renderUsageWidget(lastContext, currentUsage);
659
855
  }
660
- notifyImported();
856
+ notifyImported(resolvedName);
661
857
  pi.sendMessage({
662
858
  customType: "sub-bar",
663
- content: `sub-bar Theme ${decoded.name} loaded`,
859
+ content: `sub-bar Theme ${resolvedName} loaded`,
664
860
  display: true,
665
861
  });
666
862
  return;
667
863
  }
668
864
 
669
865
  if (action === "save") {
670
- settings = upsertDisplayTheme(settings, decoded.name, decoded.display, "imported");
866
+ settings = upsertDisplayTheme(settings, resolvedName, decoded.display, "imported");
671
867
  settings.display = { ...backup };
672
868
  saveSettings(settings);
673
- notifyImported();
869
+ notifyImported(resolvedName);
674
870
  if (lastContext) {
675
871
  renderUsageWidget(lastContext, currentUsage);
676
872
  }
@@ -0,0 +1,68 @@
1
+ # @marckrenn/pi-sub-core
2
+
3
+ ## 1.0.4
4
+
5
+ ### Patch Changes
6
+
7
+ - [#30](https://github.com/marckrenn/pi-sub/pull/30) [`af0828a`](https://github.com/marckrenn/pi-sub/commit/af0828a8d2e529497a1acff95e388a0a3eabb90e) Thanks [@marckrenn](https://github.com/marckrenn)! - Store the shared cache and lock files in the agent directory so all sub-core instances share a single cache.
8
+
9
+ - [#22](https://github.com/marckrenn/pi-sub/pull/22) [`3e5a026`](https://github.com/marckrenn/pi-sub/commit/3e5a026ea3dc113561ff32466a8aa03b91c6d876) Thanks [@marckrenn](https://github.com/marckrenn)! - Store sub-core and sub-bar settings in agent-level JSON files so updates no longer overwrite user configuration. Legacy extension `settings.json` files are migrated into the new files and removed after a successful migration.
10
+
11
+ Manual migration (if you want to do it yourself before updating):
12
+
13
+ ```
14
+ cp ~/.pi/agent/extensions/sub-core/settings.json ~/.pi/agent/pi-sub-core-settings.json
15
+ cp ~/.pi/agent/extensions/sub-bar/settings.json ~/.pi/agent/pi-sub-bar-settings.json
16
+ ```
17
+
18
+ Existing users should move legacy settings from the extension folders to:
19
+ - `~/.pi/agent/pi-sub-core-settings.json`
20
+ - `~/.pi/agent/pi-sub-bar-settings.json`
21
+
22
+ - [`a6c0d33`](https://github.com/marckrenn/pi-sub/commit/a6c0d33c8d19d2876a4a8a1a0a69302a3c63f5e8) Thanks [@marckrenn](https://github.com/marckrenn)! - Move the shared cache/lock files under `~/.pi/agent/cache/sub-core` so all clients share a single cache directory.
23
+
24
+ - [`7da1e08`](https://github.com/marckrenn/pi-sub/commit/7da1e082e634f4e4dee2560b4d490527d1543ade) Thanks [@marckrenn](https://github.com/marckrenn)! - Add a minimum refresh interval setting to cap refresh frequency even when refresh is triggered every turn.
25
+
26
+ - [`1f5e451`](https://github.com/marckrenn/pi-sub/commit/1f5e45173b9868b0d6645ae35a084142a0ac56a5) Thanks [@marckrenn](https://github.com/marckrenn)! - Gate tool registration behind `tools.usageTool` and `tools.allUsageTool` (default off) in sub-core settings.
27
+
28
+ - [`35eb185`](https://github.com/marckrenn/pi-sub/commit/35eb18590f369db4cda931b8e11099d0f3ddb4ec) Thanks [@marckrenn](https://github.com/marckrenn)! - Add usage tool aliases `get_current_usage` and `get_all_usage`.
29
+
30
+ - Updated dependencies [[`7da1e08`](https://github.com/marckrenn/pi-sub/commit/7da1e082e634f4e4dee2560b4d490527d1543ade)]:
31
+ - @marckrenn/pi-sub-shared@1.0.4
32
+
33
+ ## 1.0.3
34
+
35
+ ### Patch Changes
36
+
37
+ - [`6fa2736`](https://github.com/marckrenn/pi-sub/commit/6fa27363573f34c38a372a6d7b8b74e756716724) Thanks [@marckrenn](https://github.com/marckrenn)! - Update extension tool execute signature order for compatibility with latest Pi API.
38
+
39
+ - Updated dependencies [[`6fa2736`](https://github.com/marckrenn/pi-sub/commit/6fa27363573f34c38a372a6d7b8b74e756716724)]:
40
+ - @marckrenn/pi-sub-shared@1.0.3
41
+
42
+ ## 1.0.2
43
+
44
+ ### Patch Changes
45
+
46
+ - [#3](https://github.com/marckrenn/pi-sub/pull/3) [`4ceb5ad`](https://github.com/marckrenn/pi-sub/commit/4ceb5ad133166237652d197ba9296ad1589a813c) Thanks [@marckrenn](https://github.com/marckrenn)! - Bundle sub-core with sub-bar, refresh Antigravity quotas + settings, and update UI copy/controls.
47
+
48
+ - Updated dependencies [[`4ceb5ad`](https://github.com/marckrenn/pi-sub/commit/4ceb5ad133166237652d197ba9296ad1589a813c)]:
49
+ - @marckrenn/pi-sub-shared@1.0.2
50
+
51
+ ## 1.0.1
52
+
53
+ ### Patch Changes
54
+
55
+ - Align repo version with npm publish.
56
+ - Updated dependencies:
57
+ - @marckrenn/pi-sub-shared@1.0.1
58
+
59
+ ## 1.0.0
60
+
61
+ ### Major Changes
62
+
63
+ - [`9bedd80`](https://github.com/marckrenn/pi-sub/commit/9bedd80b0037b723e70f0376019fff59a617e7cb) Thanks [@marckrenn](https://github.com/marckrenn)! - Initial 1.0.0 release.
64
+
65
+ ### Patch Changes
66
+
67
+ - Updated dependencies [[`9bedd80`](https://github.com/marckrenn/pi-sub/commit/9bedd80b0037b723e70f0376019fff59a617e7cb)]:
68
+ - @marckrenn/pi-sub-shared@1.0.0
@@ -0,0 +1,178 @@
1
+ # sub-core
2
+
3
+ Shared usage data core for pi extensions. Sub-core owns fetching, caching, provider selection, and emits usage updates via `pi.events` for the wider `sub-*` ecosystem (UI and non-UI clients).
4
+
5
+ ## Overview
6
+
7
+ - Fetches usage + status data from providers
8
+ - Deduplicates requests via shared cache/lock
9
+ - Emits updates for display-focused extensions (e.g. `sub-bar`) and non-UI tooling extensions
10
+ - Supports Antigravity usage via auth.json (`google-antigravity`)
11
+
12
+ ## Installation
13
+
14
+ Install via the pi package manager (recommended):
15
+
16
+ ```bash
17
+ pi install npm:@marckrenn/pi-sub-core
18
+ ```
19
+
20
+ Use `-l` to install into project settings instead of global:
21
+
22
+ ```bash
23
+ pi install -l npm:@marckrenn/pi-sub-core
24
+ ```
25
+
26
+ For a UI, also install a display extension like `sub-bar` from the same repo (see the root README for the full setup).
27
+
28
+ Manual install (local development):
29
+
30
+ ```bash
31
+ git clone https://github.com/marckrenn/pi-sub.git
32
+ ln -s /path/to/pi-sub/packages/sub-core ~/.pi/agent/extensions/sub-core
33
+ ```
34
+
35
+ Alternative (no symlink): add it to `~/.pi/agent/settings.json`:
36
+
37
+ ```json
38
+ {
39
+ "extensions": ["/path/to/pi-sub/packages/sub-core/index.ts"]
40
+ }
41
+ ```
42
+
43
+ ## Tool Access
44
+
45
+ Tool registration is gated by `tools` in `~/.pi/agent/pi-sub-core-settings.json`.
46
+ By default, both tools are **off**. To enable them, set:
47
+
48
+ ```json
49
+ {
50
+ "tools": {
51
+ "usageTool": true,
52
+ "allUsageTool": true
53
+ }
54
+ }
55
+ ```
56
+
57
+ Then run `/reload` (tool registration only happens on load). You can also toggle these in `/sub-core:settings` → Tool Settings.
58
+
59
+ When enabled, `sub-core` registers tools to expose usage snapshots to Pi:
60
+
61
+ - `sub_get_usage` / `get_current_usage` – refreshes usage (forced by default) and returns `{ provider, usage }`.
62
+ - `sub_get_all_usage` / `get_all_usage` – refreshes and returns all enabled provider entries (auto-enabled providers require credentials).
63
+
64
+ ## Settings
65
+
66
+ Use `sub-core:settings` to configure shared provider settings plus **Usage Refresh Settings** and **Status Refresh Settings**. Provider enablement supports `auto` (default), `on`, and `off` — `auto` enables a provider only when credentials are detected.
67
+
68
+ Usage refresh controls cache/usage updates, while status refresh controls incident polling (you can keep status on a slower interval). The Minimum Refresh Interval caps how often refresh triggers can fetch new data even if you refresh every turn.
69
+
70
+ Antigravity usage requires an OAuth token in `~/.pi/agent/auth.json` under the `google-antigravity` key.
71
+
72
+ Anthropic extra usage formatting is controlled in Provider Settings (currency symbol + decimal separator).
73
+
74
+ Settings are stored in `~/.pi/agent/pi-sub-core-settings.json` (migrated from the legacy extension `settings.json` when present; the legacy file is removed after a successful migration).
75
+
76
+ **Settings migrations:** settings are merged with defaults on load, but renames/removals are not migrated automatically. When adding new settings or changing schema, update the defaults/merge logic and provide a migration (or instruct users to reset `pi-sub-core-settings.json`).
77
+
78
+ ## Cache
79
+
80
+ Sub-core stores a shared cache and lock file:
81
+
82
+ - `~/.pi/agent/cache/sub-core/cache.json`
83
+ - `~/.pi/agent/cache/sub-core/cache.lock`
84
+
85
+ Legacy cache files next to the extension entry or in the agent root are migrated to the cache directory and removed on first run.
86
+
87
+ ## Security notes
88
+
89
+ - Keep `~/.pi/agent/auth.json` readable only by your user (e.g. `chmod 600 ~/.pi/agent/auth.json`).
90
+ - Avoid logging token-bearing headers or auth config when troubleshooting provider calls.
91
+
92
+ ## Provider comparison
93
+
94
+ | Provider | Usage Data | Status Page | Notes |
95
+ |----------|-----------|-------------|-------|
96
+ | Anthropic (Claude) | 5h/Week windows, extra usage | ✅ | Extra usage on/off state |
97
+ | GitHub Copilot | Monthly quota, requests | ✅ | Request multiplier support |
98
+ | Google Gemini | Pro/Flash quotas | ✅ | Aggregated by model family |
99
+ | Antigravity | Model quotas | ✅ | Sandbox Cloud Code Assist quotas (tested) |
100
+ | OpenAI Codex | Primary/secondary windows | ✅ | Credits not yet supported (PRs welcome!) |
101
+ | AWS Kiro | Credits | - | Credits not yet supported (PRs welcome!) |
102
+ | z.ai | Tokens/monthly limits | - | API quota limits |
103
+
104
+ ## Development
105
+
106
+ ### Packaging notes (pi install compatibility)
107
+
108
+ Pi packages use a `pi` field in `package.json` plus the `pi-package` keyword for discoverability. This repo already declares `pi.extensions`, so you can install via:
109
+
110
+ ```bash
111
+ pi install npm:@marckrenn/pi-sub-core
112
+ ```
113
+
114
+ Manual paths/symlinks still work for local development as documented above.
115
+
116
+ ### Tested providers
117
+
118
+ Tested so far: Anthropic (Claude), OpenAI Codex, GitHub Copilot. Other providers are implemented but not yet verified in production.
119
+
120
+ ### Adding a Provider
121
+
122
+ You need to update both **sub-core** (fetch layer) and **sub-bar** (display layer).
123
+
124
+ ### Feature placement (core vs UI)
125
+
126
+ - **sub-core**: fetching, caching, provider detection/selection, status polling, tools/events, and shared settings.
127
+ - **sub-bar**: formatting, widget layout, UI-only toggles, and display-specific behavior.
128
+ - **sub-shared**: shared types/constants for anything referenced by both layers.
129
+
130
+ See the root README “Developer guide” for the decision checklist and examples.
131
+
132
+ #### sub-core (fetch + status)
133
+ 1. Add provider name to `src/types.ts` (`PROVIDERS`, `ProviderName`).
134
+ 2. Implement fetcher in `src/providers/impl/<provider>.ts`.
135
+ 3. Register provider in `src/providers/registry.ts`.
136
+ 4. Add detection + status config in `src/providers/metadata.ts`.
137
+ 5. Add provider settings defaults in `src/settings-types.ts`.
138
+
139
+ #### sub-bar (display + UI)
140
+ 1. Add provider name to `src/types.ts`.
141
+ 2. Add display rules + labels in `src/providers/metadata.ts`.
142
+ 3. Add window visibility in `src/providers/windows.ts`.
143
+ 4. Add extras in `src/providers/extras.ts` (if needed).
144
+ 5. Add settings UI + defaults in `src/providers/settings.ts` and `src/settings-types.ts`.
145
+
146
+ ### Events (public contract)
147
+
148
+ Sub-core uses `pi.events` as an in-process pub/sub bus. Any `sub-*` extension can subscribe to updates (UI or headless). Sub-core is the source of truth for provider selection and refresh behavior; clients observe state and optionally request changes.
149
+
150
+ #### Broadcasts
151
+ - `sub-core:ready` → `{ state, settings }` (first load)
152
+ - `sub-core:update-current` → `{ state }` (cache hit or fresh fetch)
153
+ - `sub-core:update-all` → `{ state }` (cached entries + current provider)
154
+ - `sub-core:settings:updated` → `{ settings }`
155
+
156
+ `update-current` state is `{ provider, usage }`.
157
+ `update-all` state is `{ provider, entries }`, where entries are cached provider snapshots.
158
+
159
+ #### Requests (pull current state)
160
+ - `sub-core:request` → `{ reply, includeSettings? }`
161
+ - `sub-core:request` → `{ type: "entries", reply, force? }` (bulk usage entries)
162
+
163
+ The `reply` callback receives `{ state }` or `{ entries }` immediately if available.
164
+
165
+ #### Actions (mutate core state)
166
+ - `sub-core:settings:patch` → `{ patch }` (updates refresh interval/provider settings and persists)
167
+ - `sub-core:action` → `{ type: "refresh" | "cycleProvider", force? }`
168
+
169
+ After an action, sub-core emits `sub-core:update-current` with the new state.
170
+
171
+ ## Credits
172
+
173
+ - Hannes Januschka ([barts](https://github.com/hjanuschka/shitty-extensions?tab=readme-ov-file#usage-barts), [@hjanuschka](https://x.com/hjanuschka))
174
+ - Peter Steinberger ([CodexBar](https://github.com/steipete/CodexBar), [@steipete](https://x.com/steipete))
175
+
176
+ ## Status
177
+
178
+ Active. Used by `sub-bar` for display.