@ory/claude-code 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 +23 -9
- package/dist/cli/main.js +26 -3
- package/dist/cli/setup.d.ts +21 -12
- package/dist/cli/setup.js +137 -54
- package/dist/handlers.js +75 -36
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -120,6 +120,8 @@ The plugin is **fail-open** on its own infrastructure failures (network errors,
|
|
|
120
120
|
|
|
121
121
|
### Enable enforcement
|
|
122
122
|
|
|
123
|
+
After install the plugin 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.
|
|
124
|
+
|
|
123
125
|
1. **Turn on the user gate.** In your shell:
|
|
124
126
|
|
|
125
127
|
```bash
|
|
@@ -128,19 +130,29 @@ The plugin is **fail-open** on its own infrastructure failures (network errors,
|
|
|
128
130
|
|
|
129
131
|
The next Claude session opens a browser for PKCE login. Subsequent sessions reuse the persisted token until it expires.
|
|
130
132
|
|
|
131
|
-
2. **
|
|
133
|
+
2. **Bootstrap tuples for the built-in tools.** One idempotent command grants the current user `use` on every tool Claude ships with (Read, Write, Bash, …):
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
npx -y -p @ory/claude-code ory-claude permissions bootstrap
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
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.
|
|
140
|
+
|
|
141
|
+
3. **Check coverage.** `permissions status` probes every tool in the harness's catalog and prints allowed / denied per tool:
|
|
132
142
|
|
|
133
143
|
```bash
|
|
134
|
-
ory
|
|
135
|
-
--namespace AgentTools \
|
|
136
|
-
--object Bash \
|
|
137
|
-
--relation invoke \
|
|
138
|
-
--subject-id <your-user-subject-id>
|
|
144
|
+
npx -y -p @ory/claude-code ory-claude permissions status
|
|
139
145
|
```
|
|
140
146
|
|
|
141
|
-
|
|
147
|
+
Add tuples for any MCP server tools or custom commands by hand, or via the Ory MCP server from inside Claude (*"grant me use on the Bash tool"*).
|
|
148
|
+
|
|
149
|
+
4. **Promote to enforce.** Once the observe-mode logs look right, switch over:
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
npx -y -p @ory/claude-code ory-claude permissions enforce
|
|
153
|
+
```
|
|
142
154
|
|
|
143
|
-
|
|
155
|
+
Denies now block the tool call; Claude 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`.
|
|
144
156
|
|
|
145
157
|
## CLI reference
|
|
146
158
|
|
|
@@ -148,6 +160,7 @@ The plugin is **fail-open** on its own infrastructure failures (network errors,
|
|
|
148
160
|
npx -y -p @ory/claude-code ory-claude install | uninstall [--global]
|
|
149
161
|
npx -y -p @ory/claude-code ory-claude configure [--project-url <url>] [--api-key <key>] [--audit-only]
|
|
150
162
|
npx -y -p @ory/claude-code ory-claude agent <status|unregister> Manage the agent's OAuth2 identity
|
|
163
|
+
npx -y -p @ory/claude-code ory-claude permissions <status|bootstrap|observe|enforce>
|
|
151
164
|
npx -y -p @ory/claude-code ory-claude local <up|down|status|seed|logs|env|configure|reset>
|
|
152
165
|
npx -y -p @ory/claude-code ory-claude status
|
|
153
166
|
```
|
|
@@ -155,7 +168,8 @@ npx -y -p @ory/claude-code ory-claude status
|
|
|
155
168
|
Highlights:
|
|
156
169
|
|
|
157
170
|
- `agent status` — show the current persisted DCR identity for the agent.
|
|
158
|
-
- `
|
|
171
|
+
- `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.
|
|
172
|
+
- `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`.
|
|
159
173
|
- `local seed` / `local env` — reseed the test user, or print env vars for pointing other tools at the local stack.
|
|
160
174
|
|
|
161
175
|
## Troubleshooting
|
package/dist/cli/main.js
CHANGED
|
@@ -55,6 +55,10 @@ function main() {
|
|
|
55
55
|
switch (command) {
|
|
56
56
|
case "install":
|
|
57
57
|
(0, setup_js_1.install)(args);
|
|
58
|
+
postInstallPermissions("ory-claude", "claude-code").then(() => process.exit(0), (err) => {
|
|
59
|
+
console.error(err.message ?? err);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
});
|
|
58
62
|
break;
|
|
59
63
|
case "uninstall":
|
|
60
64
|
(0, setup_js_1.uninstall)(args);
|
|
@@ -68,6 +72,12 @@ function main() {
|
|
|
68
72
|
process.exit(1);
|
|
69
73
|
});
|
|
70
74
|
break;
|
|
75
|
+
case "permissions":
|
|
76
|
+
(0, argus_1.runPermissionsCommand)("ory-claude", "claude-code", args).then((code) => process.exit(code), (err) => {
|
|
77
|
+
console.error(err.message ?? err);
|
|
78
|
+
process.exit(1);
|
|
79
|
+
});
|
|
80
|
+
break;
|
|
71
81
|
case "status":
|
|
72
82
|
status();
|
|
73
83
|
break;
|
|
@@ -89,18 +99,30 @@ function main() {
|
|
|
89
99
|
process.exit(1);
|
|
90
100
|
}
|
|
91
101
|
}
|
|
102
|
+
async function postInstallPermissions(binName, harness) {
|
|
103
|
+
const bootstrapped = await (0, argus_1.maybeAutoBootstrap)(binName, harness);
|
|
104
|
+
(0, argus_1.printPermissionsOnboardingHelp)(binName, harness, {
|
|
105
|
+
bootstrappedAutomatically: bootstrapped,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
92
108
|
function status() {
|
|
93
109
|
console.log("Ory Agent Plugin Status (Claude Code)");
|
|
94
110
|
console.log("======================================");
|
|
95
111
|
console.log("");
|
|
96
112
|
(0, argus_1.printOryConfig)();
|
|
97
113
|
(0, argus_1.printEnvironment)();
|
|
98
|
-
// Plugin-specific status
|
|
114
|
+
// Plugin-specific status. The default install path delegates plugin
|
|
115
|
+
// assembly to the public `ory/claude-plugins` marketplace, so the persistent
|
|
116
|
+
// dir below is only populated for `--from-source` installs (the dev launcher
|
|
117
|
+
// and local iteration). Run `claude plugin list` for an authoritative view
|
|
118
|
+
// of what the `claude` CLI has registered.
|
|
99
119
|
console.log("");
|
|
100
120
|
console.log("Plugin:");
|
|
101
121
|
const assembled = fs.existsSync(PERSISTENT_DIR);
|
|
102
|
-
console.log(`
|
|
103
|
-
|
|
122
|
+
console.log(` Local --from-source dir: ${assembled ? PERSISTENT_DIR : "(none — production installs use the ory/claude-plugins marketplace; run 'claude plugin list' to see registered plugins)"}`);
|
|
123
|
+
if (assembled) {
|
|
124
|
+
console.log(` Skills: ${fs.existsSync(path.join(PERSISTENT_DIR, "skills")) ? "installed" : "not installed"}`);
|
|
125
|
+
}
|
|
104
126
|
console.log(` Hook script: ${fs.existsSync(path.join(PACKAGE_ROOT, "dist", "hook.js")) ? "built" : "NOT BUILT (run pnpm build)"}`);
|
|
105
127
|
(0, argus_1.printLogTail)();
|
|
106
128
|
}
|
|
@@ -116,6 +138,7 @@ Commands:
|
|
|
116
138
|
install [--global] Install Ory plugin via claude CLI marketplace
|
|
117
139
|
uninstall [--global] Remove Ory plugin and marketplace
|
|
118
140
|
configure Set or view Ory project URL and API key
|
|
141
|
+
permissions <cmd> Manage permission mode and tool permissions (status, bootstrap, observe, enforce)
|
|
119
142
|
status Show plugin status and configuration
|
|
120
143
|
local <cmd> Manage local Ory dev environment (up, down, status, seed, ...)
|
|
121
144
|
|
package/dist/cli/setup.d.ts
CHANGED
|
@@ -3,26 +3,35 @@
|
|
|
3
3
|
* Setup CLI for the Ory Claude Code plugin.
|
|
4
4
|
*
|
|
5
5
|
* Uses the `claude` CLI to manage plugin installation via the marketplace:
|
|
6
|
-
* npx ory-claude-setup
|
|
7
|
-
* npx ory-claude-setup --global
|
|
8
|
-
* npx ory-claude-setup --
|
|
6
|
+
* npx ory-claude-setup # Install plugin (project scope)
|
|
7
|
+
* npx ory-claude-setup --global # Install plugin (user scope)
|
|
8
|
+
* npx ory-claude-setup --from-source # Install from the local source tree (dev only)
|
|
9
|
+
* npx ory-claude-setup --uninstall # Remove plugin
|
|
9
10
|
*/
|
|
10
11
|
/**
|
|
11
12
|
* Install the Ory plugin via the `claude` CLI.
|
|
12
13
|
*
|
|
13
|
-
*
|
|
14
|
-
* 2. Adds the Ory marketplace (pointing to that directory)
|
|
15
|
-
* 3. Installs the plugin from that marketplace
|
|
14
|
+
* Two paths exist:
|
|
16
15
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
16
|
+
* 1. Default (production) — point the `claude` CLI at the public
|
|
17
|
+
* `ory/claude-plugins` GitHub marketplace and install from there. The
|
|
18
|
+
* release workflow keeps that repo's plugin subtree pinned to the latest
|
|
19
|
+
* `@ory/claude-code` version, so the hook commands, skills, commands,
|
|
20
|
+
* and MCP config all come from the published artifact. The install CLI
|
|
21
|
+
* writes nothing to the persistent data dir on this path.
|
|
22
|
+
*
|
|
23
|
+
* 2. `--from-source` (dev only) — assemble the plugin directory from this
|
|
24
|
+
* checkout's `.claude-plugin/`, register it as a local-file marketplace,
|
|
25
|
+
* and install from there. The dev launcher uses this so changes in the
|
|
26
|
+
* monorepo are exercised end-to-end without going through the release
|
|
27
|
+
* mirror. End users should never need this flag.
|
|
19
28
|
*/
|
|
20
29
|
export declare function install(args: string[]): void;
|
|
21
30
|
/**
|
|
22
|
-
* Uninstall the Ory plugin via the `claude` CLI.
|
|
31
|
+
* Uninstall the Ory plugin via the `claude` CLI. Mirrors `install`:
|
|
23
32
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
33
|
+
* - Default: uninstall `ory-agent-plugin`, remove the production marketplace.
|
|
34
|
+
* - `--from-source`: uninstall the locally-assembled plugin, remove its
|
|
35
|
+
* marketplace entry, and clean up the assembled directory.
|
|
27
36
|
*/
|
|
28
37
|
export declare function uninstall(args: string[]): void;
|
package/dist/cli/setup.js
CHANGED
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
* Setup CLI for the Ory Claude Code plugin.
|
|
5
5
|
*
|
|
6
6
|
* Uses the `claude` CLI to manage plugin installation via the marketplace:
|
|
7
|
-
* npx ory-claude-setup
|
|
8
|
-
* npx ory-claude-setup --global
|
|
9
|
-
* npx ory-claude-setup --
|
|
7
|
+
* npx ory-claude-setup # Install plugin (project scope)
|
|
8
|
+
* npx ory-claude-setup --global # Install plugin (user scope)
|
|
9
|
+
* npx ory-claude-setup --from-source # Install from the local source tree (dev only)
|
|
10
|
+
* npx ory-claude-setup --uninstall # Remove plugin
|
|
10
11
|
*/
|
|
11
12
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
12
13
|
if (k2 === undefined) k2 = k;
|
|
@@ -50,25 +51,35 @@ const path = __importStar(require("node:path"));
|
|
|
50
51
|
const argus_1 = require("@ory/argus");
|
|
51
52
|
const PACKAGE_ROOT = path.resolve(__dirname, "..", "..");
|
|
52
53
|
/**
|
|
53
|
-
*
|
|
54
|
-
*
|
|
54
|
+
* Production marketplace published by the release workflow as the read-only
|
|
55
|
+
* mirror of this monorepo's Claude Code plugin. The `name` field below is the
|
|
56
|
+
* short identifier declared by that repo's top-level
|
|
57
|
+
* `.claude-plugin/marketplace.json` — that's what the `claude` CLI registers
|
|
58
|
+
* the marketplace under, and what `claude plugin install <plugin>@<name>`
|
|
59
|
+
* dereferences. The plugin name matches the local `.claude-plugin/plugin.json`
|
|
60
|
+
* because the sync script preserves it.
|
|
55
61
|
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
62
|
+
* Keep these in sync with whatever the production marketplace declares — if
|
|
63
|
+
* the repo's marketplace.json `name` changes, update `PROD_MARKETPLACE_NAME`
|
|
64
|
+
* here too.
|
|
65
|
+
*/
|
|
66
|
+
const PROD_MARKETPLACE_REPO = "ory/claude-plugins";
|
|
67
|
+
const PROD_MARKETPLACE_NAME = "ory";
|
|
68
|
+
const PROD_PLUGIN_NAME = "ory-agent-plugin";
|
|
69
|
+
/**
|
|
70
|
+
* For `--from-source` installs, resolve the marketplace and plugin names from
|
|
71
|
+
* the local `.claude-plugin/` JSON files instead of hardcoding them.
|
|
63
72
|
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
73
|
+
* Source of truth is `packages/claude-code/.claude-plugin/marketplace.json`
|
|
74
|
+
* (marketplace name) and `.claude-plugin/plugin.json` (plugin name). The local
|
|
75
|
+
* marketplace uses intentionally dev-flavored names — e.g.
|
|
76
|
+
* `ory-plugins-local-development` — so a developer can see at a glance the
|
|
77
|
+
* install came from this monorepo and not the production
|
|
78
|
+
* `ory/claude-plugins` repo. The default install path (no `--from-source`)
|
|
79
|
+
* bypasses these entirely and points the `claude` CLI at the production
|
|
80
|
+
* GitHub marketplace.
|
|
70
81
|
*/
|
|
71
|
-
function
|
|
82
|
+
function readLocalMarketplaceName() {
|
|
72
83
|
const mfPath = path.join(PACKAGE_ROOT, ".claude-plugin", "marketplace.json");
|
|
73
84
|
const data = JSON.parse(fs.readFileSync(mfPath, "utf-8"));
|
|
74
85
|
if (!data.name) {
|
|
@@ -76,7 +87,7 @@ function readMarketplaceName() {
|
|
|
76
87
|
}
|
|
77
88
|
return data.name;
|
|
78
89
|
}
|
|
79
|
-
function
|
|
90
|
+
function readLocalPluginName() {
|
|
80
91
|
const pjPath = path.join(PACKAGE_ROOT, ".claude-plugin", "plugin.json");
|
|
81
92
|
const data = JSON.parse(fs.readFileSync(pjPath, "utf-8"));
|
|
82
93
|
if (!data.name) {
|
|
@@ -89,18 +100,19 @@ const RENDER_OPTS = {
|
|
|
89
100
|
packageName: "@ory/claude-code",
|
|
90
101
|
};
|
|
91
102
|
/**
|
|
92
|
-
* Persistent install location used as the marketplace root
|
|
93
|
-
* shared OS-agnostic data dir so all plugin state —
|
|
94
|
-
* assets — sits in one place per platform. The
|
|
95
|
-
*
|
|
96
|
-
*
|
|
103
|
+
* Persistent install location used as the marketplace root for `--from-source`
|
|
104
|
+
* installs. Lives under the shared OS-agnostic data dir so all plugin state —
|
|
105
|
+
* config, DCR creds, harness assets — sits in one place per platform. The
|
|
106
|
+
* production install path never touches this directory: the `claude` CLI
|
|
107
|
+
* fetches everything directly from the GitHub marketplace.
|
|
97
108
|
*/
|
|
98
109
|
const PERSISTENT_DIR = (0, argus_1.getHarnessDataDir)("claude-code");
|
|
99
110
|
/** Hook command that resolves the binary via the npm registry. */
|
|
100
111
|
const HOOK_CMD_REMOTE = "npx -y -p @ory/claude-code ory-claude-hook";
|
|
101
112
|
/**
|
|
102
113
|
* Detect if we're running from a temporary npx/pnpm-dlx cache directory
|
|
103
|
-
* rather than a proper local or global npm installation.
|
|
114
|
+
* rather than a proper local or global npm installation. Only consulted on
|
|
115
|
+
* the `--from-source` path to pick the right hook command.
|
|
104
116
|
*/
|
|
105
117
|
function isRemoteContext() {
|
|
106
118
|
const normalized = PACKAGE_ROOT.replace(/\\/g, "/");
|
|
@@ -112,7 +124,7 @@ function localHookCommand() {
|
|
|
112
124
|
}
|
|
113
125
|
/**
|
|
114
126
|
* Assemble the Claude Code plugin directory in the persistent location and
|
|
115
|
-
* return its path (used as the marketplace root).
|
|
127
|
+
* return its path (used as the marketplace root for `--from-source`).
|
|
116
128
|
*
|
|
117
129
|
* The plugin manifest (`.claude-plugin/`) and MCP config (`.mcp.json`) are
|
|
118
130
|
* copied from the package; the skills, commands, and hooks are generated:
|
|
@@ -196,30 +208,100 @@ function checkClaudeCli() {
|
|
|
196
208
|
/**
|
|
197
209
|
* Install the Ory plugin via the `claude` CLI.
|
|
198
210
|
*
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
*
|
|
211
|
+
* Two paths exist:
|
|
212
|
+
*
|
|
213
|
+
* 1. Default (production) — point the `claude` CLI at the public
|
|
214
|
+
* `ory/claude-plugins` GitHub marketplace and install from there. The
|
|
215
|
+
* release workflow keeps that repo's plugin subtree pinned to the latest
|
|
216
|
+
* `@ory/claude-code` version, so the hook commands, skills, commands,
|
|
217
|
+
* and MCP config all come from the published artifact. The install CLI
|
|
218
|
+
* writes nothing to the persistent data dir on this path.
|
|
202
219
|
*
|
|
203
|
-
*
|
|
204
|
-
*
|
|
220
|
+
* 2. `--from-source` (dev only) — assemble the plugin directory from this
|
|
221
|
+
* checkout's `.claude-plugin/`, register it as a local-file marketplace,
|
|
222
|
+
* and install from there. The dev launcher uses this so changes in the
|
|
223
|
+
* monorepo are exercised end-to-end without going through the release
|
|
224
|
+
* mirror. End users should never need this flag.
|
|
205
225
|
*/
|
|
206
226
|
function install(args) {
|
|
207
227
|
const isGlobal = args.includes("--global");
|
|
228
|
+
const fromSource = args.includes("--from-source");
|
|
208
229
|
const scope = claudeScope(isGlobal);
|
|
209
230
|
if (!checkClaudeCli()) {
|
|
210
231
|
console.error("Error: 'claude' CLI not found in PATH.");
|
|
211
232
|
console.error("Install Claude Code first: https://docs.anthropic.com/en/docs/claude-code");
|
|
212
233
|
process.exit(1);
|
|
213
234
|
}
|
|
235
|
+
if (fromSource) {
|
|
236
|
+
installFromSource(scope);
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
installFromProductionMarketplace(scope);
|
|
240
|
+
}
|
|
241
|
+
(0, argus_1.printNextSteps)("Claude Code", fromSource
|
|
242
|
+
? "npx ory-claude uninstall --from-source"
|
|
243
|
+
: isRemoteContext()
|
|
244
|
+
? "npx @ory/claude-code uninstall"
|
|
245
|
+
: "npx ory-claude uninstall");
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Default production install path: register the `ory/claude-plugins` GitHub
|
|
249
|
+
* marketplace and install `ory-agent-plugin` from it. Nothing is assembled
|
|
250
|
+
* locally — the marketplace and the plugin tree both live in the GitHub repo,
|
|
251
|
+
* and the `claude` CLI fetches them on demand.
|
|
252
|
+
*/
|
|
253
|
+
function installFromProductionMarketplace(scope) {
|
|
254
|
+
console.log(`Registering Ory plugin marketplace from ${PROD_MARKETPLACE_REPO}...`);
|
|
255
|
+
const addResult = runClaude([
|
|
256
|
+
"plugin",
|
|
257
|
+
"marketplace",
|
|
258
|
+
"add",
|
|
259
|
+
PROD_MARKETPLACE_REPO,
|
|
260
|
+
"--scope",
|
|
261
|
+
scope,
|
|
262
|
+
]);
|
|
263
|
+
if (!addResult.ok) {
|
|
264
|
+
if (addResult.output.toLowerCase().includes("already")) {
|
|
265
|
+
console.log(" Marketplace already registered, updating...");
|
|
266
|
+
runClaude(["plugin", "marketplace", "update", PROD_MARKETPLACE_NAME]);
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
console.error(`Failed to add marketplace: ${addResult.output}`);
|
|
270
|
+
process.exit(1);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
console.log(" Marketplace added.");
|
|
275
|
+
}
|
|
276
|
+
console.log(`Installing ${PROD_PLUGIN_NAME}@${PROD_MARKETPLACE_NAME} plugin...`);
|
|
277
|
+
const installResult = runClaude([
|
|
278
|
+
"plugin",
|
|
279
|
+
"install",
|
|
280
|
+
`${PROD_PLUGIN_NAME}@${PROD_MARKETPLACE_NAME}`,
|
|
281
|
+
"--scope",
|
|
282
|
+
scope,
|
|
283
|
+
]);
|
|
284
|
+
if (!installResult.ok) {
|
|
285
|
+
console.error(`Failed to install plugin: ${installResult.output}`);
|
|
286
|
+
process.exit(1);
|
|
287
|
+
}
|
|
288
|
+
console.log(" Plugin installed.");
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Dev-only install path: assemble the plugin from this checkout and register
|
|
292
|
+
* it as a local-file marketplace. Used by the dev launcher (which publishes
|
|
293
|
+
* the monorepo packages to a local Verdaccio registry and then calls this CLI
|
|
294
|
+
* with `--from-source`) and by anyone hand-iterating on plugin code locally.
|
|
295
|
+
*/
|
|
296
|
+
function installFromSource(scope) {
|
|
214
297
|
const remote = isRemoteContext();
|
|
215
298
|
console.log("Assembling Ory plugin (skills, commands, hooks, MCP) at:");
|
|
216
299
|
console.log(` ${PERSISTENT_DIR}`);
|
|
217
300
|
const pluginRoot = assemblePluginDir(remote ? HOOK_CMD_REMOTE : localHookCommand());
|
|
218
|
-
const marketplaceName =
|
|
219
|
-
const pluginName =
|
|
301
|
+
const marketplaceName = readLocalMarketplaceName();
|
|
302
|
+
const pluginName = readLocalPluginName();
|
|
220
303
|
console.log(`Source: ${pluginName}@${marketplaceName} (from ${pluginRoot})`);
|
|
221
|
-
|
|
222
|
-
console.log("Adding Ory plugin marketplace...");
|
|
304
|
+
console.log("Adding local Ory plugin marketplace...");
|
|
223
305
|
const addResult = runClaude([
|
|
224
306
|
"plugin",
|
|
225
307
|
"marketplace",
|
|
@@ -241,7 +323,6 @@ function install(args) {
|
|
|
241
323
|
else {
|
|
242
324
|
console.log(" Marketplace added.");
|
|
243
325
|
}
|
|
244
|
-
// Step 2: Install the plugin from the marketplace
|
|
245
326
|
console.log(`Installing ${pluginName} plugin...`);
|
|
246
327
|
const installResult = runClaude([
|
|
247
328
|
"plugin",
|
|
@@ -255,25 +336,26 @@ function install(args) {
|
|
|
255
336
|
process.exit(1);
|
|
256
337
|
}
|
|
257
338
|
console.log(" Plugin installed.");
|
|
258
|
-
(0, argus_1.printNextSteps)("Claude Code", remote ? "npx @ory/claude-code uninstall" : "npx ory-claude uninstall");
|
|
259
339
|
}
|
|
260
340
|
/**
|
|
261
|
-
* Uninstall the Ory plugin via the `claude` CLI.
|
|
341
|
+
* Uninstall the Ory plugin via the `claude` CLI. Mirrors `install`:
|
|
262
342
|
*
|
|
263
|
-
*
|
|
264
|
-
*
|
|
265
|
-
*
|
|
343
|
+
* - Default: uninstall `ory-agent-plugin`, remove the production marketplace.
|
|
344
|
+
* - `--from-source`: uninstall the locally-assembled plugin, remove its
|
|
345
|
+
* marketplace entry, and clean up the assembled directory.
|
|
266
346
|
*/
|
|
267
347
|
function uninstall(args) {
|
|
268
348
|
const isGlobal = args.includes("--global");
|
|
349
|
+
const fromSource = args.includes("--from-source");
|
|
269
350
|
const scope = claudeScope(isGlobal);
|
|
270
351
|
if (!checkClaudeCli()) {
|
|
271
352
|
console.error("Error: 'claude' CLI not found in PATH.");
|
|
272
353
|
process.exit(1);
|
|
273
354
|
}
|
|
274
|
-
const
|
|
275
|
-
const
|
|
276
|
-
|
|
355
|
+
const pluginName = fromSource ? readLocalPluginName() : PROD_PLUGIN_NAME;
|
|
356
|
+
const marketplaceName = fromSource
|
|
357
|
+
? readLocalMarketplaceName()
|
|
358
|
+
: PROD_MARKETPLACE_NAME;
|
|
277
359
|
console.log(`Uninstalling ${pluginName} plugin...`);
|
|
278
360
|
const uninstallResult = runClaude([
|
|
279
361
|
"plugin",
|
|
@@ -288,7 +370,6 @@ function uninstall(args) {
|
|
|
288
370
|
else {
|
|
289
371
|
console.log(" Plugin uninstalled.");
|
|
290
372
|
}
|
|
291
|
-
// Step 2: Remove the marketplace
|
|
292
373
|
console.log("Removing Ory plugin marketplace...");
|
|
293
374
|
const removeResult = runClaude([
|
|
294
375
|
"plugin",
|
|
@@ -302,8 +383,9 @@ function uninstall(args) {
|
|
|
302
383
|
else {
|
|
303
384
|
console.log(" Marketplace removed.");
|
|
304
385
|
}
|
|
305
|
-
|
|
306
|
-
|
|
386
|
+
if (fromSource) {
|
|
387
|
+
cleanPersistentDir();
|
|
388
|
+
}
|
|
307
389
|
}
|
|
308
390
|
// --- Standalone binary entry point (ory-claude-setup) ---
|
|
309
391
|
if (require.main === module) {
|
|
@@ -315,13 +397,14 @@ Usage:
|
|
|
315
397
|
npx ory-claude-setup [options]
|
|
316
398
|
|
|
317
399
|
Options:
|
|
318
|
-
--global
|
|
319
|
-
--
|
|
320
|
-
|
|
400
|
+
--global Install to user scope (default: project scope)
|
|
401
|
+
--from-source Install from this checkout's local marketplace (dev only)
|
|
402
|
+
--uninstall Remove the Ory plugin and marketplace
|
|
403
|
+
-h, --help Show this help
|
|
321
404
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
405
|
+
By default, points the 'claude' CLI at the public ory/claude-plugins
|
|
406
|
+
marketplace and installs ory-agent-plugin from it. Pass --from-source to
|
|
407
|
+
install from the local monorepo checkout instead (used by the dev launcher).
|
|
325
408
|
`);
|
|
326
409
|
process.exit(0);
|
|
327
410
|
}
|
package/dist/handlers.js
CHANGED
|
@@ -188,40 +188,65 @@ async function handlePreToolUse(input, client, deps = {}) {
|
|
|
188
188
|
subject,
|
|
189
189
|
spanAttributes: { toolName },
|
|
190
190
|
});
|
|
191
|
-
|
|
191
|
+
const mcpAttrs = {
|
|
192
|
+
toolName,
|
|
193
|
+
mcpServer: mcpTool.serverName,
|
|
194
|
+
mcpTool: mcpTool.toolName,
|
|
195
|
+
...inputSummary,
|
|
196
|
+
};
|
|
197
|
+
const decision = (0, argus_1.applyPermissionMode)(client, mcpResult.allowed, {
|
|
198
|
+
object: mcpTool.serverName,
|
|
199
|
+
relation: "use",
|
|
200
|
+
subjectId,
|
|
201
|
+
spanAttributes: mcpAttrs,
|
|
202
|
+
});
|
|
203
|
+
if (decision.kind === "allow") {
|
|
204
|
+
client.tracer.record("tool.invoke", "ok", { attributes: mcpAttrs });
|
|
205
|
+
return {};
|
|
206
|
+
}
|
|
207
|
+
if (decision.kind === "observe") {
|
|
192
208
|
client.tracer.record("tool.block", "denied", {
|
|
193
|
-
attributes: {
|
|
209
|
+
attributes: { ...mcpAttrs, allowed: false, ...(0, argus_1.alertAttributes)(false) },
|
|
210
|
+
});
|
|
211
|
+
client.tracer.record("tool.invoke", "ok", {
|
|
212
|
+
attributes: { ...mcpAttrs, allowed: false, observed: true },
|
|
194
213
|
});
|
|
195
|
-
return {
|
|
196
|
-
decision: "block",
|
|
197
|
-
reason: (0, argus_1.formatDenialMessage)({ tool: toolName, subjectId, mcp: mcpTool }),
|
|
198
|
-
};
|
|
214
|
+
return {};
|
|
199
215
|
}
|
|
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
216
|
client.tracer.record("tool.block", "denied", {
|
|
214
|
-
attributes: {
|
|
217
|
+
attributes: { ...mcpAttrs, allowed: false, ...(0, argus_1.alertAttributes)(true) },
|
|
215
218
|
});
|
|
216
219
|
return {
|
|
217
220
|
decision: "block",
|
|
218
|
-
reason: (0, argus_1.formatDenialMessage)({ tool: toolName, subjectId,
|
|
221
|
+
reason: (0, argus_1.formatDenialMessage)({ tool: toolName, subjectId, mcp: mcpTool }),
|
|
219
222
|
};
|
|
220
223
|
}
|
|
221
|
-
|
|
222
|
-
|
|
224
|
+
const namespace = resolveNamespace();
|
|
225
|
+
const decision = await (0, argus_1.checkAndDecide)(client, { namespace, object: toolName, relation: "use", ...subject }, { spanAttributes: { toolName } });
|
|
226
|
+
if (decision.kind === "fail_open") {
|
|
227
|
+
return handlePermissionError(decision.error, toolName, client);
|
|
228
|
+
}
|
|
229
|
+
const attrs = { toolName, ...inputSummary };
|
|
230
|
+
if (decision.kind === "allow") {
|
|
231
|
+
client.tracer.record("tool.invoke", "ok", { attributes: { ...attrs, allowed: true } });
|
|
232
|
+
return {};
|
|
233
|
+
}
|
|
234
|
+
if (decision.kind === "observe") {
|
|
235
|
+
client.tracer.record("tool.block", "denied", {
|
|
236
|
+
attributes: { ...attrs, allowed: false, ...(0, argus_1.alertAttributes)(false) },
|
|
237
|
+
});
|
|
238
|
+
client.tracer.record("tool.invoke", "ok", {
|
|
239
|
+
attributes: { ...attrs, allowed: false, observed: true },
|
|
240
|
+
});
|
|
241
|
+
return {};
|
|
242
|
+
}
|
|
243
|
+
client.tracer.record("tool.block", "denied", {
|
|
244
|
+
attributes: { ...attrs, allowed: false, ...(0, argus_1.alertAttributes)(true) },
|
|
223
245
|
});
|
|
224
|
-
return {
|
|
246
|
+
return {
|
|
247
|
+
decision: "block",
|
|
248
|
+
reason: (0, argus_1.formatDenialMessage)({ tool: toolName, subjectId, namespace }),
|
|
249
|
+
};
|
|
225
250
|
}
|
|
226
251
|
catch (err) {
|
|
227
252
|
return handlePermissionError(err, toolName, client);
|
|
@@ -354,20 +379,34 @@ async function handlePermissionRequest(input, client) {
|
|
|
354
379
|
subject,
|
|
355
380
|
spanAttributes: { toolName },
|
|
356
381
|
});
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
:
|
|
382
|
+
const decision = (0, argus_1.applyPermissionMode)(client, mcpResult.allowed, {
|
|
383
|
+
object: mcpTool.serverName,
|
|
384
|
+
relation: "use",
|
|
385
|
+
subjectId,
|
|
386
|
+
spanAttributes: { toolName, mcpServer: mcpTool.serverName, mcpTool: mcpTool.toolName },
|
|
387
|
+
});
|
|
388
|
+
if (decision.kind === "deny") {
|
|
389
|
+
return permissionRequestDecision("deny", (0, argus_1.formatDenialMessage)({ tool: toolName, subjectId, mcp: mcpTool }));
|
|
390
|
+
}
|
|
391
|
+
return permissionRequestDecision("allow", decision.kind === "observe"
|
|
392
|
+
? "Ory observe mode — denied by policy but allowed through"
|
|
393
|
+
: "Ory MCP server permission granted");
|
|
360
394
|
}
|
|
361
395
|
const namespace = resolveNamespace();
|
|
362
|
-
const
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
396
|
+
const decision = await (0, argus_1.checkAndDecide)(client, { namespace, object: toolName, relation: "use", ...subject }, { spanAttributes: { toolName } });
|
|
397
|
+
if (decision.kind === "fail_open") {
|
|
398
|
+
const code = decision.error.code;
|
|
399
|
+
const fallbackReason = code === "network_error" || code === "rate_limited"
|
|
400
|
+
? `Ory unavailable (${code}), falling back to user prompt`
|
|
401
|
+
: `Ory permission check failed: ${decision.error.message}`;
|
|
402
|
+
return permissionRequestFallback(fallbackReason);
|
|
403
|
+
}
|
|
404
|
+
if (decision.kind === "deny") {
|
|
405
|
+
return permissionRequestDecision("deny", (0, argus_1.formatDenialMessage)({ tool: toolName, subjectId, namespace }));
|
|
406
|
+
}
|
|
407
|
+
return permissionRequestDecision("allow", decision.kind === "observe"
|
|
408
|
+
? "Ory observe mode — denied by policy but allowed through"
|
|
409
|
+
: "Ory permission granted");
|
|
371
410
|
}
|
|
372
411
|
catch (err) {
|
|
373
412
|
const oryErr = err;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ory/claude-code",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Ory plugin for Claude Code: 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",
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
"!dist/**/*.tsbuildinfo"
|
|
73
73
|
],
|
|
74
74
|
"dependencies": {
|
|
75
|
-
"@ory/argus": "0.1
|
|
75
|
+
"@ory/argus": "0.2.1"
|
|
76
76
|
},
|
|
77
77
|
"engines": {
|
|
78
78
|
"node": ">=24"
|