@promptcellar/pc 0.5.3 → 0.5.5
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/hooks/codex-capture.js +9 -9
- package/hooks/gemini-capture.js +5 -9
- package/hooks/prompt-capture.js +5 -9
- package/package.json +2 -1
- package/src/commands/login.js +34 -115
- package/src/commands/save.js +5 -2
- package/src/lib/api.js +28 -47
- package/src/lib/config.js +22 -2
- package/src/lib/context.js +14 -59
- package/src/lib/crypto.js +1 -39
- package/src/lib/device-transfer.js +1 -43
- package/src/lib/keychain.js +7 -2
- package/tests/config.test.js +29 -0
- package/tests/context.test.js +23 -0
- package/tests/device-login.test.js +28 -0
- package/tests/keychain.test.js +34 -1
- package/README.md +0 -114
- package/docs/plans/2026-01-30-wa-auth-integration-design.md +0 -118
- package/docs/plans/2026-01-30-wa-auth-integration-plan.md +0 -776
- package/docs/plans/2026-02-03-multi-prompt-capture-design.md +0 -501
|
@@ -1,501 +0,0 @@
|
|
|
1
|
-
# Multi-Prompt Capture Implementation Plan
|
|
2
|
-
|
|
3
|
-
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
|
4
|
-
|
|
5
|
-
**Goal:** Capture all user prompts in real-time (not just the first), grouped by session ID.
|
|
6
|
-
|
|
7
|
-
**Architecture:** Switch Claude Code from Stop hook to UserPromptSubmit hook. Add deduplication state tracking for Codex CLI. Gemini already fires per-prompt, just verify session handling.
|
|
8
|
-
|
|
9
|
-
**Tech Stack:** Node.js, ES modules, Conf for state storage
|
|
10
|
-
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
## Task 1: Create State Management Utility
|
|
14
|
-
|
|
15
|
-
**Files:**
|
|
16
|
-
- Create: `src/lib/state.js`
|
|
17
|
-
|
|
18
|
-
**Step 1: Create the state utility file**
|
|
19
|
-
|
|
20
|
-
```javascript
|
|
21
|
-
import Conf from 'conf';
|
|
22
|
-
|
|
23
|
-
const state = new Conf({
|
|
24
|
-
projectName: 'promptcellar',
|
|
25
|
-
configName: 'capture-state',
|
|
26
|
-
schema: {
|
|
27
|
-
threads: {
|
|
28
|
-
type: 'object',
|
|
29
|
-
default: {}
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
const MAX_AGE_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Get the last captured index for a thread.
|
|
38
|
-
* @param {string} threadId
|
|
39
|
-
* @returns {number}
|
|
40
|
-
*/
|
|
41
|
-
export function getLastCapturedIndex(threadId) {
|
|
42
|
-
const threads = state.get('threads');
|
|
43
|
-
return threads[threadId]?.lastIndex || 0;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Save the last captured index for a thread.
|
|
48
|
-
* @param {string} threadId
|
|
49
|
-
* @param {number} lastIndex
|
|
50
|
-
*/
|
|
51
|
-
export function saveLastCapturedIndex(threadId, lastIndex) {
|
|
52
|
-
const threads = state.get('threads');
|
|
53
|
-
threads[threadId] = {
|
|
54
|
-
lastIndex,
|
|
55
|
-
updatedAt: Date.now()
|
|
56
|
-
};
|
|
57
|
-
state.set('threads', threads);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Clean up stale thread entries older than maxAgeMs.
|
|
62
|
-
* @param {number} maxAgeMs - Default 24 hours
|
|
63
|
-
*/
|
|
64
|
-
export function cleanupStaleThreads(maxAgeMs = MAX_AGE_MS) {
|
|
65
|
-
const threads = state.get('threads');
|
|
66
|
-
const now = Date.now();
|
|
67
|
-
let changed = false;
|
|
68
|
-
|
|
69
|
-
for (const [threadId, data] of Object.entries(threads)) {
|
|
70
|
-
if (now - data.updatedAt > maxAgeMs) {
|
|
71
|
-
delete threads[threadId];
|
|
72
|
-
changed = true;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
if (changed) {
|
|
77
|
-
state.set('threads', threads);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export default { getLastCapturedIndex, saveLastCapturedIndex, cleanupStaleThreads };
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
**Step 2: Commit**
|
|
85
|
-
|
|
86
|
-
```bash
|
|
87
|
-
git add src/lib/state.js
|
|
88
|
-
git commit -m "feat: add state management for capture deduplication"
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
---
|
|
92
|
-
|
|
93
|
-
## Task 2: Rewrite Claude Code Hook for UserPromptSubmit
|
|
94
|
-
|
|
95
|
-
**Files:**
|
|
96
|
-
- Modify: `hooks/prompt-capture.js`
|
|
97
|
-
|
|
98
|
-
**Step 1: Rewrite the hook for UserPromptSubmit**
|
|
99
|
-
|
|
100
|
-
Replace entire file with:
|
|
101
|
-
|
|
102
|
-
```javascript
|
|
103
|
-
#!/usr/bin/env node
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Claude Code UserPromptSubmit hook for capturing prompts to PromptCellar.
|
|
107
|
-
*
|
|
108
|
-
* This script is called by Claude Code on every user prompt submission.
|
|
109
|
-
* UserPromptSubmit hooks receive JSON via stdin with:
|
|
110
|
-
* - session_id: session identifier
|
|
111
|
-
* - prompt: the user's prompt text
|
|
112
|
-
* - cwd: working directory
|
|
113
|
-
*/
|
|
114
|
-
|
|
115
|
-
import { capturePrompt } from '../src/lib/api.js';
|
|
116
|
-
import { getFullContext } from '../src/lib/context.js';
|
|
117
|
-
import { isLoggedIn, isVaultAvailable } from '../src/lib/config.js';
|
|
118
|
-
import { encryptPrompt } from '../src/lib/crypto.js';
|
|
119
|
-
import { requireVaultKey } from '../src/lib/keychain.js';
|
|
120
|
-
|
|
121
|
-
async function readStdin() {
|
|
122
|
-
return new Promise((resolve) => {
|
|
123
|
-
let data = '';
|
|
124
|
-
process.stdin.setEncoding('utf8');
|
|
125
|
-
process.stdin.on('data', chunk => data += chunk);
|
|
126
|
-
process.stdin.on('end', () => resolve(data));
|
|
127
|
-
|
|
128
|
-
// Timeout after 1 second if no input
|
|
129
|
-
setTimeout(() => resolve(data), 1000);
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
async function main() {
|
|
134
|
-
try {
|
|
135
|
-
const input = await readStdin();
|
|
136
|
-
|
|
137
|
-
if (!input.trim()) {
|
|
138
|
-
process.exit(0);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
if (!isLoggedIn()) {
|
|
142
|
-
process.exit(0);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
if (!isVaultAvailable()) {
|
|
146
|
-
process.exit(0);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
const event = JSON.parse(input);
|
|
150
|
-
|
|
151
|
-
// UserPromptSubmit provides the prompt directly
|
|
152
|
-
if (!event.prompt) {
|
|
153
|
-
process.exit(0);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
const promptContent = event.prompt.trim();
|
|
157
|
-
if (!promptContent) {
|
|
158
|
-
process.exit(0);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const context = getFullContext('claude-code');
|
|
162
|
-
|
|
163
|
-
// Override with event data if available
|
|
164
|
-
if (event.cwd) {
|
|
165
|
-
context.working_directory = event.cwd;
|
|
166
|
-
}
|
|
167
|
-
if (event.session_id) {
|
|
168
|
-
context.session_id = event.session_id;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const vaultKey = await requireVaultKey({ silent: true });
|
|
172
|
-
if (!vaultKey) {
|
|
173
|
-
process.exit(0);
|
|
174
|
-
}
|
|
175
|
-
const { encrypted_content, content_iv } = encryptPrompt(promptContent, vaultKey);
|
|
176
|
-
|
|
177
|
-
await capturePrompt({
|
|
178
|
-
...context,
|
|
179
|
-
encrypted_content,
|
|
180
|
-
content_iv
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
} catch {
|
|
184
|
-
// Fail silently — stderr from hooks can cause issues
|
|
185
|
-
process.exit(0);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
main();
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
**Step 2: Commit**
|
|
193
|
-
|
|
194
|
-
```bash
|
|
195
|
-
git add hooks/prompt-capture.js
|
|
196
|
-
git commit -m "feat: rewrite Claude hook for UserPromptSubmit (real-time capture)"
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
---
|
|
200
|
-
|
|
201
|
-
## Task 3: Update Codex Hook for Multi-Prompt Capture
|
|
202
|
-
|
|
203
|
-
**Files:**
|
|
204
|
-
- Modify: `hooks/codex-capture.js`
|
|
205
|
-
|
|
206
|
-
**Step 1: Update the hook with deduplication**
|
|
207
|
-
|
|
208
|
-
Replace entire file with:
|
|
209
|
-
|
|
210
|
-
```javascript
|
|
211
|
-
#!/usr/bin/env node
|
|
212
|
-
|
|
213
|
-
/**
|
|
214
|
-
* Codex CLI notify hook for capturing prompts to PromptCellar.
|
|
215
|
-
*
|
|
216
|
-
* Codex calls this script with a JSON argument containing:
|
|
217
|
-
* - type: event type (e.g., 'agent-turn-complete')
|
|
218
|
-
* - thread-id: session identifier
|
|
219
|
-
* - turn-id: turn identifier
|
|
220
|
-
* - cwd: working directory
|
|
221
|
-
* - input-messages: array of user messages (accumulates over session)
|
|
222
|
-
* - last-assistant-message: final assistant response
|
|
223
|
-
*
|
|
224
|
-
* This hook captures ALL new user messages since last capture using
|
|
225
|
-
* state tracking to avoid duplicates.
|
|
226
|
-
*/
|
|
227
|
-
|
|
228
|
-
import { capturePrompt } from '../src/lib/api.js';
|
|
229
|
-
import { getFullContext } from '../src/lib/context.js';
|
|
230
|
-
import { isLoggedIn, isVaultAvailable } from '../src/lib/config.js';
|
|
231
|
-
import { encryptPrompt } from '../src/lib/crypto.js';
|
|
232
|
-
import { requireVaultKey } from '../src/lib/keychain.js';
|
|
233
|
-
import { getLastCapturedIndex, saveLastCapturedIndex, cleanupStaleThreads } from '../src/lib/state.js';
|
|
234
|
-
|
|
235
|
-
function extractContent(message) {
|
|
236
|
-
if (typeof message === 'string') {
|
|
237
|
-
return message;
|
|
238
|
-
}
|
|
239
|
-
if (message.content) {
|
|
240
|
-
return Array.isArray(message.content)
|
|
241
|
-
? message.content.filter(c => c.type === 'text').map(c => c.text).join('\n')
|
|
242
|
-
: message.content;
|
|
243
|
-
}
|
|
244
|
-
return null;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
async function main() {
|
|
248
|
-
// Codex passes JSON as first argument
|
|
249
|
-
const jsonArg = process.argv[2];
|
|
250
|
-
|
|
251
|
-
if (!jsonArg) {
|
|
252
|
-
process.exit(0);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
if (!isLoggedIn()) {
|
|
256
|
-
process.exit(0);
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
if (!isVaultAvailable()) {
|
|
260
|
-
process.exit(0);
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
try {
|
|
264
|
-
const event = JSON.parse(jsonArg);
|
|
265
|
-
|
|
266
|
-
// Only capture on agent-turn-complete
|
|
267
|
-
if (event.type !== 'agent-turn-complete') {
|
|
268
|
-
process.exit(0);
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
const inputMessages = event['input-messages'] || [];
|
|
272
|
-
if (inputMessages.length === 0) {
|
|
273
|
-
process.exit(0);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
const threadId = event['thread-id'];
|
|
277
|
-
if (!threadId) {
|
|
278
|
-
process.exit(0);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// Clean up stale threads periodically
|
|
282
|
-
cleanupStaleThreads();
|
|
283
|
-
|
|
284
|
-
// Get last captured index to avoid duplicates
|
|
285
|
-
const lastIndex = getLastCapturedIndex(threadId);
|
|
286
|
-
const newMessages = inputMessages.slice(lastIndex);
|
|
287
|
-
|
|
288
|
-
if (newMessages.length === 0) {
|
|
289
|
-
process.exit(0);
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
const vaultKey = await requireVaultKey({ silent: true });
|
|
293
|
-
if (!vaultKey) {
|
|
294
|
-
process.exit(0);
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
// Build base context once
|
|
298
|
-
const baseContext = getFullContext('codex');
|
|
299
|
-
if (event.cwd) {
|
|
300
|
-
baseContext.working_directory = event.cwd;
|
|
301
|
-
}
|
|
302
|
-
baseContext.session_id = threadId;
|
|
303
|
-
|
|
304
|
-
// Capture each new message
|
|
305
|
-
for (const message of newMessages) {
|
|
306
|
-
const content = extractContent(message);
|
|
307
|
-
if (!content || !content.trim()) {
|
|
308
|
-
continue;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
const { encrypted_content, content_iv } = encryptPrompt(content.trim(), vaultKey);
|
|
312
|
-
|
|
313
|
-
await capturePrompt({
|
|
314
|
-
...baseContext,
|
|
315
|
-
encrypted_content,
|
|
316
|
-
content_iv
|
|
317
|
-
});
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
// Update state with new index
|
|
321
|
-
saveLastCapturedIndex(threadId, inputMessages.length);
|
|
322
|
-
|
|
323
|
-
} catch {
|
|
324
|
-
// Fail silently to not disrupt Codex
|
|
325
|
-
process.exit(0);
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
main();
|
|
330
|
-
```
|
|
331
|
-
|
|
332
|
-
**Step 2: Commit**
|
|
333
|
-
|
|
334
|
-
```bash
|
|
335
|
-
git add hooks/codex-capture.js
|
|
336
|
-
git commit -m "feat: update Codex hook to capture all prompts with deduplication"
|
|
337
|
-
```
|
|
338
|
-
|
|
339
|
-
---
|
|
340
|
-
|
|
341
|
-
## Task 4: Verify Gemini Hook Session Handling
|
|
342
|
-
|
|
343
|
-
**Files:**
|
|
344
|
-
- Modify: `hooks/gemini-capture.js` (minor update)
|
|
345
|
-
|
|
346
|
-
**Step 1: Review and verify session_id handling**
|
|
347
|
-
|
|
348
|
-
The Gemini hook already captures per-prompt via BeforeAgent. Verify that session_id is properly included. The current implementation at lines 81-83 already handles this:
|
|
349
|
-
|
|
350
|
-
```javascript
|
|
351
|
-
if (event.session_id) {
|
|
352
|
-
context.session_id = event.session_id;
|
|
353
|
-
}
|
|
354
|
-
```
|
|
355
|
-
|
|
356
|
-
No changes needed - the hook already works correctly for multi-prompt capture.
|
|
357
|
-
|
|
358
|
-
**Step 2: Commit (documentation only if no changes)**
|
|
359
|
-
|
|
360
|
-
No commit needed - Gemini hook already supports multi-prompt capture.
|
|
361
|
-
|
|
362
|
-
---
|
|
363
|
-
|
|
364
|
-
## Task 5: Update Setup Command for UserPromptSubmit Hook
|
|
365
|
-
|
|
366
|
-
**Files:**
|
|
367
|
-
- Modify: `src/commands/setup.js`
|
|
368
|
-
|
|
369
|
-
**Step 1: Update Claude Code hook configuration**
|
|
370
|
-
|
|
371
|
-
Change the `setupClaudeCode` function to use `UserPromptSubmit` instead of `Stop`.
|
|
372
|
-
|
|
373
|
-
Find and replace in `setupClaudeCode` function (around lines 167-210):
|
|
374
|
-
|
|
375
|
-
Change line 61-64 (isClaudeHookInstalled):
|
|
376
|
-
```javascript
|
|
377
|
-
function isClaudeHookInstalled(settings) {
|
|
378
|
-
const matchers = settings.hooks?.UserPromptSubmit || [];
|
|
379
|
-
return matchers.some(matcher =>
|
|
380
|
-
matcher.hooks?.some(hook => hook.command?.includes(HOOK_SCRIPT_NAME))
|
|
381
|
-
);
|
|
382
|
-
}
|
|
383
|
-
```
|
|
384
|
-
|
|
385
|
-
Change lines 187-189 (remove existing hook):
|
|
386
|
-
```javascript
|
|
387
|
-
// Remove existing hook matchers that contain our hook
|
|
388
|
-
settings.hooks.UserPromptSubmit = (settings.hooks.UserPromptSubmit || []).filter(matcher =>
|
|
389
|
-
!matcher.hooks?.some(hook => hook.command?.includes(HOOK_SCRIPT_NAME))
|
|
390
|
-
);
|
|
391
|
-
```
|
|
392
|
-
|
|
393
|
-
Change lines 193-206 (add new hook):
|
|
394
|
-
```javascript
|
|
395
|
-
// Ensure hooks.UserPromptSubmit exists
|
|
396
|
-
if (!settings.hooks) {
|
|
397
|
-
settings.hooks = {};
|
|
398
|
-
}
|
|
399
|
-
if (!settings.hooks.UserPromptSubmit) {
|
|
400
|
-
settings.hooks.UserPromptSubmit = [];
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
// Add the UserPromptSubmit hook
|
|
404
|
-
settings.hooks.UserPromptSubmit.push({
|
|
405
|
-
matcher: '*',
|
|
406
|
-
hooks: [{
|
|
407
|
-
type: 'command',
|
|
408
|
-
command: 'pc-capture'
|
|
409
|
-
}]
|
|
410
|
-
});
|
|
411
|
-
```
|
|
412
|
-
|
|
413
|
-
**Step 2: Update unsetup function**
|
|
414
|
-
|
|
415
|
-
Change lines 317-324 (unsetup Claude hook):
|
|
416
|
-
```javascript
|
|
417
|
-
// Remove Claude hook
|
|
418
|
-
const claudeSettings = getClaudeSettings();
|
|
419
|
-
if (isClaudeHookInstalled(claudeSettings)) {
|
|
420
|
-
claudeSettings.hooks.UserPromptSubmit = (claudeSettings.hooks.UserPromptSubmit || []).filter(matcher =>
|
|
421
|
-
!matcher.hooks?.some(hook => hook.command?.includes(HOOK_SCRIPT_NAME))
|
|
422
|
-
);
|
|
423
|
-
saveClaudeSettings(claudeSettings);
|
|
424
|
-
console.log(chalk.green(' Removed Claude Code hook.'));
|
|
425
|
-
removed = true;
|
|
426
|
-
}
|
|
427
|
-
```
|
|
428
|
-
|
|
429
|
-
**Step 3: Commit**
|
|
430
|
-
|
|
431
|
-
```bash
|
|
432
|
-
git add src/commands/setup.js
|
|
433
|
-
git commit -m "feat: update setup to use UserPromptSubmit hook for Claude Code"
|
|
434
|
-
```
|
|
435
|
-
|
|
436
|
-
---
|
|
437
|
-
|
|
438
|
-
## Task 6: Bump Version and Final Commit
|
|
439
|
-
|
|
440
|
-
**Files:**
|
|
441
|
-
- Modify: `package.json`
|
|
442
|
-
|
|
443
|
-
**Step 1: Bump version to 0.5.0**
|
|
444
|
-
|
|
445
|
-
This is a feature release (captures all prompts instead of just first).
|
|
446
|
-
|
|
447
|
-
Change line 3:
|
|
448
|
-
```json
|
|
449
|
-
"version": "0.5.0",
|
|
450
|
-
```
|
|
451
|
-
|
|
452
|
-
**Step 2: Commit version bump**
|
|
453
|
-
|
|
454
|
-
```bash
|
|
455
|
-
git add package.json
|
|
456
|
-
git commit -m "chore: bump version to 0.5.0"
|
|
457
|
-
```
|
|
458
|
-
|
|
459
|
-
---
|
|
460
|
-
|
|
461
|
-
## Task 7: Test the Implementation
|
|
462
|
-
|
|
463
|
-
**Step 1: Verify state.js loads correctly**
|
|
464
|
-
|
|
465
|
-
```bash
|
|
466
|
-
node -e "import('./src/lib/state.js').then(m => console.log('state.js OK'))"
|
|
467
|
-
```
|
|
468
|
-
|
|
469
|
-
Expected: `state.js OK`
|
|
470
|
-
|
|
471
|
-
**Step 2: Verify hooks load correctly**
|
|
472
|
-
|
|
473
|
-
```bash
|
|
474
|
-
node -e "import('./hooks/prompt-capture.js').catch(() => console.log('prompt-capture.js OK'))"
|
|
475
|
-
node -e "import('./hooks/codex-capture.js').catch(() => console.log('codex-capture.js OK'))"
|
|
476
|
-
node -e "import('./hooks/gemini-capture.js').catch(() => console.log('gemini-capture.js OK'))"
|
|
477
|
-
```
|
|
478
|
-
|
|
479
|
-
Expected: Each prints OK (the catch is because they exit on no input)
|
|
480
|
-
|
|
481
|
-
**Step 3: Verify setup command loads**
|
|
482
|
-
|
|
483
|
-
```bash
|
|
484
|
-
node -e "import('./src/commands/setup.js').then(m => console.log('setup.js OK'))"
|
|
485
|
-
```
|
|
486
|
-
|
|
487
|
-
Expected: `setup.js OK`
|
|
488
|
-
|
|
489
|
-
---
|
|
490
|
-
|
|
491
|
-
## Summary
|
|
492
|
-
|
|
493
|
-
| Task | Description |
|
|
494
|
-
|------|-------------|
|
|
495
|
-
| 1 | Create `src/lib/state.js` for Codex deduplication |
|
|
496
|
-
| 2 | Rewrite `hooks/prompt-capture.js` for UserPromptSubmit |
|
|
497
|
-
| 3 | Update `hooks/codex-capture.js` with deduplication |
|
|
498
|
-
| 4 | Verify `hooks/gemini-capture.js` (no changes needed) |
|
|
499
|
-
| 5 | Update `src/commands/setup.js` for new hook type |
|
|
500
|
-
| 6 | Bump version to 0.5.0 |
|
|
501
|
-
| 7 | Test all modules load correctly |
|