@firstpick/pi-package-webui 0.1.4 → 0.1.5

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
@@ -17,6 +17,22 @@ This package provides:
17
17
  - Pi available through this package dependency or as `pi` on `PATH`
18
18
  - A modern browser with Server-Sent Events support
19
19
 
20
+ ## Optional companion packages
21
+
22
+ The Web UI declares its companion Pi packages as npm `optionalDependencies`. A normal npm/Pi install will install them, while minimal installs can skip them with npm's optional-dependency controls such as `npm install --omit=optional`.
23
+
24
+ At startup, the browser checks loaded Pi capabilities directly through RPC-visible commands and live widget events; it does not inspect npm package folders. That means locally symlinked/dev packages and separately installed Pi packages work as long as their commands/widgets are loaded in the active Pi tab.
25
+
26
+ The side panel shows each optional feature as enabled, disabled, or install-needed. Disabling a feature is Web UI-local and hides Web UI affordances/specialized renderers without uninstalling or unloading the underlying Pi package. Installing a missing feature is an explicit, warned action: the server runs npm install for the whitelisted package from localhost only, then prompts you to `/reload` the active Pi tab so newly installed resources can load.
27
+
28
+ Optional companions:
29
+
30
+ - `@firstpick/pi-prompts-git-pr` for the guided Git workflow's `/git-staged-msg` prompt.
31
+ - `@firstpick/pi-extension-release-npm` and `@firstpick/pi-extension-release-aur` for Publish menu commands and live release widgets.
32
+ - `@firstpick/pi-extension-todo-progress` for the specialized todo-progress widget.
33
+ - `@firstpick/pi-extension-git-footer-status` and `@firstpick/pi-extension-stats` for richer Pi status/footer and stats commands.
34
+ - `@firstpick/pi-themes-bundle` for theme resources used by the browser theme picker and Pi themes.
35
+
20
36
  ## Quick start
21
37
 
22
38
  Install the package from npm into Pi, then restart Pi so `/webui-start` (also available as `/start-webui`) and `/webui-status` are loaded:
@@ -67,7 +83,7 @@ pi-webui --cwd /path/to/project
67
83
  - Feedback reactions (`👍`, `👎`, `?`) on final assistant output plus tool/bash action cards, with queued post-run submission that asks Pi to create/update a LEARNING
68
84
  - Basic extension UI bridge for `notify`, `setStatus`, `setWidget`, `setTitle`, `set_editor_text`, `select`, `confirm`, `input`, and `editor`
69
85
  - Specialized `/release-npm` and `/release-aur` widget rendering with scrollable live logs plus toggle/abort actions
70
- - Side-panel theme picker backed by the bundled `@firstpick/pi-themes-bundle` themes
86
+ - Side-panel theme picker backed by optional `@firstpick/pi-themes-bundle` themes when loaded
71
87
  - PWA metadata, icons, and service worker for install-to-home-screen support when served from a secure context
72
88
  - Static frontend: no bundler, no frontend install step
73
89
 
@@ -91,7 +107,7 @@ Options:
91
107
  --port <port> HTTP port (default: 31415)
92
108
  --no-open Do not open the browser automatically
93
109
  --no-session Start Pi RPC with --no-session
94
- --name <name> Initial Pi session name
110
+ --name <name> Initial Web UI tab display name
95
111
  -- <pi args...> Extra arguments forwarded to Pi RPC
96
112
  ```
97
113
 
@@ -130,7 +146,7 @@ Options:
130
146
  --cwd <path> Default working directory for Pi tabs (default: current dir)
131
147
  --pi <command> Pi executable to spawn (default: bundled dependency, then "pi")
132
148
  --no-session Start Pi RPC with --no-session
133
- --name <name> Initial Pi session name
149
+ --name <name> Initial Web UI tab display name
134
150
  -h, --help Show help
135
151
  -v, --version Print version
136
152
  ```
@@ -159,7 +175,7 @@ The browser button runs a native local workflow in the Web UI server process:
159
175
  4. Commit with either the short message (`git commit -m ...`) or the long message (`git commit -F ...`).
160
176
  5. Run `git push`.
161
177
 
162
- This workflow assumes `/git-staged-msg` is available in the Pi session and writes the two `dev/COMMIT/` files above. It can be cancelled between steps; active native git commands are terminated on cancel where possible.
178
+ This workflow requires `/git-staged-msg` from `@firstpick/pi-prompts-git-pr`, which writes the two `dev/COMMIT/` files above. The Web UI enables the Git workflow button only when that command is loaded in the active Pi tab. If package resources are filtered or optional dependencies were omitted, make sure `/git-staged-msg` remains enabled. The workflow can be cancelled between steps; active native git commands are terminated on cancel where possible.
163
179
 
164
180
  ## How it works
165
181
 
@@ -172,9 +188,11 @@ pi --mode rpc
172
188
  With options, each spawned command becomes:
173
189
 
174
190
  ```bash
175
- pi --mode rpc [--no-session] [--name <name>] [...extra Pi args]
191
+ pi --mode rpc [--no-session] [...extra Pi args]
176
192
  ```
177
193
 
194
+ Web UI tab titles are stored in Web UI metadata instead of being forwarded as Pi CLI `--name` flags, so multiple tabs remain compatible with bundled Pi CLI versions that do not support session naming.
195
+
178
196
  The local server exposes:
179
197
 
180
198
  - static files from `public/`
@@ -182,7 +200,8 @@ The local server exposes:
182
200
  - `GET /api/directories?tab=<tabId>&path=<path>` for the browser cwd picker
183
201
  - `GET /api/path-suggestions?tab=<tabId>&query=<path>` for `@` file/path reference autocomplete in the prompt composer
184
202
  - `GET /api/path-fast-picks` and `POST /api/path-fast-picks` for cwd picker fast picks persisted across browser tabs, Pi terminal tabs, and Web UI server restarts
185
- - `GET /api/themes` for bundled theme data from `@firstpick/pi-themes-bundle`
203
+ - `GET /api/themes` for optional theme data from `@firstpick/pi-themes-bundle` when available
204
+ - localhost-only `POST /api/optional-feature-install` for explicit, warned installation of whitelisted optional feature packages
186
205
  - `GET /api/network` and localhost-only `POST /api/network/open` for local-network exposure status/control
187
206
  - `GET /api/webui-status?detailed=1` for slash-command status reporting
188
207
  - `POST /api/shutdown` for localhost-only graceful restarts from `/webui-start`/`/start-webui`; restart captures detailed tab status first so open and recently closed tabs can be restored with their session files
package/bin/pi-webui.mjs CHANGED
@@ -104,6 +104,15 @@ const NATIVE_SLASH_COMMANDS = [
104
104
  { name: "quit", description: "Quit Pi" },
105
105
  ].map((command) => ({ ...command, source: "native", location: "Pi" }));
106
106
  const NATIVE_SLASH_COMMAND_NAMES = new Set(NATIVE_SLASH_COMMANDS.map((command) => command.name));
107
+ const OPTIONAL_FEATURE_PACKAGES = new Map([
108
+ ["gitWorkflow", "@firstpick/pi-prompts-git-pr"],
109
+ ["releaseNpm", "@firstpick/pi-extension-release-npm"],
110
+ ["releaseAur", "@firstpick/pi-extension-release-aur"],
111
+ ["todoProgressWidget", "@firstpick/pi-extension-todo-progress"],
112
+ ["gitFooterStatus", "@firstpick/pi-extension-git-footer-status"],
113
+ ["statsCommand", "@firstpick/pi-extension-stats"],
114
+ ["themeBundle", "@firstpick/pi-themes-bundle"],
115
+ ]);
107
116
 
108
117
  function usage() {
109
118
  console.log(`pi-webui ${packageJson.version}
@@ -119,7 +128,7 @@ Options:
119
128
  --cwd <path> Working directory for the Pi session (default: current dir)
120
129
  --pi <command> Pi executable to spawn (default: bundled dependency, then "pi")
121
130
  --no-session Start Pi RPC with --no-session
122
- --name <name> Initial Pi session name
131
+ --name <name> Initial Web UI tab display name
123
132
  -h, --help Show this help
124
133
  -v, --version Print version
125
134
 
@@ -232,6 +241,10 @@ function sanitizeError(error) {
232
241
  return error.stack || error.message || String(error);
233
242
  }
234
243
 
244
+ function delay(ms) {
245
+ return new Promise((resolve) => setTimeout(resolve, ms));
246
+ }
247
+
235
248
  class PiRpcProcess {
236
249
  constructor({ command, args, displayCommand, cwd }) {
237
250
  this.command = command;
@@ -274,6 +287,10 @@ class PiRpcProcess {
274
287
  this.emit({ type: "pi_process_start", pid: this.child.pid, cwd: this.cwd, command: this.displayCommand, args: this.args });
275
288
  }
276
289
 
290
+ isRunning() {
291
+ return !!this.child && this.child.exitCode === null && !this.child.killed;
292
+ }
293
+
277
294
  onEvent(listener) {
278
295
  this.listeners.add(listener);
279
296
  return () => this.listeners.delete(listener);
@@ -344,7 +361,7 @@ class PiRpcProcess {
344
361
  }
345
362
 
346
363
  send(command, timeoutMs = REQUEST_TIMEOUT_MS) {
347
- if (!this.child || !this.child.stdin || this.child.exitCode !== null) {
364
+ if (!this.isRunning() || !this.child?.stdin) {
348
365
  return Promise.reject(new Error("Pi RPC process is not running"));
349
366
  }
350
367
 
@@ -367,7 +384,7 @@ class PiRpcProcess {
367
384
  }
368
385
 
369
386
  async writeRaw(command) {
370
- if (!this.child || !this.child.stdin || this.child.exitCode !== null) {
387
+ if (!this.isRunning() || !this.child?.stdin) {
371
388
  throw new Error("Pi RPC process is not running");
372
389
  }
373
390
 
@@ -634,6 +651,49 @@ function runCommand(command, args, { cwd, timeoutMs = 2000, maxOutputLength = 20
634
651
  });
635
652
  }
636
653
 
654
+ function optionalDependencyInstallRoot() {
655
+ const parts = packageRoot.split(path.sep);
656
+ const nodeModulesIndex = parts.lastIndexOf("node_modules");
657
+ if (nodeModulesIndex >= 0) {
658
+ const root = parts.slice(0, nodeModulesIndex).join(path.sep);
659
+ return root || path.parse(packageRoot).root;
660
+ }
661
+ return packageRoot;
662
+ }
663
+
664
+ function formatCommandForDisplay(command, args) {
665
+ return [command, ...args].map((part) => (/\s/.test(part) ? JSON.stringify(part) : part)).join(" ");
666
+ }
667
+
668
+ async function installOptionalFeaturePackage(featureId) {
669
+ const packageName = OPTIONAL_FEATURE_PACKAGES.get(featureId);
670
+ if (!packageName) throw makeHttpError(400, `Unknown optional feature: ${featureId}`);
671
+
672
+ const installRoot = optionalDependencyInstallRoot();
673
+ const npmCommand = process.env.PI_WEBUI_NPM_BIN || "npm";
674
+ const args = ["install", "--prefix", installRoot, packageName];
675
+ const result = await runCommand(npmCommand, args, {
676
+ cwd: installRoot,
677
+ timeoutMs: 5 * 60 * 1000,
678
+ maxOutputLength: 80000,
679
+ });
680
+ const command = formatCommandForDisplay(npmCommand, args);
681
+ const ok = result.exitCode === 0 && !result.timedOut && !result.error;
682
+ if (!ok) {
683
+ const details = [result.error, result.timedOut ? "timed out" : undefined, result.stderr?.trim(), result.stdout?.trim()].filter(Boolean).join("\n");
684
+ throw makeHttpError(500, `Optional feature install failed: ${command}${details ? `\n${details}` : ""}`);
685
+ }
686
+ return {
687
+ featureId,
688
+ packageName,
689
+ installRoot,
690
+ command,
691
+ stdout: result.stdout,
692
+ stderr: result.stderr,
693
+ message: `Installed optional feature package ${packageName}. Reload the active Pi tab to load new resources.`,
694
+ };
695
+ }
696
+
637
697
  function displayPath(cwd) {
638
698
  const normalized = cwd.replace(/\\/g, "/");
639
699
  const home = (process.env.USERPROFILE || process.env.HOME || "").replace(/\\/g, "/");
@@ -809,9 +869,9 @@ function resolveScopedModelsFromPatterns(patterns, models) {
809
869
  async function getScopedModelData(tab) {
810
870
  const { patterns, source } = await configuredScopedModelPatterns(tab.cwd);
811
871
  if (!patterns.length) return { models: [], patterns, source };
812
- const response = await tab.rpc.send({ type: "get_available_models" });
872
+ const response = await safeRpcResponse(tab, { type: "get_available_models" });
813
873
  if (response.success === false) throw makeHttpError(400, response.error || "failed to load available models");
814
- return { models: resolveScopedModelsFromPatterns(patterns, response.data?.models || []), patterns, source };
874
+ return { models: resolveScopedModelsFromPatterns(patterns, response.data?.models || []), patterns, source, rpcRunning: response.rpcRunning !== false };
815
875
  }
816
876
 
817
877
  function pathPickerRoots(activeCwd, viewedCwd) {
@@ -1449,9 +1509,9 @@ function buildPiArgsForTab(tabIndex, title) {
1449
1509
  const args = ["--mode", "rpc"];
1450
1510
  if (options.noSession) args.push("--no-session");
1451
1511
 
1452
- const sessionName = tabIndex === 1 ? options.name : title;
1453
- if (sessionName) args.push("--name", sessionName);
1454
-
1512
+ // Keep tab naming inside Web UI metadata. Some bundled Pi CLI versions do not
1513
+ // support --name, and passing Web UI-generated tab titles through to child
1514
+ // RPC processes makes every tab after the first exit immediately.
1455
1515
  args.push(...options.piArgs);
1456
1516
  return args;
1457
1517
  }
@@ -1729,6 +1789,18 @@ function defaultTabTitle(tabIndex) {
1729
1789
  return `Terminal ${tabIndex}`;
1730
1790
  }
1731
1791
 
1792
+ async function primeTabRpc(tab) {
1793
+ try {
1794
+ const response = await tab.rpc.send({ type: "get_state" }, 1500);
1795
+ if (response.success !== false) {
1796
+ rememberTabState(tab, response.data);
1797
+ reconcileTabActivityFromState(tab, response.data);
1798
+ }
1799
+ } catch (error) {
1800
+ if (!/Timed out waiting for RPC response/i.test(sanitizeError(error))) throw error;
1801
+ }
1802
+ }
1803
+
1732
1804
  function attachRpcToTab(tab, rpc) {
1733
1805
  tab.rpcUnsubscribe?.();
1734
1806
  tab.rpc = rpc;
@@ -1777,6 +1849,15 @@ async function createTab({ id: requestedId, index, title, titleSource, conversat
1777
1849
  attachRpcToTab(tab, rpc);
1778
1850
  tabs.set(id, tab);
1779
1851
  rpc.start();
1852
+ try {
1853
+ await primeTabRpc(tab);
1854
+ } catch (error) {
1855
+ if (!tab.rpc.isRunning()) {
1856
+ tab.rpcUnsubscribe?.();
1857
+ tabs.delete(id);
1858
+ throw new Error(`Pi RPC process failed while starting ${tabTitle}: ${sanitizeError(error)}`);
1859
+ }
1860
+ }
1780
1861
  if (sessionFile && !options.noSession) {
1781
1862
  recordEvent({ type: "webui_tab_restored", tabId: tab.id, tabTitle: tab.title, cwd: tab.cwd });
1782
1863
  }
@@ -1799,7 +1880,7 @@ function tabMeta(tab) {
1799
1880
  createdAt: tab.createdAt,
1800
1881
  startedAt: tab.rpc.startedAt,
1801
1882
  pid: tab.rpc.child?.pid,
1802
- running: !!tab.rpc.child && tab.rpc.child.exitCode === null,
1883
+ running: tab.rpc.isRunning(),
1803
1884
  command: tab.rpc.displayCommand,
1804
1885
  clientCount: tab.sseClients.size,
1805
1886
  pendingExtensionUiRequestCount: pendingExtensionUiRequests(tab).length,
@@ -1968,10 +2049,67 @@ async function restartTabRpc(tab, reason = "reload") {
1968
2049
  return tab;
1969
2050
  }
1970
2051
 
2052
+ function rpcUnavailableMessage(tab) {
2053
+ return `Pi RPC process for ${tab?.title || "terminal"} is not running`;
2054
+ }
2055
+
2056
+ function fallbackRpcResponse(tab, command, error) {
2057
+ const message = sanitizeError(error) || rpcUnavailableMessage(tab);
2058
+ const base = { type: "response", command: command.type, success: true, rpcRunning: false, error: message };
2059
+ switch (command.type) {
2060
+ case "get_state":
2061
+ return {
2062
+ ...base,
2063
+ data: {
2064
+ model: null,
2065
+ thinkingLevel: "off",
2066
+ isStreaming: false,
2067
+ isCompacting: false,
2068
+ steeringMode: "one-at-a-time",
2069
+ followUpMode: "one-at-a-time",
2070
+ sessionFile: tab?.sessionFile,
2071
+ sessionId: tab?.id,
2072
+ sessionName: tab?.title,
2073
+ autoCompactionEnabled: false,
2074
+ messageCount: 0,
2075
+ pendingMessageCount: 0,
2076
+ rpcRunning: false,
2077
+ rpcError: message,
2078
+ },
2079
+ };
2080
+ case "get_messages":
2081
+ return { ...base, data: { messages: [] } };
2082
+ case "get_available_models":
2083
+ return { ...base, data: { models: [] } };
2084
+ case "get_session_stats":
2085
+ return { ...base, data: null };
2086
+ case "get_last_assistant_text":
2087
+ return { ...base, data: { text: "" } };
2088
+ default:
2089
+ return { ...base, success: false, error: message };
2090
+ }
2091
+ }
2092
+
2093
+ async function safeRpcResponse(tab, command, timeoutMs = REQUEST_TIMEOUT_MS) {
2094
+ try {
2095
+ return await tab.rpc.send(command, timeoutMs);
2096
+ } catch (error) {
2097
+ const message = sanitizeError(error);
2098
+ if (/Pi RPC process is not running/i.test(message)) return fallbackRpcResponse(tab, command, error);
2099
+ throw error;
2100
+ }
2101
+ }
2102
+
1971
2103
  async function getCommandData(tab) {
1972
- const response = await tab.rpc.send({ type: "get_commands" });
1973
- if (response.success === false) throw makeHttpError(400, response.error || "failed to load commands");
1974
- return { commands: [...NATIVE_SLASH_COMMANDS, ...(response.data?.commands || [])] };
2104
+ try {
2105
+ const response = await tab.rpc.send({ type: "get_commands" });
2106
+ if (response.success === false) throw makeHttpError(400, response.error || "failed to load commands");
2107
+ return { commands: [...NATIVE_SLASH_COMMANDS, ...(response.data?.commands || [])], rpcRunning: true };
2108
+ } catch (error) {
2109
+ const message = sanitizeError(error);
2110
+ if (!/Pi RPC process is not running/i.test(message)) throw error;
2111
+ return { commands: [...NATIVE_SLASH_COMMANDS], rpcRunning: false, error: message };
2112
+ }
1975
2113
  }
1976
2114
 
1977
2115
  function formatSessionOutput(tab, state, stats) {
@@ -2080,6 +2218,23 @@ async function closeTab(id) {
2080
2218
  return tab;
2081
2219
  }
2082
2220
 
2221
+ async function closeTabs(ids) {
2222
+ const uniqueIds = [...new Set((Array.isArray(ids) ? ids : []).map((id) => String(id || "").trim()).filter(Boolean))];
2223
+ const targetTabs = uniqueIds.map((id) => tabs.get(id)).filter(Boolean);
2224
+ if (!targetTabs.length) return [];
2225
+
2226
+ if (targetTabs.length >= tabs.size) {
2227
+ await createTab({ cwd: targetTabs[0]?.cwd || options.cwd });
2228
+ }
2229
+
2230
+ const closed = [];
2231
+ for (const tab of targetTabs) {
2232
+ if (!tabs.has(tab.id)) continue;
2233
+ closed.push(await closeTab(tab.id));
2234
+ }
2235
+ return closed;
2236
+ }
2237
+
2083
2238
  function requestedTabId(req, url, body) {
2084
2239
  const header = req.headers["x-pi-webui-tab"];
2085
2240
  const headerValue = Array.isArray(header) ? header[0] : header;
@@ -2362,6 +2517,13 @@ const server = createServer(async (req, res) => {
2362
2517
  return;
2363
2518
  }
2364
2519
 
2520
+ if (url.pathname === "/api/tabs/close" && req.method === "POST") {
2521
+ const body = await readJsonBody(req);
2522
+ const closed = await closeTabs(body.ids || body.tabIds || []);
2523
+ sendJson(res, 200, { ok: true, data: { closedIds: closed.map((tab) => tab.id), tabs: listTabs(), activeTabId: firstTab()?.id || null } });
2524
+ return;
2525
+ }
2526
+
2365
2527
  if (url.pathname.startsWith("/api/tabs/") && req.method === "PATCH") {
2366
2528
  const id = decodeURIComponent(url.pathname.slice("/api/tabs/".length));
2367
2529
  const body = await readJsonBody(req);
@@ -2511,6 +2673,14 @@ const server = createServer(async (req, res) => {
2511
2673
  return;
2512
2674
  }
2513
2675
 
2676
+ if (url.pathname === "/api/optional-feature-install" && req.method === "POST") {
2677
+ if (!isLocalAddress(req.socket.remoteAddress)) throw makeHttpError(403, "Installing optional Web UI features is only allowed from localhost");
2678
+ const body = await readJsonBody(req);
2679
+ const data = await installOptionalFeaturePackage(String(body.featureId || ""));
2680
+ sendJson(res, 200, { ok: true, data });
2681
+ return;
2682
+ }
2683
+
2514
2684
  if (url.pathname === "/api/commands" && req.method === "GET") {
2515
2685
  const tab = getRequestedTab(req, url);
2516
2686
  sendJson(res, 200, { type: "response", command: "get_commands", success: true, data: await getCommandData(tab) });
@@ -2558,7 +2728,7 @@ const server = createServer(async (req, res) => {
2558
2728
  const getCommand = req.method === "GET" ? commandFromGet(url.pathname) : undefined;
2559
2729
  if (getCommand) {
2560
2730
  const tab = getRequestedTab(req, url);
2561
- const response = await tab.rpc.send(getCommand);
2731
+ const response = await safeRpcResponse(tab, getCommand);
2562
2732
  sendJson(res, response.success === false ? 400 : 200, response);
2563
2733
  return;
2564
2734
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@firstpick/pi-package-webui",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Pi Web UI companion package with a local browser UI CLI plus /webui-start and /webui-status commands.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -15,7 +15,29 @@
15
15
  ],
16
16
  "pi": {
17
17
  "extensions": [
18
- "./index.ts"
18
+ "./index.ts",
19
+ "../pi-extension-git-footer-status/index.ts",
20
+ "../pi-extension-release-aur/index.ts",
21
+ "../pi-extension-release-npm/index.ts",
22
+ "../pi-extension-stats/index.ts",
23
+ "../pi-extension-todo-progress/index.ts",
24
+ "node_modules/@firstpick/pi-extension-git-footer-status/index.ts",
25
+ "node_modules/@firstpick/pi-extension-release-aur/index.ts",
26
+ "node_modules/@firstpick/pi-extension-release-npm/index.ts",
27
+ "node_modules/@firstpick/pi-extension-stats/index.ts",
28
+ "node_modules/@firstpick/pi-extension-todo-progress/index.ts"
29
+ ],
30
+ "skills": [
31
+ "../pi-extension-release-aur/skills",
32
+ "node_modules/@firstpick/pi-extension-release-aur/skills"
33
+ ],
34
+ "prompts": [
35
+ "../pi-package-prompts-git-pr/prompts",
36
+ "node_modules/@firstpick/pi-prompts-git-pr/prompts"
37
+ ],
38
+ "themes": [
39
+ "../pi-package-themes-bundle/themes",
40
+ "node_modules/@firstpick/pi-themes-bundle/themes"
19
41
  ]
20
42
  },
21
43
  "bin": {
@@ -26,8 +48,16 @@
26
48
  "test": "node tests/mobile-static.test.mjs"
27
49
  },
28
50
  "dependencies": {
29
- "@earendil-works/pi-coding-agent": "^0.78.0",
30
- "@firstpick/pi-themes-bundle": "^0.1.0"
51
+ "@earendil-works/pi-coding-agent": "^0.78.0"
52
+ },
53
+ "optionalDependencies": {
54
+ "@firstpick/pi-extension-git-footer-status": "^0.2.1",
55
+ "@firstpick/pi-extension-release-aur": "^0.1.3",
56
+ "@firstpick/pi-extension-release-npm": "^0.3.3",
57
+ "@firstpick/pi-extension-stats": "^0.2.0",
58
+ "@firstpick/pi-extension-todo-progress": "^0.1.7",
59
+ "@firstpick/pi-prompts-git-pr": "^0.1.0",
60
+ "@firstpick/pi-themes-bundle": "^0.1.1"
31
61
  },
32
62
  "files": [
33
63
  "index.ts",