@ory/pi 0.14.0 → 1.0.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
@@ -2,9 +2,12 @@
2
2
 
3
3
  Security and developer experience for [Pi](https://pi.dev), powered by [Ory](https://ory.com).
4
4
 
5
- **Security.** Pi runs real actions on your machine — editing files, running shell commands, calling APIs. The plugin gives every session a verifiable identity (you sign in once; Pi and any sub-agents it spawns each get their own), checks every tool call against permissions you control, and records each decision as an audit trace you can ship to your observability stack. It starts in watch mode so nothing is blocked on day one, and if Ory is ever unreachable it steps aside rather than locking you out.
5
+ Pi runs real actions on your machine — editing files, running shell commands, calling APIs. This plugin gives those actions an identity, a permission check, and an audit trail.
6
6
 
7
- **Developer experience.** A single command installs the plugin and walks you through connecting — choose Ory Network, a local Docker stack, or audit-only, and it wires up the project, sign-in client, login, and permissions for you. It also helps you build Ory into your own app: ask in plain language to scaffold login, registration, and recovery pages, run a local Ory, or manage identities and permissions through the bundled MCP server.
7
+ One command installs two independent halves:
8
+
9
+ - **Developer experience** — the Ory skill catalog, the local Ory dev stack, and activity logging. No account, no keys, nothing to configure: it works the moment it's installed.
10
+ - **Ory Agent Security** — browser sign-in, brokered permission checks before tool calls, and the delegation trail. Opt-in connection details from the [Ory Console](https://console.ory.sh) switch it on, and it takes nothing away from the half above. See [Connect to Ory Agent Security](#connect-to-ory-agent-security).
8
11
 
9
12
  ## What you'll need
10
13
 
@@ -21,29 +24,21 @@ npm install @ory/pi
21
24
 
22
25
  ## Get started
23
26
 
24
- Then run one command. It installs the plugin and walks you through connecting:
27
+ Then run one command. It installs the plugin.:
25
28
 
26
29
  ```bash
27
30
  npx -y -p @ory/pi ory-pi install
28
31
  ```
29
32
 
30
- The installer drops a tiny loader at `<project>/.pi/extensions/ory.js` so Pi discovers the plugin, adds the Ory skills under `<project>/.pi/skills`, and then opens a guided setup **in your browser** where you pick how to connect — the default takes just a click:
31
-
32
- - **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.
33
- - **Local** — run a complete Ory on your laptop with Docker. No account, no signup, no keys. Great for trying it out.
34
- - **Audit-only** — skip Ory entirely and just log what Pi does.
35
-
36
- > No browser available (CI, SSH, headless)? The same walkthrough runs right in your terminal instead — or force it with `--no-web`.
33
+ The installer drops a tiny loader at `<project>/.pi/extensions/ory.js` so Pi discovers the plugin and adds the Ory skills under `<project>/.pi/skills`. Add `--global` to install the loader for all projects (at `~/.pi/agent/extensions/ory.js`) instead of just this one.
37
34
 
38
- That's it. Confirm everything landed with:
35
+ Confirm everything landed with:
39
36
 
40
37
  ```bash
41
38
  npx -y -p @ory/pi ory-pi status
42
39
  ```
43
40
 
44
- `status` is your one-stop check: what's configured, who's signed in, which tools are covered by permissions, whether the loader is in place, and recent activity. Anything not set up yet shows as `(unset)`.
45
-
46
- Re-run install with `--reconfigure` to change your connection later, or `--no-configure` to skip the wizard. Add `--global` to install the loader for all projects (at `~/.pi/agent/extensions/ory.js`) instead of just this one.
41
+ `status` is your one-stop check: what's configured, who's signed in, which tools are covered by permissions, whether the loader is in place, and recent activity. Until you connect Agent Security, the identity and permission rows say so and name what's missing.
47
42
 
48
43
  <details>
49
44
  <summary>Prefer to let Pi manage the plugin?</summary>
@@ -54,17 +49,47 @@ Point Pi's own package installer at the npm package:
54
49
  pi install npm:@ory/pi
55
50
  ```
56
51
 
57
- This registers the extension through Pi's package manifest, but skips the guided setup — run `npx -y -p @ory/pi ory-pi install` (or `configure`) in a terminal afterwards to connect.
52
+ This registers the extension through Pi's package manifest. Run `npx -y -p @ory/pi ory-pi install` in a terminal afterwards to materialize the skills (and, later, `configure` if you want to connect).
58
53
 
59
54
  </details>
60
55
 
56
+ ## Skills and commands
57
+
58
+ Installing the plugin drops the full Ory playbook catalog into Pi. **Skills** are model-invoked — just say what you want in plain language and the matching one takes over.
59
+
60
+ | Skill | What it does for you |
61
+ |---|---|
62
+ | `ory-auth-setup` | Adds a complete auth system to your app — login, registration, recovery, verification, settings — on [Ory Elements](https://github.com/ory/elements) |
63
+ | `ory-login-flow` | Builds just the pages, wired to Ory's self-service flows |
64
+ | `ory-social-login` | "Sign in with…" for Google, GitHub, Apple, Microsoft, Discord, Slack, GitLab, Facebook |
65
+ | `ory-local-dev` | Develops and tests login/permission flows against a local Ory — no project, no account, offline |
66
+ | `ory-permissions-onboarding` | Walks a fresh install from observe mode to enforced per-tool permissions without getting blocked |
67
+ | `ory-build-agent` | Drops `@ory/argus` into an agent *you* own — Claude Agent SDK, OpenAI Agents, Mastra, Vercel AI, LangGraph/PydanticAI |
68
+ | `ory-build-integration` | Wires Ory into your app: Action webhooks, JWT validation at a gateway, live event streams |
69
+ | `ory-contribute-integration` | Authors and submits an integration to the public `ory/integrates` registry |
70
+ | `ory-e2b-sandbox` | Scaffolds an E2B sandbox template that boots with this plugin preinstalled |
71
+ | `ory-temporal-worker` | Scaffolds a Temporal TypeScript worker where every Activity is authenticated, authorized, and audited |
72
+
73
+ The local stack has its own playbooks — ask for them by name:
74
+
75
+ | Command | What it does |
76
+ |---|---|
77
+ | `ory-local-up` | Starts a local Ory (Identities, OAuth2, Permissions) in Docker and seeds a test user — it prints the email + password to sign in with |
78
+ | `ory-local-down` | Stops it, keeping your data volumes |
79
+ | `ory-temporal-up` | Starts a local Temporal dev server for the `ory-temporal-worker` scaffold |
80
+
81
+ The local stack runs entirely on your laptop: Ory APIs at `http://localhost:4000`, a login UI on `:4455` (not `:3000`, to dodge Next.js port clashes), and the Ory Console on `:4100`.
82
+
83
+ So: ask Pi *"add Ory login to this app"* and it scaffolds the pages, starts a local Ory, and wires them together.
84
+
61
85
  ## What you get
62
86
 
63
- Once connected, every tool Pi runs is governed by Ory — three things happen automatically:
87
+ Out of the box, every tool Pi runs produces a privacy-safe structured activity event in the unified local log.
64
88
 
65
- - **Who's driving.** You sign in once in your browser (a secure redirect flow — no passwords touch the plugin), and Pi gets its own identity that it registers automatically on first run. No tokens to copy around, and the "who did what" trail stays queryable later. (Pi's startup can't hard-block, so sign-in there is advisory — it still signs you in and logs it, but never stops the session.)
66
- - **What it's allowed to do.** Before a tool runs, Ory checks whether it's permitted. It starts in **watch mode** — nothing is blocked, you just *see* what would be — so it never gets in your way on day one.
67
- - **A record of everything.** Every decision (allowed, denied, skipped) is logged as a trace you can send to a viewer like Jaeger, or just a file.
89
+ Once you connect to Ory Agent Security, two more things happen automatically:
90
+
91
+ - **Who's driving.** You sign in once in your browser (a secure redirect flow — no passwords touch the plugin), and each Pi session gets its own identity that it registers automatically on first use. No tokens to copy around, and the "who did what" trail stays queryable later. (Pi's startup can't hard-block, so sign-in there is advisory — it still signs you in and logs it, but never stops the session.)
92
+ - **What it's allowed to do.** Before a tool runs, Ory checks whether it's permitted. It starts in **observe mode** — nothing is blocked, you just *see* what would be — so it never gets in your way on day one.
68
93
 
69
94
  If Ory is ever unreachable, the plugin gets out of the way and lets Pi keep working — so it can't lock you out.
70
95
 
@@ -72,59 +97,63 @@ If Ory is ever unreachable, the plugin gets out of the way and lets Pi keep work
72
97
 
73
98
  ### See what's happening
74
99
 
75
- Everything the plugin does is observable out of the box — no configuration required:
100
+ Everything the plugin does is observable out of the box:
76
101
 
77
- - **Status at a glance.** `npx -y -p @ory/pi ory-pi status` shows what's configured, who's signed in, how many built-in tools your permissions cover, and the most recent tool-call activity.
78
- - **Live dashboard.** `npx -y -p @ory/pi ory-pi 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.
79
- - **Live traces.** Every tool call is recorded as an OpenTelemetry-style span. Watch them stream as the agent works:
102
+ - **Activity log.** Privacy-safe activity is always appended to `~/.config/ory-agent-plugins/pi/ory-agent-debug.log`. View events, decisions, and errors live with:
80
103
 
81
104
  ```bash
82
105
  npx -y -p @ory/pi ory-pi watch
83
106
  ```
84
107
 
85
- Spans are also written to `~/.config/ory-agent-plugins/pi/ory-agent-trace.ndjson` (NDJSON, one span per line) — tail that file, or point `OTEL_EXPORTER_OTLP_ENDPOINT` at a collector to ship them straight to Jaeger, Honeycomb, or Grafana.
86
- - **Debug log.** For a verbose play-by-play, set `ORY_AGENT_DEBUG=true`; structured logs land in `~/.config/ory-agent-plugins/pi/ory-agent-debug.log`.
108
+ Set `ORY_AGENT_LOG_FILE` to override the path; set it empty to disable file persistence.
109
+ - **Live debug.** Launch Pi with `ORY_AGENT_DEBUG=true` to add verbose local diagnostics, including raw shell commands, to the watched log and stderr. Secrets are recursively redacted; pass `watch --json` for NDJSON.
87
110
 
88
111
  ### Ready to enforce?
89
112
 
90
- When the watch-mode logs look right, turn on blocking with one command (setup already granted you the built-in tools):
113
+ Once connected, the deny posture lives on the Ory project: when the observe-mode activity looks right, an admin promotes it to **enforce** in the **Ory Console** (Agent Security). Every session reads that posture live, so nothing has to be reinstalled.
91
114
 
92
115
  ```bash
93
- npx -y -p @ory/pi ory-pi permissions enforce
116
+ npx -y -p @ory/pi ory-pi permissions # what the project grants, and the live mode
94
117
  ```
95
118
 
96
- Now a denied tool is actually blocked and Pi shows why (the plugin returns `{ block: true, reason }` on Pi's `tool_call` event). 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.
97
-
98
- ## Also: add login to your own app
119
+ Then a denied tool is actually blocked and Pi shows why (the plugin returns `{ block: true, reason }` on Pi's `tool_call` event).
99
120
 
100
- Beyond securing Pi, the plugin helps you build Ory into whatever you're working on. Ask Pi *"add Ory login to this app"* and it scaffolds the login, registration, recovery, and settings pages (using [Ory Elements](https://github.com/ory/elements)) wired to a local Ory — no signup or keys needed. Start that local Ory by asking Pi *"start the local Ory stack"* (the `ory-local-up` skill) — it prints a test email + password to sign in with — and tear it down with `ory-local-down`.
121
+ ## Connect to Ory Agent Security
101
122
 
102
- Bundled **skills** (just ask in plain language) cover more: `ory-auth-setup`, `ory-login-flow`, `ory-social-login` (Google, GitHub, Apple…), `ory-permissions-onboarding`, and playbooks for wiring Ory into your own agents, E2B sandboxes, or Temporal workers.
123
+ Copy the connection details from the [Ory Console](https://console.ory.sh) under **Agent Security**:
103
124
 
104
- ## Configure by hand (CI / advanced)
105
-
106
- The guided setup covers most people. For scripted or CI setups, or to point at an existing Ory Network project, configure directly. Settings are saved to `~/.config/ory-agent-plugins/config.json` and shared across all your Ory agent plugins; environment variables win when both are set.
125
+ | Value | Flag | Environment variable |
126
+ |---|---|---|
127
+ | Project URL | `--project-url` | `ORY_PROJECT_URL` |
128
+ | Agent Security URL | `--agent-security-url` | `ORY_AGENT_SECURITY_URL` |
129
+ | Sign-in client id override (default `ory-agent-security-login`) | `--oauth2-client-id` | `ORY_OAUTH2_CLIENT_ID` |
107
130
 
108
131
  ```bash
109
132
  npx -y -p @ory/pi ory-pi configure \
110
133
  --project-url https://<slug>.projects.oryapis.com \
111
- --oauth2-client-id <sign-in client id>
134
+ --agent-security-url https://agents.console.ory.com
112
135
  ```
113
136
 
114
- Pi'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 public sign-in client, registered with the four loopback URLs `http://127.0.0.1:47823..47826/callback`; the guided setup makes it for you. For logging-only with no checks, use `--audit-only`. The interactive user login runs every session (always on, non-blocking) and only needs `ORY_OAUTH2_CLIENT_ID` to complete the browser flow. The equivalent env vars are `ORY_PROJECT_URL` and `ORY_OAUTH2_CLIENT_ID`.
137
+ `install` accepts the same flags, so you can register the plugin **and** connect in one shot (`install --project-url <URL> --agent-security-url <URL>`). The login client defaults to `ory-agent-security-login`; custom deployments can override it with `--oauth2-client-id`. Existing configurations fall back to the project URL when the Agent Security URL is unset. To turn sign-in and checks back off later, use `configure --disconnect`.
138
+
139
+ **Ory Network or OEL.** Either works. For an Ory Network project the URL is `https://<slug>.projects.oryapis.com`; for a self-hosted **Ory Enterprise License** deployment, point `--project-url` at that deployment's base URL and use the sign-in client id from its Agent Security configuration. Everything downstream — sign-in, checks, delegation — is identical.
140
+
141
+ **There is nothing else for you to create.** The sign-in client, the permission model, the per-tool grants and blocks, and the observe/enforce posture are all provisioned in the Console by someone with project access. At runtime the plugin only *reads* permissions — it has no write path into your project, which is why installing it needs no workspace privilege. Each Pi session registers its own identity automatically on first use.
142
+
143
+ Sign-in runs at the start of every session and never blocks — a declined, skipped, or timed-out login simply leaves that session without a user identity.
115
144
 
116
- With nothing configured, the plugin still loads and runs in **pass-through mode**: skills and logging work, but no checks run and nothing is blocked. Perfectly fine if you only want the app-building features.
145
+ Settings are saved to `~/.config/ory-agent-plugins/config.json` and shared across all your Ory agent plugins; environment variables win when both are set. Running headless with an OAuth2 access token already? Set `ORY_USER_OAUTH2_TOKEN` and the browser step is skipped entirely.
117
146
 
118
147
  ## Commands
119
148
 
120
149
  ```
121
- ory-pi install | uninstall Install/remove; --reconfigure re-runs setup, --no-web forces the terminal wizard, --no-configure skips it
150
+ ory-pi install | uninstall Install (--global for all projects; add --project-url to also connect Agent Security) / remove
122
151
  ory-pi status Show configuration, identities, permission coverage, recent activity
123
- ory-pi dashboard Open the live browser dashboard (status + service health + Change stack)
124
- ory-pi watch Tail the live trace stream (OTel spans)
125
- ory-pi permissions <cmd> status | bootstrap | observe (watch) | enforce (block)
126
- ory-pi configure <flags> Point at a project by hand (--project-url, --oauth2-client-id, --audit-only)
152
+ ory-pi permissions <cmd> status (read-only; grants + posture live in the Ory Console)
153
+ ory-pi configure <flags> Connect a project (--project-url) or --disconnect
127
154
  ory-pi agent <status|unregister> Manage Pi's own auto-created identity
155
+ ory-pi local <up|down|status|…> Run / manage a local Ory in Docker
156
+ ory-pi version Print plugin, core, and Node versions (--json for machine-readable)
128
157
  ```
129
158
 
130
159
  All prefixed with `npx -y -p @ory/pi`.
@@ -132,16 +161,14 @@ All prefixed with `npx -y -p @ory/pi`.
132
161
  ## Troubleshooting
133
162
 
134
163
  - **Local Ory won't start** — make sure Docker is running and ports `4000`, `4100`, `4455`, and `16686` are free.
135
- - **Browser sign-in loops** — reset with `ory-pi agent unregister` and try again.
136
- - **Install ran but never asked how to connect** — you're almost certainly on a stale `npx` cache. `npx -p @ory/pi` (no version pin) reuses a previously-downloaded copy instead of re-resolving to the latest, so an older CLI whose install predates the setup wizard can run while you believe you're on the current release. The install banner prints the running version; confirm it with `npx -y -p @ory/pi ory-pi version`. To force the current release, clear the cache and reinstall: `rm -rf ~/.npm/_npx` then `npx -y -p @ory/pi ory-pi install`. Pinning an exact version (`@ory/pi@<version>`) also bypasses the cached copy.
164
+ - **Browser sign-in loops** (after connecting) — reset with `ory-pi agent unregister` and try again.
165
+ - **Running an older CLI than expected** — `npx -p @ory/pi` (no version pin) reuses a previously-downloaded copy instead of re-resolving to the latest. The install banner prints the running version; confirm it with `npx -y -p @ory/pi ory-pi version`. To force the current release, clear the cache and reinstall: `rm -rf ~/.npm/_npx` then `npx -y -p @ory/pi ory-pi install`. Pinning an exact version (`@ory/pi@<version>`) also bypasses the cached copy.
137
166
  - **Pi can't find the plugin** — make sure `@ory/pi` is installed in the project (`npm install @ory/pi`) and the loader exists at `.pi/extensions/ory.js`; re-run the installer if not.
138
- - **Want to see what's happening** — `npx -y -p @ory/pi ory-pi status` for a snapshot, `npx -y -p @ory/pi ory-pi watch` for the live trace stream, or set `ORY_AGENT_DEBUG=true` for a verbose log. Traces and logs live under `~/.config/ory-agent-plugins/pi/` (see [See what's happening](#see-whats-happening)).
139
167
 
140
168
  ## Learn more
141
169
 
142
170
  - [Ory documentation](https://www.ory.com/docs/) · [Ory Console](https://console.ory.sh) · [Ory Elements](https://github.com/ory/elements)
143
171
  - [Pi repository](https://github.com/badlogic/pi-mono)
144
- - [Repo README](../../README.md) and [AGENTS.md](../../AGENTS.md) — full env-var and permission-mode reference
145
172
 
146
173
  ## License
147
174
 
@@ -6,16 +6,23 @@
6
6
  * placed there is loaded via jiti and its default export is invoked as the
7
7
  * extension factory. A bare module id in `settings.json`'s `extensions` array is
8
8
  * NOT loaded — those entries are resolved as filesystem paths. So we drop a
9
- * tiny loader file that re-exports the installed `@ory/pi` package's default
10
- * factory; Pi discovers the file, jiti resolves `@ory/pi` from `node_modules`,
11
- * and the factory runs. No `settings.json` entry is required.
9
+ * tiny loader file that re-exports the runtime's default factory by absolute
10
+ * path; Pi discovers the file and jiti loads that path directly. Re-exporting
11
+ * the bare module id would instead depend on `@ory/pi` being resolvable from
12
+ * wherever Pi happens to run, which a project-local `node_modules` does not
13
+ * guarantee. No `settings.json` entry is required.
12
14
  *
13
15
  * Verified against @earendil-works/pi-coding-agent v0.80.2 (the extension
14
16
  * loader's `discoverAndLoadExtensions` / `discoverExtensionsInDir`).
15
17
  */
16
18
  export declare const EXTENSION_MODULE = "@ory/pi";
17
19
  export declare const LOADER_FILENAME = "ory.js";
18
- export declare const LOADER_CONTENTS = "// Ory extension loader for Pi. Re-exports @ory/pi's default factory so Pi's\n// extension auto-discovery (which scans .pi/extensions/ for *.js files) can\n// load it. Generated by 'ory-pi install' / 'ory-pi-setup' \u2014 safe to delete.\nexport { default } from \"@ory/pi\";\n";
20
+ /**
21
+ * Loader body: re-export the resolved runtime's default factory by absolute
22
+ * path, so Pi's auto-discovery (which scans `.pi/extensions/` for `*.js`)
23
+ * loads exactly the copy this install resolved.
24
+ */
25
+ export declare function loaderContents(entryPath: string): string;
19
26
  /** Resolve the `.pi/extensions/` dir for the requested scope. */
20
27
  export declare function getExtensionsDir(opts: {
21
28
  global?: boolean;
@@ -30,6 +37,8 @@ export declare function getLoaderPath(opts: {
30
37
  export declare function writeOryLoader(opts: {
31
38
  global?: boolean;
32
39
  projectDir: string;
40
+ /** Absolute path to the runtime module the loader re-exports. */
41
+ entryPath: string;
33
42
  }): string;
34
43
  /**
35
44
  * Remove the loader file (and the `.pi/extensions/` dir if it's left empty).
@@ -7,9 +7,11 @@
7
7
  * placed there is loaded via jiti and its default export is invoked as the
8
8
  * extension factory. A bare module id in `settings.json`'s `extensions` array is
9
9
  * NOT loaded — those entries are resolved as filesystem paths. So we drop a
10
- * tiny loader file that re-exports the installed `@ory/pi` package's default
11
- * factory; Pi discovers the file, jiti resolves `@ory/pi` from `node_modules`,
12
- * and the factory runs. No `settings.json` entry is required.
10
+ * tiny loader file that re-exports the runtime's default factory by absolute
11
+ * path; Pi discovers the file and jiti loads that path directly. Re-exporting
12
+ * the bare module id would instead depend on `@ory/pi` being resolvable from
13
+ * wherever Pi happens to run, which a project-local `node_modules` does not
14
+ * guarantee. No `settings.json` entry is required.
13
15
  *
14
16
  * Verified against @earendil-works/pi-coding-agent v0.80.2 (the extension
15
17
  * loader's `discoverAndLoadExtensions` / `discoverExtensionsInDir`).
@@ -48,7 +50,8 @@ var __importStar = (this && this.__importStar) || (function () {
48
50
  };
49
51
  })();
50
52
  Object.defineProperty(exports, "__esModule", { value: true });
51
- exports.LOADER_CONTENTS = exports.LOADER_FILENAME = exports.EXTENSION_MODULE = void 0;
53
+ exports.LOADER_FILENAME = exports.EXTENSION_MODULE = void 0;
54
+ exports.loaderContents = loaderContents;
52
55
  exports.getExtensionsDir = getExtensionsDir;
53
56
  exports.getLoaderPath = getLoaderPath;
54
57
  exports.writeOryLoader = writeOryLoader;
@@ -58,11 +61,19 @@ const os = __importStar(require("node:os"));
58
61
  const path = __importStar(require("node:path"));
59
62
  exports.EXTENSION_MODULE = "@ory/pi";
60
63
  exports.LOADER_FILENAME = "ory.js";
61
- exports.LOADER_CONTENTS = `// Ory extension loader for Pi. Re-exports @ory/pi's default factory so Pi's
62
- // extension auto-discovery (which scans .pi/extensions/ for *.js files) can
63
- // load it. Generated by 'ory-pi install' / 'ory-pi-setup' — safe to delete.
64
- export { default } from "@ory/pi";
64
+ /**
65
+ * Loader body: re-export the resolved runtime's default factory by absolute
66
+ * path, so Pi's auto-discovery (which scans `.pi/extensions/` for `*.js`)
67
+ * loads exactly the copy this install resolved.
68
+ */
69
+ function loaderContents(entryPath) {
70
+ return `// Ory extension loader for Pi. Re-exports the Ory runtime's default factory
71
+ // so Pi's extension auto-discovery (which scans .pi/extensions/ for *.js
72
+ // files) can load it. Generated by 'ory-pi install' / 'ory-pi-setup' — safe to
73
+ // delete. Regenerate by re-running install.
74
+ export { default } from ${JSON.stringify(entryPath)};
65
75
  `;
76
+ }
66
77
  /** Resolve the `.pi/extensions/` dir for the requested scope. */
67
78
  function getExtensionsDir(opts) {
68
79
  return opts.global
@@ -78,7 +89,7 @@ function writeOryLoader(opts) {
78
89
  const dir = getExtensionsDir(opts);
79
90
  const loaderPath = path.join(dir, exports.LOADER_FILENAME);
80
91
  fs.mkdirSync(dir, { recursive: true });
81
- fs.writeFileSync(loaderPath, exports.LOADER_CONTENTS);
92
+ fs.writeFileSync(loaderPath, loaderContents(opts.entryPath));
82
93
  return loaderPath;
83
94
  }
84
95
  /**
package/dist/cli/main.js CHANGED
@@ -51,8 +51,18 @@ const loader_js_1 = require("./loader.js");
51
51
  const PACKAGE_ROOT = path.resolve(__dirname, "..", "..");
52
52
  function main() {
53
53
  const [command, ...args] = process.argv.slice(2);
54
+ // `--help` after a matched subcommand: print usage, do nothing else.
55
+ // Without this the switch below ignores the flag and `install --help`
56
+ // performs a real install (#221).
57
+ if ((0, argus_1.shouldPrintHelp)(command, args))
58
+ return help();
54
59
  switch (command) {
55
60
  case "install":
61
+ if (args.includes("--print")) {
62
+ process.argv = ["node", "setup.js", ...args];
63
+ require("./setup.js");
64
+ break;
65
+ }
56
66
  (0, argus_1.beginDeferNextSteps)();
57
67
  install(args);
58
68
  (0, argus_1.runPostInstall)("ory-pi", "pi", args).then(() => process.exit(0), (err) => {
@@ -62,7 +72,10 @@ function main() {
62
72
  break;
63
73
  case "uninstall":
64
74
  uninstall(args);
65
- (0, argus_1.clearCredentialsForUninstall)().then(() => process.exit(0), (err) => {
75
+ (0, argus_1.clearCredentialsForUninstall)({
76
+ harness: "pi",
77
+ purge: args.includes("--purge"),
78
+ }).then(() => process.exit(0), (err) => {
66
79
  console.error(err.message ?? err);
67
80
  process.exit(1);
68
81
  });
@@ -71,7 +84,7 @@ function main() {
71
84
  (0, argus_1.runConfigureCommand)("ory-pi", args);
72
85
  break;
73
86
  case "agent":
74
- (0, argus_1.runAgentCommand)("ory-pi", args).then((code) => process.exit(code), (err) => {
87
+ (0, argus_1.runAgentCommand)("ory-pi", "pi", args).then((code) => process.exit(code), (err) => {
75
88
  console.error(err.message ?? err);
76
89
  process.exit(1);
77
90
  });
@@ -88,17 +101,11 @@ function main() {
88
101
  process.exit(1);
89
102
  });
90
103
  break;
91
- case "local":
92
- (0, argus_1.runLocalCommand)("ory-pi", args).catch((err) => {
93
- console.error(err.message ?? err);
94
- process.exit(1);
95
- });
96
- break;
97
104
  case "watch":
98
- (0, argus_1.runWatchCommand)("pi", args);
105
+ (0, argus_1.runWatchCommand)("ory-pi", "pi", args).then((code) => process.exit(code), (err) => { console.error(err.message ?? err); process.exit(1); });
99
106
  break;
100
- case "dashboard":
101
- (0, argus_1.runDashboardCommand)("ory-pi", "pi", args).then((code) => process.exit(code), (err) => {
107
+ case "local":
108
+ (0, argus_1.runLocalCommand)("ory-pi", args).catch((err) => {
102
109
  console.error(err.message ?? err);
103
110
  process.exit(1);
104
111
  });
@@ -132,23 +139,48 @@ function isGlobal(args) {
132
139
  // Pi auto-discovers extensions from `<cwd>/.pi/extensions/` (project) and
133
140
  // `~/.pi/agent/extensions/` (global): each `*.js`/`*.ts` file there is loaded
134
141
  // via jiti and its default export is the factory. We drop a loader file that
135
- // re-exports @ory/pi's default factory. No settings.json entry is needed.
142
+ // re-exports the resolved runtime's default factory by absolute path. No
143
+ // settings.json entry is needed.
136
144
  // Verified against @earendil-works/pi-coding-agent v0.80.2. See ./loader.ts.
137
145
  function loaderOpts(args) {
138
146
  return { global: isGlobal(args), projectDir: parseProjectDir(args) };
139
147
  }
140
148
  function install(args) {
141
149
  const projectDir = parseProjectDir(args);
142
- const loaderPath = (0, loader_js_1.writeOryLoader)(loaderOpts(args));
150
+ // Pi loads the extension in-process, so there is no hook subprocess and no
151
+ // hook shim: the loader re-exports the runtime's main module directly. Pi
152
+ // also excludes MCP by design, so no MCP server is registered.
153
+ const runtime = (0, argus_1.wireRuntime)({
154
+ harness: "pi",
155
+ packageName: loader_js_1.EXTENSION_MODULE,
156
+ packageRoot: PACKAGE_ROOT,
157
+ installCommand: "npx -y -p @ory/pi ory-pi install",
158
+ args,
159
+ entryRelPath: "dist/index.js",
160
+ inProcess: true,
161
+ includeMcp: false,
162
+ });
163
+ console.log(runtime.target.kind === "linked"
164
+ ? `Runtime: linked to ${runtime.target.packageDir} (dev)`
165
+ : `Runtime: ${runtime.target.packageName}@${runtime.target.version} in ${runtime.target.storeDir}`);
166
+ for (const pruned of runtime.prunedStores) {
167
+ console.log(` Removed stale runtime: ${pruned}`);
168
+ }
169
+ const entryPath = path.join(runtime.target.packageDir, "dist", "index.js");
170
+ const loaderPath = (0, loader_js_1.writeOryLoader)({ ...loaderOpts(args), entryPath });
143
171
  (0, assets_js_1.installPiOryAssets)(projectDir);
144
172
  console.log(`Ory extension loader installed at ${loaderPath}`);
145
- console.log(` Re-exports the default factory from ${loader_js_1.EXTENSION_MODULE}`);
173
+ console.log(` Re-exports the default factory from ${entryPath}`);
146
174
  console.log(` Skills + commands installed under ${path.join(projectDir, ".pi", "skills")}`);
147
175
  (0, argus_1.printEnvHelp)("ory-pi");
148
176
  }
149
177
  function uninstall(args) {
150
178
  const projectDir = parseProjectDir(args);
151
179
  const removed = (0, loader_js_1.removeOryLoader)(loaderOpts(args));
180
+ (0, argus_1.removeRuntimeWiring)("pi");
181
+ for (const pruned of (0, argus_1.pruneRuntimeStores)()) {
182
+ console.log(` Removed runtime: ${pruned}`);
183
+ }
152
184
  console.log(removed
153
185
  ? "Ory extension loader removed."
154
186
  : "No Ory extension loader found. Nothing to uninstall.");
@@ -183,24 +215,22 @@ Commands:
183
215
  uninstall [--project-dir <path>] [--global] Remove the Ory extension loader
184
216
  configure Set or view Ory project URL and API key
185
217
  agent <status|unregister> Manage the agent's OAuth2 (DCR) identity
186
- permissions <cmd> Manage permission mode and tool permissions
187
- (status, bootstrap, observe, enforce)
218
+ permissions Show the live permission mode and per-tool coverage
188
219
  local <cmd> Manage local Ory dev environment
189
220
  (up, down, status, seed, logs, env, configure, reset)
190
221
  status [--project-dir <path>] Show plugin configuration and status
191
- dashboard Open the Ory Agent dashboard (system health + configuration)
192
- watch [trace-file] Watch live trace output
222
+ watch [--json] [--lines <count>] Follow the live activity/debug log
193
223
  version Show version and the ory-agent-plugins build commit
194
224
 
195
225
  After installing, the plugin hooks into these Pi lifecycle events:
196
226
  session-start (factory body) Verify session / resolve agent identity via Ory
197
227
  tool_call Check tool permissions via Ory Permissions (can block)
198
- tool_result Record tool completion trace
228
+ tool_result Record tool completion activity
199
229
 
200
230
  Examples:
201
231
  npx -y -p @ory/pi ory-pi install
202
- npx -y -p @ory/pi ory-pi configure --project-url https://<slug>.projects.oryapis.com --api-key ory_pat_...
203
- npx -y -p @ory/pi ory-pi permissions bootstrap
232
+ npx -y -p @ory/pi ory-pi configure --project-url https://<slug>.projects.oryapis.com --agent-security-url https://agents.console.ory.com
233
+ npx -y -p @ory/pi ory-pi permissions
204
234
  npx -y -p @ory/pi ory-pi local up
205
235
  npx -y -p @ory/pi ory-pi status
206
236
  `);
package/dist/cli/setup.js CHANGED
@@ -77,11 +77,16 @@ function main() {
77
77
  }
78
78
  const args = (0, argus_1.parseSetupArgs)({ supportsGlobal: true });
79
79
  if (args.print) {
80
- console.log(loader_js_1.LOADER_CONTENTS);
80
+ // Nothing is resolved or written on a --print run.
81
+ console.log((0, loader_js_1.loaderContents)("<resolved runtime>/dist/index.js"));
81
82
  return;
82
83
  }
83
84
  if (args.uninstall) {
84
85
  const removed = (0, loader_js_1.removeOryLoader)(args);
86
+ (0, argus_1.removeRuntimeWiring)("pi");
87
+ for (const pruned of (0, argus_1.pruneRuntimeStores)()) {
88
+ console.log(` Removed runtime: ${pruned}`);
89
+ }
85
90
  console.log(removed
86
91
  ? "Removed Ory extension loader."
87
92
  : "No Ory extension loader found. Nothing to uninstall.");
@@ -89,7 +94,20 @@ function main() {
89
94
  console.log(`Removed Ory skills from ${path.join(args.projectDir, ".pi", "skills")}`);
90
95
  return;
91
96
  }
92
- const loaderPath = (0, loader_js_1.writeOryLoader)(args);
97
+ const runtime = (0, argus_1.wireRuntime)({
98
+ harness: "pi",
99
+ packageName: "@ory/pi",
100
+ packageRoot: path.resolve(__dirname, "..", ".."),
101
+ installCommand: "npx -y -p @ory/pi ory-pi install",
102
+ args: process.argv.slice(2),
103
+ entryRelPath: "dist/index.js",
104
+ inProcess: true,
105
+ includeMcp: false,
106
+ });
107
+ const loaderPath = (0, loader_js_1.writeOryLoader)({
108
+ ...args,
109
+ entryPath: path.join(runtime.target.packageDir, "dist", "index.js"),
110
+ });
93
111
  (0, assets_js_1.installPiOryAssets)(args.projectDir);
94
112
  console.log(`Ory extension loader installed at ${loaderPath}`);
95
113
  console.log(`Ory skills installed to ${path.join(args.projectDir, ".pi", "skills")}`);
@@ -103,7 +121,10 @@ main();
103
121
  // stored Ory credentials. When required by the plugin's main CLI, that
104
122
  // command owns the purge, so the `require.main` guard prevents a double run.
105
123
  if (require.main === module && process.argv.includes("--uninstall")) {
106
- (0, argus_1.clearCredentialsForUninstall)().then(() => process.exit(0), (err) => {
124
+ (0, argus_1.clearCredentialsForUninstall)({
125
+ harness: "pi",
126
+ purge: process.argv.includes("--purge"),
127
+ }).then(() => process.exit(0), (err) => {
107
128
  console.error(err.message ?? err);
108
129
  process.exit(1);
109
130
  });
package/dist/plugin.d.ts CHANGED
@@ -1,43 +1,11 @@
1
- /**
2
- * Pi in-process extension (auth + blocking permission gate + tracing).
3
- *
4
- * Pi loads a default-exported factory `(pi) => void | Promise<void>` from a
5
- * TypeScript extension module (via jiti) and awaits it before startup
6
- * continues. We treat the factory body as the session-start hook: it runs the
7
- * user gate (which runs every session and never blocks — enforcement is
8
- * governed solely by permissionMode at tool-call time), the agent gate, and
9
- * writes the user→agent delegation tuple.
10
- *
11
- * The blocking permission decision lives in the `tool_call` hook, which CAN
12
- * block by returning `{ block: true, reason }` (like OpenClaw's
13
- * `before_tool_call`). The `tool_result` hook records the `tool.complete`
14
- * audit span after a tool runs.
15
- *
16
- * Pi excludes MCP and sub-agents by design, so there is NO MCP tool parsing
17
- * and NO sub-agent identity path.
18
- *
19
- * Contract verified against @earendil-works/pi-coding-agent v0.80.2 (dist
20
- * core/extensions/types.d.ts and the extension loader): the `tool_call`
21
- * handler returns `ToolCallEventResult { block?: boolean; reason? }`,
22
- * `tool_result` carries `isError`/`content`, and Pi awaits the async factory
23
- * default export.
24
- */
25
- import { OryAgentClient, ensureUserAuthenticated, ensureAgentIdentity } from "@ory/argus";
1
+ import { OryAgentClient, ensureAgentIdentity, ensureUserAuthenticated } from "@ory/argus";
26
2
  import type { PiExtension } from "./types.js";
27
3
  export interface CreateOryPluginDeps {
28
- /** Test injection point for the user login flow. */
29
4
  userLogin?: typeof ensureUserAuthenticated;
30
- /** Test injection point for the agent identity gate. */
31
5
  agentGate?: typeof ensureAgentIdentity;
6
+ /** Pin the per-process id for deterministic tests. Production mints one. */
7
+ sessionId?: string;
32
8
  }
33
- /**
34
- * Create the Ory extension factory for Pi.
35
- *
36
- * Returns a function matching Pi's `PiExtension` contract: it receives the
37
- * ExtensionAPI, runs the session-start auth gates in the body, and registers
38
- * the `tool_call` (blocking) and `tool_result` (tracing) hooks.
39
- */
40
9
  export declare function createOryPlugin(clientOrConfig?: OryAgentClient | {
41
10
  projectUrl: string;
42
- apiKey?: string;
43
11
  }, deps?: CreateOryPluginDeps): PiExtension;
package/dist/plugin.js CHANGED
@@ -1,217 +1,64 @@
1
1
  "use strict";
2
- /**
3
- * Pi in-process extension (auth + blocking permission gate + tracing).
4
- *
5
- * Pi loads a default-exported factory `(pi) => void | Promise<void>` from a
6
- * TypeScript extension module (via jiti) and awaits it before startup
7
- * continues. We treat the factory body as the session-start hook: it runs the
8
- * user gate (which runs every session and never blocks — enforcement is
9
- * governed solely by permissionMode at tool-call time), the agent gate, and
10
- * writes the user→agent delegation tuple.
11
- *
12
- * The blocking permission decision lives in the `tool_call` hook, which CAN
13
- * block by returning `{ block: true, reason }` (like OpenClaw's
14
- * `before_tool_call`). The `tool_result` hook records the `tool.complete`
15
- * audit span after a tool runs.
16
- *
17
- * Pi excludes MCP and sub-agents by design, so there is NO MCP tool parsing
18
- * and NO sub-agent identity path.
19
- *
20
- * Contract verified against @earendil-works/pi-coding-agent v0.80.2 (dist
21
- * core/extensions/types.d.ts and the extension loader): the `tool_call`
22
- * handler returns `ToolCallEventResult { block?: boolean; reason? }`,
23
- * `tool_result` carries `isError`/`content`, and Pi awaits the async factory
24
- * default export.
25
- */
26
2
  Object.defineProperty(exports, "__esModule", { value: true });
27
3
  exports.createOryPlugin = createOryPlugin;
4
+ const node_crypto_1 = require("node:crypto");
28
5
  const argus_1 = require("@ory/argus");
29
- /**
30
- * Create the Ory extension factory for Pi.
31
- *
32
- * Returns a function matching Pi's `PiExtension` contract: it receives the
33
- * ExtensionAPI, runs the session-start auth gates in the body, and registers
34
- * the `tool_call` (blocking) and `tool_result` (tracing) hooks.
35
- */
6
+ const HARNESS = "pi";
36
7
  function createOryPlugin(clientOrConfig, deps = {}) {
37
8
  return async (pi) => {
38
9
  const client = clientOrConfig instanceof argus_1.OryAgentClient
39
10
  ? clientOrConfig
40
11
  : clientOrConfig
41
- ? new argus_1.OryAgentClient({ ...clientOrConfig, harness: "pi" })
42
- : argus_1.OryAgentClient.fromEnv("pi");
43
- // The factory body IS the session-start hook: Pi awaits async extension
44
- // factories before startup continues (verified against
45
- // @earendil-works/pi-coding-agent v0.80.2). Pi also exposes a dedicated
46
- // `session_start` event, but the factory body is sufficient and runs
47
- // exactly once per process.
48
- await runSessionStart(client, deps);
49
- pi.on("tool_call", createToolCallHandler(client));
50
- pi.on("tool_result", createToolResultHandler(client));
51
- };
52
- }
53
- // ─── session start (factory body) ──────────────────────────────────
54
- async function runSessionStart(client, deps) {
55
- client.logger.info("lifecycle.session_start", { harness: "pi" });
56
- client.tracer.setContext({ traceId: (0, argus_1.deriveTraceId)("pi"), sessionId: "pi" });
57
- client.tracer.record("session.start", "ok", { attributes: {} });
58
- // Run the user login. It runs every session and never blocks the session
59
- // from starting — the flow emits the user.auth audit span, refreshes tokens,
60
- // and may prompt when interactive. Enforcement is governed solely by
61
- // permissionMode at tool-call time.
62
- const userGate = deps.userLogin ?? argus_1.ensureUserAuthenticated;
63
- await userGate(client, {
64
- binName: "ory-pi",
65
- harness: "pi",
66
- });
67
- // Resolve the agent identity (machine credentials). Never blocks; attaches
68
- // the agent's bearer token to outgoing Ory API calls.
69
- const agentGate = deps.agentGate ?? argus_1.ensureAgentIdentity;
70
- await agentGate(client, { projectUrl: (0, argus_1.resolveConfig)().projectUrl, harness: "pi" });
71
- // Once both principals are populated, write the user→agent delegation tuple.
72
- // Written at most once per install + fail-open: audit-trail data only.
73
- await (0, argus_1.writeUserDelegatesAgent)(client);
74
- }
75
- // ─── tool_call (pre-execution, blocking) ───────────────────────────
76
- function createToolCallHandler(client) {
77
- return async (event) => {
78
- const toolName = event.toolName ?? "unknown";
79
- // Pi does not put a session id on the tool_call event; the per-process
80
- // trace context set at session start ("pi") carries correlation. We tag
81
- // the span with the toolCallId so a tool_call lines up with its later
82
- // tool_result.
83
- client.logger.info("lifecycle.tool_call", { toolName, toolCallId: event.toolCallId });
84
- client.tracer.setContext({ traceId: (0, argus_1.deriveTraceId)("pi"), sessionId: "pi" });
85
- const inputSummary = (0, argus_1.summarizeToolInput)(toolName, event.input);
86
- // In audit-only mode, log the invocation but skip permission checks.
87
- if ((0, argus_1.resolveConfig)().auditOnly) {
88
- client.tracer.record("tool.invoke", "ok", {
89
- attributes: { toolName, ...inputSummary },
90
- });
91
- return;
92
- }
93
- // If Ory is not configured, pass through.
94
- if (!(0, argus_1.resolveConfig)().projectUrl) {
95
- client.tracer.record("tool.invoke", "skipped", {
96
- attributes: { toolName, reason: "not_configured", ...inputSummary },
97
- });
98
- return;
99
- }
100
- const subject = (0, argus_1.resolveUserSubject)(client, "agent:pi");
101
- const subjectId = (0, argus_1.subjectLabel)(subject);
102
- const namespace = resolveNamespace();
103
- try {
104
- const outcome = await (0, argus_1.gateToolCall)(client, {
105
- harness: "pi",
106
- toolName,
107
- check: { namespace, object: toolName, relation: "use", ...subject },
108
- spanAttributes: { toolName },
12
+ ? new argus_1.OryAgentClient({ ...clientOrConfig, harness: HARNESS })
13
+ : argus_1.OryAgentClient.fromEnv(HARNESS);
14
+ // Pi events have no session id, so one process UUID identifies the run.
15
+ const sessionId = deps.sessionId ?? (0, node_crypto_1.randomUUID)();
16
+ await (0, argus_1.withHookContext)(client, { sessionId }, async () => {
17
+ client.logger.info("lifecycle.session_start", { harness: HARNESS });
18
+ await (0, argus_1.sessionStart)(client, {
19
+ harness: HARNESS,
20
+ binName: "ory-pi",
21
+ userLogin: deps.userLogin,
22
+ agentGate: deps.agentGate,
109
23
  });
110
- // Interactive tools (operator-extensible via ORY_INTERACTIVE_TOOLS):
111
- // the user.interaction span is already recorded; pass through.
112
- if (outcome.kind === "interactive") {
113
- return;
114
- }
115
- const decision = outcome;
116
- if (decision.kind === "fail_open") {
117
- handlePermissionError(decision.error, toolName, client);
118
- return;
119
- }
120
- const attrs = { toolName, ...inputSummary };
121
- const decisionAttrs = decision.spanAttributes;
122
- if (decision.kind === "allow") {
123
- client.tracer.record("tool.invoke", "ok", {
124
- attributes: { ...attrs, ...decisionAttrs, allowed: true },
125
- });
126
- return;
127
- }
128
- if (decision.kind === "observe") {
129
- client.tracer.record("tool.block", "denied", {
130
- attributes: { ...attrs, ...decisionAttrs, allowed: false, ...(0, argus_1.alertAttributes)(false) },
131
- });
132
- client.tracer.record("tool.invoke", "ok", {
133
- attributes: { ...attrs, ...decisionAttrs, allowed: false, observed: true },
134
- });
135
- return;
136
- }
137
- // decision.kind === "deny" — enforce: block the tool call.
138
- client.tracer.record("tool.block", "denied", {
139
- attributes: { ...attrs, ...decisionAttrs, allowed: false, ...(0, argus_1.alertAttributes)(true) },
140
- });
141
- const reason = (0, argus_1.formatDenialMessage)({
142
- tool: toolName,
143
- subjectId,
144
- namespace,
145
- });
146
- client.logger.warn("tool.denied", {
147
- toolName,
148
- subjectId,
149
- message: reason,
150
- });
151
- return { block: true, reason };
152
- }
153
- catch (err) {
154
- handlePermissionError(err, toolName, client);
155
- // Fail open — allow the tool call.
156
- }
24
+ });
25
+ pi.on("tool_call", createToolCallHandler(client, sessionId));
26
+ pi.on("tool_result", createToolResultHandler(client, sessionId));
157
27
  };
158
28
  }
159
- // ─── tool_result (post-execution, tracing) ─────────────────────────
160
- function createToolResultHandler(client) {
161
- return async (event) => {
29
+ function createToolCallHandler(client, sessionId) {
30
+ return (event) => (0, argus_1.withHookContext)(client, { sessionId }, async () => {
162
31
  const toolName = event.toolName ?? "unknown";
163
- client.tracer.setContext({ traceId: (0, argus_1.deriveTraceId)("pi"), sessionId: "pi" });
164
- // Pi reports failure via the `isError` boolean and the result payload via
165
- // `content` (verified shape), not `error`/`result`.
166
- const isError = event.isError === true;
167
- client.logger.info("lifecycle.tool_result", {
32
+ client.logger.info("lifecycle.tool_call", {
168
33
  toolName,
169
34
  toolCallId: event.toolCallId,
170
- isError,
171
35
  });
172
- client.tracer.record("tool.complete", isError ? "error" : "ok", {
173
- attributes: {
174
- toolName,
175
- ...(0, argus_1.summarizeToolInput)(toolName, event.input),
176
- ...(0, argus_1.summarizeToolOutput)(toolName, event.content),
177
- ...(isError ? { error: true } : {}),
178
- },
36
+ const result = await (0, argus_1.gate)(client, {
37
+ harness: HARNESS,
38
+ toolName,
39
+ toolArgs: event.input,
40
+ subjectFallback: "agent:pi",
41
+ canBlock: true,
179
42
  });
180
- };
181
- }
182
- // ─── Helpers ───────────────────────────────────────────────────────
183
- function resolveNamespace() {
184
- return process.env.ORY_PERMISSION_NAMESPACE ?? "AgentTools";
185
- }
186
- function isOryError(err) {
187
- return (typeof err === "object" &&
188
- err !== null &&
189
- "code" in err &&
190
- "message" in err);
43
+ if (result.blocked)
44
+ return { block: true, reason: result.denialMessage };
45
+ });
191
46
  }
192
- function handlePermissionError(err, toolName, client) {
193
- if (isOryError(err)) {
194
- if (err.code === "network_error" || err.code === "rate_limited") {
195
- client.logger.warn("permission.fallback", {
196
- toolName,
197
- code: err.code,
198
- message: "Failing open",
199
- });
200
- return;
201
- }
202
- client.logger.error("permission.check.error", {
47
+ function createToolResultHandler(client, sessionId) {
48
+ return (event) => (0, argus_1.withHookContext)(client, { sessionId }, async () => {
49
+ const toolName = event.toolName ?? "unknown";
50
+ const isError = event.isError === true;
51
+ client.logger.info("lifecycle.tool_result", {
203
52
  toolName,
204
- code: err.code,
205
- message: err.message,
53
+ toolCallId: event.toolCallId,
54
+ isError,
206
55
  });
207
- }
208
- else {
209
- client.logger.error("permission.check.error", {
56
+ (0, argus_1.complete)(client, {
210
57
  toolName,
211
- error: err instanceof Error ? err.message : String(err),
58
+ input: event.input,
59
+ output: event.content,
60
+ status: isError ? "error" : "ok",
61
+ extraActivityAttributes: isError ? { error: true } : undefined,
212
62
  });
213
- }
214
- // Fail-open is log-and-allow: no tool.invoke span, matching the core
215
- // gate() vocabulary. The errored permission.check span carries the audit
216
- // trail for the failed check itself.
63
+ });
217
64
  }
package/dist/types.d.ts CHANGED
@@ -10,7 +10,7 @@
10
10
  * Tool hooks are registered with `pi.on(event, handler)`. The pre-execution
11
11
  * `tool_call` hook BLOCKS a tool by returning `{ block: true, reason }`;
12
12
  * returning nothing/undefined allows. A best-effort `tool_result` hook fires
13
- * after a tool completes and is used for audit tracing.
13
+ * after a tool completes and is used for activity auditing.
14
14
  *
15
15
  * Pi deliberately excludes MCP and sub-agents, so there is no MCP tool
16
16
  * parsing and no sub-agent identity path.
package/dist/types.js CHANGED
@@ -11,7 +11,7 @@
11
11
  * Tool hooks are registered with `pi.on(event, handler)`. The pre-execution
12
12
  * `tool_call` hook BLOCKS a tool by returning `{ block: true, reason }`;
13
13
  * returning nothing/undefined allows. A best-effort `tool_result` hook fires
14
- * after a tool completes and is used for audit tracing.
14
+ * after a tool completes and is used for activity auditing.
15
15
  *
16
16
  * Pi deliberately excludes MCP and sub-agents, so there is no MCP tool
17
17
  * parsing and no sub-agent identity path.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ory/pi",
3
- "version": "0.14.0",
3
+ "version": "1.0.1",
4
4
  "description": "Ory plugin for Pi (the minimal coding agent): 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",
@@ -37,8 +37,7 @@
37
37
  "agent-security",
38
38
  "guardrails",
39
39
  "llm",
40
- "tracing",
41
- "distributed-tracing",
40
+ "activity-auditing",
42
41
  "observability",
43
42
  "kratos",
44
43
  "keto",
@@ -49,6 +48,10 @@
49
48
  "registry": "https://registry.npmjs.org/",
50
49
  "provenance": true
51
50
  },
51
+ "reova": {
52
+ "enabled": true,
53
+ "endpoint": "https://telemetry.reo.dev/data"
54
+ },
52
55
  "main": "dist/index.js",
53
56
  "types": "dist/index.d.ts",
54
57
  "exports": {
@@ -68,12 +71,11 @@
68
71
  },
69
72
  "files": [
70
73
  "dist",
71
- "!dist/dev",
72
74
  "!dist/**/*.tsbuildinfo"
73
75
  ],
74
76
  "dependencies": {
75
- "reo-census": "^1.2.8",
76
- "@ory/argus": "0.14.0"
77
+ "reova": "^0.7.0",
78
+ "@ory/argus": "1.0.1"
77
79
  },
78
80
  "devDependencies": {
79
81
  "typescript": "^6.0.2",
@@ -87,7 +89,6 @@
87
89
  "clean": "rm -rf dist *.tsbuildinfo",
88
90
  "test": "vitest run",
89
91
  "test:watch": "vitest",
90
- "typecheck": "tsc --noEmit",
91
- "dev": "node dist/dev/launcher.js"
92
+ "typecheck": "tsc --noEmit"
92
93
  }
93
94
  }