@gi4nks/ph 1.3.2 → 1.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/CHANGELOG.md +33 -5
- package/README.md +59 -101
- package/dist/cli.js +3828 -2167
- package/dist/cli.js.map +1 -1
- package/docs/hooks.md +33 -71
- package/docs/ph-manual.md +86 -8
- package/hooks/opencode/install.sh +45 -0
- package/hooks/opencode/ph-plugin.ts +101 -0
- package/package.json +36 -5
package/docs/hooks.md
CHANGED
|
@@ -1,83 +1,45 @@
|
|
|
1
|
-
# ph
|
|
1
|
+
# ph Hook System — Status Report
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
## Completed Hooks
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
└───────────────────────────────────────────────────────────┴────────────────────────────┴────────┘
|
|
5
|
+
| Tool | Type | File | Status |
|
|
6
|
+
|------|------|------|--------|
|
|
7
|
+
| Claude Code | Shell script (Stop hook) | `hooks/claude/ph-hook.sh` | ✅ |
|
|
8
|
+
| Gemini CLI | Shell script (AfterAgent hook) | `hooks/gemini/ph-hook.sh` | ✅ |
|
|
9
|
+
| OpenCode | JS/TS Plugin | `hooks/opencode/ph-plugin.ts` | ✅ |
|
|
32
10
|
|
|
33
|
-
|
|
11
|
+
## How Hooks Work
|
|
34
12
|
|
|
35
|
-
|
|
36
|
-
- Gemini CLI skill/hook: similar integration for the Gemini CLI
|
|
13
|
+
Each supported AI CLI tool exposes a native hook mechanism:
|
|
37
14
|
|
|
38
|
-
|
|
15
|
+
- **Claude Code** — `Stop` hook, triggered after each agent session completes.
|
|
16
|
+
Reads the JSONL transcript, extracts last user prompt + assistant response, pipes to `ph log`.
|
|
39
17
|
|
|
40
|
-
|
|
41
|
-
|
|
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
|
|
18
|
+
- **Gemini CLI** — `AfterAgent` hook, triggered after each agent turn.
|
|
19
|
+
Receives JSON with `prompt` and `prompt_response` fields directly, pipes to `ph log`.
|
|
46
20
|
|
|
47
|
-
|
|
21
|
+
- **OpenCode** — Plugin system with `chat.message` + `experimental.text.complete` hooks.
|
|
22
|
+
Intercepts messages, pairs user prompts with streamed assistant responses, pipes to `ph log`.
|
|
48
23
|
|
|
49
|
-
|
|
50
|
-
|
|
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)
|
|
24
|
+
All hooks run in the background (`& + disown` for shell scripts; Promise fire-and-forget for the plugin).
|
|
25
|
+
Never block the AI tool from exiting.
|
|
52
26
|
|
|
53
|
-
|
|
27
|
+
## Data Flow
|
|
54
28
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
29
|
+
```
|
|
30
|
+
AI CLI Tool
|
|
31
|
+
↓ (automatic hook after each exchange)
|
|
32
|
+
ph log (stdin JSON)
|
|
33
|
+
↓
|
|
34
|
+
SQLite (~/.prompt_history.db)
|
|
35
|
+
↓
|
|
36
|
+
background analysis (Ollama)
|
|
37
|
+
↓
|
|
38
|
+
metadata: project, language, role, tags, relevance
|
|
39
|
+
↓
|
|
40
|
+
ph browse / ph search / ph sessions
|
|
41
|
+
```
|
|
66
42
|
|
|
67
|
-
|
|
43
|
+
## Installation
|
|
68
44
|
|
|
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.
|
|
45
|
+
See `docs/ph-manual.md` for detailed installation instructions per tool.
|
package/docs/ph-manual.md
CHANGED
|
@@ -165,16 +165,94 @@ ln -sf /path/to/ph/hooks/gemini/ph-hook.sh ~/.gemini/ph-hook.sh
|
|
|
165
165
|
|
|
166
166
|
---
|
|
167
167
|
|
|
168
|
+
### OpenCode Hook
|
|
169
|
+
|
|
170
|
+
OpenCode uses a JavaScript/TypeScript **plugin** system (rather than shell scripts). The `ph` integration is a plugin that hooks into OpenCode's message lifecycle to capture prompts and responses.
|
|
171
|
+
|
|
172
|
+
#### How the plugin captures data
|
|
173
|
+
|
|
174
|
+
The plugin registers two hooks inside OpenCode's runtime:
|
|
175
|
+
|
|
176
|
+
| Hook | Purpose |
|
|
177
|
+
|------|---------|
|
|
178
|
+
| `chat.message` | Intercepts every message in the conversation. User messages become prompts; assistant messages become response snapshots. |
|
|
179
|
+
| `experimental.text.complete` | Fires after streaming completes. Replaces the snapshot with the full, final response text. |
|
|
180
|
+
|
|
181
|
+
**Data flow:**
|
|
182
|
+
|
|
183
|
+
```
|
|
184
|
+
User types prompt
|
|
185
|
+
→ chat.message fires (role: user)
|
|
186
|
+
→ plugin stores the prompt text + metadata (agent, model, workdir)
|
|
187
|
+
|
|
188
|
+
Assistant streams response
|
|
189
|
+
→ experimental.text.complete fires for each text part
|
|
190
|
+
→ plugin accumulates the complete response text
|
|
191
|
+
|
|
192
|
+
Next user message or session end
|
|
193
|
+
→ plugin pairs prompt + accumulated response
|
|
194
|
+
→ pipes {"tool":"opencode","prompt":"...","response":"..."} to ph log
|
|
195
|
+
→ runs in background — never blocks OpenCode
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
#### Hook installation — OpenCode
|
|
199
|
+
|
|
200
|
+
OpenCode loads plugins from two directories:
|
|
201
|
+
- `~/.config/opencode/plugins/` — global (all projects)
|
|
202
|
+
- `.opencode/plugins/` — per-project
|
|
203
|
+
|
|
204
|
+
**Option A: Quick install (global)**
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
# Link the plugin into OpenCode's global plugin directory
|
|
208
|
+
mkdir -p ~/.config/opencode/plugins
|
|
209
|
+
ln -sf /path/to/ph/hooks/opencode/ph-plugin.ts ~/.config/opencode/plugins/ph-plugin.ts
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Or use the install script:
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
./hooks/opencode/install.sh # global (default)
|
|
216
|
+
./hooks/opencode/install.sh --project # current project only
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
**Option B: Manual copy**
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
cp hooks/opencode/ph-plugin.ts ~/.config/opencode/plugins/
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
The plugin is automatically loaded on the next OpenCode startup. No config changes needed — Bun handles TypeScript natively.
|
|
226
|
+
|
|
227
|
+
**Verify:**
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
# Start OpenCode — the plugin loads silently
|
|
231
|
+
opencode
|
|
232
|
+
|
|
233
|
+
# Check that prompts are being captured
|
|
234
|
+
ph search --tool opencode --limit 3
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
**Uninstall:**
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
rm ~/.config/opencode/plugins/ph-plugin.ts
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
168
245
|
### Hook Comparison
|
|
169
246
|
|
|
170
|
-
| Aspect | Claude Code | Gemini CLI |
|
|
171
|
-
|
|
172
|
-
| Hook
|
|
173
|
-
| Data delivery | `transcript_path` (JSONL
|
|
174
|
-
| Prompt extraction | Parse JSONL with `jq` slurp | `jq .prompt`
|
|
175
|
-
| Response extraction | Last assistant text block | `jq .prompt_response`
|
|
176
|
-
| Async support | Yes (`async: true`
|
|
177
|
-
| Blocks AI exit? | Never
|
|
247
|
+
| Aspect | Claude Code | Gemini CLI | OpenCode |
|
|
248
|
+
|--------|------------|------------|----------|
|
|
249
|
+
| Hook mechanism | `Stop` shell script | `AfterAgent` shell script | JS/TS plugin |
|
|
250
|
+
| Data delivery | `transcript_path` (JSONL) | Direct JSON on stdin | Event hooks (`chat.message`, `experimental.text.complete`) |
|
|
251
|
+
| Prompt extraction | Parse JSONL with `jq` slurp | `jq .prompt` | `message.role === 'user'` parts |
|
|
252
|
+
| Response extraction | Last assistant text block | `jq .prompt_response` | `experimental.text.complete` accumulation |
|
|
253
|
+
| Async support | Yes (`async: true`) | Yes (`& + disown`) | Yes (Promise fire-and-forget) |
|
|
254
|
+
| Blocks AI exit? | Never | Never | Never |
|
|
255
|
+
| Install method | Symlink + settings.json | Symlink + settings.json | Copy plugin file to `plugins/` dir |
|
|
178
256
|
|
|
179
257
|
---
|
|
180
258
|
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# install.sh — Install the ph OpenCode plugin
|
|
3
|
+
#
|
|
4
|
+
# Usage: ./install.sh [--global|--project]
|
|
5
|
+
# --global Install in ~/.config/opencode/plugins/ (default)
|
|
6
|
+
# --project Install in .opencode/plugins/ (current directory)
|
|
7
|
+
|
|
8
|
+
set -euo pipefail
|
|
9
|
+
|
|
10
|
+
PLUGIN_SOURCE="$(cd "$(dirname "$0")" && pwd)/ph-plugin.ts"
|
|
11
|
+
MODE="${1:---global}"
|
|
12
|
+
|
|
13
|
+
case "$MODE" in
|
|
14
|
+
--global)
|
|
15
|
+
TARGET_DIR="$HOME/.config/opencode/plugins"
|
|
16
|
+
;;
|
|
17
|
+
--project)
|
|
18
|
+
TARGET_DIR=".opencode/plugins"
|
|
19
|
+
mkdir -p "$TARGET_DIR"
|
|
20
|
+
TARGET_DIR="$(cd "$TARGET_DIR" && pwd)"
|
|
21
|
+
;;
|
|
22
|
+
*)
|
|
23
|
+
echo "Usage: $0 [--global|--project]"
|
|
24
|
+
exit 1
|
|
25
|
+
;;
|
|
26
|
+
esac
|
|
27
|
+
|
|
28
|
+
mkdir -p "$TARGET_DIR"
|
|
29
|
+
|
|
30
|
+
TARGET_FILE="$TARGET_DIR/ph-plugin.ts"
|
|
31
|
+
|
|
32
|
+
if [ -f "$TARGET_FILE" ]; then
|
|
33
|
+
echo "Updating existing plugin at $TARGET_FILE"
|
|
34
|
+
else
|
|
35
|
+
echo "Installing plugin to $TARGET_FILE"
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
cp "$PLUGIN_SOURCE" "$TARGET_FILE"
|
|
39
|
+
echo "✅ ph OpenCode plugin installed at $TARGET_FILE"
|
|
40
|
+
|
|
41
|
+
echo ""
|
|
42
|
+
echo "Plugin will be loaded automatically on next OpenCode startup."
|
|
43
|
+
echo "To verify: opencode config will show it in the plugin list."
|
|
44
|
+
echo ""
|
|
45
|
+
echo "To uninstall: rm $TARGET_FILE"
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type { Plugin } from "@opencode-ai/plugin";
|
|
2
|
+
|
|
3
|
+
interface PendingPrompt {
|
|
4
|
+
text: string;
|
|
5
|
+
timestamp: string;
|
|
6
|
+
agent?: string;
|
|
7
|
+
model?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Per-session state for pairing user prompts with assistant responses */
|
|
11
|
+
const sessions = new Map<string, {
|
|
12
|
+
prompt: PendingPrompt | null;
|
|
13
|
+
response: string;
|
|
14
|
+
}>();
|
|
15
|
+
|
|
16
|
+
function textFromParts(parts: { type?: string; text?: string }[]): string {
|
|
17
|
+
return parts
|
|
18
|
+
.filter(p => p.type === 'text')
|
|
19
|
+
.map(p => p.text || '')
|
|
20
|
+
.join('')
|
|
21
|
+
.trim();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function callPhLog(
|
|
25
|
+
$: any,
|
|
26
|
+
prompt: PendingPrompt,
|
|
27
|
+
response: string,
|
|
28
|
+
workdir: string,
|
|
29
|
+
): Promise<void> {
|
|
30
|
+
if (!prompt.text || !response) return;
|
|
31
|
+
|
|
32
|
+
const payload: Record<string, unknown> = {
|
|
33
|
+
tool: 'opencode',
|
|
34
|
+
prompt: prompt.text,
|
|
35
|
+
response,
|
|
36
|
+
workdir,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const meta: Record<string, unknown> = { source: 'plugin' };
|
|
40
|
+
if (prompt.agent) meta.agent = prompt.agent;
|
|
41
|
+
if (prompt.model) meta.model = prompt.model;
|
|
42
|
+
payload.metadata = JSON.stringify(meta);
|
|
43
|
+
|
|
44
|
+
const json = JSON.stringify(payload);
|
|
45
|
+
const _ = $`echo ${json} | ph log > /dev/null 2>&1`;
|
|
46
|
+
_.then(() => {}).catch(() => {});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const PhOpenCodePlugin: Plugin = async ({ directory, $ }) => {
|
|
50
|
+
return {
|
|
51
|
+
"chat.message": async (input, output) => {
|
|
52
|
+
const { sessionID, agent, model } = input;
|
|
53
|
+
const { message, parts } = output;
|
|
54
|
+
const role = (message as Record<string, unknown>).role;
|
|
55
|
+
|
|
56
|
+
if (role === 'user') {
|
|
57
|
+
const existing = sessions.get(sessionID);
|
|
58
|
+
const prompt = existing?.prompt;
|
|
59
|
+
const response = existing?.response;
|
|
60
|
+
|
|
61
|
+
// Log previous turn if we have both parts
|
|
62
|
+
if (prompt && response) {
|
|
63
|
+
await callPhLog($, prompt, response, directory);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Store new prompt, reset response
|
|
67
|
+
const text = textFromParts(parts);
|
|
68
|
+
if (text) {
|
|
69
|
+
sessions.set(sessionID, {
|
|
70
|
+
prompt: {
|
|
71
|
+
text,
|
|
72
|
+
timestamp: new Date().toISOString(),
|
|
73
|
+
agent,
|
|
74
|
+
model: model ? `${model.providerID}/${model.modelID}` : undefined,
|
|
75
|
+
},
|
|
76
|
+
response: '',
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
} else if (role === 'assistant') {
|
|
80
|
+
const existing = sessions.get(sessionID);
|
|
81
|
+
if (!existing?.prompt) return;
|
|
82
|
+
|
|
83
|
+
// Snapshot whatever text is available (non-streaming or initial frame)
|
|
84
|
+
const text = textFromParts(parts);
|
|
85
|
+
if (text) {
|
|
86
|
+
sessions.set(sessionID, { ...existing, response: text });
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
"experimental.text.complete": async (input, output) => {
|
|
92
|
+
const session = sessions.get(input.sessionID);
|
|
93
|
+
if (!session?.prompt) return;
|
|
94
|
+
|
|
95
|
+
// Replace with complete text (handles streaming: this fires after streaming ends)
|
|
96
|
+
if (output.text) {
|
|
97
|
+
sessions.set(input.sessionID, { ...session, response: output.text });
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gi4nks/ph",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "Transparent CLI wrapper that captures and indexes AI prompts",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -19,25 +19,55 @@
|
|
|
19
19
|
"type": "git",
|
|
20
20
|
"url": "git+https://github.com/gi4nks/ph.git"
|
|
21
21
|
},
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public",
|
|
24
|
+
"registry": "https://registry.npmjs.org/",
|
|
25
|
+
"provenance": true
|
|
26
|
+
},
|
|
27
|
+
"release": {
|
|
28
|
+
"branches": [
|
|
29
|
+
"main"
|
|
30
|
+
],
|
|
31
|
+
"plugins": [
|
|
32
|
+
"@semantic-release/commit-analyzer",
|
|
33
|
+
"@semantic-release/release-notes-generator",
|
|
34
|
+
"@semantic-release/changelog",
|
|
35
|
+
"@semantic-release/npm",
|
|
36
|
+
"@semantic-release/github",
|
|
37
|
+
[
|
|
38
|
+
"@semantic-release/git",
|
|
39
|
+
{
|
|
40
|
+
"assets": [
|
|
41
|
+
"package.json",
|
|
42
|
+
"CHANGELOG.md"
|
|
43
|
+
],
|
|
44
|
+
"message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
|
|
45
|
+
}
|
|
46
|
+
]
|
|
47
|
+
]
|
|
48
|
+
},
|
|
22
49
|
"scripts": {
|
|
23
50
|
"build": "tsup",
|
|
24
51
|
"dev": "tsx src/cli.ts",
|
|
25
52
|
"start": "node dist/cli.js",
|
|
26
53
|
"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"
|
|
54
|
+
"lint": "eslint src"
|
|
31
55
|
},
|
|
32
56
|
"dependencies": {
|
|
33
57
|
"@gi4nks/ink": "^1.5.0",
|
|
34
58
|
"@lydell/node-pty": "^1.2.0-beta.3",
|
|
59
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
35
60
|
"better-sqlite3": "^12.8.0",
|
|
36
61
|
"ink": "^6.8.0",
|
|
37
62
|
"react": "^19.2.4",
|
|
63
|
+
"sqlite-vec": "^0.1.9",
|
|
38
64
|
"zod": "^4.3.6"
|
|
39
65
|
},
|
|
40
66
|
"devDependencies": {
|
|
67
|
+
"@semantic-release/changelog": "^6.0.3",
|
|
68
|
+
"@semantic-release/git": "^10.0.1",
|
|
69
|
+
"@semantic-release/github": "^12.0.8",
|
|
70
|
+
"@semantic-release/npm": "^13.1.5",
|
|
41
71
|
"@types/better-sqlite3": "^7.6.13",
|
|
42
72
|
"@types/node": "^25.5.0",
|
|
43
73
|
"@types/react": "^19.2.14",
|
|
@@ -46,6 +76,7 @@
|
|
|
46
76
|
"eslint": "^9.39.4",
|
|
47
77
|
"eslint-plugin-react": "^7.37.5",
|
|
48
78
|
"eslint-plugin-react-hooks": "^7.0.1",
|
|
79
|
+
"semantic-release": "^25.0.3",
|
|
49
80
|
"standard-version": "^9.5.0",
|
|
50
81
|
"tsup": "^8.5.1",
|
|
51
82
|
"tsx": "^4.21.0",
|