@ory/gemini-cli 0.1.3 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -116,6 +116,8 @@ The extension is **fail-open** on its own infrastructure failures (network error
116
116
 
117
117
  ### Enable enforcement
118
118
 
119
+ After install the extension runs in **observe mode**: every tool call is checked against Ory Permissions, but a deny is recorded as a `permission.observe_deny` audit span and the tool runs anyway. This lets you see what *would* be blocked before turning on hard blocking.
120
+
119
121
  1. **Turn on the user gate.** In your shell:
120
122
 
121
123
  ```bash
@@ -124,19 +126,29 @@ The extension is **fail-open** on its own infrastructure failures (network error
124
126
 
125
127
  The next Gemini session opens a browser for PKCE login. Subsequent sessions reuse the persisted token until it expires.
126
128
 
127
- 2. **Grant yourself permission to use a tool.** Use the Ory MCP server from inside Gemini (*"grant me invoke on the Bash tool"*) or the CLI directly:
129
+ 2. **Bootstrap tuples for the built-in tools.** One idempotent command grants the current user `use` on every tool Gemini ships with (read_file, write_file, shell, …):
130
+
131
+ ```bash
132
+ npx -y -p @ory/gemini-cli ory-gemini permissions bootstrap
133
+ ```
134
+
135
+ If a user identity is already cached at install time, the installer runs this for you automatically — re-run after adding tools, switching subjects, or changing the namespace.
136
+
137
+ 3. **Check coverage.** `permissions status` probes every tool in the harness's catalog and prints allowed / denied per tool:
128
138
 
129
139
  ```bash
130
- ory create relationship \
131
- --namespace AgentTools \
132
- --object Bash \
133
- --relation invoke \
134
- --subject-id <your-user-subject-id>
140
+ npx -y -p @ory/gemini-cli ory-gemini permissions status
135
141
  ```
136
142
 
137
- Your subject id is printed at the start of every Gemini session when `ORY_AGENT_DEBUG=true`.
143
+ Add tuples for any MCP server tools or custom commands by hand, or via the Ory MCP server from inside Gemini (*"grant me use on the shell tool"*).
144
+
145
+ 4. **Promote to enforce.** Once the observe-mode logs look right, switch over:
146
+
147
+ ```bash
148
+ npx -y -p @ory/gemini-cli ory-gemini permissions enforce
149
+ ```
138
150
 
139
- 3. **See a denial.** Pick a tool you didn't grant (or remove the tuple) and ask Gemini to use it. The hook blocks the call and Gemini shows the denial reason. The decision is recorded as a `tool.block` trace span.
151
+ Denies now block the tool call; Gemini shows the denial reason and the decision is recorded as a `tool.block` trace span with `blocked: true`. Switch back any time with `permissions observe`.
140
152
 
141
153
  ## CLI reference
142
154
 
@@ -145,6 +157,7 @@ npx -y -p @ory/gemini-cli ory-gemini install [--link
145
157
  npx -y -p @ory/gemini-cli ory-gemini uninstall
146
158
  npx -y -p @ory/gemini-cli ory-gemini configure [--project-url <url>] [--api-key <key>] [--audit-only]
147
159
  npx -y -p @ory/gemini-cli ory-gemini agent <status|unregister> Manage the agent's OAuth2 identity
160
+ npx -y -p @ory/gemini-cli ory-gemini permissions <status|bootstrap|observe|enforce>
148
161
  npx -y -p @ory/gemini-cli ory-gemini local <up|down|status|seed|logs|env|configure|reset>
149
162
  npx -y -p @ory/gemini-cli ory-gemini status
150
163
  ```
@@ -152,7 +165,8 @@ npx -y -p @ory/gemini-cli ory-gemini status
152
165
  Highlights:
153
166
 
154
167
  - `agent status` — show the current persisted DCR identity for the agent.
155
- - `configure --audit-only` — record decisions without blocking; useful for a phased rollout.
168
+ - `permissions observe` / `permissions enforce` switch between "log denies, allow through" (the install default) and "block denies." `permissions bootstrap` writes `use` tuples for the harness's built-in tools so the promotion path doesn't require hand-writing relationships.
169
+ - `configure --audit-only` — kill switch that disables Ory entirely (no auth, no permission checks; only audit logging of tool invocations). For phased rollouts, prefer `permissions observe` over `--audit-only`.
156
170
  - `local seed` / `local env` — reseed the test user, or print env vars for pointing other tools at the local stack.
157
171
 
158
172
  ## Troubleshooting
package/dist/cli/main.js CHANGED
@@ -56,6 +56,10 @@ function main() {
56
56
  switch (command) {
57
57
  case "install":
58
58
  install(args);
59
+ postInstallPermissions("ory-gemini", "gemini-cli").then(() => process.exit(0), (err) => {
60
+ console.error(err.message ?? err);
61
+ process.exit(1);
62
+ });
59
63
  break;
60
64
  case "uninstall":
61
65
  uninstall(args);
@@ -69,6 +73,12 @@ function main() {
69
73
  process.exit(1);
70
74
  });
71
75
  break;
76
+ case "permissions":
77
+ (0, argus_1.runPermissionsCommand)("ory-gemini", "gemini-cli", args).then((code) => process.exit(code), (err) => {
78
+ console.error(err.message ?? err);
79
+ process.exit(1);
80
+ });
81
+ break;
72
82
  case "setup":
73
83
  require("./setup.js");
74
84
  break;
@@ -155,6 +165,12 @@ function manualSetup(args) {
155
165
  process.argv = ["node", "setup.js", ...args];
156
166
  require("./setup.js");
157
167
  }
168
+ async function postInstallPermissions(binName, harness) {
169
+ const bootstrapped = await (0, argus_1.maybeAutoBootstrap)(binName, harness);
170
+ (0, argus_1.printPermissionsOnboardingHelp)(binName, harness, {
171
+ bootstrappedAutomatically: bootstrapped,
172
+ });
173
+ }
158
174
  function status() {
159
175
  console.log("Ory Agent Plugin Status (Gemini CLI)");
160
176
  console.log("====================================");
@@ -183,6 +199,7 @@ Commands:
183
199
  install [--link] Install the Ory extension into Gemini CLI
184
200
  uninstall Remove the Ory extension from Gemini CLI
185
201
  configure Set or view Ory project URL and API key
202
+ permissions <cmd> Manage permission mode and tool permissions (status, bootstrap, observe, enforce)
186
203
  setup [--project-dir] Write hooks directly to settings.json (fallback)
187
204
  status Show plugin status and configuration
188
205
  local <cmd> Manage local Ory dev environment (up, down, status, seed, ...)
package/dist/handlers.js CHANGED
@@ -167,42 +167,67 @@ async function handleBeforeTool(input, client) {
167
167
  subject,
168
168
  spanAttributes: { toolName },
169
169
  });
170
- if (!mcpResult.allowed) {
170
+ const mcpAttrs = {
171
+ toolName,
172
+ mcpServer: mcpTool.serverName,
173
+ mcpTool: mcpTool.toolName,
174
+ ...inputSummary,
175
+ };
176
+ const decision = (0, argus_1.applyPermissionMode)(client, mcpResult.allowed, {
177
+ object: mcpTool.serverName,
178
+ relation: "use",
179
+ subjectId,
180
+ spanAttributes: mcpAttrs,
181
+ });
182
+ if (decision.kind === "allow") {
183
+ client.tracer.record("tool.invoke", "ok", { attributes: mcpAttrs });
184
+ return {};
185
+ }
186
+ if (decision.kind === "observe") {
171
187
  client.tracer.record("tool.block", "denied", {
172
- attributes: { toolName, mcpServer: mcpTool.serverName, mcpTool: mcpTool.toolName, ...inputSummary, ...(0, argus_1.alertAttributes)(true) },
188
+ attributes: { ...mcpAttrs, allowed: false, ...(0, argus_1.alertAttributes)(false) },
189
+ });
190
+ client.tracer.record("tool.invoke", "ok", {
191
+ attributes: { ...mcpAttrs, allowed: false, observed: true },
173
192
  });
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
- };
193
+ return {};
179
194
  }
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
195
  client.tracer.record("tool.block", "denied", {
194
- attributes: { toolName, ...inputSummary, allowed: result.allowed, ...(0, argus_1.alertAttributes)(true) },
196
+ attributes: { ...mcpAttrs, allowed: false, ...(0, argus_1.alertAttributes)(true) },
195
197
  });
196
198
  return {
197
199
  decision: "deny",
198
- reason: (0, argus_1.formatDenialMessage)({ tool: toolName, subjectId, namespace }),
199
- systemMessage: (0, argus_1.formatDenialSummary)({ tool: toolName, subjectId, namespace }),
200
+ reason: (0, argus_1.formatDenialMessage)({ tool: toolName, subjectId, mcp: mcpTool }),
201
+ systemMessage: (0, argus_1.formatDenialSummary)({ tool: toolName, subjectId, mcp: mcpTool }),
200
202
  };
201
203
  }
202
- client.tracer.record("tool.invoke", "ok", {
203
- attributes: { toolName, ...inputSummary, allowed: result.allowed },
204
+ const namespace = resolveNamespace();
205
+ const decision = await (0, argus_1.checkAndDecide)(client, { namespace, object: toolName, relation: "use", ...subject }, { spanAttributes: { toolName } });
206
+ if (decision.kind === "fail_open") {
207
+ return handlePermissionError(decision.error, toolName, client);
208
+ }
209
+ const attrs = { toolName, ...inputSummary };
210
+ if (decision.kind === "allow") {
211
+ client.tracer.record("tool.invoke", "ok", { attributes: { ...attrs, allowed: true } });
212
+ return {};
213
+ }
214
+ if (decision.kind === "observe") {
215
+ client.tracer.record("tool.block", "denied", {
216
+ attributes: { ...attrs, allowed: false, ...(0, argus_1.alertAttributes)(false) },
217
+ });
218
+ client.tracer.record("tool.invoke", "ok", {
219
+ attributes: { ...attrs, allowed: false, observed: true },
220
+ });
221
+ return {};
222
+ }
223
+ client.tracer.record("tool.block", "denied", {
224
+ attributes: { ...attrs, allowed: false, ...(0, argus_1.alertAttributes)(true) },
204
225
  });
205
- return {};
226
+ return {
227
+ decision: "deny",
228
+ reason: (0, argus_1.formatDenialMessage)({ tool: toolName, subjectId, namespace }),
229
+ systemMessage: (0, argus_1.formatDenialSummary)({ tool: toolName, subjectId, namespace }),
230
+ };
206
231
  }
207
232
  catch (err) {
208
233
  return handlePermissionError(err, toolName, client);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ory/gemini-cli",
3
- "version": "0.1.3",
3
+ "version": "0.2.1",
4
4
  "description": "Ory extension for Gemini CLI: scaffolding skills, a local Ory instance, and authentication, authorization, and audit for every tool call",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://ory.com",
@@ -69,7 +69,7 @@
69
69
  "gemini-extension"
70
70
  ],
71
71
  "dependencies": {
72
- "@ory/argus": "0.1.3"
72
+ "@ory/argus": "0.2.1"
73
73
  },
74
74
  "engines": {
75
75
  "node": ">=24"