pi-spark 0.3.1 → 0.5.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 +44 -15
- package/extensions/codex-usage/client.ts +69 -0
- package/extensions/codex-usage/config.ts +5 -0
- package/extensions/codex-usage/index.ts +43 -0
- package/extensions/codex-usage/manager.ts +47 -0
- package/extensions/codex-usage/status.ts +31 -0
- package/extensions/footer/index.ts +2 -6
- package/extensions/set-session-name/config.ts +3 -0
- package/extensions/set-session-name/index.ts +95 -0
- package/extensions/shared/config/schema.ts +6 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,11 +6,13 @@ A small, opinionated collection of [pi](https://pi.dev/) extensions.
|
|
|
6
6
|
|
|
7
7
|
## Extensions
|
|
8
8
|
|
|
9
|
+
- **Codex usage:** shows your OpenAI Codex (ChatGPT) rate-limit usage as a footer status when a Codex model is active.
|
|
9
10
|
- **Editor:** replaces the default editor with a compact working indicator (inspired by [Amp](https://ampcode.com/)) and current model info.
|
|
10
11
|
- **Footer:** shows session information, extension statuses, cost, and context usage on one line.
|
|
11
12
|
- **Fullscreen:** clears the screen and scrollback on session start, pins the editor and footer to the bottom for a full-screen session, and clears again on exit.
|
|
12
13
|
- **Presets:** switches named model presets with `/preset`, `--preset`, and quick cycle shortcuts.
|
|
13
14
|
- **Recap:** generates a short idle-session recap and exposes a `/recap` command for manual generation, inspired by [Claude Code's session recap](https://code.claude.com/docs/en/interactive-mode#session-recap).
|
|
15
|
+
- **Set session name:** exposes a `set_session_name` tool so the agent can give the current session a concise, recognizable name in the session selector.
|
|
14
16
|
|
|
15
17
|

|
|
16
18
|
|
|
@@ -32,6 +34,8 @@ pi install git:github.com/zlliang/pi-spark
|
|
|
32
34
|
|
|
33
35
|
Spark reads config from `~/.pi/agent/spark.json` and from the current project’s `.pi/spark.json`. Project config overrides matching global fields.
|
|
34
36
|
|
|
37
|
+
All extensions are enabled by default. Set a specific extension to `false` to disable it, for example, `"footer": false` disables the footer extension.
|
|
38
|
+
|
|
35
39
|
Example:
|
|
36
40
|
|
|
37
41
|
```json
|
|
@@ -41,32 +45,57 @@ Example:
|
|
|
41
45
|
},
|
|
42
46
|
"footer": false,
|
|
43
47
|
"presets": {
|
|
44
|
-
"gpt": {
|
|
45
|
-
"provider": "openai-codex",
|
|
46
|
-
"model": "gpt-5.5",
|
|
47
|
-
"thinkingLevel": "medium"
|
|
48
|
-
},
|
|
49
48
|
"claude-opus": {
|
|
50
|
-
"provider": "anthropic",
|
|
51
49
|
"model": "claude-opus-4-8",
|
|
50
|
+
"provider": "anthropic",
|
|
52
51
|
"thinkingLevel": "high"
|
|
52
|
+
},
|
|
53
|
+
"gpt": {
|
|
54
|
+
"model": "gpt-5.5",
|
|
55
|
+
"provider": "openai-codex",
|
|
56
|
+
"thinkingLevel": "medium"
|
|
53
57
|
}
|
|
54
58
|
},
|
|
55
59
|
"recap": {
|
|
56
60
|
"idle": 180000,
|
|
57
|
-
"provider": "openai-codex",
|
|
58
61
|
"model": "gpt-5.4-mini",
|
|
62
|
+
"provider": "openai-codex",
|
|
59
63
|
"thinkingLevel": "off"
|
|
60
64
|
}
|
|
61
65
|
}
|
|
62
66
|
```
|
|
63
67
|
|
|
64
|
-
|
|
68
|
+
### Codex usage
|
|
69
|
+
|
|
70
|
+
- pi-spark queries the ChatGPT backend and shows your Codex 5-hour (`5h`) and 7-day (`7d`) rate-limit usage as a footer status, refreshing on session start, model change, and after billable turns. The status appears only while a Codex (`openai-codex`) model is active and uses its stored OAuth credential.
|
|
71
|
+
|
|
72
|
+
### Editor
|
|
73
|
+
|
|
74
|
+
- `editor.spinner` controls the working indicator style and can be `dots`, `lights`, or `tildes`.
|
|
75
|
+
|
|
76
|
+
### Footer
|
|
77
|
+
|
|
78
|
+
- pi-spark replaces the footer with a compact one-line view of session metadata, extension statuses, cost, and context usage.
|
|
79
|
+
|
|
80
|
+
### Fullscreen
|
|
81
|
+
|
|
82
|
+
- pi-spark clears the screen and scrollback at session start and exit, pins the editor and footer to the bottom, and enables pi's `clearOnShrink` behavior programmatically so pinned UI stays aligned after taller components close.
|
|
83
|
+
|
|
84
|
+
### Presets
|
|
85
|
+
|
|
86
|
+
- Each key under `presets` defines a named model preset with `provider`, `model`, and optional `thinkingLevel` fields.
|
|
87
|
+
|
|
88
|
+
Use presets in these ways:
|
|
89
|
+
|
|
90
|
+
- Select interactively with `/preset` or `/preset <key>`
|
|
91
|
+
- Start pi with a preset using `pi --preset <key>`
|
|
92
|
+
- Cycle presets with `ctrl+super+p` and `ctrl+shift+super+p` (`super` is `command` on macOS)
|
|
93
|
+
|
|
94
|
+
### Recap
|
|
95
|
+
|
|
96
|
+
- pi-spark can generate a short recap after the session has been idle or when you run `/recap` manually.
|
|
97
|
+
- The `recap.idle` value is in milliseconds and must be at least `5000`. The recap model can be customized with `provider`, `model`, and `thinkingLevel`.
|
|
98
|
+
|
|
99
|
+
### Set session name
|
|
65
100
|
|
|
66
|
-
-
|
|
67
|
-
- The `editor.spinner` value can be `dots`, `lights`, or `tildes`.
|
|
68
|
-
- Fullscreen enables Pi's `clearOnShrink` behavior programmatically so the pinned editor and footer stay aligned after taller UI components close.
|
|
69
|
-
- Presets can be selected with `/preset` or `/preset <key>`.
|
|
70
|
-
- Start pi with a preset using `pi --preset <key>`.
|
|
71
|
-
- Cycle presets with `ctrl+super+p` and `ctrl+shift+super+p` (`super` is `command` on macOS).
|
|
72
|
-
- The `recap.idle` value is in milliseconds and must be at least `5000`.
|
|
101
|
+
- The agent can set or refresh the current session's display name and optionally give a reason.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
|
|
3
|
+
export const CODEX_PROVIDER = "openai-codex";
|
|
4
|
+
const USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
|
|
5
|
+
const REQUEST_TIMEOUT_MS = 30_000;
|
|
6
|
+
|
|
7
|
+
/** Normalized Codex usage with rate-limit windows reduced to used percentages. */
|
|
8
|
+
export interface CodexUsage {
|
|
9
|
+
unlimited: boolean;
|
|
10
|
+
/** Used percentage of the 5-hour window, clamped to 0-100. */
|
|
11
|
+
primaryPercent: number | undefined;
|
|
12
|
+
/** Used percentage of the 7-day window, clamped to 0-100. */
|
|
13
|
+
secondaryPercent: number | undefined;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface CodexUsageResponse {
|
|
17
|
+
rate_limit?: {
|
|
18
|
+
primary_window?: CodexRateWindow | null;
|
|
19
|
+
secondary_window?: CodexRateWindow | null;
|
|
20
|
+
} | null;
|
|
21
|
+
credits?: {
|
|
22
|
+
unlimited?: boolean;
|
|
23
|
+
} | null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface CodexRateWindow {
|
|
27
|
+
used_percent?: number | string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Read the ChatGPT account id from the stored Codex OAuth credential, if present. */
|
|
31
|
+
export function readAccountId(ctx: ExtensionContext): string | undefined {
|
|
32
|
+
const credential = ctx.modelRegistry.authStorage.get(CODEX_PROVIDER) as { accountId?: string } | undefined;
|
|
33
|
+
const accountId = credential?.accountId;
|
|
34
|
+
|
|
35
|
+
return typeof accountId === "string" && accountId.trim() ? accountId.trim() : undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Fetch and normalize Codex usage, aborting on timeout or when the parent signal aborts. */
|
|
39
|
+
export async function fetchCodexUsage(token: string, accountId?: string, parentSignal?: AbortSignal): Promise<CodexUsage> {
|
|
40
|
+
const signals = [AbortSignal.timeout(REQUEST_TIMEOUT_MS)];
|
|
41
|
+
if (parentSignal) signals.push(parentSignal);
|
|
42
|
+
|
|
43
|
+
const headers: Record<string, string> = {
|
|
44
|
+
Accept: "application/json",
|
|
45
|
+
Authorization: `Bearer ${token}`,
|
|
46
|
+
};
|
|
47
|
+
if (accountId) headers["ChatGPT-Account-Id"] = accountId;
|
|
48
|
+
|
|
49
|
+
const response = await fetch(USAGE_URL, { method: "GET", headers, signal: AbortSignal.any(signals) });
|
|
50
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
51
|
+
|
|
52
|
+
return normalizeUsage((await response.json()) as CodexUsageResponse);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function normalizeUsage(payload: CodexUsageResponse): CodexUsage {
|
|
56
|
+
return {
|
|
57
|
+
unlimited: payload.credits?.unlimited === true,
|
|
58
|
+
primaryPercent: parseUsedPercent(payload.rate_limit?.primary_window),
|
|
59
|
+
secondaryPercent: parseUsedPercent(payload.rate_limit?.secondary_window),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function parseUsedPercent(window: CodexRateWindow | null | undefined): number | undefined {
|
|
64
|
+
const raw = window?.used_percent;
|
|
65
|
+
if (raw === undefined || raw === null) return undefined;
|
|
66
|
+
|
|
67
|
+
const value = typeof raw === "number" ? raw : Number(raw);
|
|
68
|
+
return Number.isFinite(value) ? Math.min(100, Math.max(0, value)) : undefined;
|
|
69
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { CodexUsageManager } from "./manager";
|
|
2
|
+
import { loadConfig } from "../shared/config";
|
|
3
|
+
import { isUsage } from "../shared/usage";
|
|
4
|
+
|
|
5
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
6
|
+
import type { AgentMessage } from "@earendil-works/pi-agent-core";
|
|
7
|
+
|
|
8
|
+
function hasCostedUsage(messages: AgentMessage[]): boolean {
|
|
9
|
+
return messages.some((message) => {
|
|
10
|
+
const usage = (message as { usage?: unknown }).usage;
|
|
11
|
+
if (!isUsage(usage)) return false;
|
|
12
|
+
|
|
13
|
+
return usage.cost.total > 0 || usage.input > 0 || usage.output > 0;
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default function (pi: ExtensionAPI) {
|
|
18
|
+
let codexUsageManager: CodexUsageManager | undefined = undefined;
|
|
19
|
+
|
|
20
|
+
pi.on("session_start", (_event, ctx) => {
|
|
21
|
+
if (!ctx.hasUI) return;
|
|
22
|
+
|
|
23
|
+
const config = loadConfig(ctx, "codexUsage");
|
|
24
|
+
if (!config) return;
|
|
25
|
+
|
|
26
|
+
codexUsageManager = new CodexUsageManager();
|
|
27
|
+
codexUsageManager.refresh(ctx);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
pi.on("model_select", (_event, ctx) => {
|
|
31
|
+
codexUsageManager?.refresh(ctx);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
pi.on("agent_end", (event, ctx) => {
|
|
35
|
+
if (!hasCostedUsage(event.messages)) return;
|
|
36
|
+
|
|
37
|
+
codexUsageManager?.refresh(ctx);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
pi.on("session_shutdown", () => {
|
|
41
|
+
codexUsageManager = undefined;
|
|
42
|
+
});
|
|
43
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { CODEX_PROVIDER, fetchCodexUsage, readAccountId } from "./client";
|
|
2
|
+
import { renderError, renderUsage } from "./status";
|
|
3
|
+
|
|
4
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
5
|
+
|
|
6
|
+
const STATUS_KEY = "codex-usage";
|
|
7
|
+
|
|
8
|
+
export class CodexUsageManager {
|
|
9
|
+
private inflight: Promise<void> | undefined = undefined;
|
|
10
|
+
|
|
11
|
+
async refresh(ctx: ExtensionContext): Promise<void> {
|
|
12
|
+
if (!this.isCodexContext(ctx)) {
|
|
13
|
+
ctx.ui.setStatus(STATUS_KEY, undefined);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (this.inflight) return this.inflight;
|
|
18
|
+
|
|
19
|
+
this.inflight = this.fetch(ctx).finally(() => {
|
|
20
|
+
this.inflight = undefined;
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
return this.inflight;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
private async fetch(ctx: ExtensionContext): Promise<void> {
|
|
27
|
+
try {
|
|
28
|
+
const token = await ctx.modelRegistry.getApiKeyForProvider(CODEX_PROVIDER);
|
|
29
|
+
if (!token) {
|
|
30
|
+
ctx.ui.setStatus(STATUS_KEY, undefined);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const usage = await fetchCodexUsage(token, readAccountId(ctx), ctx.signal);
|
|
35
|
+
if (!this.isCodexContext(ctx)) return;
|
|
36
|
+
|
|
37
|
+
ctx.ui.setStatus(STATUS_KEY, renderUsage(ctx.ui.theme, usage));
|
|
38
|
+
} catch (error) {
|
|
39
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
40
|
+
ctx.ui.setStatus(STATUS_KEY, renderError(ctx.ui.theme, message));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
private isCodexContext(ctx: ExtensionContext): boolean {
|
|
45
|
+
return ctx.model?.provider === CODEX_PROVIDER;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
|
|
3
|
+
import type { CodexUsage } from "./client";
|
|
4
|
+
|
|
5
|
+
const LABEL = "Codex";
|
|
6
|
+
|
|
7
|
+
/** Render a footer status string for normalized Codex usage. */
|
|
8
|
+
export function renderUsage(theme: Theme, usage: CodexUsage): string {
|
|
9
|
+
const label = theme.fg("dim", LABEL);
|
|
10
|
+
|
|
11
|
+
if (usage.unlimited || (usage.primaryPercent === undefined && usage.secondaryPercent === undefined)) {
|
|
12
|
+
return `${label} ${theme.fg("success", "unlimited")}`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const fiveHour = renderLane(theme, "5h", usage.primaryPercent);
|
|
16
|
+
const sevenDay = renderLane(theme, "7d", usage.secondaryPercent);
|
|
17
|
+
return `${label} ${fiveHour} ${sevenDay}`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Render a footer status string for a usage fetch error. */
|
|
21
|
+
export function renderError(theme: Theme, message: string): string {
|
|
22
|
+
return `${theme.fg("dim", LABEL)} ${theme.fg("warning", `usage unavailable: ${message}`)}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function renderLane(theme: Theme, label: string, percent: number | undefined): string {
|
|
26
|
+
const text = `${label} ${percent === undefined ? "?" : percent.toFixed(1)}%`;
|
|
27
|
+
|
|
28
|
+
if (percent && percent > 90) return theme.fg("error", text);
|
|
29
|
+
if (percent && percent > 70) return theme.fg("warning", text);
|
|
30
|
+
return theme.fg("dim", text);
|
|
31
|
+
}
|
|
@@ -35,15 +35,11 @@ class FooterComponent implements Component {
|
|
|
35
35
|
private getLeft(): string {
|
|
36
36
|
const cwd = this.ctx.sessionManager.getCwd();
|
|
37
37
|
const url = pathToFileURL(resolve(cwd));
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
const cwdText = linkText(formatCwd(cwd, homedir()), url.href);
|
|
40
39
|
const branch = this.footerData.getGitBranch();
|
|
41
|
-
if (branch) text = `${text} [${branch}]`;
|
|
42
|
-
|
|
43
40
|
const sessionName = this.ctx.sessionManager.getSessionName();
|
|
44
|
-
if (sessionName) text = `${text} • ${sessionName}`;
|
|
45
41
|
|
|
46
|
-
return this.theme.fg("dim",
|
|
42
|
+
return this.theme.fg("dim", [cwdText, branch, sessionName].filter(Boolean).join(" • "));
|
|
47
43
|
}
|
|
48
44
|
|
|
49
45
|
private getRight(): string {
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { Container, Spacer, Text } from "@earendil-works/pi-tui";
|
|
2
|
+
import { Type } from "typebox";
|
|
3
|
+
|
|
4
|
+
import { loadConfig } from "../shared/config";
|
|
5
|
+
import { sanitizeText } from "../shared/format";
|
|
6
|
+
|
|
7
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
8
|
+
|
|
9
|
+
export default function (pi: ExtensionAPI) {
|
|
10
|
+
pi.on("session_start", (_event, ctx) => {
|
|
11
|
+
if (!ctx.hasUI) return;
|
|
12
|
+
|
|
13
|
+
const config = loadConfig(ctx, "setSessionName");
|
|
14
|
+
if (!config) return;
|
|
15
|
+
|
|
16
|
+
pi.registerTool({
|
|
17
|
+
name: "set_session_name",
|
|
18
|
+
label: "set session name",
|
|
19
|
+
description:
|
|
20
|
+
"Set or refresh the current session's display name, shown in the session selector " +
|
|
21
|
+
"instead of the first-message preview. Use this when a concise name would make the " +
|
|
22
|
+
"session easier to recognize later, such as after a long opening prompt or a substantial " +
|
|
23
|
+
"topic shift.",
|
|
24
|
+
promptSnippet: "Set or refresh the current session's concise display name",
|
|
25
|
+
promptGuidelines: [
|
|
26
|
+
"Use set_session_name when the session would benefit from a concise, recognizable name, especially after a long, vague, or pasted opening prompt.",
|
|
27
|
+
"Use set_session_name to refresh the name only after a substantial shift in the conversation's focus; do not rename for minor follow-ups.",
|
|
28
|
+
"Include a concise set_session_name reason when it would help explain why the name identifies the session.",
|
|
29
|
+
],
|
|
30
|
+
parameters: Type.Object({
|
|
31
|
+
name: Type.String({
|
|
32
|
+
minLength: 1,
|
|
33
|
+
maxLength: 120,
|
|
34
|
+
description:
|
|
35
|
+
"Concise session display name. Use a short, recognizable phrase in sentence case, " +
|
|
36
|
+
"ideally <= 72 characters. Do not use surrounding quotes, trailing punctuation, or " +
|
|
37
|
+
"generic prefixes like \"Chat about\". Examples: \"Refactor auth module\", " +
|
|
38
|
+
"\"Debug flaky CI pipeline\", \"Draft Q3 planning doc\".",
|
|
39
|
+
}),
|
|
40
|
+
reason: Type.Optional(Type.String({
|
|
41
|
+
minLength: 1,
|
|
42
|
+
maxLength: 240,
|
|
43
|
+
description:
|
|
44
|
+
"Optional concise reason for naming or renaming the current session. Explain what " +
|
|
45
|
+
"made the name useful, such as a long pasted prompt, ambiguous first message, task " +
|
|
46
|
+
"handoff, or substantial topic shift. Write a complete sentence, and keep it brief " +
|
|
47
|
+
"and user-facing. Examples: \"The long pasted prompt needed a stable label.\", " +
|
|
48
|
+
"\"The focus shifted from debugging to README updates.\", " +
|
|
49
|
+
"\"The task migrated from a previous session.\"",
|
|
50
|
+
})),
|
|
51
|
+
}),
|
|
52
|
+
renderCall(args, theme) {
|
|
53
|
+
const name = sanitizeText(args.name);
|
|
54
|
+
const reason = sanitizeText(args.reason ?? "");
|
|
55
|
+
|
|
56
|
+
const container = new Container();
|
|
57
|
+
container.addChild(new Text(`${theme.bold(theme.fg("toolTitle", "set_session_name"))} ${theme.fg("accent", `"${name}"`)}`, 0, 0));
|
|
58
|
+
|
|
59
|
+
if (reason) {
|
|
60
|
+
container.addChild(new Spacer(1));
|
|
61
|
+
container.addChild(new Text(theme.fg("muted", reason), 0, 0));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return container;
|
|
65
|
+
},
|
|
66
|
+
renderResult(result, _options, theme, context) {
|
|
67
|
+
const output = result.content
|
|
68
|
+
.filter((content) => content.type === "text")
|
|
69
|
+
.map((content) => content.text)
|
|
70
|
+
.join("\n");
|
|
71
|
+
|
|
72
|
+
return new Text(context.isError ? theme.fg("error", "\n" + output) : "", 0, 0);
|
|
73
|
+
},
|
|
74
|
+
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
75
|
+
const name = sanitizeText(params.name);
|
|
76
|
+
if (!name) throw new Error("Session name was empty after normalization. Provide a short, non-empty phrase.");
|
|
77
|
+
|
|
78
|
+
const previous = pi.getSessionName() ?? null;
|
|
79
|
+
if (previous === name) {
|
|
80
|
+
return {
|
|
81
|
+
content: [{ type: "text", text: `Session is already named "${name}". No change.` }],
|
|
82
|
+
details: { changed: false, previous },
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
pi.setSessionName(name);
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
content: [{ type: "text", text: `${previous ? `Renamed session from "${previous}" to "${name}".` : `Named session "${name}".`}` }],
|
|
90
|
+
details: { changed: true, previous },
|
|
91
|
+
};
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
}
|
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
import * as z from "zod";
|
|
2
2
|
|
|
3
|
+
import { codexUsageConfigSchema } from "../../codex-usage/config";
|
|
3
4
|
import { editorConfigSchema } from "../../editor/config";
|
|
4
5
|
import { footerConfigSchema } from "../../footer/config";
|
|
5
6
|
import { fullscreenConfigSchema } from "../../fullscreen/config";
|
|
6
|
-
import { recapConfigSchema } from "../../recap/config";
|
|
7
7
|
import { presetsConfigSchema } from "../../presets/config";
|
|
8
|
+
import { recapConfigSchema } from "../../recap/config";
|
|
9
|
+
import { setSessionNameConfigSchema } from "../../set-session-name/config";
|
|
8
10
|
|
|
9
11
|
export const configSchemas = {
|
|
12
|
+
codexUsage: codexUsageConfigSchema,
|
|
10
13
|
editor: editorConfigSchema,
|
|
11
14
|
footer: footerConfigSchema,
|
|
12
15
|
fullscreen: fullscreenConfigSchema,
|
|
13
|
-
recap: recapConfigSchema,
|
|
14
16
|
presets: presetsConfigSchema,
|
|
17
|
+
recap: recapConfigSchema,
|
|
18
|
+
setSessionName: setSessionNameConfigSchema,
|
|
15
19
|
};
|
|
16
20
|
|
|
17
21
|
export type ConfigField = keyof typeof configSchemas;
|