promptvc 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +78 -0
- package/assets/notify.mp3 +0 -0
- package/bin/promptvc +3 -0
- package/bin/promptvc-codex +8 -0
- package/dist/branding.d.ts +2 -0
- package/dist/branding.d.ts.map +1 -0
- package/dist/branding.js +44 -0
- package/dist/branding.js.map +1 -0
- package/dist/config.d.ts +2 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +74 -0
- package/dist/config.js.map +1 -0
- package/dist/git.d.ts +43 -0
- package/dist/git.d.ts.map +1 -0
- package/dist/git.js +157 -0
- package/dist/git.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +109 -0
- package/dist/index.js.map +1 -0
- package/dist/proxyCodex.d.ts +5 -0
- package/dist/proxyCodex.d.ts.map +1 -0
- package/dist/proxyCodex.js +299 -0
- package/dist/proxyCodex.js.map +1 -0
- package/dist/store.d.ts +33 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +142 -0
- package/dist/store.js.map +1 -0
- package/dist/watch.d.ts +5 -0
- package/dist/watch.d.ts.map +1 -0
- package/dist/watch.js +131 -0
- package/dist/watch.js.map +1 -0
- package/hooks/codex-notify-debug.sh +20 -0
- package/hooks/codex-notify.sh +285 -0
- package/package.json +43 -0
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# PromptVC notification hook for Codex
|
|
3
|
+
# This script is called by Codex after each turn completes
|
|
4
|
+
# Captures prompts AND git diffs per-prompt for detailed tracking
|
|
5
|
+
|
|
6
|
+
# Check if we're in a git repository and resolve the repo root
|
|
7
|
+
if ! git rev-parse --git-dir > /dev/null 2>&1; then
|
|
8
|
+
exit 0
|
|
9
|
+
fi
|
|
10
|
+
|
|
11
|
+
REPO_DIR=$(git rev-parse --show-toplevel 2>/dev/null)
|
|
12
|
+
if [ -z "$REPO_DIR" ]; then
|
|
13
|
+
exit 0
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
# Get PromptVC directory
|
|
17
|
+
PROMPTVC_DIR="$REPO_DIR/.promptvc"
|
|
18
|
+
SESSIONS_FILE="$PROMPTVC_DIR/sessions.json"
|
|
19
|
+
LAST_PROMPT_FILE="$PROMPTVC_DIR/last_prompt_count"
|
|
20
|
+
LAST_SESSION_FILE="$PROMPTVC_DIR/last_session_file"
|
|
21
|
+
TEMP_PROMPTS_FILE="$PROMPTVC_DIR/temp_prompts.json"
|
|
22
|
+
SETTINGS_FILE="$PROMPTVC_DIR/settings.json"
|
|
23
|
+
|
|
24
|
+
# Create .promptvc directory if it doesn't exist
|
|
25
|
+
mkdir -p "$PROMPTVC_DIR"
|
|
26
|
+
|
|
27
|
+
if [ ! -f "$SESSIONS_FILE" ]; then
|
|
28
|
+
echo "[]" > "$SESSIONS_FILE"
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
# Play a notification sound when Codex finishes a response
|
|
32
|
+
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
|
33
|
+
SOUND_PATH="$SCRIPT_DIR/../assets/notify.mp3"
|
|
34
|
+
FALLBACK_SOUND_PATH="$SCRIPT_DIR/../../../assets/notify.mp3"
|
|
35
|
+
play_notify_sound() {
|
|
36
|
+
if [ -f "$SETTINGS_FILE" ]; then
|
|
37
|
+
SOUND_SETTING=""
|
|
38
|
+
if command -v jq > /dev/null 2>&1; then
|
|
39
|
+
SOUND_SETTING=$(jq -r '.notifySoundEnabled // true' "$SETTINGS_FILE" 2>/dev/null)
|
|
40
|
+
else
|
|
41
|
+
SOUND_SETTING=$(sed -nE 's/.*"notifySoundEnabled"[[:space:]]*:[[:space:]]*(true|false).*/\1/p' "$SETTINGS_FILE" | head -1)
|
|
42
|
+
fi
|
|
43
|
+
if [ "$SOUND_SETTING" = "false" ]; then
|
|
44
|
+
return
|
|
45
|
+
fi
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
if [ ! -f "$SOUND_PATH" ]; then
|
|
49
|
+
if [ -f "$FALLBACK_SOUND_PATH" ]; then
|
|
50
|
+
SOUND_PATH="$FALLBACK_SOUND_PATH"
|
|
51
|
+
else
|
|
52
|
+
return
|
|
53
|
+
fi
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
if command -v afplay > /dev/null 2>&1; then
|
|
57
|
+
afplay "$SOUND_PATH" > /dev/null 2>&1 &
|
|
58
|
+
elif command -v paplay > /dev/null 2>&1; then
|
|
59
|
+
paplay "$SOUND_PATH" > /dev/null 2>&1 &
|
|
60
|
+
elif command -v aplay > /dev/null 2>&1; then
|
|
61
|
+
aplay "$SOUND_PATH" > /dev/null 2>&1 &
|
|
62
|
+
elif command -v play > /dev/null 2>&1; then
|
|
63
|
+
play "$SOUND_PATH" > /dev/null 2>&1 &
|
|
64
|
+
fi
|
|
65
|
+
}
|
|
66
|
+
trap 'play_notify_sound' EXIT
|
|
67
|
+
|
|
68
|
+
# Find the latest codex session file
|
|
69
|
+
LATEST_SESSION=$(find "$HOME/.codex/sessions" -name "rollout-*.jsonl" -type f -print0 2>/dev/null | \
|
|
70
|
+
xargs -0 ls -t 2>/dev/null | head -1)
|
|
71
|
+
|
|
72
|
+
if [ ! -f "$LATEST_SESSION" ]; then
|
|
73
|
+
exit 0
|
|
74
|
+
fi
|
|
75
|
+
|
|
76
|
+
SESSION_ID=$(basename "$LATEST_SESSION" .jsonl)
|
|
77
|
+
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
78
|
+
BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
|
|
79
|
+
|
|
80
|
+
# Reset prompt counter when a new Codex session file appears
|
|
81
|
+
PREV_SESSION=""
|
|
82
|
+
if [ -f "$LAST_SESSION_FILE" ]; then
|
|
83
|
+
PREV_SESSION=$(cat "$LAST_SESSION_FILE")
|
|
84
|
+
fi
|
|
85
|
+
NEW_SESSION="false"
|
|
86
|
+
if [ "$LATEST_SESSION" != "$PREV_SESSION" ]; then
|
|
87
|
+
echo "0" > "$LAST_PROMPT_FILE"
|
|
88
|
+
echo "$LATEST_SESSION" > "$LAST_SESSION_FILE"
|
|
89
|
+
NEW_SESSION="true"
|
|
90
|
+
fi
|
|
91
|
+
|
|
92
|
+
# Ensure jq is available
|
|
93
|
+
if ! command -v jq > /dev/null 2>&1; then
|
|
94
|
+
exit 0
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
SESSIONS_JSON=$(cat "$SESSIONS_FILE" 2>/dev/null || echo "[]")
|
|
98
|
+
if ! echo "$SESSIONS_JSON" | jq -e . > /dev/null 2>&1; then
|
|
99
|
+
SESSIONS_JSON="[]"
|
|
100
|
+
fi
|
|
101
|
+
|
|
102
|
+
if [ "$NEW_SESSION" = "true" ] && [ -n "$PREV_SESSION" ]; then
|
|
103
|
+
PREV_SESSION_ID=$(basename "$PREV_SESSION" .jsonl)
|
|
104
|
+
if [ -n "$PREV_SESSION_ID" ]; then
|
|
105
|
+
SESSIONS_JSON=$(echo "$SESSIONS_JSON" | jq \
|
|
106
|
+
--arg prevId "$PREV_SESSION_ID" \
|
|
107
|
+
--arg endedAt "$TIMESTAMP" \
|
|
108
|
+
'map(if .id == $prevId then .inProgress = false | .endedAt = $endedAt else . end)')
|
|
109
|
+
fi
|
|
110
|
+
fi
|
|
111
|
+
|
|
112
|
+
# Extract all user prompts as a JSON array
|
|
113
|
+
# This preserves multi-line prompts as single entries
|
|
114
|
+
cat "$LATEST_SESSION" | \
|
|
115
|
+
jq -R -s 'split("\n") | map(fromjson? | select(.type == "response_item" and .payload.role == "user") | .payload.content[0].text) | map(select(. != null))' \
|
|
116
|
+
> "$TEMP_PROMPTS_FILE" 2>/dev/null
|
|
117
|
+
|
|
118
|
+
if [ ! -f "$TEMP_PROMPTS_FILE" ] || [ ! -s "$TEMP_PROMPTS_FILE" ]; then
|
|
119
|
+
exit 0
|
|
120
|
+
fi
|
|
121
|
+
|
|
122
|
+
# Filter out system prompts using jq (checking entire message content)
|
|
123
|
+
# Remove prompts that contain system instruction markers
|
|
124
|
+
FILTERED_PROMPTS=$(cat "$TEMP_PROMPTS_FILE" | \
|
|
125
|
+
jq 'map(select(
|
|
126
|
+
(. | startswith("# AGENTS.md") | not) and
|
|
127
|
+
(. | startswith("<INSTRUCTIONS>") | not) and
|
|
128
|
+
(. | startswith("<environment_context>") | not) and
|
|
129
|
+
(. | contains("# AGENTS.md instructions") | not) and
|
|
130
|
+
(. | contains("<INSTRUCTIONS>") | not) and
|
|
131
|
+
(. | contains("## Skills") | not) and
|
|
132
|
+
(. | contains("These skills are discovered at startup") | not)
|
|
133
|
+
))' 2>/dev/null)
|
|
134
|
+
|
|
135
|
+
# Count current prompts
|
|
136
|
+
CURRENT_PROMPT_COUNT=$(echo "$FILTERED_PROMPTS" | jq 'length' 2>/dev/null || echo "0")
|
|
137
|
+
|
|
138
|
+
if [ "$CURRENT_PROMPT_COUNT" = "0" ]; then
|
|
139
|
+
rm -f "$TEMP_PROMPTS_FILE"
|
|
140
|
+
exit 0
|
|
141
|
+
fi
|
|
142
|
+
|
|
143
|
+
# Get the last processed prompt count
|
|
144
|
+
LAST_PROMPT_COUNT=0
|
|
145
|
+
if [ -f "$LAST_PROMPT_FILE" ]; then
|
|
146
|
+
LAST_PROMPT_COUNT=$(cat "$LAST_PROMPT_FILE")
|
|
147
|
+
fi
|
|
148
|
+
|
|
149
|
+
# If no new prompts, exit
|
|
150
|
+
if [ "$CURRENT_PROMPT_COUNT" -le "$LAST_PROMPT_COUNT" ]; then
|
|
151
|
+
rm -f "$TEMP_PROMPTS_FILE"
|
|
152
|
+
exit 0
|
|
153
|
+
fi
|
|
154
|
+
|
|
155
|
+
# Get the new prompts (starting from LAST_PROMPT_COUNT)
|
|
156
|
+
NEW_PROMPTS=$(echo "$FILTERED_PROMPTS" | jq ".[$LAST_PROMPT_COUNT:]" 2>/dev/null)
|
|
157
|
+
|
|
158
|
+
# Capture current git state
|
|
159
|
+
GIT_HASH=$(git rev-parse HEAD 2>/dev/null || echo "")
|
|
160
|
+
GIT_DIFF=$(git diff 2>/dev/null || echo "")
|
|
161
|
+
CHANGED_FILES_RAW=$(git diff --name-only 2>/dev/null || echo "")
|
|
162
|
+
|
|
163
|
+
# Build files array as JSON
|
|
164
|
+
if [ -z "$CHANGED_FILES_RAW" ]; then
|
|
165
|
+
FILES_ARRAY="[]"
|
|
166
|
+
else
|
|
167
|
+
FILES_ARRAY=$(echo "$CHANGED_FILES_RAW" | jq -R -s 'split("\n") | map(select(length > 0))')
|
|
168
|
+
fi
|
|
169
|
+
|
|
170
|
+
# Escape diff for JSON
|
|
171
|
+
ESCAPED_DIFF=$(echo "$GIT_DIFF" | jq -R -s '.')
|
|
172
|
+
|
|
173
|
+
# Process each new prompt
|
|
174
|
+
PROMPT_COUNT=$(echo "$NEW_PROMPTS" | jq 'length' 2>/dev/null || echo "0")
|
|
175
|
+
|
|
176
|
+
if [ "$PROMPT_COUNT" = "0" ]; then
|
|
177
|
+
rm -f "$TEMP_PROMPTS_FILE"
|
|
178
|
+
exit 0
|
|
179
|
+
fi
|
|
180
|
+
|
|
181
|
+
# Build array of new prompt entries
|
|
182
|
+
NEW_ENTRIES="[]"
|
|
183
|
+
for ((i=0; i<$PROMPT_COUNT; i++)); do
|
|
184
|
+
PROMPT=$(echo "$NEW_PROMPTS" | jq -r ".[$i]" 2>/dev/null)
|
|
185
|
+
|
|
186
|
+
if [ -z "$PROMPT" ] || [ "$PROMPT" = "null" ]; then
|
|
187
|
+
continue
|
|
188
|
+
fi
|
|
189
|
+
|
|
190
|
+
# Escape prompt for JSON
|
|
191
|
+
ESCAPED_PROMPT=$(echo "$PROMPT" | jq -R -s '.')
|
|
192
|
+
|
|
193
|
+
# Create prompt entry
|
|
194
|
+
PROMPT_ENTRY=$(cat <<EOF
|
|
195
|
+
{
|
|
196
|
+
"prompt": $ESCAPED_PROMPT,
|
|
197
|
+
"timestamp": "$TIMESTAMP",
|
|
198
|
+
"hash": "$GIT_HASH",
|
|
199
|
+
"files": $FILES_ARRAY,
|
|
200
|
+
"diff": $ESCAPED_DIFF
|
|
201
|
+
}
|
|
202
|
+
EOF
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
# Add to new entries array
|
|
206
|
+
NEW_ENTRIES=$(echo "$NEW_ENTRIES" | jq ". + [$PROMPT_ENTRY]" 2>/dev/null)
|
|
207
|
+
done
|
|
208
|
+
|
|
209
|
+
LATEST_PROMPT=$(echo "$NEW_PROMPTS" | jq -r '.[-1]' 2>/dev/null)
|
|
210
|
+
if [ -z "$LATEST_PROMPT" ] || [ "$LATEST_PROMPT" = "null" ]; then
|
|
211
|
+
LATEST_PROMPT=""
|
|
212
|
+
fi
|
|
213
|
+
|
|
214
|
+
# Update sessions.json with the latest prompt changes
|
|
215
|
+
if [ "$NEW_ENTRIES" != "[]" ]; then
|
|
216
|
+
UPDATED_SESSIONS=$(echo "$SESSIONS_JSON" | jq \
|
|
217
|
+
--arg id "$SESSION_ID" \
|
|
218
|
+
--arg repoRoot "$REPO_DIR" \
|
|
219
|
+
--arg branch "$BRANCH" \
|
|
220
|
+
--arg timestamp "$TIMESTAMP" \
|
|
221
|
+
--arg prompt "$LATEST_PROMPT" \
|
|
222
|
+
--arg hash "$GIT_HASH" \
|
|
223
|
+
--argjson files "$FILES_ARRAY" \
|
|
224
|
+
--argjson diff "$ESCAPED_DIFF" \
|
|
225
|
+
--argjson newEntries "$NEW_ENTRIES" \
|
|
226
|
+
'
|
|
227
|
+
def merge_files(existing; incoming):
|
|
228
|
+
reduce incoming[] as $item (existing; if index($item) then . else . + [$item] end);
|
|
229
|
+
def prompt_count(entries):
|
|
230
|
+
entries | length;
|
|
231
|
+
def response_snippet(entries):
|
|
232
|
+
"Interactive session: " + (prompt_count(entries) | tostring) + " prompt" + (if prompt_count(entries) != 1 then "s" else "" end);
|
|
233
|
+
def update_session(session):
|
|
234
|
+
session
|
|
235
|
+
| .provider = "codex"
|
|
236
|
+
| .repoRoot = $repoRoot
|
|
237
|
+
| .branch = $branch
|
|
238
|
+
| .prompt = $prompt
|
|
239
|
+
| .diff = $diff
|
|
240
|
+
| .mode = "interactive"
|
|
241
|
+
| .autoTagged = true
|
|
242
|
+
| .inProgress = true
|
|
243
|
+
| .updatedAt = $timestamp
|
|
244
|
+
| .files = merge_files((.files // []); $files)
|
|
245
|
+
| .perPromptChanges = ((.perPromptChanges // []) + $newEntries)
|
|
246
|
+
| .responseSnippet = response_snippet(.perPromptChanges);
|
|
247
|
+
if map(.id == $id) | any then
|
|
248
|
+
map(if .id == $id then
|
|
249
|
+
update_session(.)
|
|
250
|
+
| if (.createdAt // "") == "" then .createdAt = $timestamp else . end
|
|
251
|
+
| if (.preHash // "") == "" then .preHash = $hash else . end
|
|
252
|
+
| .postHash = (.postHash // null)
|
|
253
|
+
else . end)
|
|
254
|
+
else
|
|
255
|
+
[ {
|
|
256
|
+
id: $id,
|
|
257
|
+
provider: "codex",
|
|
258
|
+
repoRoot: $repoRoot,
|
|
259
|
+
branch: $branch,
|
|
260
|
+
preHash: $hash,
|
|
261
|
+
postHash: null,
|
|
262
|
+
prompt: $prompt,
|
|
263
|
+
responseSnippet: response_snippet($newEntries),
|
|
264
|
+
files: $files,
|
|
265
|
+
diff: $diff,
|
|
266
|
+
createdAt: $timestamp,
|
|
267
|
+
updatedAt: $timestamp,
|
|
268
|
+
mode: "interactive",
|
|
269
|
+
autoTagged: true,
|
|
270
|
+
inProgress: true,
|
|
271
|
+
perPromptChanges: $newEntries
|
|
272
|
+
} ] + .
|
|
273
|
+
end
|
|
274
|
+
')
|
|
275
|
+
|
|
276
|
+
echo "$UPDATED_SESSIONS" > "$SESSIONS_FILE"
|
|
277
|
+
|
|
278
|
+
# Update last prompt count
|
|
279
|
+
echo "$CURRENT_PROMPT_COUNT" > "$LAST_PROMPT_FILE"
|
|
280
|
+
fi
|
|
281
|
+
|
|
282
|
+
# Clean up
|
|
283
|
+
rm -f "$TEMP_PROMPTS_FILE"
|
|
284
|
+
|
|
285
|
+
exit 0
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "promptvc",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI tool to wrap AI assistants and log prompts/diffs",
|
|
5
|
+
"homepage": "https://v0-prompt-version-control-pi.vercel.app/",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/sehmim/PromptVC.git"
|
|
9
|
+
},
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/sehmim/PromptVC/issues"
|
|
12
|
+
},
|
|
13
|
+
"main": "dist/index.js",
|
|
14
|
+
"bin": {
|
|
15
|
+
"promptvc": "bin/promptvc",
|
|
16
|
+
"promptvc-codex": "bin/promptvc-codex"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"bin",
|
|
20
|
+
"dist",
|
|
21
|
+
"hooks",
|
|
22
|
+
"assets"
|
|
23
|
+
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsc",
|
|
26
|
+
"clean": "rm -rf dist",
|
|
27
|
+
"dev": "tsc --watch",
|
|
28
|
+
"prepublishOnly": "npm run build",
|
|
29
|
+
"typecheck": "tsc --noEmit"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@promptvc/types": "workspace:*",
|
|
33
|
+
"kleur": "^4.1.5",
|
|
34
|
+
"commander": "^11.1.0",
|
|
35
|
+
"simple-git": "^3.22.0",
|
|
36
|
+
"uuid": "^9.0.1"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/node": "^20.10.6",
|
|
40
|
+
"@types/uuid": "^9.0.7",
|
|
41
|
+
"typescript": "^5.3.3"
|
|
42
|
+
}
|
|
43
|
+
}
|