@ory/claude-code 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/.claude-plugin/marketplace.json +13 -0
- package/.claude-plugin/plugin.json +10 -0
- package/.mcp.json +12 -0
- package/README.md +101 -0
- package/dist/cli/main.d.ts +12 -0
- package/dist/cli/main.js +131 -0
- package/dist/cli/setup.d.ts +28 -0
- package/dist/cli/setup.js +297 -0
- package/dist/handlers.d.ts +14 -0
- package/dist/handlers.js +525 -0
- package/dist/hook.d.ts +9 -0
- package/dist/hook.js +76 -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 +67 -0
- package/dist/types.d.ts +72 -0
- package/dist/types.js +2 -0
- package/hooks/hooks.json +104 -0
- package/package.json +86 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ory-claude",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Ory authentication and authorization for Claude Code — session auth, tool permissions, and audit logging",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Ory",
|
|
7
|
+
"url": "https://ory.com"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/ory/claude-plugins"
|
|
10
|
+
}
|
package/.mcp.json
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Ory Agent Plugin: Claude Code
|
|
2
|
+
|
|
3
|
+
[Ory](https://ory.com) bundled into [Claude Code](https://docs.anthropic.com/en/docs/claude-code): skills and slash commands that scaffold Ory authentication into your codebase, a local Ory stack you can spin up in one command, and (when pointed at an Ory project) authentication, authorization, and audit for every tool Claude runs.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
From inside Claude Code. Try these in order; the first that works is the simplest path:
|
|
8
|
+
|
|
9
|
+
**1. Anthropic's public plugin marketplace.** Find **Ory Agent Plugin** and install:
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
/plugin install ory-agent-plugin
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
**2. The Ory-hosted marketplace:**
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
/plugin marketplace add ory/claude-plugins
|
|
19
|
+
/plugin install ory-claude@ory-plugins
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**3. The Ory installer.** No prior `npm install` required:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npx @ory/claude-code install # current project
|
|
26
|
+
npx @ory/claude-code install --global # all projects (user scope)
|
|
27
|
+
npx @ory/claude-code uninstall
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Every flow registers the same plugin: skills, slash commands, hooks, and the Ory MCP server.
|
|
31
|
+
|
|
32
|
+
## Developer experience
|
|
33
|
+
|
|
34
|
+
This plugin is a productivity layer for Ory itself. You don't need a real Ory project, an account, or any prior Ory experience to start using it.
|
|
35
|
+
|
|
36
|
+
### Skills for scaffolding Ory into your application
|
|
37
|
+
|
|
38
|
+
Ask Claude to add Ory auth to your codebase, or invoke the slash commands directly. Each skill is a vetted, end-to-end playbook:
|
|
39
|
+
|
|
40
|
+
- **`/ory:auth-setup`**: full project setup. Install the Ory CLI, create an Ory Network project, add Ory Elements, configure the SDK, build the auth pages, wire session middleware.
|
|
41
|
+
- **`/ory:login-flow`**: login, registration, recovery, verification, and settings pages with Ory Elements. Next.js App Router and React SPA variants.
|
|
42
|
+
- **`/ory:social-login`**: Google, GitHub, Apple, Microsoft, Discord, and other OIDC providers with Jsonnet data mappers.
|
|
43
|
+
- **`/ory:local-dev`**: drive the local Ory stack (below) from within Claude to prototype and test against without a remote project.
|
|
44
|
+
|
|
45
|
+
Skills are versioned with the plugin so guidance stays in sync as Ory APIs evolve.
|
|
46
|
+
|
|
47
|
+
### Ory MCP server
|
|
48
|
+
|
|
49
|
+
Bundled and registered automatically. It exposes the Ory CLI and the Ory Network REST API as MCP tools so Claude can manage identities, OAuth2 clients, projects, permission tuples, and configuration without ever leaving the chat. Useful for seeding test data, verifying a scaffolded integration, or running one-off admin tasks.
|
|
50
|
+
|
|
51
|
+
### Local Ory stack in one command
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
/ory:local-up # start a local Ory instance in Docker
|
|
55
|
+
/ory:local-down # tear it all down
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`local up` runs a local Ory instance in Docker, covering everything the plugin and your scaffolded application need. It also brings up a login UI on `:3000` and Jaeger on `:16686`, all reachable through `http://localhost:4000`. A test user identity is seeded and the credentials are printed for you. Use it to:
|
|
59
|
+
|
|
60
|
+
- **Learn Ory hands-on** without signing up for a hosted project.
|
|
61
|
+
- **Prototype** flows (login, social, MFA, recovery, permission tuples) against a real Ory backend in your local dev loop.
|
|
62
|
+
- **Test** an auth integration end-to-end before pushing anything to a real environment.
|
|
63
|
+
- **Develop** your application against the same identity, OAuth2, and permission surfaces you'll ship with.
|
|
64
|
+
|
|
65
|
+
Point `ORY_PROJECT_URL` at `http://localhost:4000` (or run `npx ory-claude configure`) and the security features below run against the local stack.
|
|
66
|
+
|
|
67
|
+
## Configure
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
npx ory-claude configure --project-url https://<id>.projects.oryapis.com --api-key ory_pat_...
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Config is saved to `~/.config/ory-agent-plugins/config.json` and shared across every Ory agent plugin on the machine. Without it the plugin still loads cleanly and runs in **pass-through mode**: skills and commands work, but nothing is blocked.
|
|
74
|
+
|
|
75
|
+
## Agent security (Argus)
|
|
76
|
+
|
|
77
|
+
Once the plugin is pointed at an Ory project (local or hosted), Claude's session and every tool call are governed by Ory.
|
|
78
|
+
|
|
79
|
+
- **Authentication.** Two identities. The human at the keyboard (the **user**) authenticates interactively via Ory Identities when `ORY_AUTH_GATE=1`. The Claude process (the **agent**) gets its own OAuth2 identity, self-registered via Dynamic Client Registration on first run. Sub-agents launched by the `Task` tool each receive their own typed identity.
|
|
80
|
+
- **Authorization.** Before any tool runs, the plugin checks Ory Permissions against the user's subject and blocks the call on `deny`. MCP tool calls additionally get a server-level check.
|
|
81
|
+
- **Audit.** Every decision (allow, deny, fallback) is recorded as a structured trace span: NDJSON file output and/or OTLP/HTTP export to Jaeger, Honeycomb, Grafana, and similar collectors. The user-to-agent (and agent-to-subagent) delegation chain is written to Ory as Zanzibar tuples so "agent X acting on behalf of user Y" stays queryable after tokens expire.
|
|
82
|
+
|
|
83
|
+
The plugin is **fail-open** on its own infrastructure failures (network errors, rate limits, missing config), so enforcement is only as strong as your tuples; grant explicit `invoke` relations for the tools each user should be able to run.
|
|
84
|
+
|
|
85
|
+
## CLI
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
npx ory-claude install | uninstall [--global]
|
|
89
|
+
npx ory-claude configure [--project-url <url>] [--api-key <key>] [--audit-only]
|
|
90
|
+
npx ory-claude agent <status|unregister> Manage the agent's OAuth2 identity
|
|
91
|
+
npx ory-claude local <up|down|status|seed|logs|env|configure|reset>
|
|
92
|
+
npx ory-claude status
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Links
|
|
96
|
+
|
|
97
|
+
- [ory.com](https://ory.com)
|
|
98
|
+
|
|
99
|
+
## License
|
|
100
|
+
|
|
101
|
+
Apache-2.0
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Main CLI for @ory/claude-code.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* npx ory-claude install [--global] Install plugin via claude CLI marketplace
|
|
7
|
+
* npx ory-claude uninstall [--global] Remove plugin via claude CLI marketplace
|
|
8
|
+
* npx ory-claude configure Set or view Ory project URL and API key
|
|
9
|
+
* npx ory-claude status Show plugin status
|
|
10
|
+
* npx ory-claude local <cmd> Manage local Ory dev environment
|
|
11
|
+
*/
|
|
12
|
+
export {};
|
package/dist/cli/main.js
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* Main CLI for @ory/claude-code.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* npx ory-claude install [--global] Install plugin via claude CLI marketplace
|
|
8
|
+
* npx ory-claude uninstall [--global] Remove plugin via claude CLI marketplace
|
|
9
|
+
* npx ory-claude configure Set or view Ory project URL and API key
|
|
10
|
+
* npx ory-claude status Show plugin status
|
|
11
|
+
* npx ory-claude local <cmd> Manage local Ory dev environment
|
|
12
|
+
*/
|
|
13
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
16
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
17
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
18
|
+
}
|
|
19
|
+
Object.defineProperty(o, k2, desc);
|
|
20
|
+
}) : (function(o, m, k, k2) {
|
|
21
|
+
if (k2 === undefined) k2 = k;
|
|
22
|
+
o[k2] = m[k];
|
|
23
|
+
}));
|
|
24
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
25
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
26
|
+
}) : function(o, v) {
|
|
27
|
+
o["default"] = v;
|
|
28
|
+
});
|
|
29
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
30
|
+
var ownKeys = function(o) {
|
|
31
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
32
|
+
var ar = [];
|
|
33
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
34
|
+
return ar;
|
|
35
|
+
};
|
|
36
|
+
return ownKeys(o);
|
|
37
|
+
};
|
|
38
|
+
return function (mod) {
|
|
39
|
+
if (mod && mod.__esModule) return mod;
|
|
40
|
+
var result = {};
|
|
41
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
42
|
+
__setModuleDefault(result, mod);
|
|
43
|
+
return result;
|
|
44
|
+
};
|
|
45
|
+
})();
|
|
46
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
47
|
+
const path = __importStar(require("node:path"));
|
|
48
|
+
const fs = __importStar(require("node:fs"));
|
|
49
|
+
const argus_1 = require("@ory/argus");
|
|
50
|
+
const setup_js_1 = require("./setup.js");
|
|
51
|
+
const PACKAGE_ROOT = path.resolve(__dirname, "..", "..");
|
|
52
|
+
const PERSISTENT_DIR = (0, argus_1.getHarnessDataDir)("claude-code");
|
|
53
|
+
function main() {
|
|
54
|
+
const [command, ...args] = process.argv.slice(2);
|
|
55
|
+
switch (command) {
|
|
56
|
+
case "install":
|
|
57
|
+
(0, setup_js_1.install)(args);
|
|
58
|
+
break;
|
|
59
|
+
case "uninstall":
|
|
60
|
+
(0, setup_js_1.uninstall)(args);
|
|
61
|
+
break;
|
|
62
|
+
case "configure":
|
|
63
|
+
(0, argus_1.runConfigureCommand)("ory-claude", args);
|
|
64
|
+
break;
|
|
65
|
+
case "agent":
|
|
66
|
+
(0, argus_1.runAgentCommand)("ory-claude", args).then((code) => process.exit(code), (err) => {
|
|
67
|
+
console.error(err.message ?? err);
|
|
68
|
+
process.exit(1);
|
|
69
|
+
});
|
|
70
|
+
break;
|
|
71
|
+
case "status":
|
|
72
|
+
status();
|
|
73
|
+
break;
|
|
74
|
+
case "local":
|
|
75
|
+
(0, argus_1.runLocalCommand)("ory-claude", args).catch((err) => {
|
|
76
|
+
console.error(err.message ?? err);
|
|
77
|
+
process.exit(1);
|
|
78
|
+
});
|
|
79
|
+
break;
|
|
80
|
+
case "help":
|
|
81
|
+
case "--help":
|
|
82
|
+
case "-h":
|
|
83
|
+
case undefined:
|
|
84
|
+
help();
|
|
85
|
+
break;
|
|
86
|
+
default:
|
|
87
|
+
console.error(`Unknown command: ${command}`);
|
|
88
|
+
help();
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function status() {
|
|
93
|
+
console.log("Ory Agent Plugin Status (Claude Code)");
|
|
94
|
+
console.log("======================================");
|
|
95
|
+
console.log("");
|
|
96
|
+
(0, argus_1.printOryConfig)();
|
|
97
|
+
(0, argus_1.printEnvironment)();
|
|
98
|
+
// Plugin-specific status
|
|
99
|
+
console.log("");
|
|
100
|
+
console.log("Plugin:");
|
|
101
|
+
const assembled = fs.existsSync(PERSISTENT_DIR);
|
|
102
|
+
console.log(` Plugin directory: ${assembled ? PERSISTENT_DIR : "(not installed — run: npx ory-claude install)"}`);
|
|
103
|
+
console.log(` Skills: ${assembled && fs.existsSync(path.join(PERSISTENT_DIR, "skills")) ? "installed" : "not installed"}`);
|
|
104
|
+
console.log(` Hook script: ${fs.existsSync(path.join(PACKAGE_ROOT, "dist", "hook.js")) ? "built" : "NOT BUILT (run pnpm build)"}`);
|
|
105
|
+
(0, argus_1.printLogTail)();
|
|
106
|
+
}
|
|
107
|
+
function help() {
|
|
108
|
+
console.log(`
|
|
109
|
+
ory-claude — Ory plugin for Claude Code
|
|
110
|
+
|
|
111
|
+
Usage:
|
|
112
|
+
npx @ory/claude-code <command> [options]
|
|
113
|
+
npx ory-claude <command> [options]
|
|
114
|
+
|
|
115
|
+
Commands:
|
|
116
|
+
install [--global] Install Ory plugin via claude CLI marketplace
|
|
117
|
+
uninstall [--global] Remove Ory plugin and marketplace
|
|
118
|
+
configure Set or view Ory project URL and API key
|
|
119
|
+
status Show plugin status and configuration
|
|
120
|
+
local <cmd> Manage local Ory dev environment (up, down, status, seed, ...)
|
|
121
|
+
|
|
122
|
+
Examples:
|
|
123
|
+
npx @ory/claude-code install # Install remotely (no prior npm install needed)
|
|
124
|
+
npx @ory/claude-code install --global # Install globally (user scope)
|
|
125
|
+
npx ory-claude install # Install from local/global npm installation
|
|
126
|
+
npx ory-claude configure --project-url https://your-project.projects.oryapis.com --api-key ory_pat_...
|
|
127
|
+
npx ory-claude status # Check configuration
|
|
128
|
+
npx ory-claude uninstall # Remove plugin
|
|
129
|
+
`);
|
|
130
|
+
}
|
|
131
|
+
main();
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Setup CLI for the Ory Claude Code plugin.
|
|
4
|
+
*
|
|
5
|
+
* Uses the `claude` CLI to manage plugin installation via the marketplace:
|
|
6
|
+
* npx ory-claude-setup # Install plugin (project scope)
|
|
7
|
+
* npx ory-claude-setup --global # Install plugin (user scope)
|
|
8
|
+
* npx ory-claude-setup --uninstall # Remove plugin
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Install the Ory plugin via the `claude` CLI.
|
|
12
|
+
*
|
|
13
|
+
* 1. Assembles the plugin directory (manifest, skills, commands, hooks, MCP)
|
|
14
|
+
* 2. Adds the Ory marketplace (pointing to that directory)
|
|
15
|
+
* 3. Installs the plugin from that marketplace
|
|
16
|
+
*
|
|
17
|
+
* Hooks, skills, commands, and the MCP server are picked up automatically
|
|
18
|
+
* from the assembled directory by the Claude Code plugin system.
|
|
19
|
+
*/
|
|
20
|
+
export declare function install(args: string[]): void;
|
|
21
|
+
/**
|
|
22
|
+
* Uninstall the Ory plugin via the `claude` CLI.
|
|
23
|
+
*
|
|
24
|
+
* 1. Uninstalls the plugin
|
|
25
|
+
* 2. Removes the Ory marketplace
|
|
26
|
+
* 3. Cleans up the assembled plugin directory
|
|
27
|
+
*/
|
|
28
|
+
export declare function uninstall(args: string[]): void;
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* Setup CLI for the Ory Claude Code plugin.
|
|
5
|
+
*
|
|
6
|
+
* Uses the `claude` CLI to manage plugin installation via the marketplace:
|
|
7
|
+
* npx ory-claude-setup # Install plugin (project scope)
|
|
8
|
+
* npx ory-claude-setup --global # Install plugin (user scope)
|
|
9
|
+
* npx ory-claude-setup --uninstall # Remove plugin
|
|
10
|
+
*/
|
|
11
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
12
|
+
if (k2 === undefined) k2 = k;
|
|
13
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
14
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
15
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
16
|
+
}
|
|
17
|
+
Object.defineProperty(o, k2, desc);
|
|
18
|
+
}) : (function(o, m, k, k2) {
|
|
19
|
+
if (k2 === undefined) k2 = k;
|
|
20
|
+
o[k2] = m[k];
|
|
21
|
+
}));
|
|
22
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
23
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
24
|
+
}) : function(o, v) {
|
|
25
|
+
o["default"] = v;
|
|
26
|
+
});
|
|
27
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
28
|
+
var ownKeys = function(o) {
|
|
29
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
30
|
+
var ar = [];
|
|
31
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
32
|
+
return ar;
|
|
33
|
+
};
|
|
34
|
+
return ownKeys(o);
|
|
35
|
+
};
|
|
36
|
+
return function (mod) {
|
|
37
|
+
if (mod && mod.__esModule) return mod;
|
|
38
|
+
var result = {};
|
|
39
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
40
|
+
__setModuleDefault(result, mod);
|
|
41
|
+
return result;
|
|
42
|
+
};
|
|
43
|
+
})();
|
|
44
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
+
exports.install = install;
|
|
46
|
+
exports.uninstall = uninstall;
|
|
47
|
+
const node_child_process_1 = require("node:child_process");
|
|
48
|
+
const fs = __importStar(require("node:fs"));
|
|
49
|
+
const path = __importStar(require("node:path"));
|
|
50
|
+
const argus_1 = require("@ory/argus");
|
|
51
|
+
const PACKAGE_ROOT = path.resolve(__dirname, "..", "..");
|
|
52
|
+
const MARKETPLACE_NAME = "ory-plugins";
|
|
53
|
+
const PLUGIN_NAME = "ory-claude";
|
|
54
|
+
const RENDER_OPTS = {
|
|
55
|
+
binName: "ory-claude",
|
|
56
|
+
packageName: "@ory/claude-code",
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Persistent install location used as the marketplace root. Lives under the
|
|
60
|
+
* shared OS-agnostic data dir so all plugin state — config, DCR creds, harness
|
|
61
|
+
* assets — sits in one place per platform. The plugin directory is assembled
|
|
62
|
+
* here for both local and remote installs so the marketplace never points at
|
|
63
|
+
* (or writes into) the package source tree.
|
|
64
|
+
*/
|
|
65
|
+
const PERSISTENT_DIR = (0, argus_1.getHarnessDataDir)("claude-code");
|
|
66
|
+
/** Hook command that resolves the binary via the npm registry. */
|
|
67
|
+
const HOOK_CMD_REMOTE = "npx -y -p @ory/claude-code ory-claude-hook";
|
|
68
|
+
/**
|
|
69
|
+
* Detect if we're running from a temporary npx/pnpm-dlx cache directory
|
|
70
|
+
* rather than a proper local or global npm installation.
|
|
71
|
+
*/
|
|
72
|
+
function isRemoteContext() {
|
|
73
|
+
const normalized = PACKAGE_ROOT.replace(/\\/g, "/");
|
|
74
|
+
return normalized.includes("/_npx/") || normalized.includes("/dlx-");
|
|
75
|
+
}
|
|
76
|
+
/** Hook command for a local/global install — runs the built hook directly. */
|
|
77
|
+
function localHookCommand() {
|
|
78
|
+
return `node ${path.join(PACKAGE_ROOT, "dist", "hook.js")}`;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Assemble the Claude Code plugin directory in the persistent location and
|
|
82
|
+
* return its path (used as the marketplace root).
|
|
83
|
+
*
|
|
84
|
+
* The plugin manifest (`.claude-plugin/`) and MCP config (`.mcp.json`) are
|
|
85
|
+
* copied from the package; the skills, commands, and hooks are generated:
|
|
86
|
+
* skills/commands are materialized from the shared `@ory/argus` templates (the
|
|
87
|
+
* single source of truth across every harness), and `hooks.json` is written
|
|
88
|
+
* with a hook command appropriate for the install context.
|
|
89
|
+
*/
|
|
90
|
+
function assemblePluginDir(hookCommand) {
|
|
91
|
+
fs.mkdirSync(PERSISTENT_DIR, { recursive: true });
|
|
92
|
+
// Static manifests copied straight from the package.
|
|
93
|
+
for (const asset of [".claude-plugin", ".mcp.json"]) {
|
|
94
|
+
const src = path.join(PACKAGE_ROOT, asset);
|
|
95
|
+
if (fs.existsSync(src)) {
|
|
96
|
+
fs.cpSync(src, path.join(PERSISTENT_DIR, asset), {
|
|
97
|
+
recursive: true,
|
|
98
|
+
force: true,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// Skills — materialized fresh from the shared core templates.
|
|
103
|
+
const skillsDir = path.join(PERSISTENT_DIR, "skills");
|
|
104
|
+
fs.rmSync(skillsDir, { recursive: true, force: true });
|
|
105
|
+
(0, argus_1.writeSkillTree)(skillsDir, (0, argus_1.renderOrySkills)("claude-code", RENDER_OPTS));
|
|
106
|
+
// Commands — plain markdown command files (Claude Code's `/ory:<name>`).
|
|
107
|
+
const commandsDir = path.join(PERSISTENT_DIR, "commands");
|
|
108
|
+
fs.rmSync(commandsDir, { recursive: true, force: true });
|
|
109
|
+
fs.mkdirSync(commandsDir, { recursive: true });
|
|
110
|
+
for (const cmd of (0, argus_1.renderOryCommands)("claude-code", RENDER_OPTS)) {
|
|
111
|
+
fs.writeFileSync(path.join(commandsDir, `${cmd.slug}.md`), (0, argus_1.commandToPlainMarkdown)(cmd));
|
|
112
|
+
}
|
|
113
|
+
// Hooks — generated with a command appropriate for the install context.
|
|
114
|
+
const hooksDir = path.join(PERSISTENT_DIR, "hooks");
|
|
115
|
+
fs.mkdirSync(hooksDir, { recursive: true });
|
|
116
|
+
const entry = [
|
|
117
|
+
{ matcher: "", hooks: [{ type: "command", command: hookCommand }] },
|
|
118
|
+
];
|
|
119
|
+
fs.writeFileSync(path.join(hooksDir, "hooks.json"), JSON.stringify({
|
|
120
|
+
description: "Ory Claude Code Hooks",
|
|
121
|
+
hooks: {
|
|
122
|
+
SessionStart: entry,
|
|
123
|
+
SessionEnd: entry,
|
|
124
|
+
UserPromptSubmit: entry,
|
|
125
|
+
PreToolUse: entry,
|
|
126
|
+
PostToolUse: entry,
|
|
127
|
+
PostToolUseFailure: entry,
|
|
128
|
+
SubagentStart: entry,
|
|
129
|
+
SubagentStop: entry,
|
|
130
|
+
PermissionRequest: entry,
|
|
131
|
+
},
|
|
132
|
+
}, null, 2) + "\n");
|
|
133
|
+
return PERSISTENT_DIR;
|
|
134
|
+
}
|
|
135
|
+
/** Remove the persistent install directory if it exists. */
|
|
136
|
+
function cleanPersistentDir() {
|
|
137
|
+
if (fs.existsSync(PERSISTENT_DIR)) {
|
|
138
|
+
fs.rmSync(PERSISTENT_DIR, { recursive: true, force: true });
|
|
139
|
+
console.log(` Removed ${PERSISTENT_DIR}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/** Map --global flag to the claude CLI's scope values. */
|
|
143
|
+
function claudeScope(isGlobal) {
|
|
144
|
+
return isGlobal ? "user" : "project";
|
|
145
|
+
}
|
|
146
|
+
/** Run a `claude` CLI command and capture output. */
|
|
147
|
+
function runClaude(args) {
|
|
148
|
+
const result = (0, node_child_process_1.spawnSync)("claude", args, {
|
|
149
|
+
encoding: "utf-8",
|
|
150
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
151
|
+
});
|
|
152
|
+
const output = ((result.stdout ?? "") + (result.stderr ?? "")).trim();
|
|
153
|
+
return { ok: result.status === 0, output };
|
|
154
|
+
}
|
|
155
|
+
/** Check that the `claude` CLI is available. */
|
|
156
|
+
function checkClaudeCli() {
|
|
157
|
+
const result = (0, node_child_process_1.spawnSync)("claude", ["--version"], {
|
|
158
|
+
encoding: "utf-8",
|
|
159
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
160
|
+
});
|
|
161
|
+
return result.status === 0;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Install the Ory plugin via the `claude` CLI.
|
|
165
|
+
*
|
|
166
|
+
* 1. Assembles the plugin directory (manifest, skills, commands, hooks, MCP)
|
|
167
|
+
* 2. Adds the Ory marketplace (pointing to that directory)
|
|
168
|
+
* 3. Installs the plugin from that marketplace
|
|
169
|
+
*
|
|
170
|
+
* Hooks, skills, commands, and the MCP server are picked up automatically
|
|
171
|
+
* from the assembled directory by the Claude Code plugin system.
|
|
172
|
+
*/
|
|
173
|
+
function install(args) {
|
|
174
|
+
const isGlobal = args.includes("--global");
|
|
175
|
+
const scope = claudeScope(isGlobal);
|
|
176
|
+
if (!checkClaudeCli()) {
|
|
177
|
+
console.error("Error: 'claude' CLI not found in PATH.");
|
|
178
|
+
console.error("Install Claude Code first: https://docs.anthropic.com/en/docs/claude-code");
|
|
179
|
+
process.exit(1);
|
|
180
|
+
}
|
|
181
|
+
const remote = isRemoteContext();
|
|
182
|
+
console.log("Assembling Ory plugin (skills, commands, hooks, MCP) at:");
|
|
183
|
+
console.log(` ${PERSISTENT_DIR}`);
|
|
184
|
+
const pluginRoot = assemblePluginDir(remote ? HOOK_CMD_REMOTE : localHookCommand());
|
|
185
|
+
// Step 1: Add the Ory marketplace
|
|
186
|
+
console.log("Adding Ory plugin marketplace...");
|
|
187
|
+
const addResult = runClaude([
|
|
188
|
+
"plugin",
|
|
189
|
+
"marketplace",
|
|
190
|
+
"add",
|
|
191
|
+
pluginRoot,
|
|
192
|
+
"--scope",
|
|
193
|
+
scope,
|
|
194
|
+
]);
|
|
195
|
+
if (!addResult.ok) {
|
|
196
|
+
if (addResult.output.toLowerCase().includes("already")) {
|
|
197
|
+
console.log(" Marketplace already registered, updating...");
|
|
198
|
+
runClaude(["plugin", "marketplace", "update", MARKETPLACE_NAME]);
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
console.error(`Failed to add marketplace: ${addResult.output}`);
|
|
202
|
+
process.exit(1);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
console.log(" Marketplace added.");
|
|
207
|
+
}
|
|
208
|
+
// Step 2: Install the plugin from the marketplace
|
|
209
|
+
console.log("Installing ory-claude plugin...");
|
|
210
|
+
const installResult = runClaude([
|
|
211
|
+
"plugin",
|
|
212
|
+
"install",
|
|
213
|
+
`${PLUGIN_NAME}@${MARKETPLACE_NAME}`,
|
|
214
|
+
"--scope",
|
|
215
|
+
scope,
|
|
216
|
+
]);
|
|
217
|
+
if (!installResult.ok) {
|
|
218
|
+
console.error(`Failed to install plugin: ${installResult.output}`);
|
|
219
|
+
process.exit(1);
|
|
220
|
+
}
|
|
221
|
+
console.log(" Plugin installed.");
|
|
222
|
+
(0, argus_1.printNextSteps)("Claude Code", remote ? "npx @ory/claude-code uninstall" : "npx ory-claude uninstall");
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Uninstall the Ory plugin via the `claude` CLI.
|
|
226
|
+
*
|
|
227
|
+
* 1. Uninstalls the plugin
|
|
228
|
+
* 2. Removes the Ory marketplace
|
|
229
|
+
* 3. Cleans up the assembled plugin directory
|
|
230
|
+
*/
|
|
231
|
+
function uninstall(args) {
|
|
232
|
+
const isGlobal = args.includes("--global");
|
|
233
|
+
const scope = claudeScope(isGlobal);
|
|
234
|
+
if (!checkClaudeCli()) {
|
|
235
|
+
console.error("Error: 'claude' CLI not found in PATH.");
|
|
236
|
+
process.exit(1);
|
|
237
|
+
}
|
|
238
|
+
// Step 1: Uninstall the plugin
|
|
239
|
+
console.log("Uninstalling ory-claude plugin...");
|
|
240
|
+
const uninstallResult = runClaude([
|
|
241
|
+
"plugin",
|
|
242
|
+
"uninstall",
|
|
243
|
+
PLUGIN_NAME,
|
|
244
|
+
"--scope",
|
|
245
|
+
scope,
|
|
246
|
+
]);
|
|
247
|
+
if (!uninstallResult.ok) {
|
|
248
|
+
console.warn(` Warning: ${uninstallResult.output}`);
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
console.log(" Plugin uninstalled.");
|
|
252
|
+
}
|
|
253
|
+
// Step 2: Remove the marketplace
|
|
254
|
+
console.log("Removing Ory plugin marketplace...");
|
|
255
|
+
const removeResult = runClaude([
|
|
256
|
+
"plugin",
|
|
257
|
+
"marketplace",
|
|
258
|
+
"remove",
|
|
259
|
+
MARKETPLACE_NAME,
|
|
260
|
+
]);
|
|
261
|
+
if (!removeResult.ok) {
|
|
262
|
+
console.warn(` Warning: ${removeResult.output}`);
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
console.log(" Marketplace removed.");
|
|
266
|
+
}
|
|
267
|
+
// Step 3: Clean up the assembled plugin directory.
|
|
268
|
+
cleanPersistentDir();
|
|
269
|
+
}
|
|
270
|
+
// --- Standalone binary entry point (ory-claude-setup) ---
|
|
271
|
+
if (require.main === module) {
|
|
272
|
+
if (process.argv.includes("--help") || process.argv.includes("-h")) {
|
|
273
|
+
console.log(`
|
|
274
|
+
ory-claude-setup — Install or remove the Ory plugin for Claude Code
|
|
275
|
+
|
|
276
|
+
Usage:
|
|
277
|
+
npx ory-claude-setup [options]
|
|
278
|
+
|
|
279
|
+
Options:
|
|
280
|
+
--global Install to user scope (default: project scope)
|
|
281
|
+
--uninstall Remove the Ory plugin and marketplace
|
|
282
|
+
-h, --help Show this help
|
|
283
|
+
|
|
284
|
+
Uses the 'claude' CLI to manage plugins via the marketplace system.
|
|
285
|
+
The plugin's skills, commands, hooks, and MCP server are configured
|
|
286
|
+
automatically.
|
|
287
|
+
`);
|
|
288
|
+
process.exit(0);
|
|
289
|
+
}
|
|
290
|
+
const args = process.argv.slice(2);
|
|
291
|
+
if (args.includes("--uninstall")) {
|
|
292
|
+
uninstall(args);
|
|
293
|
+
}
|
|
294
|
+
else {
|
|
295
|
+
install(args);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { OryAgentClient, ensureUserAuthenticated, ensureAgentIdentity, ensureSubAgentIdentity } from "@ory/argus";
|
|
2
|
+
import type { ClaudeCodeHookInput, ClaudeCodeHookOutput } from "./types.js";
|
|
3
|
+
export interface HandleHookEventDeps {
|
|
4
|
+
/** Test injection point for the user auth gate. */
|
|
5
|
+
authGate?: typeof ensureUserAuthenticated;
|
|
6
|
+
/** Test injection point for the agent identity gate. */
|
|
7
|
+
agentGate?: typeof ensureAgentIdentity;
|
|
8
|
+
/** Test injection point for the sub-agent identity resolver. */
|
|
9
|
+
subAgentGate?: typeof ensureSubAgentIdentity;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Route a Claude Code hook event to the appropriate Ory integration.
|
|
13
|
+
*/
|
|
14
|
+
export declare function handleHookEvent(input: ClaudeCodeHookInput, client: OryAgentClient, deps?: HandleHookEventDeps): Promise<ClaudeCodeHookOutput>;
|