@juli-os/ledger 0.1.5 → 0.1.6
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/index.js +31 -36
- package/dist/juli-ledger.js +31 -36
- package/dist/platform/shared/json.d.ts +0 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -224,10 +224,11 @@ CREATE INDEX IF NOT EXISTS idx_runs_session ON runs(session, status);
|
|
|
224
224
|
`;
|
|
225
225
|
var str = (v) => typeof v === "string" ? v : v === null || v === void 0 ? "" : String(v);
|
|
226
226
|
var num = (v) => typeof v === "number" ? v : Number(v ?? 0);
|
|
227
|
-
var
|
|
228
|
-
const jsonValue = JSON.stringify(
|
|
229
|
-
return `%"
|
|
227
|
+
var jsonFieldPattern = (field, value) => {
|
|
228
|
+
const jsonValue = JSON.stringify(value).slice(1, -1);
|
|
229
|
+
return `%"${field}":"${jsonValue.replace(/[%_\\]/g, (c) => `\\${c}`)}"%`;
|
|
230
230
|
};
|
|
231
|
+
var dedupPattern = (key) => jsonFieldPattern("dedup_key", key);
|
|
231
232
|
var SEQ_PARK_BASE = 1e9;
|
|
232
233
|
var SEND_STEP_LIKE_CUTOFF = "2026-09-19";
|
|
233
234
|
var rowToWorkflow = (r) => ({
|
|
@@ -352,8 +353,7 @@ var createLedgerStore = (dbPath, deps = {}) => {
|
|
|
352
353
|
return getWorkflowById(id);
|
|
353
354
|
},
|
|
354
355
|
getWorkflow(id) {
|
|
355
|
-
|
|
356
|
-
return r ? rowToWorkflow(r) : null;
|
|
356
|
+
return getWorkflowById(id);
|
|
357
357
|
},
|
|
358
358
|
listWorkflows(limit = 100, status) {
|
|
359
359
|
if (status !== void 0 && status !== "") {
|
|
@@ -456,8 +456,7 @@ var createLedgerStore = (dbPath, deps = {}) => {
|
|
|
456
456
|
}
|
|
457
457
|
},
|
|
458
458
|
getStep(id) {
|
|
459
|
-
|
|
460
|
-
return r ? rowToStep(r) : null;
|
|
459
|
+
return getStepById(id);
|
|
461
460
|
},
|
|
462
461
|
stepsForWorkflow(id) {
|
|
463
462
|
return db.prepare(`SELECT ${STEP_COLS} FROM workflow_steps WHERE workflow_id=? ORDER BY seq`).all(id).map(rowToStep);
|
|
@@ -528,25 +527,21 @@ var createLedgerStore = (dbPath, deps = {}) => {
|
|
|
528
527
|
).all(status, limit).map(rowToStep);
|
|
529
528
|
},
|
|
530
529
|
runningAgentStepForSession(session) {
|
|
531
|
-
const
|
|
532
|
-
`SELECT st.id AS id, st.workflow_id AS workflow_id, st.seq AS seq, st.kind AS kind,
|
|
530
|
+
const proj = `SELECT st.id AS id, st.workflow_id AS workflow_id, st.seq AS seq, st.kind AS kind,
|
|
533
531
|
COALESCE(st.title,'') AS title, st.status AS status,
|
|
534
532
|
COALESCE(st.input,'') AS input, COALESCE(st.output,'') AS output,
|
|
535
533
|
COALESCE(st.summary,'') AS summary, st.updated_at AS updated_at
|
|
536
534
|
FROM workflow_steps st
|
|
537
|
-
JOIN workflows w ON w.id = st.workflow_id
|
|
535
|
+
JOIN workflows w ON w.id = st.workflow_id`;
|
|
536
|
+
const r = db.prepare(
|
|
537
|
+
`${proj}
|
|
538
538
|
JOIN runs r ON r.workflow_id = st.workflow_id
|
|
539
539
|
WHERE st.status='running' AND st.kind IN (${sqlInList(AGENT_STEP_KINDS)}) AND r.session=? AND w.status='running'
|
|
540
540
|
ORDER BY st.updated_at DESC LIMIT 1`
|
|
541
541
|
).get(session);
|
|
542
542
|
if (r) return rowToStep(r);
|
|
543
543
|
const r2 = db.prepare(
|
|
544
|
-
|
|
545
|
-
COALESCE(st.title,'') AS title, st.status AS status,
|
|
546
|
-
COALESCE(st.input,'') AS input, COALESCE(st.output,'') AS output,
|
|
547
|
-
COALESCE(st.summary,'') AS summary, st.updated_at AS updated_at
|
|
548
|
-
FROM workflow_steps st
|
|
549
|
-
JOIN workflows w ON w.id = st.workflow_id
|
|
544
|
+
`${proj}
|
|
550
545
|
WHERE st.status='running' AND st.kind IN (${sqlInList(AGENT_STEP_KINDS)}) AND w.status='running'
|
|
551
546
|
AND (w.session=? OR COALESCE(json_extract(w.meta,'$.assigned_session'),'')=?)
|
|
552
547
|
ORDER BY st.updated_at DESC LIMIT 1`
|
|
@@ -555,7 +550,8 @@ var createLedgerStore = (dbPath, deps = {}) => {
|
|
|
555
550
|
},
|
|
556
551
|
sessionHasOpenWork(session, excludeWorkflowId) {
|
|
557
552
|
const anchor = "(session=?1 OR COALESCE(json_extract(meta,'$.assigned_session'),'')=?1 OR id IN (SELECT workflow_id FROM runs WHERE session=?1))";
|
|
558
|
-
const
|
|
553
|
+
const openFilter = `status IN (${sqlInList(ACTIVE_WORKFLOW_STATUSES)})`;
|
|
554
|
+
const sql = excludeWorkflowId !== void 0 && excludeWorkflowId !== "" ? `SELECT COUNT(*) AS n FROM workflows WHERE ${openFilter} AND ${anchor} AND id != ?2` : `SELECT COUNT(*) AS n FROM workflows WHERE ${openFilter} AND ${anchor}`;
|
|
559
555
|
const row = excludeWorkflowId !== void 0 && excludeWorkflowId !== "" ? db.prepare(sql).get(session, excludeWorkflowId) : db.prepare(sql).get(session);
|
|
560
556
|
return Number(row["n"]) > 0;
|
|
561
557
|
},
|
|
@@ -630,7 +626,7 @@ var createLedgerStore = (dbPath, deps = {}) => {
|
|
|
630
626
|
/** 自回环守卫查询(2026-09-20 断点十三):message_id 是否命中本引擎已
|
|
631
627
|
* 发送记录——completed send 步的 output.message_id(email_sent 事件同源
|
|
632
628
|
* 落账)。进件层据此丢弃投递副本回灌(引擎给轮询邮箱自己发信的 echo)。
|
|
633
|
-
* LIKE 预筛 + 精确比对:JSON 编码取实际存储字节,%/_/\ 照
|
|
629
|
+
* LIKE 预筛 + 精确比对:JSON 编码取实际存储字节,%/_/\ 照 jsonFieldPattern
|
|
634
630
|
* 成对转义(message-id 几乎必含 `_`)。 */
|
|
635
631
|
recordSentMessageId(messageId, meta) {
|
|
636
632
|
if (messageId === "") return;
|
|
@@ -644,8 +640,7 @@ var createLedgerStore = (dbPath, deps = {}) => {
|
|
|
644
640
|
`SELECT 1 FROM events WHERE type = 'email_sent' AND entity_type = 'email' AND entity_id = ? LIMIT 1`
|
|
645
641
|
).get(messageId);
|
|
646
642
|
if (registered !== void 0) return true;
|
|
647
|
-
const
|
|
648
|
-
const pattern = `%"message_id":"${jsonValue.replace(/[%_\\]/g, (c) => `\\${c}`)}"%`;
|
|
643
|
+
const pattern = jsonFieldPattern("message_id", messageId);
|
|
649
644
|
const rows = db.prepare(
|
|
650
645
|
`SELECT output FROM workflow_steps WHERE kind = 'send' AND status = 'completed'
|
|
651
646
|
AND updated_at < ? AND output LIKE ? ESCAPE '\\'`
|
|
@@ -881,18 +876,18 @@ var priceUsage = (rows) => {
|
|
|
881
876
|
const key = r.model === "" ? "\uFF08\u672A\u8BB0\u5F55\u6A21\u578B\uFF09" : r.model;
|
|
882
877
|
let a = accs.get(key);
|
|
883
878
|
if (a === void 0) {
|
|
884
|
-
const
|
|
879
|
+
const p2 = findPrice(key);
|
|
885
880
|
a = {
|
|
886
881
|
model: key,
|
|
887
|
-
label:
|
|
882
|
+
label: p2?.label ?? key,
|
|
888
883
|
free: false,
|
|
889
|
-
priced: p !== null,
|
|
890
884
|
calls: 0,
|
|
891
885
|
inputTokens: 0,
|
|
892
886
|
cachedTokens: 0,
|
|
893
887
|
outputTokens: 0,
|
|
894
888
|
tiersHit: /* @__PURE__ */ new Set(),
|
|
895
|
-
cost: { cache: 0, input: 0, output: 0 }
|
|
889
|
+
cost: { cache: 0, input: 0, output: 0 },
|
|
890
|
+
price: p2
|
|
896
891
|
};
|
|
897
892
|
accs.set(key, a);
|
|
898
893
|
}
|
|
@@ -900,8 +895,9 @@ var priceUsage = (rows) => {
|
|
|
900
895
|
a.inputTokens += r.inputTokens;
|
|
901
896
|
a.cachedTokens += r.cachedTokens;
|
|
902
897
|
a.outputTokens += r.outputTokens;
|
|
903
|
-
|
|
904
|
-
|
|
898
|
+
const p = a.price;
|
|
899
|
+
if (p === null) continue;
|
|
900
|
+
const t = tierOf(p, r);
|
|
905
901
|
a.tiersHit.add(t.note);
|
|
906
902
|
if (t.input === 0 && t.output === 0) {
|
|
907
903
|
a.free = true;
|
|
@@ -917,7 +913,7 @@ var priceUsage = (rows) => {
|
|
|
917
913
|
model: a.model,
|
|
918
914
|
label: a.label,
|
|
919
915
|
free: a.free,
|
|
920
|
-
priced: a.
|
|
916
|
+
priced: a.price !== null,
|
|
921
917
|
calls: a.calls,
|
|
922
918
|
inputTokens: a.inputTokens,
|
|
923
919
|
cachedTokens: a.cachedTokens,
|
|
@@ -981,11 +977,15 @@ CREATE TABLE IF NOT EXISTS usage_meta (
|
|
|
981
977
|
);
|
|
982
978
|
`;
|
|
983
979
|
var n = (v) => typeof v === "number" ? v : Number(v ?? 0);
|
|
984
|
-
var
|
|
985
|
-
if (windows.length === 0) return [];
|
|
980
|
+
var windowConds = (tmuxSession, windows) => {
|
|
986
981
|
const conds = windows.map(() => "(created_at >= ? AND created_at <= ?)").join(" OR ");
|
|
987
982
|
const params = [tmuxSession];
|
|
988
983
|
for (const [a, b] of windows) params.push(a, b);
|
|
984
|
+
return { conds, params };
|
|
985
|
+
};
|
|
986
|
+
var windowUsageRows = (db, tmuxSession, windows) => {
|
|
987
|
+
if (windows.length === 0) return [];
|
|
988
|
+
const { conds, params } = windowConds(tmuxSession, windows);
|
|
989
989
|
return db.prepare(
|
|
990
990
|
`SELECT model, input_tokens AS i, output_tokens AS o, cached_tokens AS k
|
|
991
991
|
FROM prompt_usage WHERE source='claude_code' AND tmux_session=? AND (${conds})`
|
|
@@ -1077,9 +1077,7 @@ var createUsageStore = (dbPath, now = () => /* @__PURE__ */ new Date()) => {
|
|
|
1077
1077
|
},
|
|
1078
1078
|
perSessionWindows(tmuxSession, windows) {
|
|
1079
1079
|
if (windows.length === 0) return { count: 0, inputTokens: 0, outputTokens: 0 };
|
|
1080
|
-
const conds
|
|
1081
|
-
const params = [tmuxSession];
|
|
1082
|
-
for (const [a, b] of windows) params.push(a, b);
|
|
1080
|
+
const { conds, params } = windowConds(tmuxSession, windows);
|
|
1083
1081
|
const r = db.prepare(
|
|
1084
1082
|
`SELECT COUNT(*) AS c, COALESCE(SUM(input_tokens),0) AS i, COALESCE(SUM(output_tokens),0) AS o
|
|
1085
1083
|
FROM prompt_usage WHERE source='claude_code' AND tmux_session=? AND (${conds})`
|
|
@@ -1160,9 +1158,6 @@ var createUsageStore = (dbPath, now = () => /* @__PURE__ */ new Date()) => {
|
|
|
1160
1158
|
VALUES (?, ?, '', '') ON CONFLICT(claude_session_id) DO NOTHING`).run(claudeSessionId, tmuxSession);
|
|
1161
1159
|
},
|
|
1162
1160
|
ingestProjectTranscripts(claudeProjectsDir) {
|
|
1163
|
-
const mapped = new Set(
|
|
1164
|
-
db.prepare("SELECT claude_session_id FROM claude_sessions").all().map((r) => String(r["claude_session_id"]))
|
|
1165
|
-
);
|
|
1166
1161
|
let count = 0;
|
|
1167
1162
|
try {
|
|
1168
1163
|
const projects = readdirSync(claudeProjectsDir);
|
|
@@ -1217,11 +1212,11 @@ var ingestFile = (db, claudeId, transcriptPath) => {
|
|
|
1217
1212
|
const newOffset = offset + Buffer.byteLength(complete, "utf8");
|
|
1218
1213
|
db.exec("BEGIN");
|
|
1219
1214
|
try {
|
|
1215
|
+
const sess = tmuxOf(db, claudeId);
|
|
1220
1216
|
for (const line of complete.split("\n")) {
|
|
1221
1217
|
if (line === "") continue;
|
|
1222
1218
|
const rec = parseAssistantLine(line);
|
|
1223
1219
|
if (!rec) continue;
|
|
1224
|
-
const sess = tmuxOf(db, claudeId);
|
|
1225
1220
|
db.prepare(`INSERT INTO prompt_usage (source, note, model, input_tokens, output_tokens, cached_tokens, duration_ms, error, created_at, tmux_session)
|
|
1226
1221
|
VALUES ('claude_code', ?, ?, ?, ?, ?, 0, '', ?, ?)`).run(
|
|
1227
1222
|
`turn=${String(rec.callContext).slice(0, 40)}`,
|
package/dist/juli-ledger.js
CHANGED
|
@@ -182,10 +182,11 @@ CREATE INDEX IF NOT EXISTS idx_runs_session ON runs(session, status);
|
|
|
182
182
|
`;
|
|
183
183
|
var str = (v) => typeof v === "string" ? v : v === null || v === void 0 ? "" : String(v);
|
|
184
184
|
var num = (v) => typeof v === "number" ? v : Number(v ?? 0);
|
|
185
|
-
var
|
|
186
|
-
const jsonValue = JSON.stringify(
|
|
187
|
-
return `%"
|
|
185
|
+
var jsonFieldPattern = (field, value) => {
|
|
186
|
+
const jsonValue = JSON.stringify(value).slice(1, -1);
|
|
187
|
+
return `%"${field}":"${jsonValue.replace(/[%_\\]/g, (c) => `\\${c}`)}"%`;
|
|
188
188
|
};
|
|
189
|
+
var dedupPattern = (key) => jsonFieldPattern("dedup_key", key);
|
|
189
190
|
var SEQ_PARK_BASE = 1e9;
|
|
190
191
|
var SEND_STEP_LIKE_CUTOFF = "2026-09-19";
|
|
191
192
|
var rowToWorkflow = (r) => ({
|
|
@@ -310,8 +311,7 @@ var createLedgerStore = (dbPath, deps = {}) => {
|
|
|
310
311
|
return getWorkflowById(id);
|
|
311
312
|
},
|
|
312
313
|
getWorkflow(id) {
|
|
313
|
-
|
|
314
|
-
return r ? rowToWorkflow(r) : null;
|
|
314
|
+
return getWorkflowById(id);
|
|
315
315
|
},
|
|
316
316
|
listWorkflows(limit = 100, status) {
|
|
317
317
|
if (status !== void 0 && status !== "") {
|
|
@@ -414,8 +414,7 @@ var createLedgerStore = (dbPath, deps = {}) => {
|
|
|
414
414
|
}
|
|
415
415
|
},
|
|
416
416
|
getStep(id) {
|
|
417
|
-
|
|
418
|
-
return r ? rowToStep(r) : null;
|
|
417
|
+
return getStepById(id);
|
|
419
418
|
},
|
|
420
419
|
stepsForWorkflow(id) {
|
|
421
420
|
return db.prepare(`SELECT ${STEP_COLS} FROM workflow_steps WHERE workflow_id=? ORDER BY seq`).all(id).map(rowToStep);
|
|
@@ -486,25 +485,21 @@ var createLedgerStore = (dbPath, deps = {}) => {
|
|
|
486
485
|
).all(status, limit).map(rowToStep);
|
|
487
486
|
},
|
|
488
487
|
runningAgentStepForSession(session) {
|
|
489
|
-
const
|
|
490
|
-
`SELECT st.id AS id, st.workflow_id AS workflow_id, st.seq AS seq, st.kind AS kind,
|
|
488
|
+
const proj = `SELECT st.id AS id, st.workflow_id AS workflow_id, st.seq AS seq, st.kind AS kind,
|
|
491
489
|
COALESCE(st.title,'') AS title, st.status AS status,
|
|
492
490
|
COALESCE(st.input,'') AS input, COALESCE(st.output,'') AS output,
|
|
493
491
|
COALESCE(st.summary,'') AS summary, st.updated_at AS updated_at
|
|
494
492
|
FROM workflow_steps st
|
|
495
|
-
JOIN workflows w ON w.id = st.workflow_id
|
|
493
|
+
JOIN workflows w ON w.id = st.workflow_id`;
|
|
494
|
+
const r = db.prepare(
|
|
495
|
+
`${proj}
|
|
496
496
|
JOIN runs r ON r.workflow_id = st.workflow_id
|
|
497
497
|
WHERE st.status='running' AND st.kind IN (${sqlInList(AGENT_STEP_KINDS)}) AND r.session=? AND w.status='running'
|
|
498
498
|
ORDER BY st.updated_at DESC LIMIT 1`
|
|
499
499
|
).get(session);
|
|
500
500
|
if (r) return rowToStep(r);
|
|
501
501
|
const r2 = db.prepare(
|
|
502
|
-
|
|
503
|
-
COALESCE(st.title,'') AS title, st.status AS status,
|
|
504
|
-
COALESCE(st.input,'') AS input, COALESCE(st.output,'') AS output,
|
|
505
|
-
COALESCE(st.summary,'') AS summary, st.updated_at AS updated_at
|
|
506
|
-
FROM workflow_steps st
|
|
507
|
-
JOIN workflows w ON w.id = st.workflow_id
|
|
502
|
+
`${proj}
|
|
508
503
|
WHERE st.status='running' AND st.kind IN (${sqlInList(AGENT_STEP_KINDS)}) AND w.status='running'
|
|
509
504
|
AND (w.session=? OR COALESCE(json_extract(w.meta,'$.assigned_session'),'')=?)
|
|
510
505
|
ORDER BY st.updated_at DESC LIMIT 1`
|
|
@@ -513,7 +508,8 @@ var createLedgerStore = (dbPath, deps = {}) => {
|
|
|
513
508
|
},
|
|
514
509
|
sessionHasOpenWork(session, excludeWorkflowId) {
|
|
515
510
|
const anchor = "(session=?1 OR COALESCE(json_extract(meta,'$.assigned_session'),'')=?1 OR id IN (SELECT workflow_id FROM runs WHERE session=?1))";
|
|
516
|
-
const
|
|
511
|
+
const openFilter = `status IN (${sqlInList(ACTIVE_WORKFLOW_STATUSES)})`;
|
|
512
|
+
const sql = excludeWorkflowId !== void 0 && excludeWorkflowId !== "" ? `SELECT COUNT(*) AS n FROM workflows WHERE ${openFilter} AND ${anchor} AND id != ?2` : `SELECT COUNT(*) AS n FROM workflows WHERE ${openFilter} AND ${anchor}`;
|
|
517
513
|
const row = excludeWorkflowId !== void 0 && excludeWorkflowId !== "" ? db.prepare(sql).get(session, excludeWorkflowId) : db.prepare(sql).get(session);
|
|
518
514
|
return Number(row["n"]) > 0;
|
|
519
515
|
},
|
|
@@ -588,7 +584,7 @@ var createLedgerStore = (dbPath, deps = {}) => {
|
|
|
588
584
|
/** 自回环守卫查询(2026-09-20 断点十三):message_id 是否命中本引擎已
|
|
589
585
|
* 发送记录——completed send 步的 output.message_id(email_sent 事件同源
|
|
590
586
|
* 落账)。进件层据此丢弃投递副本回灌(引擎给轮询邮箱自己发信的 echo)。
|
|
591
|
-
* LIKE 预筛 + 精确比对:JSON 编码取实际存储字节,%/_/\ 照
|
|
587
|
+
* LIKE 预筛 + 精确比对:JSON 编码取实际存储字节,%/_/\ 照 jsonFieldPattern
|
|
592
588
|
* 成对转义(message-id 几乎必含 `_`)。 */
|
|
593
589
|
recordSentMessageId(messageId, meta) {
|
|
594
590
|
if (messageId === "") return;
|
|
@@ -602,8 +598,7 @@ var createLedgerStore = (dbPath, deps = {}) => {
|
|
|
602
598
|
`SELECT 1 FROM events WHERE type = 'email_sent' AND entity_type = 'email' AND entity_id = ? LIMIT 1`
|
|
603
599
|
).get(messageId);
|
|
604
600
|
if (registered !== void 0) return true;
|
|
605
|
-
const
|
|
606
|
-
const pattern = `%"message_id":"${jsonValue.replace(/[%_\\]/g, (c) => `\\${c}`)}"%`;
|
|
601
|
+
const pattern = jsonFieldPattern("message_id", messageId);
|
|
607
602
|
const rows = db.prepare(
|
|
608
603
|
`SELECT output FROM workflow_steps WHERE kind = 'send' AND status = 'completed'
|
|
609
604
|
AND updated_at < ? AND output LIKE ? ESCAPE '\\'`
|
|
@@ -839,18 +834,18 @@ var priceUsage = (rows) => {
|
|
|
839
834
|
const key = r.model === "" ? "\uFF08\u672A\u8BB0\u5F55\u6A21\u578B\uFF09" : r.model;
|
|
840
835
|
let a = accs.get(key);
|
|
841
836
|
if (a === void 0) {
|
|
842
|
-
const
|
|
837
|
+
const p2 = findPrice(key);
|
|
843
838
|
a = {
|
|
844
839
|
model: key,
|
|
845
|
-
label:
|
|
840
|
+
label: p2?.label ?? key,
|
|
846
841
|
free: false,
|
|
847
|
-
priced: p !== null,
|
|
848
842
|
calls: 0,
|
|
849
843
|
inputTokens: 0,
|
|
850
844
|
cachedTokens: 0,
|
|
851
845
|
outputTokens: 0,
|
|
852
846
|
tiersHit: /* @__PURE__ */ new Set(),
|
|
853
|
-
cost: { cache: 0, input: 0, output: 0 }
|
|
847
|
+
cost: { cache: 0, input: 0, output: 0 },
|
|
848
|
+
price: p2
|
|
854
849
|
};
|
|
855
850
|
accs.set(key, a);
|
|
856
851
|
}
|
|
@@ -858,8 +853,9 @@ var priceUsage = (rows) => {
|
|
|
858
853
|
a.inputTokens += r.inputTokens;
|
|
859
854
|
a.cachedTokens += r.cachedTokens;
|
|
860
855
|
a.outputTokens += r.outputTokens;
|
|
861
|
-
|
|
862
|
-
|
|
856
|
+
const p = a.price;
|
|
857
|
+
if (p === null) continue;
|
|
858
|
+
const t = tierOf(p, r);
|
|
863
859
|
a.tiersHit.add(t.note);
|
|
864
860
|
if (t.input === 0 && t.output === 0) {
|
|
865
861
|
a.free = true;
|
|
@@ -875,7 +871,7 @@ var priceUsage = (rows) => {
|
|
|
875
871
|
model: a.model,
|
|
876
872
|
label: a.label,
|
|
877
873
|
free: a.free,
|
|
878
|
-
priced: a.
|
|
874
|
+
priced: a.price !== null,
|
|
879
875
|
calls: a.calls,
|
|
880
876
|
inputTokens: a.inputTokens,
|
|
881
877
|
cachedTokens: a.cachedTokens,
|
|
@@ -939,11 +935,15 @@ CREATE TABLE IF NOT EXISTS usage_meta (
|
|
|
939
935
|
);
|
|
940
936
|
`;
|
|
941
937
|
var n = (v) => typeof v === "number" ? v : Number(v ?? 0);
|
|
942
|
-
var
|
|
943
|
-
if (windows.length === 0) return [];
|
|
938
|
+
var windowConds = (tmuxSession, windows) => {
|
|
944
939
|
const conds = windows.map(() => "(created_at >= ? AND created_at <= ?)").join(" OR ");
|
|
945
940
|
const params = [tmuxSession];
|
|
946
941
|
for (const [a, b] of windows) params.push(a, b);
|
|
942
|
+
return { conds, params };
|
|
943
|
+
};
|
|
944
|
+
var windowUsageRows = (db, tmuxSession, windows) => {
|
|
945
|
+
if (windows.length === 0) return [];
|
|
946
|
+
const { conds, params } = windowConds(tmuxSession, windows);
|
|
947
947
|
return db.prepare(
|
|
948
948
|
`SELECT model, input_tokens AS i, output_tokens AS o, cached_tokens AS k
|
|
949
949
|
FROM prompt_usage WHERE source='claude_code' AND tmux_session=? AND (${conds})`
|
|
@@ -1035,9 +1035,7 @@ var createUsageStore = (dbPath, now = () => /* @__PURE__ */ new Date()) => {
|
|
|
1035
1035
|
},
|
|
1036
1036
|
perSessionWindows(tmuxSession, windows) {
|
|
1037
1037
|
if (windows.length === 0) return { count: 0, inputTokens: 0, outputTokens: 0 };
|
|
1038
|
-
const conds
|
|
1039
|
-
const params = [tmuxSession];
|
|
1040
|
-
for (const [a, b] of windows) params.push(a, b);
|
|
1038
|
+
const { conds, params } = windowConds(tmuxSession, windows);
|
|
1041
1039
|
const r = db.prepare(
|
|
1042
1040
|
`SELECT COUNT(*) AS c, COALESCE(SUM(input_tokens),0) AS i, COALESCE(SUM(output_tokens),0) AS o
|
|
1043
1041
|
FROM prompt_usage WHERE source='claude_code' AND tmux_session=? AND (${conds})`
|
|
@@ -1118,9 +1116,6 @@ var createUsageStore = (dbPath, now = () => /* @__PURE__ */ new Date()) => {
|
|
|
1118
1116
|
VALUES (?, ?, '', '') ON CONFLICT(claude_session_id) DO NOTHING`).run(claudeSessionId, tmuxSession);
|
|
1119
1117
|
},
|
|
1120
1118
|
ingestProjectTranscripts(claudeProjectsDir) {
|
|
1121
|
-
const mapped = new Set(
|
|
1122
|
-
db.prepare("SELECT claude_session_id FROM claude_sessions").all().map((r) => String(r["claude_session_id"]))
|
|
1123
|
-
);
|
|
1124
1119
|
let count = 0;
|
|
1125
1120
|
try {
|
|
1126
1121
|
const projects = readdirSync(claudeProjectsDir);
|
|
@@ -1175,11 +1170,11 @@ var ingestFile = (db, claudeId, transcriptPath) => {
|
|
|
1175
1170
|
const newOffset = offset + Buffer.byteLength(complete, "utf8");
|
|
1176
1171
|
db.exec("BEGIN");
|
|
1177
1172
|
try {
|
|
1173
|
+
const sess = tmuxOf(db, claudeId);
|
|
1178
1174
|
for (const line of complete.split("\n")) {
|
|
1179
1175
|
if (line === "") continue;
|
|
1180
1176
|
const rec = parseAssistantLine(line);
|
|
1181
1177
|
if (!rec) continue;
|
|
1182
|
-
const sess = tmuxOf(db, claudeId);
|
|
1183
1178
|
db.prepare(`INSERT INTO prompt_usage (source, note, model, input_tokens, output_tokens, cached_tokens, duration_ms, error, created_at, tmux_session)
|
|
1184
1179
|
VALUES ('claude_code', ?, ?, ?, ?, ?, 0, '', ?, ?)`).run(
|
|
1185
1180
|
`turn=${String(rec.callContext).slice(0, 40)}`,
|
|
@@ -6,5 +6,3 @@ export type JsonRecord = {
|
|
|
6
6
|
};
|
|
7
7
|
export declare const safeParse: (raw: string | null | undefined) => JsonValue | null;
|
|
8
8
|
export declare const asRecord: (v: JsonValue | null | undefined) => JsonRecord;
|
|
9
|
-
export declare const asString: (v: JsonValue | undefined) => string;
|
|
10
|
-
export declare const truncate: (s: string, n: number) => string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juli-os/ledger",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "juli-os L1 layer: the ledger of record for agent work — workflows, steps, case files, and token usage in one zero-dependency SQLite layer.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|