@oculisecurity/cli 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.
- package/LICENSE.txt +201 -0
- package/README.md +67 -0
- package/dist/cli.d.ts +18 -0
- package/dist/cli.js +565 -0
- package/dist/commands/init.d.ts +14 -0
- package/dist/commands/init.js +135 -0
- package/dist/commands/report.d.ts +33 -0
- package/dist/commands/report.js +145 -0
- package/dist/commands/serve.d.ts +27 -0
- package/dist/commands/serve.js +163 -0
- package/dist/commands/tail.d.ts +7 -0
- package/dist/commands/tail.js +211 -0
- package/dist/commands/uninstall.d.ts +13 -0
- package/dist/commands/uninstall.js +111 -0
- package/dist/config.d.ts +17 -0
- package/dist/config.js +90 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +35 -0
- package/dist/init.d.ts +9 -0
- package/dist/init.js +50 -0
- package/dist/install/claude-code.d.ts +13 -0
- package/dist/install/claude-code.js +118 -0
- package/dist/install/cursor.d.ts +13 -0
- package/dist/install/cursor.js +119 -0
- package/dist/install/detect.d.ts +5 -0
- package/dist/install/detect.js +64 -0
- package/dist/middleware/auth.d.ts +15 -0
- package/dist/middleware/auth.js +116 -0
- package/dist/routes/adapters/claude-code.d.ts +38 -0
- package/dist/routes/adapters/claude-code.js +125 -0
- package/dist/routes/adapters/cursor.d.ts +21 -0
- package/dist/routes/adapters/cursor.js +139 -0
- package/dist/routes/adapters/index.d.ts +16 -0
- package/dist/routes/adapters/index.js +56 -0
- package/dist/routes/adapters/router.d.ts +31 -0
- package/dist/routes/adapters/router.js +97 -0
- package/dist/routes/adapters/schema.d.ts +141 -0
- package/dist/routes/adapters/schema.js +83 -0
- package/dist/routes/adapters/windsurf.d.ts +6 -0
- package/dist/routes/adapters/windsurf.js +48 -0
- package/dist/routes/admin.d.ts +15 -0
- package/dist/routes/admin.js +399 -0
- package/dist/routes/call.d.ts +13 -0
- package/dist/routes/call.js +68 -0
- package/dist/routes/events.d.ts +7 -0
- package/dist/routes/events.js +125 -0
- package/dist/routes/health.d.ts +2 -0
- package/dist/routes/health.js +12 -0
- package/dist/routes/hooks.d.ts +11 -0
- package/dist/routes/hooks.js +166 -0
- package/dist/routes/mcp.d.ts +10 -0
- package/dist/routes/mcp.js +170 -0
- package/dist/routes/openai-tools.d.ts +9 -0
- package/dist/routes/openai-tools.js +121 -0
- package/dist/server.d.ts +11 -0
- package/dist/server.js +118 -0
- package/dist/services/audit.d.ts +92 -0
- package/dist/services/audit.js +388 -0
- package/dist/services/data-dir.d.ts +7 -0
- package/dist/services/data-dir.js +61 -0
- package/dist/services/local-policy-templates.d.ts +9 -0
- package/dist/services/local-policy-templates.js +47 -0
- package/dist/services/local-policy.d.ts +39 -0
- package/dist/services/local-policy.js +172 -0
- package/dist/services/policy-store.d.ts +82 -0
- package/dist/services/policy-store.js +331 -0
- package/dist/services/policy.d.ts +8 -0
- package/dist/services/policy.js +126 -0
- package/dist/services/ratelimit.d.ts +26 -0
- package/dist/services/ratelimit.js +60 -0
- package/dist/services/sanitizer.d.ts +9 -0
- package/dist/services/sanitizer.js +73 -0
- package/dist/services/sqlite-loader.d.ts +4 -0
- package/dist/services/sqlite-loader.js +16 -0
- package/dist/services/telemetry-log.d.ts +76 -0
- package/dist/services/telemetry-log.js +260 -0
- package/dist/services/tool-executor.d.ts +46 -0
- package/dist/services/tool-executor.js +167 -0
- package/dist/services/upstream.d.ts +18 -0
- package/dist/services/upstream.js +72 -0
- package/dist/types.d.ts +112 -0
- package/dist/types.js +3 -0
- package/package.json +72 -0
- package/public/favicon.svg +4 -0
- package/public/index.html +3893 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.detectAdapter = detectAdapter;
|
|
37
|
+
const ClaudeCode = __importStar(require("./claude-code"));
|
|
38
|
+
const Cursor = __importStar(require("./cursor"));
|
|
39
|
+
const Windsurf = __importStar(require("./windsurf"));
|
|
40
|
+
// Order matters: most specific first to avoid false positives
|
|
41
|
+
const ADAPTERS = [
|
|
42
|
+
ClaudeCode,
|
|
43
|
+
Cursor,
|
|
44
|
+
Windsurf,
|
|
45
|
+
];
|
|
46
|
+
/**
|
|
47
|
+
* Find the first adapter that recognises the given payload.
|
|
48
|
+
* Returns null if no adapter matches (unknown source).
|
|
49
|
+
*/
|
|
50
|
+
function detectAdapter(body) {
|
|
51
|
+
for (const adapter of ADAPTERS) {
|
|
52
|
+
if (adapter.detect(body))
|
|
53
|
+
return adapter;
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { OculiEvent } from './schema';
|
|
2
|
+
import { HookAdapter } from './index';
|
|
3
|
+
export interface RouteResult {
|
|
4
|
+
/** Canonical adapter name ('claude-code', 'cursor', 'windsurf'). */
|
|
5
|
+
adapterName: string;
|
|
6
|
+
/** The adapter that handled this payload. */
|
|
7
|
+
adapter: HookAdapter;
|
|
8
|
+
/** Normalised event — the only thing the policy engine and telemetry see. */
|
|
9
|
+
event: OculiEvent;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Route a parsed hook payload to the correct adapter.
|
|
13
|
+
*
|
|
14
|
+
* Priority:
|
|
15
|
+
* 1. Explicit — if `explicit` names a known adapter, use it immediately
|
|
16
|
+
* without inspecting the payload. Zero detection overhead, deterministic.
|
|
17
|
+
* 2. Auto-detect — iterate the registry until an adapter's detect() matches.
|
|
18
|
+
*
|
|
19
|
+
* Returns null when no adapter matches.
|
|
20
|
+
*/
|
|
21
|
+
export declare function route(body: Record<string, unknown>, explicit?: string): RouteResult | null;
|
|
22
|
+
/**
|
|
23
|
+
* Route a raw JSON string (hook stdin) to the correct adapter.
|
|
24
|
+
* Parses stdin and delegates to route().
|
|
25
|
+
*
|
|
26
|
+
* @param stdin Raw JSON string from the IDE hook.
|
|
27
|
+
* @param explicitName Optional adapter name — skips auto-detection when provided.
|
|
28
|
+
*/
|
|
29
|
+
export declare function routeStdin(stdin: string, explicitName?: string): RouteResult | null;
|
|
30
|
+
/** Returns the canonical names of all registered adapters. */
|
|
31
|
+
export declare function registeredAdapters(): string[];
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.route = route;
|
|
37
|
+
exports.routeStdin = routeStdin;
|
|
38
|
+
exports.registeredAdapters = registeredAdapters;
|
|
39
|
+
const ClaudeCode = __importStar(require("./claude-code"));
|
|
40
|
+
const Cursor = __importStar(require("./cursor"));
|
|
41
|
+
const Windsurf = __importStar(require("./windsurf"));
|
|
42
|
+
/**
|
|
43
|
+
* Central adapter registry.
|
|
44
|
+
* Add new IDE adapters here — nothing else needs to change.
|
|
45
|
+
*/
|
|
46
|
+
const ADAPTER_REGISTRY = {
|
|
47
|
+
'claude-code': ClaudeCode,
|
|
48
|
+
'cursor': Cursor,
|
|
49
|
+
'windsurf': Windsurf,
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Route a parsed hook payload to the correct adapter.
|
|
53
|
+
*
|
|
54
|
+
* Priority:
|
|
55
|
+
* 1. Explicit — if `explicit` names a known adapter, use it immediately
|
|
56
|
+
* without inspecting the payload. Zero detection overhead, deterministic.
|
|
57
|
+
* 2. Auto-detect — iterate the registry until an adapter's detect() matches.
|
|
58
|
+
*
|
|
59
|
+
* Returns null when no adapter matches.
|
|
60
|
+
*/
|
|
61
|
+
function route(body, explicit) {
|
|
62
|
+
// Strategy 1 — explicit adapter by name
|
|
63
|
+
if (explicit) {
|
|
64
|
+
const adapter = ADAPTER_REGISTRY[explicit.toLowerCase()];
|
|
65
|
+
if (adapter) {
|
|
66
|
+
return { adapterName: explicit.toLowerCase(), adapter, event: adapter.normalize(body) };
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// Strategy 2 — auto-detect from payload fields
|
|
70
|
+
for (const [name, adapter] of Object.entries(ADAPTER_REGISTRY)) {
|
|
71
|
+
if (adapter.detect(body)) {
|
|
72
|
+
return { adapterName: name, adapter, event: adapter.normalize(body) };
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Route a raw JSON string (hook stdin) to the correct adapter.
|
|
79
|
+
* Parses stdin and delegates to route().
|
|
80
|
+
*
|
|
81
|
+
* @param stdin Raw JSON string from the IDE hook.
|
|
82
|
+
* @param explicitName Optional adapter name — skips auto-detection when provided.
|
|
83
|
+
*/
|
|
84
|
+
function routeStdin(stdin, explicitName) {
|
|
85
|
+
let body;
|
|
86
|
+
try {
|
|
87
|
+
body = JSON.parse(stdin);
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
return route(body, explicitName);
|
|
93
|
+
}
|
|
94
|
+
/** Returns the canonical names of all registered adapters. */
|
|
95
|
+
function registeredAdapters() {
|
|
96
|
+
return Object.keys(ADAPTER_REGISTRY);
|
|
97
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* OculiEvent — canonical hook event schema (schema_version: '1')
|
|
4
|
+
*
|
|
5
|
+
* All IDE hook adapters (Claude Code, Cursor, Windsurf, …) normalize their
|
|
6
|
+
* raw wire payloads to this shape before the gateway processes them.
|
|
7
|
+
*
|
|
8
|
+
* snake_case throughout to match the raw wire formats as closely as possible.
|
|
9
|
+
*/
|
|
10
|
+
export declare const OculiEventSchema: z.ZodObject<{
|
|
11
|
+
/** Increment when the schema has breaking changes. */
|
|
12
|
+
schema_version: z.ZodLiteral<"1">;
|
|
13
|
+
/** Which IDE emitted the hook: 'claude-code' | 'cursor' | 'windsurf' | … */
|
|
14
|
+
ide_source: z.ZodString;
|
|
15
|
+
/** Actor name. Defaults to ide_source when no JWT is present. */
|
|
16
|
+
actor: z.ZodString;
|
|
17
|
+
/** Organization ID. Defaults to 'default'. */
|
|
18
|
+
org_id: z.ZodDefault<z.ZodString>;
|
|
19
|
+
/** Raw event name from the IDE, preserved verbatim (e.g. 'PreToolUse', 'beforeShellExecution'). */
|
|
20
|
+
hook_event_name: z.ZodString;
|
|
21
|
+
/**
|
|
22
|
+
* Lifecycle phase:
|
|
23
|
+
* pre — hook fires before tool execution; enforcement gate
|
|
24
|
+
* post — hook fires after tool execution; telemetry only
|
|
25
|
+
* complete — agent session ended (Cursor 'stop' event); telemetry only
|
|
26
|
+
*/
|
|
27
|
+
phase: z.ZodEnum<["pre", "post", "complete"]>;
|
|
28
|
+
/** Session identifier. Claude Code: session_id. Cursor: conversation_id. */
|
|
29
|
+
session_id: z.ZodString;
|
|
30
|
+
/** Sub-session trace. Cursor: generation_id. */
|
|
31
|
+
trace_id: z.ZodOptional<z.ZodString>;
|
|
32
|
+
/** Normalized tool name. Absent for stop/complete events. */
|
|
33
|
+
tool: z.ZodOptional<z.ZodString>;
|
|
34
|
+
/** Tool arguments as a key-value map. */
|
|
35
|
+
tool_args: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
36
|
+
/** Tool result (post-phase only). */
|
|
37
|
+
tool_result: z.ZodOptional<z.ZodUnknown>;
|
|
38
|
+
/** File path for file-oriented operations (readFile, writeFile). */
|
|
39
|
+
file_path: z.ZodOptional<z.ZodString>;
|
|
40
|
+
/** Shell command string for shell execution events. */
|
|
41
|
+
shell_command: z.ZodOptional<z.ZodString>;
|
|
42
|
+
/** MCP server name for MCP tool calls. */
|
|
43
|
+
mcp_server: z.ZodOptional<z.ZodString>;
|
|
44
|
+
/** Workspace root directories (Cursor). */
|
|
45
|
+
workspace_roots: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
46
|
+
/** Current working directory (Claude Code). */
|
|
47
|
+
cwd: z.ZodOptional<z.ZodString>;
|
|
48
|
+
/** ISO 8601 timestamp; set by the adapter at normalize time. */
|
|
49
|
+
timestamp: z.ZodString;
|
|
50
|
+
/** Tool execution duration in milliseconds (post-phase only). */
|
|
51
|
+
duration_ms: z.ZodOptional<z.ZodNumber>;
|
|
52
|
+
/** Error message if the tool failed. */
|
|
53
|
+
error: z.ZodOptional<z.ZodString>;
|
|
54
|
+
/** Workspace and identity context, unified from IDE-specific fields. */
|
|
55
|
+
context: z.ZodOptional<z.ZodObject<{
|
|
56
|
+
/** Workspace root (Claude Code: cwd; Cursor: workspace_roots[0]). */
|
|
57
|
+
workspace: z.ZodOptional<z.ZodString>;
|
|
58
|
+
/** IDE conversation identifier (Cursor: conversation_id). */
|
|
59
|
+
conversation_id: z.ZodOptional<z.ZodString>;
|
|
60
|
+
}, "strip", z.ZodTypeAny, {
|
|
61
|
+
workspace?: string | undefined;
|
|
62
|
+
conversation_id?: string | undefined;
|
|
63
|
+
}, {
|
|
64
|
+
workspace?: string | undefined;
|
|
65
|
+
conversation_id?: string | undefined;
|
|
66
|
+
}>>;
|
|
67
|
+
/** Normalised action data for the tool invocation. */
|
|
68
|
+
action: z.ZodOptional<z.ZodObject<{
|
|
69
|
+
/** Shell command for shell execution events (tool: 'shell'). */
|
|
70
|
+
command: z.ZodOptional<z.ZodString>;
|
|
71
|
+
/** SHA-256 prefix (16 hex chars) of file content for read/edit events. */
|
|
72
|
+
content_hash: z.ZodOptional<z.ZodString>;
|
|
73
|
+
}, "strip", z.ZodTypeAny, {
|
|
74
|
+
command?: string | undefined;
|
|
75
|
+
content_hash?: string | undefined;
|
|
76
|
+
}, {
|
|
77
|
+
command?: string | undefined;
|
|
78
|
+
content_hash?: string | undefined;
|
|
79
|
+
}>>;
|
|
80
|
+
/** Original wire payload, preserved verbatim for audit and debugging. */
|
|
81
|
+
raw_payload: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
82
|
+
}, "strip", z.ZodTypeAny, {
|
|
83
|
+
schema_version: "1";
|
|
84
|
+
ide_source: string;
|
|
85
|
+
actor: string;
|
|
86
|
+
org_id: string;
|
|
87
|
+
hook_event_name: string;
|
|
88
|
+
phase: "pre" | "post" | "complete";
|
|
89
|
+
session_id: string;
|
|
90
|
+
timestamp: string;
|
|
91
|
+
raw_payload: Record<string, unknown>;
|
|
92
|
+
trace_id?: string | undefined;
|
|
93
|
+
tool?: string | undefined;
|
|
94
|
+
tool_args?: Record<string, unknown> | undefined;
|
|
95
|
+
tool_result?: unknown;
|
|
96
|
+
file_path?: string | undefined;
|
|
97
|
+
shell_command?: string | undefined;
|
|
98
|
+
mcp_server?: string | undefined;
|
|
99
|
+
workspace_roots?: string[] | undefined;
|
|
100
|
+
cwd?: string | undefined;
|
|
101
|
+
duration_ms?: number | undefined;
|
|
102
|
+
error?: string | undefined;
|
|
103
|
+
context?: {
|
|
104
|
+
workspace?: string | undefined;
|
|
105
|
+
conversation_id?: string | undefined;
|
|
106
|
+
} | undefined;
|
|
107
|
+
action?: {
|
|
108
|
+
command?: string | undefined;
|
|
109
|
+
content_hash?: string | undefined;
|
|
110
|
+
} | undefined;
|
|
111
|
+
}, {
|
|
112
|
+
schema_version: "1";
|
|
113
|
+
ide_source: string;
|
|
114
|
+
actor: string;
|
|
115
|
+
hook_event_name: string;
|
|
116
|
+
phase: "pre" | "post" | "complete";
|
|
117
|
+
session_id: string;
|
|
118
|
+
timestamp: string;
|
|
119
|
+
raw_payload: Record<string, unknown>;
|
|
120
|
+
org_id?: string | undefined;
|
|
121
|
+
trace_id?: string | undefined;
|
|
122
|
+
tool?: string | undefined;
|
|
123
|
+
tool_args?: Record<string, unknown> | undefined;
|
|
124
|
+
tool_result?: unknown;
|
|
125
|
+
file_path?: string | undefined;
|
|
126
|
+
shell_command?: string | undefined;
|
|
127
|
+
mcp_server?: string | undefined;
|
|
128
|
+
workspace_roots?: string[] | undefined;
|
|
129
|
+
cwd?: string | undefined;
|
|
130
|
+
duration_ms?: number | undefined;
|
|
131
|
+
error?: string | undefined;
|
|
132
|
+
context?: {
|
|
133
|
+
workspace?: string | undefined;
|
|
134
|
+
conversation_id?: string | undefined;
|
|
135
|
+
} | undefined;
|
|
136
|
+
action?: {
|
|
137
|
+
command?: string | undefined;
|
|
138
|
+
content_hash?: string | undefined;
|
|
139
|
+
} | undefined;
|
|
140
|
+
}>;
|
|
141
|
+
export type OculiEvent = z.infer<typeof OculiEventSchema>;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OculiEventSchema = void 0;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
/**
|
|
6
|
+
* OculiEvent — canonical hook event schema (schema_version: '1')
|
|
7
|
+
*
|
|
8
|
+
* All IDE hook adapters (Claude Code, Cursor, Windsurf, …) normalize their
|
|
9
|
+
* raw wire payloads to this shape before the gateway processes them.
|
|
10
|
+
*
|
|
11
|
+
* snake_case throughout to match the raw wire formats as closely as possible.
|
|
12
|
+
*/
|
|
13
|
+
exports.OculiEventSchema = zod_1.z.object({
|
|
14
|
+
/** Increment when the schema has breaking changes. */
|
|
15
|
+
schema_version: zod_1.z.literal('1'),
|
|
16
|
+
// ── Source identity ────────────────────────────────────────────────────────
|
|
17
|
+
/** Which IDE emitted the hook: 'claude-code' | 'cursor' | 'windsurf' | … */
|
|
18
|
+
ide_source: zod_1.z.string().min(1),
|
|
19
|
+
/** Actor name. Defaults to ide_source when no JWT is present. */
|
|
20
|
+
actor: zod_1.z.string().min(1),
|
|
21
|
+
/** Organization ID. Defaults to 'default'. */
|
|
22
|
+
org_id: zod_1.z.string().default('default'),
|
|
23
|
+
// ── Hook identity ──────────────────────────────────────────────────────────
|
|
24
|
+
/** Raw event name from the IDE, preserved verbatim (e.g. 'PreToolUse', 'beforeShellExecution'). */
|
|
25
|
+
hook_event_name: zod_1.z.string().min(1),
|
|
26
|
+
/**
|
|
27
|
+
* Lifecycle phase:
|
|
28
|
+
* pre — hook fires before tool execution; enforcement gate
|
|
29
|
+
* post — hook fires after tool execution; telemetry only
|
|
30
|
+
* complete — agent session ended (Cursor 'stop' event); telemetry only
|
|
31
|
+
*/
|
|
32
|
+
phase: zod_1.z.enum(['pre', 'post', 'complete']),
|
|
33
|
+
// ── Session / trace ────────────────────────────────────────────────────────
|
|
34
|
+
/** Session identifier. Claude Code: session_id. Cursor: conversation_id. */
|
|
35
|
+
session_id: zod_1.z.string().min(1),
|
|
36
|
+
/** Sub-session trace. Cursor: generation_id. */
|
|
37
|
+
trace_id: zod_1.z.string().optional(),
|
|
38
|
+
// ── Tool invocation (absent for 'stop' / 'complete' events) ───────────────
|
|
39
|
+
/** Normalized tool name. Absent for stop/complete events. */
|
|
40
|
+
tool: zod_1.z.string().optional(),
|
|
41
|
+
/** Tool arguments as a key-value map. */
|
|
42
|
+
tool_args: zod_1.z.record(zod_1.z.unknown()).optional(),
|
|
43
|
+
/** Tool result (post-phase only). */
|
|
44
|
+
tool_result: zod_1.z.unknown().optional(),
|
|
45
|
+
// ── Resource context ───────────────────────────────────────────────────────
|
|
46
|
+
/** File path for file-oriented operations (readFile, writeFile). */
|
|
47
|
+
file_path: zod_1.z.string().optional(),
|
|
48
|
+
/** Shell command string for shell execution events. */
|
|
49
|
+
shell_command: zod_1.z.string().optional(),
|
|
50
|
+
/** MCP server name for MCP tool calls. */
|
|
51
|
+
mcp_server: zod_1.z.string().optional(),
|
|
52
|
+
/** Workspace root directories (Cursor). */
|
|
53
|
+
workspace_roots: zod_1.z.array(zod_1.z.string()).optional(),
|
|
54
|
+
/** Current working directory (Claude Code). */
|
|
55
|
+
cwd: zod_1.z.string().optional(),
|
|
56
|
+
// ── Timing ─────────────────────────────────────────────────────────────────
|
|
57
|
+
/** ISO 8601 timestamp; set by the adapter at normalize time. */
|
|
58
|
+
timestamp: zod_1.z.string().min(1),
|
|
59
|
+
/** Tool execution duration in milliseconds (post-phase only). */
|
|
60
|
+
duration_ms: zod_1.z.number().optional(),
|
|
61
|
+
// ── Status ─────────────────────────────────────────────────────────────────
|
|
62
|
+
/** Error message if the tool failed. */
|
|
63
|
+
error: zod_1.z.string().optional(),
|
|
64
|
+
// ── Normalised cross-IDE context ───────────────────────────────────────────
|
|
65
|
+
/** Workspace and identity context, unified from IDE-specific fields. */
|
|
66
|
+
context: zod_1.z.object({
|
|
67
|
+
/** Workspace root (Claude Code: cwd; Cursor: workspace_roots[0]). */
|
|
68
|
+
workspace: zod_1.z.string().optional(),
|
|
69
|
+
/** IDE conversation identifier (Cursor: conversation_id). */
|
|
70
|
+
conversation_id: zod_1.z.string().optional(),
|
|
71
|
+
}).optional(),
|
|
72
|
+
// ── Normalised action payload ──────────────────────────────────────────────
|
|
73
|
+
/** Normalised action data for the tool invocation. */
|
|
74
|
+
action: zod_1.z.object({
|
|
75
|
+
/** Shell command for shell execution events (tool: 'shell'). */
|
|
76
|
+
command: zod_1.z.string().optional(),
|
|
77
|
+
/** SHA-256 prefix (16 hex chars) of file content for read/edit events. */
|
|
78
|
+
content_hash: zod_1.z.string().optional(),
|
|
79
|
+
}).optional(),
|
|
80
|
+
// ── Raw payload ────────────────────────────────────────────────────────────
|
|
81
|
+
/** Original wire payload, preserved verbatim for audit and debugging. */
|
|
82
|
+
raw_payload: zod_1.z.record(zod_1.z.unknown()),
|
|
83
|
+
});
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { OculiEvent } from './schema';
|
|
2
|
+
export declare function detect(body: Record<string, unknown>): boolean;
|
|
3
|
+
export declare function normalize(body: Record<string, unknown>): OculiEvent;
|
|
4
|
+
export declare function formatAllow(_event: OculiEvent): string;
|
|
5
|
+
export declare function formatDeny(reason: string, _event: OculiEvent): string;
|
|
6
|
+
export declare function denyStatusCode(): number;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.detect = detect;
|
|
4
|
+
exports.normalize = normalize;
|
|
5
|
+
exports.formatAllow = formatAllow;
|
|
6
|
+
exports.formatDeny = formatDeny;
|
|
7
|
+
exports.denyStatusCode = denyStatusCode;
|
|
8
|
+
function detect(body) {
|
|
9
|
+
return (typeof body.agent_action_name === 'string' &&
|
|
10
|
+
body.agent_action_name.startsWith('pre_mcp_tool_use') ||
|
|
11
|
+
(typeof body.agent_action_name === 'string' &&
|
|
12
|
+
body.agent_action_name.startsWith('post_mcp_tool_use')));
|
|
13
|
+
}
|
|
14
|
+
function normalize(body) {
|
|
15
|
+
const actionName = body.agent_action_name;
|
|
16
|
+
const phase = actionName.startsWith('pre_') ? 'pre' : 'post';
|
|
17
|
+
const toolInfo = body.tool_info;
|
|
18
|
+
const tool = toolInfo
|
|
19
|
+
? `${toolInfo.mcp_server_name}__${toolInfo.mcp_tool_name}`
|
|
20
|
+
: undefined;
|
|
21
|
+
const mcp_server = toolInfo?.mcp_server_name;
|
|
22
|
+
return {
|
|
23
|
+
schema_version: '1',
|
|
24
|
+
ide_source: 'windsurf',
|
|
25
|
+
actor: 'windsurf',
|
|
26
|
+
org_id: 'default',
|
|
27
|
+
hook_event_name: actionName,
|
|
28
|
+
phase,
|
|
29
|
+
session_id: body.trajectory_id ?? 'windsurf-unknown',
|
|
30
|
+
trace_id: body.execution_id,
|
|
31
|
+
tool,
|
|
32
|
+
tool_args: toolInfo?.mcp_tool_arguments,
|
|
33
|
+
mcp_server,
|
|
34
|
+
timestamp: body.timestamp ?? new Date().toISOString(),
|
|
35
|
+
raw_payload: body,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
// Windsurf allow: HTTP 200, no specific body needed
|
|
39
|
+
function formatAllow(_event) {
|
|
40
|
+
return JSON.stringify({ status: 'allow' });
|
|
41
|
+
}
|
|
42
|
+
// Windsurf block: caller sends HTTP 403 — body is informational only
|
|
43
|
+
function formatDeny(reason, _event) {
|
|
44
|
+
return JSON.stringify({ status: 'deny', reason });
|
|
45
|
+
}
|
|
46
|
+
function denyStatusCode() {
|
|
47
|
+
return 403; // Windsurf uses HTTP 403 to signal a block
|
|
48
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { FastifyInstance } from 'fastify';
|
|
2
|
+
import { AppConfig } from '../config';
|
|
3
|
+
import { AuditService } from '../services/audit';
|
|
4
|
+
import { UpstreamConnector } from '../services/upstream';
|
|
5
|
+
import { PolicyStore } from '../services/policy-store';
|
|
6
|
+
import { PolicyService } from '../services/policy';
|
|
7
|
+
interface AdminRouteContext {
|
|
8
|
+
audit: AuditService;
|
|
9
|
+
upstream: UpstreamConnector;
|
|
10
|
+
config: AppConfig;
|
|
11
|
+
policyStore: PolicyStore;
|
|
12
|
+
policy: PolicyService;
|
|
13
|
+
}
|
|
14
|
+
export declare function registerAdminRoutes(app: FastifyInstance, ctx: AdminRouteContext): void;
|
|
15
|
+
export {};
|