@ory/claude-code 0.13.9 → 0.14.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 CHANGED
@@ -21,12 +21,14 @@ Run one command. It installs the plugin and walks you through connecting:
21
21
  npx -y -p @ory/claude-code ory-claude install
22
22
  ```
23
23
 
24
- You'll be asked how you want to connect — **press Enter for the default**:
24
+ It opens a guided setup **in your browser** where you pick how to connect — the default takes just a click:
25
25
 
26
26
  - **Ory Network** *(default)* — sign in, or create a free account, in your browser. The project, keys, permissions, and login are all set up for you. Nothing to configure by hand.
27
27
  - **Local** — run a complete Ory on your laptop with Docker. No account, no signup, no keys. Great for trying it out.
28
28
  - **Audit-only** — skip Ory entirely and just log what Claude does.
29
29
 
30
+ > No browser available (CI, SSH, headless)? The same walkthrough runs right in your terminal instead — or force it with `--no-web`.
31
+
30
32
  That's it. Confirm everything landed with:
31
33
 
32
34
  ```bash
@@ -52,6 +54,7 @@ If Ory is ever unreachable, the plugin gets out of the way and lets Claude keep
52
54
  Everything the plugin does is observable out of the box — no configuration required:
53
55
 
54
56
  - **Status at a glance.** `npx -y -p @ory/claude-code ory-claude status` shows what's configured, who's signed in, how many built-in tools your permissions cover, and the most recent tool-call activity.
57
+ - **Live dashboard.** `npx -y -p @ory/claude-code ory-claude dashboard` opens the same picture in your browser — configuration, identities, permission coverage, Ory service health, and the latest tool-call activity, all refreshing live. From there you can flip **enforcement** on or off, toggle **user login**, and use **Change stack** to reconnect to a different Ory — no CLI required.
55
58
  - **Live traces.** Every tool call is recorded as an OpenTelemetry-style span. Watch them stream as the agent works:
56
59
 
57
60
  ```bash
@@ -69,7 +72,7 @@ When the watch-mode logs look right, turn on blocking with one command (setup al
69
72
  npx -y -p @ory/claude-code ory-claude permissions enforce
70
73
  ```
71
74
 
72
- Now a denied tool is actually blocked and Claude shows why. Go back to watch mode anytime with `permissions observe`. Use `permissions status` to see what's covered and `permissions bootstrap` to (re-)grant the built-in tools — or just ask Claude in chat, e.g. *"grant me use of the Bash tool."*
75
+ Now a denied tool is actually blocked and Claude shows why. Go back to watch mode anytime with `permissions observe`. Prefer clicking? The dashboard has the same **Enforce** switch — flip it on, or back to watch mode, without touching the CLI. Use `permissions status` to see what's covered and `permissions bootstrap` to (re-)grant the built-in tools — or just ask Claude in chat, e.g. *"grant me use of the Bash tool."*
73
76
 
74
77
  ## Also: add login to your own app
75
78
 
@@ -84,8 +87,7 @@ The guided setup covers most people. For scripted or CI setups, or to point at a
84
87
  ```bash
85
88
  npx -y -p @ory/claude-code ory-claude configure \
86
89
  --project-url https://<slug>.projects.oryapis.com \
87
- --oauth2-client-id <login client id> \
88
- --user-login
90
+ --oauth2-client-id <login client id>
89
91
  ```
90
92
 
91
93
  Claude's own identity registers itself automatically on first run — nothing to create. The `--oauth2-client-id` is the one piece browser sign-in needs; the guided setup makes it for you, or see below to do it by hand. For logging-only with no checks, use `--audit-only`.
@@ -97,7 +99,7 @@ The guided setup normally does this. To do it yourself, create a **public** OAut
97
99
 
98
100
  ```bash
99
101
  ory create oauth2-client --project <project-id> \
100
- --name "ory-agent-plugin" \
102
+ --name "Ory Agent Security · user login (PKCE)" \
101
103
  --grant-type authorization_code,refresh_token \
102
104
  --response-type code \
103
105
  --scope openid,offline_access \
@@ -117,11 +119,12 @@ With nothing configured, the plugin still loads and runs in **pass-through mode*
117
119
  ## Commands
118
120
 
119
121
  ```
120
- ory-claude install | uninstall Install/remove; --reconfigure re-runs setup, --no-configure skips it
122
+ ory-claude install | uninstall Install/remove; --reconfigure re-runs setup, --no-web forces the terminal wizard, --no-configure skips it
121
123
  ory-claude status Show configuration, identities, permission coverage, recent activity
124
+ ory-claude dashboard Open the live browser dashboard (status + service health + Change stack)
122
125
  ory-claude watch Tail the live trace stream (OTel spans)
123
126
  ory-claude permissions <cmd> status | bootstrap | observe (watch) | enforce (block)
124
- ory-claude configure <flags> Point at a project by hand (--project-url, --oauth2-client-id, --user-login, --audit-only)
127
+ ory-claude configure <flags> Point at a project by hand (--project-url, --oauth2-client-id, --audit-only)
125
128
  ory-claude agent <status|unregister> Manage Claude's own auto-created identity
126
129
  ory-claude local <up|down|status|…> Run / manage a local Ory in Docker
127
130
  ```
package/dist/cli/main.js CHANGED
@@ -98,6 +98,12 @@ function main() {
98
98
  case "watch":
99
99
  (0, argus_1.runWatchCommand)("claude-code", args);
100
100
  break;
101
+ case "dashboard":
102
+ (0, argus_1.runDashboardCommand)("ory-claude", "claude-code", args).then((code) => process.exit(code), (err) => {
103
+ console.error(err.message ?? err);
104
+ process.exit(1);
105
+ });
106
+ break;
101
107
  case "version":
102
108
  case "--version":
103
109
  case "-v":
@@ -155,6 +161,7 @@ Commands:
155
161
  agent <cmd> Manage the agent's OAuth2 (DCR) identity (status, unregister)
156
162
  permissions <cmd> Manage permission mode and tool permissions (status, bootstrap, observe, enforce)
157
163
  status Show plugin status and configuration
164
+ dashboard Open a live web dashboard (stack health + configuration)
158
165
  watch [trace-file] Tail the trace stream (OTel spans) live
159
166
  version Show version and the ory-agent-plugins build commit
160
167
  local <cmd> Manage local Ory dev environment (up, down, status, seed, ...)
package/dist/cli/setup.js CHANGED
@@ -108,8 +108,18 @@ const RENDER_OPTS = {
108
108
  * fetches everything directly from the GitHub marketplace.
109
109
  */
110
110
  const PERSISTENT_DIR = (0, argus_1.getHarnessDataDir)("claude-code");
111
- /** Hook command that resolves the binary via the npm registry. */
112
- const HOOK_CMD_REMOTE = "npx -y -p @ory/claude-code ory-claude-hook";
111
+ /**
112
+ * Hook command for a `--from-source` install fetched via npx. The plugin lives
113
+ * in an ephemeral `_npx` cache dir (cleaned between runs), so the hook must
114
+ * re-resolve `@ory/claude-code` at runtime rather than point at a soon-to-vanish
115
+ * path. `--from-source` is dev-only and always paired with the local Verdaccio
116
+ * registry, so the hook's npx is pinned to it (with `--min-release-age=0` so a
117
+ * just-published dev version isn't filtered out). Without the explicit
118
+ * `--registry`, a normally-launched `claude` (i.e. not via the dev launcher,
119
+ * which sets `npm_config_registry`) would resolve `@ory/claude-code` from
120
+ * npmjs.org and run the last *published* release instead of your local build.
121
+ */
122
+ const HOOK_CMD_FROM_SOURCE_REMOTE = `npx -y --registry ${argus_1.REGISTRY_URL} --min-release-age=0 -p @ory/claude-code ory-claude-hook`;
113
123
  /**
114
124
  * Detect if we're running from a temporary npx/pnpm-dlx cache directory
115
125
  * rather than a proper local or global npm installation. Only consulted on
@@ -145,6 +155,25 @@ function assemblePluginDir(hookCommand) {
145
155
  });
146
156
  }
147
157
  }
158
+ // Stamp the assembled plugin manifest with this package's version. The
159
+ // committed manifest carries a frozen placeholder version, and the `claude`
160
+ // CLI keys its cached component inventory (commands, skills) on the plugin
161
+ // version — so without this, a release that adds a command would keep the
162
+ // stale command set until the user manually removed the plugin. Tracking the
163
+ // package version means every release advertises a new plugin version and
164
+ // Claude Code refreshes.
165
+ const manifestPath = path.join(PERSISTENT_DIR, ".claude-plugin", "plugin.json");
166
+ try {
167
+ const pkgVersion = JSON.parse(fs.readFileSync(path.join(PACKAGE_ROOT, "package.json"), "utf8")).version;
168
+ if (pkgVersion && fs.existsSync(manifestPath)) {
169
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
170
+ manifest.version = pkgVersion;
171
+ fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
172
+ }
173
+ }
174
+ catch {
175
+ /* best-effort — a missing/unreadable manifest just keeps its default version */
176
+ }
148
177
  // Skills — materialized fresh from the shared core templates.
149
178
  const skillsDir = path.join(PERSISTENT_DIR, "skills");
150
179
  fs.rmSync(skillsDir, { recursive: true, force: true });
@@ -318,10 +347,19 @@ function installFromSource(scope) {
318
347
  const remote = isRemoteContext();
319
348
  console.log("Assembling Ory plugin (skills, commands, hooks, MCP) at:");
320
349
  console.log(` ${PERSISTENT_DIR}`);
321
- const pluginRoot = assemblePluginDir(remote ? HOOK_CMD_REMOTE : localHookCommand());
350
+ const pluginRoot = assemblePluginDir(remote ? HOOK_CMD_FROM_SOURCE_REMOTE : localHookCommand());
322
351
  const marketplaceName = readLocalMarketplaceName();
323
352
  const pluginName = readLocalPluginName();
324
353
  console.log(`Source: ${pluginName}@${marketplaceName} (from ${pluginRoot})`);
354
+ // Force a clean slate before re-adding. The `claude` CLI caches an
355
+ // already-installed plugin by version and won't re-read its command/skill
356
+ // files on a same-version re-install — so a freshly-assembled command (e.g.
357
+ // `dashboard`) would be missing after a republish. Removing the plugin and
358
+ // its marketplace first guarantees the current command set is picked up on
359
+ // every dev iteration. Best-effort and quiet: a not-yet-installed plugin
360
+ // just no-ops here.
361
+ runClaude(["plugin", "uninstall", pluginName, "--scope", scope]);
362
+ runClaude(["plugin", "marketplace", "remove", marketplaceName]);
325
363
  console.log("Adding local Ory plugin marketplace...");
326
364
  const addResult = runClaude([
327
365
  "plugin",
package/dist/handlers.js CHANGED
@@ -61,13 +61,12 @@ async function handleSessionStart(input, client, deps) {
61
61
  attributes: { model: input.model, source: input.source },
62
62
  });
63
63
  // Run the user login (interactive PKCE on first session, refresh
64
- // when needed). When ORY_USER_LOGIN is unset this is a no-op and we
65
- // fall through to the legacy verify path below.
64
+ // when needed). This runs every session and never blocks
65
+ // enforcement is governed solely by permissionMode at tool-call time.
66
66
  const userGate = deps.userLogin ?? argus_1.ensureUserAuthenticated;
67
- const decision = await userGate(client, {
67
+ await userGate(client, {
68
68
  binName: "ory-claude",
69
69
  harness: "claude-code",
70
- allowBlock: true,
71
70
  });
72
71
  // Resolve the agent identity (machine credentials) regardless of how
73
72
  // user auth went — it never blocks and attaches the agent's bearer
@@ -81,83 +80,8 @@ async function handleSessionStart(input, client, deps) {
81
80
  // populated, and any failure is logged and swallowed (fail-open —
82
81
  // delegation tracking is for audit, not enforcement).
83
82
  await (0, argus_1.writeUserDelegatesAgent)(client);
84
- if (!decision.proceed) {
85
- return { decision: "block", reason: decision.reason };
86
- }
87
- if (decision.mode !== "disabled") {
88
- return {};
89
- }
90
- const resolved = (0, argus_1.resolveConfig)();
91
- if (resolved.auditOnly) {
92
- client.logger.info("config.audit_only", {
93
- message: "Audit-only mode enabled. Auth and permission checks are disabled.",
94
- });
95
- return {};
96
- }
97
- if (!resolved.projectUrl) {
98
- client.logger.warn("config.not_configured", {
99
- message: "Ory plugin is not configured. Auth and permission checks are disabled. " +
100
- "Run 'npx ory-claude configure' to connect to an Ory project.",
101
- });
102
- return {};
103
- }
104
- // Try session token first, then OAuth2 token
105
- const sessionToken = process.env.ORY_SESSION_TOKEN;
106
- const oauth2Token = process.env.ORY_OAUTH2_TOKEN;
107
- if (sessionToken) {
108
- await verifySessionToken(sessionToken, client);
109
- return {};
110
- }
111
- if (oauth2Token) {
112
- await verifyOAuth2Token(oauth2Token, client);
113
- return {};
114
- }
115
- client.logger.warn("session.no_credentials", {
116
- message: "Neither ORY_SESSION_TOKEN nor ORY_OAUTH2_TOKEN is set. " +
117
- "Skipping authentication.",
118
- });
119
83
  return {};
120
84
  }
121
- async function verifySessionToken(token, client) {
122
- try {
123
- const session = await client.verifySession(token);
124
- if (!session.active) {
125
- client.logger.warn("session.inactive", {
126
- message: "Ory session is not active. Re-authenticate to enable auth checks.",
127
- });
128
- }
129
- }
130
- catch (err) {
131
- const oryErr = err;
132
- client.logger.warn("session.verify_failed", {
133
- code: oryErr.code,
134
- message: oryErr.message,
135
- });
136
- }
137
- }
138
- async function verifyOAuth2Token(token, client) {
139
- try {
140
- const tokenInfo = await client.introspectToken(token);
141
- if (!tokenInfo.active) {
142
- client.logger.warn("oauth2.token_inactive", {
143
- message: "Ory OAuth2 token is not active. Obtain a new token to enable auth checks.",
144
- });
145
- return;
146
- }
147
- client.logger.info("oauth2.session_authenticated", {
148
- clientId: tokenInfo.clientId,
149
- subject: tokenInfo.subject,
150
- scope: tokenInfo.scope,
151
- });
152
- }
153
- catch (err) {
154
- const oryErr = err;
155
- client.logger.warn("oauth2.introspect_failed", {
156
- code: oryErr.code,
157
- message: oryErr.message,
158
- });
159
- }
160
- }
161
85
  // ─── PreToolUse ─────────────────────────────────────────────────────
162
86
  async function handlePreToolUse(input, client, deps = {}) {
163
87
  const toolName = input.tool_name ?? "unknown";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ory/claude-code",
3
- "version": "0.13.9",
3
+ "version": "0.14.0",
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://github.com/ory/claude-plugins/tree/master/plugins/ory-agent-plugin",
@@ -76,7 +76,7 @@
76
76
  ],
77
77
  "dependencies": {
78
78
  "reo-census": "^1.2.8",
79
- "@ory/argus": "0.13.9"
79
+ "@ory/argus": "0.14.0"
80
80
  },
81
81
  "engines": {
82
82
  "node": ">=22"