pi-spark 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -11,6 +11,7 @@ A small, opinionated collection of [pi](https://pi.dev/) extensions.
11
11
  - **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
12
  - **Presets:** switches named model presets with `/preset`, `--preset`, and quick cycle shortcuts.
13
13
  - **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).
14
+ - **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
15
 
15
16
  ![Screenshot](./assets/screenshot.png)
16
17
 
@@ -32,6 +33,8 @@ pi install git:github.com/zlliang/pi-spark
32
33
 
33
34
  Spark reads config from `~/.pi/agent/spark.json` and from the current project’s `.pi/spark.json`. Project config overrides matching global fields.
34
35
 
36
+ All extensions are enabled by default. Set a specific extension to `false` to disable it, for example, `"footer": false` disables the footer extension.
37
+
35
38
  Example:
36
39
 
37
40
  ```json
@@ -41,32 +44,53 @@ Example:
41
44
  },
42
45
  "footer": false,
43
46
  "presets": {
44
- "gpt": {
45
- "provider": "openai-codex",
46
- "model": "gpt-5.5",
47
- "thinkingLevel": "medium"
48
- },
49
47
  "claude-opus": {
50
- "provider": "anthropic",
51
48
  "model": "claude-opus-4-8",
49
+ "provider": "anthropic",
52
50
  "thinkingLevel": "high"
51
+ },
52
+ "gpt": {
53
+ "model": "gpt-5.5",
54
+ "provider": "openai-codex",
55
+ "thinkingLevel": "medium"
53
56
  }
54
57
  },
55
58
  "recap": {
56
59
  "idle": 180000,
57
- "provider": "openai-codex",
58
60
  "model": "gpt-5.4-mini",
61
+ "provider": "openai-codex",
59
62
  "thinkingLevel": "off"
60
63
  }
61
64
  }
62
65
  ```
63
66
 
64
- Notes:
67
+ ### Editor
68
+
69
+ - `editor.spinner` controls the working indicator style and can be `dots`, `lights`, or `tildes`.
70
+
71
+ ### Footer
72
+
73
+ - pi-spark replaces the footer with a compact one-line view of session metadata, extension statuses, cost, and context usage.
74
+
75
+ ### Fullscreen
76
+
77
+ - 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.
78
+
79
+ ### Presets
80
+
81
+ - Each key under `presets` defines a named model preset with `provider`, `model`, and optional `thinkingLevel` fields.
82
+
83
+ Use presets in these ways:
84
+
85
+ - Select interactively with `/preset` or `/preset <key>`
86
+ - Start pi with a preset using `pi --preset <key>`
87
+ - Cycle presets with `ctrl+super+p` and `ctrl+shift+super+p` (`super` is `command` on macOS)
88
+
89
+ ### Recap
90
+
91
+ - pi-spark can generate a short recap after the session has been idle or when you run `/recap` manually.
92
+ - The `recap.idle` value is in milliseconds and must be at least `5000`. The recap model can be customized with `provider`, `model`, and `thinkingLevel`.
93
+
94
+ ### Set session name
65
95
 
66
- - Set an extension key to `false` to disable it.
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`.
96
+ - The agent can set or refresh the current session's display name and optionally give a reason.
@@ -0,0 +1,3 @@
1
+ import * as z from "zod";
2
+
3
+ export const setSessionNameConfigSchema = z.object({});
@@ -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
+ }
@@ -3,15 +3,17 @@ import * as z from "zod";
3
3
  import { editorConfigSchema } from "../../editor/config";
4
4
  import { footerConfigSchema } from "../../footer/config";
5
5
  import { fullscreenConfigSchema } from "../../fullscreen/config";
6
- import { recapConfigSchema } from "../../recap/config";
7
6
  import { presetsConfigSchema } from "../../presets/config";
7
+ import { recapConfigSchema } from "../../recap/config";
8
+ import { setSessionNameConfigSchema } from "../../set-session-name/config";
8
9
 
9
10
  export const configSchemas = {
10
11
  editor: editorConfigSchema,
11
12
  footer: footerConfigSchema,
12
13
  fullscreen: fullscreenConfigSchema,
13
- recap: recapConfigSchema,
14
14
  presets: presetsConfigSchema,
15
+ recap: recapConfigSchema,
16
+ setSessionName: setSessionNameConfigSchema,
15
17
  };
16
18
 
17
19
  export type ConfigField = keyof typeof configSchemas;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-spark",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "A small, opinionated collection of pi extensions",
5
5
  "keywords": [
6
6
  "pi-coding-agent",