@jefuriiij/synthra 0.1.24 → 0.1.25
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 +23 -0
- package/LICENSE +21 -21
- package/README.md +222 -222
- package/dist/cli/index.js +180 -172
- package/dist/cli/index.js.map +1 -1
- package/dist/dashboard/index.js +1 -1
- package/dist/dashboard/index.js.map +1 -1
- package/dist/server/index.js.map +1 -1
- package/package.json +66 -66
package/dist/cli/index.js
CHANGED
|
@@ -18,7 +18,7 @@ var init_package = __esm({
|
|
|
18
18
|
"package.json"() {
|
|
19
19
|
package_default = {
|
|
20
20
|
name: "@jefuriiij/synthra",
|
|
21
|
-
version: "0.1.
|
|
21
|
+
version: "0.1.25",
|
|
22
22
|
publishConfig: {
|
|
23
23
|
access: "public"
|
|
24
24
|
},
|
|
@@ -1454,33 +1454,41 @@ exit 0
|
|
|
1454
1454
|
var pre_tool_use_default = '# PreToolUse hook \u2014 Windows PowerShell.\n# THE MOAT (improvement #1). Reads the tool call from stdin (JSON), POSTs it\n# to /gate, and if the server says "block" emits a JSON deny-decision to\n# stdout. Claude Code reads stdout JSON to enforce the decision.\n# Always exits 0; failure-to-reach-server leaves Claude untouched.\n\n$ErrorActionPreference = "SilentlyContinue"\n\n$raw = [Console]::In.ReadToEnd()\nif (-not $raw) { exit 0 }\n\ntry {\n $hookInput = $raw | ConvertFrom-Json -ErrorAction Stop\n} catch {\n exit 0\n}\n\n$portFile = Join-Path $PWD ".synthra-graph\\mcp_port"\nif (-not (Test-Path $portFile)) { exit 0 }\n$port = (Get-Content -Path $portFile -Raw).Trim()\nif (-not $port) { exit 0 }\n\n$payload = @{\n tool_name = $hookInput.tool_name\n tool_input = $hookInput.tool_input\n} | ConvertTo-Json -Depth 10 -Compress\n\ntry {\n $resp = Invoke-RestMethod -Uri "http://127.0.0.1:$port/gate" -Method POST `\n -Body $payload -ContentType "application/json" -TimeoutSec 3\n} catch {\n exit 0\n}\n\nif ($resp.decision -eq "block") {\n $denyJson = @{\n hookSpecificOutput = @{\n hookEventName = "PreToolUse"\n permissionDecision = "deny"\n permissionDecisionReason = $resp.reason\n }\n } | ConvertTo-Json -Depth 5 -Compress\n Write-Output $denyJson\n}\nexit 0\n';
|
|
1455
1455
|
|
|
1456
1456
|
// src/hooks/scripts/pre-tool-use.sh
|
|
1457
|
-
var pre_tool_use_default2 = `#!/usr/bin/env bash
|
|
1458
|
-
# PreToolUse hook \u2014 bash. POSTs the tool call to /gate; if server returns
|
|
1459
|
-
# "block", emits the deny-decision JSON to stdout for Claude Code to enforce
|
|
1460
|
-
# Always exits 0; server failures leave Claude untouched
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
if [ -
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
exit 0
|
|
1457
|
+
var pre_tool_use_default2 = `#!/usr/bin/env bash
|
|
1458
|
+
# PreToolUse hook \u2014 bash. POSTs the tool call to /gate; if server returns
|
|
1459
|
+
# "block", emits the deny-decision JSON to stdout for Claude Code to enforce.
|
|
1460
|
+
# Always exits 0; server failures leave Claude untouched.
|
|
1461
|
+
# Requires \`jq\` to read the gate response; falls back to silent no-op (no
|
|
1462
|
+
# enforcement) if absent \u2014 same policy as the Stop/Prime hooks.
|
|
1463
|
+
|
|
1464
|
+
set +e
|
|
1465
|
+
|
|
1466
|
+
PORT_FILE="$PWD/.synthra-graph/mcp_port"
|
|
1467
|
+
if [ ! -f "$PORT_FILE" ]; then exit 0; fi
|
|
1468
|
+
PORT=$(cat "$PORT_FILE" 2>/dev/null | tr -d '[:space:]')
|
|
1469
|
+
if [ -z "$PORT" ]; then exit 0; fi
|
|
1470
|
+
|
|
1471
|
+
INPUT=$(cat 2>/dev/null)
|
|
1472
|
+
if [ -z "$INPUT" ]; then exit 0; fi
|
|
1473
|
+
|
|
1474
|
+
RESP=$(curl -sS --max-time 3 -X POST -H "Content-Type: application/json" \\
|
|
1475
|
+
--data "$INPUT" "http://127.0.0.1:$PORT/gate" 2>/dev/null)
|
|
1476
|
+
|
|
1477
|
+
# Parse the gate response with jq, not a greedy sed capture. The block \`reason\`
|
|
1478
|
+
# legitimately contains double quotes (it quotes the query, e.g. "login"), so the
|
|
1479
|
+
# old sed capture (\\(.*\\)") both over-ran into the trailing JSON fields and, once
|
|
1480
|
+
# embedded raw in the heredoc, produced invalid hook output. jq reads each field
|
|
1481
|
+
# and re-emits the deny object with correct escaping. (matches stop.sh / prime.sh,
|
|
1482
|
+
# jq fix #1.) No jq \u2192 no enforcement; bail silently like the other hooks.
|
|
1483
|
+
if ! command -v jq >/dev/null 2>&1; then exit 0; fi
|
|
1484
|
+
|
|
1485
|
+
DECISION=$(printf '%s' "$RESP" | jq -r '.decision // empty' 2>/dev/null)
|
|
1486
|
+
if [ "$DECISION" = "block" ]; then
|
|
1487
|
+
REASON=$(printf '%s' "$RESP" | jq -r '.reason // empty' 2>/dev/null)
|
|
1488
|
+
jq -nc --arg r "$REASON" \\
|
|
1489
|
+
'{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:$r}}'
|
|
1490
|
+
fi
|
|
1491
|
+
exit 0
|
|
1484
1492
|
`;
|
|
1485
1493
|
|
|
1486
1494
|
// src/hooks/scripts/prime.ps1
|
|
@@ -1536,153 +1544,153 @@ exit 0
|
|
|
1536
1544
|
`;
|
|
1537
1545
|
|
|
1538
1546
|
// src/hooks/scripts/stop.ps1
|
|
1539
|
-
var stop_default = `# Stop hook \u2014 Windows PowerShell
|
|
1540
|
-
# Reads Claude's transcript JSONL from $hookInput.transcript_path, sums
|
|
1541
|
-
# usage.* token counts across all assistant turns since the last offset, and
|
|
1542
|
-
# POSTs the totals to /log. Uses a per-transcript .stopoffset file to avoid
|
|
1543
|
-
# double-counting on session resume
|
|
1544
|
-
|
|
1545
|
-
$ErrorActionPreference = "SilentlyContinue"
|
|
1546
|
-
|
|
1547
|
-
$raw = [Console]::In.ReadToEnd()
|
|
1548
|
-
if (-not $raw) { exit 0 }
|
|
1549
|
-
try { $hookInput = $raw | ConvertFrom-Json -ErrorAction Stop } catch { exit 0 }
|
|
1550
|
-
|
|
1551
|
-
$transcript = $hookInput.transcript_path
|
|
1552
|
-
if (-not $transcript -or -not (Test-Path $transcript)) { exit 0 }
|
|
1553
|
-
|
|
1554
|
-
$portFile = Join-Path $PWD ".synthra-graph\\mcp_port"
|
|
1555
|
-
if (-not (Test-Path $portFile)) { exit 0 }
|
|
1556
|
-
$port = (Get-Content -Path $portFile -Raw).Trim()
|
|
1557
|
-
if (-not $port) { exit 0 }
|
|
1558
|
-
|
|
1559
|
-
$offsetFile = "$transcript.stopoffset"
|
|
1560
|
-
$startOffset = 0
|
|
1561
|
-
if (Test-Path $offsetFile) {
|
|
1562
|
-
$val = (Get-Content -Path $offsetFile -Raw).Trim()
|
|
1563
|
-
if ($val -match '^\\d+$') { $startOffset = [int]$val }
|
|
1564
|
-
}
|
|
1565
|
-
|
|
1566
|
-
$lines = Get-Content -Path $transcript
|
|
1567
|
-
$inT = 0; $outT = 0; $cc = 0; $cr = 0; $model = ""
|
|
1568
|
-
$lineNum = 0
|
|
1569
|
-
foreach ($line in $lines) {
|
|
1570
|
-
$lineNum
|
|
1571
|
-
if ($lineNum -le $startOffset) { continue }
|
|
1572
|
-
if (-not $line) { continue }
|
|
1573
|
-
try { $e = $line | ConvertFrom-Json -ErrorAction Stop } catch { continue }
|
|
1574
|
-
$usage = $e.message.usage
|
|
1575
|
-
if (-not $usage) { continue }
|
|
1576
|
-
$inT += [int]($usage.input_tokens | ForEach-Object { if ($_) { $_ } else { 0 } })
|
|
1577
|
-
$outT += [int]($usage.output_tokens | ForEach-Object { if ($_) { $_ } else { 0 } })
|
|
1578
|
-
$cc += [int]($usage.cache_creation_input_tokens | ForEach-Object { if ($_) { $_ } else { 0 } })
|
|
1579
|
-
$cr += [int]($usage.cache_read_input_tokens | ForEach-Object { if ($_) { $_ } else { 0 } })
|
|
1580
|
-
if ($e.message.model) { $model = $e.message.model }
|
|
1581
|
-
}
|
|
1582
|
-
|
|
1583
|
-
Set-Content -Path $offsetFile -Value $lineNum -Encoding ASCII
|
|
1584
|
-
|
|
1585
|
-
if ($inT -eq 0 -and $outT -eq 0) { exit 0 }
|
|
1586
|
-
|
|
1587
|
-
$payload = @{
|
|
1588
|
-
input_tokens = $inT
|
|
1589
|
-
output_tokens = $outT
|
|
1590
|
-
cache_creation_input_tokens = $cc
|
|
1591
|
-
cache_read_input_tokens = $cr
|
|
1592
|
-
model = $model
|
|
1593
|
-
description = "synthra-stop-hook"
|
|
1594
|
-
project = $PWD.Path
|
|
1595
|
-
} | ConvertTo-Json -Compress
|
|
1596
|
-
|
|
1597
|
-
try {
|
|
1598
|
-
Invoke-RestMethod -Uri "http://127.0.0.1:$port/log" -Method POST
|
|
1599
|
-
-Body $payload -ContentType "application/json" -TimeoutSec 3 | Out-Null
|
|
1600
|
-
} catch {
|
|
1601
|
-
# silent
|
|
1602
|
-
}
|
|
1603
|
-
|
|
1604
|
-
# Refresh CONTEXT.md from the branch-scoped store
|
|
1605
|
-
$ctxPayload = @{ transcript_path = $transcript } | ConvertTo-Json -Compress
|
|
1606
|
-
try {
|
|
1607
|
-
Invoke-RestMethod -Uri "http://127.0.0.1:$port/context-update" -Method POST
|
|
1608
|
-
-Body $ctxPayload -ContentType "application/json" -TimeoutSec 3 | Out-Null
|
|
1609
|
-
} catch {
|
|
1610
|
-
# silent
|
|
1611
|
-
}
|
|
1612
|
-
exit 0
|
|
1547
|
+
var stop_default = `# Stop hook \u2014 Windows PowerShell.
|
|
1548
|
+
# Reads Claude's transcript JSONL from $hookInput.transcript_path, sums
|
|
1549
|
+
# usage.* token counts across all assistant turns since the last offset, and
|
|
1550
|
+
# POSTs the totals to /log. Uses a per-transcript .stopoffset file to avoid
|
|
1551
|
+
# double-counting on session resume.
|
|
1552
|
+
|
|
1553
|
+
$ErrorActionPreference = "SilentlyContinue"
|
|
1554
|
+
|
|
1555
|
+
$raw = [Console]::In.ReadToEnd()
|
|
1556
|
+
if (-not $raw) { exit 0 }
|
|
1557
|
+
try { $hookInput = $raw | ConvertFrom-Json -ErrorAction Stop } catch { exit 0 }
|
|
1558
|
+
|
|
1559
|
+
$transcript = $hookInput.transcript_path
|
|
1560
|
+
if (-not $transcript -or -not (Test-Path $transcript)) { exit 0 }
|
|
1561
|
+
|
|
1562
|
+
$portFile = Join-Path $PWD ".synthra-graph\\mcp_port"
|
|
1563
|
+
if (-not (Test-Path $portFile)) { exit 0 }
|
|
1564
|
+
$port = (Get-Content -Path $portFile -Raw).Trim()
|
|
1565
|
+
if (-not $port) { exit 0 }
|
|
1566
|
+
|
|
1567
|
+
$offsetFile = "$transcript.stopoffset"
|
|
1568
|
+
$startOffset = 0
|
|
1569
|
+
if (Test-Path $offsetFile) {
|
|
1570
|
+
$val = (Get-Content -Path $offsetFile -Raw).Trim()
|
|
1571
|
+
if ($val -match '^\\d+$') { $startOffset = [int]$val }
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
$lines = Get-Content -Path $transcript
|
|
1575
|
+
$inT = 0; $outT = 0; $cc = 0; $cr = 0; $model = ""
|
|
1576
|
+
$lineNum = 0
|
|
1577
|
+
foreach ($line in $lines) {
|
|
1578
|
+
$lineNum++
|
|
1579
|
+
if ($lineNum -le $startOffset) { continue }
|
|
1580
|
+
if (-not $line) { continue }
|
|
1581
|
+
try { $e = $line | ConvertFrom-Json -ErrorAction Stop } catch { continue }
|
|
1582
|
+
$usage = $e.message.usage
|
|
1583
|
+
if (-not $usage) { continue }
|
|
1584
|
+
$inT += [int]($usage.input_tokens | ForEach-Object { if ($_) { $_ } else { 0 } })
|
|
1585
|
+
$outT += [int]($usage.output_tokens | ForEach-Object { if ($_) { $_ } else { 0 } })
|
|
1586
|
+
$cc += [int]($usage.cache_creation_input_tokens | ForEach-Object { if ($_) { $_ } else { 0 } })
|
|
1587
|
+
$cr += [int]($usage.cache_read_input_tokens | ForEach-Object { if ($_) { $_ } else { 0 } })
|
|
1588
|
+
if ($e.message.model) { $model = $e.message.model }
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
Set-Content -Path $offsetFile -Value $lineNum -Encoding ASCII
|
|
1592
|
+
|
|
1593
|
+
if ($inT -eq 0 -and $outT -eq 0) { exit 0 }
|
|
1594
|
+
|
|
1595
|
+
$payload = @{
|
|
1596
|
+
input_tokens = $inT
|
|
1597
|
+
output_tokens = $outT
|
|
1598
|
+
cache_creation_input_tokens = $cc
|
|
1599
|
+
cache_read_input_tokens = $cr
|
|
1600
|
+
model = $model
|
|
1601
|
+
description = "synthra-stop-hook"
|
|
1602
|
+
project = $PWD.Path
|
|
1603
|
+
} | ConvertTo-Json -Compress
|
|
1604
|
+
|
|
1605
|
+
try {
|
|
1606
|
+
Invoke-RestMethod -Uri "http://127.0.0.1:$port/log" -Method POST \`
|
|
1607
|
+
-Body $payload -ContentType "application/json" -TimeoutSec 3 | Out-Null
|
|
1608
|
+
} catch {
|
|
1609
|
+
# silent
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
# Refresh CONTEXT.md from the branch-scoped store.
|
|
1613
|
+
$ctxPayload = @{ transcript_path = $transcript } | ConvertTo-Json -Compress
|
|
1614
|
+
try {
|
|
1615
|
+
Invoke-RestMethod -Uri "http://127.0.0.1:$port/context-update" -Method POST \`
|
|
1616
|
+
-Body $ctxPayload -ContentType "application/json" -TimeoutSec 3 | Out-Null
|
|
1617
|
+
} catch {
|
|
1618
|
+
# silent
|
|
1619
|
+
}
|
|
1620
|
+
exit 0
|
|
1613
1621
|
`;
|
|
1614
1622
|
|
|
1615
1623
|
// src/hooks/scripts/stop.sh
|
|
1616
|
-
var stop_default2 = `#!/usr/bin/env bash
|
|
1617
|
-
# Stop hook \u2014 bash. Reads transcript JSONL, sums usage.* across new lines
|
|
1618
|
-
# POSTs totals to /log. Uses a .stopoffset file to avoid double-counting
|
|
1619
|
-
# Requires \`jq\` for robust JSON parsing; falls back to silent no-op if absent
|
|
1620
|
-
|
|
1621
|
-
set +e
|
|
1622
|
-
|
|
1623
|
-
INPUT=$(cat 2>/dev/null)
|
|
1624
|
-
if [ -z "$INPUT" ]; then exit 0; fi
|
|
1625
|
-
|
|
1626
|
-
# jq is required for the parsing below \u2014 bail early (silent no-op) if it's absent
|
|
1627
|
-
if ! command -v jq >/dev/null 2>&1; then exit 0; fi
|
|
1628
|
-
|
|
1629
|
-
# Extract transcript_path with jq, not sed. A greedy sed capture (\\(.*\\)") grabs the
|
|
1630
|
-
# trailing JSON fields after transcript_path and yields a path that doesn't exist, so
|
|
1631
|
-
# the -f check below always failed and totals were never POSTed to /log. (issue #1)
|
|
1632
|
-
TRANSCRIPT=$(printf '%s' "$INPUT" | jq -r '.transcript_path // empty' 2>/dev/null)
|
|
1633
|
-
if [ -z "$TRANSCRIPT" ] || [ ! -f "$TRANSCRIPT" ]; then exit 0; fi
|
|
1634
|
-
|
|
1635
|
-
PORT_FILE="$PWD/.synthra-graph/mcp_port"
|
|
1636
|
-
if [ ! -f "$PORT_FILE" ]; then exit 0; fi
|
|
1637
|
-
PORT=$(cat "$PORT_FILE" 2>/dev/null | tr -d '[:space:]')
|
|
1638
|
-
if [ -z "$PORT" ]; then exit 0; fi
|
|
1639
|
-
|
|
1640
|
-
OFFSET_FILE="\${TRANSCRIPT}.stopoffset"
|
|
1641
|
-
START_OFFSET=0
|
|
1642
|
-
if [ -f "$OFFSET_FILE" ]; then
|
|
1643
|
-
START_OFFSET=$(cat "$OFFSET_FILE" 2>/dev/null | tr -d '[:space:]')
|
|
1644
|
-
case "$START_OFFSET" in ''|*[!0-9]*) START_OFFSET=0 ;; esac
|
|
1645
|
-
fi
|
|
1646
|
-
|
|
1647
|
-
TOTAL_LINES=$(wc -l < "$TRANSCRIPT" 2>/dev/null | tr -d ' ')
|
|
1648
|
-
TOTAL_LINES=\${TOTAL_LINES:-0}
|
|
1649
|
-
|
|
1650
|
-
if [ "$TOTAL_LINES" -le "$START_OFFSET" ]; then exit 0; fi
|
|
1651
|
-
|
|
1652
|
-
USAGE=$(tail -n +$((START_OFFSET + 1)) "$TRANSCRIPT" 2>/dev/null
|
|
1653
|
-
| jq -s '
|
|
1654
|
-
map(select(.message.usage != null) | .message)
|
|
1655
|
-
| reduce .[] as $m (
|
|
1656
|
-
{in:0, out:0, cc:0, cr:0, model:""}
|
|
1657
|
-
.in += ($m.usage.input_tokens // 0)
|
|
1658
|
-
| .out += ($m.usage.output_tokens // 0)
|
|
1659
|
-
| .cc += ($m.usage.cache_creation_input_tokens // 0)
|
|
1660
|
-
| .cr += ($m.usage.cache_read_input_tokens // 0)
|
|
1661
|
-
| .model = ($m.model // .model)
|
|
1662
|
-
)
|
|
1663
|
-
' 2>/dev/null)
|
|
1664
|
-
|
|
1665
|
-
printf '%s' "$TOTAL_LINES" > "$OFFSET_FILE"
|
|
1666
|
-
|
|
1667
|
-
IN=$(printf '%s' "$USAGE" | jq -r '.in // 0')
|
|
1668
|
-
OUT=$(printf '%s' "$USAGE" | jq -r '.out // 0')
|
|
1669
|
-
CC=$(printf '%s' "$USAGE" | jq -r '.cc // 0')
|
|
1670
|
-
CR=$(printf '%s' "$USAGE" | jq -r '.cr // 0')
|
|
1671
|
-
MODEL=$(printf '%s' "$USAGE" | jq -r '.model // ""')
|
|
1672
|
-
|
|
1673
|
-
if [ "$IN" = "0" ] && [ "$OUT" = "0" ]; then exit 0; fi
|
|
1674
|
-
|
|
1675
|
-
curl -sS --max-time 3 -X POST -H "Content-Type: application/json"
|
|
1676
|
-
--data "$(jq -nc --argjson i "$IN" --argjson o "$OUT" --argjson cc "$CC" --argjson cr "$CR" --arg m "$MODEL" --arg p "$PWD"
|
|
1677
|
-
'{input_tokens:$i, output_tokens:$o, cache_creation_input_tokens:$cc, cache_read_input_tokens:$cr, model:$m, description:"synthra-stop-hook", project:$p}')"
|
|
1678
|
-
"http://127.0.0.1:$PORT/log" >/dev/null 2>&1
|
|
1679
|
-
|
|
1680
|
-
# Refresh CONTEXT.md from the branch-scoped store
|
|
1681
|
-
curl -sS --max-time 3 -X POST -H "Content-Type: application/json"
|
|
1682
|
-
--data "$(jq -nc --arg t "$TRANSCRIPT" '{transcript_path:$t}')"
|
|
1683
|
-
"http://127.0.0.1:$PORT/context-update" >/dev/null 2>&1
|
|
1684
|
-
|
|
1685
|
-
exit 0
|
|
1624
|
+
var stop_default2 = `#!/usr/bin/env bash
|
|
1625
|
+
# Stop hook \u2014 bash. Reads transcript JSONL, sums usage.* across new lines,
|
|
1626
|
+
# POSTs totals to /log. Uses a .stopoffset file to avoid double-counting.
|
|
1627
|
+
# Requires \`jq\` for robust JSON parsing; falls back to silent no-op if absent.
|
|
1628
|
+
|
|
1629
|
+
set +e
|
|
1630
|
+
|
|
1631
|
+
INPUT=$(cat 2>/dev/null)
|
|
1632
|
+
if [ -z "$INPUT" ]; then exit 0; fi
|
|
1633
|
+
|
|
1634
|
+
# jq is required for the parsing below \u2014 bail early (silent no-op) if it's absent.
|
|
1635
|
+
if ! command -v jq >/dev/null 2>&1; then exit 0; fi
|
|
1636
|
+
|
|
1637
|
+
# Extract transcript_path with jq, not sed. A greedy sed capture (\\(.*\\)") grabs the
|
|
1638
|
+
# trailing JSON fields after transcript_path and yields a path that doesn't exist, so
|
|
1639
|
+
# the -f check below always failed and totals were never POSTed to /log. (issue #1)
|
|
1640
|
+
TRANSCRIPT=$(printf '%s' "$INPUT" | jq -r '.transcript_path // empty' 2>/dev/null)
|
|
1641
|
+
if [ -z "$TRANSCRIPT" ] || [ ! -f "$TRANSCRIPT" ]; then exit 0; fi
|
|
1642
|
+
|
|
1643
|
+
PORT_FILE="$PWD/.synthra-graph/mcp_port"
|
|
1644
|
+
if [ ! -f "$PORT_FILE" ]; then exit 0; fi
|
|
1645
|
+
PORT=$(cat "$PORT_FILE" 2>/dev/null | tr -d '[:space:]')
|
|
1646
|
+
if [ -z "$PORT" ]; then exit 0; fi
|
|
1647
|
+
|
|
1648
|
+
OFFSET_FILE="\${TRANSCRIPT}.stopoffset"
|
|
1649
|
+
START_OFFSET=0
|
|
1650
|
+
if [ -f "$OFFSET_FILE" ]; then
|
|
1651
|
+
START_OFFSET=$(cat "$OFFSET_FILE" 2>/dev/null | tr -d '[:space:]')
|
|
1652
|
+
case "$START_OFFSET" in ''|*[!0-9]*) START_OFFSET=0 ;; esac
|
|
1653
|
+
fi
|
|
1654
|
+
|
|
1655
|
+
TOTAL_LINES=$(wc -l < "$TRANSCRIPT" 2>/dev/null | tr -d ' ')
|
|
1656
|
+
TOTAL_LINES=\${TOTAL_LINES:-0}
|
|
1657
|
+
|
|
1658
|
+
if [ "$TOTAL_LINES" -le "$START_OFFSET" ]; then exit 0; fi
|
|
1659
|
+
|
|
1660
|
+
USAGE=$(tail -n +$((START_OFFSET + 1)) "$TRANSCRIPT" 2>/dev/null \\
|
|
1661
|
+
| jq -s '
|
|
1662
|
+
map(select(.message.usage != null) | .message)
|
|
1663
|
+
| reduce .[] as $m (
|
|
1664
|
+
{in:0, out:0, cc:0, cr:0, model:""};
|
|
1665
|
+
.in += ($m.usage.input_tokens // 0)
|
|
1666
|
+
| .out += ($m.usage.output_tokens // 0)
|
|
1667
|
+
| .cc += ($m.usage.cache_creation_input_tokens // 0)
|
|
1668
|
+
| .cr += ($m.usage.cache_read_input_tokens // 0)
|
|
1669
|
+
| .model = ($m.model // .model)
|
|
1670
|
+
)
|
|
1671
|
+
' 2>/dev/null)
|
|
1672
|
+
|
|
1673
|
+
printf '%s' "$TOTAL_LINES" > "$OFFSET_FILE"
|
|
1674
|
+
|
|
1675
|
+
IN=$(printf '%s' "$USAGE" | jq -r '.in // 0')
|
|
1676
|
+
OUT=$(printf '%s' "$USAGE" | jq -r '.out // 0')
|
|
1677
|
+
CC=$(printf '%s' "$USAGE" | jq -r '.cc // 0')
|
|
1678
|
+
CR=$(printf '%s' "$USAGE" | jq -r '.cr // 0')
|
|
1679
|
+
MODEL=$(printf '%s' "$USAGE" | jq -r '.model // ""')
|
|
1680
|
+
|
|
1681
|
+
if [ "$IN" = "0" ] && [ "$OUT" = "0" ]; then exit 0; fi
|
|
1682
|
+
|
|
1683
|
+
curl -sS --max-time 3 -X POST -H "Content-Type: application/json" \\
|
|
1684
|
+
--data "$(jq -nc --argjson i "$IN" --argjson o "$OUT" --argjson cc "$CC" --argjson cr "$CR" --arg m "$MODEL" --arg p "$PWD" \\
|
|
1685
|
+
'{input_tokens:$i, output_tokens:$o, cache_creation_input_tokens:$cc, cache_read_input_tokens:$cr, model:$m, description:"synthra-stop-hook", project:$p}')" \\
|
|
1686
|
+
"http://127.0.0.1:$PORT/log" >/dev/null 2>&1
|
|
1687
|
+
|
|
1688
|
+
# Refresh CONTEXT.md from the branch-scoped store.
|
|
1689
|
+
curl -sS --max-time 3 -X POST -H "Content-Type: application/json" \\
|
|
1690
|
+
--data "$(jq -nc --arg t "$TRANSCRIPT" '{transcript_path:$t}')" \\
|
|
1691
|
+
"http://127.0.0.1:$PORT/context-update" >/dev/null 2>&1
|
|
1692
|
+
|
|
1693
|
+
exit 0
|
|
1686
1694
|
`;
|
|
1687
1695
|
|
|
1688
1696
|
// src/hooks/installer.ts
|