panopticon-cli 0.4.31 → 0.4.33
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/dist/{agents-GQDAKTEQ.js → agents-VLK4BMVA.js} +10 -7
- package/dist/chunk-7SN4L4PH.js +150 -0
- package/dist/chunk-7SN4L4PH.js.map +1 -0
- package/dist/chunk-7XNJJBH6.js +538 -0
- package/dist/chunk-7XNJJBH6.js.map +1 -0
- package/dist/chunk-AQXETQHW.js +113 -0
- package/dist/chunk-AQXETQHW.js.map +1 -0
- package/dist/{chunk-TMXN7THF.js → chunk-ASY7T35E.js} +170 -64
- package/dist/chunk-ASY7T35E.js.map +1 -0
- package/dist/chunk-B3PF6JPQ.js +212 -0
- package/dist/chunk-B3PF6JPQ.js.map +1 -0
- package/dist/{chunk-HNEWTIR3.js → chunk-BKCWRMUX.js} +100 -40
- package/dist/chunk-BKCWRMUX.js.map +1 -0
- package/dist/chunk-CFCUOV3Q.js +669 -0
- package/dist/chunk-CFCUOV3Q.js.map +1 -0
- package/dist/chunk-CWELWPWQ.js +32 -0
- package/dist/chunk-CWELWPWQ.js.map +1 -0
- package/dist/chunk-DI7ABPNQ.js +352 -0
- package/dist/chunk-DI7ABPNQ.js.map +1 -0
- package/dist/{chunk-VU4FLXV5.js → chunk-FQ66DECN.js} +31 -4
- package/dist/chunk-FQ66DECN.js.map +1 -0
- package/dist/{review-status-GWQYY77L.js → chunk-GFP3PIPB.js} +14 -7
- package/dist/chunk-GFP3PIPB.js.map +1 -0
- package/dist/chunk-JQBV3Q2W.js +29 -0
- package/dist/chunk-JQBV3Q2W.js.map +1 -0
- package/dist/{chunk-BWGFN44T.js → chunk-JT4O4YVM.js} +28 -16
- package/dist/chunk-JT4O4YVM.js.map +1 -0
- package/dist/{chunk-VIWUCJ4V.js → chunk-KJ2TRXNK.js} +34 -36
- package/dist/chunk-KJ2TRXNK.js.map +1 -0
- package/dist/{chunk-JY7R7V4G.js → chunk-OMNXYPXC.js} +2 -2
- package/dist/chunk-OMNXYPXC.js.map +1 -0
- package/dist/chunk-PELXV435.js +215 -0
- package/dist/chunk-PELXV435.js.map +1 -0
- package/dist/chunk-PI7Y3PSN.js +797 -0
- package/dist/chunk-PI7Y3PSN.js.map +1 -0
- package/dist/chunk-RBUO57TC.js +154 -0
- package/dist/chunk-RBUO57TC.js.map +1 -0
- package/dist/chunk-XFR2DLMR.js +600 -0
- package/dist/chunk-XFR2DLMR.js.map +1 -0
- package/dist/chunk-XKT5MHPT.js +677 -0
- package/dist/chunk-XKT5MHPT.js.map +1 -0
- package/dist/{chunk-HCTJFIJJ.js → chunk-YLPSQAM2.js} +2 -2
- package/dist/{chunk-HCTJFIJJ.js.map → chunk-YLPSQAM2.js.map} +1 -1
- package/dist/{chunk-6HXKTOD7.js → chunk-ZTFNYOC7.js} +53 -38
- package/dist/chunk-ZTFNYOC7.js.map +1 -0
- package/dist/cli/index.js +4362 -2922
- package/dist/cli/index.js.map +1 -1
- package/dist/{config-BOAMSKTF.js → config-4CJNUE3O.js} +7 -3
- package/dist/dashboard/prompts/merge-agent.md +217 -0
- package/dist/dashboard/prompts/review-agent.md +409 -0
- package/dist/dashboard/prompts/sync-main.md +84 -0
- package/dist/dashboard/prompts/test-agent.md +283 -0
- package/dist/dashboard/prompts/work-agent.md +247 -0
- package/dist/dashboard/public/assets/index-UjZq6ykz.css +32 -0
- package/dist/dashboard/public/assets/index-kAJqtLDO.js +708 -0
- package/dist/dashboard/public/index.html +2 -2
- package/dist/dashboard/server.js +15272 -3169
- package/dist/{dns-L3L2BB27.js → dns-7BDJSD3E.js} +4 -2
- package/dist/{feedback-writer-AAKF5BTK.js → feedback-writer-LVZ5TFYZ.js} +8 -4
- package/dist/feedback-writer-LVZ5TFYZ.js.map +1 -0
- package/dist/hume-WMAUBBV2.js +13 -0
- package/dist/index.d.ts +153 -40
- package/dist/index.js +65 -23
- package/dist/index.js.map +1 -1
- package/dist/{projects-VXRUCMLM.js → projects-JEIVIYC6.js} +3 -3
- package/dist/rally-RKFSWC7E.js +10 -0
- package/dist/{remote-agents-Z3R2A5BN.js → remote-agents-TFSMW7GN.js} +2 -2
- package/dist/{remote-workspace-2G6V2KNP.js → remote-workspace-AHVHQEES.js} +8 -8
- package/dist/review-status-EPFG4XM7.js +19 -0
- package/dist/shadow-state-5MDP6YXH.js +30 -0
- package/dist/shadow-state-5MDP6YXH.js.map +1 -0
- package/dist/{specialist-context-6SE5VRRC.js → specialist-context-T3NBMCIE.js} +8 -7
- package/dist/{specialist-context-6SE5VRRC.js.map → specialist-context-T3NBMCIE.js.map} +1 -1
- package/dist/{specialist-logs-EXLOQHQ2.js → specialist-logs-CVKD3YJ3.js} +7 -6
- package/dist/specialist-logs-CVKD3YJ3.js.map +1 -0
- package/dist/{specialists-BRUHPAXE.js → specialists-TKAP6T6Z.js} +7 -6
- package/dist/specialists-TKAP6T6Z.js.map +1 -0
- package/dist/tldr-daemon-T3THOUGT.js +21 -0
- package/dist/tldr-daemon-T3THOUGT.js.map +1 -0
- package/dist/traefik-QX4ZV4YG.js +19 -0
- package/dist/traefik-QX4ZV4YG.js.map +1 -0
- package/dist/tunnel-W2GZBLEV.js +13 -0
- package/dist/tunnel-W2GZBLEV.js.map +1 -0
- package/dist/workspace-manager-KLHUCIZV.js +22 -0
- package/dist/workspace-manager-KLHUCIZV.js.map +1 -0
- package/package.json +2 -2
- package/scripts/heartbeat-hook +37 -10
- package/scripts/patches/llm-tldr-tsx-support.py +109 -0
- package/scripts/pre-tool-hook +26 -15
- package/scripts/record-cost-event.js +177 -43
- package/scripts/record-cost-event.ts +87 -3
- package/scripts/statusline.sh +169 -0
- package/scripts/stop-hook +14 -11
- package/scripts/tldr-post-edit +72 -0
- package/scripts/tldr-read-enforcer +275 -0
- package/skills/check-merged/SKILL.md +143 -0
- package/skills/crash-investigation/SKILL.md +301 -0
- package/skills/github-cli/SKILL.md +185 -0
- package/skills/pan-reopen/SKILL.md +65 -0
- package/skills/pan-sync-main/SKILL.md +87 -0
- package/skills/pan-tldr/SKILL.md +149 -0
- package/skills/react-best-practices/SKILL.md +125 -0
- package/skills/spec-readiness/REPORT-TEMPLATE.md +158 -0
- package/skills/spec-readiness/SCORING-REFERENCE.md +369 -0
- package/skills/spec-readiness/SKILL.md +400 -0
- package/skills/spec-readiness-setup/SKILL.md +361 -0
- package/skills/workspace-status/SKILL.md +56 -0
- package/templates/traefik/dynamic/panopticon.yml.template +0 -5
- package/templates/traefik/traefik.yml +0 -8
- package/dist/chunk-3XAB4IXF.js +0 -51
- package/dist/chunk-3XAB4IXF.js.map +0 -1
- package/dist/chunk-6HXKTOD7.js.map +0 -1
- package/dist/chunk-BBCUK6N2.js +0 -241
- package/dist/chunk-BBCUK6N2.js.map +0 -1
- package/dist/chunk-BWGFN44T.js.map +0 -1
- package/dist/chunk-ELK6Q7QI.js +0 -545
- package/dist/chunk-ELK6Q7QI.js.map +0 -1
- package/dist/chunk-HNEWTIR3.js.map +0 -1
- package/dist/chunk-JY7R7V4G.js.map +0 -1
- package/dist/chunk-LYSBSZYV.js +0 -1523
- package/dist/chunk-LYSBSZYV.js.map +0 -1
- package/dist/chunk-TMXN7THF.js.map +0 -1
- package/dist/chunk-VIWUCJ4V.js.map +0 -1
- package/dist/chunk-VU4FLXV5.js.map +0 -1
- package/dist/dashboard/public/assets/index-C7X6LP5Z.css +0 -32
- package/dist/dashboard/public/assets/index-izWbAt7V.js +0 -645
- package/dist/feedback-writer-AAKF5BTK.js.map +0 -1
- package/dist/review-status-GWQYY77L.js.map +0 -1
- package/dist/traefik-CUJM6K5Z.js +0 -12
- /package/dist/{agents-GQDAKTEQ.js.map → agents-VLK4BMVA.js.map} +0 -0
- /package/dist/{config-BOAMSKTF.js.map → config-4CJNUE3O.js.map} +0 -0
- /package/dist/{dns-L3L2BB27.js.map → dns-7BDJSD3E.js.map} +0 -0
- /package/dist/{projects-VXRUCMLM.js.map → hume-WMAUBBV2.js.map} +0 -0
- /package/dist/{remote-agents-Z3R2A5BN.js.map → projects-JEIVIYC6.js.map} +0 -0
- /package/dist/{specialist-logs-EXLOQHQ2.js.map → rally-RKFSWC7E.js.map} +0 -0
- /package/dist/{specialists-BRUHPAXE.js.map → remote-agents-TFSMW7GN.js.map} +0 -0
- /package/dist/{remote-workspace-2G6V2KNP.js.map → remote-workspace-AHVHQEES.js.map} +0 -0
- /package/dist/{traefik-CUJM6K5Z.js.map → review-status-EPFG4XM7.js.map} +0 -0
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// scripts/record-cost-event.ts
|
|
4
|
-
import { readFileSync as
|
|
5
|
-
import {
|
|
4
|
+
import { readFileSync as readFileSync3, existsSync as existsSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, openSync, readSync, fstatSync, closeSync } from "fs";
|
|
5
|
+
import { execFileSync } from "child_process";
|
|
6
|
+
import { join as join5 } from "path";
|
|
6
7
|
import { homedir as homedir3 } from "os";
|
|
7
8
|
|
|
8
9
|
// src/lib/cost.ts
|
|
@@ -22,6 +23,7 @@ var BIN_DIR = join(PANOPTICON_HOME, "bin");
|
|
|
22
23
|
var BACKUPS_DIR = join(PANOPTICON_HOME, "backups");
|
|
23
24
|
var COSTS_DIR = join(PANOPTICON_HOME, "costs");
|
|
24
25
|
var HEARTBEATS_DIR = join(PANOPTICON_HOME, "heartbeats");
|
|
26
|
+
var ARCHIVES_DIR = join(PANOPTICON_HOME, "archives");
|
|
25
27
|
var TRAEFIK_DIR = join(PANOPTICON_HOME, "traefik");
|
|
26
28
|
var TRAEFIK_DYNAMIC_DIR = join(TRAEFIK_DIR, "dynamic");
|
|
27
29
|
var TRAEFIK_CERTS_DIR = join(TRAEFIK_DIR, "certs");
|
|
@@ -29,36 +31,16 @@ var CERTS_DIR = join(PANOPTICON_HOME, "certs");
|
|
|
29
31
|
var CONFIG_FILE = join(CONFIG_DIR, "config.toml");
|
|
30
32
|
var SETTINGS_FILE = join(CONFIG_DIR, "settings.json");
|
|
31
33
|
var CLAUDE_DIR = join(homedir(), ".claude");
|
|
32
|
-
var
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
codex: {
|
|
43
|
-
skills: join(CODEX_DIR, "skills"),
|
|
44
|
-
commands: join(CODEX_DIR, "commands"),
|
|
45
|
-
agents: join(CODEX_DIR, "agents")
|
|
46
|
-
},
|
|
47
|
-
cursor: {
|
|
48
|
-
skills: join(CURSOR_DIR, "skills"),
|
|
49
|
-
commands: join(CURSOR_DIR, "commands"),
|
|
50
|
-
agents: join(CURSOR_DIR, "agents")
|
|
51
|
-
},
|
|
52
|
-
gemini: {
|
|
53
|
-
skills: join(GEMINI_DIR, "skills"),
|
|
54
|
-
commands: join(GEMINI_DIR, "commands"),
|
|
55
|
-
agents: join(GEMINI_DIR, "agents")
|
|
56
|
-
},
|
|
57
|
-
opencode: {
|
|
58
|
-
skills: join(OPENCODE_DIR, "skills"),
|
|
59
|
-
commands: join(OPENCODE_DIR, "commands"),
|
|
60
|
-
agents: join(OPENCODE_DIR, "agents")
|
|
61
|
-
}
|
|
34
|
+
var LEGACY_RUNTIME_DIRS = {
|
|
35
|
+
codex: join(homedir(), ".codex"),
|
|
36
|
+
cursor: join(homedir(), ".cursor"),
|
|
37
|
+
gemini: join(homedir(), ".gemini"),
|
|
38
|
+
opencode: join(homedir(), ".opencode")
|
|
39
|
+
};
|
|
40
|
+
var SYNC_TARGET = {
|
|
41
|
+
skills: join(CLAUDE_DIR, "skills"),
|
|
42
|
+
commands: join(CLAUDE_DIR, "commands"),
|
|
43
|
+
agents: join(CLAUDE_DIR, "agents")
|
|
62
44
|
};
|
|
63
45
|
var TEMPLATES_DIR = join(PANOPTICON_HOME, "templates");
|
|
64
46
|
var CLAUDE_MD_TEMPLATES = join(TEMPLATES_DIR, "claude-md", "sections");
|
|
@@ -75,6 +57,15 @@ var SOURCE_TRAEFIK_TEMPLATES = join(SOURCE_TEMPLATES_DIR, "traefik");
|
|
|
75
57
|
var SOURCE_SCRIPTS_DIR = join(packageRoot, "scripts");
|
|
76
58
|
var SOURCE_SKILLS_DIR = join(packageRoot, "skills");
|
|
77
59
|
var SOURCE_DEV_SKILLS_DIR = join(packageRoot, "dev-skills");
|
|
60
|
+
var SOURCE_AGENTS_DIR = join(packageRoot, "agents");
|
|
61
|
+
var SOURCE_RULES_DIR = join(packageRoot, "rules");
|
|
62
|
+
var CACHE_AGENTS_DIR = join(PANOPTICON_HOME, "agent-definitions");
|
|
63
|
+
var CACHE_RULES_DIR = join(PANOPTICON_HOME, "rules");
|
|
64
|
+
var CACHE_MANIFEST = join(PANOPTICON_HOME, ".manifest.json");
|
|
65
|
+
var DOCS_DIR = join(PANOPTICON_HOME, "docs");
|
|
66
|
+
var PRDS_DIR = join(DOCS_DIR, "prds");
|
|
67
|
+
var PRD_DRAFTS_DIR = join(PRDS_DIR, "drafts");
|
|
68
|
+
var PRD_PUBLISHED_DIR = join(PRDS_DIR, "published");
|
|
78
69
|
|
|
79
70
|
// src/lib/cost.ts
|
|
80
71
|
var DEFAULT_PRICING = [
|
|
@@ -162,26 +153,118 @@ function appendCostEvent(event2) {
|
|
|
162
153
|
appendFileSync(getEventsFile(), line, "utf-8");
|
|
163
154
|
}
|
|
164
155
|
|
|
156
|
+
// src/lib/tldr-daemon.ts
|
|
157
|
+
import { exec } from "child_process";
|
|
158
|
+
import { promisify } from "util";
|
|
159
|
+
import { existsSync as existsSync2, writeFileSync as writeFileSync2, readFileSync as readFileSync2, mkdirSync as mkdirSync2, unlinkSync } from "fs";
|
|
160
|
+
import { join as join4 } from "path";
|
|
161
|
+
function getTldrMetrics(workspacePath, sinceCheckpoint = false) {
|
|
162
|
+
const tldrDir = join4(workspacePath, ".tldr");
|
|
163
|
+
const interceptionsLog = join4(tldrDir, "interceptions.log");
|
|
164
|
+
const bypassesLog = join4(tldrDir, "bypasses.log");
|
|
165
|
+
const checkpointFile = join4(tldrDir, "metrics-checkpoint.json");
|
|
166
|
+
let interceptionsStartLine = 0;
|
|
167
|
+
let bypassesStartLine = 0;
|
|
168
|
+
if (sinceCheckpoint && existsSync2(checkpointFile)) {
|
|
169
|
+
try {
|
|
170
|
+
const checkpoint = JSON.parse(readFileSync2(checkpointFile, "utf-8"));
|
|
171
|
+
interceptionsStartLine = checkpoint.interceptionsLine || 0;
|
|
172
|
+
bypassesStartLine = checkpoint.bypassesLine || 0;
|
|
173
|
+
} catch {
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
const allInterceptionLines = existsSync2(interceptionsLog) ? readFileSync2(interceptionsLog, "utf-8").split("\n").filter((l) => l.trim()) : [];
|
|
177
|
+
const newInterceptions = allInterceptionLines.slice(interceptionsStartLine);
|
|
178
|
+
let estimatedTokensSaved = 0;
|
|
179
|
+
const filesAnalyzed = [];
|
|
180
|
+
for (const line of newInterceptions) {
|
|
181
|
+
const parts = line.trim().split(" ");
|
|
182
|
+
if (parts.length >= 3) {
|
|
183
|
+
const fileSizeBytes = parseInt(parts[1], 10) || 0;
|
|
184
|
+
const relPath = parts.slice(2).join(" ");
|
|
185
|
+
const fullTokens = Math.round(fileSizeBytes / 4);
|
|
186
|
+
estimatedTokensSaved += Math.max(0, fullTokens - 1e3);
|
|
187
|
+
if (relPath && !filesAnalyzed.includes(relPath)) {
|
|
188
|
+
filesAnalyzed.push(relPath);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
const allBypassLines = existsSync2(bypassesLog) ? readFileSync2(bypassesLog, "utf-8").split("\n").filter((l) => l.trim()) : [];
|
|
193
|
+
const newBypasses = allBypassLines.slice(bypassesStartLine);
|
|
194
|
+
const bypassReasons = {};
|
|
195
|
+
for (const line of newBypasses) {
|
|
196
|
+
const parts = line.trim().split(" ");
|
|
197
|
+
if (parts.length >= 2) {
|
|
198
|
+
const reason = parts[1];
|
|
199
|
+
bypassReasons[reason] = (bypassReasons[reason] || 0) + 1;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return {
|
|
203
|
+
interceptions: newInterceptions.length,
|
|
204
|
+
bypasses: newBypasses.length,
|
|
205
|
+
estimatedTokensSaved,
|
|
206
|
+
filesAnalyzed,
|
|
207
|
+
bypassReasons
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
function captureTldrMetrics(workspacePath) {
|
|
211
|
+
const tldrDir = join4(workspacePath, ".tldr");
|
|
212
|
+
if (!existsSync2(tldrDir)) {
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
const metrics = getTldrMetrics(workspacePath, true);
|
|
216
|
+
const interceptionsLog = join4(tldrDir, "interceptions.log");
|
|
217
|
+
const bypassesLog = join4(tldrDir, "bypasses.log");
|
|
218
|
+
const checkpointFile = join4(tldrDir, "metrics-checkpoint.json");
|
|
219
|
+
const interceptionsTotal = existsSync2(interceptionsLog) ? readFileSync2(interceptionsLog, "utf-8").split("\n").filter((l) => l.trim()).length : 0;
|
|
220
|
+
const bypassesTotal = existsSync2(bypassesLog) ? readFileSync2(bypassesLog, "utf-8").split("\n").filter((l) => l.trim()).length : 0;
|
|
221
|
+
const checkpoint = {
|
|
222
|
+
interceptionsLine: interceptionsTotal,
|
|
223
|
+
bypassesLine: bypassesTotal,
|
|
224
|
+
capturedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
225
|
+
};
|
|
226
|
+
try {
|
|
227
|
+
writeFileSync2(checkpointFile, JSON.stringify(checkpoint, null, 2), "utf-8");
|
|
228
|
+
} catch {
|
|
229
|
+
}
|
|
230
|
+
return metrics;
|
|
231
|
+
}
|
|
232
|
+
var execAsync = promisify(exec);
|
|
233
|
+
var TLDR_STATE_DIR = join4(PANOPTICON_HOME, "tldr");
|
|
234
|
+
|
|
165
235
|
// scripts/record-cost-event.ts
|
|
166
236
|
var event;
|
|
167
237
|
try {
|
|
168
|
-
const input =
|
|
238
|
+
const input = readFileSync3(0, "utf-8");
|
|
169
239
|
event = JSON.parse(input);
|
|
170
240
|
} catch {
|
|
171
241
|
process.exit(0);
|
|
172
242
|
}
|
|
173
243
|
var transcriptPath = event?.transcript_path;
|
|
174
|
-
if (!transcriptPath || !
|
|
244
|
+
if (!transcriptPath || !existsSync3(transcriptPath)) {
|
|
175
245
|
process.exit(0);
|
|
176
246
|
}
|
|
177
247
|
var sessionId = event?.session_id || "unknown";
|
|
178
|
-
var stateDir =
|
|
179
|
-
|
|
180
|
-
var stateFile =
|
|
248
|
+
var stateDir = join5(process.env.HOME || homedir3(), ".panopticon", "costs", "state");
|
|
249
|
+
mkdirSync3(stateDir, { recursive: true });
|
|
250
|
+
var stateFile = join5(stateDir, `${sessionId}.offset`);
|
|
181
251
|
var lastOffset = 0;
|
|
182
|
-
if (
|
|
252
|
+
if (existsSync3(stateFile)) {
|
|
183
253
|
try {
|
|
184
|
-
lastOffset = parseInt(
|
|
254
|
+
lastOffset = parseInt(readFileSync3(stateFile, "utf-8").trim(), 10) || 0;
|
|
255
|
+
} catch {
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
var seenFile = join5(stateDir, `${sessionId}.seen`);
|
|
259
|
+
var seenRequestIds = /* @__PURE__ */ new Set();
|
|
260
|
+
if (existsSync3(seenFile)) {
|
|
261
|
+
try {
|
|
262
|
+
const seenContent = readFileSync3(seenFile, "utf-8").trim();
|
|
263
|
+
if (seenContent) {
|
|
264
|
+
for (const id of seenContent.split("\n")) {
|
|
265
|
+
if (id.trim()) seenRequestIds.add(id.trim());
|
|
266
|
+
}
|
|
267
|
+
}
|
|
185
268
|
} catch {
|
|
186
269
|
}
|
|
187
270
|
}
|
|
@@ -194,7 +277,7 @@ try {
|
|
|
194
277
|
var stat = fstatSync(fd);
|
|
195
278
|
if (stat.size <= lastOffset) {
|
|
196
279
|
closeSync(fd);
|
|
197
|
-
|
|
280
|
+
writeFileSync3(stateFile, String(stat.size), "utf-8");
|
|
198
281
|
process.exit(0);
|
|
199
282
|
}
|
|
200
283
|
var bytesToRead = stat.size - lastOffset;
|
|
@@ -204,8 +287,38 @@ closeSync(fd);
|
|
|
204
287
|
var newContent = buffer.toString("utf-8");
|
|
205
288
|
var lines = newContent.split("\n");
|
|
206
289
|
var agentId = process.env.PANOPTICON_AGENT_ID || "unattributed";
|
|
207
|
-
var issueId = process.env.PANOPTICON_ISSUE_ID || "
|
|
290
|
+
var issueId = process.env.PANOPTICON_ISSUE_ID || "";
|
|
208
291
|
var sessionType = process.env.PANOPTICON_SESSION_TYPE || "implementation";
|
|
292
|
+
if (!issueId || issueId === "UNKNOWN") {
|
|
293
|
+
try {
|
|
294
|
+
const branch = execFileSync("git", ["branch", "--show-current"], {
|
|
295
|
+
encoding: "utf-8",
|
|
296
|
+
timeout: 2e3,
|
|
297
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
298
|
+
}).trim();
|
|
299
|
+
const branchMatch = branch.match(/(pan|min|aud)[-](\d+)/i);
|
|
300
|
+
if (branchMatch) {
|
|
301
|
+
issueId = `${branchMatch[1].toUpperCase()}-${branchMatch[2]}`;
|
|
302
|
+
}
|
|
303
|
+
} catch {
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
if (!issueId) {
|
|
307
|
+
issueId = "UNKNOWN";
|
|
308
|
+
}
|
|
309
|
+
var tldrMetrics = null;
|
|
310
|
+
try {
|
|
311
|
+
const workspaceRoot = execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
|
312
|
+
encoding: "utf-8",
|
|
313
|
+
timeout: 2e3,
|
|
314
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
315
|
+
}).trim();
|
|
316
|
+
if (workspaceRoot) {
|
|
317
|
+
tldrMetrics = captureTldrMetrics(workspaceRoot);
|
|
318
|
+
}
|
|
319
|
+
} catch {
|
|
320
|
+
}
|
|
321
|
+
var tldrAttachedToFirstEvent = false;
|
|
209
322
|
for (const line of lines) {
|
|
210
323
|
if (!line.trim()) continue;
|
|
211
324
|
try {
|
|
@@ -213,6 +326,13 @@ for (const line of lines) {
|
|
|
213
326
|
if (entry.type !== "assistant" || !entry.message?.usage) {
|
|
214
327
|
continue;
|
|
215
328
|
}
|
|
329
|
+
const requestId = entry.requestId;
|
|
330
|
+
if (requestId) {
|
|
331
|
+
if (seenRequestIds.has(requestId)) {
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
seenRequestIds.add(requestId);
|
|
335
|
+
}
|
|
216
336
|
const usage = entry.message.usage;
|
|
217
337
|
const model = entry.message.model || "claude-sonnet-4";
|
|
218
338
|
const inputTokens = usage.input_tokens || 0;
|
|
@@ -237,6 +357,15 @@ for (const line of lines) {
|
|
|
237
357
|
cacheWriteTokens,
|
|
238
358
|
cacheTTL: "5m"
|
|
239
359
|
}, pricing);
|
|
360
|
+
const tldrFields = tldrMetrics && !tldrAttachedToFirstEvent && tldrMetrics.interceptions + tldrMetrics.bypasses > 0 ? {
|
|
361
|
+
tldrInterceptions: tldrMetrics.interceptions,
|
|
362
|
+
tldrBypasses: tldrMetrics.bypasses,
|
|
363
|
+
tldrTokensSaved: tldrMetrics.estimatedTokensSaved,
|
|
364
|
+
tldrBypassReasons: Object.keys(tldrMetrics.bypassReasons).length > 0 ? tldrMetrics.bypassReasons : void 0
|
|
365
|
+
} : {};
|
|
366
|
+
if (tldrMetrics && !tldrAttachedToFirstEvent) {
|
|
367
|
+
tldrAttachedToFirstEvent = true;
|
|
368
|
+
}
|
|
240
369
|
appendCostEvent({
|
|
241
370
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
242
371
|
type: "cost",
|
|
@@ -249,10 +378,15 @@ for (const line of lines) {
|
|
|
249
378
|
output: outputTokens,
|
|
250
379
|
cacheRead: cacheReadTokens,
|
|
251
380
|
cacheWrite: cacheWriteTokens,
|
|
252
|
-
cost
|
|
381
|
+
cost,
|
|
382
|
+
...requestId ? { requestId } : {},
|
|
383
|
+
...tldrFields
|
|
253
384
|
});
|
|
254
385
|
} catch {
|
|
255
386
|
}
|
|
256
387
|
}
|
|
257
|
-
|
|
388
|
+
writeFileSync3(stateFile, String(stat.size), "utf-8");
|
|
389
|
+
if (seenRequestIds.size > 0) {
|
|
390
|
+
writeFileSync3(seenFile, Array.from(seenRequestIds).join("\n") + "\n", "utf-8");
|
|
391
|
+
}
|
|
258
392
|
process.exit(0);
|
|
@@ -12,10 +12,12 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import { readFileSync, existsSync, writeFileSync, mkdirSync, openSync, readSync, fstatSync, closeSync } from 'fs';
|
|
15
|
+
import { execFileSync } from 'child_process';
|
|
15
16
|
import { join } from 'path';
|
|
16
17
|
import { homedir } from 'os';
|
|
17
18
|
import { calculateCost, getPricing, AIProvider } from '../src/lib/cost.js';
|
|
18
19
|
import { appendCostEvent } from '../src/lib/costs/events.js';
|
|
20
|
+
import { captureTldrMetrics, type TldrSessionMetrics } from '../src/lib/tldr-daemon.js';
|
|
19
21
|
|
|
20
22
|
// ============== Types ==============
|
|
21
23
|
|
|
@@ -73,6 +75,21 @@ if (existsSync(stateFile)) {
|
|
|
73
75
|
} catch { /* start from 0 */ }
|
|
74
76
|
}
|
|
75
77
|
|
|
78
|
+
// Load persisted seen requestIds to guard against crash-before-write duplicates (PAN-238)
|
|
79
|
+
// Claude Code's transcript can have multiple entries per requestId — we emit exactly one event per requestId.
|
|
80
|
+
const seenFile = join(stateDir, `${sessionId}.seen`);
|
|
81
|
+
const seenRequestIds = new Set<string>();
|
|
82
|
+
if (existsSync(seenFile)) {
|
|
83
|
+
try {
|
|
84
|
+
const seenContent = readFileSync(seenFile, 'utf-8').trim();
|
|
85
|
+
if (seenContent) {
|
|
86
|
+
for (const id of seenContent.split('\n')) {
|
|
87
|
+
if (id.trim()) seenRequestIds.add(id.trim());
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
} catch { /* start fresh */ }
|
|
91
|
+
}
|
|
92
|
+
|
|
76
93
|
// Read only NEW content from the transcript (efficient for large files)
|
|
77
94
|
let fd: number;
|
|
78
95
|
try {
|
|
@@ -97,12 +114,49 @@ closeSync(fd);
|
|
|
97
114
|
const newContent = buffer.toString('utf-8');
|
|
98
115
|
const lines = newContent.split('\n');
|
|
99
116
|
|
|
100
|
-
// Get agent/issue context from environment
|
|
117
|
+
// Get agent/issue context from environment, with git branch fallback
|
|
101
118
|
const agentId: string = process.env.PANOPTICON_AGENT_ID || 'unattributed';
|
|
102
|
-
|
|
119
|
+
let issueId: string = process.env.PANOPTICON_ISSUE_ID || '';
|
|
103
120
|
const sessionType: string = process.env.PANOPTICON_SESSION_TYPE || 'implementation';
|
|
104
121
|
|
|
122
|
+
// Infer issue ID from git branch if not set (covers ad-hoc Claude sessions)
|
|
123
|
+
if (!issueId || issueId === 'UNKNOWN') {
|
|
124
|
+
try {
|
|
125
|
+
const branch = execFileSync('git', ['branch', '--show-current'], {
|
|
126
|
+
encoding: 'utf-8',
|
|
127
|
+
timeout: 2000,
|
|
128
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
129
|
+
}).trim();
|
|
130
|
+
const branchMatch = branch.match(/(pan|min|aud)[-](\d+)/i);
|
|
131
|
+
if (branchMatch) {
|
|
132
|
+
issueId = `${branchMatch[1].toUpperCase()}-${branchMatch[2]}`;
|
|
133
|
+
}
|
|
134
|
+
} catch {
|
|
135
|
+
// Git not available or not in a repo — that's fine
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Final fallback
|
|
140
|
+
if (!issueId) {
|
|
141
|
+
issueId = 'UNKNOWN';
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Capture TLDR metrics for this batch (PAN-236)
|
|
145
|
+
// Find workspace root via git (same process already used for branch detection above)
|
|
146
|
+
let tldrMetrics: TldrSessionMetrics | null = null;
|
|
147
|
+
try {
|
|
148
|
+
const workspaceRoot = execFileSync('git', ['rev-parse', '--show-toplevel'], {
|
|
149
|
+
encoding: 'utf-8',
|
|
150
|
+
timeout: 2000,
|
|
151
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
152
|
+
}).trim();
|
|
153
|
+
if (workspaceRoot) {
|
|
154
|
+
tldrMetrics = captureTldrMetrics(workspaceRoot);
|
|
155
|
+
}
|
|
156
|
+
} catch { /* git not available or no workspace — skip TLDR metrics */ }
|
|
157
|
+
|
|
105
158
|
// Process new transcript lines looking for assistant messages with usage
|
|
159
|
+
let tldrAttachedToFirstEvent = false;
|
|
106
160
|
for (const line of lines) {
|
|
107
161
|
if (!line.trim()) continue;
|
|
108
162
|
|
|
@@ -114,6 +168,15 @@ for (const line of lines) {
|
|
|
114
168
|
continue;
|
|
115
169
|
}
|
|
116
170
|
|
|
171
|
+
// Skip already-seen requestIds — transcript has multiple entries per API request (PAN-238)
|
|
172
|
+
const requestId = entry.requestId;
|
|
173
|
+
if (requestId) {
|
|
174
|
+
if (seenRequestIds.has(requestId)) {
|
|
175
|
+
continue; // Duplicate entry for this request — already emitted a cost event
|
|
176
|
+
}
|
|
177
|
+
seenRequestIds.add(requestId);
|
|
178
|
+
}
|
|
179
|
+
|
|
117
180
|
const usage = entry.message.usage;
|
|
118
181
|
const model: string = entry.message.model || 'claude-sonnet-4';
|
|
119
182
|
|
|
@@ -147,6 +210,22 @@ for (const line of lines) {
|
|
|
147
210
|
cacheTTL: '5m',
|
|
148
211
|
}, pricing);
|
|
149
212
|
|
|
213
|
+
// Attach TLDR metrics to the first event in each batch (delta since last batch)
|
|
214
|
+
const tldrFields = tldrMetrics && !tldrAttachedToFirstEvent && tldrMetrics.interceptions + tldrMetrics.bypasses > 0
|
|
215
|
+
? {
|
|
216
|
+
tldrInterceptions: tldrMetrics.interceptions,
|
|
217
|
+
tldrBypasses: tldrMetrics.bypasses,
|
|
218
|
+
tldrTokensSaved: tldrMetrics.estimatedTokensSaved,
|
|
219
|
+
tldrBypassReasons: Object.keys(tldrMetrics.bypassReasons).length > 0
|
|
220
|
+
? tldrMetrics.bypassReasons
|
|
221
|
+
: undefined,
|
|
222
|
+
}
|
|
223
|
+
: {};
|
|
224
|
+
|
|
225
|
+
if (tldrMetrics && !tldrAttachedToFirstEvent) {
|
|
226
|
+
tldrAttachedToFirstEvent = true;
|
|
227
|
+
}
|
|
228
|
+
|
|
150
229
|
// Record the cost event
|
|
151
230
|
appendCostEvent({
|
|
152
231
|
ts: new Date().toISOString(),
|
|
@@ -161,13 +240,18 @@ for (const line of lines) {
|
|
|
161
240
|
cacheRead: cacheReadTokens,
|
|
162
241
|
cacheWrite: cacheWriteTokens,
|
|
163
242
|
cost,
|
|
243
|
+
...(requestId ? { requestId } : {}),
|
|
244
|
+
...tldrFields,
|
|
164
245
|
});
|
|
165
246
|
} catch {
|
|
166
247
|
// Skip malformed lines silently
|
|
167
248
|
}
|
|
168
249
|
}
|
|
169
250
|
|
|
170
|
-
// Save new byte offset for next invocation
|
|
251
|
+
// Save new byte offset and seen requestIds for next invocation
|
|
171
252
|
writeFileSync(stateFile, String(stat.size), 'utf-8');
|
|
253
|
+
if (seenRequestIds.size > 0) {
|
|
254
|
+
writeFileSync(seenFile, Array.from(seenRequestIds).join('\n') + '\n', 'utf-8');
|
|
255
|
+
}
|
|
172
256
|
|
|
173
257
|
process.exit(0);
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Claude Code status line — all available info + plan usage limits
|
|
3
|
+
# JSON is piped via stdin on each update
|
|
4
|
+
|
|
5
|
+
input=$(cat)
|
|
6
|
+
|
|
7
|
+
# Single jq call to extract all fields at once
|
|
8
|
+
eval "$(echo "$input" | jq -r '
|
|
9
|
+
@sh "model=\(.model.display_name // "")",
|
|
10
|
+
@sh "model_id=\(.model.id // "")",
|
|
11
|
+
@sh "current_dir=\(.workspace.current_dir // "")",
|
|
12
|
+
@sh "project_dir=\(.workspace.project_dir // "")",
|
|
13
|
+
@sh "cost=\(.cost.total_cost_usd // 0)",
|
|
14
|
+
@sh "lines_added=\(.cost.total_lines_added // 0)",
|
|
15
|
+
@sh "lines_removed=\(.cost.total_lines_removed // 0)",
|
|
16
|
+
@sh "ctx_used_pct=\(.context_window.used_percentage // 0)",
|
|
17
|
+
@sh "ctx_size=\(.context_window.context_window_size // 0)",
|
|
18
|
+
@sh "ctx_in=\(.context_window.current_usage.input_tokens // 0)",
|
|
19
|
+
@sh "ctx_out=\(.context_window.current_usage.output_tokens // 0)"
|
|
20
|
+
' 2>/dev/null)"
|
|
21
|
+
|
|
22
|
+
# ANSI colors
|
|
23
|
+
RST='\033[0m'; DIM='\033[2m'
|
|
24
|
+
CYN='\033[36m'; GRN='\033[32m'; YLW='\033[33m'
|
|
25
|
+
MAG='\033[35m'; RED='\033[31m'; WHT='\033[37m'
|
|
26
|
+
|
|
27
|
+
# Helper: format token count
|
|
28
|
+
fmt() {
|
|
29
|
+
local n=${1:-0}
|
|
30
|
+
if (( n >= 1000000 )); then printf "%.1fM" "$(echo "scale=1;$n/1000000" | bc)"
|
|
31
|
+
elif (( n >= 1000 )); then printf "%.1fk" "$(echo "scale=1;$n/1000" | bc)"
|
|
32
|
+
else echo "$n"; fi
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
# Helper: color a percentage (green < 50, yellow < 80, red >= 80)
|
|
36
|
+
pct_color() {
|
|
37
|
+
local pct_int=${1%.*}
|
|
38
|
+
if (( ${pct_int:-0} >= 80 )); then echo "$RED"
|
|
39
|
+
elif (( ${pct_int:-0} >= 50 )); then echo "$YLW"
|
|
40
|
+
else echo "$GRN"; fi
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
# Helper: format time remaining from ISO timestamp
|
|
44
|
+
time_remaining() {
|
|
45
|
+
local reset_at="$1"
|
|
46
|
+
[ -z "$reset_at" ] || [ "$reset_at" = "null" ] && return
|
|
47
|
+
local reset_epoch now_epoch diff_s hours mins
|
|
48
|
+
reset_epoch=$(date -d "$reset_at" +%s 2>/dev/null) || return
|
|
49
|
+
now_epoch=$(date +%s)
|
|
50
|
+
diff_s=$(( reset_epoch - now_epoch ))
|
|
51
|
+
(( diff_s <= 0 )) && { echo "now"; return; }
|
|
52
|
+
hours=$(( diff_s / 3600 ))
|
|
53
|
+
mins=$(( (diff_s % 3600) / 60 ))
|
|
54
|
+
if (( hours > 0 )); then echo "${hours}h${mins}m"
|
|
55
|
+
else echo "${mins}m"; fi
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
# --- Usage limits (cached for 60s) ---
|
|
59
|
+
CACHE_FILE="/tmp/.claude-usage-cache-$(id -u)"
|
|
60
|
+
CACHE_TTL=60
|
|
61
|
+
usage_5h="" usage_7d="" reset_5h="" reset_7d=""
|
|
62
|
+
|
|
63
|
+
fetch_usage() {
|
|
64
|
+
local creds_file="$HOME/.claude/.credentials.json"
|
|
65
|
+
[ -f "$creds_file" ] || return
|
|
66
|
+
local token
|
|
67
|
+
token=$(jq -r '.claudeAiOauth.accessToken // empty' "$creds_file" 2>/dev/null)
|
|
68
|
+
[ -z "$token" ] && return
|
|
69
|
+
local response
|
|
70
|
+
response=$(curl -sf --max-time 3 \
|
|
71
|
+
-H "Accept: application/json" \
|
|
72
|
+
-H "Content-Type: application/json" \
|
|
73
|
+
-H "Authorization: Bearer $token" \
|
|
74
|
+
-H "anthropic-beta: oauth-2025-04-20" \
|
|
75
|
+
"https://api.anthropic.com/api/oauth/usage" 2>/dev/null) || return
|
|
76
|
+
echo "$response" > "$CACHE_FILE"
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
# Use cache if fresh, otherwise fetch in background
|
|
80
|
+
if [ -f "$CACHE_FILE" ]; then
|
|
81
|
+
cache_age=$(( $(date +%s) - $(stat -c %Y "$CACHE_FILE" 2>/dev/null || echo 0) ))
|
|
82
|
+
if (( cache_age > CACHE_TTL )); then
|
|
83
|
+
# Fetch in background so we don't block the statusline
|
|
84
|
+
fetch_usage &
|
|
85
|
+
fi
|
|
86
|
+
else
|
|
87
|
+
# First run — fetch synchronously (one-time cost)
|
|
88
|
+
fetch_usage
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
# Read cached data
|
|
92
|
+
if [ -f "$CACHE_FILE" ]; then
|
|
93
|
+
eval "$(jq -r '
|
|
94
|
+
@sh "usage_5h=\(.five_hour.utilization // "")",
|
|
95
|
+
@sh "reset_5h=\(.five_hour.resets_at // "")",
|
|
96
|
+
@sh "usage_7d=\(.seven_day.utilization // "")",
|
|
97
|
+
@sh "reset_7d=\(.seven_day.resets_at // "")"
|
|
98
|
+
' "$CACHE_FILE" 2>/dev/null)"
|
|
99
|
+
fi
|
|
100
|
+
|
|
101
|
+
# Git branch (fast — reads file directly, no subprocess)
|
|
102
|
+
git_branch=""
|
|
103
|
+
dir="${current_dir:-.}"
|
|
104
|
+
while [ "$dir" != "/" ]; do
|
|
105
|
+
if [ -f "$dir/.git/HEAD" ]; then
|
|
106
|
+
ref=$(< "$dir/.git/HEAD")
|
|
107
|
+
git_branch="${ref#ref: refs/heads/}"
|
|
108
|
+
break
|
|
109
|
+
fi
|
|
110
|
+
dir=$(dirname "$dir")
|
|
111
|
+
done
|
|
112
|
+
|
|
113
|
+
# Context % color
|
|
114
|
+
ctx_color=$(pct_color "$ctx_used_pct")
|
|
115
|
+
|
|
116
|
+
# Cost formatting
|
|
117
|
+
cost_fmt=$(printf '$%.4f' "${cost:-0}")
|
|
118
|
+
|
|
119
|
+
# Line 1: model | dir | git branch
|
|
120
|
+
line1=""
|
|
121
|
+
[ -n "$model" ] && line1+=$(printf "%b%s%b" "$MAG" "$model" "$RST")
|
|
122
|
+
[ -n "$model_id" ] && line1+=$(printf " %b(%s)%b" "$DIM" "$model_id" "$RST")
|
|
123
|
+
if [ -n "$current_dir" ]; then
|
|
124
|
+
short_dir="${current_dir/#$HOME/~}"
|
|
125
|
+
line1+=$(printf " %b%s%b" "$CYN" "$short_dir" "$RST")
|
|
126
|
+
fi
|
|
127
|
+
[ -n "$git_branch" ] && line1+=$(printf " %b%b%s%b" "$DIM" "$GRN" "$git_branch" "$RST")
|
|
128
|
+
|
|
129
|
+
# Line 2: context usage | cost | lines changed
|
|
130
|
+
line2=""
|
|
131
|
+
line2+=$(printf "%bctx%b %b%.0f%%%b" "$DIM" "$RST" "$ctx_color" "$ctx_used_pct" "$RST")
|
|
132
|
+
line2+=$(printf " %b%s%b/%b%s%b" "$WHT" "$(fmt "$ctx_in")" "$RST" "$DIM" "$(fmt "$ctx_size")" "$RST")
|
|
133
|
+
line2+=$(printf " %bout%b %s" "$DIM" "$RST" "$(fmt "$ctx_out")")
|
|
134
|
+
line2+=$(printf " %bcost%b %b%s%b" "$DIM" "$RST" "$YLW" "$cost_fmt" "$RST")
|
|
135
|
+
if (( lines_added > 0 || lines_removed > 0 )); then
|
|
136
|
+
line2+=$(printf " %b+%d%b/%b-%d%b" "$GRN" "$lines_added" "$RST" "$RED" "$lines_removed" "$RST")
|
|
137
|
+
fi
|
|
138
|
+
|
|
139
|
+
# Line 3: plan usage limits (5h + 7d)
|
|
140
|
+
line3=""
|
|
141
|
+
if [ -n "$usage_5h" ]; then
|
|
142
|
+
u5_color=$(pct_color "$usage_5h")
|
|
143
|
+
u5_reset=$(time_remaining "$reset_5h")
|
|
144
|
+
line3+=$(printf "%b5h%b %b%.0f%%%b" "$DIM" "$RST" "$u5_color" "$usage_5h" "$RST")
|
|
145
|
+
[ -n "$u5_reset" ] && line3+=$(printf " %b(%s)%b" "$DIM" "$u5_reset" "$RST")
|
|
146
|
+
fi
|
|
147
|
+
if [ -n "$usage_7d" ]; then
|
|
148
|
+
u7_color=$(pct_color "$usage_7d")
|
|
149
|
+
u7_reset=$(time_remaining "$reset_7d")
|
|
150
|
+
[ -n "$line3" ] && line3+=" "
|
|
151
|
+
line3+=$(printf "%b7d%b %b%.0f%%%b" "$DIM" "$RST" "$u7_color" "$usage_7d" "$RST")
|
|
152
|
+
[ -n "$u7_reset" ] && line3+=$(printf " %b(%s)%b" "$DIM" "$u7_reset" "$RST")
|
|
153
|
+
fi
|
|
154
|
+
|
|
155
|
+
# Write context % to agent dir for dashboard monitoring (non-blocking)
|
|
156
|
+
if [ -n "$PANOPTICON_AGENT_ID" ] && [ -n "$ctx_used_pct" ]; then
|
|
157
|
+
CTX_DIR="$HOME/.panopticon/agents/$PANOPTICON_AGENT_ID"
|
|
158
|
+
if [ -d "$CTX_DIR" ]; then
|
|
159
|
+
printf '%.0f' "$ctx_used_pct" > "$CTX_DIR/context-pct" 2>/dev/null || true
|
|
160
|
+
# Capture initial context % (first time only)
|
|
161
|
+
if [ ! -f "$CTX_DIR/initial-context-pct" ]; then
|
|
162
|
+
printf '%.0f' "$ctx_used_pct" > "$CTX_DIR/initial-context-pct" 2>/dev/null || true
|
|
163
|
+
fi
|
|
164
|
+
fi
|
|
165
|
+
fi
|
|
166
|
+
|
|
167
|
+
printf "%b\n" "$line1"
|
|
168
|
+
printf "%b\n" "$line2"
|
|
169
|
+
[ -n "$line3" ] && printf "%b\n" "$line3"
|
package/scripts/stop-hook
CHANGED
|
@@ -26,17 +26,20 @@ fi
|
|
|
26
26
|
STATE_DIR="$HOME/.panopticon/agents/$AGENT_ID"
|
|
27
27
|
mkdir -p "$STATE_DIR"
|
|
28
28
|
|
|
29
|
-
#
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
lastActivity
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
29
|
+
# Update runtime state in runtime.json (NOT state.json — state.json holds AgentState config
|
|
30
|
+
# like workspace, runtime, model which must not be overwritten by hooks)
|
|
31
|
+
RUNTIME_FILE="$STATE_DIR/runtime.json"
|
|
32
|
+
TEMP_FILE="$RUNTIME_FILE.tmp"
|
|
33
|
+
if [ -f "$RUNTIME_FILE" ]; then
|
|
34
|
+
# Merge with existing runtime state (preserves contextPercent, sessionId, etc.)
|
|
35
|
+
jq --arg ts "$(date -Iseconds)" \
|
|
36
|
+
'.state = "idle" | .lastActivity = $ts | del(.currentTool)' \
|
|
37
|
+
"$RUNTIME_FILE" > "$TEMP_FILE" 2>/dev/null && mv "$TEMP_FILE" "$RUNTIME_FILE" 2>/dev/null || true
|
|
38
|
+
else
|
|
39
|
+
# Create initial runtime state
|
|
40
|
+
jq -n --arg ts "$(date -Iseconds)" \
|
|
41
|
+
'{state: "idle", lastActivity: $ts}' > "$TEMP_FILE" 2>/dev/null && mv "$TEMP_FILE" "$RUNTIME_FILE" 2>/dev/null || true
|
|
42
|
+
fi
|
|
40
43
|
|
|
41
44
|
# Optionally send heartbeat to API server (non-blocking)
|
|
42
45
|
# Only if dashboard is running
|