inscript 0.2.0__tar.gz → 0.3.0__tar.gz
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.
- {inscript-0.2.0 → inscript-0.3.0}/PKG-INFO +1 -1
- {inscript-0.2.0 → inscript-0.3.0}/inscript.egg-info/PKG-INFO +1 -1
- {inscript-0.2.0 → inscript-0.3.0}/inscript_pkg/__init__.py +103 -15
- {inscript-0.2.0 → inscript-0.3.0}/inscript_pkg/hook.py +62 -4
- {inscript-0.2.0 → inscript-0.3.0}/pyproject.toml +1 -1
- {inscript-0.2.0 → inscript-0.3.0}/LICENSE +0 -0
- {inscript-0.2.0 → inscript-0.3.0}/README.md +0 -0
- {inscript-0.2.0 → inscript-0.3.0}/inscript.egg-info/SOURCES.txt +0 -0
- {inscript-0.2.0 → inscript-0.3.0}/inscript.egg-info/dependency_links.txt +0 -0
- {inscript-0.2.0 → inscript-0.3.0}/inscript.egg-info/entry_points.txt +0 -0
- {inscript-0.2.0 → inscript-0.3.0}/inscript.egg-info/top_level.txt +0 -0
- {inscript-0.2.0 → inscript-0.3.0}/setup.cfg +0 -0
|
@@ -17,7 +17,7 @@ import time
|
|
|
17
17
|
from datetime import datetime, timezone
|
|
18
18
|
from pathlib import Path
|
|
19
19
|
|
|
20
|
-
__version__ = "0.
|
|
20
|
+
__version__ = "0.3.0"
|
|
21
21
|
|
|
22
22
|
INSCRIPT_DIR = Path.home() / ".inscript"
|
|
23
23
|
ACTIVE_PROJECT_FILE = INSCRIPT_DIR / "active_project"
|
|
@@ -202,6 +202,20 @@ def _cmd_status():
|
|
|
202
202
|
print(f" {s['session_id']} — {s.get('project', '?')}")
|
|
203
203
|
|
|
204
204
|
|
|
205
|
+
def _load_prompts(sdir: Path) -> list[dict]:
|
|
206
|
+
"""Load prompts for a session."""
|
|
207
|
+
prompts_file = sdir / "prompts.jsonl"
|
|
208
|
+
if not prompts_file.exists():
|
|
209
|
+
return []
|
|
210
|
+
prompts = []
|
|
211
|
+
for line in prompts_file.open():
|
|
212
|
+
try:
|
|
213
|
+
prompts.append(json.loads(line))
|
|
214
|
+
except json.JSONDecodeError:
|
|
215
|
+
pass
|
|
216
|
+
return prompts
|
|
217
|
+
|
|
218
|
+
|
|
205
219
|
def _cmd_log(session_id: str | None):
|
|
206
220
|
if session_id is None:
|
|
207
221
|
session_id = active_session()
|
|
@@ -212,25 +226,54 @@ def _cmd_log(session_id: str | None):
|
|
|
212
226
|
return
|
|
213
227
|
session_id = sessions[0]["session_id"]
|
|
214
228
|
|
|
215
|
-
|
|
229
|
+
sdir = session_dir(session_id)
|
|
230
|
+
touches_file = sdir / "touches.jsonl"
|
|
216
231
|
if not touches_file.exists():
|
|
217
232
|
print(f"No activity log for {session_id}", file=__import__("sys").stderr)
|
|
218
233
|
return
|
|
219
234
|
|
|
220
|
-
|
|
235
|
+
prompts = _load_prompts(sdir)
|
|
236
|
+
|
|
237
|
+
# Group touches by prompt_idx
|
|
238
|
+
touches_by_prompt: dict[int | None, list[dict]] = {}
|
|
221
239
|
files_seen = set()
|
|
222
240
|
edits = 0
|
|
223
241
|
for line in touches_file.open():
|
|
224
242
|
try:
|
|
225
243
|
e = json.loads(line)
|
|
226
|
-
|
|
227
|
-
|
|
244
|
+
pidx = e.get("prompt_idx")
|
|
245
|
+
touches_by_prompt.setdefault(pidx, []).append(e)
|
|
228
246
|
files_seen.add(e.get("file"))
|
|
229
247
|
if e.get("action") in ("edit", "write"):
|
|
230
248
|
edits += 1
|
|
231
249
|
except json.JSONDecodeError:
|
|
232
250
|
pass
|
|
233
|
-
|
|
251
|
+
|
|
252
|
+
print(f"Session: {session_id}\n")
|
|
253
|
+
|
|
254
|
+
if prompts:
|
|
255
|
+
for p in prompts:
|
|
256
|
+
idx = p.get("idx", 0)
|
|
257
|
+
prompt_text = p.get("prompt", "")
|
|
258
|
+
# Truncate long prompts
|
|
259
|
+
if len(prompt_text) > 80:
|
|
260
|
+
prompt_text = prompt_text[:77] + "..."
|
|
261
|
+
print(f" [{p.get('ts', '?')}] \"{prompt_text}\"")
|
|
262
|
+
for t in touches_by_prompt.get(idx, []):
|
|
263
|
+
extra = f" ({t['lines_changed']} lines)" if t.get("lines_changed") else ""
|
|
264
|
+
print(f" {t.get('action', '?'):6s} {t.get('file', '?')}{extra}")
|
|
265
|
+
print()
|
|
266
|
+
else:
|
|
267
|
+
# No prompts recorded — flat list
|
|
268
|
+
for line in touches_file.open():
|
|
269
|
+
try:
|
|
270
|
+
e = json.loads(line)
|
|
271
|
+
extra = f" ({e['lines_changed']} lines)" if e.get("lines_changed") else ""
|
|
272
|
+
print(f" {e.get('ts', '?')} {e.get('action', '?'):6s} {e.get('file', '?')}{extra}")
|
|
273
|
+
except json.JSONDecodeError:
|
|
274
|
+
pass
|
|
275
|
+
|
|
276
|
+
print(f" {len(files_seen)} files, {edits} edits, {len(prompts)} prompts")
|
|
234
277
|
|
|
235
278
|
|
|
236
279
|
def _cmd_overlap():
|
|
@@ -300,30 +343,75 @@ def _cmd_export(session_id: str):
|
|
|
300
343
|
if summary_file.exists():
|
|
301
344
|
s = json.loads(summary_file.read_text())
|
|
302
345
|
print(f"- **Ended**: {s.get('end_time', '?')}")
|
|
346
|
+
print(f"- **Prompts**: {s.get('prompts', '?')}")
|
|
303
347
|
print(f"- **Files read**: {s.get('files_read', '?')}")
|
|
304
348
|
print(f"- **Files written**: {s.get('files_written', '?')}")
|
|
305
349
|
print(f"- **Total edits**: {s.get('total_edits', '?')}")
|
|
306
350
|
|
|
351
|
+
# Load prompts, touches, and diffs
|
|
352
|
+
prompts = _load_prompts(sdir)
|
|
353
|
+
|
|
354
|
+
touches_by_prompt: dict[int | None, list[dict]] = {}
|
|
307
355
|
if touches_file.exists():
|
|
308
|
-
print(f"\n## Activity\n")
|
|
309
|
-
print("| Time | Action | File |")
|
|
310
|
-
print("|------|--------|------|")
|
|
311
356
|
for line in touches_file.open():
|
|
312
357
|
try:
|
|
313
358
|
e = json.loads(line)
|
|
314
|
-
|
|
359
|
+
touches_by_prompt.setdefault(e.get("prompt_idx"), []).append(e)
|
|
315
360
|
except json.JSONDecodeError:
|
|
316
361
|
pass
|
|
317
362
|
|
|
363
|
+
diffs_by_prompt: dict[int | None, list[dict]] = {}
|
|
318
364
|
if diffs_file.exists():
|
|
319
|
-
print(f"\n## Changes\n")
|
|
320
365
|
for line in diffs_file.open():
|
|
321
366
|
try:
|
|
322
367
|
d = json.loads(line)
|
|
323
|
-
|
|
368
|
+
diffs_by_prompt.setdefault(d.get("prompt_idx"), []).append(d)
|
|
369
|
+
except json.JSONDecodeError:
|
|
370
|
+
pass
|
|
371
|
+
|
|
372
|
+
if prompts:
|
|
373
|
+
# Group output by prompt
|
|
374
|
+
for p in prompts:
|
|
375
|
+
idx = p.get("idx", 0)
|
|
376
|
+
print(f"\n## \"{p.get('prompt', '?')}\"\n")
|
|
377
|
+
print(f"*{p.get('ts', '')}*\n")
|
|
378
|
+
|
|
379
|
+
touches = touches_by_prompt.get(idx, [])
|
|
380
|
+
if touches:
|
|
381
|
+
print("| Action | File | Details |")
|
|
382
|
+
print("|--------|------|---------|")
|
|
383
|
+
for t in touches:
|
|
384
|
+
details = ""
|
|
385
|
+
if t.get("lines_changed"):
|
|
386
|
+
details = f"{t['lines_changed']} lines"
|
|
387
|
+
elif t.get("lines"):
|
|
388
|
+
details = f"{t['lines']} lines"
|
|
389
|
+
print(f"| {t.get('action', '')} | `{t.get('file', '')}` | {details} |")
|
|
390
|
+
print()
|
|
391
|
+
|
|
392
|
+
diffs = diffs_by_prompt.get(idx, [])
|
|
393
|
+
for d in diffs:
|
|
324
394
|
if d.get("old_string") is not None:
|
|
395
|
+
print(f"**`{d.get('file', '?')}`**")
|
|
325
396
|
print(f"```diff\n- {d['old_string']}\n+ {d.get('new_string', '')}\n```\n")
|
|
326
397
|
elif d.get("is_new"):
|
|
327
|
-
print(f"
|
|
328
|
-
|
|
329
|
-
|
|
398
|
+
print(f"**`{d.get('file', '?')}`** — new file ({d.get('lines', '?')} lines)\n")
|
|
399
|
+
else:
|
|
400
|
+
# No prompts — flat output
|
|
401
|
+
if touches_by_prompt:
|
|
402
|
+
print(f"\n## Activity\n")
|
|
403
|
+
print("| Time | Action | File |")
|
|
404
|
+
print("|------|--------|------|")
|
|
405
|
+
for touches in touches_by_prompt.values():
|
|
406
|
+
for e in touches:
|
|
407
|
+
print(f"| {e.get('ts', '')} | {e.get('action', '')} | `{e.get('file', '')}` |")
|
|
408
|
+
|
|
409
|
+
if diffs_by_prompt:
|
|
410
|
+
print(f"\n## Changes\n")
|
|
411
|
+
for diffs in diffs_by_prompt.values():
|
|
412
|
+
for d in diffs:
|
|
413
|
+
if d.get("old_string") is not None:
|
|
414
|
+
print(f"### `{d.get('file', '?')}` ({d.get('ts', '')})\n")
|
|
415
|
+
print(f"```diff\n- {d['old_string']}\n+ {d.get('new_string', '')}\n```\n")
|
|
416
|
+
elif d.get("is_new"):
|
|
417
|
+
print(f"New file `{d.get('file', '?')}` ({d.get('lines', '?')} lines)\n")
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"""Inscript hook — records agent activity to ~/.inscript/.
|
|
2
2
|
|
|
3
|
-
Handles
|
|
4
|
-
SessionStart
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
Handles four Claude Code hook events:
|
|
4
|
+
SessionStart → creates session directory + meta.json
|
|
5
|
+
UserPromptSubmit → records the user's prompt, starts a new work block
|
|
6
|
+
PostToolUse → appends to touches.jsonl + diffs.jsonl, tags with prompt
|
|
7
|
+
Stop → finalizes summary.json
|
|
7
8
|
|
|
8
9
|
All entry points read JSON from stdin and write to the filesystem.
|
|
9
10
|
Designed to run async (non-blocking) so the agent never waits.
|
|
@@ -90,6 +91,20 @@ def _append_jsonl(path: Path, data: dict) -> None:
|
|
|
90
91
|
f.write(json.dumps(data, default=str) + "\n")
|
|
91
92
|
|
|
92
93
|
|
|
94
|
+
def _count_lines(path: Path) -> int:
|
|
95
|
+
"""Count lines in a JSONL file."""
|
|
96
|
+
try:
|
|
97
|
+
return sum(1 for _ in path.open())
|
|
98
|
+
except OSError:
|
|
99
|
+
return 0
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _current_prompt_idx(sdir: Path) -> int | None:
|
|
103
|
+
"""Get the index of the most recent prompt (0-based)."""
|
|
104
|
+
n = _count_lines(sdir / "prompts.jsonl")
|
|
105
|
+
return n - 1 if n > 0 else None
|
|
106
|
+
|
|
107
|
+
|
|
93
108
|
# ---------------------------------------------------------------------------
|
|
94
109
|
# SessionStart handler
|
|
95
110
|
# ---------------------------------------------------------------------------
|
|
@@ -120,6 +135,37 @@ def handle_session_start(data: dict) -> None:
|
|
|
120
135
|
ACTIVE_SESSION_FILE.write_text(session_id + "\n")
|
|
121
136
|
|
|
122
137
|
|
|
138
|
+
# ---------------------------------------------------------------------------
|
|
139
|
+
# UserPromptSubmit handler
|
|
140
|
+
# ---------------------------------------------------------------------------
|
|
141
|
+
|
|
142
|
+
def handle_prompt_submit(data: dict) -> None:
|
|
143
|
+
"""Record a user prompt, starting a new work block."""
|
|
144
|
+
session_id = active_session()
|
|
145
|
+
if not session_id:
|
|
146
|
+
session_id = f"s-{int(time.time())}"
|
|
147
|
+
handle_session_start({"session_id": session_id})
|
|
148
|
+
|
|
149
|
+
sdir = SESSIONS_DIR / session_id
|
|
150
|
+
|
|
151
|
+
# Extract prompt text from hook input
|
|
152
|
+
# Claude Code sends tool_input with the user's message
|
|
153
|
+
prompt = data.get("tool_input", {}).get("prompt", "")
|
|
154
|
+
if not prompt:
|
|
155
|
+
# Try alternate locations
|
|
156
|
+
prompt = data.get("prompt", "") or data.get("message", "")
|
|
157
|
+
if not prompt:
|
|
158
|
+
return
|
|
159
|
+
|
|
160
|
+
prompt_idx = _count_lines(sdir / "prompts.jsonl")
|
|
161
|
+
|
|
162
|
+
_append_jsonl(sdir / "prompts.jsonl", {
|
|
163
|
+
"idx": prompt_idx,
|
|
164
|
+
"ts": _now_time(),
|
|
165
|
+
"prompt": prompt,
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
|
|
123
169
|
# ---------------------------------------------------------------------------
|
|
124
170
|
# PostToolUse handler
|
|
125
171
|
# ---------------------------------------------------------------------------
|
|
@@ -175,12 +221,15 @@ def handle_post_tool_use(data: dict) -> None:
|
|
|
175
221
|
|
|
176
222
|
# Append to touches.jsonl
|
|
177
223
|
action = _tool_action(tool_name)
|
|
224
|
+
prompt_idx = _current_prompt_idx(sdir)
|
|
178
225
|
touch = {
|
|
179
226
|
"ts": _now_time(),
|
|
180
227
|
"file": display_path,
|
|
181
228
|
"action": action,
|
|
182
229
|
"tool": tool_name,
|
|
183
230
|
}
|
|
231
|
+
if prompt_idx is not None:
|
|
232
|
+
touch["prompt_idx"] = prompt_idx
|
|
184
233
|
|
|
185
234
|
# Add lines_changed for Edit
|
|
186
235
|
if tool_name == "Edit" and tool_input.get("new_string") is not None:
|
|
@@ -201,6 +250,8 @@ def handle_post_tool_use(data: dict) -> None:
|
|
|
201
250
|
"file": display_path,
|
|
202
251
|
"tool": tool_name,
|
|
203
252
|
}
|
|
253
|
+
if prompt_idx is not None:
|
|
254
|
+
diff_entry["prompt_idx"] = prompt_idx
|
|
204
255
|
|
|
205
256
|
if tool_name == "Edit":
|
|
206
257
|
diff_entry["old_string"] = tool_input.get("old_string", "")
|
|
@@ -297,9 +348,14 @@ def handle_stop(data: dict) -> None:
|
|
|
297
348
|
except ValueError:
|
|
298
349
|
pass
|
|
299
350
|
|
|
351
|
+
# Count prompts
|
|
352
|
+
prompts_file = sdir / "prompts.jsonl"
|
|
353
|
+
prompt_count = _count_lines(prompts_file)
|
|
354
|
+
|
|
300
355
|
summary = {
|
|
301
356
|
"end_time": end_time,
|
|
302
357
|
"duration_seconds": duration,
|
|
358
|
+
"prompts": prompt_count,
|
|
303
359
|
"files_read": len(files_read),
|
|
304
360
|
"files_written": len(files_written),
|
|
305
361
|
"files_read_list": sorted(files_read),
|
|
@@ -334,6 +390,8 @@ def main() -> None:
|
|
|
334
390
|
|
|
335
391
|
if hook_event == "SessionStart":
|
|
336
392
|
handle_session_start(data)
|
|
393
|
+
elif hook_event == "UserPromptSubmit":
|
|
394
|
+
handle_prompt_submit(data)
|
|
337
395
|
elif hook_event == "Stop":
|
|
338
396
|
handle_stop(data)
|
|
339
397
|
else:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|