@sovara/claude-code 0.0.0-stage → 0.1.1

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sovara Labs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,8 @@
1
- # Temporary Holding Version
1
+ # @sovara/claude-code
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The Node.js hook program for [Sovara for Claude Code](https://github.com/SovaraLabs/sovara-claude-code#readme):
4
+ `npm install -g @sovara/claude-code`, then load the `sovara-recording` plugin.
5
+
6
+ ## License
7
+
8
+ MIT.
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+ /** Command line: `sovara-claude-code hook` reads one Claude Code hook event from stdin. */
3
+ import { handle } from "./hooks.js";
4
+ async function main() {
5
+ if (process.argv.slice(2).join(" ") !== "hook") {
6
+ console.error("usage: sovara-claude-code hook");
7
+ return;
8
+ }
9
+ let input = "";
10
+ for await (const chunk of process.stdin)
11
+ input += chunk;
12
+ let event;
13
+ try {
14
+ event = JSON.parse(input || "{}");
15
+ }
16
+ catch {
17
+ return;
18
+ }
19
+ if (!event || typeof event !== "object" || Array.isArray(event))
20
+ return;
21
+ const output = await handle(event);
22
+ if (output)
23
+ process.stdout.write(`${output}\n`);
24
+ }
25
+ // Always exit 0, so a Sovara problem never interrupts Claude Code.
26
+ main().catch(() => undefined).finally(() => process.exit(0));
27
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,2FAA2F;AAC3F,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAEpC,KAAK,UAAU,IAAI;IACjB,IAAI,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,MAAM,EAAE,CAAC;QAC/C,OAAO,CAAC,KAAK,CAAC,gCAAgC,CAAC,CAAC;QAChD,OAAO;IACT,CAAC;IACD,IAAI,KAAK,GAAG,EAAE,CAAC;IACf,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,OAAO,CAAC,KAAK;QAAE,KAAK,IAAI,KAAK,CAAC;IACxD,IAAI,KAAc,CAAC;IACnB,IAAI,CAAC;QACH,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC;IACpC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO;IACT,CAAC;IACD,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO;IACxE,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAgC,CAAC,CAAC;IAC9D,IAAI,MAAM;QAAE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC,CAAC;AAClD,CAAC;AAED,mEAAmE;AACnE,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC"}
@@ -0,0 +1,18 @@
1
+ export type HookEvent = {
2
+ hook_event_name?: string;
3
+ session_id?: string;
4
+ [field: string]: unknown;
5
+ };
6
+ type Options = {
7
+ fetch?: typeof globalThis.fetch;
8
+ };
9
+ /** Per-request timeout, so a hook's usual three requests fit in the 15 s Claude Code allows it. */
10
+ export declare const REQUEST_TIMEOUT_MS = 4000;
11
+ /**
12
+ * Run the handler for one hook event; resolve to text for Claude Code, or null.
13
+ *
14
+ * Never rejects: a failure is logged by type only and the session continues unrecorded.
15
+ */
16
+ export declare function handle(event: HookEvent, options?: Options): Promise<string | null>;
17
+ export {};
18
+ //# sourceMappingURL=hooks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AA0BA,MAAM,MAAM,SAAS,GAAG;IAAE,eAAe,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,CAAC;AACpG,KAAK,OAAO,GAAG;IAAE,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAA;CAAE,CAAC;AAGnD,mGAAmG;AACnG,eAAO,MAAM,kBAAkB,OAAQ,CAAC;AAwJxC;;;;GAIG;AACH,wBAAsB,MAAM,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,GAAE,OAAY,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAc5F"}
package/dist/hooks.js ADDED
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Record a Claude Code session in Sovara from its hook events.
3
+ *
4
+ * Hooks run as separate processes, so the session's recording is shared through a handle
5
+ * (`openRecording`) kept in the session's state file. A hook never blocks Claude Code: failures
6
+ * are swallowed and requests retry only briefly.
7
+ */
8
+ import { basename } from "node:path";
9
+ import { closeRecording, exportRecording, openRecording, RecordingError, recordStep, resumeRecording, retrieveLessons, SovaraConfigError, startTurn, } from "@sovara/sdk";
10
+ import { flushRecordings, jsonValue, withShortRetries } from "@sovara/sdk/integration";
11
+ import { load, prune, remove, save } from "./state.js";
12
+ /** Per-request timeout, so a hook's usual three requests fit in the 15 s Claude Code allows it. */
13
+ export const REQUEST_TIMEOUT_MS = 4_000;
14
+ /** How long a hook waits, before exiting, for reports the SDK sends in the background. */
15
+ const FLUSH_MS = REQUEST_TIMEOUT_MS;
16
+ /** Wrap `fetch` so each request is aborted after REQUEST_TIMEOUT_MS. */
17
+ function withTimeout(fetcher) {
18
+ return (input, init) => {
19
+ const timeout = AbortSignal.timeout(REQUEST_TIMEOUT_MS);
20
+ const signal = init?.signal ? AbortSignal.any([init.signal, timeout]) : timeout;
21
+ return fetcher(input, { ...init, signal });
22
+ };
23
+ }
24
+ /** Errors meaning the recording no longer exists; an unknown write token is a 403 SovaraConfigError. */
25
+ const LOST_RECORDING = new Set(["recording_inactive", "not_found"]);
26
+ function projectName(event) {
27
+ const configured = process.env.SOVARA_PROJECT?.trim();
28
+ return configured || basename(String(event.cwd ?? process.cwd())) || "claude-code";
29
+ }
30
+ function textMessage(role, text) {
31
+ return { role, content: [{ type: "text", text }] };
32
+ }
33
+ /** Open a recording of the session's trace; the same session always gets the same trace. */
34
+ function open(event, options) {
35
+ return openRecording({
36
+ projectName: projectName(event),
37
+ name: "Claude Code session",
38
+ traceId: `claude-code:${event.session_id}`,
39
+ fetch: options.fetch,
40
+ });
41
+ }
42
+ async function sessionStart(event, options) {
43
+ prune();
44
+ if (load(event.session_id) !== null)
45
+ return null;
46
+ save(event.session_id, { session: await open(event, options), turn: null, prompt: null });
47
+ return null;
48
+ }
49
+ function beginTurn(session, prompt, options) {
50
+ return resumeRecording(session, async () => {
51
+ await startTurn(prompt);
52
+ return exportRecording();
53
+ }, { fetch: options.fetch });
54
+ }
55
+ function lessonsFor(handle, prompt, options) {
56
+ // Retrieved, not applied: Claude Code does not show what reaches the model.
57
+ return resumeRecording(handle, () => retrieveLessons(prompt), { fetch: options.fetch });
58
+ }
59
+ async function userPromptSubmit(event, options) {
60
+ let state = load(event.session_id);
61
+ if (state === null) {
62
+ // SessionStart could not open the recording; the trace key is stable, so open it now.
63
+ state = { session: await open(event, options), turn: null, prompt: null };
64
+ save(event.session_id, state);
65
+ }
66
+ const prompt = String(event.prompt ?? "");
67
+ let turn;
68
+ try {
69
+ turn = await beginTurn(state.session, prompt, options);
70
+ }
71
+ catch (error) {
72
+ // Only a recording the exec server lost (e.g. after a restart) is reopened; a passing
73
+ // failure must not open a second recording of the session.
74
+ const lost = (error instanceof SovaraConfigError && error.status === 403)
75
+ || (error instanceof RecordingError && LOST_RECORDING.has(error.code));
76
+ if (!lost)
77
+ throw error;
78
+ state = { ...state, session: await open(event, options) };
79
+ turn = await beginTurn(state.session, prompt, options);
80
+ }
81
+ // Saved before retrieving lessons, so later steps join this turn even if retrieval fails.
82
+ save(event.session_id, { ...state, turn, prompt });
83
+ let lessons = null;
84
+ if (prompt) {
85
+ try {
86
+ lessons = await lessonsFor(turn ?? state.session, prompt, options);
87
+ }
88
+ catch (error) {
89
+ if (!(error instanceof RecordingError))
90
+ throw error;
91
+ }
92
+ }
93
+ if (!lessons)
94
+ return null;
95
+ return JSON.stringify({
96
+ hookSpecificOutput: { hookEventName: "UserPromptSubmit", additionalContext: lessons.text },
97
+ });
98
+ }
99
+ async function record(state, call, outcome, options) {
100
+ await resumeRecording(state.turn ?? state.session, () => recordStep(call, outcome), { fetch: options.fetch });
101
+ }
102
+ function toolCall(event) {
103
+ const args = jsonValue(event.tool_input);
104
+ return {
105
+ kind: "tool",
106
+ name: String(event.tool_name ?? "tool"),
107
+ arguments: (args && typeof args === "object" && !Array.isArray(args) ? args : { input: args }),
108
+ call_id: String(event.tool_use_id ?? "").trim() || null,
109
+ metadata: { source: "claude-code" },
110
+ };
111
+ }
112
+ async function postToolUse(event, options) {
113
+ const state = load(event.session_id);
114
+ if (state)
115
+ await record(state, toolCall(event), { status: "success", output: jsonValue(event.tool_response) }, options);
116
+ return null;
117
+ }
118
+ async function postToolUseFailure(event, options) {
119
+ const state = load(event.session_id);
120
+ const outcome = { status: "error", error: { type: "ToolError", message: String(event.error ?? "") } };
121
+ if (state)
122
+ await record(state, toolCall(event), outcome, options);
123
+ return null;
124
+ }
125
+ async function stop(event, options) {
126
+ const state = load(event.session_id);
127
+ const reply = String(event.last_assistant_message ?? "");
128
+ if (!state || !reply || event.stop_hook_active)
129
+ return null;
130
+ const call = {
131
+ kind: "llm",
132
+ provider: "anthropic",
133
+ input: { format: "conversation", messages: state.prompt ? [textMessage("user", state.prompt)] : [] },
134
+ metadata: { source: "claude-code" },
135
+ };
136
+ await record(state, call, { status: "success", output: { message: textMessage("assistant", reply) } }, options);
137
+ return null;
138
+ }
139
+ async function sessionEnd(event, options) {
140
+ const state = load(event.session_id);
141
+ if (!state)
142
+ return null;
143
+ try {
144
+ await closeRecording(state.session, { fetch: options.fetch });
145
+ }
146
+ finally {
147
+ remove(event.session_id);
148
+ }
149
+ return null;
150
+ }
151
+ const HANDLERS = {
152
+ SessionStart: sessionStart,
153
+ UserPromptSubmit: userPromptSubmit,
154
+ PostToolUse: postToolUse,
155
+ PostToolUseFailure: postToolUseFailure,
156
+ Stop: stop,
157
+ SessionEnd: sessionEnd,
158
+ };
159
+ /**
160
+ * Run the handler for one hook event; resolve to text for Claude Code, or null.
161
+ *
162
+ * Never rejects: a failure is logged by type only and the session continues unrecorded.
163
+ */
164
+ export async function handle(event, options = {}) {
165
+ const handler = HANDLERS[String(event.hook_event_name)];
166
+ if (!handler || !event.session_id)
167
+ return null;
168
+ try {
169
+ const timed = { ...options, fetch: withTimeout(options.fetch ?? globalThis.fetch) };
170
+ const output = await withShortRetries(() => handler(event, timed));
171
+ // The hook process exits right after; let reports queued in the background finish.
172
+ await flushRecordings(FLUSH_MS);
173
+ return output;
174
+ }
175
+ catch (error) {
176
+ const type = error instanceof Error ? error.name : typeof error;
177
+ console.error(`[sovara] Claude Code hook failed event=${String(event.hook_event_name)} error_type=${type}`);
178
+ return null;
179
+ }
180
+ }
181
+ //# sourceMappingURL=hooks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks.js","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Record Claude Code sessions in Sovara and add relevant lessons to each prompt.
3
+ *
4
+ * Claude Code runs `sovara-claude-code hook` for each hook event (see the `sovara-recording`
5
+ * plugin). `handle(event)` is the same entry point for tests.
6
+ */
7
+ export { handle, type HookEvent } from "./hooks.js";
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,MAAM,EAAE,KAAK,SAAS,EAAE,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Record Claude Code sessions in Sovara and add relevant lessons to each prompt.
3
+ *
4
+ * Claude Code runs `sovara-claude-code hook` for each hook event (see the `sovara-recording`
5
+ * plugin). `handle(event)` is the same entry point for tests.
6
+ */
7
+ export { handle } from "./hooks.js";
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,MAAM,EAAkB,MAAM,YAAY,CAAC"}
@@ -0,0 +1,12 @@
1
+ export type SessionState = {
2
+ session: string;
3
+ turn: string | null;
4
+ prompt: string | null;
5
+ };
6
+ export declare function stateDir(): string;
7
+ export declare function load(sessionId: string): SessionState | null;
8
+ export declare function save(sessionId: string, state: SessionState): void;
9
+ export declare function remove(sessionId: string): void;
10
+ /** Delete state files of sessions that ended without SessionEnd. */
11
+ export declare function prune(): void;
12
+ //# sourceMappingURL=state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"AAYA,MAAM,MAAM,YAAY,GAAG;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB,CAAC;AAMF,wBAAgB,QAAQ,IAAI,MAAM,CAGjC;AAOD,wBAAgB,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAO3D;AAED,wBAAgB,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,IAAI,CAWjE;AAED,wBAAgB,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAE9C;AAED,oEAAoE;AACpE,wBAAgB,KAAK,IAAI,IAAI,CAiB5B"}
package/dist/state.js ADDED
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Per-session state shared by the hook processes of one Claude Code session.
3
+ *
4
+ * Each session has one JSON file holding its recording handles, which carry a write token: the
5
+ * file is private (0600), written atomically, and never logged. The format is shared with the
6
+ * Python integration (`sovara-claude-code`).
7
+ */
8
+ import { mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
9
+ import { homedir } from "node:os";
10
+ import { join } from "node:path";
11
+ import { randomUUID } from "node:crypto";
12
+ /** A session whose SessionEnd never ran (a crash) is forgotten after this long. */
13
+ const STALE_AFTER_MS = 7 * 24 * 3600 * 1000;
14
+ const SAFE_ID = /^[A-Za-z0-9_-]{1,128}$/;
15
+ export function stateDir() {
16
+ const pluginData = process.env.CLAUDE_PLUGIN_DATA?.trim();
17
+ return pluginData || join(homedir(), ".sovara", "claude-code");
18
+ }
19
+ function statePath(sessionId) {
20
+ if (!SAFE_ID.test(sessionId))
21
+ throw new Error("Unsupported Claude Code session ID.");
22
+ return join(stateDir(), `${sessionId}.json`);
23
+ }
24
+ export function load(sessionId) {
25
+ try {
26
+ return JSON.parse(readFileSync(statePath(sessionId), "utf8"));
27
+ }
28
+ catch (error) {
29
+ if (error.code === "ENOENT")
30
+ return null;
31
+ throw error;
32
+ }
33
+ }
34
+ export function save(sessionId, state) {
35
+ const path = statePath(sessionId);
36
+ mkdirSync(stateDir(), { recursive: true, mode: 0o700 });
37
+ const tmp = join(stateDir(), `.tmp-${randomUUID()}`);
38
+ try {
39
+ writeFileSync(tmp, JSON.stringify(state), { mode: 0o600 });
40
+ renameSync(tmp, path);
41
+ }
42
+ catch (error) {
43
+ rmSync(tmp, { force: true });
44
+ throw error;
45
+ }
46
+ }
47
+ export function remove(sessionId) {
48
+ rmSync(statePath(sessionId), { force: true });
49
+ }
50
+ /** Delete state files of sessions that ended without SessionEnd. */
51
+ export function prune() {
52
+ const cutoff = Date.now() - STALE_AFTER_MS;
53
+ let names;
54
+ try {
55
+ names = readdirSync(stateDir());
56
+ }
57
+ catch {
58
+ return;
59
+ }
60
+ for (const name of names) {
61
+ if (!name.endsWith(".json"))
62
+ continue;
63
+ try {
64
+ const path = join(stateDir(), name);
65
+ if (statSync(path).mtimeMs < cutoff)
66
+ rmSync(path, { force: true });
67
+ }
68
+ catch {
69
+ continue;
70
+ }
71
+ }
72
+ }
73
+ //# sourceMappingURL=state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.js","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,41 @@
1
1
  {
2
2
  "name": "@sovara/claude-code",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.1",
4
+ "description": "Record Claude Code sessions in Sovara and add relevant lessons to each prompt",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/SovaraLabs/sovara-claude-code.git",
10
+ "directory": "typescript"
11
+ },
12
+ "homepage": "https://github.com/SovaraLabs/sovara-claude-code#readme",
13
+ "keywords": ["sovara", "claude-code", "observability"],
14
+ "bin": {
15
+ "sovara-claude-code": "dist/cli.js"
16
+ },
17
+ "exports": {
18
+ ".": {
19
+ "types": "./dist/index.d.ts",
20
+ "import": "./dist/index.js"
21
+ }
22
+ },
23
+ "files": ["dist", "README.md", "LICENSE"],
24
+ "engines": {
25
+ "node": ">=24.22.1"
26
+ },
27
+ "scripts": {
28
+ "build": "tsc -p tsconfig.build.json",
29
+ "typecheck": "tsc --noEmit",
30
+ "test": "vitest run",
31
+ "check": "npm run typecheck && npm test && npm run build"
32
+ },
33
+ "dependencies": {
34
+ "@sovara/sdk": ">=0.10.0 <0.12.0"
35
+ },
36
+ "devDependencies": {
37
+ "@types/node": "^22.10.2",
38
+ "typescript": "^5.6.3",
39
+ "vitest": "^4.1.1"
40
+ }
41
+ }