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