@claude-transcripts/cli 0.7.1 → 0.8.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/README.md +2 -0
- package/dist/{app-axk7ny8y.js → app-v7rh39yp.js} +1 -1
- package/dist/{cli-afbmvjwt.js → cli-3dqna555.js} +871 -129
- package/dist/cli.js +2 -2
- package/package.json +1 -1
|
@@ -32,6 +32,11 @@ var ACTIONS = [
|
|
|
32
32
|
summary: "Tell the user, in the transcript, whether and where this session is recorded",
|
|
33
33
|
implemented: true
|
|
34
34
|
},
|
|
35
|
+
{
|
|
36
|
+
key: "check-hook-compat",
|
|
37
|
+
summary: "Check the running Claude Code's hook spec against ours and report any drift",
|
|
38
|
+
implemented: true
|
|
39
|
+
},
|
|
35
40
|
{
|
|
36
41
|
key: "inject-recall-policy",
|
|
37
42
|
summary: "Prime the session with the recall policy + how much history this cwd has",
|
|
@@ -52,8 +57,9 @@ var ACTIONS = [
|
|
|
52
57
|
},
|
|
53
58
|
{
|
|
54
59
|
key: "reconcile-session",
|
|
55
|
-
summary: "
|
|
56
|
-
implemented:
|
|
60
|
+
summary: "Write the missing summary for a session that never fired SessionEnd, from its events (and S3 transcript)",
|
|
61
|
+
implemented: true,
|
|
62
|
+
triggers: { cli: "reconcile", follower: true }
|
|
57
63
|
}
|
|
58
64
|
];
|
|
59
65
|
var BINDINGS = [
|
|
@@ -63,6 +69,7 @@ var BINDINGS = [
|
|
|
63
69
|
"seed-session-start",
|
|
64
70
|
"write-event-marker",
|
|
65
71
|
"announce-recording",
|
|
72
|
+
"check-hook-compat",
|
|
66
73
|
"inject-recall-policy"
|
|
67
74
|
]
|
|
68
75
|
},
|
|
@@ -746,6 +753,24 @@ var CLI_SPEC = {
|
|
|
746
753
|
summary: "Rebuild the search indexes from CouchDB",
|
|
747
754
|
examples: ["reindex"]
|
|
748
755
|
},
|
|
756
|
+
{
|
|
757
|
+
name: "reconcile",
|
|
758
|
+
group: "admin",
|
|
759
|
+
summary: "Finalise sessions that never ended (write their missing summary)",
|
|
760
|
+
args: [
|
|
761
|
+
{
|
|
762
|
+
name: "session-id",
|
|
763
|
+
description: "sessions to finalise (repeatable); default: every session that reads as `incomplete`"
|
|
764
|
+
},
|
|
765
|
+
{ name: "--dry-run", description: "say what would be written, write nothing" },
|
|
766
|
+
{
|
|
767
|
+
name: "--force",
|
|
768
|
+
description: "also finalise a named session that still reads as `running`"
|
|
769
|
+
},
|
|
770
|
+
{ name: "--json", description: "print the outcomes as JSON (what the follower reads)" }
|
|
771
|
+
],
|
|
772
|
+
examples: ["reconcile --dry-run", "reconcile 3f9a2c1e-\u2026 --force", "reconcile --json"]
|
|
773
|
+
},
|
|
749
774
|
{
|
|
750
775
|
name: "doctor",
|
|
751
776
|
group: "admin",
|
|
@@ -753,6 +778,25 @@ var CLI_SPEC = {
|
|
|
753
778
|
args: [{ name: "--keep", description: "leave the synthetic session behind for inspection" }],
|
|
754
779
|
examples: ["doctor", "doctor --keep"]
|
|
755
780
|
},
|
|
781
|
+
{
|
|
782
|
+
name: "compat",
|
|
783
|
+
group: "admin",
|
|
784
|
+
summary: "Check Claude Code's hook spec against this build's (exit 1 on drift)",
|
|
785
|
+
args: [
|
|
786
|
+
{
|
|
787
|
+
name: "action",
|
|
788
|
+
choices: ["check", "fetch"],
|
|
789
|
+
default: "check",
|
|
790
|
+
description: "`check` reports drift; `fetch` caches a version's spec from npm (SessionStart " + "starts it detached when it meets a version it doesn't know)"
|
|
791
|
+
},
|
|
792
|
+
{
|
|
793
|
+
name: "--cc-version",
|
|
794
|
+
type: "string",
|
|
795
|
+
description: "the Claude Code version to check (default: the installed one)"
|
|
796
|
+
}
|
|
797
|
+
],
|
|
798
|
+
examples: ["compat", "compat --cc-version 2.1.300 --json"]
|
|
799
|
+
},
|
|
756
800
|
{
|
|
757
801
|
name: "hook",
|
|
758
802
|
group: "admin",
|
|
@@ -1810,6 +1854,397 @@ var WORKLOAD_ROLES = new Set(["backing", "admin-ui", "app"]);
|
|
|
1810
1854
|
function toStoreEnv(model) {
|
|
1811
1855
|
return { FOSSIL_REPOSITORIES: Object.values(model.stores.repositories).join(",") };
|
|
1812
1856
|
}
|
|
1857
|
+
// packages/shared/src/claude-code-hook-spec.json
|
|
1858
|
+
var claude_code_hook_spec_default = {
|
|
1859
|
+
claudeCode: "2.1.310",
|
|
1860
|
+
sdk: "0.3.310",
|
|
1861
|
+
base: {
|
|
1862
|
+
agent_id: [],
|
|
1863
|
+
agent_type: [],
|
|
1864
|
+
cwd: [],
|
|
1865
|
+
effort: [],
|
|
1866
|
+
permission_mode: [],
|
|
1867
|
+
prompt_id: [],
|
|
1868
|
+
session_id: [],
|
|
1869
|
+
transcript_path: []
|
|
1870
|
+
},
|
|
1871
|
+
events: {
|
|
1872
|
+
PreToolUse: {
|
|
1873
|
+
mcp_server: [],
|
|
1874
|
+
tool_input: [],
|
|
1875
|
+
tool_name: [],
|
|
1876
|
+
tool_use_id: []
|
|
1877
|
+
},
|
|
1878
|
+
PostToolUse: {
|
|
1879
|
+
duration_ms: [],
|
|
1880
|
+
mcp_server: [],
|
|
1881
|
+
tool_input: [],
|
|
1882
|
+
tool_name: [],
|
|
1883
|
+
tool_response: [],
|
|
1884
|
+
tool_use_id: []
|
|
1885
|
+
},
|
|
1886
|
+
PostToolUseFailure: {
|
|
1887
|
+
duration_ms: [],
|
|
1888
|
+
error: [],
|
|
1889
|
+
is_interrupt: [],
|
|
1890
|
+
mcp_server: [],
|
|
1891
|
+
tool_input: [],
|
|
1892
|
+
tool_name: [],
|
|
1893
|
+
tool_use_id: []
|
|
1894
|
+
},
|
|
1895
|
+
PostToolBatch: {
|
|
1896
|
+
tool_calls: []
|
|
1897
|
+
},
|
|
1898
|
+
Notification: {
|
|
1899
|
+
message: [],
|
|
1900
|
+
notification_type: [],
|
|
1901
|
+
title: []
|
|
1902
|
+
},
|
|
1903
|
+
UserPromptSubmit: {
|
|
1904
|
+
prompt: [],
|
|
1905
|
+
session_title: [],
|
|
1906
|
+
source: ["loop_wakeup", "poll_event", "schedule_wakeup", "sdk", "system", "user"]
|
|
1907
|
+
},
|
|
1908
|
+
UserPromptExpansion: {
|
|
1909
|
+
command_args: [],
|
|
1910
|
+
command_name: [],
|
|
1911
|
+
command_source: [],
|
|
1912
|
+
expansion_type: ["mcp_prompt", "slash_command"],
|
|
1913
|
+
prompt: []
|
|
1914
|
+
},
|
|
1915
|
+
SessionStart: {
|
|
1916
|
+
agent_type: [],
|
|
1917
|
+
context_tokens: [],
|
|
1918
|
+
estimated_cache_write_usd: [],
|
|
1919
|
+
model: [],
|
|
1920
|
+
prompt_cache_likely_expired: [],
|
|
1921
|
+
seconds_since_last_response: [],
|
|
1922
|
+
session_title: [],
|
|
1923
|
+
source: ["clear", "compact", "fork", "resume", "startup"]
|
|
1924
|
+
},
|
|
1925
|
+
SessionEnd: {
|
|
1926
|
+
reason: ["clear", "logout", "other", "prompt_input_exit", "resume"]
|
|
1927
|
+
},
|
|
1928
|
+
Stop: {
|
|
1929
|
+
background_tasks: [],
|
|
1930
|
+
last_assistant_message: [],
|
|
1931
|
+
session_crons: [],
|
|
1932
|
+
stop_hook_active: []
|
|
1933
|
+
},
|
|
1934
|
+
StopFailure: {
|
|
1935
|
+
error: [
|
|
1936
|
+
"account_on_hold",
|
|
1937
|
+
"authentication_failed",
|
|
1938
|
+
"billing_error",
|
|
1939
|
+
"cloud_credential_error",
|
|
1940
|
+
"invalid_request",
|
|
1941
|
+
"max_output_tokens",
|
|
1942
|
+
"model_not_found",
|
|
1943
|
+
"oauth_org_not_allowed",
|
|
1944
|
+
"overloaded",
|
|
1945
|
+
"rate_limit",
|
|
1946
|
+
"server_error",
|
|
1947
|
+
"unknown",
|
|
1948
|
+
"verification_required"
|
|
1949
|
+
],
|
|
1950
|
+
error_details: [],
|
|
1951
|
+
last_assistant_message: []
|
|
1952
|
+
},
|
|
1953
|
+
SubagentStart: {
|
|
1954
|
+
agent_id: [],
|
|
1955
|
+
agent_type: []
|
|
1956
|
+
},
|
|
1957
|
+
SubagentStop: {
|
|
1958
|
+
agent_id: [],
|
|
1959
|
+
agent_transcript_path: [],
|
|
1960
|
+
agent_type: [],
|
|
1961
|
+
background_tasks: [],
|
|
1962
|
+
last_assistant_message: [],
|
|
1963
|
+
session_crons: [],
|
|
1964
|
+
stop_hook_active: []
|
|
1965
|
+
},
|
|
1966
|
+
PreCompact: {
|
|
1967
|
+
custom_instructions: [],
|
|
1968
|
+
trigger: ["auto", "manual"]
|
|
1969
|
+
},
|
|
1970
|
+
PostCompact: {
|
|
1971
|
+
compact_summary: [],
|
|
1972
|
+
trigger: ["auto", "manual"]
|
|
1973
|
+
},
|
|
1974
|
+
PreModelSwitch: {
|
|
1975
|
+
cache_ttl: ["1h", "5m"],
|
|
1976
|
+
context_tokens: [],
|
|
1977
|
+
estimated_cache_write_usd: [],
|
|
1978
|
+
from_model: [],
|
|
1979
|
+
pricing: ["catalog", "configured", "default"],
|
|
1980
|
+
prompt_cache_warm: [],
|
|
1981
|
+
requested_model: [],
|
|
1982
|
+
source: ["command", "picker", "sdk"],
|
|
1983
|
+
to_model: []
|
|
1984
|
+
},
|
|
1985
|
+
PostModelSwitch: {
|
|
1986
|
+
cache_ttl: ["1h", "5m"],
|
|
1987
|
+
context_tokens: [],
|
|
1988
|
+
estimated_cache_write_usd: [],
|
|
1989
|
+
from_model: [],
|
|
1990
|
+
pricing: ["catalog", "configured", "default"],
|
|
1991
|
+
prompt_cache_warm: [],
|
|
1992
|
+
requested_model: [],
|
|
1993
|
+
source: ["auto", "command", "picker", "resume", "sdk"],
|
|
1994
|
+
to_model: []
|
|
1995
|
+
},
|
|
1996
|
+
PermissionRequest: {
|
|
1997
|
+
mcp_server: [],
|
|
1998
|
+
permission_suggestions: [],
|
|
1999
|
+
tool_input: [],
|
|
2000
|
+
tool_name: []
|
|
2001
|
+
},
|
|
2002
|
+
PermissionDenied: {
|
|
2003
|
+
mcp_server: [],
|
|
2004
|
+
reason: [],
|
|
2005
|
+
tool_input: [],
|
|
2006
|
+
tool_name: [],
|
|
2007
|
+
tool_use_id: []
|
|
2008
|
+
},
|
|
2009
|
+
Setup: {
|
|
2010
|
+
trigger: ["init", "maintenance"]
|
|
2011
|
+
},
|
|
2012
|
+
TeammateIdle: {
|
|
2013
|
+
team_name: [],
|
|
2014
|
+
teammate_name: []
|
|
2015
|
+
},
|
|
2016
|
+
TaskCreated: {
|
|
2017
|
+
task_description: [],
|
|
2018
|
+
task_id: [],
|
|
2019
|
+
task_subject: [],
|
|
2020
|
+
team_name: [],
|
|
2021
|
+
teammate_name: []
|
|
2022
|
+
},
|
|
2023
|
+
TaskCompleted: {
|
|
2024
|
+
task_description: [],
|
|
2025
|
+
task_id: [],
|
|
2026
|
+
task_subject: [],
|
|
2027
|
+
team_name: [],
|
|
2028
|
+
teammate_name: []
|
|
2029
|
+
},
|
|
2030
|
+
Elicitation: {
|
|
2031
|
+
elicitation_id: [],
|
|
2032
|
+
mcp_server_name: [],
|
|
2033
|
+
message: [],
|
|
2034
|
+
mode: ["form", "url"],
|
|
2035
|
+
requested_schema: [],
|
|
2036
|
+
url: []
|
|
2037
|
+
},
|
|
2038
|
+
ElicitationResult: {
|
|
2039
|
+
action: ["accept", "cancel", "decline"],
|
|
2040
|
+
content: [],
|
|
2041
|
+
elicitation_id: [],
|
|
2042
|
+
mcp_server_name: [],
|
|
2043
|
+
mode: ["form", "url"]
|
|
2044
|
+
},
|
|
2045
|
+
ConfigChange: {
|
|
2046
|
+
file_path: [],
|
|
2047
|
+
source: ["local_settings", "policy_settings", "project_settings", "skills", "user_settings"]
|
|
2048
|
+
},
|
|
2049
|
+
WorktreeCreate: {
|
|
2050
|
+
name: []
|
|
2051
|
+
},
|
|
2052
|
+
WorktreeRemove: {
|
|
2053
|
+
worktree_path: []
|
|
2054
|
+
},
|
|
2055
|
+
InstructionsLoaded: {
|
|
2056
|
+
file_path: [],
|
|
2057
|
+
globs: [],
|
|
2058
|
+
load_reason: ["compact", "include", "nested_traversal", "path_glob_match", "session_start"],
|
|
2059
|
+
memory_type: ["Local", "Managed", "Project", "User"],
|
|
2060
|
+
parent_file_path: [],
|
|
2061
|
+
trigger_file_path: []
|
|
2062
|
+
},
|
|
2063
|
+
CwdChanged: {
|
|
2064
|
+
new_cwd: [],
|
|
2065
|
+
old_cwd: []
|
|
2066
|
+
},
|
|
2067
|
+
FileChanged: {
|
|
2068
|
+
event: ["add", "change", "unlink"],
|
|
2069
|
+
file_path: []
|
|
2070
|
+
},
|
|
2071
|
+
DirectoryAdded: {
|
|
2072
|
+
directory: [],
|
|
2073
|
+
source: ["register_repo_root", "slash_command"]
|
|
2074
|
+
},
|
|
2075
|
+
MessageDisplay: {
|
|
2076
|
+
delta: [],
|
|
2077
|
+
final: [],
|
|
2078
|
+
index: [],
|
|
2079
|
+
message_id: [],
|
|
2080
|
+
turn_id: []
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
};
|
|
2084
|
+
|
|
2085
|
+
// packages/shared/src/claude-code-spec.ts
|
|
2086
|
+
var VERIFIED_HOOK_SPEC = claude_code_hook_spec_default;
|
|
2087
|
+
function stripComments(src) {
|
|
2088
|
+
return src.replace(/\/\*[\s\S]*?\*\//g, "").replace(/^\s*\/\/.*$/gm, "");
|
|
2089
|
+
}
|
|
2090
|
+
function typeBody(dts, name) {
|
|
2091
|
+
const head = `export declare type ${name} = `;
|
|
2092
|
+
const start = dts.indexOf(head);
|
|
2093
|
+
if (start < 0)
|
|
2094
|
+
return null;
|
|
2095
|
+
let depth = 0;
|
|
2096
|
+
for (let i = start + head.length;i < dts.length; i++) {
|
|
2097
|
+
const c = dts[i];
|
|
2098
|
+
if (c === "{" || c === "(")
|
|
2099
|
+
depth++;
|
|
2100
|
+
else if (c === "}" || c === ")")
|
|
2101
|
+
depth--;
|
|
2102
|
+
else if (c === ";" && depth === 0)
|
|
2103
|
+
return dts.slice(start + head.length, i);
|
|
2104
|
+
}
|
|
2105
|
+
return null;
|
|
2106
|
+
}
|
|
2107
|
+
var LIT = `(?:'[^']*'|"[^"]*")`;
|
|
2108
|
+
var LIT_UNION = new RegExp(`^${LIT}(?:\\s*\\|\\s*${LIT})*$`);
|
|
2109
|
+
function literalAliases(dts) {
|
|
2110
|
+
const out = new Map;
|
|
2111
|
+
const re = /^export declare type (\w+) = (.+);$/gm;
|
|
2112
|
+
for (const m of dts.matchAll(re)) {
|
|
2113
|
+
if (LIT_UNION.test(m[2]))
|
|
2114
|
+
out.set(m[1], literals(m[2]));
|
|
2115
|
+
}
|
|
2116
|
+
return out;
|
|
2117
|
+
}
|
|
2118
|
+
function literals(src) {
|
|
2119
|
+
return [...src.matchAll(/'([^']*)'|"([^"]*)"/g)].map((m) => m[1] ?? m[2]).sort();
|
|
2120
|
+
}
|
|
2121
|
+
function fieldsOf(body, aliases) {
|
|
2122
|
+
const fields = {};
|
|
2123
|
+
let depth = 0;
|
|
2124
|
+
for (const line of body.split(`
|
|
2125
|
+
`)) {
|
|
2126
|
+
if (depth === 1) {
|
|
2127
|
+
const m = /^\s*(\w+)\??:\s*(.*)$/.exec(line);
|
|
2128
|
+
if (m && m[1] !== "hook_event_name") {
|
|
2129
|
+
const type = m[2].replace(/;\s*$/, "").trim();
|
|
2130
|
+
if (LIT_UNION.test(type))
|
|
2131
|
+
fields[m[1]] = literals(type);
|
|
2132
|
+
else
|
|
2133
|
+
fields[m[1]] = aliases.get(type) ?? [];
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
for (const c of line) {
|
|
2137
|
+
if (c === "{")
|
|
2138
|
+
depth++;
|
|
2139
|
+
else if (c === "}")
|
|
2140
|
+
depth--;
|
|
2141
|
+
}
|
|
2142
|
+
}
|
|
2143
|
+
return Object.fromEntries(Object.entries(fields).sort(([a], [b]) => a.localeCompare(b)));
|
|
2144
|
+
}
|
|
2145
|
+
function parseSdkHookSpec(source, versions) {
|
|
2146
|
+
const dts = stripComments(source.replace(/\r\n/g, `
|
|
2147
|
+
`));
|
|
2148
|
+
const list = /export declare const HOOK_EVENTS: readonly \[([^\]]*)\]/.exec(dts);
|
|
2149
|
+
if (!list)
|
|
2150
|
+
throw new Error("sdk.d.ts declares no HOOK_EVENTS \u2014 the SDK's layout changed");
|
|
2151
|
+
const baseBody = typeBody(dts, "BaseHookInput");
|
|
2152
|
+
if (!baseBody)
|
|
2153
|
+
throw new Error("sdk.d.ts declares no BaseHookInput \u2014 the SDK's layout changed");
|
|
2154
|
+
const aliases = literalAliases(dts);
|
|
2155
|
+
const events = {};
|
|
2156
|
+
const names = [...list[1].matchAll(/'([^']+)'|"([^"]+)"/g)].map((m) => m[1] ?? m[2]);
|
|
2157
|
+
if (!names.length)
|
|
2158
|
+
throw new Error("sdk.d.ts lists no HOOK_EVENTS \u2014 the SDK's layout changed");
|
|
2159
|
+
for (const event of names) {
|
|
2160
|
+
const body = typeBody(dts, `${event}HookInput`);
|
|
2161
|
+
events[event] = body ? fieldsOf(body, aliases) : {};
|
|
2162
|
+
}
|
|
2163
|
+
return { ...versions, base: fieldsOf(baseBody, aliases), events };
|
|
2164
|
+
}
|
|
2165
|
+
function flatten(fields) {
|
|
2166
|
+
const out = new Set;
|
|
2167
|
+
for (const [name, values] of Object.entries(fields)) {
|
|
2168
|
+
out.add(name);
|
|
2169
|
+
for (const v of values)
|
|
2170
|
+
out.add(`${name}=${v}`);
|
|
2171
|
+
}
|
|
2172
|
+
return out;
|
|
2173
|
+
}
|
|
2174
|
+
function fieldDrift(event, before, after) {
|
|
2175
|
+
const a = flatten(before);
|
|
2176
|
+
const b = flatten(after);
|
|
2177
|
+
const whole = (s, other) => !other.has(s.split("=")[0]);
|
|
2178
|
+
const added = [...b].filter((f) => !a.has(f) && (!f.includes("=") || !whole(f, a))).sort();
|
|
2179
|
+
const removed = [...a].filter((f) => !b.has(f) && (!f.includes("=") || !whole(f, b))).sort();
|
|
2180
|
+
return added.length || removed.length ? { event, added, removed } : null;
|
|
2181
|
+
}
|
|
2182
|
+
function compareHookSpec(catalogue, verified, runningVersion, running) {
|
|
2183
|
+
const wired = catalogue.filter((h) => h.wired);
|
|
2184
|
+
const report = {
|
|
2185
|
+
running: runningVersion,
|
|
2186
|
+
verified: verified.claudeCode,
|
|
2187
|
+
known: running !== null,
|
|
2188
|
+
unlisted: [],
|
|
2189
|
+
goneWired: [],
|
|
2190
|
+
goneListed: [],
|
|
2191
|
+
fields: [],
|
|
2192
|
+
eventCount: running ? Object.keys(running.events).length : 0,
|
|
2193
|
+
wiredCount: wired.length
|
|
2194
|
+
};
|
|
2195
|
+
if (!running)
|
|
2196
|
+
return report;
|
|
2197
|
+
const listed = new Set(catalogue.map((h) => h.event));
|
|
2198
|
+
report.unlisted = Object.keys(running.events).filter((e) => !listed.has(e)).sort();
|
|
2199
|
+
for (const h of catalogue) {
|
|
2200
|
+
if (h.event in running.events)
|
|
2201
|
+
continue;
|
|
2202
|
+
(h.wired ? report.goneWired : report.goneListed).push(h.event);
|
|
2203
|
+
}
|
|
2204
|
+
const base = fieldDrift("*", verified.base, running.base);
|
|
2205
|
+
if (base)
|
|
2206
|
+
report.fields.push(base);
|
|
2207
|
+
for (const h of wired) {
|
|
2208
|
+
const after = running.events[h.event];
|
|
2209
|
+
if (!after)
|
|
2210
|
+
continue;
|
|
2211
|
+
const drift = fieldDrift(h.event, verified.events[h.event] ?? {}, after);
|
|
2212
|
+
if (drift)
|
|
2213
|
+
report.fields.push(drift);
|
|
2214
|
+
}
|
|
2215
|
+
return report;
|
|
2216
|
+
}
|
|
2217
|
+
function hasDrift(r) {
|
|
2218
|
+
return !r.known || r.unlisted.length > 0 || r.goneWired.length > 0 || r.goneListed.length > 0 || r.fields.length > 0;
|
|
2219
|
+
}
|
|
2220
|
+
function formatHookCompat(r) {
|
|
2221
|
+
if (!r.known) {
|
|
2222
|
+
return [
|
|
2223
|
+
`? Hook spec for Claude Code ${r.running} not checked yet (this build knows ${r.verified}) \u2014 fetching it; the result shows next session`
|
|
2224
|
+
];
|
|
2225
|
+
}
|
|
2226
|
+
if (!hasDrift(r)) {
|
|
2227
|
+
return [
|
|
2228
|
+
`\u2713 Hook spec matches Claude Code ${r.running} (${r.eventCount} events, ${r.wiredCount} recorded)`
|
|
2229
|
+
];
|
|
2230
|
+
}
|
|
2231
|
+
const against = r.running === r.verified ? "" : ` (this build was checked against ${r.verified})`;
|
|
2232
|
+
const lines = [`\u25B3 Hook spec drift with Claude Code ${r.running}${against}:`];
|
|
2233
|
+
const list = (xs) => xs.join(", ");
|
|
2234
|
+
if (r.goneWired.length)
|
|
2235
|
+
lines.push(` \xB7 recorded, but no longer fired: ${list(r.goneWired)}`);
|
|
2236
|
+
if (r.unlisted.length)
|
|
2237
|
+
lines.push(` \xB7 new events we don't handle: ${list(r.unlisted)}`);
|
|
2238
|
+
if (r.goneListed.length)
|
|
2239
|
+
lines.push(` \xB7 listed, but no longer fired: ${list(r.goneListed)}`);
|
|
2240
|
+
for (const f of r.fields) {
|
|
2241
|
+
const name = f.event === "*" ? "common fields" : f.event;
|
|
2242
|
+
const parts = [...f.added.map((x) => `+${x}`), ...f.removed.map((x) => `\u2212${x}`)];
|
|
2243
|
+
lines.push(` \xB7 ${name}: ${parts.join(" ")}`);
|
|
2244
|
+
}
|
|
2245
|
+
lines.push(" Details: claude-transcripts compat");
|
|
2246
|
+
return lines;
|
|
2247
|
+
}
|
|
1813
2248
|
// packages/shared/src/couch-url.ts
|
|
1814
2249
|
var DEFAULT_COUCHDB_HOST = "127.0.0.1";
|
|
1815
2250
|
var DEFAULT_COUCHDB_PORT = "7652";
|
|
@@ -2563,6 +2998,14 @@ async function getOrNull(path) {
|
|
|
2563
2998
|
return null;
|
|
2564
2999
|
return await res.json();
|
|
2565
3000
|
}
|
|
3001
|
+
async function getTextOrNull(path) {
|
|
3002
|
+
const res = await fetch(`${base()}${path}`);
|
|
3003
|
+
if (res.status === 404)
|
|
3004
|
+
return null;
|
|
3005
|
+
if (!res.ok)
|
|
3006
|
+
throw new Error(`GET ${path} \u2192 ${res.status} ${res.statusText}${await errorDetail(res)}`);
|
|
3007
|
+
return await res.text();
|
|
3008
|
+
}
|
|
2566
3009
|
async function putStream(path, filePath, contentType) {
|
|
2567
3010
|
const res = await fetch(`${base()}${path}`, {
|
|
2568
3011
|
method: "PUT",
|
|
@@ -2730,6 +3173,27 @@ function buildSummaryDoc(facts, source, opts = {}) {
|
|
|
2730
3173
|
...opts.actor ? { actor: opts.actor } : {}
|
|
2731
3174
|
};
|
|
2732
3175
|
}
|
|
3176
|
+
function buildReconciledSummary(s, transcript, reconciledAt) {
|
|
3177
|
+
return {
|
|
3178
|
+
_id: `summary:${s.sessionId}`,
|
|
3179
|
+
type: "summary",
|
|
3180
|
+
event: "SessionEnd",
|
|
3181
|
+
session_id: s.sessionId,
|
|
3182
|
+
timestamp: s.lastActivity || s.timestamp,
|
|
3183
|
+
hostname: s.hostname,
|
|
3184
|
+
cwd: s.cwd,
|
|
3185
|
+
...s.model ? { model: s.model } : {},
|
|
3186
|
+
end_reason: "unknown",
|
|
3187
|
+
event_count: s.eventCount,
|
|
3188
|
+
prompt_count: s.promptCount,
|
|
3189
|
+
error_count: s.errorCount,
|
|
3190
|
+
tool_counts: s.toolCounts,
|
|
3191
|
+
transcript_bytes: transcript ? Buffer.byteLength(transcript) : s.transcriptSize ?? 0,
|
|
3192
|
+
...transcript ? { token_usage: sumTranscriptTokens(transcript) } : {},
|
|
3193
|
+
source: "reconcile",
|
|
3194
|
+
reconciled_at: reconciledAt
|
|
3195
|
+
};
|
|
3196
|
+
}
|
|
2733
3197
|
function buildChunkDocs(jsonl, facts, source, maxEntriesPerChunk, withContent = true) {
|
|
2734
3198
|
const slices = sliceIntoChunks(jsonl, maxEntriesPerChunk);
|
|
2735
3199
|
const allEntries = withContent ? buildChunkEntries(jsonl) : undefined;
|
|
@@ -3065,7 +3529,7 @@ class DryRunSink {
|
|
|
3065
3529
|
}
|
|
3066
3530
|
async putSummary(doc) {
|
|
3067
3531
|
const tools = Object.keys(doc.tool_counts).length;
|
|
3068
|
-
console.log(` [dry-run] PUT ${doc._id} (${doc.token_usage
|
|
3532
|
+
console.log(` [dry-run] PUT ${doc._id} (${doc.token_usage?.total ?? "?"} tok, ${doc.prompt_count} prompts, ${tools} tools)`);
|
|
3069
3533
|
}
|
|
3070
3534
|
async putEvents(docs) {
|
|
3071
3535
|
if (docs.length)
|
|
@@ -3097,15 +3561,18 @@ class WebapiSink {
|
|
|
3097
3561
|
return res.deleted;
|
|
3098
3562
|
}
|
|
3099
3563
|
async putSummary(doc) {
|
|
3100
|
-
await ingestSummary(doc);
|
|
3564
|
+
await ingestSummary({ ...doc });
|
|
3101
3565
|
}
|
|
3102
3566
|
async putEvents(docs) {
|
|
3103
3567
|
if (docs.length)
|
|
3104
3568
|
await ingestEvents({ docs });
|
|
3105
3569
|
}
|
|
3106
3570
|
async putChunks(docs) {
|
|
3107
|
-
if (docs.length)
|
|
3108
|
-
|
|
3571
|
+
if (!docs.length)
|
|
3572
|
+
return;
|
|
3573
|
+
await ingestChunks({
|
|
3574
|
+
docs: docs.map((d) => ({ ...d, entries: d.entries?.map((e) => ({ ...e })) }))
|
|
3575
|
+
});
|
|
3109
3576
|
}
|
|
3110
3577
|
async putTranscript(sessionId, bytes) {
|
|
3111
3578
|
await putRaw(`/api/ingest/${encodeURIComponent(sessionId)}/transcript`, bytes, "application/x-ndjson");
|
|
@@ -3270,6 +3737,138 @@ async function runBackfill(argv) {
|
|
|
3270
3737
|
return failed > 0 ? 1 : 0;
|
|
3271
3738
|
}
|
|
3272
3739
|
|
|
3740
|
+
// packages/cli/src/lib/claude-code-spec.ts
|
|
3741
|
+
import { spawnSync } from "child_process";
|
|
3742
|
+
import { createHash } from "crypto";
|
|
3743
|
+
import { existsSync, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
3744
|
+
import { join as join3 } from "path";
|
|
3745
|
+
import { gunzipSync } from "zlib";
|
|
3746
|
+
var SDK_PACKAGE = "@anthropic-ai/claude-agent-sdk";
|
|
3747
|
+
var REGISTRY = process.env.NPM_CONFIG_REGISTRY?.replace(/\/$/, "") ?? "https://registry.npmjs.org";
|
|
3748
|
+
function claudeCodeVersion(env = process.env) {
|
|
3749
|
+
const candidates = [env.CLAUDE_CODE_EXECPATH, "claude"].filter(Boolean);
|
|
3750
|
+
for (const bin of candidates) {
|
|
3751
|
+
try {
|
|
3752
|
+
const res = spawnSync(bin, ["--version"], { encoding: "utf8", timeout: 3000, cwd: "/" });
|
|
3753
|
+
const m = /^(\d+\.\d+\.\d+)/.exec(res.stdout?.trim() ?? "");
|
|
3754
|
+
if (m)
|
|
3755
|
+
return m[1];
|
|
3756
|
+
} catch {}
|
|
3757
|
+
}
|
|
3758
|
+
return null;
|
|
3759
|
+
}
|
|
3760
|
+
function cacheDir() {
|
|
3761
|
+
return join3(installPaths().dataDir, "claude-code-specs");
|
|
3762
|
+
}
|
|
3763
|
+
function cacheFile(version) {
|
|
3764
|
+
return join3(cacheDir(), `${version}.json`);
|
|
3765
|
+
}
|
|
3766
|
+
function knownHookSpec(version) {
|
|
3767
|
+
if (version === VERIFIED_HOOK_SPEC.claudeCode)
|
|
3768
|
+
return VERIFIED_HOOK_SPEC;
|
|
3769
|
+
try {
|
|
3770
|
+
const file = cacheFile(version);
|
|
3771
|
+
return existsSync(file) ? JSON.parse(readFileSync2(file, "utf8")) : null;
|
|
3772
|
+
} catch {
|
|
3773
|
+
return null;
|
|
3774
|
+
}
|
|
3775
|
+
}
|
|
3776
|
+
function cacheHookSpec(spec) {
|
|
3777
|
+
mkdirSync(cacheDir(), { recursive: true });
|
|
3778
|
+
writeFileSync(cacheFile(spec.claudeCode), `${JSON.stringify(spec, null, 2)}
|
|
3779
|
+
`);
|
|
3780
|
+
}
|
|
3781
|
+
async function getJson(url) {
|
|
3782
|
+
const res = await fetch(url, { headers: { accept: "application/json" } });
|
|
3783
|
+
if (!res.ok)
|
|
3784
|
+
throw new Error(`GET ${url} \u2192 HTTP ${res.status}`);
|
|
3785
|
+
return await res.json();
|
|
3786
|
+
}
|
|
3787
|
+
async function sdkReleases() {
|
|
3788
|
+
const doc = await getJson(`${REGISTRY}/${SDK_PACKAGE.replace("/", "%2f")}`);
|
|
3789
|
+
const releases = Object.values(doc.versions).sort((a, b) => (doc.time[a.version] ?? "").localeCompare(doc.time[b.version] ?? ""));
|
|
3790
|
+
return { releases, latest: doc["dist-tags"].latest ?? "" };
|
|
3791
|
+
}
|
|
3792
|
+
function tarEntry(tgz, path) {
|
|
3793
|
+
const tar = gunzipSync(tgz);
|
|
3794
|
+
let off = 0;
|
|
3795
|
+
while (off + 512 <= tar.length) {
|
|
3796
|
+
const name = tar.subarray(off, off + 100).toString("utf8").replace(/\0.*$/s, "");
|
|
3797
|
+
if (!name)
|
|
3798
|
+
return null;
|
|
3799
|
+
const size = Number.parseInt(tar.subarray(off + 124, off + 136).toString("utf8").trim(), 8);
|
|
3800
|
+
const body = off + 512;
|
|
3801
|
+
if (name === path)
|
|
3802
|
+
return tar.subarray(body, body + size).toString("utf8");
|
|
3803
|
+
off = body + Math.ceil(size / 512) * 512;
|
|
3804
|
+
}
|
|
3805
|
+
return null;
|
|
3806
|
+
}
|
|
3807
|
+
async function fetchHookSpec(version) {
|
|
3808
|
+
const { releases } = await sdkReleases();
|
|
3809
|
+
const release = releases.filter((r) => r.claudeCodeVersion === version).at(-1);
|
|
3810
|
+
if (!release)
|
|
3811
|
+
throw new Error(`no ${SDK_PACKAGE} release names Claude Code ${version}`);
|
|
3812
|
+
const res = await fetch(release.dist.tarball);
|
|
3813
|
+
if (!res.ok)
|
|
3814
|
+
throw new Error(`GET ${release.dist.tarball} \u2192 HTTP ${res.status}`);
|
|
3815
|
+
const tgz = new Uint8Array(await res.arrayBuffer());
|
|
3816
|
+
const integrity = release.dist.integrity;
|
|
3817
|
+
if (integrity?.startsWith("sha512-")) {
|
|
3818
|
+
const got = `sha512-${createHash("sha512").update(tgz).digest("base64")}`;
|
|
3819
|
+
if (got !== integrity)
|
|
3820
|
+
throw new Error(`${SDK_PACKAGE}@${release.version}: integrity mismatch`);
|
|
3821
|
+
}
|
|
3822
|
+
const dts = tarEntry(tgz, "package/sdk.d.ts");
|
|
3823
|
+
if (!dts)
|
|
3824
|
+
throw new Error(`${SDK_PACKAGE}@${release.version} has no sdk.d.ts`);
|
|
3825
|
+
return parseSdkHookSpec(dts, { claudeCode: version, sdk: release.version });
|
|
3826
|
+
}
|
|
3827
|
+
|
|
3828
|
+
// packages/cli/src/commands/compat.ts
|
|
3829
|
+
async function specFor(version) {
|
|
3830
|
+
const known = knownHookSpec(version);
|
|
3831
|
+
if (known)
|
|
3832
|
+
return known;
|
|
3833
|
+
const spec = await fetchHookSpec(version);
|
|
3834
|
+
cacheHookSpec(spec);
|
|
3835
|
+
return spec;
|
|
3836
|
+
}
|
|
3837
|
+
async function runCompat(argv) {
|
|
3838
|
+
const { positionals, options } = parseFlags(argv);
|
|
3839
|
+
const action = positionals[0] ?? "check";
|
|
3840
|
+
const version = strOpt(options, "cc-version") ?? claudeCodeVersion();
|
|
3841
|
+
if (!version) {
|
|
3842
|
+
console.error("compat: can't tell which Claude Code is installed \u2014 pass --cc-version");
|
|
3843
|
+
return 2;
|
|
3844
|
+
}
|
|
3845
|
+
let spec;
|
|
3846
|
+
try {
|
|
3847
|
+
spec = await specFor(version);
|
|
3848
|
+
} catch (err) {
|
|
3849
|
+
console.error(`compat: no hook spec for Claude Code ${version}: ${err.message}`);
|
|
3850
|
+
return 2;
|
|
3851
|
+
}
|
|
3852
|
+
if (action === "fetch") {
|
|
3853
|
+
if (!options.json)
|
|
3854
|
+
console.log(`compat: hook spec for Claude Code ${version} (${spec.sdk})`);
|
|
3855
|
+
return 0;
|
|
3856
|
+
}
|
|
3857
|
+
const report = compareHookSpec(HOOK_TYPES, VERIFIED_HOOK_SPEC, version, spec);
|
|
3858
|
+
if (options.json) {
|
|
3859
|
+
console.log(JSON.stringify(report, null, 2));
|
|
3860
|
+
} else {
|
|
3861
|
+
for (const line of formatHookCompat(report)) {
|
|
3862
|
+
if (!line.includes("claude-transcripts compat"))
|
|
3863
|
+
console.log(line);
|
|
3864
|
+
}
|
|
3865
|
+
console.log(`
|
|
3866
|
+
checked against: Claude Code ${version} via @anthropic-ai/claude-agent-sdk@${spec.sdk}` + `
|
|
3867
|
+
this build: verified on Claude Code ${VERIFIED_HOOK_SPEC.claudeCode}`);
|
|
3868
|
+
}
|
|
3869
|
+
return hasDrift(report) ? 1 : 0;
|
|
3870
|
+
}
|
|
3871
|
+
|
|
3273
3872
|
// packages/cli/src/commands/completions.ts
|
|
3274
3873
|
var BIN = "claude-transcripts";
|
|
3275
3874
|
async function runCompletions(argv) {
|
|
@@ -3466,13 +4065,13 @@ async function runDoctor(argv) {
|
|
|
3466
4065
|
}
|
|
3467
4066
|
|
|
3468
4067
|
// packages/cli/src/commands/export.ts
|
|
3469
|
-
import { mkdirSync as
|
|
4068
|
+
import { mkdirSync as mkdirSync3 } from "fs";
|
|
3470
4069
|
import { hostname as hostname3 } from "os";
|
|
3471
|
-
import { join as
|
|
4070
|
+
import { join as join6 } from "path";
|
|
3472
4071
|
|
|
3473
4072
|
// packages/cli/src/lib/app-config.ts
|
|
3474
|
-
import { existsSync, readFileSync as
|
|
3475
|
-
import { join as
|
|
4073
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
|
|
4074
|
+
import { join as join4 } from "path";
|
|
3476
4075
|
|
|
3477
4076
|
// packages/cli/src/lib/assets.generated.ts
|
|
3478
4077
|
var EMBEDDED_ASSETS = {
|
|
@@ -3855,7 +4454,11 @@ exec fossil server "$@"
|
|
|
3855
4454
|
"logging": {
|
|
3856
4455
|
"chunk": { "maxEntriesPerChunk": 200, "flushIntervalMs": 15000 }
|
|
3857
4456
|
},
|
|
3858
|
-
"sessions": {
|
|
4457
|
+
"sessions": {
|
|
4458
|
+
"liveWindowMs": 86400000,
|
|
4459
|
+
"idleThresholdMs": 300000,
|
|
4460
|
+
"reconcile": { "follower": true, "intervalMs": 3600000, "batchSize": 50 }
|
|
4461
|
+
},
|
|
3859
4462
|
"search": { "flushIntervalMs": 180000, "taskRetentionMs": 3600000 }
|
|
3860
4463
|
},
|
|
3861
4464
|
"couchdb": {
|
|
@@ -3913,10 +4516,10 @@ exec fossil server "$@"
|
|
|
3913
4516
|
|
|
3914
4517
|
// packages/cli/src/lib/app-config.ts
|
|
3915
4518
|
function repoConfig() {
|
|
3916
|
-
const root =
|
|
4519
|
+
const root = join4(import.meta.dir, "..", "..", "..", "..");
|
|
3917
4520
|
for (const name of ["config.json", "config.template.json"]) {
|
|
3918
|
-
const p =
|
|
3919
|
-
if (
|
|
4521
|
+
const p = join4(root, "config", name);
|
|
4522
|
+
if (existsSync2(p))
|
|
3920
4523
|
return p;
|
|
3921
4524
|
}
|
|
3922
4525
|
return null;
|
|
@@ -3926,16 +4529,16 @@ function loadAppConfig() {
|
|
|
3926
4529
|
const candidates = [paths.appConfig, repoConfig()].filter((p) => Boolean(p));
|
|
3927
4530
|
for (const path of candidates) {
|
|
3928
4531
|
try {
|
|
3929
|
-
if (
|
|
3930
|
-
return JSON.parse(
|
|
4532
|
+
if (existsSync2(path))
|
|
4533
|
+
return JSON.parse(readFileSync3(path, "utf8"));
|
|
3931
4534
|
} catch {}
|
|
3932
4535
|
}
|
|
3933
4536
|
return JSON.parse(EMBEDDED_ASSETS.appConfig.content);
|
|
3934
4537
|
}
|
|
3935
4538
|
|
|
3936
4539
|
// packages/cli/src/lib/bundle.ts
|
|
3937
|
-
import { chmodSync, existsSync as
|
|
3938
|
-
import { join as
|
|
4540
|
+
import { chmodSync, existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
|
|
4541
|
+
import { join as join5 } from "path";
|
|
3939
4542
|
var BUNDLE_FORMAT = 1;
|
|
3940
4543
|
var MANIFEST_FILE = "manifest.json";
|
|
3941
4544
|
var DOCS_FILE = "docs.ndjson";
|
|
@@ -3952,28 +4555,28 @@ function isExportableDoc(id, type) {
|
|
|
3952
4555
|
function bundlePaths(dir) {
|
|
3953
4556
|
return {
|
|
3954
4557
|
dir,
|
|
3955
|
-
manifest:
|
|
3956
|
-
docs:
|
|
3957
|
-
blobs:
|
|
3958
|
-
blobDir: (sessionId) =>
|
|
4558
|
+
manifest: join5(dir, MANIFEST_FILE),
|
|
4559
|
+
docs: join5(dir, DOCS_FILE),
|
|
4560
|
+
blobs: join5(dir, BLOBS_DIR),
|
|
4561
|
+
blobDir: (sessionId) => join5(dir, BLOBS_DIR, sessionId)
|
|
3959
4562
|
};
|
|
3960
4563
|
}
|
|
3961
4564
|
function prepareBundleDir(dir) {
|
|
3962
|
-
|
|
3963
|
-
|
|
4565
|
+
mkdirSync2(dir, { recursive: true, mode: 448 });
|
|
4566
|
+
mkdirSync2(join5(dir, BLOBS_DIR), { recursive: true, mode: 448 });
|
|
3964
4567
|
}
|
|
3965
4568
|
function writeManifest(dir, manifest) {
|
|
3966
|
-
|
|
4569
|
+
writeFileSync2(bundlePaths(dir).manifest, `${JSON.stringify(manifest, null, 2)}
|
|
3967
4570
|
`, {
|
|
3968
4571
|
mode: 384
|
|
3969
4572
|
});
|
|
3970
4573
|
}
|
|
3971
4574
|
function readManifest(dir) {
|
|
3972
4575
|
const p = bundlePaths(dir).manifest;
|
|
3973
|
-
if (!
|
|
4576
|
+
if (!existsSync3(p)) {
|
|
3974
4577
|
throw new Error(`no ${MANIFEST_FILE} in ${dir} \u2014 not a bundle, or the export was interrupted`);
|
|
3975
4578
|
}
|
|
3976
|
-
return JSON.parse(
|
|
4579
|
+
return JSON.parse(readFileSync4(p, "utf8"));
|
|
3977
4580
|
}
|
|
3978
4581
|
function toBundleDoc(doc) {
|
|
3979
4582
|
const { _rev, ...rest } = doc;
|
|
@@ -4020,7 +4623,7 @@ function formatBytes(n) {
|
|
|
4020
4623
|
|
|
4021
4624
|
// packages/cli/src/commands/export.ts
|
|
4022
4625
|
var PAGE = 500;
|
|
4023
|
-
async function
|
|
4626
|
+
async function getJson2(path) {
|
|
4024
4627
|
const res = await fetch(`${webapiUrl()}${path}`);
|
|
4025
4628
|
if (!res.ok)
|
|
4026
4629
|
throw new Error(`GET ${path} \u2192 ${res.status} ${res.statusText}`);
|
|
@@ -4028,7 +4631,7 @@ async function getJson(path) {
|
|
|
4028
4631
|
}
|
|
4029
4632
|
async function schemaVersion() {
|
|
4030
4633
|
try {
|
|
4031
|
-
const s = await
|
|
4634
|
+
const s = await getJson2("/api/migrate/status");
|
|
4032
4635
|
return s.currentVersion ?? 0;
|
|
4033
4636
|
} catch {
|
|
4034
4637
|
return 0;
|
|
@@ -4036,7 +4639,7 @@ async function schemaVersion() {
|
|
|
4036
4639
|
}
|
|
4037
4640
|
async function appVersion() {
|
|
4038
4641
|
try {
|
|
4039
|
-
const h = await
|
|
4642
|
+
const h = await getJson2("/health");
|
|
4040
4643
|
return h.version ?? "unknown";
|
|
4041
4644
|
} catch {
|
|
4042
4645
|
return "unknown";
|
|
@@ -4075,7 +4678,7 @@ async function runExport(argv) {
|
|
|
4075
4678
|
});
|
|
4076
4679
|
if (startkey)
|
|
4077
4680
|
params.set("startkey", JSON.stringify(startkey));
|
|
4078
|
-
const page = await
|
|
4681
|
+
const page = await getJson2(`/api/couch/${encodeURIComponent(db)}/_all_docs?${params}`);
|
|
4079
4682
|
const rows = page.rows ?? [];
|
|
4080
4683
|
const batch = rows.slice(0, PAGE);
|
|
4081
4684
|
if (!batch.length)
|
|
@@ -4126,8 +4729,8 @@ async function runExport(argv) {
|
|
|
4126
4729
|
const res = await fetch(`${webapiUrl()}/api/s3/sessions/${sid}/${name}`);
|
|
4127
4730
|
if (!res.ok || !res.body)
|
|
4128
4731
|
continue;
|
|
4129
|
-
|
|
4130
|
-
const target =
|
|
4732
|
+
mkdirSync3(paths.blobDir(sid), { recursive: true, mode: 448 });
|
|
4733
|
+
const target = join6(paths.blobDir(sid), name);
|
|
4131
4734
|
const { bytes, sha256 } = await streamToFile(res.body, target);
|
|
4132
4735
|
checksums[`blobs/${sid}/${name}`] = sha256;
|
|
4133
4736
|
blobCount++;
|
|
@@ -4160,7 +4763,7 @@ async function runExport(argv) {
|
|
|
4160
4763
|
}
|
|
4161
4764
|
|
|
4162
4765
|
// packages/cli/src/commands/hook.ts
|
|
4163
|
-
import { existsSync as
|
|
4766
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "fs";
|
|
4164
4767
|
import { dirname as dirname2 } from "path";
|
|
4165
4768
|
|
|
4166
4769
|
// packages/cli/src/lib/statusline.ts
|
|
@@ -4349,11 +4952,11 @@ function osc8(url, text) {
|
|
|
4349
4952
|
import {
|
|
4350
4953
|
closeSync,
|
|
4351
4954
|
openSync,
|
|
4352
|
-
readFileSync as
|
|
4955
|
+
readFileSync as readFileSync5,
|
|
4353
4956
|
readSync,
|
|
4354
4957
|
statSync as statSync2,
|
|
4355
4958
|
unlinkSync,
|
|
4356
|
-
writeFileSync as
|
|
4959
|
+
writeFileSync as writeFileSync3
|
|
4357
4960
|
} from "fs";
|
|
4358
4961
|
import { hostname as hostname4 } from "os";
|
|
4359
4962
|
var {S3Client } = globalThis.Bun;
|
|
@@ -4383,6 +4986,26 @@ function uploadArgv(sessionId, transcriptPath) {
|
|
|
4383
4986
|
return [...cliArgv(), "hook", "upload", "--session", sessionId, "--transcript", transcriptPath];
|
|
4384
4987
|
}
|
|
4385
4988
|
|
|
4989
|
+
// packages/cli/src/hook/compat.ts
|
|
4990
|
+
var REAL = {
|
|
4991
|
+
version: () => claudeCodeVersion(),
|
|
4992
|
+
spec: knownHookSpec,
|
|
4993
|
+
fetch: (version) => spawnDetached([...cliArgv(), "compat", "fetch", "--cc-version", version])
|
|
4994
|
+
};
|
|
4995
|
+
function hookCompatLines(deps = REAL) {
|
|
4996
|
+
try {
|
|
4997
|
+
const version = deps.version();
|
|
4998
|
+
if (!version)
|
|
4999
|
+
return ["? Hook spec not checked \u2014 `claude --version` gave no answer"];
|
|
5000
|
+
const spec = deps.spec(version);
|
|
5001
|
+
if (!spec)
|
|
5002
|
+
deps.fetch(version);
|
|
5003
|
+
return formatHookCompat(compareHookSpec(HOOK_TYPES, VERIFIED_HOOK_SPEC, version, spec));
|
|
5004
|
+
} catch {
|
|
5005
|
+
return [];
|
|
5006
|
+
}
|
|
5007
|
+
}
|
|
5008
|
+
|
|
4386
5009
|
// packages/cli/src/hook/mirror.ts
|
|
4387
5010
|
var DEFAULT_TIMEOUT_MS = 2000;
|
|
4388
5011
|
var DEFAULT_BLOB_TIMEOUT_MS = 60000;
|
|
@@ -4508,7 +5131,7 @@ function normalizeHookConfig(raw) {
|
|
|
4508
5131
|
}
|
|
4509
5132
|
function loadHookConfig(path) {
|
|
4510
5133
|
try {
|
|
4511
|
-
const raw = JSON.parse(
|
|
5134
|
+
const raw = JSON.parse(readFileSync5(path, "utf8"));
|
|
4512
5135
|
if (!raw?.couch?.url)
|
|
4513
5136
|
return null;
|
|
4514
5137
|
return normalizeHookConfig(raw);
|
|
@@ -4619,14 +5242,14 @@ function makeCounts(sessionId) {
|
|
|
4619
5242
|
const file = `/tmp/claude-transcripts-${sessionId}.counts`;
|
|
4620
5243
|
const read = () => {
|
|
4621
5244
|
try {
|
|
4622
|
-
return JSON.parse(
|
|
5245
|
+
return JSON.parse(readFileSync5(file, "utf8"));
|
|
4623
5246
|
} catch {
|
|
4624
5247
|
return emptyCounts();
|
|
4625
5248
|
}
|
|
4626
5249
|
};
|
|
4627
5250
|
const write = (c) => {
|
|
4628
5251
|
try {
|
|
4629
|
-
|
|
5252
|
+
writeFileSync3(file, JSON.stringify(c));
|
|
4630
5253
|
} catch {}
|
|
4631
5254
|
};
|
|
4632
5255
|
return {
|
|
@@ -4656,14 +5279,14 @@ function makeTargets(sessionId) {
|
|
|
4656
5279
|
const file = targetsFile(sessionId);
|
|
4657
5280
|
const read = () => {
|
|
4658
5281
|
try {
|
|
4659
|
-
return JSON.parse(
|
|
5282
|
+
return JSON.parse(readFileSync5(file, "utf8"));
|
|
4660
5283
|
} catch {
|
|
4661
5284
|
return null;
|
|
4662
5285
|
}
|
|
4663
5286
|
};
|
|
4664
5287
|
const write = (t) => {
|
|
4665
5288
|
try {
|
|
4666
|
-
|
|
5289
|
+
writeFileSync3(file, JSON.stringify(t));
|
|
4667
5290
|
} catch {}
|
|
4668
5291
|
};
|
|
4669
5292
|
return {
|
|
@@ -4743,14 +5366,14 @@ function makeChunkState(sessionId) {
|
|
|
4743
5366
|
const lockFile = `/tmp/claude-transcripts-${sessionId}.chunklock`;
|
|
4744
5367
|
const load = () => {
|
|
4745
5368
|
try {
|
|
4746
|
-
return JSON.parse(
|
|
5369
|
+
return JSON.parse(readFileSync5(stateFile, "utf8"));
|
|
4747
5370
|
} catch {
|
|
4748
5371
|
return { offset: 0, lastFlushMs: 0 };
|
|
4749
5372
|
}
|
|
4750
5373
|
};
|
|
4751
5374
|
const save = (s) => {
|
|
4752
5375
|
try {
|
|
4753
|
-
|
|
5376
|
+
writeFileSync3(stateFile, JSON.stringify(s));
|
|
4754
5377
|
} catch {}
|
|
4755
5378
|
};
|
|
4756
5379
|
return {
|
|
@@ -4826,6 +5449,7 @@ function buildContext(payload, config) {
|
|
|
4826
5449
|
const sessionsDb = config.couch.databases.sessions ?? DEFAULT_SESSIONS_DB;
|
|
4827
5450
|
return {
|
|
4828
5451
|
output: {},
|
|
5452
|
+
hookCompat: () => hookCompatLines(),
|
|
4829
5453
|
chunkHighWater: () => fetchChunkHighWater(config, sessionsDb, sessionId),
|
|
4830
5454
|
event,
|
|
4831
5455
|
sessionId,
|
|
@@ -4869,10 +5493,12 @@ function recordingBanner(targets, sessionId) {
|
|
|
4869
5493
|
}
|
|
4870
5494
|
var NOT_RECORDING_BANNER = "\u25CB Claude Transcripts not recording \u2014 no instance configured. Run `claude-transcripts install`.";
|
|
4871
5495
|
function sessionStartEnvelope(out) {
|
|
4872
|
-
|
|
5496
|
+
const systemMessage = [out.systemMessage, ...out.notices ?? []].filter(Boolean).join(`
|
|
5497
|
+
`);
|
|
5498
|
+
if (!systemMessage && !out.additionalContext)
|
|
4873
5499
|
return null;
|
|
4874
5500
|
return {
|
|
4875
|
-
...
|
|
5501
|
+
...systemMessage ? { systemMessage } : {},
|
|
4876
5502
|
...out.additionalContext ? {
|
|
4877
5503
|
hookSpecificOutput: {
|
|
4878
5504
|
hookEventName: "SessionStart",
|
|
@@ -4891,7 +5517,7 @@ function emitSessionStart(out) {
|
|
|
4891
5517
|
}
|
|
4892
5518
|
|
|
4893
5519
|
// packages/cli/src/hook/handlers.ts
|
|
4894
|
-
import { readFileSync as
|
|
5520
|
+
import { readFileSync as readFileSync6, statSync as statSync3 } from "fs";
|
|
4895
5521
|
|
|
4896
5522
|
// packages/cli/src/hook/recall.ts
|
|
4897
5523
|
var FACTS_TIMEOUT_MS = 2000;
|
|
@@ -4981,6 +5607,13 @@ var announceRecording = async (ctx) => {
|
|
|
4981
5607
|
const targets = ctx.targets.read() ?? resolveTargets(ctx.config, safeWebapiUrl());
|
|
4982
5608
|
ctx.output.systemMessage = recordingBanner(targets, ctx.sessionId);
|
|
4983
5609
|
};
|
|
5610
|
+
var checkHookCompat = async (ctx) => {
|
|
5611
|
+
if (ctx.payload?.source === "compact")
|
|
5612
|
+
return;
|
|
5613
|
+
const lines = ctx.hookCompat();
|
|
5614
|
+
if (lines.length)
|
|
5615
|
+
ctx.output.notices = [...ctx.output.notices ?? [], ...lines];
|
|
5616
|
+
};
|
|
4984
5617
|
var injectRecallPolicy = async (ctx) => {
|
|
4985
5618
|
const primer = await buildPrimer(ctx);
|
|
4986
5619
|
if (primer)
|
|
@@ -5109,7 +5742,7 @@ var writeSummary = async (ctx) => {
|
|
|
5109
5742
|
let transcriptBytes = 0;
|
|
5110
5743
|
if (ctx.transcriptPath) {
|
|
5111
5744
|
try {
|
|
5112
|
-
jsonl =
|
|
5745
|
+
jsonl = readFileSync6(ctx.transcriptPath, "utf8");
|
|
5113
5746
|
transcriptBytes = Buffer.byteLength(jsonl);
|
|
5114
5747
|
} catch {}
|
|
5115
5748
|
}
|
|
@@ -5135,7 +5768,7 @@ var writeSummary = async (ctx) => {
|
|
|
5135
5768
|
async function uploadTranscript(blob, bucket, sessionId, transcriptPath) {
|
|
5136
5769
|
let data;
|
|
5137
5770
|
try {
|
|
5138
|
-
data =
|
|
5771
|
+
data = readFileSync6(transcriptPath, "utf8");
|
|
5139
5772
|
} catch {
|
|
5140
5773
|
return;
|
|
5141
5774
|
}
|
|
@@ -5151,6 +5784,7 @@ var uploadBlobs = async (ctx) => {
|
|
|
5151
5784
|
var HANDLERS = {
|
|
5152
5785
|
"seed-session-start": seedSessionStart,
|
|
5153
5786
|
"announce-recording": announceRecording,
|
|
5787
|
+
"check-hook-compat": checkHookCompat,
|
|
5154
5788
|
"inject-recall-policy": injectRecallPolicy,
|
|
5155
5789
|
"update-counts": updateCounts,
|
|
5156
5790
|
"write-event-marker": writeEventMarker,
|
|
@@ -5215,35 +5849,35 @@ async function dispatch(raw, configPath) {
|
|
|
5215
5849
|
}
|
|
5216
5850
|
|
|
5217
5851
|
// packages/cli/src/lib/plugin.ts
|
|
5218
|
-
import { readFileSync as
|
|
5219
|
-
import { dirname, join as
|
|
5852
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
5853
|
+
import { dirname, join as join7 } from "path";
|
|
5220
5854
|
var PLUGIN_NAME = "claude-transcripts";
|
|
5221
5855
|
function readJson(path) {
|
|
5222
5856
|
try {
|
|
5223
|
-
const parsed = JSON.parse(
|
|
5857
|
+
const parsed = JSON.parse(readFileSync7(path, "utf8"));
|
|
5224
5858
|
return parsed && typeof parsed === "object" ? parsed : null;
|
|
5225
5859
|
} catch {
|
|
5226
5860
|
return null;
|
|
5227
5861
|
}
|
|
5228
5862
|
}
|
|
5229
5863
|
function pluginsDir(settingsPath) {
|
|
5230
|
-
return
|
|
5864
|
+
return join7(dirname(settingsPath), "plugins");
|
|
5231
5865
|
}
|
|
5232
5866
|
function pluginEvents(settingsPath, key) {
|
|
5233
|
-
const installed = readJson(
|
|
5867
|
+
const installed = readJson(join7(pluginsDir(settingsPath), "installed_plugins.json"));
|
|
5234
5868
|
const entry = installed?.plugins?.[key];
|
|
5235
5869
|
const first = Array.isArray(entry) ? entry[0] : undefined;
|
|
5236
5870
|
const installPath = typeof first?.installPath === "string" ? first.installPath : undefined;
|
|
5237
5871
|
if (!installPath)
|
|
5238
5872
|
return;
|
|
5239
|
-
const hooks = readJson(
|
|
5873
|
+
const hooks = readJson(join7(installPath, "hooks", "hooks.json"));
|
|
5240
5874
|
const events = hooks?.hooks;
|
|
5241
5875
|
if (!events || typeof events !== "object")
|
|
5242
5876
|
return;
|
|
5243
5877
|
return Object.keys(events);
|
|
5244
5878
|
}
|
|
5245
5879
|
function pluginVersion(settingsPath, key) {
|
|
5246
|
-
const installed = readJson(
|
|
5880
|
+
const installed = readJson(join7(pluginsDir(settingsPath), "installed_plugins.json"));
|
|
5247
5881
|
const entry = installed?.plugins?.[key];
|
|
5248
5882
|
const first = Array.isArray(entry) ? entry[0] : undefined;
|
|
5249
5883
|
return typeof first?.version === "string" ? first.version : undefined;
|
|
@@ -5269,7 +5903,7 @@ function pluginRegistration(settingsPath, settings) {
|
|
|
5269
5903
|
// packages/cli/src/commands/hook.ts
|
|
5270
5904
|
function readSettings(path) {
|
|
5271
5905
|
try {
|
|
5272
|
-
return JSON.parse(
|
|
5906
|
+
return JSON.parse(readFileSync8(path, "utf8"));
|
|
5273
5907
|
} catch {
|
|
5274
5908
|
return {};
|
|
5275
5909
|
}
|
|
@@ -5366,8 +6000,8 @@ function install(argv) {
|
|
|
5366
6000
|
console.log(`hook: would register ${added} event(s)` + `${replaced ? `, replacing ${replaced} stale one(s)` : ""}` + `${corrected ? `, correcting ${corrected} existing one(s)` : ""} in ${paths.claudeSettings}`);
|
|
5367
6001
|
return 0;
|
|
5368
6002
|
}
|
|
5369
|
-
|
|
5370
|
-
|
|
6003
|
+
mkdirSync4(dirname2(paths.claudeSettings), { recursive: true });
|
|
6004
|
+
writeFileSync4(paths.claudeSettings, `${JSON.stringify({ ...settings, hooks }, null, 2)}
|
|
5371
6005
|
`);
|
|
5372
6006
|
const notes = [
|
|
5373
6007
|
replaced ? `replaced ${replaced} stale registration(s)` : "",
|
|
@@ -5398,7 +6032,7 @@ function uninstall() {
|
|
|
5398
6032
|
if (kept.length)
|
|
5399
6033
|
hooks[event] = kept;
|
|
5400
6034
|
}
|
|
5401
|
-
|
|
6035
|
+
writeFileSync4(paths.claudeSettings, `${JSON.stringify({ ...settings, hooks }, null, 2)}
|
|
5402
6036
|
`);
|
|
5403
6037
|
console.log(`hook: removed ${removed} registration(s) from ${paths.claudeSettings}`);
|
|
5404
6038
|
const plugin = pluginRegistration(paths.claudeSettings, settings);
|
|
@@ -5408,7 +6042,7 @@ function uninstall() {
|
|
|
5408
6042
|
}
|
|
5409
6043
|
function configuredMirrors(path) {
|
|
5410
6044
|
try {
|
|
5411
|
-
const parsed = JSON.parse(
|
|
6045
|
+
const parsed = JSON.parse(readFileSync8(path, "utf8"));
|
|
5412
6046
|
return (parsed.mirrors ?? []).map((m) => m?.url ?? "(no url)");
|
|
5413
6047
|
} catch {
|
|
5414
6048
|
return [];
|
|
@@ -5425,7 +6059,7 @@ function status() {
|
|
|
5425
6059
|
const found = findRegistered(settings);
|
|
5426
6060
|
const plugin = pluginRegistration(paths.claudeSettings, settings);
|
|
5427
6061
|
console.log(`hook: settings ${paths.claudeSettings}`);
|
|
5428
|
-
console.log(`hook: config ${paths.hookConfig} ${
|
|
6062
|
+
console.log(`hook: config ${paths.hookConfig} ${existsSync4(paths.hookConfig) ? "" : "(MISSING \u2014 run `install`)"}`);
|
|
5429
6063
|
console.log(`hook: command ${hookCommand()}`);
|
|
5430
6064
|
if (plugin)
|
|
5431
6065
|
console.log(`hook: plugin ${pluginSummary(plugin)}`);
|
|
@@ -5499,8 +6133,8 @@ async function runHook(argv) {
|
|
|
5499
6133
|
}
|
|
5500
6134
|
|
|
5501
6135
|
// packages/cli/src/commands/import.ts
|
|
5502
|
-
import { existsSync as
|
|
5503
|
-
import { join as
|
|
6136
|
+
import { existsSync as existsSync5, statSync as statSync4 } from "fs";
|
|
6137
|
+
import { join as join8 } from "path";
|
|
5504
6138
|
var BATCH = 500;
|
|
5505
6139
|
function num(n) {
|
|
5506
6140
|
return n.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
@@ -5570,8 +6204,8 @@ function checkSchema(bundleVersion, target, registry = REAL_REGISTRY) {
|
|
|
5570
6204
|
async function verifyChecksums(dir, manifest) {
|
|
5571
6205
|
const bad = [];
|
|
5572
6206
|
for (const [rel, expected] of Object.entries(manifest.checksums)) {
|
|
5573
|
-
const path =
|
|
5574
|
-
if (!
|
|
6207
|
+
const path = join8(dir, rel);
|
|
6208
|
+
if (!existsSync5(path)) {
|
|
5575
6209
|
bad.push(`${rel} (missing)`);
|
|
5576
6210
|
continue;
|
|
5577
6211
|
}
|
|
@@ -5704,7 +6338,7 @@ async function runImport(argv) {
|
|
|
5704
6338
|
let blobs = 0;
|
|
5705
6339
|
let blobBytes = 0;
|
|
5706
6340
|
let skipped = 0;
|
|
5707
|
-
if (withBlobs &&
|
|
6341
|
+
if (withBlobs && existsSync5(paths.blobs)) {
|
|
5708
6342
|
for (const rel of Object.keys(manifest.checksums)) {
|
|
5709
6343
|
if (!rel.startsWith("blobs/"))
|
|
5710
6344
|
continue;
|
|
@@ -5715,7 +6349,7 @@ async function runImport(argv) {
|
|
|
5715
6349
|
skipped++;
|
|
5716
6350
|
continue;
|
|
5717
6351
|
}
|
|
5718
|
-
const path =
|
|
6352
|
+
const path = join8(dir, rel);
|
|
5719
6353
|
await putStream(`/api/ingest/${sessionId}/transcript`, path, "application/x-ndjson");
|
|
5720
6354
|
blobs++;
|
|
5721
6355
|
blobBytes += statSync4(path).size;
|
|
@@ -5739,58 +6373,58 @@ async function runImport(argv) {
|
|
|
5739
6373
|
}
|
|
5740
6374
|
|
|
5741
6375
|
// packages/cli/src/commands/install.ts
|
|
5742
|
-
import { existsSync as
|
|
5743
|
-
import { join as
|
|
6376
|
+
import { existsSync as existsSync9, readFileSync as readFileSync13 } from "fs";
|
|
6377
|
+
import { join as join10 } from "path";
|
|
5744
6378
|
|
|
5745
6379
|
// packages/cli/src/lib/assets.ts
|
|
5746
6380
|
import {
|
|
5747
|
-
existsSync as
|
|
6381
|
+
existsSync as existsSync6,
|
|
5748
6382
|
lstatSync,
|
|
5749
|
-
mkdirSync as
|
|
5750
|
-
readFileSync as
|
|
6383
|
+
mkdirSync as mkdirSync5,
|
|
6384
|
+
readFileSync as readFileSync9,
|
|
5751
6385
|
symlinkSync,
|
|
5752
6386
|
unlinkSync as unlinkSync2,
|
|
5753
|
-
writeFileSync as
|
|
6387
|
+
writeFileSync as writeFileSync5
|
|
5754
6388
|
} from "fs";
|
|
5755
|
-
import { dirname as dirname3, join as
|
|
6389
|
+
import { dirname as dirname3, join as join9 } from "path";
|
|
5756
6390
|
function writeAssets(paths) {
|
|
5757
6391
|
const out = [];
|
|
5758
6392
|
for (const asset of Object.values(EMBEDDED_ASSETS)) {
|
|
5759
6393
|
const isUserOwned = asset.path.startsWith("config/");
|
|
5760
|
-
const target = isUserOwned ? paths.appConfig :
|
|
5761
|
-
if (isUserOwned &&
|
|
6394
|
+
const target = isUserOwned ? paths.appConfig : join9(paths.dataDir, asset.path);
|
|
6395
|
+
if (isUserOwned && existsSync6(target)) {
|
|
5762
6396
|
out.push({ path: target, changed: false });
|
|
5763
6397
|
continue;
|
|
5764
6398
|
}
|
|
5765
|
-
const previous =
|
|
6399
|
+
const previous = existsSync6(target) ? readFileSync9(target, "utf8") : null;
|
|
5766
6400
|
if (previous === asset.content) {
|
|
5767
6401
|
out.push({ path: target, changed: false });
|
|
5768
6402
|
continue;
|
|
5769
6403
|
}
|
|
5770
|
-
|
|
5771
|
-
|
|
6404
|
+
mkdirSync5(dirname3(target), { recursive: true });
|
|
6405
|
+
writeFileSync5(target, asset.content);
|
|
5772
6406
|
out.push({ path: target, changed: true });
|
|
5773
6407
|
}
|
|
5774
6408
|
return out;
|
|
5775
6409
|
}
|
|
5776
6410
|
function linkInstanceEnv(paths) {
|
|
5777
|
-
const link =
|
|
6411
|
+
const link = join9(paths.dataDir, ".env");
|
|
5778
6412
|
try {
|
|
5779
6413
|
if (lstatSync(link, { throwIfNoEntry: false }))
|
|
5780
6414
|
unlinkSync2(link);
|
|
5781
6415
|
} catch {}
|
|
5782
|
-
|
|
6416
|
+
mkdirSync5(paths.dataDir, { recursive: true });
|
|
5783
6417
|
symlinkSync(paths.instanceEnv, link);
|
|
5784
6418
|
}
|
|
5785
6419
|
function writeVersionStamp(paths, version) {
|
|
5786
|
-
|
|
5787
|
-
|
|
6420
|
+
mkdirSync5(dirname3(paths.versionFile), { recursive: true });
|
|
6421
|
+
writeFileSync5(paths.versionFile, `${version}
|
|
5788
6422
|
`);
|
|
5789
6423
|
}
|
|
5790
6424
|
function composeFiles(paths, upstream) {
|
|
5791
|
-
const files = [
|
|
6425
|
+
const files = [join9(paths.dataDir, EMBEDDED_ASSETS.compose.path)];
|
|
5792
6426
|
if (upstream)
|
|
5793
|
-
files.push(
|
|
6427
|
+
files.push(join9(paths.dataDir, EMBEDDED_ASSETS.composeUpstream.path));
|
|
5794
6428
|
return files;
|
|
5795
6429
|
}
|
|
5796
6430
|
|
|
@@ -5858,7 +6492,7 @@ function healthTargets(env) {
|
|
|
5858
6492
|
}
|
|
5859
6493
|
|
|
5860
6494
|
// packages/cli/src/lib/hook-config.ts
|
|
5861
|
-
import { chmodSync as chmodSync2, mkdirSync as
|
|
6495
|
+
import { chmodSync as chmodSync2, mkdirSync as mkdirSync6, readFileSync as readFileSync10, writeFileSync as writeFileSync6 } from "fs";
|
|
5862
6496
|
import { dirname as dirname4 } from "path";
|
|
5863
6497
|
function buildHookConfig(app, env) {
|
|
5864
6498
|
const user = env.COUCHDB_USER;
|
|
@@ -5892,7 +6526,7 @@ function webapiFromEnv(env) {
|
|
|
5892
6526
|
var PRESERVED_KEYS = ["mirrors", "webapi"];
|
|
5893
6527
|
function readExisting(path) {
|
|
5894
6528
|
try {
|
|
5895
|
-
const parsed = JSON.parse(
|
|
6529
|
+
const parsed = JSON.parse(readFileSync10(path, "utf8"));
|
|
5896
6530
|
return parsed && typeof parsed === "object" ? parsed : {};
|
|
5897
6531
|
} catch {
|
|
5898
6532
|
return {};
|
|
@@ -5908,8 +6542,8 @@ function mergePreserved(next, existing) {
|
|
|
5908
6542
|
}
|
|
5909
6543
|
function writeHookConfig(path, config) {
|
|
5910
6544
|
const merged = mergePreserved(config ?? {}, readExisting(path));
|
|
5911
|
-
|
|
5912
|
-
|
|
6545
|
+
mkdirSync6(dirname4(path), { recursive: true });
|
|
6546
|
+
writeFileSync6(path, `${JSON.stringify(merged, null, 2)}
|
|
5913
6547
|
`);
|
|
5914
6548
|
chmodSync2(path, 384);
|
|
5915
6549
|
}
|
|
@@ -5986,7 +6620,7 @@ function renderPruneReport(report, repo) {
|
|
|
5986
6620
|
|
|
5987
6621
|
// packages/cli/src/lib/instance-env.ts
|
|
5988
6622
|
import { randomBytes } from "crypto";
|
|
5989
|
-
import { chmodSync as chmodSync3, existsSync as
|
|
6623
|
+
import { chmodSync as chmodSync3, existsSync as existsSync7, mkdirSync as mkdirSync7, readFileSync as readFileSync11, writeFileSync as writeFileSync7 } from "fs";
|
|
5990
6624
|
import { dirname as dirname5 } from "path";
|
|
5991
6625
|
var DEFAULT_PORT_BASE = 7650;
|
|
5992
6626
|
var PORT_KEYS = [
|
|
@@ -6086,7 +6720,7 @@ function backfillPorts(existing) {
|
|
|
6086
6720
|
}
|
|
6087
6721
|
function loadOrCreateInstanceEnv(path, opts = {}) {
|
|
6088
6722
|
const generated = buildInstanceEnv(opts);
|
|
6089
|
-
const existing =
|
|
6723
|
+
const existing = existsSync7(path) ? parseEnv(readFileSync11(path, "utf8")) : {};
|
|
6090
6724
|
const merged = { ...generated, ...backfillPorts(existing), ...existing };
|
|
6091
6725
|
for (const key of INSTALLER_OWNED) {
|
|
6092
6726
|
if (generated[key] !== undefined)
|
|
@@ -6100,12 +6734,12 @@ function loadOrCreateInstanceEnv(path, opts = {}) {
|
|
|
6100
6734
|
return merged;
|
|
6101
6735
|
}
|
|
6102
6736
|
function writeInstanceEnv(path, env) {
|
|
6103
|
-
|
|
6104
|
-
|
|
6737
|
+
mkdirSync7(dirname5(path), { recursive: true });
|
|
6738
|
+
writeFileSync7(path, formatEnv(env, HEADER));
|
|
6105
6739
|
chmodSync3(path, 384);
|
|
6106
6740
|
}
|
|
6107
6741
|
function updateInstanceEnv(path, patch) {
|
|
6108
|
-
const env =
|
|
6742
|
+
const env = existsSync7(path) ? parseEnv(readFileSync11(path, "utf8")) : {};
|
|
6109
6743
|
const next = { ...env, ...patch };
|
|
6110
6744
|
writeInstanceEnv(path, next);
|
|
6111
6745
|
return next;
|
|
@@ -6387,11 +7021,11 @@ async function removeStaleKeys(api, keep) {
|
|
|
6387
7021
|
}
|
|
6388
7022
|
|
|
6389
7023
|
// packages/cli/src/commands/statusline.ts
|
|
6390
|
-
import { existsSync as
|
|
7024
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync8, readFileSync as readFileSync12, writeFileSync as writeFileSync8 } from "fs";
|
|
6391
7025
|
import { dirname as dirname6 } from "path";
|
|
6392
7026
|
function readSettings2(path) {
|
|
6393
7027
|
try {
|
|
6394
|
-
return JSON.parse(
|
|
7028
|
+
return JSON.parse(readFileSync12(path, "utf8"));
|
|
6395
7029
|
} catch {
|
|
6396
7030
|
return {};
|
|
6397
7031
|
}
|
|
@@ -6409,7 +7043,7 @@ async function render() {
|
|
|
6409
7043
|
try {
|
|
6410
7044
|
input = JSON.parse(await Bun.stdin.text());
|
|
6411
7045
|
} catch {}
|
|
6412
|
-
const configured =
|
|
7046
|
+
const configured = existsSync8(installPaths().hookConfig);
|
|
6413
7047
|
const id = input.session_id;
|
|
6414
7048
|
const targets = id ? makeTargets(id).read() : null;
|
|
6415
7049
|
const line = renderStatusline({
|
|
@@ -6454,8 +7088,8 @@ function install2(argv) {
|
|
|
6454
7088
|
console.log(` ${JSON.stringify(next)}`);
|
|
6455
7089
|
return 0;
|
|
6456
7090
|
}
|
|
6457
|
-
|
|
6458
|
-
|
|
7091
|
+
mkdirSync8(dirname6(paths.claudeSettings), { recursive: true });
|
|
7092
|
+
writeFileSync8(paths.claudeSettings, `${JSON.stringify({ ...settings, statusLine: next }, null, 2)}
|
|
6459
7093
|
`);
|
|
6460
7094
|
console.log(`statusline: registered \u2192 ${paths.claudeSettings}`);
|
|
6461
7095
|
console.log("statusline: restart any open Claude Code session for it to take effect.");
|
|
@@ -6473,7 +7107,7 @@ function uninstall2() {
|
|
|
6473
7107
|
return 1;
|
|
6474
7108
|
}
|
|
6475
7109
|
const { statusLine: _drop, ...rest } = settings;
|
|
6476
|
-
|
|
7110
|
+
writeFileSync8(paths.claudeSettings, `${JSON.stringify(rest, null, 2)}
|
|
6477
7111
|
`);
|
|
6478
7112
|
console.log(`statusline: removed from ${paths.claudeSettings}`);
|
|
6479
7113
|
return 0;
|
|
@@ -6597,7 +7231,7 @@ async function runInstall(argv) {
|
|
|
6597
7231
|
const noPrune = options["no-prune"] === true;
|
|
6598
7232
|
const portBaseOpt = strOpt(options, "port-base");
|
|
6599
7233
|
const paths = installPaths();
|
|
6600
|
-
const priorAppTag =
|
|
7234
|
+
const priorAppTag = existsSync9(paths.instanceEnv) ? /^APP_TAG=(\S+)\s*$/m.exec(readFileSync13(paths.instanceEnv, "utf8"))?.[1] : undefined;
|
|
6601
7235
|
const TOTAL = 8;
|
|
6602
7236
|
console.log(`claude-transcripts install (${VERSION})`);
|
|
6603
7237
|
console.log(` config ${paths.configDir}`);
|
|
@@ -6625,8 +7259,8 @@ async function runInstall(argv) {
|
|
|
6625
7259
|
portBase = found;
|
|
6626
7260
|
}
|
|
6627
7261
|
step(2, TOTAL, "Configuration");
|
|
6628
|
-
const firstRun = !
|
|
6629
|
-
const priorKeys = new Set(firstRun ? [] : Object.keys(parseEnv(
|
|
7262
|
+
const firstRun = !existsSync9(paths.instanceEnv);
|
|
7263
|
+
const priorKeys = new Set(firstRun ? [] : Object.keys(parseEnv(readFileSync13(paths.instanceEnv, "utf8"))));
|
|
6630
7264
|
let env = loadOrCreateInstanceEnv(paths.instanceEnv, {
|
|
6631
7265
|
portBase,
|
|
6632
7266
|
appTag: VERSION === DEV_VERSION ? "main" : VERSION,
|
|
@@ -6730,7 +7364,7 @@ async function runInstall(argv) {
|
|
|
6730
7364
|
install: done.`);
|
|
6731
7365
|
console.log(` UI http://127.0.0.1:${env.WEBAPI_PORT}/app`);
|
|
6732
7366
|
console.log(` API http://127.0.0.1:${env.WEBAPI_PORT}/api/docs`);
|
|
6733
|
-
console.log(` data ${
|
|
7367
|
+
console.log(` data ${join10(paths.deployDir, "data")} (survives uninstall unless --purge)`);
|
|
6734
7368
|
console.log(" remove claude-transcripts uninstall");
|
|
6735
7369
|
return 0;
|
|
6736
7370
|
}
|
|
@@ -6790,10 +7424,10 @@ async function runMigrate(argv) {
|
|
|
6790
7424
|
}
|
|
6791
7425
|
|
|
6792
7426
|
// packages/cli/src/commands/provision.ts
|
|
6793
|
-
import { existsSync as
|
|
7427
|
+
import { existsSync as existsSync10 } from "fs";
|
|
6794
7428
|
async function runProvision(_argv) {
|
|
6795
7429
|
const paths = installPaths();
|
|
6796
|
-
if (!
|
|
7430
|
+
if (!existsSync10(paths.instanceEnv)) {
|
|
6797
7431
|
console.error("provision: this machine has no instance \u2014 run `claude-transcripts install` first.");
|
|
6798
7432
|
return 1;
|
|
6799
7433
|
}
|
|
@@ -6824,6 +7458,112 @@ async function runProvision(_argv) {
|
|
|
6824
7458
|
return ok ? 0 : 1;
|
|
6825
7459
|
}
|
|
6826
7460
|
|
|
7461
|
+
// packages/cli/src/commands/reconcile.ts
|
|
7462
|
+
var PAGE2 = 500;
|
|
7463
|
+
async function incompleteSessions() {
|
|
7464
|
+
const out = [];
|
|
7465
|
+
for (let skip = 0;; skip += PAGE2) {
|
|
7466
|
+
const page = await listSessions({ status: "incomplete", limit: PAGE2, skip });
|
|
7467
|
+
out.push(...page.sessions);
|
|
7468
|
+
if (out.length >= page.totalCount || page.sessions.length === 0)
|
|
7469
|
+
return out;
|
|
7470
|
+
}
|
|
7471
|
+
}
|
|
7472
|
+
async function sessionOrNull(id) {
|
|
7473
|
+
try {
|
|
7474
|
+
return await getSession(id);
|
|
7475
|
+
} catch (err) {
|
|
7476
|
+
if (isConnectionError(err))
|
|
7477
|
+
throw err;
|
|
7478
|
+
return null;
|
|
7479
|
+
}
|
|
7480
|
+
}
|
|
7481
|
+
async function reconcileOne(s, opts) {
|
|
7482
|
+
const sessionId = s.sessionId;
|
|
7483
|
+
if (s.status === "ended")
|
|
7484
|
+
return { sessionId, result: "skipped", reason: "already ended" };
|
|
7485
|
+
if (s.status === "running" && !opts.force) {
|
|
7486
|
+
return { sessionId, result: "skipped", reason: "still running (--force to finalise it)" };
|
|
7487
|
+
}
|
|
7488
|
+
try {
|
|
7489
|
+
const transcript = opts.blobs ? await getTextOrNull(`/api/s3/sessions/${encodeURIComponent(sessionId)}/transcript.jsonl`) : null;
|
|
7490
|
+
const doc = buildReconciledSummary(s, transcript, opts.now);
|
|
7491
|
+
if (!opts.dryRun)
|
|
7492
|
+
await opts.sink.putSummary(doc);
|
|
7493
|
+
return {
|
|
7494
|
+
sessionId,
|
|
7495
|
+
result: opts.dryRun ? "would-reconcile" : "reconciled",
|
|
7496
|
+
tokens: transcript !== null
|
|
7497
|
+
};
|
|
7498
|
+
} catch (err) {
|
|
7499
|
+
return { sessionId, result: "failed", reason: err.message };
|
|
7500
|
+
}
|
|
7501
|
+
}
|
|
7502
|
+
function describe(o) {
|
|
7503
|
+
const tokens = o.tokens === false ? " (no S3 transcript \u2014 no token totals)" : "";
|
|
7504
|
+
switch (o.result) {
|
|
7505
|
+
case "reconciled":
|
|
7506
|
+
return ` finalised ${o.sessionId}${tokens}`;
|
|
7507
|
+
case "would-reconcile":
|
|
7508
|
+
return ` would finalise ${o.sessionId}${tokens}`;
|
|
7509
|
+
case "skipped":
|
|
7510
|
+
return ` skipped ${o.sessionId} \u2014 ${o.reason}`;
|
|
7511
|
+
case "failed":
|
|
7512
|
+
return ` FAILED ${o.sessionId} \u2014 ${o.reason}`;
|
|
7513
|
+
}
|
|
7514
|
+
}
|
|
7515
|
+
async function runReconcile(argv) {
|
|
7516
|
+
const { positionals, options } = parseFlags(argv);
|
|
7517
|
+
const webapi = strOpt(options, "webapi");
|
|
7518
|
+
if (webapi)
|
|
7519
|
+
setWebapiUrl(webapi);
|
|
7520
|
+
const json = options.json === true;
|
|
7521
|
+
const dryRun = options["dry-run"] === true;
|
|
7522
|
+
const force = options.force === true;
|
|
7523
|
+
if (!await webapiReachable()) {
|
|
7524
|
+
console.error(unreachableHint("reconcile"));
|
|
7525
|
+
return 1;
|
|
7526
|
+
}
|
|
7527
|
+
const outcomes = [];
|
|
7528
|
+
const now = new Date().toISOString();
|
|
7529
|
+
const sink = new WebapiSink;
|
|
7530
|
+
try {
|
|
7531
|
+
const manifest = await getOrNull("/");
|
|
7532
|
+
const blobs = manifest?.features?.s3Blobs !== false;
|
|
7533
|
+
const sessions = [];
|
|
7534
|
+
if (positionals.length) {
|
|
7535
|
+
for (const id of positionals) {
|
|
7536
|
+
const s = await sessionOrNull(id);
|
|
7537
|
+
if (s)
|
|
7538
|
+
sessions.push(s);
|
|
7539
|
+
else
|
|
7540
|
+
outcomes.push({ sessionId: id, result: "failed", reason: "no such session" });
|
|
7541
|
+
}
|
|
7542
|
+
} else {
|
|
7543
|
+
sessions.push(...await incompleteSessions());
|
|
7544
|
+
}
|
|
7545
|
+
for (const s of sessions) {
|
|
7546
|
+
outcomes.push(await reconcileOne(s, { dryRun, force, now, blobs, sink }));
|
|
7547
|
+
}
|
|
7548
|
+
} catch (err) {
|
|
7549
|
+
console.error(`reconcile: failed \u2014 ${err.message}`);
|
|
7550
|
+
console.error(unreachableHint("reconcile"));
|
|
7551
|
+
return 1;
|
|
7552
|
+
}
|
|
7553
|
+
const failed = outcomes.filter((o) => o.result === "failed").length;
|
|
7554
|
+
if (json) {
|
|
7555
|
+
console.log(JSON.stringify({ dryRun, outcomes }, null, 2));
|
|
7556
|
+
} else if (!outcomes.length) {
|
|
7557
|
+
console.log("reconcile: nothing to finalise \u2014 no session reads as incomplete");
|
|
7558
|
+
} else {
|
|
7559
|
+
for (const o of outcomes)
|
|
7560
|
+
console.log(describe(o));
|
|
7561
|
+
const done = outcomes.filter((o) => o.result === "reconciled" || o.result === "would-reconcile");
|
|
7562
|
+
console.log(`reconcile: ${dryRun ? "would finalise" : "finalised"} ${done.length}, skipped ${outcomes.length - done.length - failed}, failed ${failed}`);
|
|
7563
|
+
}
|
|
7564
|
+
return failed ? 1 : 0;
|
|
7565
|
+
}
|
|
7566
|
+
|
|
6827
7567
|
// packages/cli/src/commands/reindex.ts
|
|
6828
7568
|
function num2(n) {
|
|
6829
7569
|
return n.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
@@ -7142,18 +7882,18 @@ async function runSessions(argv) {
|
|
|
7142
7882
|
}
|
|
7143
7883
|
|
|
7144
7884
|
// packages/cli/src/commands/setup.ts
|
|
7145
|
-
import { existsSync as
|
|
7885
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync9, readFileSync as readFileSync14, writeFileSync as writeFileSync9 } from "fs";
|
|
7146
7886
|
import { homedir as homedir3 } from "os";
|
|
7147
|
-
import { dirname as dirname7, join as
|
|
7148
|
-
var REPO_ROOT =
|
|
7149
|
-
var HOOK_DIR =
|
|
7150
|
-
var HOOK_CONFIG_PATH = process.env.CT_HOOK_CONFIG ??
|
|
7887
|
+
import { dirname as dirname7, join as join11 } from "path";
|
|
7888
|
+
var REPO_ROOT = join11(import.meta.dir, "..", "..", "..", "..");
|
|
7889
|
+
var HOOK_DIR = join11(REPO_ROOT, "hooks");
|
|
7890
|
+
var HOOK_CONFIG_PATH = process.env.CT_HOOK_CONFIG ?? join11(homedir3(), ".config", "claude-transcripts", "config.json");
|
|
7151
7891
|
var GLOBAL_SETTINGS = installPaths().claudeSettings;
|
|
7152
|
-
var PROJECT_SETTINGS =
|
|
7892
|
+
var PROJECT_SETTINGS = join11(process.cwd(), ".claude", "settings.json");
|
|
7153
7893
|
function loadRepoConfig() {
|
|
7154
|
-
const live =
|
|
7155
|
-
const template =
|
|
7156
|
-
return JSON.parse(
|
|
7894
|
+
const live = join11(REPO_ROOT, "config", "config.json");
|
|
7895
|
+
const template = join11(REPO_ROOT, "config", "config.template.json");
|
|
7896
|
+
return JSON.parse(readFileSync14(existsSync11(live) ? live : template, "utf8"));
|
|
7157
7897
|
}
|
|
7158
7898
|
function couchHeaders(auth) {
|
|
7159
7899
|
const h = { "Content-Type": "application/json" };
|
|
@@ -7203,7 +7943,7 @@ async function probeGarage(blob) {
|
|
|
7203
7943
|
}
|
|
7204
7944
|
function readSettings3(path) {
|
|
7205
7945
|
try {
|
|
7206
|
-
const parsed = JSON.parse(
|
|
7946
|
+
const parsed = JSON.parse(readFileSync14(path, "utf8"));
|
|
7207
7947
|
return parsed && typeof parsed === "object" ? parsed : {};
|
|
7208
7948
|
} catch {
|
|
7209
7949
|
return {};
|
|
@@ -7212,7 +7952,7 @@ function readSettings3(path) {
|
|
|
7212
7952
|
function globalRegistrationBlockedBy(settingsPath = GLOBAL_SETTINGS) {
|
|
7213
7953
|
return pluginRegistration(settingsPath, readSettings3(settingsPath));
|
|
7214
7954
|
}
|
|
7215
|
-
var DISPATCH_PATH =
|
|
7955
|
+
var DISPATCH_PATH = join11(HOOK_DIR, "scripts", "dispatch.ts");
|
|
7216
7956
|
function dispatchCommand() {
|
|
7217
7957
|
return hookLaunch(`bun run "${DISPATCH_PATH}"`);
|
|
7218
7958
|
}
|
|
@@ -7223,7 +7963,7 @@ function isRegistered(path) {
|
|
|
7223
7963
|
}
|
|
7224
7964
|
function registerGlobal(dryRun) {
|
|
7225
7965
|
const cmd = dispatchCommand();
|
|
7226
|
-
const generated = JSON.parse(
|
|
7966
|
+
const generated = JSON.parse(readFileSync14(join11(HOOK_DIR, "hooks", "hooks.json"), "utf8"));
|
|
7227
7967
|
const settings = readSettings3(GLOBAL_SETTINGS);
|
|
7228
7968
|
const hooks = settings.hooks ?? {};
|
|
7229
7969
|
for (const [event, groups] of Object.entries(generated.hooks)) {
|
|
@@ -7243,8 +7983,8 @@ function registerGlobal(dryRun) {
|
|
|
7243
7983
|
console.log(` [check] would register ${Object.keys(generated.hooks).length} events in ${GLOBAL_SETTINGS}`);
|
|
7244
7984
|
return;
|
|
7245
7985
|
}
|
|
7246
|
-
|
|
7247
|
-
|
|
7986
|
+
mkdirSync9(dirname7(GLOBAL_SETTINGS), { recursive: true });
|
|
7987
|
+
writeFileSync9(GLOBAL_SETTINGS, `${JSON.stringify({ ...settings, hooks }, null, 2)}
|
|
7248
7988
|
`);
|
|
7249
7989
|
console.log(` registered ${Object.keys(generated.hooks).length} events (system-wide) in ${GLOBAL_SETTINGS}`);
|
|
7250
7990
|
}
|
|
@@ -7253,7 +7993,7 @@ function registerProject() {
|
|
|
7253
7993
|
}
|
|
7254
7994
|
function reportExisting() {
|
|
7255
7995
|
const g = isRegistered(GLOBAL_SETTINGS);
|
|
7256
|
-
const p =
|
|
7996
|
+
const p = existsSync11(PROJECT_SETTINGS) && isRegistered(PROJECT_SETTINGS);
|
|
7257
7997
|
console.log(` existing registration \u2192 global: ${g ? "yes" : "no"}, this project: ${p ? "yes" : "no"}`);
|
|
7258
7998
|
const plugin = globalRegistrationBlockedBy();
|
|
7259
7999
|
if (plugin) {
|
|
@@ -7289,7 +8029,7 @@ async function runSetup(argv) {
|
|
|
7289
8029
|
reportExisting();
|
|
7290
8030
|
if (check) {
|
|
7291
8031
|
console.log("setup --check (read-only):");
|
|
7292
|
-
console.log(` hook config: ${
|
|
8032
|
+
console.log(` hook config: ${existsSync11(HOOK_CONFIG_PATH) ? "present" : "MISSING"} (${HOOK_CONFIG_PATH})`);
|
|
7293
8033
|
console.log(` couch: ${couch.url}`);
|
|
7294
8034
|
await provisionCouch2(couch, true);
|
|
7295
8035
|
await probeGarage(hookConfig.blob);
|
|
@@ -7320,12 +8060,12 @@ async function runSetup(argv) {
|
|
|
7320
8060
|
}
|
|
7321
8061
|
|
|
7322
8062
|
// packages/cli/src/commands/stack.ts
|
|
7323
|
-
import { existsSync as
|
|
8063
|
+
import { existsSync as existsSync12 } from "fs";
|
|
7324
8064
|
async function runStack(argv) {
|
|
7325
8065
|
const { positionals, options } = parseFlags(argv);
|
|
7326
8066
|
const cmd = positionals[0] ?? "ps";
|
|
7327
8067
|
const paths = installPaths();
|
|
7328
|
-
if (!
|
|
8068
|
+
if (!existsSync12(paths.instanceEnv)) {
|
|
7329
8069
|
console.error("stack: this machine has no instance \u2014 run `claude-transcripts install` first.");
|
|
7330
8070
|
return 1;
|
|
7331
8071
|
}
|
|
@@ -7463,8 +8203,8 @@ async function runTurns(argv) {
|
|
|
7463
8203
|
}
|
|
7464
8204
|
|
|
7465
8205
|
// packages/cli/src/commands/uninstall.ts
|
|
7466
|
-
import { existsSync as
|
|
7467
|
-
import { join as
|
|
8206
|
+
import { existsSync as existsSync13, readdirSync, rmSync } from "fs";
|
|
8207
|
+
import { join as join12 } from "path";
|
|
7468
8208
|
var DATA_SUBDIR = "data";
|
|
7469
8209
|
function removeDeployDir(deployDir, purge) {
|
|
7470
8210
|
if (purge) {
|
|
@@ -7479,7 +8219,7 @@ function removeDeployDir(deployDir, purge) {
|
|
|
7479
8219
|
}
|
|
7480
8220
|
for (const entry of entries) {
|
|
7481
8221
|
if (entry !== DATA_SUBDIR)
|
|
7482
|
-
rmSync(
|
|
8222
|
+
rmSync(join12(deployDir, entry), { recursive: true, force: true });
|
|
7483
8223
|
}
|
|
7484
8224
|
}
|
|
7485
8225
|
async function confirmPurge(auto) {
|
|
@@ -7502,7 +8242,7 @@ async function runUninstall(argv) {
|
|
|
7502
8242
|
console.log("uninstall: deregistering the hook and the statusline");
|
|
7503
8243
|
await runHook(["uninstall"]);
|
|
7504
8244
|
await runStatusline(["uninstall"]);
|
|
7505
|
-
if (
|
|
8245
|
+
if (existsSync13(paths.instanceEnv)) {
|
|
7506
8246
|
console.log("uninstall: stopping the stack");
|
|
7507
8247
|
try {
|
|
7508
8248
|
const env = loadOrCreateInstanceEnv(paths.instanceEnv);
|
|
@@ -7517,7 +8257,7 @@ async function runUninstall(argv) {
|
|
|
7517
8257
|
rmSync(target, { recursive: true, force: true });
|
|
7518
8258
|
} catch {}
|
|
7519
8259
|
}
|
|
7520
|
-
const dataDir =
|
|
8260
|
+
const dataDir = join12(paths.deployDir, DATA_SUBDIR);
|
|
7521
8261
|
try {
|
|
7522
8262
|
removeDeployDir(paths.deployDir, purge);
|
|
7523
8263
|
} catch (err) {
|
|
@@ -7554,6 +8294,8 @@ var COMMANDS = {
|
|
|
7554
8294
|
migrate: runMigrate,
|
|
7555
8295
|
reindex: runReindex,
|
|
7556
8296
|
doctor: runDoctor,
|
|
8297
|
+
reconcile: runReconcile,
|
|
8298
|
+
compat: runCompat,
|
|
7557
8299
|
sessions: runSessions,
|
|
7558
8300
|
search: runSearch,
|
|
7559
8301
|
turns: runTurns,
|