@pragma-sh/claude-code-plugin 0.1.0-alpha.0 → 1.0.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.
@@ -1,7 +1,7 @@
1
- // ../plugin/dist/shared/chunk-k5kqv14t.js
2
- var PLUGIN_API_VERSION = "0.0.0";
1
+ // ../plugin/dist/shared/chunk-jypnsjrp.js
2
+ var PLUGIN_API_VERSION = "1.0.0";
3
3
 
4
- // ../plugin/dist/shared/chunk-0rpz2gg0.js
4
+ // ../plugin/dist/shared/chunk-jx8c1n1n.js
5
5
  function defineAgent(input) {
6
6
  return input;
7
7
  }
package/hooks/report.sh CHANGED
@@ -50,6 +50,8 @@ children_dir="${state_dir}/pragma-cli-${agent}-${tab}.subagents"
50
50
  # marker-guarded report for the rest of the turn. This file lets `cleared` tell
51
51
  # a stale clear from a legitimate one.
52
52
  turn_session_file="${state_dir}/pragma-cli-${agent}-${tab}.turn-session"
53
+ assistant_cursor_file="${state_dir}/pragma-cli-${agent}-${tab}.assistant-cursor"
54
+ assistant_hash_file="${state_dir}/pragma-cli-${agent}-${tab}.assistant-hash"
53
55
 
54
56
  # Poll cadence and absolute lifetime backstop (overridable for tests). The
55
57
  # backstop guarantees a watcher can't outlive its session forever if the session
@@ -78,7 +80,7 @@ report_checked() {
78
80
 
79
81
  # Reports a coarse rich message. Hook payloads are intentionally not parsed here
80
82
  # beyond existing transcript handling; this keeps hooks fail-open and portable.
81
- # AgentMessage.ts is milliseconds since Unix epoch (see @pragma/constants).
83
+ # AgentMessage.ts is milliseconds since Unix epoch (see @pragma-sh/constants).
82
84
  # `date +%s` is seconds — multiply so chat clients that stamp local input with
83
85
  # Date.now() don't sort every agent bubble above the user's messages.
84
86
  message_ts_ms() {
@@ -185,6 +187,81 @@ print(json.dumps({
185
187
  "$pragma_cli" agent message --agent "$agent" --payload "$payload" >/dev/null 2>&1 || true
186
188
  }
187
189
 
190
+ # Reports assistant text once per turn. Stop payloads often repeat the newest
191
+ # transcript segment, so retain its checksum to avoid duplicate chat bubbles.
192
+ assistant_message_once() {
193
+ text="$1"
194
+ [ -n "$text" ] || return 0
195
+ hash=$(printf '%s' "$text" | cksum | tr -d '[:space:]')
196
+ [ "$(cat "$assistant_hash_file" 2>/dev/null)" = "$hash" ] && return 0
197
+ content_message assistant "$text"
198
+ printf '%s' "$hash" >"$assistant_hash_file"
199
+ }
200
+
201
+ assistant_text_count() {
202
+ tp="$1"
203
+ [ -n "$py3" ] && [ -n "$tp" ] && [ -f "$tp" ] || {
204
+ printf '0'
205
+ return 0
206
+ }
207
+ "$py3" -c '
208
+ import json, sys
209
+ count = 0
210
+ for line in open(sys.argv[1], encoding="utf-8", errors="replace"):
211
+ try:
212
+ obj = json.loads(line)
213
+ except Exception:
214
+ continue
215
+ if obj.get("type") != "assistant":
216
+ continue
217
+ content = (obj.get("message") or {}).get("content") or []
218
+ if any(isinstance(c, dict) and c.get("type") == "text" and c.get("text") for c in content):
219
+ count += 1
220
+ print(count)
221
+ ' "$tp" 2>/dev/null
222
+ }
223
+
224
+ # Prints base64-encoded assistant text records after a text-record cursor.
225
+ assistant_texts_since() {
226
+ tp="$1"
227
+ start="$2"
228
+ [ -n "$py3" ] && [ -n "$tp" ] && [ -f "$tp" ] || return 0
229
+ "$py3" -c '
230
+ import base64, json, sys
231
+ start = int(sys.argv[2])
232
+ index = 0
233
+ for line in open(sys.argv[1], encoding="utf-8", errors="replace"):
234
+ try:
235
+ obj = json.loads(line)
236
+ except Exception:
237
+ continue
238
+ if obj.get("type") != "assistant":
239
+ continue
240
+ content = (obj.get("message") or {}).get("content") or []
241
+ parts = [c.get("text", "") for c in content if isinstance(c, dict) and c.get("type") == "text"]
242
+ text = "\n".join(p for p in parts if p)
243
+ if not text:
244
+ continue
245
+ if index >= start:
246
+ print(base64.b64encode(text.encode()).decode())
247
+ index += 1
248
+ ' "$tp" "$start" 2>/dev/null
249
+ }
250
+
251
+ # Streams newly completed transcript segments. Cursor is initialized when turn
252
+ # starts, excluding every assistant record from earlier turns.
253
+ sync_assistant_transcript() {
254
+ tp="$1"
255
+ [ -n "$py3" ] && [ -n "$tp" ] && [ -f "$tp" ] || return 0
256
+ cursor=$(cat "$assistant_cursor_file" 2>/dev/null)
257
+ [ -n "$cursor" ] || cursor=0
258
+ for encoded in $(assistant_texts_since "$tp" "$cursor"); do
259
+ text=$("$py3" -c 'import base64,sys; print(base64.b64decode(sys.argv[1]).decode())' "$encoded" 2>/dev/null)
260
+ assistant_message_once "$text"
261
+ done
262
+ assistant_text_count "$tp" >"$assistant_cursor_file"
263
+ }
264
+
188
265
  # Prints the newest assistant text in a Claude Code transcript JSONL — the
189
266
  # reply the turn that just stopped produced. Empty output when unavailable.
190
267
  last_assistant_text() {
@@ -626,7 +703,7 @@ run_watcher() {
626
703
  # Re-check the token right before acting so we never clear a turn that
627
704
  # started in the gap between the poll and now.
628
705
  [ "$(cat "$marker" 2>/dev/null)" = "$token" ] || exit 0
629
- rm -f "$marker" "$turn_session_file"
706
+ rm -f "$marker" "$turn_session_file" "$assistant_cursor_file" "$assistant_hash_file"
630
707
  report cleared
631
708
  exit 0
632
709
  fi
@@ -710,8 +787,10 @@ case "${1:-}" in
710
787
  ;;
711
788
  esac
712
789
  stop_watcher
790
+ rm -f "$assistant_cursor_file" "$assistant_hash_file"
713
791
  tp="$(transcript_path "$input")"
714
792
  if [ -n "$tp" ]; then
793
+ assistant_text_count "$tp" >"$assistant_cursor_file"
715
794
  # Pin the watcher to where the transcript stands *now* so a prior turn's
716
795
  # interrupt marker (already in the file) can't be mistaken for this turn's
717
796
  # cancel while Claude is still thinking and has appended nothing yet.
@@ -728,7 +807,7 @@ case "${1:-}" in
728
807
  tp="$(transcript_path "$input")"
729
808
  if turn_interrupted "$tp"; then
730
809
  stop_watcher
731
- rm -f "$marker" "$turn_session_file"
810
+ rm -f "$marker" "$turn_session_file" "$assistant_cursor_file" "$assistant_hash_file"
732
811
  report cleared
733
812
  message system "Claude Code turn interrupted"
734
813
  elif has_running_subagents "$input" || has_tracked_subagents; then
@@ -739,10 +818,11 @@ case "${1:-}" in
739
818
  # is still detected, and surface the parent's interim reply.
740
819
  [ -f "$marker" ] || printf '%s' "$$-$(date +%s)" >"$marker"
741
820
  report started
821
+ sync_assistant_transcript "$tp"
742
822
  reply="$(json_field last_assistant_message "$input")"
743
823
  [ -n "$reply" ] || reply="$(last_assistant_text "$tp")"
744
824
  if [ -n "$reply" ]; then
745
- content_message assistant "$reply"
825
+ assistant_message_once "$reply"
746
826
  else
747
827
  message assistant "Claude Code is waiting on subagents"
748
828
  fi
@@ -751,17 +831,19 @@ case "${1:-}" in
751
831
  # green "done" dot.
752
832
  stop_watcher
753
833
  rm -f "$marker" "$turn_session_file"
754
- report stopped
755
834
  # Stop carries the completed reply directly on current Claude builds.
756
835
  # Prefer it over rereading the transcript, then retain transcript support
757
836
  # for older builds that omit the field.
837
+ sync_assistant_transcript "$tp"
758
838
  reply="$(json_field last_assistant_message "$input")"
759
839
  [ -n "$reply" ] || reply="$(last_assistant_text "$tp")"
760
840
  if [ -n "$reply" ]; then
761
- content_message assistant "$reply"
841
+ assistant_message_once "$reply"
762
842
  else
763
843
  message assistant "Claude Code turn completed"
764
844
  fi
845
+ report stopped
846
+ rm -f "$assistant_cursor_file" "$assistant_hash_file"
765
847
  fi
766
848
  ;;
767
849
  cleared)
@@ -778,7 +860,7 @@ case "${1:-}" in
778
860
  exit 0
779
861
  fi
780
862
  stop_watcher
781
- rm -f "$marker" "$turn_session_file"
863
+ rm -f "$marker" "$turn_session_file" "$assistant_cursor_file" "$assistant_hash_file"
782
864
  clear_subagents
783
865
  if [ "$hook_event_name" = "SessionEnd" ]; then
784
866
  rm -f "$session_file"
@@ -796,6 +878,7 @@ case "${1:-}" in
796
878
  # marker so a stray PostToolUse outside a turn can't flash a phantom "running".
797
879
  if [ -f "$marker" ]; then
798
880
  report started
881
+ sync_assistant_transcript "$(transcript_path "$input")"
799
882
  message tool "Claude Code tool finished"
800
883
  fi
801
884
  ;;
@@ -863,7 +946,7 @@ case "${1:-}" in
863
946
  # Claude Code build emits it, a lingering marker still clears the turn.)
864
947
  if [ -f "$marker" ]; then
865
948
  stop_watcher
866
- rm -f "$marker" "$turn_session_file"
949
+ rm -f "$marker" "$turn_session_file" "$assistant_cursor_file" "$assistant_hash_file"
867
950
  report cleared
868
951
  message system "Claude Code turn cleared"
869
952
  fi
package/package.json CHANGED
@@ -1,8 +1,13 @@
1
1
  {
2
2
  "name": "@pragma-sh/claude-code-plugin",
3
- "version": "0.1.0-alpha.0",
3
+ "version": "1.0.0",
4
4
  "description": "Claude Code plugin that reports agent status to Pragma automatically via shell hooks.",
5
5
  "license": "AGPL-3.0-only",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/pragma-sh/pragma.git",
9
+ "directory": "packages/claude-code-plugin"
10
+ },
6
11
  "files": [
7
12
  ".claude-plugin",
8
13
  "assets",
@@ -27,8 +32,8 @@
27
32
  "lint": "oxlint ."
28
33
  },
29
34
  "devDependencies": {
30
- "@pragma/plugin": "workspace:*",
31
- "@pragma/watcher-kit": "workspace:*",
35
+ "@pragma-sh/plugin": "workspace:*",
36
+ "@pragma-sh/watcher-kit": "workspace:*",
32
37
  "@types/node": "^24.12.3",
33
38
  "bunup": "^0.16.32",
34
39
  "typescript": "^6.0.3",
@@ -2,6 +2,7 @@
2
2
  "$schema": "https://raw.githubusercontent.com/pragma-sh/pragma/main/packages/plugin-registry/pragma-plugin.schema.json",
3
3
  "name": "Claude Code",
4
4
  "description": "Launch Claude Code in Pragma with live status, questions, usage limits, and session naming.",
5
+ "longDescription": "Launch Claude Code from the Pragma launcher and the integration reports live status, questions, approvals, usage limits, and session naming back to the app. Status lands on the tab, the sidebar, and agent alerts, so several Claude Code sessions stay readable side by side across worktrees.",
5
6
  "categories": ["agent-plugin"],
6
7
  "images": [
7
8
  {
@@ -9,6 +10,9 @@
9
10
  "alt": "Claude Code logo"
10
11
  }
11
12
  ],
12
- "install": { "command": "sh", "args": ["scripts/install.sh"] },
13
+ "install": {
14
+ "command": "sh",
15
+ "args": ["scripts/install.sh"]
16
+ },
13
17
  "agentBinary": "claude"
14
18
  }
@@ -6,8 +6,8 @@ import {
6
6
  type PluginDefinition,
7
7
  type UsageLimit,
8
8
  type UsageLimitsResult,
9
- } from "@pragma/plugin/catalog";
10
- import { createTuiWatcher } from "@pragma/watcher-kit";
9
+ } from "@pragma-sh/plugin/catalog";
10
+ import { createTuiWatcher } from "@pragma-sh/watcher-kit";
11
11
 
12
12
  /** Lets Claude Code's paste-aware TUI commit interjected text before Enter. */
13
13
  const INTERJECT_SUBMIT_DELAY_MS = 200;