@henryqw/pi-footer 2.2.4 → 4.0.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 +72 -11
- package/extensions/footer.ts +18 -27
- package/extensions/open.ts +77 -0
- package/package.json +10 -6
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# `@henryqw/pi-footer`
|
|
2
2
|
|
|
3
|
-
Keep checkout identity, model usage, elapsed agent work, and extension status visible while you work in Pi.
|
|
3
|
+
Keep checkout identity, model usage, elapsed agent work, and extension status visible while you work in Pi. Open the current Pi working directory with `/open`, using VS Code by default or another configured command. The footer and open commands can be enabled independently.
|
|
4
4
|
|
|
5
5
|

|
|
6
6
|
|
|
@@ -10,18 +10,50 @@ Keep checkout identity, model usage, elapsed agent work, and extension status vi
|
|
|
10
10
|
pi install npm:@henryqw/pi-footer
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
+
Version 3 replaces `@henryqw/pi-open-in`. If you have it installed, remove it before upgrading to prevent duplicate `/open` and `/set-open-in` commands:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pi remove npm:@henryqw/pi-open-in
|
|
17
|
+
pi install npm:@henryqw/pi-footer
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
For project-local installs, use `--local` on both commands. Remove manually configured paths to the old package as well, then restart Pi. The existing open-command config stays at `config/pi-open-in/config.json`; do not move it.
|
|
21
|
+
|
|
22
|
+
Both entry points load by default. To use `/open` without the custom footer, use Pi's native package resource filter in `settings.json`:
|
|
23
|
+
|
|
24
|
+
```json
|
|
25
|
+
{
|
|
26
|
+
"packages": [
|
|
27
|
+
{
|
|
28
|
+
"source": "npm:@henryqw/pi-footer",
|
|
29
|
+
"extensions": ["extensions/open.ts"]
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Select `extensions/footer.ts` instead for only the footer, or use `pi config` to toggle either resource. No package activation config is needed.
|
|
36
|
+
|
|
13
37
|
## Works with
|
|
14
38
|
|
|
15
39
|
| Package | Relationship | Purpose |
|
|
16
40
|
| --- | --- | --- |
|
|
17
41
|
| [`@henryqw/pi-codegraph`](https://pi.henry.wang/extensions/pi-codegraph) | Improves | Shows CodeGraph index status, quiet setup progress, and direct tool activity when loaded. |
|
|
42
|
+
| [`@henryqw/pi-config-store`](https://pi.henry.wang/extensions/pi-config-store) | Required | Owns config-home resolution and storage. |
|
|
18
43
|
| [`@henryqw/pi-multi-codex`](https://pi.henry.wang/extensions/pi-multi-codex) | Improves | Adds active Codex subscription quota and reset status. |
|
|
19
|
-
| [`@henryqw/pi-open-in`](https://pi.henry.wang/extensions/pi-open-in) | Improves | Adds `/open` and `/set-open-in` commands for editor configuration. |
|
|
20
44
|
| [`@henryqw/pi-pr`](https://pi.henry.wang/extensions/pi-pr) | Improves | Adds current-branch pull-request status. |
|
|
21
45
|
|
|
22
46
|
## Use
|
|
23
47
|
|
|
24
|
-
After installing, start a Pi TUI session to see checkout, usage, model, thinking, and extension statuses.
|
|
48
|
+
After installing, start a Pi TUI session to see checkout, usage, model, thinking, and extension statuses. Run `/open` to open the current working directory in VS Code by default; it also works while the agent is busy.
|
|
49
|
+
|
|
50
|
+
| Surface | Type | Purpose |
|
|
51
|
+
| --- | --- | --- |
|
|
52
|
+
| Footer | ui | Shows checkout identity, usage, model, elapsed agent work, and extension statuses. |
|
|
53
|
+
| `/open` | command | Opens the current working directory with the configured command. |
|
|
54
|
+
| `/set-open-in <command>` | command | Sets the command used by `/open`. |
|
|
55
|
+
|
|
56
|
+
Run `/set-open-in <command>` to choose another command, such as `cursor --reuse-window`. `/open` runs `<command> <current-working-directory>`.
|
|
25
57
|
|
|
26
58
|
```text
|
|
27
59
|
pi-harness · clear-field-f8d2 [+2 ~3 ?1 ↑2] · PR #123 · approved Codex #1 · 50% · 7d 1d 1h 22m
|
|
@@ -60,18 +92,47 @@ Only the non-empty `pi-multi-codex` quota status occupies the right side of the
|
|
|
60
92
|
|
|
61
93
|
Statuses from other extensions, including Ponytail and `pi-rewind`, share the left side of the third line. They are sorted by key; colors, links, glyphs, and interior spacing are preserved. Leading and trailing whitespace is trimmed, line breaks become spaces, and long statuses may be clipped to fit the footer. Only direct `codegraph_explore` tool calls trigger the `● CG` badge; CodeGraph queries made from inside scripted tools are not shown.
|
|
62
94
|
|
|
63
|
-
##
|
|
95
|
+
## Config
|
|
64
96
|
|
|
65
|
-
|
|
97
|
+
Package-owned: `~/.pi/agent/config/pi-open-in/config.json` (under `getAgentDir()` when overridden). The retained `pi-open-in` home belongs to the open commands now shipped here.
|
|
66
98
|
|
|
67
|
-
|
|
99
|
+
| Name | Description | Values | Default |
|
|
100
|
+
| --- | --- | --- | --- |
|
|
101
|
+
| `command` | Command run by `/open`; required when the file exists. | Non-empty string. | `"code"` |
|
|
68
102
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
103
|
+
A missing file silently uses the default command, `code`. An invalid existing file makes `/open` fail visibly and remains unchanged; see Limits and recovery. `/open` reads the config on every run. Only `/set-open-in` writes the file, atomically. Reads do not create or write the config home.
|
|
104
|
+
|
|
105
|
+
## API
|
|
106
|
+
|
|
107
|
+
Consumers should use the owner API instead of reading the config file directly. Replace imports from `@henryqw/pi-open-in/open-uri` with `@henryqw/pi-footer/open-uri`:
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
import { loadOpenInConfig } from "@henryqw/pi-footer/open-uri";
|
|
111
|
+
|
|
112
|
+
const { source, value } = loadOpenInConfig();
|
|
73
113
|
```
|
|
74
114
|
|
|
75
|
-
|
|
115
|
+
These exports serve package consumers; the commands in Use are the Pi interfaces.
|
|
116
|
+
|
|
117
|
+
| Surface | Type | Purpose |
|
|
118
|
+
| --- | --- | --- |
|
|
119
|
+
| `default` (`openInExtension(pi)`) | function | Registers the open commands with Pi, without the footer. |
|
|
120
|
+
| `OpenInConfig` | type | Describes the configured command. |
|
|
121
|
+
| `loadOpenInConfig(agentDir?)` | function | Reads and validates the owner config. |
|
|
122
|
+
| `configuredOpenUri(path)` | function | Returns a safe VS Code URI for a supported command. |
|
|
123
|
+
|
|
124
|
+
`loadOpenInConfig` returns `source` as `"missing"` or `"file"`; `value.command` is validated. Pass an agent directory when needed.
|
|
125
|
+
|
|
126
|
+
`configuredOpenUri(path)` works when the executable is `code`. With `code -n` or `code --new-window`, it adds `windowId=_blank` so the link opens in a new window. It returns `undefined` for other commands or invalid config. `@henryqw/pi-config-store` owns the config home and storage.
|
|
127
|
+
|
|
128
|
+
## Limits and recovery
|
|
129
|
+
|
|
130
|
+
The open command splits on whitespace into an executable and arguments. Tokens cannot contain spaces, and quoting is unsupported. Use a wrapper script for executables in spaced paths.
|
|
131
|
+
|
|
132
|
+
An existing config file must be a JSON object with exactly one non-empty string `command` property. Otherwise `/open` fails with a visible error, offers no open URI, and leaves the malformed file unchanged. If the configured command exits unsuccessfully, Pi reports its error output or exit code; fix the command and run `/open` again.
|
|
133
|
+
|
|
134
|
+
When the configured executable is `code` and Pi reports hyperlink support, the accent-colored checkout name links to the current path. The link opens a new window for `code -n` or `code --new-window`. A missing config silently uses `code`. Other executables and terminals with hyperlinks disabled render plain text.
|
|
135
|
+
|
|
136
|
+
Pi handles the custom URI in its default fullscreen TUI. Use normal primary click. Regular TUI (`"tuiMode": "regular"` or `--tui-mode regular`) delegates OSC 8 activation to the terminal, so the link works only where the terminal opens custom URI schemes.
|
|
76
137
|
|
|
77
138
|
Ghostty uses `Cmd+click` but may not open custom URI schemes.
|
package/extensions/footer.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
2
|
import { basename, dirname, join } from "node:path";
|
|
3
|
-
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import type { ExtensionAPI, ExtensionContext, SessionEntry } from "@earendil-works/pi-coding-agent";
|
|
4
4
|
import { getCapabilities, hyperlink, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
5
|
-
import { configuredOpenUri } from "
|
|
5
|
+
import { configuredOpenUri } from "./open.ts";
|
|
6
6
|
|
|
7
7
|
const THINKING_COLORS = {
|
|
8
8
|
minimal: 46,
|
|
@@ -12,7 +12,7 @@ const THINKING_COLORS = {
|
|
|
12
12
|
xhigh: 208,
|
|
13
13
|
max: 196,
|
|
14
14
|
} as const;
|
|
15
|
-
const
|
|
15
|
+
const QUOTA_STATUS_KEY = "pi-multi-codex";
|
|
16
16
|
const AGENT_TIME_ENTRY = "pi-footer:agent-work";
|
|
17
17
|
const SUBAGENT_BACKGROUND_RESULT = "subagent-background-result";
|
|
18
18
|
/** Pi's VIRTUAL_MODEL_API: catalog entries that route each request to a physical model. */
|
|
@@ -219,9 +219,7 @@ export default function footerExtension(pi: ExtensionAPI): void {
|
|
|
219
219
|
const resumeActive = () => {
|
|
220
220
|
if (!promptPaused) return;
|
|
221
221
|
promptPaused = false;
|
|
222
|
-
|
|
223
|
-
if (requestRuntimeRender) runtimeTimer = setInterval(requestRuntimeRender, 1_000);
|
|
224
|
-
requestRuntimeRender?.();
|
|
222
|
+
startActive();
|
|
225
223
|
};
|
|
226
224
|
const finalizeActive = (): boolean => {
|
|
227
225
|
if (activeStartedAt === undefined && !promptPaused) return false;
|
|
@@ -233,15 +231,9 @@ export default function footerExtension(pi: ExtensionAPI): void {
|
|
|
233
231
|
return true;
|
|
234
232
|
};
|
|
235
233
|
|
|
236
|
-
pi.on("agent_start",
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
pi.on("ui_prompt_start", () => {
|
|
240
|
-
pauseActive();
|
|
241
|
-
});
|
|
242
|
-
pi.on("ui_prompt_end", () => {
|
|
243
|
-
resumeActive();
|
|
244
|
-
});
|
|
234
|
+
pi.on("agent_start", startActive);
|
|
235
|
+
pi.on("ui_prompt_start", pauseActive);
|
|
236
|
+
pi.on("ui_prompt_end", resumeActive);
|
|
245
237
|
pi.on("agent_settled", async (_event, ctx) => {
|
|
246
238
|
if (!ctx.isIdle()) return;
|
|
247
239
|
if (finalizeActive()) pi.appendEntry(AGENT_TIME_ENTRY, activeMilliseconds);
|
|
@@ -301,12 +293,12 @@ export default function footerExtension(pi: ExtensionAPI): void {
|
|
|
301
293
|
// and some (Claude bridge) emit message_end only after post-stream teardown.
|
|
302
294
|
let assistantStartedAt: number | undefined;
|
|
303
295
|
let assistantLastUpdateAt: number | undefined;
|
|
304
|
-
pi.on("message_update",
|
|
296
|
+
pi.on("message_update", (event) => {
|
|
305
297
|
if (event.message.role !== "assistant") return;
|
|
306
298
|
assistantLastUpdateAt = performance.now();
|
|
307
299
|
assistantStartedAt ??= assistantLastUpdateAt;
|
|
308
300
|
});
|
|
309
|
-
pi.on("message_end",
|
|
301
|
+
pi.on("message_end", (event) => {
|
|
310
302
|
if (event.message.role !== "assistant") return;
|
|
311
303
|
const output = event.message.usage?.output ?? 0;
|
|
312
304
|
const seconds = assistantStartedAt === undefined ? 0 : (assistantLastUpdateAt! - assistantStartedAt) / 1000;
|
|
@@ -314,14 +306,14 @@ export default function footerExtension(pi: ExtensionAPI): void {
|
|
|
314
306
|
tps = seconds > 0 ? output / seconds : undefined;
|
|
315
307
|
});
|
|
316
308
|
|
|
317
|
-
//
|
|
309
|
+
// Session entries are append-only; the leaf also changes on tree navigation.
|
|
318
310
|
let usageKey: string | undefined;
|
|
319
311
|
let routed: ReturnType<typeof latestResponse>;
|
|
320
312
|
let input = 0;
|
|
321
313
|
let output = 0;
|
|
322
314
|
let cost = 0;
|
|
323
315
|
let cacheRate: number | undefined;
|
|
324
|
-
const computeUsage = () => {
|
|
316
|
+
const computeUsage = (entries: SessionEntry[]) => {
|
|
325
317
|
input = 0;
|
|
326
318
|
output = 0;
|
|
327
319
|
cost = 0;
|
|
@@ -333,7 +325,7 @@ export default function footerExtension(pi: ExtensionAPI): void {
|
|
|
333
325
|
cost += usage.cost.total;
|
|
334
326
|
};
|
|
335
327
|
|
|
336
|
-
for (const entry of
|
|
328
|
+
for (const entry of entries) {
|
|
337
329
|
if (entry.type === "message" && entry.message.role === "assistant") {
|
|
338
330
|
const usage = entry.message.usage;
|
|
339
331
|
const prompt = usage.input + usage.cacheRead + usage.cacheWrite;
|
|
@@ -364,10 +356,10 @@ export default function footerExtension(pi: ExtensionAPI): void {
|
|
|
364
356
|
invalidate() { },
|
|
365
357
|
render(width: number): string[] {
|
|
366
358
|
const entries = ctx.sessionManager.getEntries();
|
|
367
|
-
const key = `${entries.length}:${
|
|
359
|
+
const key = `${entries.length}:${ctx.sessionManager.getLeafId()}`;
|
|
368
360
|
if (key !== usageKey) {
|
|
369
361
|
usageKey = key;
|
|
370
|
-
computeUsage();
|
|
362
|
+
computeUsage(entries);
|
|
371
363
|
routed = latestResponse(ctx);
|
|
372
364
|
}
|
|
373
365
|
|
|
@@ -380,13 +372,12 @@ export default function footerExtension(pi: ExtensionAPI): void {
|
|
|
380
372
|
const codegraph = activeCodegraphCalls.size > 0
|
|
381
373
|
? `${theme.fg("accent", "●")} CG`
|
|
382
374
|
: sanitizeStatus(extensionStatuses.get("pi-codegraph") ?? "");
|
|
383
|
-
const
|
|
375
|
+
const quotaStatus = sanitizeStatus(extensionStatuses.get(QUOTA_STATUS_KEY) ?? "");
|
|
384
376
|
const externalStatuses: string[] = [];
|
|
385
377
|
for (const [key, value] of [...extensionStatuses].sort(([a], [b]) => a.localeCompare(b))) {
|
|
386
|
-
if (key === "pi-pr" || key === "pi-codegraph") continue;
|
|
378
|
+
if (key === "pi-pr" || key === "pi-codegraph" || key === QUOTA_STATUS_KEY) continue;
|
|
387
379
|
const text = sanitizeStatus(value);
|
|
388
|
-
if (
|
|
389
|
-
(key === HENRY_STATUS_KEY ? henryStatuses : externalStatuses).push(text);
|
|
380
|
+
if (text) externalStatuses.push(text);
|
|
390
381
|
}
|
|
391
382
|
const ellipsis = theme.fg("dim", "…");
|
|
392
383
|
const usage = theme.fg("dim", [
|
|
@@ -415,7 +406,7 @@ export default function footerExtension(pi: ExtensionAPI): void {
|
|
|
415
406
|
: theme.fg("dim", checkout);
|
|
416
407
|
const checkoutStatus = gitSummary.badges ? `${checkoutLink} ${theme.fg("dim", gitSummary.badges)}` : checkoutLink;
|
|
417
408
|
const identityLine = prStatus ? `${identity}${checkoutStatus} · ${prStatus}` : `${identity}${checkoutStatus}`;
|
|
418
|
-
const firstLine =
|
|
409
|
+
const firstLine = quotaStatus ? align(identityLine, quotaStatus, width, ellipsis) : identityLine;
|
|
419
410
|
const lines = [
|
|
420
411
|
firstLine,
|
|
421
412
|
align(usage, model, width, ellipsis),
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { pathToFileURL } from "node:url";
|
|
2
|
+
import { type ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import { createConfigStore } from "@henryqw/pi-config-store";
|
|
4
|
+
|
|
5
|
+
const DEFAULT_COMMAND = "code";
|
|
6
|
+
|
|
7
|
+
export type OpenInConfig = { command: string };
|
|
8
|
+
|
|
9
|
+
function parseOpenInConfig(value: unknown): OpenInConfig {
|
|
10
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
11
|
+
throw new Error('Invalid open-in config: expected exactly one non-empty string "command" property');
|
|
12
|
+
}
|
|
13
|
+
const config = value as Record<string, unknown>;
|
|
14
|
+
if (Object.keys(config).length !== 1 || typeof config.command !== "string" || !config.command.trim()) {
|
|
15
|
+
throw new Error('Invalid open-in config: expected exactly one non-empty string "command" property');
|
|
16
|
+
}
|
|
17
|
+
return { command: config.command.trim() };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function createOpenInConfigStore(agentDir?: string) {
|
|
21
|
+
return createConfigStore<OpenInConfig>({
|
|
22
|
+
extensionId: "pi-open-in",
|
|
23
|
+
agentDir,
|
|
24
|
+
defaults: () => ({ command: DEFAULT_COMMAND }),
|
|
25
|
+
parse: parseOpenInConfig,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function loadOpenInConfig(agentDir?: string): { source: "file" | "missing"; value: OpenInConfig } {
|
|
30
|
+
return createOpenInConfigStore(agentDir).loadSync();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function configuredOpenUri(path: string): string | undefined {
|
|
34
|
+
let args: string[];
|
|
35
|
+
try {
|
|
36
|
+
const [executable, ...commandArgs] = loadOpenInConfig().value.command.split(/\s+/);
|
|
37
|
+
if (executable !== "code") return undefined;
|
|
38
|
+
args = commandArgs;
|
|
39
|
+
} catch {
|
|
40
|
+
// Invalid config must not break footer render; just omit the URI.
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
const query = args.includes("-n") || args.includes("--new-window") ? "?windowId=_blank" : "";
|
|
44
|
+
if (path.startsWith("\\\\")) {
|
|
45
|
+
const [host, ...parts] = path.slice(2).split("\\");
|
|
46
|
+
const uri = new URL(`file://${host}`);
|
|
47
|
+
uri.pathname = `/${parts.join("/")}`;
|
|
48
|
+
return `vscode://file//${uri.host}${uri.pathname}${query}`;
|
|
49
|
+
}
|
|
50
|
+
return `vscode://file${pathToFileURL(path).pathname}${query}`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export default function openInExtension(pi: ExtensionAPI): void {
|
|
54
|
+
const configStore = createOpenInConfigStore();
|
|
55
|
+
|
|
56
|
+
// Reload config on each call; split the command on whitespace (no quoted tokens).
|
|
57
|
+
pi.registerCommand("open", {
|
|
58
|
+
description: "Open the current path with the configured command",
|
|
59
|
+
handler: async (_args, ctx) => {
|
|
60
|
+
const [executable, ...args] = configStore.loadSync().value.command.split(/\s+/);
|
|
61
|
+
const result = await pi.exec(executable, [...args, ctx.cwd]);
|
|
62
|
+
if (result.code !== 0) {
|
|
63
|
+
throw new Error(`Open command failed: ${result.stderr.trim() || `exit code ${result.code}`}`);
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
pi.registerCommand("set-open-in", {
|
|
69
|
+
description: "Set command used by /open",
|
|
70
|
+
handler: async (args, ctx) => {
|
|
71
|
+
const command = args.trim();
|
|
72
|
+
if (!command) throw new Error("Usage: /set-open-in <command>");
|
|
73
|
+
await configStore.save({ command });
|
|
74
|
+
ctx.ui.notify(`Saved open-in command: ${command}`, "info");
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@henryqw/pi-footer",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Show
|
|
3
|
+
"version": "4.0.0",
|
|
4
|
+
"description": "Show repository and usage details in the Pi footer and open the current working directory.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
7
7
|
"pi",
|
|
@@ -19,14 +19,17 @@
|
|
|
19
19
|
"README.md",
|
|
20
20
|
"example.png"
|
|
21
21
|
],
|
|
22
|
+
"exports": {
|
|
23
|
+
"./open-uri": "./extensions/open.ts"
|
|
24
|
+
},
|
|
22
25
|
"scripts": {
|
|
23
26
|
"test": "node --test test/*.test.ts",
|
|
24
27
|
"typecheck": "tsc --noEmit --allowImportingTsExtensions --target ES2022 --module NodeNext --moduleResolution NodeNext --skipLibCheck extensions/*.ts test/*.test.ts",
|
|
25
28
|
"pack:check": "npm pack --dry-run"
|
|
26
29
|
},
|
|
27
30
|
"peerDependencies": {
|
|
28
|
-
"@earendil-works/pi-coding-agent": "
|
|
29
|
-
"@earendil-works/pi-tui": "
|
|
31
|
+
"@earendil-works/pi-coding-agent": "^1.0.0",
|
|
32
|
+
"@earendil-works/pi-tui": "^1.0.0"
|
|
30
33
|
},
|
|
31
34
|
"repository": {
|
|
32
35
|
"type": "git",
|
|
@@ -41,12 +44,13 @@
|
|
|
41
44
|
},
|
|
42
45
|
"pi": {
|
|
43
46
|
"extensions": [
|
|
44
|
-
"./extensions/footer.ts"
|
|
47
|
+
"./extensions/footer.ts",
|
|
48
|
+
"./extensions/open.ts"
|
|
45
49
|
],
|
|
46
50
|
"image": "https://raw.githubusercontent.com/HenryQW/pi-harness/main/extensions/pi-footer/example.png"
|
|
47
51
|
},
|
|
48
52
|
"dependencies": {
|
|
49
|
-
"@henryqw/pi-
|
|
53
|
+
"@henryqw/pi-config-store": "^3.0.0"
|
|
50
54
|
},
|
|
51
55
|
"devDependencies": {
|
|
52
56
|
"@earendil-works/pi-coding-agent": "1.0.0",
|