@ory/gemini-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/README.md +100 -0
- package/dist/cli/assets.d.ts +16 -0
- package/dist/cli/assets.js +102 -0
- package/dist/cli/main.d.ts +12 -0
- package/dist/cli/main.js +202 -0
- package/dist/cli/setup.d.ts +11 -0
- package/dist/cli/setup.js +102 -0
- package/dist/handlers.d.ts +12 -0
- package/dist/handlers.js +312 -0
- package/dist/hook.d.ts +9 -0
- package/dist/hook.js +74 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +7 -0
- package/dist/settings.d.ts +12 -0
- package/dist/settings.js +66 -0
- package/dist/types.d.ts +65 -0
- package/dist/types.js +2 -0
- package/gemini-extension/GEMINI.md +5 -0
- package/gemini-extension/gemini-extension.json +26 -0
- package/gemini-extension/hooks/hooks.json +102 -0
- package/package.json +83 -0
package/dist/handlers.js
ADDED
|
@@ -0,0 +1,312 @@
|
|
|
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 Gemini CLI 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 "SessionEnd":
|
|
25
|
+
return await handleSessionEnd(input, client);
|
|
26
|
+
case "BeforeTool":
|
|
27
|
+
return await handleBeforeTool(input, client);
|
|
28
|
+
case "AfterTool":
|
|
29
|
+
return await handleAfterTool(input, client);
|
|
30
|
+
case "BeforeToolSelection":
|
|
31
|
+
return await handleBeforeToolSelection(input, client);
|
|
32
|
+
case "Notification":
|
|
33
|
+
return await handleNotification(input, client);
|
|
34
|
+
case "PreCompress":
|
|
35
|
+
return await handlePreCompress(input, client);
|
|
36
|
+
default:
|
|
37
|
+
client.logger.debug("hook.passthrough", { event });
|
|
38
|
+
client.tracer.record("hook.passthrough", "skipped", {
|
|
39
|
+
attributes: { event },
|
|
40
|
+
});
|
|
41
|
+
return {};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
finally {
|
|
45
|
+
client.tracer.clearContext();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// ─── SessionStart ───────────────────────────────────────────────────
|
|
49
|
+
async function handleSessionStart(input, client, deps) {
|
|
50
|
+
client.logger.info("lifecycle.session_start", {
|
|
51
|
+
sessionId: input.session_id,
|
|
52
|
+
source: input.source,
|
|
53
|
+
cwd: input.cwd,
|
|
54
|
+
});
|
|
55
|
+
client.tracer.record("session.start", "ok", {
|
|
56
|
+
attributes: { source: input.source },
|
|
57
|
+
});
|
|
58
|
+
const userGate = deps.authGate ?? argus_1.ensureUserAuthenticated;
|
|
59
|
+
const decision = await userGate(client, {
|
|
60
|
+
binName: "ory-gemini",
|
|
61
|
+
harness: "gemini-cli",
|
|
62
|
+
allowBlock: true,
|
|
63
|
+
});
|
|
64
|
+
// Resolve the agent identity (machine credentials). Never blocks;
|
|
65
|
+
// attaches the agent's bearer token to outgoing Ory API calls.
|
|
66
|
+
const agentGate = deps.agentGate ?? argus_1.ensureAgentIdentity;
|
|
67
|
+
await agentGate(client, { projectUrl: (0, argus_1.resolveConfig)().projectUrl, harness: "gemini-cli" });
|
|
68
|
+
if (!decision.proceed) {
|
|
69
|
+
return { decision: "deny", reason: decision.reason };
|
|
70
|
+
}
|
|
71
|
+
if (decision.mode !== "disabled") {
|
|
72
|
+
return {};
|
|
73
|
+
}
|
|
74
|
+
const resolved = (0, argus_1.resolveConfig)();
|
|
75
|
+
if (resolved.auditOnly) {
|
|
76
|
+
client.logger.info("config.audit_only", {
|
|
77
|
+
message: "Audit-only mode enabled. Auth and permission checks are disabled.",
|
|
78
|
+
});
|
|
79
|
+
return {};
|
|
80
|
+
}
|
|
81
|
+
if (!resolved.projectUrl) {
|
|
82
|
+
client.logger.warn("config.not_configured", {
|
|
83
|
+
message: "Ory plugin is not configured. Auth and permission checks are disabled. " +
|
|
84
|
+
"Run 'npx ory-gemini configure' to connect to an Ory project.",
|
|
85
|
+
});
|
|
86
|
+
return {};
|
|
87
|
+
}
|
|
88
|
+
// Try session token first, then OAuth2 token
|
|
89
|
+
const sessionToken = process.env.ORY_SESSION_TOKEN;
|
|
90
|
+
const oauth2Token = process.env.ORY_OAUTH2_TOKEN;
|
|
91
|
+
if (sessionToken) {
|
|
92
|
+
await verifySessionToken(sessionToken, client);
|
|
93
|
+
return {};
|
|
94
|
+
}
|
|
95
|
+
if (oauth2Token) {
|
|
96
|
+
await verifyOAuth2Token(oauth2Token, client);
|
|
97
|
+
return {};
|
|
98
|
+
}
|
|
99
|
+
client.logger.warn("session.no_credentials", {
|
|
100
|
+
message: "Neither ORY_SESSION_TOKEN nor ORY_OAUTH2_TOKEN is set. " +
|
|
101
|
+
"Skipping authentication.",
|
|
102
|
+
});
|
|
103
|
+
return {};
|
|
104
|
+
}
|
|
105
|
+
async function verifySessionToken(token, client) {
|
|
106
|
+
try {
|
|
107
|
+
const session = await client.verifySession(token);
|
|
108
|
+
if (!session.active) {
|
|
109
|
+
client.logger.warn("session.inactive", {
|
|
110
|
+
message: "Ory session is not active. Re-authenticate to enable auth checks.",
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
const oryErr = err;
|
|
116
|
+
client.logger.warn("session.verify_failed", {
|
|
117
|
+
code: oryErr.code,
|
|
118
|
+
message: oryErr.message,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
async function verifyOAuth2Token(token, client) {
|
|
123
|
+
try {
|
|
124
|
+
const tokenInfo = await client.introspectToken(token);
|
|
125
|
+
if (!tokenInfo.active) {
|
|
126
|
+
client.logger.warn("oauth2.token_inactive", {
|
|
127
|
+
message: "Ory OAuth2 token is not active. Obtain a new token to enable auth checks.",
|
|
128
|
+
});
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
client.logger.info("oauth2.session_authenticated", {
|
|
132
|
+
clientId: tokenInfo.clientId,
|
|
133
|
+
subject: tokenInfo.subject,
|
|
134
|
+
scope: tokenInfo.scope,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
const oryErr = err;
|
|
139
|
+
client.logger.warn("oauth2.introspect_failed", {
|
|
140
|
+
code: oryErr.code,
|
|
141
|
+
message: oryErr.message,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
// ─── BeforeTool ────────────────────────────────────────────────────
|
|
146
|
+
async function handleBeforeTool(input, client) {
|
|
147
|
+
const toolName = input.tool_name ?? "unknown";
|
|
148
|
+
client.logger.info("lifecycle.before_tool", {
|
|
149
|
+
sessionId: input.session_id,
|
|
150
|
+
toolName,
|
|
151
|
+
toolInput: input.tool_input,
|
|
152
|
+
});
|
|
153
|
+
const inputSummary = (0, argus_1.summarizeToolInput)(toolName, input.tool_input);
|
|
154
|
+
// In audit-only mode, log the invocation but skip permission checks
|
|
155
|
+
if ((0, argus_1.resolveConfig)().auditOnly) {
|
|
156
|
+
client.tracer.record("tool.invoke", "ok", {
|
|
157
|
+
attributes: { toolName, ...inputSummary },
|
|
158
|
+
});
|
|
159
|
+
return {};
|
|
160
|
+
}
|
|
161
|
+
const subject = (0, argus_1.resolveUserSubject)(client, `session:${input.session_id}`);
|
|
162
|
+
const subjectId = (0, argus_1.subjectLabel)(subject);
|
|
163
|
+
const mcpTool = (0, argus_1.parseGeminiMcpTool)(toolName, input.mcp_context);
|
|
164
|
+
try {
|
|
165
|
+
if (mcpTool) {
|
|
166
|
+
const mcpResult = await (0, argus_1.checkMcpPermission)(client, mcpTool, {
|
|
167
|
+
subject,
|
|
168
|
+
spanAttributes: { toolName },
|
|
169
|
+
});
|
|
170
|
+
if (!mcpResult.allowed) {
|
|
171
|
+
client.tracer.record("tool.block", "denied", {
|
|
172
|
+
attributes: { toolName, mcpServer: mcpTool.serverName, mcpTool: mcpTool.toolName, ...inputSummary, ...(0, argus_1.alertAttributes)(true) },
|
|
173
|
+
});
|
|
174
|
+
return {
|
|
175
|
+
decision: "deny",
|
|
176
|
+
reason: (0, argus_1.formatDenialMessage)({ tool: toolName, subjectId, mcp: mcpTool }),
|
|
177
|
+
systemMessage: (0, argus_1.formatDenialSummary)({ tool: toolName, subjectId, mcp: mcpTool }),
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
client.tracer.record("tool.invoke", "ok", {
|
|
181
|
+
attributes: { toolName, mcpServer: mcpTool.serverName, mcpTool: mcpTool.toolName, ...inputSummary },
|
|
182
|
+
});
|
|
183
|
+
return {};
|
|
184
|
+
}
|
|
185
|
+
const namespace = resolveNamespace();
|
|
186
|
+
const result = await client.checkPermission({
|
|
187
|
+
namespace,
|
|
188
|
+
object: toolName,
|
|
189
|
+
relation: "use",
|
|
190
|
+
...subject,
|
|
191
|
+
}, { spanAttributes: { toolName } });
|
|
192
|
+
if (!result.allowed) {
|
|
193
|
+
client.tracer.record("tool.block", "denied", {
|
|
194
|
+
attributes: { toolName, ...inputSummary, allowed: result.allowed, ...(0, argus_1.alertAttributes)(true) },
|
|
195
|
+
});
|
|
196
|
+
return {
|
|
197
|
+
decision: "deny",
|
|
198
|
+
reason: (0, argus_1.formatDenialMessage)({ tool: toolName, subjectId, namespace }),
|
|
199
|
+
systemMessage: (0, argus_1.formatDenialSummary)({ tool: toolName, subjectId, namespace }),
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
client.tracer.record("tool.invoke", "ok", {
|
|
203
|
+
attributes: { toolName, ...inputSummary, allowed: result.allowed },
|
|
204
|
+
});
|
|
205
|
+
return {};
|
|
206
|
+
}
|
|
207
|
+
catch (err) {
|
|
208
|
+
return handlePermissionError(err, toolName, client);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
// ─── AfterTool ─────────────────────────────────────────────────────
|
|
212
|
+
async function handleAfterTool(input, client) {
|
|
213
|
+
const toolName = input.tool_name ?? "unknown";
|
|
214
|
+
client.logger.info("lifecycle.after_tool", {
|
|
215
|
+
sessionId: input.session_id,
|
|
216
|
+
toolName,
|
|
217
|
+
toolResponse: input.tool_response,
|
|
218
|
+
});
|
|
219
|
+
client.tracer.record("tool.complete", "ok", {
|
|
220
|
+
attributes: {
|
|
221
|
+
toolName,
|
|
222
|
+
...(0, argus_1.summarizeToolInput)(toolName, input.tool_input),
|
|
223
|
+
...(0, argus_1.summarizeToolOutput)(toolName, input.tool_response),
|
|
224
|
+
},
|
|
225
|
+
});
|
|
226
|
+
return {};
|
|
227
|
+
}
|
|
228
|
+
// ─── SessionEnd ────────────────────────────────────────────────────
|
|
229
|
+
async function handleSessionEnd(input, client) {
|
|
230
|
+
client.logger.info("lifecycle.session_end", {
|
|
231
|
+
sessionId: input.session_id,
|
|
232
|
+
reason: input.reason,
|
|
233
|
+
});
|
|
234
|
+
client.tracer.record("session.end", "ok", {
|
|
235
|
+
attributes: { reason: input.reason },
|
|
236
|
+
});
|
|
237
|
+
return {};
|
|
238
|
+
}
|
|
239
|
+
// ─── BeforeToolSelection ───────────────────────────────────────────
|
|
240
|
+
//
|
|
241
|
+
// Fires after BeforeModel and before tool dispatch with the LLM request
|
|
242
|
+
// payload (containing the candidate tool list). The hook can return
|
|
243
|
+
// `hookSpecificOutput.toolConfig.{ mode, allowedFunctionNames }` to filter
|
|
244
|
+
// the model's tool surface in one shot, collapsing many BeforeTool denials
|
|
245
|
+
// into one upstream check. We currently emit an audit span only — a future
|
|
246
|
+
// enhancement should extract `tools` from `llm_request` and call
|
|
247
|
+
// `client.batchCheckPermissions()` to populate `allowedFunctionNames`.
|
|
248
|
+
async function handleBeforeToolSelection(input, client) {
|
|
249
|
+
client.logger.info("lifecycle.before_tool_selection", {
|
|
250
|
+
sessionId: input.session_id,
|
|
251
|
+
});
|
|
252
|
+
client.tracer.record("permission.batch_check", "ok", {
|
|
253
|
+
attributes: { stage: "before_tool_selection" },
|
|
254
|
+
});
|
|
255
|
+
return {};
|
|
256
|
+
}
|
|
257
|
+
// ─── Notification ──────────────────────────────────────────────────
|
|
258
|
+
//
|
|
259
|
+
// Fires on tool-permission prompts and other UI notifications. Cannot
|
|
260
|
+
// block; observability only.
|
|
261
|
+
async function handleNotification(input, client) {
|
|
262
|
+
client.logger.info("lifecycle.notification", {
|
|
263
|
+
sessionId: input.session_id,
|
|
264
|
+
notificationType: input.notification_type,
|
|
265
|
+
message: input.message,
|
|
266
|
+
});
|
|
267
|
+
client.tracer.record("notification", "ok", {
|
|
268
|
+
attributes: {
|
|
269
|
+
notificationType: input.notification_type,
|
|
270
|
+
message: input.message,
|
|
271
|
+
},
|
|
272
|
+
});
|
|
273
|
+
return {};
|
|
274
|
+
}
|
|
275
|
+
// ─── PreCompress ───────────────────────────────────────────────────
|
|
276
|
+
async function handlePreCompress(input, client) {
|
|
277
|
+
client.logger.info("lifecycle.pre_compress", {
|
|
278
|
+
sessionId: input.session_id,
|
|
279
|
+
trigger: input.trigger,
|
|
280
|
+
});
|
|
281
|
+
client.tracer.record("compaction", "ok", {
|
|
282
|
+
attributes: { trigger: input.trigger, stage: "pre_compress" },
|
|
283
|
+
});
|
|
284
|
+
return {};
|
|
285
|
+
}
|
|
286
|
+
// ─── Helpers ────────────────────────────────────────────────────────
|
|
287
|
+
function resolveNamespace() {
|
|
288
|
+
return process.env.ORY_PERMISSION_NAMESPACE ?? "AgentTools";
|
|
289
|
+
}
|
|
290
|
+
function handlePermissionError(oryErr, toolName, client) {
|
|
291
|
+
if (oryErr.code === "network_error") {
|
|
292
|
+
client.logger.warn("permission.network_error", {
|
|
293
|
+
toolName,
|
|
294
|
+
message: "Ory unreachable, failing open",
|
|
295
|
+
});
|
|
296
|
+
return {};
|
|
297
|
+
}
|
|
298
|
+
if (oryErr.code === "rate_limited") {
|
|
299
|
+
client.logger.warn("permission.rate_limited", {
|
|
300
|
+
toolName,
|
|
301
|
+
message: "Ory rate limited, failing open",
|
|
302
|
+
});
|
|
303
|
+
return {};
|
|
304
|
+
}
|
|
305
|
+
// For other errors, also fail open but log prominently
|
|
306
|
+
client.logger.error("permission.check.error", {
|
|
307
|
+
toolName,
|
|
308
|
+
code: oryErr.code,
|
|
309
|
+
message: oryErr.message,
|
|
310
|
+
});
|
|
311
|
+
return {};
|
|
312
|
+
}
|
package/dist/hook.d.ts
ADDED
package/dist/hook.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* Gemini CLI hook entry point.
|
|
5
|
+
* This script is invoked by Gemini CLI 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
|
+
clearTimeout(idleTimer);
|
|
33
|
+
idleTimer = setTimeout(done, 100);
|
|
34
|
+
});
|
|
35
|
+
process.stdin.on("end", done);
|
|
36
|
+
process.stdin.on("error", (err) => {
|
|
37
|
+
if (!resolved) {
|
|
38
|
+
resolved = true;
|
|
39
|
+
reject(err);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
let idleTimer;
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
async function main() {
|
|
46
|
+
const client = argus_1.OryAgentClient.fromEnv("gemini-cli");
|
|
47
|
+
const raw = await readStdin();
|
|
48
|
+
let input;
|
|
49
|
+
try {
|
|
50
|
+
input = JSON.parse(raw);
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
client.logger.error("hook.stdin.parse_failed", { raw: raw.slice(0, 200) });
|
|
54
|
+
await client.tracer.shutdown();
|
|
55
|
+
process.exit(0); // Don't block on parse errors
|
|
56
|
+
}
|
|
57
|
+
const output = await (0, handlers_js_1.handleHookEvent)(input, client);
|
|
58
|
+
if (output.decision === "deny" || output.decision === "block") {
|
|
59
|
+
process.stdout.write(JSON.stringify(output));
|
|
60
|
+
await client.tracer.shutdown();
|
|
61
|
+
process.exit(2);
|
|
62
|
+
}
|
|
63
|
+
if (output.hookSpecificOutput ||
|
|
64
|
+
output.systemMessage ||
|
|
65
|
+
output.decision === "allow") {
|
|
66
|
+
process.stdout.write(JSON.stringify(output));
|
|
67
|
+
}
|
|
68
|
+
await client.tracer.shutdown();
|
|
69
|
+
process.exit(0);
|
|
70
|
+
}
|
|
71
|
+
main().catch((err) => {
|
|
72
|
+
process.stderr.write(`[ory-agent] fatal: ${err}\n`);
|
|
73
|
+
process.exit(0); // Fail open
|
|
74
|
+
});
|
package/dist/index.d.ts
ADDED
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 .gemini/settings.json hooks section 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;
|
package/dist/settings.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
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.generateSettings = generateSettings;
|
|
37
|
+
exports.generateInstalledSettings = generateInstalledSettings;
|
|
38
|
+
exports.generateDevSettings = generateDevSettings;
|
|
39
|
+
const path = __importStar(require("node:path"));
|
|
40
|
+
const argus_1 = require("@ory/argus");
|
|
41
|
+
/**
|
|
42
|
+
* Generate the .gemini/settings.json hooks section that configures Ory hooks.
|
|
43
|
+
*/
|
|
44
|
+
function generateSettings(hookScriptPath) {
|
|
45
|
+
const cmd = `node ${hookScriptPath}`;
|
|
46
|
+
return {
|
|
47
|
+
hooks: {
|
|
48
|
+
SessionStart: (0, argus_1.matcherHookEntry)(cmd),
|
|
49
|
+
BeforeTool: (0, argus_1.matcherHookEntry)(cmd),
|
|
50
|
+
AfterTool: (0, argus_1.matcherHookEntry)(cmd),
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Generate settings assuming the plugin is installed via npm/pnpm.
|
|
56
|
+
*/
|
|
57
|
+
function generateInstalledSettings() {
|
|
58
|
+
return generateSettings("ory-gemini-hook");
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Generate settings pointing to a local dev build.
|
|
62
|
+
*/
|
|
63
|
+
function generateDevSettings(packageDir) {
|
|
64
|
+
const hookPath = path.resolve(packageDir, "dist", "hook.js");
|
|
65
|
+
return generateSettings(hookPath);
|
|
66
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gemini CLI hook event payload delivered on stdin.
|
|
3
|
+
*
|
|
4
|
+
* Gemini CLI invokes hooks as shell commands, passing JSON on stdin
|
|
5
|
+
* and reading JSON from stdout. Exit code 2 blocks the action.
|
|
6
|
+
*
|
|
7
|
+
* Events: SessionStart, SessionEnd, BeforeAgent, AfterAgent,
|
|
8
|
+
* BeforeModel, BeforeToolSelection, AfterModel, BeforeTool, AfterTool,
|
|
9
|
+
* Notification, PreCompress.
|
|
10
|
+
*
|
|
11
|
+
* See: https://geminicli.com/docs/hooks/reference/
|
|
12
|
+
*/
|
|
13
|
+
export interface GeminiHookInput {
|
|
14
|
+
session_id: string;
|
|
15
|
+
transcript_path: string;
|
|
16
|
+
cwd: string;
|
|
17
|
+
hook_event_name: string;
|
|
18
|
+
timestamp?: string;
|
|
19
|
+
source?: string;
|
|
20
|
+
reason?: string;
|
|
21
|
+
prompt?: string;
|
|
22
|
+
prompt_response?: string;
|
|
23
|
+
stop_hook_active?: boolean;
|
|
24
|
+
tool_name?: string;
|
|
25
|
+
tool_input?: Record<string, unknown>;
|
|
26
|
+
tool_response?: Record<string, unknown>;
|
|
27
|
+
mcp_context?: Record<string, unknown>;
|
|
28
|
+
original_request_name?: string;
|
|
29
|
+
llm_request?: Record<string, unknown>;
|
|
30
|
+
llm_response?: Record<string, unknown>;
|
|
31
|
+
notification_type?: string;
|
|
32
|
+
message?: string;
|
|
33
|
+
details?: Record<string, unknown>;
|
|
34
|
+
trigger?: "auto" | "manual";
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Gemini CLI hook output written to stdout.
|
|
38
|
+
*/
|
|
39
|
+
export interface GeminiHookOutput {
|
|
40
|
+
decision?: "allow" | "deny" | "block";
|
|
41
|
+
reason?: string;
|
|
42
|
+
systemMessage?: string;
|
|
43
|
+
continue?: boolean;
|
|
44
|
+
stopReason?: string;
|
|
45
|
+
suppressOutput?: boolean;
|
|
46
|
+
hookSpecificOutput?: GeminiHookSpecificOutput;
|
|
47
|
+
}
|
|
48
|
+
export interface GeminiHookSpecificOutput {
|
|
49
|
+
/** BeforeTool: override tool arguments */
|
|
50
|
+
tool_input?: Record<string, unknown>;
|
|
51
|
+
/** AfterTool: append to tool result */
|
|
52
|
+
additionalContext?: string;
|
|
53
|
+
/** AfterTool: chain a follow-up tool call */
|
|
54
|
+
tailToolCallRequest?: Record<string, unknown>;
|
|
55
|
+
/** AfterAgent: clear agent context */
|
|
56
|
+
clearContext?: boolean;
|
|
57
|
+
/** BeforeModel: synthesize an llm_response and short-circuit the model call */
|
|
58
|
+
llm_response?: Record<string, unknown>;
|
|
59
|
+
/** BeforeToolSelection: restrict the tools the model can pick */
|
|
60
|
+
toolConfig?: {
|
|
61
|
+
mode?: "AUTO" | "ANY" | "NONE";
|
|
62
|
+
allowedFunctionNames?: string[];
|
|
63
|
+
};
|
|
64
|
+
[key: string]: unknown;
|
|
65
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
This project has the Ory agent plugin installed. Tool invocations are checked against Ory Permissions (Zanzibar-style) and traced automatically.
|
|
2
|
+
|
|
3
|
+
If a tool call is blocked, the reason will indicate which permission is missing. The user may need to update their Ory Permissions configuration to grant access.
|
|
4
|
+
|
|
5
|
+
To check configuration status, the user can run: `npx ory-gemini status`
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ory",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Ory authentication, authorization, and distributed tracing for Gemini CLI",
|
|
5
|
+
"contextFileName": "GEMINI.md",
|
|
6
|
+
"settings": [
|
|
7
|
+
{
|
|
8
|
+
"name": "Ory Project URL",
|
|
9
|
+
"description": "Your Ory project URL (e.g. https://your-project.projects.oryapis.com)",
|
|
10
|
+
"envVar": "ORY_PROJECT_URL",
|
|
11
|
+
"sensitive": false
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"name": "Ory API Key",
|
|
15
|
+
"description": "Your Ory API key (starts with ory_pat_)",
|
|
16
|
+
"envVar": "ORY_API_KEY",
|
|
17
|
+
"sensitive": true
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"name": "Session Token",
|
|
21
|
+
"description": "Session token for agent authentication",
|
|
22
|
+
"envVar": "ORY_SESSION_TOKEN",
|
|
23
|
+
"sensitive": true
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
}
|