@use-aistack/cli 0.5.0 → 0.6.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.
- package/LICENSE +21 -0
- package/dist/index.js +915 -376
- package/dist/index.js.map +1 -1
- package/package.json +7 -8
package/dist/index.js
CHANGED
|
@@ -5,8 +5,8 @@ import { Command } from "commander";
|
|
|
5
5
|
|
|
6
6
|
// src/api.ts
|
|
7
7
|
var BASE_URL = process.env.AISTACK_URL || "https://aistack.to";
|
|
8
|
-
async function request(
|
|
9
|
-
return fetch(`${BASE_URL}${
|
|
8
|
+
async function request(path3, options = {}) {
|
|
9
|
+
return fetch(`${BASE_URL}${path3}`, {
|
|
10
10
|
...options,
|
|
11
11
|
headers: {
|
|
12
12
|
"Content-Type": "application/json",
|
|
@@ -319,9 +319,9 @@ function canonicalizeRepoUrl(input) {
|
|
|
319
319
|
function repoNameFromCanonical(canonical) {
|
|
320
320
|
return parseRepo(canonical)?.repo ?? "";
|
|
321
321
|
}
|
|
322
|
-
function normalizeUpstreamPath(
|
|
323
|
-
if (!
|
|
324
|
-
return
|
|
322
|
+
function normalizeUpstreamPath(path3) {
|
|
323
|
+
if (!path3) return "";
|
|
324
|
+
return path3.split("/").filter(Boolean).join("/");
|
|
325
325
|
}
|
|
326
326
|
|
|
327
327
|
// src/git.ts
|
|
@@ -367,16 +367,16 @@ function buildRepoLinkResource(canonical) {
|
|
|
367
367
|
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
368
368
|
import { homedir as homedir2 } from "os";
|
|
369
369
|
import { join as join2 } from "path";
|
|
370
|
-
function readJson(
|
|
370
|
+
function readJson(path3) {
|
|
371
371
|
try {
|
|
372
|
-
if (!existsSync2(
|
|
373
|
-
return JSON.parse(readFileSync2(
|
|
372
|
+
if (!existsSync2(path3)) return null;
|
|
373
|
+
return JSON.parse(readFileSync2(path3, "utf-8"));
|
|
374
374
|
} catch {
|
|
375
375
|
return null;
|
|
376
376
|
}
|
|
377
377
|
}
|
|
378
|
-
function hooksFrom(
|
|
379
|
-
const hooks = readJson(
|
|
378
|
+
function hooksFrom(path3, source, out, seen) {
|
|
379
|
+
const hooks = readJson(path3)?.hooks;
|
|
380
380
|
if (!hooks || typeof hooks !== "object") return;
|
|
381
381
|
for (const [event, config] of Object.entries(hooks)) {
|
|
382
382
|
const stableKey = `hooks:${source}:${event}`;
|
|
@@ -516,16 +516,16 @@ function buildMcpResource(name, group, pkg) {
|
|
|
516
516
|
pkg
|
|
517
517
|
};
|
|
518
518
|
}
|
|
519
|
-
function readText(
|
|
519
|
+
function readText(path3) {
|
|
520
520
|
try {
|
|
521
|
-
if (!existsSync3(
|
|
522
|
-
return readFileSync3(
|
|
521
|
+
if (!existsSync3(path3)) return null;
|
|
522
|
+
return readFileSync3(path3, "utf-8");
|
|
523
523
|
} catch {
|
|
524
524
|
return null;
|
|
525
525
|
}
|
|
526
526
|
}
|
|
527
|
-
function readParsed(
|
|
528
|
-
const raw = readText(
|
|
527
|
+
function readParsed(path3, parse) {
|
|
528
|
+
const raw = readText(path3);
|
|
529
529
|
if (raw === null) return null;
|
|
530
530
|
try {
|
|
531
531
|
return parse(raw);
|
|
@@ -533,14 +533,14 @@ function readParsed(path2, parse) {
|
|
|
533
533
|
return null;
|
|
534
534
|
}
|
|
535
535
|
}
|
|
536
|
-
function readJson2(
|
|
537
|
-
return readParsed(
|
|
536
|
+
function readJson2(path3) {
|
|
537
|
+
return readParsed(path3, JSON.parse);
|
|
538
538
|
}
|
|
539
|
-
function readYaml(
|
|
540
|
-
return readParsed(
|
|
539
|
+
function readYaml(path3) {
|
|
540
|
+
return readParsed(path3, parseYaml);
|
|
541
541
|
}
|
|
542
|
-
function readToml(
|
|
543
|
-
return readParsed(
|
|
542
|
+
function readToml(path3) {
|
|
543
|
+
return readParsed(path3, parseToml);
|
|
544
544
|
}
|
|
545
545
|
function continueListToMap(file) {
|
|
546
546
|
if (!file?.mcpServers?.length) return void 0;
|
|
@@ -660,8 +660,8 @@ function resolveSource(entry, mpRepoUrl) {
|
|
|
660
660
|
const src = entry.source;
|
|
661
661
|
if (typeof src === "string") {
|
|
662
662
|
if (!mpRepoUrl) return null;
|
|
663
|
-
const
|
|
664
|
-
return { url: mpRepoUrl, path:
|
|
663
|
+
const path3 = src.replace(/^\.\//, "").replace(/\/+$/, "");
|
|
664
|
+
return { url: mpRepoUrl, path: path3 || void 0 };
|
|
665
665
|
}
|
|
666
666
|
if (src && typeof src === "object" && src.url) {
|
|
667
667
|
return { url: src.url, path: src.path, sha: src.sha };
|
|
@@ -698,10 +698,10 @@ function resolvePluginLinks(installed, marketplaces, manifests) {
|
|
|
698
698
|
}
|
|
699
699
|
return out;
|
|
700
700
|
}
|
|
701
|
-
function readJson3(
|
|
701
|
+
function readJson3(path3) {
|
|
702
702
|
try {
|
|
703
|
-
if (!existsSync4(
|
|
704
|
-
return JSON.parse(readFileSync4(
|
|
703
|
+
if (!existsSync4(path3)) return null;
|
|
704
|
+
return JSON.parse(readFileSync4(path3, "utf-8"));
|
|
705
705
|
} catch {
|
|
706
706
|
return null;
|
|
707
707
|
}
|
|
@@ -781,8 +781,8 @@ function loadGitignore(cwd) {
|
|
|
781
781
|
}
|
|
782
782
|
function readFileSafe(filePath) {
|
|
783
783
|
try {
|
|
784
|
-
const
|
|
785
|
-
if (
|
|
784
|
+
const stat5 = statSync(filePath);
|
|
785
|
+
if (stat5.size > MAX_FILE_SIZE) return null;
|
|
786
786
|
return readFileSync5(filePath, "utf-8");
|
|
787
787
|
} catch {
|
|
788
788
|
return null;
|
|
@@ -1632,19 +1632,28 @@ async function loginCommand() {
|
|
|
1632
1632
|
// src/commands/sync.ts
|
|
1633
1633
|
import * as p7 from "@clack/prompts";
|
|
1634
1634
|
|
|
1635
|
-
// src/autosync/
|
|
1636
|
-
import
|
|
1637
|
-
|
|
1638
|
-
// src/autosync/hook.ts
|
|
1635
|
+
// src/autosync/codexHook.ts
|
|
1636
|
+
import { createHash } from "crypto";
|
|
1639
1637
|
import { existsSync as existsSync8, mkdirSync as mkdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync3 } from "fs";
|
|
1640
1638
|
import { homedir as homedir7 } from "os";
|
|
1641
1639
|
import { dirname as dirname5, join as join8 } from "path";
|
|
1642
|
-
|
|
1643
|
-
|
|
1640
|
+
function codexHome() {
|
|
1641
|
+
return process.env.CODEX_HOME || join8(homedir7(), ".codex");
|
|
1642
|
+
}
|
|
1643
|
+
function codexHooksFile() {
|
|
1644
|
+
return join8(codexHome(), "hooks.json");
|
|
1645
|
+
}
|
|
1646
|
+
function codexPresent() {
|
|
1647
|
+
return existsSync8(codexHome());
|
|
1648
|
+
}
|
|
1649
|
+
function codexConfigFile() {
|
|
1650
|
+
return join8(codexHome(), "config.toml");
|
|
1651
|
+
}
|
|
1652
|
+
var CODEX_HOOK_COMMAND = `sh -c 'setsid nohup sh -c "npx -y @use-aistack/cli@latest sync --auto || npx -y --prefer-offline @use-aistack/cli sync --auto" >/dev/null 2>&1 &'`;
|
|
1644
1653
|
function isOurs(entry) {
|
|
1645
1654
|
return typeof entry.command === "string" && entry.command.includes("@use-aistack/cli") && entry.command.includes("sync --auto");
|
|
1646
1655
|
}
|
|
1647
|
-
function
|
|
1656
|
+
function readHooksJson(file) {
|
|
1648
1657
|
if (!existsSync8(file)) return { settings: {} };
|
|
1649
1658
|
try {
|
|
1650
1659
|
const raw = JSON.parse(readFileSync7(file, "utf-8"));
|
|
@@ -1656,8 +1665,9 @@ function readClaudeSettings(file) {
|
|
|
1656
1665
|
return { error: `${file} is not valid JSON \u2014 fix it, then retry` };
|
|
1657
1666
|
}
|
|
1658
1667
|
}
|
|
1659
|
-
|
|
1660
|
-
|
|
1668
|
+
var CODEX_TRUST_INSTRUCTION = "Codex hook written \u2014 open Codex and run /hooks once to trust it, or it will not run.";
|
|
1669
|
+
function installCodexAutoSyncHook(file = codexHooksFile()) {
|
|
1670
|
+
const read = readHooksJson(file);
|
|
1661
1671
|
if ("error" in read) return { ok: false, message: read.error };
|
|
1662
1672
|
const settings = read.settings;
|
|
1663
1673
|
const hooks = settings.hooks ?? {};
|
|
@@ -1667,16 +1677,107 @@ function installAutoSyncHook(file = CLAUDE_SETTINGS_FILE) {
|
|
|
1667
1677
|
hooks: (m.hooks ?? []).filter((h) => !isOurs(h))
|
|
1668
1678
|
})).filter((m) => (m.hooks?.length ?? 0) > 0);
|
|
1669
1679
|
kept.push({
|
|
1670
|
-
|
|
1680
|
+
matcher: "startup",
|
|
1681
|
+
hooks: [{ type: "command", command: CODEX_HOOK_COMMAND, timeout: 30 }]
|
|
1671
1682
|
});
|
|
1672
1683
|
settings.hooks = { ...hooks, SessionStart: kept };
|
|
1673
1684
|
mkdirSync3(dirname5(file), { recursive: true });
|
|
1674
1685
|
writeFileSync3(file, `${JSON.stringify(settings, null, 2)}
|
|
1686
|
+
`);
|
|
1687
|
+
return { ok: true, message: CODEX_TRUST_INSTRUCTION };
|
|
1688
|
+
}
|
|
1689
|
+
function removeCodexAutoSyncHook(file = codexHooksFile()) {
|
|
1690
|
+
if (!existsSync8(file))
|
|
1691
|
+
return { ok: true, message: "no Codex hook to remove" };
|
|
1692
|
+
const read = readHooksJson(file);
|
|
1693
|
+
if ("error" in read) return { ok: false, message: read.error };
|
|
1694
|
+
const settings = read.settings;
|
|
1695
|
+
const sessionStart = settings.hooks?.SessionStart;
|
|
1696
|
+
if (!Array.isArray(sessionStart)) {
|
|
1697
|
+
return { ok: true, message: "no Codex hook to remove" };
|
|
1698
|
+
}
|
|
1699
|
+
const kept = sessionStart.map((m) => ({
|
|
1700
|
+
...m,
|
|
1701
|
+
hooks: (m.hooks ?? []).filter((h) => !isOurs(h))
|
|
1702
|
+
})).filter((m) => (m.hooks?.length ?? 0) > 0);
|
|
1703
|
+
const hooks = { ...settings.hooks };
|
|
1704
|
+
if (kept.length > 0) {
|
|
1705
|
+
hooks.SessionStart = kept;
|
|
1706
|
+
} else {
|
|
1707
|
+
delete hooks.SessionStart;
|
|
1708
|
+
}
|
|
1709
|
+
if (Object.keys(hooks).length > 0) {
|
|
1710
|
+
settings.hooks = hooks;
|
|
1711
|
+
} else {
|
|
1712
|
+
delete settings.hooks;
|
|
1713
|
+
}
|
|
1714
|
+
writeFileSync3(file, `${JSON.stringify(settings, null, 2)}
|
|
1715
|
+
`);
|
|
1716
|
+
return { ok: true, message: `hook removed from ${file}` };
|
|
1717
|
+
}
|
|
1718
|
+
function codexAutoSyncHookInstalled(file = codexHooksFile()) {
|
|
1719
|
+
const read = readHooksJson(file);
|
|
1720
|
+
if ("error" in read) return false;
|
|
1721
|
+
const sessionStart = read.settings.hooks?.SessionStart;
|
|
1722
|
+
if (!Array.isArray(sessionStart)) return false;
|
|
1723
|
+
return sessionStart.some((m) => (m.hooks ?? []).some((h) => isOurs(h)));
|
|
1724
|
+
}
|
|
1725
|
+
function codexHookTrusted(configFile = codexConfigFile()) {
|
|
1726
|
+
let text;
|
|
1727
|
+
try {
|
|
1728
|
+
text = readFileSync7(configFile, "utf-8");
|
|
1729
|
+
} catch {
|
|
1730
|
+
return null;
|
|
1731
|
+
}
|
|
1732
|
+
const hash = createHash("sha256").update(CODEX_HOOK_COMMAND).digest("hex");
|
|
1733
|
+
return text.includes(hash);
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
// src/autosync/optin.ts
|
|
1737
|
+
import * as p6 from "@clack/prompts";
|
|
1738
|
+
|
|
1739
|
+
// src/autosync/hook.ts
|
|
1740
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "fs";
|
|
1741
|
+
import { homedir as homedir8 } from "os";
|
|
1742
|
+
import { dirname as dirname6, join as join9 } from "path";
|
|
1743
|
+
var CLAUDE_SETTINGS_FILE = join9(homedir8(), ".claude", "settings.json");
|
|
1744
|
+
var AUTO_SYNC_HOOK_COMMAND = "npx -y @use-aistack/cli@latest sync --auto || npx -y --prefer-offline @use-aistack/cli sync --auto";
|
|
1745
|
+
function isOurs2(entry) {
|
|
1746
|
+
return typeof entry.command === "string" && entry.command.includes("@use-aistack/cli") && entry.command.includes("sync --auto");
|
|
1747
|
+
}
|
|
1748
|
+
function readClaudeSettings(file) {
|
|
1749
|
+
if (!existsSync9(file)) return { settings: {} };
|
|
1750
|
+
try {
|
|
1751
|
+
const raw = JSON.parse(readFileSync8(file, "utf-8"));
|
|
1752
|
+
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
1753
|
+
return { settings: raw };
|
|
1754
|
+
}
|
|
1755
|
+
return { error: `${file} does not hold a JSON object` };
|
|
1756
|
+
} catch {
|
|
1757
|
+
return { error: `${file} is not valid JSON \u2014 fix it, then retry` };
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
function installAutoSyncHook(file = CLAUDE_SETTINGS_FILE) {
|
|
1761
|
+
const read = readClaudeSettings(file);
|
|
1762
|
+
if ("error" in read) return { ok: false, message: read.error };
|
|
1763
|
+
const settings = read.settings;
|
|
1764
|
+
const hooks = settings.hooks ?? {};
|
|
1765
|
+
const sessionStart = Array.isArray(hooks.SessionStart) ? hooks.SessionStart : [];
|
|
1766
|
+
const kept = sessionStart.map((m) => ({
|
|
1767
|
+
...m,
|
|
1768
|
+
hooks: (m.hooks ?? []).filter((h) => !isOurs2(h))
|
|
1769
|
+
})).filter((m) => (m.hooks?.length ?? 0) > 0);
|
|
1770
|
+
kept.push({
|
|
1771
|
+
hooks: [{ type: "command", command: AUTO_SYNC_HOOK_COMMAND, async: true }]
|
|
1772
|
+
});
|
|
1773
|
+
settings.hooks = { ...hooks, SessionStart: kept };
|
|
1774
|
+
mkdirSync4(dirname6(file), { recursive: true });
|
|
1775
|
+
writeFileSync4(file, `${JSON.stringify(settings, null, 2)}
|
|
1675
1776
|
`);
|
|
1676
1777
|
return { ok: true, message: `SessionStart hook written to ${file}` };
|
|
1677
1778
|
}
|
|
1678
1779
|
function removeAutoSyncHook(file = CLAUDE_SETTINGS_FILE) {
|
|
1679
|
-
if (!
|
|
1780
|
+
if (!existsSync9(file)) return { ok: true, message: "no hook to remove" };
|
|
1680
1781
|
const read = readClaudeSettings(file);
|
|
1681
1782
|
if ("error" in read) return { ok: false, message: read.error };
|
|
1682
1783
|
const settings = read.settings;
|
|
@@ -1686,7 +1787,7 @@ function removeAutoSyncHook(file = CLAUDE_SETTINGS_FILE) {
|
|
|
1686
1787
|
}
|
|
1687
1788
|
const kept = sessionStart.map((m) => ({
|
|
1688
1789
|
...m,
|
|
1689
|
-
hooks: (m.hooks ?? []).filter((h) => !
|
|
1790
|
+
hooks: (m.hooks ?? []).filter((h) => !isOurs2(h))
|
|
1690
1791
|
})).filter((m) => (m.hooks?.length ?? 0) > 0);
|
|
1691
1792
|
const hooks = { ...settings.hooks };
|
|
1692
1793
|
if (kept.length > 0) {
|
|
@@ -1699,7 +1800,7 @@ function removeAutoSyncHook(file = CLAUDE_SETTINGS_FILE) {
|
|
|
1699
1800
|
} else {
|
|
1700
1801
|
delete settings.hooks;
|
|
1701
1802
|
}
|
|
1702
|
-
|
|
1803
|
+
writeFileSync4(file, `${JSON.stringify(settings, null, 2)}
|
|
1703
1804
|
`);
|
|
1704
1805
|
return { ok: true, message: `hook removed from ${file}` };
|
|
1705
1806
|
}
|
|
@@ -1709,6 +1810,13 @@ function enableAutoSync(frequencyHours = DEFAULT_FREQUENCY_HOURS, deps = {}) {
|
|
|
1709
1810
|
const install = deps.installHook ?? installAutoSyncHook;
|
|
1710
1811
|
const result = install();
|
|
1711
1812
|
if (!result.ok) return result;
|
|
1813
|
+
const hasCodex = (deps.codexPresentImpl ?? codexPresent)();
|
|
1814
|
+
let trustLine = null;
|
|
1815
|
+
if (hasCodex) {
|
|
1816
|
+
const codexResult = (deps.installCodexHook ?? installCodexAutoSyncHook)();
|
|
1817
|
+
if (!codexResult.ok) return codexResult;
|
|
1818
|
+
trustLine = codexResult.message;
|
|
1819
|
+
}
|
|
1712
1820
|
saveSettings(
|
|
1713
1821
|
{
|
|
1714
1822
|
autoSyncAnswered: true,
|
|
@@ -1716,9 +1824,13 @@ function enableAutoSync(frequencyHours = DEFAULT_FREQUENCY_HOURS, deps = {}) {
|
|
|
1716
1824
|
},
|
|
1717
1825
|
deps.settingsFile
|
|
1718
1826
|
);
|
|
1827
|
+
const sessionWord = hasCodex ? "Claude Code or Codex" : "Claude Code";
|
|
1719
1828
|
return {
|
|
1720
1829
|
ok: true,
|
|
1721
|
-
message:
|
|
1830
|
+
message: [
|
|
1831
|
+
`Auto-sync is on \u2014 about every ${frequencyHours}h when a ${sessionWord} session starts. Turn it off any time: npx @use-aistack/cli sync --auto off`,
|
|
1832
|
+
...trustLine ? [trustLine] : []
|
|
1833
|
+
].join("\n")
|
|
1722
1834
|
};
|
|
1723
1835
|
}
|
|
1724
1836
|
function disableAutoSync(deps = {}) {
|
|
@@ -1735,13 +1847,15 @@ function disableAutoSync(deps = {}) {
|
|
|
1735
1847
|
deps.settingsFile
|
|
1736
1848
|
);
|
|
1737
1849
|
const result = remove();
|
|
1738
|
-
|
|
1850
|
+
const codexResult = (deps.codexPresentImpl ?? codexPresent)() ? (deps.removeCodexHook ?? removeCodexAutoSyncHook)() : { ok: true, message: "" };
|
|
1851
|
+
const failures = [result, codexResult].filter((r) => !r.ok).map((r) => r.message);
|
|
1852
|
+
if (failures.length > 0) {
|
|
1739
1853
|
return {
|
|
1740
1854
|
ok: false,
|
|
1741
|
-
message: `Auto-sync is off (nothing will publish), but
|
|
1855
|
+
message: `Auto-sync is off (nothing will publish), but a hook could not be removed: ${failures.join("; ")}`
|
|
1742
1856
|
};
|
|
1743
1857
|
}
|
|
1744
|
-
return { ok: true, message: "Auto-sync is off. The
|
|
1858
|
+
return { ok: true, message: "Auto-sync is off. The hooks were removed." };
|
|
1745
1859
|
}
|
|
1746
1860
|
async function offerAutoSyncOptIn() {
|
|
1747
1861
|
if (getSettings().autoSyncAnswered === true) return false;
|
|
@@ -1783,18 +1897,22 @@ async function offerAutoSyncOptIn() {
|
|
|
1783
1897
|
// src/autosync/run.ts
|
|
1784
1898
|
import {
|
|
1785
1899
|
appendFileSync,
|
|
1786
|
-
mkdirSync as
|
|
1787
|
-
readFileSync as
|
|
1788
|
-
writeFileSync as
|
|
1900
|
+
mkdirSync as mkdirSync5,
|
|
1901
|
+
readFileSync as readFileSync10,
|
|
1902
|
+
writeFileSync as writeFileSync5
|
|
1789
1903
|
} from "fs";
|
|
1790
|
-
import { homedir as
|
|
1791
|
-
import { dirname as
|
|
1904
|
+
import { homedir as homedir11 } from "os";
|
|
1905
|
+
import { dirname as dirname7, join as join10 } from "path";
|
|
1792
1906
|
|
|
1793
1907
|
// src/sync/stage.ts
|
|
1794
|
-
import { createHash } from "crypto";
|
|
1908
|
+
import { createHash as createHash2 } from "crypto";
|
|
1795
1909
|
|
|
1796
|
-
// src/
|
|
1910
|
+
// src/harness/claude/adapter.ts
|
|
1911
|
+
import { stat as stat2 } from "fs/promises";
|
|
1912
|
+
|
|
1913
|
+
// src/harness/shared/pricing.ts
|
|
1797
1914
|
var PRICING_TABLE_VERSION = "anthropic-list-2026-07-25";
|
|
1915
|
+
var OPENAI_PRICING_TABLE_VERSION = "openai-list-2026-08-01";
|
|
1798
1916
|
var CACHE_WRITE_5M_MULTIPLIER = 1.25;
|
|
1799
1917
|
var CACHE_WRITE_1H_MULTIPLIER = 2;
|
|
1800
1918
|
var CACHE_READ_MULTIPLIER = 0.1;
|
|
@@ -1815,7 +1933,13 @@ var PRICES = {
|
|
|
1815
1933
|
// Fast mode (research preview) — Claude API only, Opus 5 / Opus 4.8 only.
|
|
1816
1934
|
// Opus 4.7 fast mode was removed, so there is deliberately no 4-7 entry.
|
|
1817
1935
|
"claude-opus-5#fast": [{ from: null, to: null, input: 10, output: 50 }],
|
|
1818
|
-
"claude-opus-4-8#fast": [{ from: null, to: null, input: 10, output: 50 }]
|
|
1936
|
+
"claude-opus-4-8#fast": [{ from: null, to: null, input: 10, output: 50 }],
|
|
1937
|
+
// OpenAI (Codex) — standard-context tier (<272K; observed context window is
|
|
1938
|
+
// 258,400). gpt-5.3-codex and the 5.6 line have NO published price yet, so
|
|
1939
|
+
// they are deliberately absent and surface as unpriced (#66 decision 6).
|
|
1940
|
+
"gpt-5.5": [{ from: null, to: null, input: 5, output: 30 }],
|
|
1941
|
+
"gpt-5.4": [{ from: null, to: null, input: 2.5, output: 15 }],
|
|
1942
|
+
"gpt-5.4-mini": [{ from: null, to: null, input: 0.75, output: 4.5 }]
|
|
1819
1943
|
};
|
|
1820
1944
|
function normalizeModel(model) {
|
|
1821
1945
|
const [base, suffix] = model.split("#");
|
|
@@ -1846,7 +1970,7 @@ function apiEquivalentCost(modelKey, t, atMs) {
|
|
|
1846
1970
|
return (t.input * p8.input + t.output * p8.output + (t.cacheWrite5m + t.cacheWriteUnsplit) * p8.input * CACHE_WRITE_5M_MULTIPLIER + t.cacheWrite1h * p8.input * CACHE_WRITE_1H_MULTIPLIER + t.cacheRead * p8.input * CACHE_READ_MULTIPLIER) / M;
|
|
1847
1971
|
}
|
|
1848
1972
|
|
|
1849
|
-
// src/
|
|
1973
|
+
// src/harness/shared/aggregate.ts
|
|
1850
1974
|
var asObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v) ? v : null;
|
|
1851
1975
|
var asStr = (v) => typeof v === "string" && v.length > 0 ? v : null;
|
|
1852
1976
|
var asNum = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
|
|
@@ -1919,243 +2043,29 @@ function emptyUsage() {
|
|
|
1919
2043
|
output: 0,
|
|
1920
2044
|
cacheWrite5m: 0,
|
|
1921
2045
|
cacheWrite1h: 0,
|
|
1922
|
-
cacheWriteUnsplit: 0,
|
|
1923
|
-
cacheRead: 0,
|
|
1924
|
-
messages: 0,
|
|
1925
|
-
costUSD: 0,
|
|
1926
|
-
unpricedTokens: 0
|
|
1927
|
-
};
|
|
1928
|
-
}
|
|
1929
|
-
var countsTotal = (t) => t.input + t.output + t.cacheWrite5m + t.cacheWrite1h + t.cacheWriteUnsplit + t.cacheRead;
|
|
1930
|
-
function ingestRecord(agg, raw, ctx) {
|
|
1931
|
-
const rec = asObj(raw);
|
|
1932
|
-
if (!rec) return;
|
|
1933
|
-
agg.records++;
|
|
1934
|
-
agg.projectDirs.add(ctx.projectDir);
|
|
1935
|
-
const version = asStr(rec.version);
|
|
1936
|
-
if (version) agg.ccVersions.add(cleanName(version));
|
|
1937
|
-
const sessionId = asStr(rec.sessionId);
|
|
1938
|
-
if (sessionId) agg.sessions.add(sessionId);
|
|
1939
|
-
let tsMs = null;
|
|
1940
|
-
const timestamp = asStr(rec.timestamp);
|
|
1941
|
-
if (timestamp) {
|
|
1942
|
-
const ts = Date.parse(timestamp);
|
|
1943
|
-
if (!Number.isNaN(ts)) {
|
|
1944
|
-
tsMs = ts;
|
|
1945
|
-
agg.activeDays.add(timestamp.slice(0, 10));
|
|
1946
|
-
agg.firstTs = agg.firstTs === null ? ts : Math.min(agg.firstTs, ts);
|
|
1947
|
-
agg.lastTs = agg.lastTs === null ? ts : Math.max(agg.lastTs, ts);
|
|
1948
|
-
}
|
|
1949
|
-
}
|
|
1950
|
-
const type = asStr(rec.type);
|
|
1951
|
-
if (type === "assistant") ingestAssistant(agg, rec, tsMs);
|
|
1952
|
-
else if (type === "user") ingestUser(agg, rec);
|
|
1953
|
-
}
|
|
1954
|
-
function ingestAssistant(agg, rec, tsMs) {
|
|
1955
|
-
agg.assistantRecords++;
|
|
1956
|
-
const msg = asObj(rec.message);
|
|
1957
|
-
if (!msg) return;
|
|
1958
|
-
const messageId = asStr(msg.id);
|
|
1959
|
-
const requestId = asStr(rec.requestId);
|
|
1960
|
-
const existing = messageId === null ? void 0 : agg.seen.get(messageId);
|
|
1961
|
-
const isReplay = existing !== void 0 && existing.requestId !== requestId;
|
|
1962
|
-
if (!isReplay) ingestContentBlocks(agg, msg.content);
|
|
1963
|
-
const usage = asObj(msg.usage);
|
|
1964
|
-
if (!usage) return;
|
|
1965
|
-
const model = asName(msg.model) ?? "(unknown)";
|
|
1966
|
-
if (model.startsWith("<")) {
|
|
1967
|
-
agg.syntheticRecords++;
|
|
1968
|
-
agg.syntheticTokens += countsTotal(readCounts(usage));
|
|
1969
|
-
return;
|
|
1970
|
-
}
|
|
1971
|
-
if (tsMs === null) agg.untimestampedResponses++;
|
|
1972
|
-
const sidechain = rec.isSidechain === true;
|
|
1973
|
-
const contribution = buildContribution(usage, model, sidechain, tsMs);
|
|
1974
|
-
if (messageId === null) {
|
|
1975
|
-
agg.unkeyedResponses++;
|
|
1976
|
-
acceptContribution(agg, contribution);
|
|
1977
|
-
return;
|
|
1978
|
-
}
|
|
1979
|
-
if (existing === void 0) {
|
|
1980
|
-
agg.distinctResponses++;
|
|
1981
|
-
acceptContribution(agg, contribution);
|
|
1982
|
-
agg.seen.set(messageId, { requestId, contribution });
|
|
1983
|
-
return;
|
|
1984
|
-
}
|
|
1985
|
-
if (isReplay) agg.realReplaysFolded++;
|
|
1986
|
-
else agg.continuationsFolded++;
|
|
1987
|
-
if (!supersedes(contribution, existing.contribution)) return;
|
|
1988
|
-
agg.supersededByLarger++;
|
|
1989
|
-
retractContribution(agg, existing.contribution);
|
|
1990
|
-
acceptContribution(agg, contribution);
|
|
1991
|
-
agg.seen.set(messageId, { requestId: existing.requestId, contribution });
|
|
1992
|
-
}
|
|
1993
|
-
function acceptContribution(agg, c) {
|
|
1994
|
-
applyContribution(agg, c, 1);
|
|
1995
|
-
}
|
|
1996
|
-
function retractContribution(agg, c) {
|
|
1997
|
-
applyContribution(agg, c, -1);
|
|
1998
|
-
}
|
|
1999
|
-
function supersedes(next, prev) {
|
|
2000
|
-
if (prev.sidechain !== next.sidechain)
|
|
2001
|
-
return prev.sidechain && !next.sidechain;
|
|
2002
|
-
return next.total > prev.total;
|
|
2003
|
-
}
|
|
2004
|
-
function readCounts(usage) {
|
|
2005
|
-
const t = {
|
|
2006
|
-
input: asNum(usage.input_tokens),
|
|
2007
|
-
output: asNum(usage.output_tokens),
|
|
2008
|
-
cacheWrite5m: 0,
|
|
2009
|
-
cacheWrite1h: 0,
|
|
2010
|
-
cacheWriteUnsplit: 0,
|
|
2011
|
-
cacheRead: asNum(usage.cache_read_input_tokens)
|
|
2012
|
-
};
|
|
2013
|
-
const cacheWriteTotal = asNum(usage.cache_creation_input_tokens);
|
|
2014
|
-
const cc = asObj(usage.cache_creation);
|
|
2015
|
-
if (cc) {
|
|
2016
|
-
t.cacheWrite5m = asNum(cc.ephemeral_5m_input_tokens);
|
|
2017
|
-
t.cacheWrite1h = asNum(cc.ephemeral_1h_input_tokens);
|
|
2018
|
-
const residual = cacheWriteTotal - (t.cacheWrite5m + t.cacheWrite1h);
|
|
2019
|
-
if (residual > 0) t.cacheWriteUnsplit = residual;
|
|
2020
|
-
} else {
|
|
2021
|
-
t.cacheWriteUnsplit = cacheWriteTotal;
|
|
2022
|
-
}
|
|
2023
|
-
return t;
|
|
2024
|
-
}
|
|
2025
|
-
function modelKeyFor(model, speed) {
|
|
2026
|
-
return normalizeModel(speed === "fast" ? `${model}#fast` : model);
|
|
2027
|
-
}
|
|
2028
|
-
function makeEntry(modelKey, counts, tsMs) {
|
|
2029
|
-
return {
|
|
2030
|
-
modelKey,
|
|
2031
|
-
counts,
|
|
2032
|
-
costUSD: apiEquivalentCost(modelKey, counts, tsMs)
|
|
2033
|
-
};
|
|
2034
|
-
}
|
|
2035
|
-
function buildContribution(usage, model, sidechain, tsMs) {
|
|
2036
|
-
const modelKey = modelKeyFor(model, asStr(usage.speed));
|
|
2037
|
-
const entries = [makeEntry(modelKey, readCounts(usage), tsMs)];
|
|
2038
|
-
const mirrored = /* @__PURE__ */ new Map();
|
|
2039
|
-
let fallbackAttempts = 0;
|
|
2040
|
-
let untypedMirrors = 0;
|
|
2041
|
-
for (const rawIt of asArr(usage.iterations)) {
|
|
2042
|
-
const it = asObj(rawIt);
|
|
2043
|
-
if (!it) continue;
|
|
2044
|
-
const itType = asName(it.type) ?? "(untyped)";
|
|
2045
|
-
const itModel = asName(it.model);
|
|
2046
|
-
const itKey = itModel === null ? null : modelKeyFor(itModel, asStr(it.speed));
|
|
2047
|
-
if (itType === "advisor_message") {
|
|
2048
|
-
entries.push(makeEntry(itKey ?? modelKey, readCounts(it), tsMs));
|
|
2049
|
-
continue;
|
|
2050
|
-
}
|
|
2051
|
-
if (itKey === null) {
|
|
2052
|
-
untypedMirrors++;
|
|
2053
|
-
bump(mirrored, itType);
|
|
2054
|
-
continue;
|
|
2055
|
-
}
|
|
2056
|
-
if (itKey === modelKey) {
|
|
2057
|
-
bump(mirrored, itType);
|
|
2058
|
-
continue;
|
|
2059
|
-
}
|
|
2060
|
-
entries.push(makeEntry(itKey, readCounts(it), tsMs));
|
|
2061
|
-
fallbackAttempts++;
|
|
2062
|
-
}
|
|
2063
|
-
const serverTools = asObj(usage.server_tool_use);
|
|
2064
|
-
return {
|
|
2065
|
-
entries,
|
|
2066
|
-
total: entries.reduce((a, e) => a + countsTotal(e.counts), 0),
|
|
2067
|
-
sidechain,
|
|
2068
|
-
webSearch: serverTools ? asNum(serverTools.web_search_requests) : 0,
|
|
2069
|
-
webFetch: serverTools ? asNum(serverTools.web_fetch_requests) : 0,
|
|
2070
|
-
mirroredIterationTypes: [...mirrored],
|
|
2071
|
-
fallbackAttempts,
|
|
2072
|
-
untypedMirrors
|
|
2073
|
-
};
|
|
2074
|
-
}
|
|
2075
|
-
function applyContribution(agg, c, sign) {
|
|
2076
|
-
c.entries.forEach(({ modelKey, counts, costUSD }, i) => {
|
|
2077
|
-
let m = agg.byModel.get(modelKey);
|
|
2078
|
-
if (!m) {
|
|
2079
|
-
m = emptyUsage();
|
|
2080
|
-
agg.byModel.set(modelKey, m);
|
|
2081
|
-
}
|
|
2082
|
-
if (i === 0) m.messages += sign;
|
|
2083
|
-
m.input += sign * counts.input;
|
|
2084
|
-
m.output += sign * counts.output;
|
|
2085
|
-
m.cacheWrite5m += sign * counts.cacheWrite5m;
|
|
2086
|
-
m.cacheWrite1h += sign * counts.cacheWrite1h;
|
|
2087
|
-
m.cacheWriteUnsplit += sign * counts.cacheWriteUnsplit;
|
|
2088
|
-
m.cacheRead += sign * counts.cacheRead;
|
|
2089
|
-
if (costUSD === null) m.unpricedTokens += sign * countsTotal(counts);
|
|
2090
|
-
else m.costUSD += sign * costUSD;
|
|
2091
|
-
});
|
|
2092
|
-
if (c.sidechain) agg.sidechainTokens += sign * c.total;
|
|
2093
|
-
else agg.mainTokens += sign * c.total;
|
|
2094
|
-
agg.webSearchRequests += sign * c.webSearch;
|
|
2095
|
-
agg.webFetchRequests += sign * c.webFetch;
|
|
2096
|
-
agg.fallbackAttempts += sign * c.fallbackAttempts;
|
|
2097
|
-
agg.untypedMirrors += sign * c.untypedMirrors;
|
|
2098
|
-
for (const [type, count] of c.mirroredIterationTypes) {
|
|
2099
|
-
bump(agg.mirroredIterationTypes, type, sign * count);
|
|
2100
|
-
}
|
|
2101
|
-
}
|
|
2102
|
-
function ingestContentBlocks(agg, content) {
|
|
2103
|
-
for (const rawBlock of asArr(content)) {
|
|
2104
|
-
const block = asObj(rawBlock);
|
|
2105
|
-
if (!block) continue;
|
|
2106
|
-
const type = asStr(block.type);
|
|
2107
|
-
if (type === "thinking") agg.thinkingBlocks++;
|
|
2108
|
-
else if (type === "text") agg.textBlocks++;
|
|
2109
|
-
else if (type === "tool_use") ingestToolUse(agg, block);
|
|
2110
|
-
}
|
|
2111
|
-
}
|
|
2112
|
-
function ingestToolUse(agg, block) {
|
|
2113
|
-
const name = asName(block.name);
|
|
2114
|
-
if (!name) return;
|
|
2115
|
-
const blockId = asStr(block.id);
|
|
2116
|
-
if (!blockId) {
|
|
2117
|
-
agg.toolBlocksWithoutId++;
|
|
2118
|
-
return;
|
|
2119
|
-
}
|
|
2120
|
-
if (agg.toolCallDedup.has(blockId)) return;
|
|
2121
|
-
agg.toolCallDedup.add(blockId);
|
|
2122
|
-
const input = asObj(block.input) ?? {};
|
|
2123
|
-
if (name.startsWith("mcp__")) {
|
|
2124
|
-
const parts = name.slice("mcp__".length).split("__");
|
|
2125
|
-
bump(agg.mcpServerCalls, parts[0] || "(unknown)");
|
|
2126
|
-
bump(agg.mcpToolCalls, name);
|
|
2127
|
-
return;
|
|
2128
|
-
}
|
|
2129
|
-
if (name === "Skill") {
|
|
2130
|
-
bump(agg.skillCalls, asName(input.skill) ?? "(unnamed)");
|
|
2131
|
-
bump(agg.toolCalls, "Skill");
|
|
2132
|
-
return;
|
|
2133
|
-
}
|
|
2134
|
-
if (name === "Agent" || name === "Task") {
|
|
2135
|
-
bump(agg.subagentCalls, asName(input.subagent_type) ?? "(default)");
|
|
2136
|
-
bump(agg.toolCalls, "Agent");
|
|
2137
|
-
return;
|
|
2138
|
-
}
|
|
2139
|
-
bump(agg.toolCalls, name);
|
|
2046
|
+
cacheWriteUnsplit: 0,
|
|
2047
|
+
cacheRead: 0,
|
|
2048
|
+
messages: 0,
|
|
2049
|
+
costUSD: 0,
|
|
2050
|
+
unpricedTokens: 0
|
|
2051
|
+
};
|
|
2140
2052
|
}
|
|
2141
|
-
var
|
|
2142
|
-
function
|
|
2143
|
-
|
|
2144
|
-
if (!
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
if (
|
|
2156
|
-
|
|
2157
|
-
bump(agg.slashCommands, cleanName(match[1]));
|
|
2158
|
-
}
|
|
2053
|
+
var countsTotal = (t) => t.input + t.output + t.cacheWrite5m + t.cacheWrite1h + t.cacheWriteUnsplit + t.cacheRead;
|
|
2054
|
+
function addModelUsage(agg, modelKey, counts, costUSD, messages = 1) {
|
|
2055
|
+
let m = agg.byModel.get(modelKey);
|
|
2056
|
+
if (!m) {
|
|
2057
|
+
m = emptyUsage();
|
|
2058
|
+
agg.byModel.set(modelKey, m);
|
|
2059
|
+
}
|
|
2060
|
+
m.messages += messages;
|
|
2061
|
+
m.input += counts.input;
|
|
2062
|
+
m.output += counts.output;
|
|
2063
|
+
m.cacheWrite5m += counts.cacheWrite5m;
|
|
2064
|
+
m.cacheWrite1h += counts.cacheWrite1h;
|
|
2065
|
+
m.cacheWriteUnsplit += counts.cacheWriteUnsplit;
|
|
2066
|
+
m.cacheRead += counts.cacheRead;
|
|
2067
|
+
if (costUSD === null) m.unpricedTokens += countsTotal(counts);
|
|
2068
|
+
else m.costUSD += costUSD;
|
|
2159
2069
|
}
|
|
2160
2070
|
function buildModelRows(agg) {
|
|
2161
2071
|
const rows = [];
|
|
@@ -2257,7 +2167,7 @@ function finalize(agg) {
|
|
|
2257
2167
|
};
|
|
2258
2168
|
}
|
|
2259
2169
|
|
|
2260
|
-
// src/
|
|
2170
|
+
// src/harness/shared/bundled-allowlist.ts
|
|
2261
2171
|
var BUILTIN_SUBAGENTS = [
|
|
2262
2172
|
"(default)",
|
|
2263
2173
|
"claude",
|
|
@@ -2358,7 +2268,7 @@ var BUNDLED_CURATED_ALLOWLIST = {
|
|
|
2358
2268
|
slashCommands: BUILTIN_SLASH_COMMANDS
|
|
2359
2269
|
};
|
|
2360
2270
|
|
|
2361
|
-
// src/
|
|
2271
|
+
// src/harness/shared/allowlist.ts
|
|
2362
2272
|
var BUILTIN_TOOLS = /* @__PURE__ */ new Set([
|
|
2363
2273
|
"Agent",
|
|
2364
2274
|
"Artifact",
|
|
@@ -2547,43 +2457,271 @@ function publishedName(name, sets) {
|
|
|
2547
2457
|
if (inner && sets.curated.has(inner)) return inner;
|
|
2548
2458
|
return null;
|
|
2549
2459
|
}
|
|
2550
|
-
function filterAtoms(atoms, sets) {
|
|
2551
|
-
const merged = /* @__PURE__ */ new Map();
|
|
2552
|
-
const keptPrivate = [];
|
|
2553
|
-
for (const atom of atoms) {
|
|
2554
|
-
const published = publishedName(atom.name, sets);
|
|
2555
|
-
if (published === null) {
|
|
2556
|
-
keptPrivate.push({
|
|
2557
|
-
name: atom.name,
|
|
2558
|
-
count: atom.count,
|
|
2559
|
-
group: pluginGroup(atom.name)
|
|
2560
|
-
});
|
|
2561
|
-
continue;
|
|
2460
|
+
function filterAtoms(atoms, sets) {
|
|
2461
|
+
const merged = /* @__PURE__ */ new Map();
|
|
2462
|
+
const keptPrivate = [];
|
|
2463
|
+
for (const atom of atoms) {
|
|
2464
|
+
const published = publishedName(atom.name, sets);
|
|
2465
|
+
if (published === null) {
|
|
2466
|
+
keptPrivate.push({
|
|
2467
|
+
name: atom.name,
|
|
2468
|
+
count: atom.count,
|
|
2469
|
+
group: pluginGroup(atom.name)
|
|
2470
|
+
});
|
|
2471
|
+
continue;
|
|
2472
|
+
}
|
|
2473
|
+
merged.set(published, (merged.get(published) ?? 0) + atom.count);
|
|
2474
|
+
}
|
|
2475
|
+
const allowed = [...merged].map(([name, count]) => ({ name, count }));
|
|
2476
|
+
allowed.sort((a, b) => b.count - a.count || a.name.localeCompare(b.name));
|
|
2477
|
+
keptPrivate.sort((a, b) => b.count - a.count || a.name.localeCompare(b.name));
|
|
2478
|
+
return { allowed, keptPrivate, withheld: keptPrivate.length };
|
|
2479
|
+
}
|
|
2480
|
+
|
|
2481
|
+
// src/harness/claude/analyzer.ts
|
|
2482
|
+
function createAggregate2() {
|
|
2483
|
+
return createAggregate();
|
|
2484
|
+
}
|
|
2485
|
+
function ingestRecord(agg, raw, ctx) {
|
|
2486
|
+
const rec = asObj(raw);
|
|
2487
|
+
if (!rec) return;
|
|
2488
|
+
agg.records++;
|
|
2489
|
+
agg.projectDirs.add(ctx.projectDir);
|
|
2490
|
+
const version = asStr(rec.version);
|
|
2491
|
+
if (version) agg.ccVersions.add(cleanName(version));
|
|
2492
|
+
const sessionId = asStr(rec.sessionId);
|
|
2493
|
+
if (sessionId) agg.sessions.add(sessionId);
|
|
2494
|
+
let tsMs = null;
|
|
2495
|
+
const timestamp = asStr(rec.timestamp);
|
|
2496
|
+
if (timestamp) {
|
|
2497
|
+
const ts = Date.parse(timestamp);
|
|
2498
|
+
if (!Number.isNaN(ts)) {
|
|
2499
|
+
tsMs = ts;
|
|
2500
|
+
agg.activeDays.add(timestamp.slice(0, 10));
|
|
2501
|
+
agg.firstTs = agg.firstTs === null ? ts : Math.min(agg.firstTs, ts);
|
|
2502
|
+
agg.lastTs = agg.lastTs === null ? ts : Math.max(agg.lastTs, ts);
|
|
2503
|
+
}
|
|
2504
|
+
}
|
|
2505
|
+
const type = asStr(rec.type);
|
|
2506
|
+
if (type === "assistant") ingestAssistant(agg, rec, tsMs);
|
|
2507
|
+
else if (type === "user") ingestUser(agg, rec);
|
|
2508
|
+
}
|
|
2509
|
+
function ingestAssistant(agg, rec, tsMs) {
|
|
2510
|
+
agg.assistantRecords++;
|
|
2511
|
+
const msg = asObj(rec.message);
|
|
2512
|
+
if (!msg) return;
|
|
2513
|
+
const messageId = asStr(msg.id);
|
|
2514
|
+
const requestId = asStr(rec.requestId);
|
|
2515
|
+
const existing = messageId === null ? void 0 : agg.seen.get(messageId);
|
|
2516
|
+
const isReplay = existing !== void 0 && existing.requestId !== requestId;
|
|
2517
|
+
if (!isReplay) ingestContentBlocks(agg, msg.content);
|
|
2518
|
+
const usage = asObj(msg.usage);
|
|
2519
|
+
if (!usage) return;
|
|
2520
|
+
const model = asName(msg.model) ?? "(unknown)";
|
|
2521
|
+
if (model.startsWith("<")) {
|
|
2522
|
+
agg.syntheticRecords++;
|
|
2523
|
+
agg.syntheticTokens += countsTotal(readCounts(usage));
|
|
2524
|
+
return;
|
|
2525
|
+
}
|
|
2526
|
+
if (tsMs === null) agg.untimestampedResponses++;
|
|
2527
|
+
const sidechain = rec.isSidechain === true;
|
|
2528
|
+
const contribution = buildContribution(usage, model, sidechain, tsMs);
|
|
2529
|
+
if (messageId === null) {
|
|
2530
|
+
agg.unkeyedResponses++;
|
|
2531
|
+
acceptContribution(agg, contribution);
|
|
2532
|
+
return;
|
|
2533
|
+
}
|
|
2534
|
+
if (existing === void 0) {
|
|
2535
|
+
agg.distinctResponses++;
|
|
2536
|
+
acceptContribution(agg, contribution);
|
|
2537
|
+
agg.seen.set(messageId, { requestId, contribution });
|
|
2538
|
+
return;
|
|
2539
|
+
}
|
|
2540
|
+
if (isReplay) agg.realReplaysFolded++;
|
|
2541
|
+
else agg.continuationsFolded++;
|
|
2542
|
+
if (!supersedes(contribution, existing.contribution)) return;
|
|
2543
|
+
agg.supersededByLarger++;
|
|
2544
|
+
retractContribution(agg, existing.contribution);
|
|
2545
|
+
acceptContribution(agg, contribution);
|
|
2546
|
+
agg.seen.set(messageId, { requestId: existing.requestId, contribution });
|
|
2547
|
+
}
|
|
2548
|
+
function acceptContribution(agg, c) {
|
|
2549
|
+
applyContribution(agg, c, 1);
|
|
2550
|
+
}
|
|
2551
|
+
function retractContribution(agg, c) {
|
|
2552
|
+
applyContribution(agg, c, -1);
|
|
2553
|
+
}
|
|
2554
|
+
function supersedes(next, prev) {
|
|
2555
|
+
if (prev.sidechain !== next.sidechain)
|
|
2556
|
+
return prev.sidechain && !next.sidechain;
|
|
2557
|
+
return next.total > prev.total;
|
|
2558
|
+
}
|
|
2559
|
+
function readCounts(usage) {
|
|
2560
|
+
const t = {
|
|
2561
|
+
input: asNum(usage.input_tokens),
|
|
2562
|
+
output: asNum(usage.output_tokens),
|
|
2563
|
+
cacheWrite5m: 0,
|
|
2564
|
+
cacheWrite1h: 0,
|
|
2565
|
+
cacheWriteUnsplit: 0,
|
|
2566
|
+
cacheRead: asNum(usage.cache_read_input_tokens)
|
|
2567
|
+
};
|
|
2568
|
+
const cacheWriteTotal = asNum(usage.cache_creation_input_tokens);
|
|
2569
|
+
const cc = asObj(usage.cache_creation);
|
|
2570
|
+
if (cc) {
|
|
2571
|
+
t.cacheWrite5m = asNum(cc.ephemeral_5m_input_tokens);
|
|
2572
|
+
t.cacheWrite1h = asNum(cc.ephemeral_1h_input_tokens);
|
|
2573
|
+
const residual = cacheWriteTotal - (t.cacheWrite5m + t.cacheWrite1h);
|
|
2574
|
+
if (residual > 0) t.cacheWriteUnsplit = residual;
|
|
2575
|
+
} else {
|
|
2576
|
+
t.cacheWriteUnsplit = cacheWriteTotal;
|
|
2577
|
+
}
|
|
2578
|
+
return t;
|
|
2579
|
+
}
|
|
2580
|
+
function modelKeyFor(model, speed) {
|
|
2581
|
+
return normalizeModel(speed === "fast" ? `${model}#fast` : model);
|
|
2582
|
+
}
|
|
2583
|
+
function makeEntry(modelKey, counts, tsMs) {
|
|
2584
|
+
return {
|
|
2585
|
+
modelKey,
|
|
2586
|
+
counts,
|
|
2587
|
+
costUSD: apiEquivalentCost(modelKey, counts, tsMs)
|
|
2588
|
+
};
|
|
2589
|
+
}
|
|
2590
|
+
function buildContribution(usage, model, sidechain, tsMs) {
|
|
2591
|
+
const modelKey = modelKeyFor(model, asStr(usage.speed));
|
|
2592
|
+
const entries = [makeEntry(modelKey, readCounts(usage), tsMs)];
|
|
2593
|
+
const mirrored = /* @__PURE__ */ new Map();
|
|
2594
|
+
let fallbackAttempts = 0;
|
|
2595
|
+
let untypedMirrors = 0;
|
|
2596
|
+
for (const rawIt of asArr(usage.iterations)) {
|
|
2597
|
+
const it = asObj(rawIt);
|
|
2598
|
+
if (!it) continue;
|
|
2599
|
+
const itType = asName(it.type) ?? "(untyped)";
|
|
2600
|
+
const itModel = asName(it.model);
|
|
2601
|
+
const itKey = itModel === null ? null : modelKeyFor(itModel, asStr(it.speed));
|
|
2602
|
+
if (itType === "advisor_message") {
|
|
2603
|
+
entries.push(makeEntry(itKey ?? modelKey, readCounts(it), tsMs));
|
|
2604
|
+
continue;
|
|
2605
|
+
}
|
|
2606
|
+
if (itKey === null) {
|
|
2607
|
+
untypedMirrors++;
|
|
2608
|
+
bump(mirrored, itType);
|
|
2609
|
+
continue;
|
|
2610
|
+
}
|
|
2611
|
+
if (itKey === modelKey) {
|
|
2612
|
+
bump(mirrored, itType);
|
|
2613
|
+
continue;
|
|
2614
|
+
}
|
|
2615
|
+
entries.push(makeEntry(itKey, readCounts(it), tsMs));
|
|
2616
|
+
fallbackAttempts++;
|
|
2617
|
+
}
|
|
2618
|
+
const serverTools = asObj(usage.server_tool_use);
|
|
2619
|
+
return {
|
|
2620
|
+
entries,
|
|
2621
|
+
total: entries.reduce((a, e) => a + countsTotal(e.counts), 0),
|
|
2622
|
+
sidechain,
|
|
2623
|
+
webSearch: serverTools ? asNum(serverTools.web_search_requests) : 0,
|
|
2624
|
+
webFetch: serverTools ? asNum(serverTools.web_fetch_requests) : 0,
|
|
2625
|
+
mirroredIterationTypes: [...mirrored],
|
|
2626
|
+
fallbackAttempts,
|
|
2627
|
+
untypedMirrors
|
|
2628
|
+
};
|
|
2629
|
+
}
|
|
2630
|
+
function applyContribution(agg, c, sign) {
|
|
2631
|
+
c.entries.forEach(({ modelKey, counts, costUSD }, i) => {
|
|
2632
|
+
let m = agg.byModel.get(modelKey);
|
|
2633
|
+
if (!m) {
|
|
2634
|
+
m = emptyUsage();
|
|
2635
|
+
agg.byModel.set(modelKey, m);
|
|
2636
|
+
}
|
|
2637
|
+
if (i === 0) m.messages += sign;
|
|
2638
|
+
m.input += sign * counts.input;
|
|
2639
|
+
m.output += sign * counts.output;
|
|
2640
|
+
m.cacheWrite5m += sign * counts.cacheWrite5m;
|
|
2641
|
+
m.cacheWrite1h += sign * counts.cacheWrite1h;
|
|
2642
|
+
m.cacheWriteUnsplit += sign * counts.cacheWriteUnsplit;
|
|
2643
|
+
m.cacheRead += sign * counts.cacheRead;
|
|
2644
|
+
if (costUSD === null) m.unpricedTokens += sign * countsTotal(counts);
|
|
2645
|
+
else m.costUSD += sign * costUSD;
|
|
2646
|
+
});
|
|
2647
|
+
if (c.sidechain) agg.sidechainTokens += sign * c.total;
|
|
2648
|
+
else agg.mainTokens += sign * c.total;
|
|
2649
|
+
agg.webSearchRequests += sign * c.webSearch;
|
|
2650
|
+
agg.webFetchRequests += sign * c.webFetch;
|
|
2651
|
+
agg.fallbackAttempts += sign * c.fallbackAttempts;
|
|
2652
|
+
agg.untypedMirrors += sign * c.untypedMirrors;
|
|
2653
|
+
for (const [type, count] of c.mirroredIterationTypes) {
|
|
2654
|
+
bump(agg.mirroredIterationTypes, type, sign * count);
|
|
2655
|
+
}
|
|
2656
|
+
}
|
|
2657
|
+
function ingestContentBlocks(agg, content) {
|
|
2658
|
+
for (const rawBlock of asArr(content)) {
|
|
2659
|
+
const block = asObj(rawBlock);
|
|
2660
|
+
if (!block) continue;
|
|
2661
|
+
const type = asStr(block.type);
|
|
2662
|
+
if (type === "thinking") agg.thinkingBlocks++;
|
|
2663
|
+
else if (type === "text") agg.textBlocks++;
|
|
2664
|
+
else if (type === "tool_use") ingestToolUse(agg, block);
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
function ingestToolUse(agg, block) {
|
|
2668
|
+
const name = asName(block.name);
|
|
2669
|
+
if (!name) return;
|
|
2670
|
+
const blockId = asStr(block.id);
|
|
2671
|
+
if (!blockId) {
|
|
2672
|
+
agg.toolBlocksWithoutId++;
|
|
2673
|
+
return;
|
|
2674
|
+
}
|
|
2675
|
+
if (agg.toolCallDedup.has(blockId)) return;
|
|
2676
|
+
agg.toolCallDedup.add(blockId);
|
|
2677
|
+
const input = asObj(block.input) ?? {};
|
|
2678
|
+
if (name.startsWith("mcp__")) {
|
|
2679
|
+
const parts = name.slice("mcp__".length).split("__");
|
|
2680
|
+
bump(agg.mcpServerCalls, parts[0] || "(unknown)");
|
|
2681
|
+
bump(agg.mcpToolCalls, name);
|
|
2682
|
+
return;
|
|
2683
|
+
}
|
|
2684
|
+
if (name === "Skill") {
|
|
2685
|
+
bump(agg.skillCalls, asName(input.skill) ?? "(unnamed)");
|
|
2686
|
+
bump(agg.toolCalls, "Skill");
|
|
2687
|
+
return;
|
|
2688
|
+
}
|
|
2689
|
+
if (name === "Agent" || name === "Task") {
|
|
2690
|
+
bump(agg.subagentCalls, asName(input.subagent_type) ?? "(default)");
|
|
2691
|
+
bump(agg.toolCalls, "Agent");
|
|
2692
|
+
return;
|
|
2693
|
+
}
|
|
2694
|
+
bump(agg.toolCalls, name);
|
|
2695
|
+
}
|
|
2696
|
+
var SLASH_RE = /<command-name>\/?([^<\n\r]{1,64})<\/command-name>/g;
|
|
2697
|
+
function ingestUser(agg, rec) {
|
|
2698
|
+
const msg = asObj(rec.message);
|
|
2699
|
+
if (!msg) return;
|
|
2700
|
+
const content = msg.content;
|
|
2701
|
+
let text = "";
|
|
2702
|
+
if (typeof content === "string") text = content;
|
|
2703
|
+
else {
|
|
2704
|
+
for (const rawBlock of asArr(content)) {
|
|
2705
|
+
const block = asObj(rawBlock);
|
|
2706
|
+
if (!block) continue;
|
|
2707
|
+
if (asStr(block.type) === "text") text += asStr(block.text) ?? "";
|
|
2562
2708
|
}
|
|
2563
|
-
merged.set(published, (merged.get(published) ?? 0) + atom.count);
|
|
2564
2709
|
}
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2710
|
+
if (!text.includes("<command-name>")) return;
|
|
2711
|
+
for (const match of text.matchAll(SLASH_RE)) {
|
|
2712
|
+
bump(agg.slashCommands, cleanName(match[1]));
|
|
2713
|
+
}
|
|
2569
2714
|
}
|
|
2570
2715
|
|
|
2571
|
-
// src/
|
|
2716
|
+
// src/harness/claude/scan.ts
|
|
2572
2717
|
import { createReadStream } from "fs";
|
|
2573
2718
|
import { readdir, realpath, stat } from "fs/promises";
|
|
2574
|
-
import { homedir as
|
|
2719
|
+
import { homedir as homedir9 } from "os";
|
|
2575
2720
|
import path from "path";
|
|
2576
2721
|
import readline from "readline";
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
return env.split(",").map((s) => s.trim()).filter(Boolean).map((s) => path.join(s, "projects"));
|
|
2581
|
-
}
|
|
2582
|
-
const roots = [path.join(homedir8(), ".claude", "projects")];
|
|
2583
|
-
const xdg = process.env.XDG_CONFIG_HOME ?? path.join(homedir8(), ".config");
|
|
2584
|
-
roots.push(path.join(xdg, "claude", "projects"));
|
|
2585
|
-
return roots;
|
|
2586
|
-
}
|
|
2722
|
+
|
|
2723
|
+
// src/harness/shared/window.ts
|
|
2724
|
+
var DEFAULT_WINDOW_DAYS = 30;
|
|
2587
2725
|
function windowStartMs(now, days) {
|
|
2588
2726
|
const startOfToday = Date.UTC(
|
|
2589
2727
|
new Date(now).getUTCFullYear(),
|
|
@@ -2592,6 +2730,27 @@ function windowStartMs(now, days) {
|
|
|
2592
2730
|
);
|
|
2593
2731
|
return startOfToday - (days - 1) * 864e5;
|
|
2594
2732
|
}
|
|
2733
|
+
function emptyScanStats() {
|
|
2734
|
+
return {
|
|
2735
|
+
filesFound: 0,
|
|
2736
|
+
filesRead: 0,
|
|
2737
|
+
filesSkippedByMtime: 0,
|
|
2738
|
+
filesSkippedAsDuplicate: 0,
|
|
2739
|
+
filesUnreadable: 0
|
|
2740
|
+
};
|
|
2741
|
+
}
|
|
2742
|
+
|
|
2743
|
+
// src/harness/claude/scan.ts
|
|
2744
|
+
function transcriptRoots() {
|
|
2745
|
+
const env = process.env.CLAUDE_CONFIG_DIR;
|
|
2746
|
+
if (env) {
|
|
2747
|
+
return env.split(",").map((s) => s.trim()).filter(Boolean).map((s) => path.join(s, "projects"));
|
|
2748
|
+
}
|
|
2749
|
+
const roots = [path.join(homedir9(), ".claude", "projects")];
|
|
2750
|
+
const xdg = process.env.XDG_CONFIG_HOME ?? path.join(homedir9(), ".config");
|
|
2751
|
+
roots.push(path.join(xdg, "claude", "projects"));
|
|
2752
|
+
return roots;
|
|
2753
|
+
}
|
|
2595
2754
|
async function* walkJsonl(dir) {
|
|
2596
2755
|
let entries;
|
|
2597
2756
|
try {
|
|
@@ -2606,13 +2765,7 @@ async function* walkJsonl(dir) {
|
|
|
2606
2765
|
}
|
|
2607
2766
|
}
|
|
2608
2767
|
async function scan(agg, opts = {}) {
|
|
2609
|
-
const stats =
|
|
2610
|
-
filesFound: 0,
|
|
2611
|
-
filesRead: 0,
|
|
2612
|
-
filesSkippedByMtime: 0,
|
|
2613
|
-
filesSkippedAsDuplicate: 0,
|
|
2614
|
-
filesUnreadable: 0
|
|
2615
|
-
};
|
|
2768
|
+
const stats = emptyScanStats();
|
|
2616
2769
|
const visited = /* @__PURE__ */ new Set();
|
|
2617
2770
|
for (const root of opts.roots ?? transcriptRoots()) {
|
|
2618
2771
|
if (!await exists(root)) continue;
|
|
@@ -2685,9 +2838,321 @@ async function ingestFile(agg, file, projectDir, sinceMs) {
|
|
|
2685
2838
|
}
|
|
2686
2839
|
}
|
|
2687
2840
|
|
|
2688
|
-
// src/
|
|
2841
|
+
// src/harness/claude/adapter.ts
|
|
2842
|
+
var CLAUDE_HARNESS_NAME = "claude-code";
|
|
2843
|
+
async function exists2(p8) {
|
|
2844
|
+
try {
|
|
2845
|
+
await stat2(p8);
|
|
2846
|
+
return true;
|
|
2847
|
+
} catch {
|
|
2848
|
+
return false;
|
|
2849
|
+
}
|
|
2850
|
+
}
|
|
2851
|
+
var claudeAdapter = {
|
|
2852
|
+
name: CLAUDE_HARNESS_NAME,
|
|
2853
|
+
builtinTools: BUILTIN_TOOLS,
|
|
2854
|
+
pricingTableVersion: PRICING_TABLE_VERSION,
|
|
2855
|
+
async detect() {
|
|
2856
|
+
for (const root of transcriptRoots()) {
|
|
2857
|
+
if (await exists2(root)) return true;
|
|
2858
|
+
}
|
|
2859
|
+
return false;
|
|
2860
|
+
},
|
|
2861
|
+
async scan(opts) {
|
|
2862
|
+
const aggregate = createAggregate2();
|
|
2863
|
+
const stats = await scan(aggregate, {
|
|
2864
|
+
sinceMs: opts.sinceMs,
|
|
2865
|
+
...opts.onProgress ? { onProgress: opts.onProgress } : {}
|
|
2866
|
+
});
|
|
2867
|
+
return { aggregate, stats };
|
|
2868
|
+
}
|
|
2869
|
+
};
|
|
2870
|
+
|
|
2871
|
+
// src/harness/codex/adapter.ts
|
|
2872
|
+
import { stat as stat4 } from "fs/promises";
|
|
2873
|
+
|
|
2874
|
+
// src/harness/codex/analyzer.ts
|
|
2875
|
+
function createAggregate3() {
|
|
2876
|
+
return createAggregate();
|
|
2877
|
+
}
|
|
2878
|
+
function createFileState() {
|
|
2879
|
+
return {
|
|
2880
|
+
sessionId: null,
|
|
2881
|
+
cliVersion: null,
|
|
2882
|
+
cwd: null,
|
|
2883
|
+
modelKey: null,
|
|
2884
|
+
counted: false
|
|
2885
|
+
};
|
|
2886
|
+
}
|
|
2887
|
+
function ingestLine(agg, raw, state, sinceMs) {
|
|
2888
|
+
const rec = asObj(raw);
|
|
2889
|
+
if (!rec) return;
|
|
2890
|
+
agg.records++;
|
|
2891
|
+
let tsMs = null;
|
|
2892
|
+
const timestamp = asStr(rec.timestamp);
|
|
2893
|
+
if (timestamp) {
|
|
2894
|
+
const ts = Date.parse(timestamp);
|
|
2895
|
+
if (!Number.isNaN(ts)) tsMs = ts;
|
|
2896
|
+
}
|
|
2897
|
+
const inWindow = sinceMs === void 0 || tsMs !== null && tsMs >= sinceMs;
|
|
2898
|
+
const type = asStr(rec.type);
|
|
2899
|
+
const payload = asObj(rec.payload);
|
|
2900
|
+
if (type === "session_meta" && payload) {
|
|
2901
|
+
state.sessionId = asStr(payload.id) ?? asStr(payload.session_id) ?? state.sessionId;
|
|
2902
|
+
state.cliVersion = asStr(payload.cli_version) ?? state.cliVersion;
|
|
2903
|
+
state.cwd = asStr(payload.cwd) ?? state.cwd;
|
|
2904
|
+
} else if (type === "turn_context" && payload) {
|
|
2905
|
+
const model = asName(payload.model);
|
|
2906
|
+
if (model) state.modelKey = normalizeModel(model);
|
|
2907
|
+
}
|
|
2908
|
+
if (!inWindow) return;
|
|
2909
|
+
if (tsMs !== null && timestamp) {
|
|
2910
|
+
agg.activeDays.add(timestamp.slice(0, 10));
|
|
2911
|
+
agg.firstTs = agg.firstTs === null ? tsMs : Math.min(agg.firstTs, tsMs);
|
|
2912
|
+
agg.lastTs = agg.lastTs === null ? tsMs : Math.max(agg.lastTs, tsMs);
|
|
2913
|
+
}
|
|
2914
|
+
noteActivity(agg, state);
|
|
2915
|
+
if (type === "event_msg" && payload) ingestEvent(agg, payload, state, tsMs);
|
|
2916
|
+
else if (type === "response_item" && payload) ingestItem(agg, payload);
|
|
2917
|
+
}
|
|
2918
|
+
function noteActivity(agg, state) {
|
|
2919
|
+
if (state.counted) return;
|
|
2920
|
+
state.counted = true;
|
|
2921
|
+
if (state.sessionId) agg.sessions.add(state.sessionId);
|
|
2922
|
+
if (state.cliVersion) agg.ccVersions.add(cleanName(state.cliVersion));
|
|
2923
|
+
agg.projectDirs.add(state.cwd ?? "(unknown)");
|
|
2924
|
+
}
|
|
2925
|
+
function ingestEvent(agg, payload, state, tsMs) {
|
|
2926
|
+
if (asStr(payload.type) !== "token_count") return;
|
|
2927
|
+
const info = asObj(payload.info);
|
|
2928
|
+
const last = info ? asObj(info.last_token_usage) : null;
|
|
2929
|
+
if (!last) return;
|
|
2930
|
+
const inputTotal = asNum(last.input_tokens);
|
|
2931
|
+
const cached = Math.min(asNum(last.cached_input_tokens), inputTotal);
|
|
2932
|
+
const counts = {
|
|
2933
|
+
input: inputTotal - cached,
|
|
2934
|
+
output: asNum(last.output_tokens),
|
|
2935
|
+
cacheWrite5m: 0,
|
|
2936
|
+
cacheWrite1h: 0,
|
|
2937
|
+
cacheWriteUnsplit: 0,
|
|
2938
|
+
cacheRead: cached
|
|
2939
|
+
};
|
|
2940
|
+
const total = countsTotal(counts);
|
|
2941
|
+
if (total === 0) return;
|
|
2942
|
+
if (tsMs === null) agg.untimestampedResponses++;
|
|
2943
|
+
agg.distinctResponses++;
|
|
2944
|
+
const modelKey = state.modelKey ?? "(unknown)";
|
|
2945
|
+
addModelUsage(
|
|
2946
|
+
agg,
|
|
2947
|
+
modelKey,
|
|
2948
|
+
counts,
|
|
2949
|
+
apiEquivalentCost(modelKey, counts, tsMs)
|
|
2950
|
+
);
|
|
2951
|
+
agg.mainTokens += total;
|
|
2952
|
+
}
|
|
2953
|
+
function ingestCall(agg, name, callId) {
|
|
2954
|
+
if (callId) {
|
|
2955
|
+
if (agg.toolCallDedup.has(callId)) return;
|
|
2956
|
+
agg.toolCallDedup.add(callId);
|
|
2957
|
+
}
|
|
2958
|
+
const sep = name.indexOf("__");
|
|
2959
|
+
if (sep > 0) {
|
|
2960
|
+
bump(agg.mcpServerCalls, cleanName(name.slice(0, sep)));
|
|
2961
|
+
bump(agg.mcpToolCalls, cleanName(name));
|
|
2962
|
+
return;
|
|
2963
|
+
}
|
|
2964
|
+
bump(agg.toolCalls, cleanName(name));
|
|
2965
|
+
}
|
|
2966
|
+
function ingestItem(agg, payload) {
|
|
2967
|
+
const type = asStr(payload.type);
|
|
2968
|
+
if (type === "function_call" || type === "custom_tool_call") {
|
|
2969
|
+
const name = asName(payload.name);
|
|
2970
|
+
if (!name) return;
|
|
2971
|
+
ingestCall(agg, name, asStr(payload.call_id) ?? asStr(payload.id));
|
|
2972
|
+
return;
|
|
2973
|
+
}
|
|
2974
|
+
if (type === "local_shell_call") {
|
|
2975
|
+
ingestCall(agg, "local_shell", asStr(payload.call_id) ?? asStr(payload.id));
|
|
2976
|
+
} else if (type === "web_search_call") {
|
|
2977
|
+
agg.webSearchRequests++;
|
|
2978
|
+
ingestCall(agg, "web_search", asStr(payload.id));
|
|
2979
|
+
} else if (type === "tool_search_call") {
|
|
2980
|
+
ingestCall(agg, "tool_search", asStr(payload.id));
|
|
2981
|
+
}
|
|
2982
|
+
}
|
|
2983
|
+
function noteConfiguredMcpServers(agg, serverNames) {
|
|
2984
|
+
for (const raw of serverNames) {
|
|
2985
|
+
const name = cleanName(raw);
|
|
2986
|
+
if (!agg.mcpServerCalls.has(name)) agg.mcpServerCalls.set(name, 0);
|
|
2987
|
+
}
|
|
2988
|
+
}
|
|
2989
|
+
|
|
2990
|
+
// src/harness/codex/scan.ts
|
|
2991
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
2992
|
+
import { readdir as readdir2, realpath as realpath2, stat as stat3 } from "fs/promises";
|
|
2993
|
+
import { homedir as homedir10 } from "os";
|
|
2994
|
+
import path2 from "path";
|
|
2995
|
+
import * as zlib from "zlib";
|
|
2996
|
+
import { parse as parseToml2 } from "smol-toml";
|
|
2997
|
+
function codexHome2() {
|
|
2998
|
+
return process.env.CODEX_HOME || path2.join(homedir10(), ".codex");
|
|
2999
|
+
}
|
|
3000
|
+
function rolloutRoots() {
|
|
3001
|
+
return [path2.join(codexHome2(), "sessions")];
|
|
3002
|
+
}
|
|
3003
|
+
var ROLLOUT_RE = /^rollout-.*\.jsonl(\.zst)?$/;
|
|
3004
|
+
async function* walkRollouts(dir) {
|
|
3005
|
+
let entries;
|
|
3006
|
+
try {
|
|
3007
|
+
entries = await readdir2(dir, { withFileTypes: true });
|
|
3008
|
+
} catch {
|
|
3009
|
+
return;
|
|
3010
|
+
}
|
|
3011
|
+
for (const e of entries) {
|
|
3012
|
+
const full = path2.join(dir, e.name);
|
|
3013
|
+
if (e.isDirectory()) yield* walkRollouts(full);
|
|
3014
|
+
else if (e.isFile() && ROLLOUT_RE.test(e.name)) yield full;
|
|
3015
|
+
}
|
|
3016
|
+
}
|
|
3017
|
+
var zstdDecompress = typeof zlib.zstdDecompressSync === "function" ? (buf) => zlib.zstdDecompressSync(buf) : null;
|
|
3018
|
+
async function scan2(agg, opts = {}) {
|
|
3019
|
+
const stats = emptyScanStats();
|
|
3020
|
+
const visited = /* @__PURE__ */ new Set();
|
|
3021
|
+
for (const root of opts.roots ?? rolloutRoots()) {
|
|
3022
|
+
if (!await exists3(root)) continue;
|
|
3023
|
+
for await (const file of walkRollouts(root)) {
|
|
3024
|
+
stats.filesFound++;
|
|
3025
|
+
let resolved;
|
|
3026
|
+
try {
|
|
3027
|
+
resolved = await realpath2(file);
|
|
3028
|
+
} catch {
|
|
3029
|
+
resolved = file;
|
|
3030
|
+
}
|
|
3031
|
+
if (visited.has(resolved)) {
|
|
3032
|
+
stats.filesSkippedAsDuplicate++;
|
|
3033
|
+
continue;
|
|
3034
|
+
}
|
|
3035
|
+
visited.add(resolved);
|
|
3036
|
+
if (opts.sinceMs !== void 0) {
|
|
3037
|
+
try {
|
|
3038
|
+
const st = await stat3(file);
|
|
3039
|
+
if (st.mtimeMs < opts.sinceMs) {
|
|
3040
|
+
stats.filesSkippedByMtime++;
|
|
3041
|
+
continue;
|
|
3042
|
+
}
|
|
3043
|
+
} catch {
|
|
3044
|
+
}
|
|
3045
|
+
}
|
|
3046
|
+
agg.files++;
|
|
3047
|
+
stats.filesRead++;
|
|
3048
|
+
if (opts.onProgress && agg.files % 200 === 0) opts.onProgress(agg.files);
|
|
3049
|
+
try {
|
|
3050
|
+
ingestFile2(agg, file, opts.sinceMs);
|
|
3051
|
+
} catch {
|
|
3052
|
+
stats.filesUnreadable++;
|
|
3053
|
+
stats.filesRead--;
|
|
3054
|
+
}
|
|
3055
|
+
}
|
|
3056
|
+
}
|
|
3057
|
+
readConfiguredMcpServers(agg, opts.configFile);
|
|
3058
|
+
return stats;
|
|
3059
|
+
}
|
|
3060
|
+
async function exists3(p8) {
|
|
3061
|
+
try {
|
|
3062
|
+
await stat3(p8);
|
|
3063
|
+
return true;
|
|
3064
|
+
} catch {
|
|
3065
|
+
return false;
|
|
3066
|
+
}
|
|
3067
|
+
}
|
|
3068
|
+
function ingestFile2(agg, file, sinceMs) {
|
|
3069
|
+
let text;
|
|
3070
|
+
if (file.endsWith(".zst")) {
|
|
3071
|
+
if (zstdDecompress === null) {
|
|
3072
|
+
throw new Error("zstd not supported by this Node runtime");
|
|
3073
|
+
}
|
|
3074
|
+
text = zstdDecompress(readFileSync9(file)).toString("utf8");
|
|
3075
|
+
} else {
|
|
3076
|
+
text = readFileSync9(file, "utf8");
|
|
3077
|
+
}
|
|
3078
|
+
const state = createFileState();
|
|
3079
|
+
for (const line of text.split("\n")) {
|
|
3080
|
+
if (!line) continue;
|
|
3081
|
+
agg.lines++;
|
|
3082
|
+
let rec;
|
|
3083
|
+
try {
|
|
3084
|
+
rec = JSON.parse(line);
|
|
3085
|
+
} catch {
|
|
3086
|
+
agg.parseErrors++;
|
|
3087
|
+
continue;
|
|
3088
|
+
}
|
|
3089
|
+
ingestLine(agg, rec, state, sinceMs);
|
|
3090
|
+
}
|
|
3091
|
+
}
|
|
3092
|
+
function readConfiguredMcpServers(agg, configFile) {
|
|
3093
|
+
const file = configFile ?? path2.join(codexHome2(), "config.toml");
|
|
3094
|
+
let names = [];
|
|
3095
|
+
try {
|
|
3096
|
+
const parsed = parseToml2(readFileSync9(file, "utf8"));
|
|
3097
|
+
const servers = parsed.mcp_servers;
|
|
3098
|
+
if (servers && typeof servers === "object" && !Array.isArray(servers)) {
|
|
3099
|
+
names = Object.keys(servers);
|
|
3100
|
+
}
|
|
3101
|
+
} catch {
|
|
3102
|
+
return;
|
|
3103
|
+
}
|
|
3104
|
+
noteConfiguredMcpServers(agg, names);
|
|
3105
|
+
}
|
|
3106
|
+
|
|
3107
|
+
// src/harness/codex/adapter.ts
|
|
3108
|
+
var CODEX_HARNESS_NAME = "codex";
|
|
3109
|
+
var CODEX_BUILTIN_TOOLS = /* @__PURE__ */ new Set([
|
|
3110
|
+
"apply_patch",
|
|
3111
|
+
"exec_command",
|
|
3112
|
+
"grep_command",
|
|
3113
|
+
"list_dir",
|
|
3114
|
+
"read_file",
|
|
3115
|
+
"request_user_input",
|
|
3116
|
+
"shell",
|
|
3117
|
+
"unified_exec",
|
|
3118
|
+
"update_plan",
|
|
3119
|
+
"view_image",
|
|
3120
|
+
"write_stdin",
|
|
3121
|
+
// synthetic names for non-function_call response items
|
|
3122
|
+
"local_shell",
|
|
3123
|
+
"web_search",
|
|
3124
|
+
"tool_search"
|
|
3125
|
+
]);
|
|
3126
|
+
async function exists4(p8) {
|
|
3127
|
+
try {
|
|
3128
|
+
await stat4(p8);
|
|
3129
|
+
return true;
|
|
3130
|
+
} catch {
|
|
3131
|
+
return false;
|
|
3132
|
+
}
|
|
3133
|
+
}
|
|
3134
|
+
var codexAdapter = {
|
|
3135
|
+
name: CODEX_HARNESS_NAME,
|
|
3136
|
+
builtinTools: CODEX_BUILTIN_TOOLS,
|
|
3137
|
+
pricingTableVersion: OPENAI_PRICING_TABLE_VERSION,
|
|
3138
|
+
async detect() {
|
|
3139
|
+
for (const root of rolloutRoots()) {
|
|
3140
|
+
if (await exists4(root)) return true;
|
|
3141
|
+
}
|
|
3142
|
+
return false;
|
|
3143
|
+
},
|
|
3144
|
+
async scan(opts) {
|
|
3145
|
+
const aggregate = createAggregate3();
|
|
3146
|
+
const stats = await scan2(aggregate, {
|
|
3147
|
+
sinceMs: opts.sinceMs,
|
|
3148
|
+
...opts.onProgress ? { onProgress: opts.onProgress } : {}
|
|
3149
|
+
});
|
|
3150
|
+
return { aggregate, stats };
|
|
3151
|
+
}
|
|
3152
|
+
};
|
|
3153
|
+
|
|
3154
|
+
// src/harness/shared/payload.ts
|
|
2689
3155
|
var SCHEMA_VERSION = 1;
|
|
2690
|
-
var HARNESS_NAME = "claude-code";
|
|
2691
3156
|
var MODEL_ID_UNSAFE_RE = /[^A-Za-z0-9._:-]+/g;
|
|
2692
3157
|
var MODEL_ID_MAX = 64;
|
|
2693
3158
|
function sanitizeModelId(id) {
|
|
@@ -2771,7 +3236,16 @@ function buildModels(rows, totalTokens, publishCost) {
|
|
|
2771
3236
|
});
|
|
2772
3237
|
}
|
|
2773
3238
|
function buildPayload(input) {
|
|
2774
|
-
const {
|
|
3239
|
+
const {
|
|
3240
|
+
aggregate: agg,
|
|
3241
|
+
stats,
|
|
3242
|
+
syncConfig,
|
|
3243
|
+
now,
|
|
3244
|
+
windowDays,
|
|
3245
|
+
harnessName,
|
|
3246
|
+
builtinTools,
|
|
3247
|
+
pricingTableVersion
|
|
3248
|
+
} = input;
|
|
2775
3249
|
const finalized = finalize(agg);
|
|
2776
3250
|
const { publishCost, allowlist, optIns } = syncConfig;
|
|
2777
3251
|
const fromMs = windowStartMs(now, windowDays);
|
|
@@ -2782,7 +3256,7 @@ function buildPayload(input) {
|
|
|
2782
3256
|
const totalToolCalls = finalized.totalToolCalls;
|
|
2783
3257
|
const builtins = buildCategory(
|
|
2784
3258
|
finalized.tools,
|
|
2785
|
-
|
|
3259
|
+
builtinTools,
|
|
2786
3260
|
optIns.builtinTools,
|
|
2787
3261
|
totalToolCalls
|
|
2788
3262
|
);
|
|
@@ -2815,10 +3289,10 @@ function buildPayload(input) {
|
|
|
2815
3289
|
capturedAt: now,
|
|
2816
3290
|
window: { days: windowDays, from, to },
|
|
2817
3291
|
harness: {
|
|
2818
|
-
name:
|
|
3292
|
+
name: harnessName,
|
|
2819
3293
|
version: finalized.harnessVersion === null ? null : sanitizeModelId(finalized.harnessVersion)
|
|
2820
3294
|
},
|
|
2821
|
-
pricingTable: publishCost ?
|
|
3295
|
+
pricingTable: publishCost ? pricingTableVersion : null,
|
|
2822
3296
|
activity: {
|
|
2823
3297
|
sessions: finalized.sessions,
|
|
2824
3298
|
activeDays,
|
|
@@ -2865,13 +3339,44 @@ function buildPayload(input) {
|
|
|
2865
3339
|
}
|
|
2866
3340
|
};
|
|
2867
3341
|
}
|
|
3342
|
+
function mergeKeptPrivate(halves) {
|
|
3343
|
+
const out = {};
|
|
3344
|
+
for (const category of NAME_CATEGORIES) {
|
|
3345
|
+
const merged = /* @__PURE__ */ new Map();
|
|
3346
|
+
for (const half of halves) {
|
|
3347
|
+
for (const atom of half[category]) {
|
|
3348
|
+
const held = merged.get(atom.name);
|
|
3349
|
+
if (held) held.count += atom.count;
|
|
3350
|
+
else merged.set(atom.name, { ...atom });
|
|
3351
|
+
}
|
|
3352
|
+
}
|
|
3353
|
+
out[category] = [...merged.values()].sort(
|
|
3354
|
+
(a, b) => b.count - a.count || a.name.localeCompare(b.name)
|
|
3355
|
+
);
|
|
3356
|
+
}
|
|
3357
|
+
return out;
|
|
3358
|
+
}
|
|
2868
3359
|
function buildSyncBody(built, syncConfig) {
|
|
2869
|
-
|
|
2870
|
-
return {
|
|
3360
|
+
const payloads = built.map((b) => b.payload);
|
|
3361
|
+
if (!syncConfig.reviewKeptPrivate) return { payloads };
|
|
3362
|
+
return {
|
|
3363
|
+
payloads,
|
|
3364
|
+
keptPrivate: mergeKeptPrivate(built.map((b) => b.keptPrivate))
|
|
3365
|
+
};
|
|
2871
3366
|
}
|
|
2872
3367
|
|
|
2873
|
-
// src/
|
|
2874
|
-
var
|
|
3368
|
+
// src/harness/index.ts
|
|
3369
|
+
var HARNESS_ADAPTERS = [
|
|
3370
|
+
claudeAdapter,
|
|
3371
|
+
codexAdapter
|
|
3372
|
+
];
|
|
3373
|
+
async function detectedAdapters() {
|
|
3374
|
+
const out = [];
|
|
3375
|
+
for (const adapter of HARNESS_ADAPTERS) {
|
|
3376
|
+
if (await adapter.detect()) out.push(adapter);
|
|
3377
|
+
}
|
|
3378
|
+
return out;
|
|
3379
|
+
}
|
|
2875
3380
|
|
|
2876
3381
|
// src/sync/summary.ts
|
|
2877
3382
|
function fmtTokens(n) {
|
|
@@ -2905,14 +3410,17 @@ function withheldCount(payload) {
|
|
|
2905
3410
|
return w.builtinTools + w.mcpServers + w.skills + w.subagents + w.slashCommands;
|
|
2906
3411
|
}
|
|
2907
3412
|
function buildGateDialog(ctx) {
|
|
2908
|
-
const {
|
|
2909
|
-
const
|
|
3413
|
+
const { payloads, keptPrivate } = ctx.body;
|
|
3414
|
+
const tokens = payloads.reduce((a, p8) => a + p8.activity.totalTokens, 0);
|
|
3415
|
+
const usds = payloads.map((p8) => totalUSD(p8)).filter((u) => u !== null);
|
|
3416
|
+
const usd = usds.length > 0 ? usds.reduce((a, b) => a + b, 0) : null;
|
|
3417
|
+
const days = payloads[0]?.window.days ?? 0;
|
|
2910
3418
|
const facts = [
|
|
2911
|
-
`${fmtTokens(
|
|
2912
|
-
`${
|
|
3419
|
+
`${fmtTokens(tokens)} tokens`,
|
|
3420
|
+
`${days} days`,
|
|
2913
3421
|
...usd === null ? [] : [fmtUSD(usd)]
|
|
2914
3422
|
].join(" \xB7 ");
|
|
2915
|
-
const n = withheldCount(
|
|
3423
|
+
const n = payloads.reduce((a, p8) => a + withheldCount(p8), 0);
|
|
2916
3424
|
const lines2 = [`Publish to aistack? ${facts}`];
|
|
2917
3425
|
if (n > 0) {
|
|
2918
3426
|
lines2.push(
|
|
@@ -2943,18 +3451,16 @@ function keptPrivateRows(keptPrivate) {
|
|
|
2943
3451
|
return rows;
|
|
2944
3452
|
}
|
|
2945
3453
|
var KEPT_PRIVATE_ROWS_SHOWN = 6;
|
|
2946
|
-
function
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
3454
|
+
function harnessLabel(name) {
|
|
3455
|
+
if (name === "claude-code") return "Claude Code";
|
|
3456
|
+
if (name === "codex") return "Codex";
|
|
3457
|
+
return name;
|
|
3458
|
+
}
|
|
3459
|
+
function payloadBlock(payload, showHeader) {
|
|
2950
3460
|
const out = [];
|
|
2951
|
-
|
|
2952
|
-
out.push("");
|
|
2953
|
-
if (config.stack === null) {
|
|
2954
|
-
out.push("to (no linked stack \u2014 publish is unavailable)");
|
|
2955
|
-
} else {
|
|
3461
|
+
if (showHeader) {
|
|
2956
3462
|
out.push(
|
|
2957
|
-
|
|
3463
|
+
`\u2014 ${harnessLabel(payload.harness.name)}${payload.harness.version ? ` ${payload.harness.version}` : ""}`
|
|
2958
3464
|
);
|
|
2959
3465
|
}
|
|
2960
3466
|
out.push(
|
|
@@ -2987,7 +3493,28 @@ function buildGateSummary(ctx) {
|
|
|
2987
3493
|
const names = atoms.map((a) => a.name).join(", ");
|
|
2988
3494
|
out.push(` ${CATEGORY_LABEL[category].padEnd(9)} ${names}`);
|
|
2989
3495
|
}
|
|
2990
|
-
|
|
3496
|
+
return out;
|
|
3497
|
+
}
|
|
3498
|
+
function buildGateSummary(ctx) {
|
|
3499
|
+
const { body, keptPrivate, config, source, baseUrl } = ctx;
|
|
3500
|
+
const { payloads } = body;
|
|
3501
|
+
const host = baseUrl.replace(/^https?:\/\//, "");
|
|
3502
|
+
const out = [];
|
|
3503
|
+
out.push("from your machine \u2014 sync preview");
|
|
3504
|
+
out.push("");
|
|
3505
|
+
if (config.stack === null) {
|
|
3506
|
+
out.push("to (no linked stack \u2014 publish is unavailable)");
|
|
3507
|
+
} else {
|
|
3508
|
+
out.push(
|
|
3509
|
+
`to ${config.stack.name} \xB7 ${host}/stacks/${config.stack.slug}`
|
|
3510
|
+
);
|
|
3511
|
+
}
|
|
3512
|
+
for (const payload of payloads) {
|
|
3513
|
+
out.push(...payloadBlock(payload, payloads.length > 1));
|
|
3514
|
+
out.push("");
|
|
3515
|
+
}
|
|
3516
|
+
if (out[out.length - 1] === "") out.pop();
|
|
3517
|
+
const n = payloads.reduce((a, p8) => a + withheldCount(p8), 0);
|
|
2991
3518
|
if (n > 0) {
|
|
2992
3519
|
out.push("");
|
|
2993
3520
|
out.push(`kept private: ${n} name${n === 1 ? "" : "s"}`);
|
|
@@ -3023,40 +3550,49 @@ function buildGateSummary(ctx) {
|
|
|
3023
3550
|
|
|
3024
3551
|
// src/sync/stage.ts
|
|
3025
3552
|
function stageId(bodyJson) {
|
|
3026
|
-
return
|
|
3553
|
+
return createHash2("sha256").update(bodyJson).digest("hex").slice(0, 12);
|
|
3027
3554
|
}
|
|
3028
3555
|
async function stageSync(deps) {
|
|
3029
3556
|
const now = (deps.now ?? Date.now)();
|
|
3030
3557
|
const token = (deps.getTokenImpl ?? getToken)();
|
|
3031
3558
|
const loadConfig = deps.loadConfigImpl ?? loadSyncConfig;
|
|
3032
|
-
const
|
|
3559
|
+
const adapters = deps.adaptersImpl ?? detectedAdapters;
|
|
3033
3560
|
const windowDays = deps.windowDays ?? DEFAULT_WINDOW_DAYS;
|
|
3034
3561
|
const { config, source } = await loadConfig({
|
|
3035
3562
|
baseUrl: deps.baseUrl,
|
|
3036
3563
|
...token ? { token } : {}
|
|
3037
3564
|
});
|
|
3038
|
-
const
|
|
3039
|
-
const
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3565
|
+
const built = [];
|
|
3566
|
+
const sinceMs = windowStartMs(now, windowDays);
|
|
3567
|
+
for (const adapter of await adapters()) {
|
|
3568
|
+
const { aggregate, stats } = await adapter.scan({ sinceMs });
|
|
3569
|
+
built.push(
|
|
3570
|
+
buildPayload({
|
|
3571
|
+
aggregate,
|
|
3572
|
+
stats,
|
|
3573
|
+
syncConfig: config,
|
|
3574
|
+
now,
|
|
3575
|
+
windowDays,
|
|
3576
|
+
harnessName: adapter.name,
|
|
3577
|
+
builtinTools: adapter.builtinTools,
|
|
3578
|
+
pricingTableVersion: adapter.pricingTableVersion
|
|
3579
|
+
})
|
|
3580
|
+
);
|
|
3581
|
+
}
|
|
3049
3582
|
const body = buildSyncBody(built, config);
|
|
3050
3583
|
const bodyJson = JSON.stringify(body);
|
|
3584
|
+
const keptPrivate = mergeKeptPrivate(built.map((b) => b.keptPrivate));
|
|
3051
3585
|
const ctx = {
|
|
3052
3586
|
body,
|
|
3053
|
-
keptPrivate
|
|
3587
|
+
keptPrivate,
|
|
3054
3588
|
config,
|
|
3055
3589
|
source,
|
|
3056
3590
|
baseUrl: deps.baseUrl
|
|
3057
3591
|
};
|
|
3058
3592
|
let blockedReason = null;
|
|
3059
|
-
if (
|
|
3593
|
+
if (built.length === 0) {
|
|
3594
|
+
blockedReason = "No supported harness was found on this machine \u2014 no Claude Code and no Codex logs to read.";
|
|
3595
|
+
} else if (token === null) {
|
|
3060
3596
|
blockedReason = "This machine is not linked. Run `npx @use-aistack/cli login` first.";
|
|
3061
3597
|
} else if (config.stack === null) {
|
|
3062
3598
|
blockedReason = source === "bundled" ? "Could not fetch your settings from aistack, so the destination stack is unknown. Publish needs it. Check the network and preview again." : "The token resolves no destination stack. Run `npx @use-aistack/cli login` again to re-link this machine.";
|
|
@@ -3065,7 +3601,7 @@ async function stageSync(deps) {
|
|
|
3065
3601
|
id: stageId(bodyJson),
|
|
3066
3602
|
bodyJson,
|
|
3067
3603
|
body,
|
|
3068
|
-
keptPrivate
|
|
3604
|
+
keptPrivate,
|
|
3069
3605
|
summary: buildGateSummary(ctx),
|
|
3070
3606
|
dialog: buildGateDialog(ctx),
|
|
3071
3607
|
config,
|
|
@@ -3076,16 +3612,16 @@ async function stageSync(deps) {
|
|
|
3076
3612
|
}
|
|
3077
3613
|
|
|
3078
3614
|
// src/autosync/run.ts
|
|
3079
|
-
var SYNC_LOG_FILE =
|
|
3615
|
+
var SYNC_LOG_FILE = join10(homedir11(), ".config", "aistack", "sync.log");
|
|
3080
3616
|
var SYNC_LOG_MAX_LINES = 200;
|
|
3081
3617
|
var FIX_COMMAND = "npx @use-aistack/cli sync";
|
|
3082
3618
|
function appendLogLine(file, line) {
|
|
3083
|
-
|
|
3619
|
+
mkdirSync5(dirname7(file), { recursive: true });
|
|
3084
3620
|
appendFileSync(file, `${line}
|
|
3085
3621
|
`);
|
|
3086
|
-
const lines2 =
|
|
3622
|
+
const lines2 = readFileSync10(file, "utf-8").split("\n").filter(Boolean);
|
|
3087
3623
|
if (lines2.length > SYNC_LOG_MAX_LINES) {
|
|
3088
|
-
|
|
3624
|
+
writeFileSync5(file, `${lines2.slice(-SYNC_LOG_MAX_LINES).join("\n")}
|
|
3089
3625
|
`);
|
|
3090
3626
|
}
|
|
3091
3627
|
}
|
|
@@ -3195,13 +3731,16 @@ async function syncCommand(options = {}) {
|
|
|
3195
3731
|
if (lastAuto !== void 0) {
|
|
3196
3732
|
p7.log.message(dim(`auto-sync: ${lastAuto}`));
|
|
3197
3733
|
}
|
|
3734
|
+
if (codexAutoSyncHookInstalled() && codexHookTrusted() === false) {
|
|
3735
|
+
p7.log.warn(CODEX_TRUST_INSTRUCTION);
|
|
3736
|
+
}
|
|
3198
3737
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
3199
3738
|
outroError("sync needs an interactive terminal \u2014 nothing was sent");
|
|
3200
3739
|
process.exitCode = 1;
|
|
3201
3740
|
return;
|
|
3202
3741
|
}
|
|
3203
3742
|
const s = p7.spinner();
|
|
3204
|
-
s.start("Scanning local
|
|
3743
|
+
s.start("Scanning local agent transcripts");
|
|
3205
3744
|
let staged;
|
|
3206
3745
|
try {
|
|
3207
3746
|
staged = await stageSync({ baseUrl: BASE_URL });
|
|
@@ -3265,7 +3804,7 @@ var STAGE_TTL_MS = 10 * 60 * 1e3;
|
|
|
3265
3804
|
var ELICIT_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
3266
3805
|
var PREVIEW_TOOL = {
|
|
3267
3806
|
name: "sync_preview",
|
|
3268
|
-
description: "Scan local Claude Code
|
|
3807
|
+
description: "Scan local agent transcripts (Claude Code, Codex) and stage a measured-usage snapshot for aistack. Returns the full preview of exactly what would publish. Show the returned text to the user VERBATIM \u2014 it is the review surface. Nothing is sent.",
|
|
3269
3808
|
inputSchema: { type: "object", properties: {} },
|
|
3270
3809
|
annotations: {
|
|
3271
3810
|
title: "aistack \u2014 preview sync (sends nothing)",
|