@gi4nks/ph 1.3.2

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/docs/hooks.md ADDED
@@ -0,0 +1,83 @@
1
+ # ph Implementation Status — Complete Report
2
+
3
+ ✅ Completed
4
+
5
+ ┌───────────────────────────────────────────────────────────┬────────────────────────────┬────────┐
6
+ │ Feature │ File │ Status │
7
+ ├───────────────────────────────────────────────────────────┼────────────────────────────┼────────┤
8
+ │ Git Context Snapshot │ src/runner/git-context.ts │ ✅ │
9
+ ├───────────────────────────────────────────────────────────┼────────────────────────────┼────────┤
10
+ │ Background Analysis │ src/background/analyzer.ts │ ✅ │
11
+ ├───────────────────────────────────────────────────────────┼────────────────────────────┼────────┤
12
+ │ Work Sessions (ph sessions) │ src/sessions/index.ts │ ✅ │
13
+ ├───────────────────────────────────────────────────────────┼────────────────────────────┼────────┤
14
+ │ Prompt Rerunning (TUI r) │ src/ui/BrowseApp.tsx │ ✅ │
15
+ ├───────────────────────────────────────────────────────────┼────────────────────────────┼────────┤
16
+ │ Ollama embeddings (nomic-embed-text-v2-moe) │ src/embedding/index.ts │ ✅ │
17
+ ├───────────────────────────────────────────────────────────┼────────────────────────────┼────────┤
18
+ │ Response column in DB + FTS5 migration │ src/db/index.ts │ ✅ │
19
+ ├───────────────────────────────────────────────────────────┼────────────────────────────┼────────┤
20
+ │ ph log command (flag + stdin JSON) │ src/cli.ts │ ✅ │
21
+ ├───────────────────────────────────────────────────────────┼────────────────────────────┼────────┤
22
+ │ TUI detail view with response │ src/ui/BrowseApp.tsx │ ✅ │
23
+ ├───────────────────────────────────────────────────────────┼────────────────────────────┼────────┤
24
+ │ Export markdown with response + git context │ src/cli.ts │ ✅ │
25
+ ├───────────────────────────────────────────────────────────┼────────────────────────────┼────────┤
26
+ │ Gemini importer response capture │ src/importer/gemini.ts │ ✅ │
27
+ ├───────────────────────────────────────────────────────────┼────────────────────────────┼────────┤
28
+ │ Claude importer from transcript files │ src/importer/claude.ts │ ✅ │
29
+ ├───────────────────────────────────────────────────────────┼────────────────────────────┼────────┤
30
+ │ Documentation (README, CLAUDE.md, GEMINI.md, ROADMAP.md) │ various │ ✅ │
31
+ └───────────────────────────────────────────────────────────┴────────────────────────────┴────────┘
32
+
33
+ 🔄 In Progress (background agent)
34
+
35
+ - Claude Code hook (~/.claude/settings.json + ~/.claude/ph-hook.sh): Stop hook that calls ph log after each response
36
+ - Gemini CLI skill/hook: similar integration for the Gemini CLI
37
+
38
+ 📋 Hook Architecture (being implemented)
39
+
40
+ Claude finishes a response
41
+ → Stop hook in settings.json
42
+ → ~/.claude/ph-hook.sh receives JSON on stdin
43
+ → jq extracts last user prompt + assistant response text
44
+ → ph log --tool claude --prompt "..." --response "..."
45
+ → record inserted into DB with background analysis
46
+
47
+ ⏳ Still To Do (after hooks)
48
+
49
+ - Claude importer from history.jsonl: already replaced by the new transcript importer — ✅ covered
50
+ - End-to-end testing of hooks in a real session
51
+ - ph import claude update in cli.ts: verify that cmdImport calls the new importer correctly (the file was rewritten but the caller in cli.ts might still reference history.jsonl in the output log)
52
+
53
+ Final Data Flow
54
+
55
+ Gemini CLI / Claude Code
56
+ ↓ (automatic hook after each exchange)
57
+ ph log (stdin JSON)
58
+ ↓
59
+ SQLite (~/.prompt_history.db)
60
+ ↓
61
+ background analysis (Ollama gemma3:1b)
62
+ ↓
63
+ metadata: project, language, role, tags, relevance
64
+ ↓
65
+ ph browse / ph search / ph sessions
66
+
67
+ The main agent is completing the hook part — once finished you will have the completely automatic system without having to use ph as a wrapper.
68
+
69
+
70
+ ⏺ Everything is set. Summary of what has been done:
71
+
72
+ ---
73
+ Claude Code — Stop hook registered in ~/.claude/settings.json:
74
+ - Reads the session's JSONL transcript
75
+ - Extracts the last user prompt (those with promptId, not tool-result) and the last assistant response
76
+ - Calls ph log in the background (disown) → never blocks Claude
77
+
78
+ Gemini CLI — AfterAgent hook registered in ~/.gemini/settings.json:
79
+ - Directly receives the JSON payload containing prompt and prompt_response from Gemini CLI
80
+ - Extracts the user prompt and the assistant response
81
+ - Calls ph log in the background → never blocks Gemini
82
+
83
+ From this moment, every exchange in Claude Code or Gemini CLI is automatically saved in ph without the need to use it as a wrapper.
@@ -0,0 +1,283 @@
1
+ # ph — Prompt History CLI
2
+
3
+ **Architecture, Hook System & Usage Guide**
4
+
5
+ ---
6
+
7
+ ## 1. Overview
8
+
9
+ ph is a transparent capture layer for AI CLI tools such as Claude Code and Gemini CLI. Every prompt you type — and the AI response that follows — is automatically saved to a local SQLite database with full-text and semantic search, Git context, and optional background analysis.
10
+
11
+ The core design principle is zero friction: ph never interrupts your workflow. Capture happens silently in the background. Your AI sessions remain unchanged.
12
+
13
+ ### Key Features
14
+
15
+ - **Automatic Capture** — saves prompts, responses, and metadata without any manual action
16
+ - **Git Context** — records current branch, modified files, and diff at capture time
17
+ - **Full-text & Semantic Search** — FTS5 text search and vector-based semantic search (via Ollama embeddings)
18
+ - **Background Analysis** — automatic role/tag/relevance classification via Ollama or Gemini
19
+ - **Interactive TUI** — browse, filter, edit, re-run, and export your history with `ph browse`
20
+ - **Privacy First** — all data stays local; no data ever leaves your machine
21
+
22
+ ---
23
+
24
+ ## 2. Architecture
25
+
26
+ ph uses a two-tier storage model: a SQLite database with FTS5 full-text indexing for fast retrieval, and a JSON config file for runtime settings. Both live in your home directory by default.
27
+
28
+ ### Storage Layout
29
+
30
+ | Item | Default Path | Description |
31
+ |------|-------------|-------------|
32
+ | Database | `~/.prompt_history.db` | SQLite with FTS5 + vector embeddings |
33
+ | Config | `~/.ph_config.json` | JSON config file (0o600 permissions) |
34
+
35
+ The database uses WAL (Write-Ahead Logging) mode, enabling concurrent reads and writes. This is essential for the background analysis system, which processes prompts asynchronously without blocking your terminal session.
36
+
37
+ ---
38
+
39
+ ## 3. The Hook System
40
+
41
+ The hook system is ph's most powerful integration mechanism. Rather than requiring you to manually invoke ph for each session, hooks let the AI CLI tools themselves notify ph after every interaction — automatically, silently, and without any impact on response latency.
42
+
43
+ > **Key insight:** Hooks are post-action handlers registered inside the AI tool's own config. ph never wraps or intercepts the AI process. The AI runs normally; the hook fires after the session ends.
44
+
45
+ ### How Hooks Work
46
+
47
+ Each supported AI CLI tool exposes a native hook mechanism:
48
+
49
+ - **Claude Code** — provides a `Stop` hook, triggered after each agent session completes
50
+ - **Gemini CLI** — provides an `AfterAgent` hook, triggered after each agent turn
51
+
52
+ When the hook fires, the AI tool passes a JSON payload on stdin to your registered script. The script reads that payload, extracts the prompt and response, and pipes the data to `ph log` — which persists everything to the local SQLite database.
53
+
54
+ The entire operation runs in the background using shell job control (`& + disown`), so it never adds latency to your terminal session and never blocks the AI tool from exiting.
55
+
56
+ ---
57
+
58
+ ### Claude Code Hook
59
+
60
+ #### How Claude Code delivers data
61
+
62
+ When the `Stop` hook fires, Claude Code sends a JSON object on stdin:
63
+
64
+ ```json
65
+ {
66
+ "session_id": "abc123...",
67
+ "transcript_path": "/path/to/session.jsonl"
68
+ }
69
+ ```
70
+
71
+ The transcript is a JSONL file — one JSON object per line — containing the full session history. Each line is one of:
72
+
73
+ | Turn type | Identifier |
74
+ |-----------|-----------|
75
+ | Human turn | Has `promptId` key; `message.content` is a plain string |
76
+ | Tool result | Has `toolUseResult` key; `message.content` is an array |
77
+ | Assistant turn | `message.role == "assistant"`; content is an array of typed blocks |
78
+
79
+ #### What the hook script does
80
+
81
+ 1. Reads the hook input from stdin to obtain the transcript path
82
+ 2. Uses `jq` with slurp mode to parse the entire JSONL in one pass
83
+ 3. Filters for genuine human turns: must have `promptId`, string content, and no `toolUseResult` key
84
+ 4. Extracts the last human turn as the prompt
85
+ 5. Scans assistant turns for the last `text`-type block as the response
86
+ 6. Builds a JSON payload with `tool`, `prompt`, `response`, and `workdir` fields
87
+ 7. Pipes the payload to `ph log` running in the background (`& + disown`)
88
+ 8. Exits `0` unconditionally — Claude Code is never blocked
89
+
90
+ #### Hook installation — Claude Code
91
+
92
+ ```bash
93
+ # Step 1: link the hook script
94
+ ln -sf /path/to/ph/hooks/claude/ph-hook.sh ~/.claude/ph-hook.sh
95
+
96
+ # Step 2: register in ~/.claude/settings.json
97
+ {
98
+ "hooks": {
99
+ "Stop": [
100
+ {
101
+ "matcher": ".*",
102
+ "hooks": [
103
+ {
104
+ "type": "command",
105
+ "command": "~/.claude/ph-hook.sh",
106
+ "async": true
107
+ }
108
+ ]
109
+ }
110
+ ]
111
+ }
112
+ }
113
+ ```
114
+
115
+ ---
116
+
117
+ ### Gemini CLI Hook
118
+
119
+ #### How Gemini CLI delivers data
120
+
121
+ Gemini CLI's `AfterAgent` hook is simpler: it delivers the prompt and response directly in the stdin payload, with no transcript file to parse:
122
+
123
+ ```json
124
+ {
125
+ "session_id": "...",
126
+ "transcript_path": "...",
127
+ "cwd": "/path/to/workdir",
128
+ "hook_event_name": "AfterAgent",
129
+ "timestamp": "...",
130
+ "prompt": "the user's prompt text",
131
+ "prompt_response": "the assistant's full response text"
132
+ }
133
+ ```
134
+
135
+ #### What the hook script does
136
+
137
+ 1. Reads the JSON payload from stdin
138
+ 2. Extracts `prompt`, `prompt_response`, and `cwd` directly via `jq`
139
+ 3. Builds a JSON payload with `tool`, `prompt`, `response`, and `workdir` fields
140
+ 4. Pipes to `ph log` in the background
141
+ 5. Exits `0` unconditionally
142
+
143
+ #### Hook installation — Gemini CLI
144
+
145
+ ```bash
146
+ # Step 1: link the hook script
147
+ ln -sf /path/to/ph/hooks/gemini/ph-hook.sh ~/.gemini/ph-hook.sh
148
+
149
+ # Step 2: register in ~/.gemini/settings.json
150
+ {
151
+ "hooks": {
152
+ "AfterAgent": [
153
+ {
154
+ "hooks": [
155
+ {
156
+ "type": "command",
157
+ "command": "~/.gemini/ph-hook.sh"
158
+ }
159
+ ]
160
+ }
161
+ ]
162
+ }
163
+ }
164
+ ```
165
+
166
+ ---
167
+
168
+ ### Hook Comparison
169
+
170
+ | Aspect | Claude Code | Gemini CLI |
171
+ |--------|------------|------------|
172
+ | Hook name | `Stop` | `AfterAgent` |
173
+ | Data delivery | `transcript_path` (JSONL file) | Direct JSON on stdin |
174
+ | Prompt extraction | Parse JSONL with `jq` slurp | `jq .prompt` field |
175
+ | Response extraction | Last assistant text block | `jq .prompt_response` field |
176
+ | Async support | Yes (`async: true` in config) | Yes (`& + disown` in script) |
177
+ | Blocks AI exit? | Never (exits `0`) | Never (exits `0`) |
178
+
179
+ ---
180
+
181
+ ## 4. Installation
182
+
183
+ ### From Source
184
+
185
+ ```bash
186
+ git clone git@github.com:gi4nks/ph.git
187
+ cd ph
188
+ npm install
189
+ make build
190
+ make install # installs the global 'ph' symlink
191
+ ```
192
+
193
+ ### Via npm
194
+
195
+ ```bash
196
+ # Install globally:
197
+ npm install -g @gi4nks/ph
198
+ ```
199
+
200
+ ### Initial Configuration
201
+
202
+ ```bash
203
+ # Set your Gemini API key (optional — Ollama works without it)
204
+ ph config set gemini-api-key "YOUR_KEY"
205
+
206
+ # Choose your analysis provider
207
+ ph config set analyze-provider ollama # default, fully local
208
+ # ph config set analyze-provider gemini # cloud alternative
209
+
210
+ # Enable background analysis
211
+ ph config set background-analysis true
212
+
213
+ # Optional: move the database to a custom location
214
+ ph config set db-path "/path/to/your/custom.db"
215
+ ```
216
+
217
+ ---
218
+
219
+ ## 5. Usage
220
+
221
+ ### Search
222
+
223
+ ```bash
224
+ # Full-text search
225
+ ph search "how to refactor"
226
+
227
+ # Semantic search (vector similarity)
228
+ ph search --semantic "refactoring patterns"
229
+ ```
230
+
231
+ ### Interactive TUI
232
+
233
+ ```bash
234
+ ph browse
235
+ ```
236
+
237
+ Available hotkeys inside the TUI:
238
+
239
+ | Key | Action |
240
+ |-----|--------|
241
+ | `f` | Open filters panel |
242
+ | `e` | Edit prompt metadata (tags, notes) |
243
+ | `r` | Re-run selected prompt in your AI tool |
244
+ | `y` | Copy prompt to clipboard |
245
+ | `x` | Delete prompt from history |
246
+
247
+ ### Export
248
+
249
+ ```bash
250
+ # Export last 5 prompts as Markdown
251
+ ph export --format md --limit 5 > prompts.md
252
+ ```
253
+
254
+ ### Database Maintenance
255
+
256
+ ```bash
257
+ # Remove short or old prompts
258
+ ph cleanup --min-length 20 --days 30
259
+
260
+ # Compact the database
261
+ ph vacuum
262
+
263
+ # Dry-run to preview what would be removed
264
+ ph cleanup --dry-run --days 60
265
+ ```
266
+
267
+ ---
268
+
269
+ ## 6. Why Prompt History Matters
270
+
271
+ Every interaction with an AI tool is a unit of intellectual work. A well-crafted prompt encodes context, intent, and engineering decisions that took time to develop. Without capture, that work is lost the moment the session closes.
272
+
273
+ ph treats your prompt history as a first-class artifact: searchable, annotated, versioned with Git context, and available for re-use. Over time, your history becomes a personal knowledge base of how you think and work with AI tools.
274
+
275
+ > **The problem:** Manually saving prompts takes time, breaks flow, and rarely happens consistently. Without structure, even saved prompts become unsearchable noise.
276
+
277
+ > **The solution:** ph captures everything automatically via hooks, indexes it locally, and gives you semantic search so you can find what you need — even if you only remember the intent, not the exact wording.
278
+
279
+ ---
280
+
281
+ ## Repository
282
+
283
+ [https://github.com/gi4nks/ph](https://github.com/gi4nks/ph)
@@ -0,0 +1,45 @@
1
+ # Claude Code Hook for ph
2
+
3
+ This hook allows Claude Code to automatically save every prompt and response to the `ph` database.
4
+
5
+ ## Installation
6
+
7
+ 1. Ensure `ph` is installed and working.
8
+ 2. Create a symbolic link of the script in Claude's configuration folder:
9
+
10
+ ```bash
11
+ ln -sf "$(pwd)/ph-hook.sh" ~/.claude/ph-hook.sh
12
+ chmod +x ~/.claude/ph-hook.sh
13
+ ```
14
+
15
+ ## Claude Code Configuration
16
+
17
+ Add (or update) the `hooks` section in your `~/.claude/settings.json` file:
18
+
19
+ ```json
20
+ {
21
+ "hooks": {
22
+ "Stop": [
23
+ {
24
+ "matcher": ".*",
25
+ "hooks": [
26
+ {
27
+ "type": "command",
28
+ "command": "/Users/gianluca/.claude/ph-hook.sh",
29
+ "async": true
30
+ }
31
+ ]
32
+ }
33
+ ]
34
+ }
35
+ }
36
+ ```
37
+
38
+ **Note:** Ensure the path in the command correctly points to the symbolic link or the actual script. Using the symbolic link at `~/.claude/ph-hook.sh` keeps the configuration portable.
39
+
40
+ ## How it works
41
+
42
+ The script is executed by Claude Code at the `Stop` event (end of an exchange).
43
+ 1. Reads the current session transcript.
44
+ 2. Extracts the last user prompt and the last assistant response.
45
+ 3. Sends the data to `ph log` in the background.
@@ -0,0 +1,103 @@
1
+ #!/usr/bin/env bash
2
+ # ph-hook.sh — Claude Code Stop hook: logs the last prompt+response pair to ph
3
+ #
4
+ # Claude Code passes a JSON object on stdin with:
5
+ # { "session_id": "...", "transcript_path": "/path/to/session.jsonl" }
6
+ #
7
+ # The transcript is a JSONL file where each line is one of:
8
+ # - Human turn: { "promptId": "...", "message": {"role":"user", "content": "string"}, "cwd": "...", ... }
9
+ # - Tool result: { "toolUseResult": ..., "message": {"role":"user", "content": [...]}, ... }
10
+ # - Assistant turn: { "message": {"role":"assistant", "content": [{type,text},...] }, ... }
11
+ #
12
+ # This script:
13
+ # 1. Reads the hook input to get transcript_path
14
+ # 2. Uses jq to find the last genuine human-typed user message (has promptId, string content)
15
+ # 3. Extracts the last assistant text block
16
+ # 4. Pipes {"tool":"claude","prompt":"...","response":"...","workdir":"..."} to ph log
17
+ # 5. Always exits 0 so Claude Code is never blocked
18
+
19
+ set -uo pipefail
20
+
21
+ # ── Read hook input ──────────────────────────────────────────────────────────
22
+ HOOK_INPUT=$(cat)
23
+
24
+ TRANSCRIPT_PATH=$(printf '%s' "$HOOK_INPUT" | jq -r '.transcript_path // empty')
25
+
26
+ # Nothing to do if no transcript path provided
27
+ if [[ -z "$TRANSCRIPT_PATH" ]] || [[ ! -f "$TRANSCRIPT_PATH" ]]; then
28
+ exit 0
29
+ fi
30
+
31
+ # ── Extract last user prompt using jq slurp ──────────────────────────────────
32
+ # Filter for genuine human turns: has "promptId" AND message.content is a string
33
+ # (tool result lines also have role:user but have "toolUseResult" key and array content)
34
+ LAST_USER_JSON=$(
35
+ jq -Rs '
36
+ split("\n")
37
+ | map(select(length > 0))
38
+ | map(. as $line | try (. | fromjson) catch null)
39
+ | map(select(. != null))
40
+ | map(select(
41
+ has("promptId")
42
+ and has("message")
43
+ and (.message.role == "user")
44
+ and (.message.content | type == "string")
45
+ and (has("toolUseResult") | not)
46
+ ))
47
+ | last
48
+ ' "$TRANSCRIPT_PATH" 2>/dev/null
49
+ )
50
+
51
+ if [[ -z "$LAST_USER_JSON" ]] || [[ "$LAST_USER_JSON" == "null" ]]; then
52
+ exit 0
53
+ fi
54
+
55
+ LAST_USER_PROMPT=$(printf '%s' "$LAST_USER_JSON" | jq -r '.message.content // empty' 2>/dev/null)
56
+ WORKDIR=$(printf '%s' "$LAST_USER_JSON" | jq -r '.cwd // empty' 2>/dev/null)
57
+
58
+ # Skip if no user prompt found
59
+ if [[ -z "$LAST_USER_PROMPT" ]]; then
60
+ exit 0
61
+ fi
62
+
63
+ if [[ -z "$WORKDIR" ]]; then
64
+ WORKDIR="$HOME"
65
+ fi
66
+
67
+ # ── Extract last assistant text response ─────────────────────────────────────
68
+ # Assistant messages have message.content as an array of typed blocks.
69
+ # We want the last "text" block across recent assistant messages.
70
+ LAST_ASSISTANT_RESPONSE=$(
71
+ jq -Rs '
72
+ split("\n")
73
+ | map(select(length > 0))
74
+ | map(. as $line | try (. | fromjson) catch null)
75
+ | map(select(. != null))
76
+ | map(select(
77
+ has("message")
78
+ and (.message.role == "assistant")
79
+ and (.message.content | type == "array")
80
+ ))
81
+ | map(.message.content | map(select(.type == "text") | .text) | join(""))
82
+ | map(select(length > 0))
83
+ | last // ""
84
+ ' "$TRANSCRIPT_PATH" 2>/dev/null | jq -r '.' 2>/dev/null
85
+ )
86
+
87
+ # ── Call ph log ───────────────────────────────────────────────────────────────
88
+ # Use jq to safely build the JSON payload (handles escaping automatically)
89
+ JSON_PAYLOAD=$(
90
+ jq -n \
91
+ --arg tool "claude" \
92
+ --arg prompt "$LAST_USER_PROMPT" \
93
+ --arg response "${LAST_ASSISTANT_RESPONSE:-}" \
94
+ --arg workdir "$WORKDIR" \
95
+ '{"tool":$tool,"prompt":$prompt,"response":$response,"workdir":$workdir}'
96
+ )
97
+
98
+ # Run ph log in the background so it never blocks Claude Code's exit
99
+ # Redirect all output to /dev/null to keep things silent
100
+ printf '%s' "$JSON_PAYLOAD" | ph log >/dev/null 2>&1 &
101
+ disown $! 2>/dev/null || true
102
+
103
+ exit 0
@@ -0,0 +1,43 @@
1
+ # Gemini CLI Hook for ph
2
+
3
+ This hook allows Gemini CLI to automatically save every prompt and response to the `ph` database.
4
+
5
+ ## Installation
6
+
7
+ 1. Ensure `ph` is installed and working.
8
+ 2. Create a symbolic link of the script in Gemini's configuration folder:
9
+
10
+ ```bash
11
+ ln -sf "$(pwd)/ph-hook.sh" ~/.gemini/ph-hook.sh
12
+ chmod +x ~/.gemini/ph-hook.sh
13
+ ```
14
+
15
+ ## Gemini CLI Configuration
16
+
17
+ Add (or update) the `hooks` section in your `~/.gemini/settings.json` file:
18
+
19
+ ```json
20
+ {
21
+ "hooks": {
22
+ "AfterAgent": [
23
+ {
24
+ "hooks": [
25
+ {
26
+ "type": "command",
27
+ "command": "/Users/gianluca/.gemini/ph-hook.sh"
28
+ }
29
+ ]
30
+ }
31
+ ]
32
+ }
33
+ }
34
+ ```
35
+
36
+ **Note:** Ensure the path in the command correctly points to the symbolic link or the actual script. Using the symbolic link at `~/.gemini/ph-hook.sh` keeps the configuration portable.
37
+
38
+ ## How it works
39
+
40
+ The script is executed by Gemini CLI at the `AfterAgent` event (after each agent response).
41
+ 1. Receives the JSON input from Gemini CLI containing the prompt and response.
42
+ 2. Extracts the user prompt and the assistant response.
43
+ 3. Sends the data to `ph log` in the background.
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env bash
2
+ # ph-hook.sh — Gemini CLI AfterAgent hook: logs the last prompt+response pair to ph
3
+ #
4
+ # Gemini CLI sends a JSON object on stdin with:
5
+ # {
6
+ # "session_id": "...",
7
+ # "transcript_path": "...",
8
+ # "cwd": "/path/to/workdir",
9
+ # "hook_event_name": "AfterAgent",
10
+ # "timestamp": "...",
11
+ # "prompt": "the user's prompt text",
12
+ # "prompt_response": "the assistant's full response text"
13
+ # }
14
+ #
15
+ # This script:
16
+ # 1. Reads the hook input JSON
17
+ # 2. Extracts prompt, prompt_response, and cwd
18
+ # 3. Pipes {"tool":"gemini","prompt":"...","response":"...","workdir":"..."} to ph log
19
+ # 4. Always exits 0 so Gemini CLI is never blocked
20
+
21
+ set -uo pipefail
22
+
23
+ # ── Read hook input ──────────────────────────────────────────────────────────
24
+ HOOK_INPUT=$(cat)
25
+
26
+ PROMPT=$(printf '%s' "$HOOK_INPUT" | jq -r '.prompt // empty')
27
+ RESPONSE=$(printf '%s' "$HOOK_INPUT" | jq -r '.prompt_response // empty')
28
+ WORKDIR=$(printf '%s' "$HOOK_INPUT" | jq -r '.cwd // empty')
29
+
30
+ # Nothing to do if no prompt
31
+ if [[ -z "$PROMPT" ]]; then
32
+ exit 0
33
+ fi
34
+
35
+ # Fallback workdir
36
+ if [[ -z "$WORKDIR" ]]; then
37
+ WORKDIR="$HOME"
38
+ fi
39
+
40
+ # ── Call ph log ───────────────────────────────────────────────────────────────
41
+ # Use jq to safely build the JSON payload (handles escaping automatically)
42
+ JSON_PAYLOAD=$(
43
+ jq -n \
44
+ --arg tool "gemini" \
45
+ --arg prompt "$PROMPT" \
46
+ --arg response "${RESPONSE:-}" \
47
+ --arg workdir "$WORKDIR" \
48
+ '{"tool":$tool,"prompt":$prompt,"response":$response,"workdir":$workdir}'
49
+ )
50
+
51
+ # Run ph log in the background so it never blocks Gemini CLI
52
+ # Redirect all output to /dev/null to keep things silent
53
+ printf '%s' "$JSON_PAYLOAD" | ph log >/dev/null 2>&1 &
54
+ disown $! 2>/dev/null || true
55
+
56
+ exit 0
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@gi4nks/ph",
3
+ "version": "1.3.2",
4
+ "description": "Transparent CLI wrapper that captures and indexes AI prompts",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "bin": {
8
+ "ph": "dist/cli.js"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "README.md",
13
+ "LICENSE",
14
+ "docs",
15
+ "CHANGELOG.md",
16
+ "hooks"
17
+ ],
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/gi4nks/ph.git"
21
+ },
22
+ "scripts": {
23
+ "build": "tsup",
24
+ "dev": "tsx src/cli.ts",
25
+ "start": "node dist/cli.js",
26
+ "test": "vitest run",
27
+ "lint": "eslint src",
28
+ "release:patch": "standard-version --release-as patch",
29
+ "release:minor": "standard-version --release-as minor",
30
+ "release:major": "standard-version --release-as major"
31
+ },
32
+ "dependencies": {
33
+ "@gi4nks/ink": "^1.5.0",
34
+ "@lydell/node-pty": "^1.2.0-beta.3",
35
+ "better-sqlite3": "^12.8.0",
36
+ "ink": "^6.8.0",
37
+ "react": "^19.2.4",
38
+ "zod": "^4.3.6"
39
+ },
40
+ "devDependencies": {
41
+ "@types/better-sqlite3": "^7.6.13",
42
+ "@types/node": "^25.5.0",
43
+ "@types/react": "^19.2.14",
44
+ "@typescript-eslint/eslint-plugin": "^8.57.2",
45
+ "@typescript-eslint/parser": "^8.57.2",
46
+ "eslint": "^9.39.4",
47
+ "eslint-plugin-react": "^7.37.5",
48
+ "eslint-plugin-react-hooks": "^7.0.1",
49
+ "standard-version": "^9.5.0",
50
+ "tsup": "^8.5.1",
51
+ "tsx": "^4.21.0",
52
+ "typescript": "^5.9.3",
53
+ "vitest": "^4.1.0"
54
+ }
55
+ }