@ory/claude-code 0.1.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.
@@ -0,0 +1,525 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handleHookEvent = handleHookEvent;
4
+ const argus_1 = require("@ory/argus");
5
+ /**
6
+ * Route a Claude Code hook event to the appropriate Ory integration.
7
+ */
8
+ async function handleHookEvent(input, client, deps = {}) {
9
+ const event = input.hook_event_name;
10
+ client.logger.debug("hook.received", {
11
+ event,
12
+ sessionId: input.session_id,
13
+ toolName: input.tool_name,
14
+ });
15
+ // Set trace context so all spans within this hook invocation correlate
16
+ client.tracer.setContext({
17
+ traceId: (0, argus_1.deriveTraceId)(input.session_id),
18
+ sessionId: input.session_id,
19
+ });
20
+ try {
21
+ switch (event) {
22
+ case "SessionStart":
23
+ return await handleSessionStart(input, client, deps);
24
+ case "PreToolUse":
25
+ return await handlePreToolUse(input, client, deps);
26
+ case "PostToolUse":
27
+ return await handlePostToolUse(input, client);
28
+ case "PostToolUseFailure":
29
+ return await handlePostToolUseFailure(input, client);
30
+ case "PermissionRequest":
31
+ return await handlePermissionRequest(input, client);
32
+ case "UserPromptSubmit":
33
+ return await handleUserPromptSubmit(input, client);
34
+ case "SubagentStart":
35
+ return await handleSubagentStart(input, client, deps);
36
+ case "SubagentStop":
37
+ return await handleSubagentStop(input, client);
38
+ case "SessionEnd":
39
+ return await handleSessionEnd(input, client);
40
+ default:
41
+ client.logger.debug("hook.passthrough", { event });
42
+ client.tracer.record("hook.passthrough", "skipped", {
43
+ attributes: { event },
44
+ });
45
+ return { continue: true };
46
+ }
47
+ }
48
+ finally {
49
+ client.tracer.clearContext();
50
+ }
51
+ }
52
+ // ─── SessionStart ───────────────────────────────────────────────────
53
+ async function handleSessionStart(input, client, deps) {
54
+ client.logger.info("lifecycle.session_start", {
55
+ sessionId: input.session_id,
56
+ model: input.model,
57
+ source: input.source,
58
+ cwd: input.cwd,
59
+ });
60
+ client.tracer.record("session.start", "ok", {
61
+ attributes: { model: input.model, source: input.source },
62
+ });
63
+ // Run the user auth gate (interactive PKCE on first session, refresh
64
+ // when needed). When ORY_AUTH_GATE is unset this is a no-op and we
65
+ // fall through to the legacy verify path below.
66
+ const userGate = deps.authGate ?? argus_1.ensureUserAuthenticated;
67
+ const decision = await userGate(client, {
68
+ binName: "ory-claude",
69
+ harness: "claude-code",
70
+ allowBlock: true,
71
+ });
72
+ // Resolve the agent identity (machine credentials) regardless of how
73
+ // user auth went — it never blocks and attaches the agent's bearer
74
+ // token to outgoing Ory API calls so the audit trail records who
75
+ // acted on the user's behalf.
76
+ const agentGate = deps.agentGate ?? argus_1.ensureAgentIdentity;
77
+ await agentGate(client, { projectUrl: (0, argus_1.resolveConfig)().projectUrl, harness: "claude-code" });
78
+ // Record the user→agent delegation so the audit trail captures that
79
+ // this user authorized this agent for this session. Best-effort:
80
+ // requires both principals to be populated, and any failure is logged
81
+ // and swallowed (fail-open — delegation tracking is for audit, not
82
+ // enforcement).
83
+ await recordUserDelegatesAgent(client);
84
+ if (!decision.proceed) {
85
+ return { decision: "block", reason: decision.reason };
86
+ }
87
+ if (decision.mode !== "disabled") {
88
+ return {};
89
+ }
90
+ const resolved = (0, argus_1.resolveConfig)();
91
+ if (resolved.auditOnly) {
92
+ client.logger.info("config.audit_only", {
93
+ message: "Audit-only mode enabled. Auth and permission checks are disabled.",
94
+ });
95
+ return {};
96
+ }
97
+ if (!resolved.projectUrl) {
98
+ client.logger.warn("config.not_configured", {
99
+ message: "Ory plugin is not configured. Auth and permission checks are disabled. " +
100
+ "Run 'npx ory-claude configure' to connect to an Ory project.",
101
+ });
102
+ return {};
103
+ }
104
+ // Try session token first, then OAuth2 token
105
+ const sessionToken = process.env.ORY_SESSION_TOKEN;
106
+ const oauth2Token = process.env.ORY_OAUTH2_TOKEN;
107
+ if (sessionToken) {
108
+ await verifySessionToken(sessionToken, client);
109
+ return {};
110
+ }
111
+ if (oauth2Token) {
112
+ await verifyOAuth2Token(oauth2Token, client);
113
+ return {};
114
+ }
115
+ client.logger.warn("session.no_credentials", {
116
+ message: "Neither ORY_SESSION_TOKEN nor ORY_OAUTH2_TOKEN is set. " +
117
+ "Skipping authentication.",
118
+ });
119
+ return {};
120
+ }
121
+ async function verifySessionToken(token, client) {
122
+ try {
123
+ const session = await client.verifySession(token);
124
+ if (!session.active) {
125
+ client.logger.warn("session.inactive", {
126
+ message: "Ory session is not active. Re-authenticate to enable auth checks.",
127
+ });
128
+ }
129
+ }
130
+ catch (err) {
131
+ const oryErr = err;
132
+ client.logger.warn("session.verify_failed", {
133
+ code: oryErr.code,
134
+ message: oryErr.message,
135
+ });
136
+ }
137
+ }
138
+ async function verifyOAuth2Token(token, client) {
139
+ try {
140
+ const tokenInfo = await client.introspectToken(token);
141
+ if (!tokenInfo.active) {
142
+ client.logger.warn("oauth2.token_inactive", {
143
+ message: "Ory OAuth2 token is not active. Obtain a new token to enable auth checks.",
144
+ });
145
+ return;
146
+ }
147
+ client.logger.info("oauth2.session_authenticated", {
148
+ clientId: tokenInfo.clientId,
149
+ subject: tokenInfo.subject,
150
+ scope: tokenInfo.scope,
151
+ });
152
+ }
153
+ catch (err) {
154
+ const oryErr = err;
155
+ client.logger.warn("oauth2.introspect_failed", {
156
+ code: oryErr.code,
157
+ message: oryErr.message,
158
+ });
159
+ }
160
+ }
161
+ // ─── PreToolUse ─────────────────────────────────────────────────────
162
+ async function handlePreToolUse(input, client, deps = {}) {
163
+ const toolName = input.tool_name ?? "unknown";
164
+ client.logger.info("lifecycle.pre_tool_use", {
165
+ sessionId: input.session_id,
166
+ toolName,
167
+ toolInput: input.tool_input,
168
+ });
169
+ const inputSummary = (0, argus_1.summarizeToolInput)(toolName, input.tool_input);
170
+ // If the agent is invoking a sub-agent (Claude Code's "Task"/"Agent"
171
+ // tool with a `subagent_type`), resolve a distinct OAuth2 identity for
172
+ // that sub-agent via DCR and record the agent→sub-agent delegation
173
+ // tuple. Best-effort — failures here never block the actual tool call.
174
+ await maybeRegisterSubAgent(toolName, input.tool_input, client, deps);
175
+ // In audit-only mode, log the invocation but skip permission checks
176
+ if ((0, argus_1.resolveConfig)().auditOnly) {
177
+ client.tracer.record("tool.invoke", "ok", {
178
+ attributes: { toolName, ...inputSummary },
179
+ });
180
+ return {};
181
+ }
182
+ const subject = (0, argus_1.resolveUserSubject)(client, `session:${input.session_id}`);
183
+ const subjectId = (0, argus_1.subjectLabel)(subject);
184
+ const mcpTool = (0, argus_1.parseClaudeCodeMcpTool)(toolName);
185
+ try {
186
+ if (mcpTool) {
187
+ const mcpResult = await (0, argus_1.checkMcpPermission)(client, mcpTool, {
188
+ subject,
189
+ spanAttributes: { toolName },
190
+ });
191
+ if (!mcpResult.allowed) {
192
+ client.tracer.record("tool.block", "denied", {
193
+ attributes: { toolName, mcpServer: mcpTool.serverName, mcpTool: mcpTool.toolName, ...inputSummary, ...(0, argus_1.alertAttributes)(true) },
194
+ });
195
+ return {
196
+ decision: "block",
197
+ reason: (0, argus_1.formatDenialMessage)({ tool: toolName, subjectId, mcp: mcpTool }),
198
+ };
199
+ }
200
+ client.tracer.record("tool.invoke", "ok", {
201
+ attributes: { toolName, mcpServer: mcpTool.serverName, mcpTool: mcpTool.toolName, ...inputSummary },
202
+ });
203
+ return {};
204
+ }
205
+ const namespace = resolveNamespace();
206
+ const result = await client.checkPermission({
207
+ namespace,
208
+ object: toolName,
209
+ relation: "use",
210
+ ...subject,
211
+ }, { spanAttributes: { toolName } });
212
+ if (!result.allowed) {
213
+ client.tracer.record("tool.block", "denied", {
214
+ attributes: { toolName, ...inputSummary, allowed: result.allowed, ...(0, argus_1.alertAttributes)(true) },
215
+ });
216
+ return {
217
+ decision: "block",
218
+ reason: (0, argus_1.formatDenialMessage)({ tool: toolName, subjectId, namespace }),
219
+ };
220
+ }
221
+ client.tracer.record("tool.invoke", "ok", {
222
+ attributes: { toolName, ...inputSummary, allowed: result.allowed },
223
+ });
224
+ return {};
225
+ }
226
+ catch (err) {
227
+ return handlePermissionError(err, toolName, client);
228
+ }
229
+ }
230
+ // ─── PostToolUse ────────────────────────────────────────────────────
231
+ async function handlePostToolUse(input, client) {
232
+ const toolName = input.tool_name ?? "unknown";
233
+ client.logger.info("lifecycle.post_tool_use", {
234
+ sessionId: input.session_id,
235
+ toolName,
236
+ toolResponse: input.tool_response,
237
+ });
238
+ client.tracer.record("tool.complete", "ok", {
239
+ attributes: {
240
+ toolName,
241
+ toolUseId: input.tool_use_id,
242
+ ...(0, argus_1.summarizeToolInput)(toolName, input.tool_input),
243
+ ...(0, argus_1.summarizeToolOutput)(toolName, input.tool_response),
244
+ },
245
+ });
246
+ return {};
247
+ }
248
+ // ─── PostToolUseFailure ─────────────────────────────────────────────
249
+ async function handlePostToolUseFailure(input, client) {
250
+ const toolName = input.tool_name ?? "unknown";
251
+ client.logger.info("lifecycle.post_tool_use_failure", {
252
+ sessionId: input.session_id,
253
+ toolName,
254
+ toolUseId: input.tool_use_id,
255
+ });
256
+ client.tracer.record("tool.fail", "error", {
257
+ attributes: {
258
+ toolName,
259
+ toolUseId: input.tool_use_id,
260
+ toolError: typeof input.tool_error === "string"
261
+ ? input.tool_error.slice(0, 500)
262
+ : input.tool_error
263
+ ? "[object]"
264
+ : undefined,
265
+ ...(0, argus_1.summarizeToolInput)(toolName, input.tool_input),
266
+ },
267
+ });
268
+ return {};
269
+ }
270
+ // ─── UserPromptSubmit ──────────────────────────────────────────────
271
+ async function handleUserPromptSubmit(input, client) {
272
+ client.logger.info("lifecycle.user_prompt_submit", {
273
+ sessionId: input.session_id,
274
+ promptLen: input.prompt?.length,
275
+ });
276
+ client.tracer.record("user.prompt", "ok", {
277
+ attributes: { promptLen: input.prompt?.length },
278
+ });
279
+ return {};
280
+ }
281
+ // ─── SubagentStart ─────────────────────────────────────────────────
282
+ //
283
+ // Dedicated sub-agent invocation event. Replaces the legacy path of
284
+ // sniffing the `Task`/`Agent` tool name in PreToolUse — that still runs
285
+ // as a fallback for older Claude Code versions, but registerSubAgent
286
+ // is idempotent so duplicate registration is harmless.
287
+ async function handleSubagentStart(input, client, deps) {
288
+ const subAgentType = input.subagent_type ?? input.agent_type;
289
+ const agentId = input.agent_id;
290
+ client.logger.info("lifecycle.subagent_start", {
291
+ sessionId: input.session_id,
292
+ subAgentType,
293
+ agentId,
294
+ });
295
+ client.tracer.record("subagent.start", "ok", {
296
+ attributes: { subAgentType, agentId },
297
+ });
298
+ if (subAgentType) {
299
+ await registerSubAgent(subAgentType, client, deps);
300
+ }
301
+ else {
302
+ client.logger.debug("subagent_start.no_type", {
303
+ message: "SubagentStart event without subagent_type — skipping DCR.",
304
+ });
305
+ }
306
+ return {};
307
+ }
308
+ // ─── SubagentStop ──────────────────────────────────────────────────
309
+ async function handleSubagentStop(input, client) {
310
+ client.logger.info("lifecycle.subagent_stop", {
311
+ sessionId: input.session_id,
312
+ subAgentType: input.subagent_type ?? input.agent_type,
313
+ agentId: input.agent_id,
314
+ });
315
+ client.tracer.record("subagent.stop", "ok", {
316
+ attributes: {
317
+ subAgentType: input.subagent_type ?? input.agent_type,
318
+ agentId: input.agent_id,
319
+ responseLen: input.subagent_response?.length,
320
+ },
321
+ });
322
+ return {};
323
+ }
324
+ // ─── SessionEnd ────────────────────────────────────────────────────
325
+ async function handleSessionEnd(input, client) {
326
+ client.logger.info("lifecycle.session_end", {
327
+ sessionId: input.session_id,
328
+ reason: input.reason,
329
+ });
330
+ client.tracer.record("session.end", "ok", {
331
+ attributes: { reason: input.reason },
332
+ });
333
+ return {};
334
+ }
335
+ // ─── PermissionRequest ──────────────────────────────────────────────
336
+ async function handlePermissionRequest(input, client) {
337
+ const toolName = input.tool_name ?? "unknown";
338
+ client.logger.info("lifecycle.permission_request", {
339
+ sessionId: input.session_id,
340
+ toolName,
341
+ toolInput: input.tool_input,
342
+ });
343
+ // Audit-only mode: omit `decision` so Claude falls through to its normal
344
+ // user-permission prompt.
345
+ if ((0, argus_1.resolveConfig)().auditOnly) {
346
+ return permissionRequestFallback("Audit-only mode — deferring to user");
347
+ }
348
+ const subject = (0, argus_1.resolveUserSubject)(client, `session:${input.session_id}`);
349
+ const subjectId = (0, argus_1.subjectLabel)(subject);
350
+ const mcpTool = (0, argus_1.parseClaudeCodeMcpTool)(toolName);
351
+ try {
352
+ if (mcpTool) {
353
+ const mcpResult = await (0, argus_1.checkMcpPermission)(client, mcpTool, {
354
+ subject,
355
+ spanAttributes: { toolName },
356
+ });
357
+ return permissionRequestDecision(mcpResult.allowed ? "allow" : "deny", mcpResult.allowed
358
+ ? "Ory MCP server permission granted"
359
+ : (0, argus_1.formatDenialMessage)({ tool: toolName, subjectId, mcp: mcpTool }));
360
+ }
361
+ const namespace = resolveNamespace();
362
+ const result = await client.checkPermission({
363
+ namespace,
364
+ object: toolName,
365
+ relation: "use",
366
+ ...subject,
367
+ }, { spanAttributes: { toolName } });
368
+ return permissionRequestDecision(result.allowed ? "allow" : "deny", result.allowed
369
+ ? "Ory permission granted"
370
+ : (0, argus_1.formatDenialMessage)({ tool: toolName, subjectId, namespace }));
371
+ }
372
+ catch (err) {
373
+ const oryErr = err;
374
+ const fallbackReason = oryErr.code === "network_error" || oryErr.code === "rate_limited"
375
+ ? `Ory unavailable (${oryErr.code}), falling back to user prompt`
376
+ : `Ory permission check failed: ${oryErr.message}`;
377
+ return permissionRequestFallback(fallbackReason);
378
+ }
379
+ }
380
+ function permissionRequestDecision(behavior, reason) {
381
+ return {
382
+ hookSpecificOutput: {
383
+ hookEventName: "PermissionRequest",
384
+ decision: { behavior },
385
+ message: reason,
386
+ },
387
+ };
388
+ }
389
+ function permissionRequestFallback(reason) {
390
+ return {
391
+ hookSpecificOutput: {
392
+ hookEventName: "PermissionRequest",
393
+ message: reason,
394
+ },
395
+ };
396
+ }
397
+ // ─── Helpers ────────────────────────────────────────────────────────
398
+ function resolveNamespace() {
399
+ return process.env.ORY_PERMISSION_NAMESPACE ?? "AgentTools";
400
+ }
401
+ /**
402
+ * Write the user→agent delegation tuple. Idempotent and fail-open:
403
+ * requires both principal subjects to be populated; any error (including
404
+ * unconfigured projectUrl, which manifests as a network_error) is logged
405
+ * and swallowed. The actual permission enforcement is unchanged — this
406
+ * tuple is purely audit-trail data.
407
+ */
408
+ async function recordUserDelegatesAgent(client) {
409
+ const user = client.userPrincipal.subject;
410
+ const agent = client.agentPrincipal.subject;
411
+ if (!user || !agent) {
412
+ client.logger.debug("delegation.skip", {
413
+ reason: "missing principal",
414
+ hasUser: !!user,
415
+ hasAgent: !!agent,
416
+ });
417
+ return;
418
+ }
419
+ try {
420
+ await client.createRelationship({
421
+ namespace: resolveNamespace(),
422
+ object: `agent:${agent}`,
423
+ relation: "delegate",
424
+ subjectId: `user:${user}`,
425
+ }, { spanAttributes: { delegation: "user-to-agent" } });
426
+ }
427
+ catch (err) {
428
+ const oryErr = err;
429
+ client.logger.warn("delegation.user_to_agent.failed", {
430
+ code: oryErr.code,
431
+ message: oryErr.message,
432
+ });
433
+ }
434
+ }
435
+ /**
436
+ * Register an OAuth2 identity for a sub-agent and write the
437
+ * `agent → subagent` delegation tuple. Fail-open and idempotent:
438
+ * `createRelationship` absorbs 409s, so multiple call paths
439
+ * (SubagentStart event + Task-tool fallback) won't duplicate state.
440
+ */
441
+ async function registerSubAgent(subAgentType, client, deps) {
442
+ const subAgentGate = deps.subAgentGate ?? argus_1.ensureSubAgentIdentity;
443
+ let identity;
444
+ try {
445
+ identity = await subAgentGate(client, {
446
+ subAgentType,
447
+ projectUrl: (0, argus_1.resolveConfig)().projectUrl,
448
+ harness: "claude-code",
449
+ });
450
+ }
451
+ catch (err) {
452
+ client.logger.warn("subagent.identity.failed", {
453
+ subAgentType,
454
+ message: err instanceof Error ? err.message : String(err),
455
+ });
456
+ return;
457
+ }
458
+ if (identity.kind !== "dynamic" || !identity.subject)
459
+ return;
460
+ const agent = client.agentPrincipal.subject;
461
+ if (!agent) {
462
+ client.logger.debug("delegation.skip", {
463
+ reason: "agent principal not populated",
464
+ subAgentType,
465
+ });
466
+ return;
467
+ }
468
+ try {
469
+ await client.createRelationship({
470
+ namespace: resolveNamespace(),
471
+ object: `subagent:${identity.subject}`,
472
+ relation: "delegate",
473
+ subjectId: `agent:${agent}`,
474
+ }, { spanAttributes: { delegation: "agent-to-subagent", subAgentType } });
475
+ }
476
+ catch (err) {
477
+ const oryErr = err;
478
+ client.logger.warn("delegation.agent_to_subagent.failed", {
479
+ subAgentType,
480
+ code: oryErr.code,
481
+ message: oryErr.message,
482
+ });
483
+ }
484
+ }
485
+ /**
486
+ * Legacy fallback for older Claude Code versions that don't fire
487
+ * `SubagentStart`: the sub-agent invocation appears as a `Task`/`Agent`
488
+ * tool call in PreToolUse with a `subagent_type` field on tool_input.
489
+ * `SubagentStart` is the preferred path; this is a no-op when both fire.
490
+ */
491
+ async function maybeRegisterSubAgent(toolName, toolInput, client, deps) {
492
+ if (toolName !== "Task" && toolName !== "Agent")
493
+ return;
494
+ const subAgentType = typeof toolInput === "object"
495
+ && toolInput !== null
496
+ && typeof toolInput.subagent_type === "string"
497
+ ? toolInput.subagent_type
498
+ : undefined;
499
+ if (!subAgentType)
500
+ return;
501
+ await registerSubAgent(subAgentType, client, deps);
502
+ }
503
+ function handlePermissionError(oryErr, toolName, client) {
504
+ if (oryErr.code === "network_error") {
505
+ client.logger.warn("permission.network_error", {
506
+ toolName,
507
+ message: "Ory unreachable, failing open",
508
+ });
509
+ return {};
510
+ }
511
+ if (oryErr.code === "rate_limited") {
512
+ client.logger.warn("permission.rate_limited", {
513
+ toolName,
514
+ message: "Ory rate limited, failing open",
515
+ });
516
+ return {};
517
+ }
518
+ // For other errors, also fail open but log prominently
519
+ client.logger.error("permission.check.error", {
520
+ toolName,
521
+ code: oryErr.code,
522
+ message: oryErr.message,
523
+ });
524
+ return {};
525
+ }
package/dist/hook.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Claude Code hook entry point.
4
+ * This script is invoked by Claude Code for each hook event.
5
+ * Input: JSON on stdin
6
+ * Output: JSON on stdout
7
+ * Exit code 2 = block the action
8
+ */
9
+ export {};
package/dist/hook.js ADDED
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ /**
4
+ * Claude Code hook entry point.
5
+ * This script is invoked by Claude Code for each hook event.
6
+ * Input: JSON on stdin
7
+ * Output: JSON on stdout
8
+ * Exit code 2 = block the action
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ const argus_1 = require("@ory/argus");
12
+ const handlers_js_1 = require("./handlers.js");
13
+ /**
14
+ * Read all of stdin as a string.
15
+ *
16
+ * Uses event listeners instead of `for await` so that we don't depend on the
17
+ * parent process closing stdin (sending EOF). If no EOF arrives, we resolve
18
+ * after a short idle gap once data has been received.
19
+ */
20
+ function readStdin() {
21
+ return new Promise((resolve, reject) => {
22
+ const chunks = [];
23
+ let resolved = false;
24
+ function done() {
25
+ if (!resolved) {
26
+ resolved = true;
27
+ resolve(Buffer.concat(chunks).toString("utf-8"));
28
+ }
29
+ }
30
+ process.stdin.on("data", (chunk) => {
31
+ chunks.push(chunk);
32
+ // Reset idle timer on every chunk. Once data stops arriving for 100 ms
33
+ // we assume the full payload has been delivered.
34
+ clearTimeout(idleTimer);
35
+ idleTimer = setTimeout(done, 100);
36
+ });
37
+ process.stdin.on("end", done);
38
+ process.stdin.on("error", (err) => {
39
+ if (!resolved) {
40
+ resolved = true;
41
+ reject(err);
42
+ }
43
+ });
44
+ let idleTimer;
45
+ });
46
+ }
47
+ async function main() {
48
+ const client = argus_1.OryAgentClient.fromEnv("claude-code");
49
+ const raw = await readStdin();
50
+ let input;
51
+ try {
52
+ input = JSON.parse(raw);
53
+ }
54
+ catch {
55
+ client.logger.error("hook.stdin.parse_failed", { raw: raw.slice(0, 200) });
56
+ await client.tracer.shutdown();
57
+ process.exit(0); // Don't block on parse errors
58
+ }
59
+ const output = await (0, handlers_js_1.handleHookEvent)(input, client);
60
+ if (output.decision === "block") {
61
+ process.stdout.write(JSON.stringify(output));
62
+ await client.tracer.shutdown();
63
+ process.exit(2);
64
+ }
65
+ if (output.hookSpecificOutput ||
66
+ output.updatedInput ||
67
+ output.decision === "approve") {
68
+ process.stdout.write(JSON.stringify(output));
69
+ }
70
+ await client.tracer.shutdown();
71
+ process.exit(0);
72
+ }
73
+ main().catch((err) => {
74
+ process.stderr.write(`[ory-agent] fatal: ${err}\n`);
75
+ process.exit(0); // Fail open
76
+ });
@@ -0,0 +1,2 @@
1
+ export { handleHookEvent } from "./handlers.js";
2
+ export { generateSettings } from "./settings.js";
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generateSettings = exports.handleHookEvent = void 0;
4
+ var handlers_js_1 = require("./handlers.js");
5
+ Object.defineProperty(exports, "handleHookEvent", { enumerable: true, get: function () { return handlers_js_1.handleHookEvent; } });
6
+ var settings_js_1 = require("./settings.js");
7
+ Object.defineProperty(exports, "generateSettings", { enumerable: true, get: function () { return settings_js_1.generateSettings; } });
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Generate the .claude/settings.local.json that configures Ory hooks.
3
+ */
4
+ export declare function generateSettings(hookScriptPath: string): object;
5
+ /**
6
+ * Generate settings assuming the plugin is installed via npm/pnpm.
7
+ */
8
+ export declare function generateInstalledSettings(): object;
9
+ /**
10
+ * Generate settings pointing to a local dev build.
11
+ */
12
+ export declare function generateDevSettings(packageDir: string): object;