mitsupi 1.1.0 → 1.1.1

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/CHANGELOG.md CHANGED
@@ -2,6 +2,11 @@
2
2
 
3
3
  All notable changes to agent-stuff are documented here.
4
4
 
5
+ ## 1.1.1
6
+
7
+ * Removed the deprecated `qna` extension.
8
+ * Added `uv` extension and skill for uv integration.
9
+
5
10
  ## 1.1.0
6
11
 
7
12
  * Added project review guidelines and preserved review state across navigation.
package/README.md CHANGED
@@ -16,7 +16,6 @@ All skill files are in the [`skills`](skills) folder:
16
16
  * [`/web-browser`](skills/web-browser) - Claude Skill for using Puppeteer in a Node environment to browse the web
17
17
  * [`/tmux`](skills/tmux) - Claude Skill for driving tmux directly with keystrokes and pane output scraping
18
18
  * [`/sentry`](skills/sentry) - Alternative way to access Sentry as a Claude Skill for reading issues
19
- * [`/improve-skill`](skills/improve-skill) - Claude Skill for analyzing coding agent sessions to improve or create new skills
20
19
  * [`/pi-share`](skills/pi-share) - Claude Skill for loading and parsing session transcripts from shittycodingagent.ai
21
20
  * [`/anachb`](skills/anachb) - Claude Skill for querying Austrian public transport (VOR AnachB) for departures, routes, and disruptions
22
21
  * [`/oebb-scotty`](skills/oebb-scotty) - Claude Skill for Austrian rail travel planning via ÖBB Scotty API
@@ -26,8 +25,7 @@ All skill files are in the [`skills`](skills) folder:
26
25
 
27
26
  Custom extensions for the PI Coding Agent can be found in the [`pi-extensions`](pi-extensions) folder:
28
27
 
29
- * [`qna.ts`](pi-extensions/qna.ts) - Extracts questions from the last assistant message into the editor for easy answering. Uses Claude Haiku for cost-efficient extraction when available.
30
- * [`answer.ts`](pi-extensions/answer.ts) - Alternative to `qna.ts` with a custom interactive TUI for answering questions one by one.
28
+ * [`answer.ts`](pi-extensions/answer.ts) - Interactive TUI for answering questions one by one.
31
29
  * [`review.ts`](pi-extensions/review.ts) - Code review command inspired by Codex. Supports reviewing uncommitted changes, against a base branch (PR style), specific commits, or with custom instructions. Includes Ctrl+R shortcut.
32
30
  * [`loop.ts`](pi-extensions/loop.ts) - Runs a prompt loop for rapid iterative coding with optional auto-continue control.
33
31
  * [`files.ts`](pi-extensions/files.ts) - Unified file browser that merges git status (dirty first) with session references, plus reveal/open/edit and diff actions.
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ echo "Error: pip is disabled. Use uv instead:" >&2
3
+ echo "" >&2
4
+ echo " To install a package for a script: uv run --with PACKAGE python script.py" >&2
5
+ echo " To add a dependency to the project: uv add PACKAGE" >&2
6
+ echo "" >&2
7
+ exit 1
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ echo "Error: pip3 is disabled. Use uv instead:" >&2
3
+ echo "" >&2
4
+ echo " To install a package for a script: uv run --with PACKAGE python script.py" >&2
5
+ echo " To add a dependency to the project: uv add PACKAGE" >&2
6
+ echo "" >&2
7
+ exit 1
@@ -0,0 +1,3 @@
1
+ #!/bin/bash
2
+ echo "Error: Use uv instead of poetry (uv init, uv add, uv sync, uv run)" >&2
3
+ exit 1
@@ -0,0 +1,50 @@
1
+ #!/bin/bash
2
+
3
+ # Check for disallowed module invocations
4
+ for arg in "$@"; do
5
+ case "$arg" in
6
+ -mpip|-m\ pip|pip)
7
+ echo "Error: 'python -m pip' is disabled. Use uv instead:" >&2
8
+ echo "" >&2
9
+ echo " To install a package for a script: uv run --with PACKAGE python script.py" >&2
10
+ echo " To add a dependency to the project: uv add PACKAGE" >&2
11
+ echo "" >&2
12
+ exit 1
13
+ ;;
14
+ -mvenv|-m\ venv|venv)
15
+ echo "Error: 'python -m venv' is disabled. Use uv instead:" >&2
16
+ echo "" >&2
17
+ echo " To create a virtual environment: uv venv" >&2
18
+ echo "" >&2
19
+ exit 1
20
+ ;;
21
+ esac
22
+ done
23
+
24
+ # Check for -m flag followed by pip or venv
25
+ prev=""
26
+ for arg in "$@"; do
27
+ if [ "$prev" = "-m" ]; then
28
+ case "$arg" in
29
+ pip)
30
+ echo "Error: 'python -m pip' is disabled. Use uv instead:" >&2
31
+ echo "" >&2
32
+ echo " To install a package for a script: uv run --with PACKAGE python script.py" >&2
33
+ echo " To add a dependency to the project: uv add PACKAGE" >&2
34
+ echo "" >&2
35
+ exit 1
36
+ ;;
37
+ venv)
38
+ echo "Error: 'python -m venv' is disabled. Use uv instead:" >&2
39
+ echo "" >&2
40
+ echo " To create a virtual environment: uv venv" >&2
41
+ echo "" >&2
42
+ exit 1
43
+ ;;
44
+ esac
45
+ fi
46
+ prev="$arg"
47
+ done
48
+
49
+ # Dispatch to uv run python
50
+ exec uv run python "$@"
@@ -0,0 +1,50 @@
1
+ #!/bin/bash
2
+
3
+ # Check for disallowed module invocations
4
+ for arg in "$@"; do
5
+ case "$arg" in
6
+ -mpip|-m\ pip|pip)
7
+ echo "Error: 'python3 -m pip' is disabled. Use uv instead:" >&2
8
+ echo "" >&2
9
+ echo " To install a package for a script: uv run --with PACKAGE python script.py" >&2
10
+ echo " To add a dependency to the project: uv add PACKAGE" >&2
11
+ echo "" >&2
12
+ exit 1
13
+ ;;
14
+ -mvenv|-m\ venv|venv)
15
+ echo "Error: 'python3 -m venv' is disabled. Use uv instead:" >&2
16
+ echo "" >&2
17
+ echo " To create a virtual environment: uv venv" >&2
18
+ echo "" >&2
19
+ exit 1
20
+ ;;
21
+ esac
22
+ done
23
+
24
+ # Check for -m flag followed by pip or venv
25
+ prev=""
26
+ for arg in "$@"; do
27
+ if [ "$prev" = "-m" ]; then
28
+ case "$arg" in
29
+ pip)
30
+ echo "Error: 'python3 -m pip' is disabled. Use uv instead:" >&2
31
+ echo "" >&2
32
+ echo " To install a package for a script: uv run --with PACKAGE python script.py" >&2
33
+ echo " To add a dependency to the project: uv add PACKAGE" >&2
34
+ echo "" >&2
35
+ exit 1
36
+ ;;
37
+ venv)
38
+ echo "Error: 'python3 -m venv' is disabled. Use uv instead:" >&2
39
+ echo "" >&2
40
+ echo " To create a virtual environment: uv venv" >&2
41
+ echo "" >&2
42
+ exit 1
43
+ ;;
44
+ esac
45
+ fi
46
+ prev="$arg"
47
+ done
48
+
49
+ # Dispatch to uv run python
50
+ exec uv run python "$@"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mitsupi",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "Armin's pi coding agent commands, skills, extensions, and themes",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Q&A extraction hook - extracts questions from assistant responses
3
3
  *
4
- * Alternative to qna.ts with a custom interactive TUI for answering questions.
4
+ * Custom interactive TUI for answering questions.
5
5
  *
6
6
  * Demonstrates the "prompt generator" pattern with custom TUI:
7
7
  * 1. /answer command gets the last assistant message
@@ -0,0 +1,42 @@
1
+ /**
2
+ * UV Extension - Redirects Python tooling to uv equivalents
3
+ *
4
+ * This extension wraps the bash tool to prepend intercepted-commands to PATH,
5
+ * which contains shim scripts that intercept common Python tooling commands
6
+ * and redirect agents to use uv instead.
7
+ *
8
+ * Intercepted commands:
9
+ * - pip/pip3: Blocked with suggestions to use `uv add` or `uv run --with`
10
+ * - poetry: Blocked with uv equivalents (uv init, uv add, uv sync, uv run)
11
+ * - python/python3: Redirected to `uv run python`, with special handling to
12
+ * block `python -m pip` and `python -m venv`
13
+ *
14
+ * The shim scripts are located in the intercepted-commands directory and
15
+ * provide helpful error messages with the equivalent uv commands.
16
+ */
17
+
18
+ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
19
+ import { createBashTool } from "@mariozechner/pi-coding-agent";
20
+ import { dirname, join } from "path";
21
+ import { fileURLToPath } from "url";
22
+
23
+ const __dirname = dirname(fileURLToPath(import.meta.url));
24
+ const interceptedCommandsPath = join(__dirname, "..", "intercepted-commands");
25
+
26
+ export default function (pi: ExtensionAPI) {
27
+ const cwd = process.cwd();
28
+ const bashTool = createBashTool(cwd, {
29
+ commandPrefix: `export PATH="${interceptedCommandsPath}:$PATH"`,
30
+ });
31
+
32
+ pi.on("session_start", (_event, ctx) => {
33
+ ctx.ui.notify("UV interceptor loaded", "info");
34
+ });
35
+
36
+ pi.registerTool({
37
+ ...bashTool,
38
+ async execute(id, params, onUpdate, _ctx, signal) {
39
+ return bashTool.execute(id, params, signal, onUpdate);
40
+ },
41
+ });
42
+ }
@@ -0,0 +1,36 @@
1
+ ---
2
+ name: uv
3
+ description: "Use `uv` instead of pip/python/venv. Run scripts with `uv run script.py`, add deps with `uv add`, use inline script metadata for standalone scripts."
4
+ ---
5
+
6
+ ## Quick Reference
7
+
8
+ ```bash
9
+ uv run script.py # Run a script
10
+ uv run --with requests script.py # Run with ad-hoc dependency
11
+ uv add requests # Add dependency to project
12
+ uv init --script foo.py # Create script with inline metadata
13
+ ```
14
+
15
+ ## Inline Script Dependencies
16
+
17
+ ```python
18
+ # /// script
19
+ # requires-python = ">=3.12"
20
+ # dependencies = ["requests"]
21
+ # ///
22
+ ```
23
+
24
+ See [scripts.md](scripts.md) for full details on running scripts, locking, and reproducibility.
25
+
26
+ ## Build Backend
27
+
28
+ Use `uv_build` for pure Python packages:
29
+
30
+ ```toml
31
+ [build-system]
32
+ requires = ["uv_build>=0.9.28,<0.10.0"]
33
+ build-backend = "uv_build"
34
+ ```
35
+
36
+ See [build.md](build.md) for project structure, namespaces, and file inclusion.
@@ -0,0 +1,65 @@
1
+ # uv Build Backend
2
+
3
+ Use `uv_build` for pure Python packages. For extension modules, use `hatchling` instead.
4
+
5
+ ## pyproject.toml
6
+
7
+ ```toml
8
+ [project]
9
+ name = "my-package"
10
+ version = "0.1.0"
11
+ requires-python = ">=3.12"
12
+ dependencies = []
13
+
14
+ [build-system]
15
+ requires = ["uv_build>=0.9.28,<0.10.0"]
16
+ build-backend = "uv_build"
17
+ ```
18
+
19
+ ## Project Structure
20
+
21
+ Default layout uses `src/<package_name>/__init__.py`:
22
+
23
+ ```
24
+ pyproject.toml
25
+ src/
26
+ └── my_package/
27
+ └── __init__.py
28
+ ```
29
+
30
+ Package name is normalized: `Foo-Bar` → `foo_bar`.
31
+
32
+ ### Custom Module Location
33
+
34
+ ```toml
35
+ [tool.uv.build-backend]
36
+ module-name = "mymodule"
37
+ module-root = "" # Use project root instead of src/
38
+ ```
39
+
40
+ ### Namespace Packages
41
+
42
+ For `foo.bar` namespace:
43
+
44
+ ```
45
+ src/foo/bar/__init__.py # No __init__.py in foo/
46
+ ```
47
+
48
+ ```toml
49
+ [tool.uv.build-backend]
50
+ module-name = "foo.bar"
51
+ ```
52
+
53
+ ## File Inclusion/Exclusion
54
+
55
+ Excludes `__pycache__`, `*.pyc`, `*.pyo` by default.
56
+
57
+ ```toml
58
+ [tool.uv.build-backend]
59
+ source-include = ["assets/**"]
60
+ source-exclude = ["/dist", "tests/**"]
61
+ ```
62
+
63
+ - Includes are anchored (`pyproject.toml` = only root)
64
+ - Excludes are not anchored (`__pycache__` = all dirs named that)
65
+ - Use `/prefix` to anchor excludes
@@ -0,0 +1,98 @@
1
+ # Running Scripts with uv
2
+
3
+ ## Basic Usage
4
+
5
+ ```bash
6
+ uv run script.py # Run a script
7
+ uv run script.py arg1 arg2 # With arguments
8
+ uv run --python 3.10 script.py # Specific Python version
9
+ echo 'print("hi")' | uv run - # From stdin
10
+ ```
11
+
12
+ In a project directory, use `--no-project` to skip installing the project:
13
+
14
+ ```bash
15
+ uv run --no-project script.py
16
+ ```
17
+
18
+ ## Ad-hoc Dependencies
19
+
20
+ ```bash
21
+ uv run --with requests script.py
22
+ uv run --with 'requests>2,<3' script.py
23
+ uv run --with requests --with rich script.py
24
+ ```
25
+
26
+ ## Inline Script Metadata (Recommended)
27
+
28
+ Declare dependencies directly in the script:
29
+
30
+ ```python
31
+ # /// script
32
+ # requires-python = ">=3.12"
33
+ # dependencies = [
34
+ # "requests<3",
35
+ # "rich",
36
+ # ]
37
+ # ///
38
+
39
+ import requests
40
+ from rich import print
41
+ ```
42
+
43
+ Then just: `uv run script.py`
44
+
45
+ ### Managing Dependencies
46
+
47
+ ```bash
48
+ uv init --script example.py --python 3.12 # Create script with metadata
49
+ uv add --script example.py requests rich # Add dependencies
50
+ ```
51
+
52
+ ### Alternative Index
53
+
54
+ ```bash
55
+ uv add --index "https://example.com/simple" --script example.py requests
56
+ ```
57
+
58
+ Adds to metadata:
59
+
60
+ ```python
61
+ # [[tool.uv.index]]
62
+ # url = "https://example.com/simple"
63
+ ```
64
+
65
+ ## Locking Dependencies
66
+
67
+ ```bash
68
+ uv lock --script example.py # Creates example.py.lock
69
+ ```
70
+
71
+ ## Reproducibility
72
+
73
+ Pin resolution date:
74
+
75
+ ```python
76
+ # /// script
77
+ # dependencies = ["requests"]
78
+ # [tool.uv]
79
+ # exclude-newer = "2023-10-16T00:00:00Z"
80
+ # ///
81
+ ```
82
+
83
+ ## Executable Scripts (Shebang)
84
+
85
+ ```python
86
+ #!/usr/bin/env -S uv run --script
87
+ # /// script
88
+ # dependencies = ["httpx"]
89
+ # ///
90
+
91
+ import httpx
92
+ print(httpx.get("https://example.com"))
93
+ ```
94
+
95
+ ```bash
96
+ chmod +x myscript
97
+ ./myscript
98
+ ```
@@ -1,167 +0,0 @@
1
- /**
2
- * Q&A extraction hook - extracts questions from assistant responses
3
- *
4
- * Demonstrates the "prompt generator" pattern:
5
- * 1. /qna command gets the last assistant message
6
- * 2. Shows a spinner while extracting (hides editor)
7
- * 3. Loads the result into the editor for user to fill in answers
8
- */
9
-
10
- import { complete, type Model, type Api, type UserMessage } from "@mariozechner/pi-ai";
11
- import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
12
- import { BorderedLoader } from "@mariozechner/pi-coding-agent";
13
-
14
- const SYSTEM_PROMPT = `You are a question extractor. Given text from a conversation, extract any questions that need answering and format them for the user to fill in.
15
-
16
- Output format:
17
- - List each question on its own line, prefixed with "Q: "
18
- - After each question, add a blank line for the answer prefixed with "A: "
19
- - If no questions are found, output "No questions found in the last message."
20
- - When there is important context needed to answer a question, use quote characters (">") and add it into the question block as additional lines.
21
-
22
- Example output:
23
- Q: What is your preferred database?
24
- > we can only configure MySQL and PostgreSQL because of what is implemented.
25
- A:
26
-
27
- Q: Should we use TypeScript or JavaScript?
28
- A:
29
-
30
- Keep questions in the order they appeared. Be concise.`;
31
-
32
- const HAIKU_MODEL_ID = "claude-haiku-4-5";
33
-
34
- /**
35
- * Check if the current model is a Claude Opus or Sonnet model from Anthropic provider.
36
- * If so, try to use claude-haiku-4-5 instead for cost efficiency.
37
- */
38
- async function selectExtractionModel(
39
- currentModel: Model<Api>,
40
- modelRegistry: { find: (provider: string, modelId: string) => Model<Api> | undefined; getApiKey: (model: Model<Api>) => Promise<string | undefined> },
41
- ): Promise<Model<Api>> {
42
- // Only consider switching if the provider is anthropic and the model is opus or sonnet
43
- if (currentModel.provider !== "anthropic") {
44
- return currentModel;
45
- }
46
-
47
- const modelId = currentModel.id.toLowerCase();
48
- const isOpusOrSonnet = modelId.includes("opus") || modelId.includes("sonnet");
49
- if (!isOpusOrSonnet) {
50
- return currentModel;
51
- }
52
-
53
- // Try to find and use claude-haiku-4-5
54
- const haikuModel = modelRegistry.find("anthropic", HAIKU_MODEL_ID);
55
- if (!haikuModel) {
56
- return currentModel;
57
- }
58
-
59
- // Check if we have an API key for the haiku model
60
- const apiKey = await modelRegistry.getApiKey(haikuModel);
61
- if (!apiKey) {
62
- return currentModel;
63
- }
64
-
65
- return haikuModel;
66
- }
67
-
68
- export default function (pi: ExtensionAPI) {
69
- const qnaHandler = async (ctx: ExtensionContext) => {
70
- if (!ctx.hasUI) {
71
- ctx.ui.notify("qna requires interactive mode", "error");
72
- return;
73
- }
74
-
75
- if (!ctx.model) {
76
- ctx.ui.notify("No model selected", "error");
77
- return;
78
- }
79
-
80
- // Find the last assistant message on the current branch
81
- const branch = ctx.sessionManager.getBranch();
82
- let lastAssistantText: string | undefined;
83
-
84
- for (let i = branch.length - 1; i >= 0; i--) {
85
- const entry = branch[i];
86
- if (entry.type === "message") {
87
- const msg = entry.message;
88
- if ("role" in msg && msg.role === "assistant") {
89
- if (msg.stopReason !== "stop") {
90
- ctx.ui.notify(`Last assistant message incomplete (${msg.stopReason})`, "error");
91
- return;
92
- }
93
- const textParts = msg.content
94
- .filter((c): c is { type: "text"; text: string } => c.type === "text")
95
- .map((c) => c.text);
96
- if (textParts.length > 0) {
97
- lastAssistantText = textParts.join("\n");
98
- break;
99
- }
100
- }
101
- }
102
- }
103
-
104
- if (!lastAssistantText) {
105
- ctx.ui.notify("No assistant messages found", "error");
106
- return;
107
- }
108
-
109
- // Select the best model for extraction (prefer haiku for cost efficiency)
110
- const extractionModel = await selectExtractionModel(ctx.model, ctx.modelRegistry);
111
-
112
- // Run extraction with loader UI
113
- const result = await ctx.ui.custom<string | null>((tui, theme, _kb, done) => {
114
- const loader = new BorderedLoader(tui, theme, `Extracting questions using ${extractionModel.id}...`);
115
- loader.onAbort = () => done(null);
116
-
117
- // Do the work
118
- const doExtract = async () => {
119
- const apiKey = await ctx.modelRegistry.getApiKey(extractionModel);
120
- const userMessage: UserMessage = {
121
- role: "user",
122
- content: [{ type: "text", text: lastAssistantText! }],
123
- timestamp: Date.now(),
124
- };
125
-
126
- const response = await complete(
127
- extractionModel,
128
- { systemPrompt: SYSTEM_PROMPT, messages: [userMessage] },
129
- { apiKey, signal: loader.signal },
130
- );
131
-
132
- if (response.stopReason === "aborted") {
133
- return null;
134
- }
135
-
136
- return response.content
137
- .filter((c): c is { type: "text"; text: string } => c.type === "text")
138
- .map((c) => c.text)
139
- .join("\n");
140
- };
141
-
142
- doExtract()
143
- .then(done)
144
- .catch(() => done(null));
145
-
146
- return loader;
147
- });
148
-
149
- if (result === null) {
150
- ctx.ui.notify("Cancelled", "info");
151
- return;
152
- }
153
-
154
- ctx.ui.setEditorText(result);
155
- ctx.ui.notify("Questions loaded. Edit and submit when ready.", "info");
156
- };
157
-
158
- pi.registerCommand("qna", {
159
- description: "Extract questions from last assistant message into editor",
160
- handler: (_args, ctx) => qnaHandler(ctx),
161
- });
162
-
163
- pi.registerShortcut("ctrl+,", {
164
- description: "Extract questions into editor",
165
- handler: qnaHandler,
166
- });
167
- }
@@ -1,155 +0,0 @@
1
- ---
2
- name: improve-skill
3
- description: "Analyze coding agent session transcripts to improve existing skills or create new ones. Use when asked to improve a skill based on a session, or extract a new skill from session history."
4
- ---
5
-
6
- # Improve Skill
7
-
8
- This skill helps analyze coding agent sessions to improve or create skills. It works with Claude Code, Pi, and Codex session files.
9
-
10
- ## Quick Start
11
-
12
- Extract the current session and generate an improvement prompt:
13
-
14
- ```bash
15
- # Auto-detect agent and extract current session
16
- ./scripts/extract-session.js
17
- ```
18
-
19
- ## Session Extraction
20
-
21
- The `extract-session.js` script finds and parses session files from any of the three agents:
22
-
23
- ```bash
24
- # Auto-detect (uses most recent session for current working directory)
25
- ./scripts/extract-session.js
26
-
27
- # Specify agent type
28
- ./scripts/extract-session.js --agent claude
29
- ./scripts/extract-session.js --agent pi
30
- ./scripts/extract-session.js --agent codex
31
-
32
- # Specify a different working directory
33
- ./scripts/extract-session.js --cwd /path/to/project
34
-
35
- # Use a specific session file
36
- ./scripts/extract-session.js /path/to/session.jsonl
37
- ```
38
-
39
- **Session file locations:**
40
- - **Claude Code**: `~/.claude/projects/<encoded-cwd>/*.jsonl`
41
- - **Pi**: `~/.pi/agent/sessions/<encoded-cwd>/*.jsonl`
42
- - **Codex**: `~/.codex/sessions/YYYY/MM/DD/*.jsonl`
43
-
44
- ## Workflow: Improve an Existing Skill
45
-
46
- When asked to improve a skill based on a session:
47
-
48
- 1. **Extract the session transcript:**
49
- ```bash
50
- ./scripts/extract-session.js > /tmp/session-transcript.txt
51
- ```
52
-
53
- 2. **Find the existing skill** in one of these locations:
54
- - `~/.codex/skills/<skill-name>/SKILL.md`
55
- - `~/.claude/skills/<skill-name>/SKILL.md`
56
- - `~/.pi/agent/skills/<skill-name>/SKILL.md`
57
-
58
- 3. **Generate an improvement prompt** for a new session:
59
-
60
- ```
61
- ═══════════════════════════════════════════════════════════════════════════════
62
- COPY THE FOLLOWING PROMPT INTO A NEW AGENT SESSION:
63
- ═══════════════════════════════════════════════════════════════════════════════
64
-
65
- I need to improve the "<skill-name>" skill based on a session where I used it.
66
-
67
- First, read the current skill at: <path-to-skill>
68
-
69
- Then analyze this session transcript to understand:
70
- - Where I struggled to use the skill correctly
71
- - What information was missing from the skill
72
- - What examples would have helped
73
- - What I had to figure out on my own
74
-
75
- <session_transcript>
76
- <paste transcript here>
77
- </session_transcript>
78
-
79
- Based on this analysis, improve the skill by:
80
- 1. Adding missing instructions or clarifications
81
- 2. Adding examples for common use cases discovered
82
- 3. Fixing any incorrect guidance
83
- 4. Making the skill more concise where possible
84
-
85
- Write the improved skill back to the same location.
86
-
87
- ═══════════════════════════════════════════════════════════════════════════════
88
- ```
89
-
90
- ## Workflow: Create a New Skill
91
-
92
- When asked to create a new skill from a session:
93
-
94
- 1. **Extract the session transcript:**
95
- ```bash
96
- ./scripts/extract-session.js > /tmp/session-transcript.txt
97
- ```
98
-
99
- 2. **Generate a creation prompt** for a new session:
100
-
101
- ```
102
- ═══════════════════════════════════════════════════════════════════════════════
103
- COPY THE FOLLOWING PROMPT INTO A NEW AGENT SESSION:
104
- ═══════════════════════════════════════════════════════════════════════════════
105
-
106
- Analyze this session transcript to extract a reusable skill called "<skill-name>":
107
-
108
- <session_transcript>
109
- <paste transcript here>
110
- </session_transcript>
111
-
112
- Create a new skill that captures:
113
- 1. The core capability or workflow demonstrated
114
- 2. Key commands, APIs, or patterns used
115
- 3. Common pitfalls and how to avoid them
116
- 4. Example usage for typical scenarios
117
-
118
- Write the skill to: ~/.codex/skills/<skill-name>/SKILL.md
119
-
120
- Use this format:
121
- ---
122
- name: <skill-name>
123
- description: "<one-line description>"
124
- ---
125
-
126
- # <Skill Name> Skill
127
-
128
- <overview and quick reference>
129
-
130
- ## <Section for each major capability>
131
-
132
- <instructions and examples>
133
-
134
- ═══════════════════════════════════════════════════════════════════════════════
135
- ```
136
-
137
- ## Why a Separate Session?
138
-
139
- The improvement prompt is meant to be copied into a **fresh agent session** because:
140
-
141
- 1. **Token efficiency** - The current session already has a lot of context; starting fresh means only the transcript and skill are loaded
142
- 2. **Clean analysis** - The new session can focus purely on improvement without being influenced by the current task
143
- 3. **Reproducibility** - The prompt is self-contained and can be shared or reused
144
-
145
- ## Tips for Good Skill Improvements
146
-
147
- When analyzing a transcript, look for:
148
-
149
- - **Confusion patterns** - Where did the agent retry or change approach?
150
- - **Missing examples** - What specific commands or code patterns were discovered?
151
- - **Workarounds** - What did the agent have to figure out that wasn't documented?
152
- - **Errors** - What failed and how was it resolved?
153
- - **Successful patterns** - What worked well and should be highlighted?
154
-
155
- Keep skills concise - focus on the most important information and examples.
@@ -1,349 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * Extract session transcript from Claude Code, Pi, or Codex session files.
5
- *
6
- * Usage:
7
- * ./extract-session.js [session-path]
8
- * ./extract-session.js --agent claude|pi|codex [--cwd /path/to/dir]
9
- *
10
- * If no arguments, auto-detects based on current working directory.
11
- */
12
-
13
- const fs = require('fs');
14
- const path = require('path');
15
- const os = require('os');
16
-
17
- // Parse arguments
18
- const args = process.argv.slice(2);
19
- let sessionPath = null;
20
- let agent = null;
21
- let cwd = process.cwd();
22
-
23
- for (let i = 0; i < args.length; i++) {
24
- if (args[i] === '--agent' && args[i + 1]) {
25
- agent = args[++i];
26
- } else if (args[i] === '--cwd' && args[i + 1]) {
27
- cwd = args[++i];
28
- } else if (!args[i].startsWith('-')) {
29
- sessionPath = args[i];
30
- }
31
- }
32
-
33
- /**
34
- * Encode CWD for session path lookup
35
- */
36
- function encodeCwd(cwd, style) {
37
- if (style === 'pi') {
38
- // Pi uses: --<cwd-without-leading-slash-with-slashes-as-dashes>--
39
- // e.g., /Users/mitsuhiko/Development/myproject -> --Users-mitsuhiko-Development-myproject--
40
- const safePath = `--${cwd.replace(/^[/\\]/, '').replace(/[/\\:]/g, '-')}--`;
41
- return safePath;
42
- }
43
- // Claude Code: just replace / with -
44
- return cwd.replace(/\//g, '-');
45
- }
46
-
47
- /**
48
- * Find the most recent session file in a directory
49
- */
50
- function findMostRecentSession(dir) {
51
- if (!fs.existsSync(dir)) return null;
52
-
53
- const files = fs.readdirSync(dir)
54
- .filter(f => f.endsWith('.jsonl'))
55
- .map(f => ({
56
- name: f,
57
- path: path.join(dir, f),
58
- mtime: fs.statSync(path.join(dir, f)).mtime
59
- }))
60
- .sort((a, b) => b.mtime - a.mtime);
61
-
62
- return files.length > 0 ? files[0].path : null;
63
- }
64
-
65
- /**
66
- * Find Codex session matching CWD
67
- */
68
- function findCodexSession(targetCwd) {
69
- const baseDir = path.join(os.homedir(), '.codex', 'sessions');
70
- if (!fs.existsSync(baseDir)) return null;
71
-
72
- // Find all session files, sorted by mtime
73
- const allSessions = [];
74
-
75
- function walkDir(dir) {
76
- if (!fs.existsSync(dir)) return;
77
- for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
78
- const fullPath = path.join(dir, entry.name);
79
- if (entry.isDirectory()) {
80
- walkDir(fullPath);
81
- } else if (entry.name.endsWith('.jsonl')) {
82
- allSessions.push({
83
- path: fullPath,
84
- mtime: fs.statSync(fullPath).mtime
85
- });
86
- }
87
- }
88
- }
89
-
90
- walkDir(baseDir);
91
- allSessions.sort((a, b) => b.mtime - a.mtime);
92
-
93
- // Find most recent matching CWD
94
- for (const session of allSessions.slice(0, 50)) { // Check last 50
95
- try {
96
- const firstLine = fs.readFileSync(session.path, 'utf8').split('\n')[0];
97
- const data = JSON.parse(firstLine);
98
- if (data.payload?.cwd === targetCwd) {
99
- return session.path;
100
- }
101
- } catch (e) {
102
- // Skip invalid files
103
- }
104
- }
105
-
106
- return null;
107
- }
108
-
109
- /**
110
- * Auto-detect session based on CWD
111
- */
112
- function autoDetectSession(cwd) {
113
- // Try Claude Code first
114
- const claudePath = path.join(os.homedir(), '.claude', 'projects', encodeCwd(cwd, 'claude'));
115
- let session = findMostRecentSession(claudePath);
116
- if (session) return { agent: 'claude', path: session };
117
-
118
- // Try Pi
119
- const piPath = path.join(os.homedir(), '.pi', 'agent', 'sessions', encodeCwd(cwd, 'pi'));
120
- session = findMostRecentSession(piPath);
121
- if (session) return { agent: 'pi', path: session };
122
-
123
- // Try Codex
124
- session = findCodexSession(cwd);
125
- if (session) return { agent: 'codex', path: session };
126
-
127
- return null;
128
- }
129
-
130
- /**
131
- * Parse Claude Code session format
132
- */
133
- function parseClaudeSession(content) {
134
- const messages = [];
135
- const lines = content.trim().split('\n');
136
-
137
- for (const line of lines) {
138
- try {
139
- const entry = JSON.parse(line);
140
- if (entry.message?.role && entry.message?.content) {
141
- const msg = entry.message;
142
- messages.push({
143
- role: msg.role,
144
- content: extractContent(msg.content),
145
- timestamp: entry.timestamp
146
- });
147
- }
148
- } catch (e) {
149
- // Skip invalid lines
150
- }
151
- }
152
-
153
- return messages;
154
- }
155
-
156
- /**
157
- * Parse Pi session format
158
- */
159
- function parsePiSession(content) {
160
- const messages = [];
161
- const lines = content.trim().split('\n');
162
-
163
- for (const line of lines) {
164
- try {
165
- const entry = JSON.parse(line);
166
- if (entry.type === 'message' && entry.message?.role) {
167
- messages.push({
168
- role: entry.message.role,
169
- content: extractContent(entry.message.content),
170
- timestamp: entry.timestamp
171
- });
172
- }
173
- } catch (e) {
174
- // Skip invalid lines
175
- }
176
- }
177
-
178
- return messages;
179
- }
180
-
181
- /**
182
- * Parse Codex session format
183
- */
184
- function parseCodexSession(content) {
185
- const messages = [];
186
- const lines = content.trim().split('\n');
187
-
188
- for (const line of lines) {
189
- try {
190
- const entry = JSON.parse(line);
191
- if (entry.type === 'response_item' && entry.payload?.role) {
192
- const payload = entry.payload;
193
- messages.push({
194
- role: payload.role,
195
- content: extractContent(payload.content),
196
- timestamp: entry.timestamp
197
- });
198
- }
199
- } catch (e) {
200
- // Skip invalid lines
201
- }
202
- }
203
-
204
- return messages;
205
- }
206
-
207
- /**
208
- * Extract text content from various content formats
209
- */
210
- function extractContent(content) {
211
- if (typeof content === 'string') return content;
212
- if (!Array.isArray(content)) return JSON.stringify(content);
213
-
214
- const parts = [];
215
- for (const item of content) {
216
- if (typeof item === 'string') {
217
- parts.push(item);
218
- } else if (item.type === 'text') {
219
- parts.push(item.text);
220
- } else if (item.type === 'input_text') {
221
- parts.push(item.text);
222
- } else if (item.type === 'tool_use') {
223
- parts.push(`[Tool: ${item.name}]\n${JSON.stringify(item.input, null, 2)}`);
224
- } else if (item.type === 'tool_result') {
225
- const result = typeof item.content === 'string'
226
- ? item.content
227
- : JSON.stringify(item.content);
228
- // Truncate long tool results
229
- const truncated = result.length > 500
230
- ? result.slice(0, 500) + '\n[... truncated ...]'
231
- : result;
232
- parts.push(`[Tool Result]\n${truncated}`);
233
- } else {
234
- parts.push(`[${item.type}]`);
235
- }
236
- }
237
-
238
- return parts.join('\n');
239
- }
240
-
241
- /**
242
- * Format messages as readable transcript
243
- */
244
- function formatTranscript(messages, maxMessages = 100) {
245
- const recent = messages.slice(-maxMessages);
246
- const lines = [];
247
-
248
- for (const msg of recent) {
249
- const role = msg.role.toUpperCase();
250
- lines.push(`\n### ${role}:\n`);
251
- lines.push(msg.content);
252
- }
253
-
254
- if (messages.length > maxMessages) {
255
- lines.unshift(`\n[... ${messages.length - maxMessages} earlier messages omitted ...]\n`);
256
- }
257
-
258
- return lines.join('\n');
259
- }
260
-
261
- // Main
262
- async function main() {
263
- let result;
264
-
265
- if (sessionPath) {
266
- // Explicit path provided
267
- if (!fs.existsSync(sessionPath)) {
268
- console.error(`Session file not found: ${sessionPath}`);
269
- process.exit(1);
270
- }
271
- // Guess agent from path
272
- if (sessionPath.includes('.claude')) {
273
- result = { agent: 'claude', path: sessionPath };
274
- } else if (sessionPath.includes('.pi')) {
275
- result = { agent: 'pi', path: sessionPath };
276
- } else if (sessionPath.includes('.codex')) {
277
- result = { agent: 'codex', path: sessionPath };
278
- } else {
279
- // Default to Claude format
280
- result = { agent: 'claude', path: sessionPath };
281
- }
282
- } else if (agent) {
283
- // Agent specified, find session for that agent
284
- if (agent === 'claude') {
285
- const dir = path.join(os.homedir(), '.claude', 'projects', encodeCwd(cwd, 'claude'));
286
- const session = findMostRecentSession(dir);
287
- if (!session) {
288
- console.error(`No Claude Code session found for: ${cwd}`);
289
- process.exit(1);
290
- }
291
- result = { agent: 'claude', path: session };
292
- } else if (agent === 'pi') {
293
- const dir = path.join(os.homedir(), '.pi', 'agent', 'sessions', encodeCwd(cwd, 'pi'));
294
- const session = findMostRecentSession(dir);
295
- if (!session) {
296
- console.error(`No Pi session found for: ${cwd}`);
297
- process.exit(1);
298
- }
299
- result = { agent: 'pi', path: session };
300
- } else if (agent === 'codex') {
301
- const session = findCodexSession(cwd);
302
- if (!session) {
303
- console.error(`No Codex session found for: ${cwd}`);
304
- process.exit(1);
305
- }
306
- result = { agent: 'codex', path: session };
307
- } else {
308
- console.error(`Unknown agent: ${agent}`);
309
- process.exit(1);
310
- }
311
- } else {
312
- // Auto-detect
313
- result = autoDetectSession(cwd);
314
- if (!result) {
315
- console.error(`No session found for: ${cwd}`);
316
- console.error('Try specifying --agent claude|pi|codex or provide a session path directly.');
317
- process.exit(1);
318
- }
319
- }
320
-
321
- // Read and parse session
322
- const content = fs.readFileSync(result.path, 'utf8');
323
-
324
- let messages;
325
- switch (result.agent) {
326
- case 'claude':
327
- messages = parseClaudeSession(content);
328
- break;
329
- case 'pi':
330
- messages = parsePiSession(content);
331
- break;
332
- case 'codex':
333
- messages = parseCodexSession(content);
334
- break;
335
- }
336
-
337
- // Output metadata and transcript
338
- console.log(`# Session Transcript`);
339
- console.log(`Agent: ${result.agent}`);
340
- console.log(`File: ${result.path}`);
341
- console.log(`Messages: ${messages.length}`);
342
- console.log('');
343
- console.log(formatTranscript(messages));
344
- }
345
-
346
- main().catch(e => {
347
- console.error(e.message);
348
- process.exit(1);
349
- });