@ory/cline 0.13.9 → 1.0.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 +68 -55
- package/dist/cli/main.d.ts +1 -1
- package/dist/cli/main.js +17 -8
- package/dist/cli/setup.d.ts +2 -2
- package/dist/cli/setup.js +36 -30
- package/dist/cli/shims.d.ts +8 -8
- package/dist/cli/shims.js +8 -8
- package/dist/handlers.d.ts +4 -12
- package/dist/handlers.js +96 -310
- package/dist/hook.js +14 -4
- package/package.json +9 -8
package/README.md
CHANGED
|
@@ -2,9 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
Security and developer experience for [Cline](https://github.com/cline/cline), powered by [Ory](https://ory.com).
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Cline 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
|
-
|
|
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
|
|
|
@@ -15,107 +18,119 @@ Security and developer experience for [Cline](https://github.com/cline/cline), p
|
|
|
15
18
|
|
|
16
19
|
## Get started
|
|
17
20
|
|
|
18
|
-
Run one command
|
|
21
|
+
Run one command:
|
|
19
22
|
|
|
20
23
|
```bash
|
|
21
24
|
npx -y -p @ory/cline ory-cline install
|
|
22
25
|
```
|
|
23
26
|
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
- **Local** — run a complete Ory on your laptop with Docker. No account, no signup, no keys. Great for trying it out.
|
|
28
|
-
- **Audit-only** — skip Ory entirely and just log what Cline does.
|
|
29
|
-
|
|
30
|
-
That's it. Confirm everything landed with:
|
|
27
|
+
Add `--global` to install for every project (`~/.cline/hooks/`) instead of just this one. Confirm everything landed with:
|
|
31
28
|
|
|
32
29
|
```bash
|
|
33
30
|
npx -y -p @ory/cline ory-cline status
|
|
34
31
|
```
|
|
35
32
|
|
|
36
|
-
`status` is your one-stop check: what's configured, who's signed in, which tools are covered by permissions, and recent activity.
|
|
33
|
+
`status` is your one-stop check: what's configured, who's signed in, which tools are covered by permissions, and recent activity. Until you connect Agent Security, the identity and permission rows say so and name what's missing.
|
|
34
|
+
|
|
35
|
+
## Skills and commands
|
|
36
|
+
|
|
37
|
+
Installing the plugin drops the full Ory playbook catalog into Cline. **Skills** are model-invoked — just say what you want in plain language and the matching one takes over.
|
|
38
|
+
|
|
39
|
+
| Skill | What it does for you |
|
|
40
|
+
|---|---|
|
|
41
|
+
| `ory-auth-setup` | Adds a complete auth system to your app — login, registration, recovery, verification, settings — on [Ory Elements](https://github.com/ory/elements) |
|
|
42
|
+
| `ory-login-flow` | Builds just the pages, wired to Ory's self-service flows |
|
|
43
|
+
| `ory-social-login` | "Sign in with…" for Google, GitHub, Apple, Microsoft, Discord, Slack, GitLab, Facebook |
|
|
44
|
+
| `ory-local-dev` | Develops and tests login/permission flows against a local Ory — no project, no account, offline |
|
|
45
|
+
| `ory-permissions-onboarding` | Walks a fresh install from observe mode to enforced per-tool permissions without getting blocked |
|
|
46
|
+
| `ory-build-agent` | Drops `@ory/argus` into an agent *you* own — Claude Agent SDK, OpenAI Agents, Mastra, Vercel AI, LangGraph/PydanticAI |
|
|
47
|
+
| `ory-build-integration` | Wires Ory into your app: Action webhooks, JWT validation at a gateway, live event streams |
|
|
48
|
+
| `ory-contribute-integration` | Authors and submits an integration to the public `ory/integrates` registry |
|
|
49
|
+
| `ory-e2b-sandbox` | Scaffolds an E2B sandbox template that boots with this plugin preinstalled |
|
|
50
|
+
| `ory-temporal-worker` | Scaffolds a Temporal TypeScript worker where every Activity is authenticated, authorized, and audited |
|
|
51
|
+
|
|
52
|
+
The local stack has its own playbooks — ask for them by name:
|
|
53
|
+
|
|
54
|
+
| Command | What it does |
|
|
55
|
+
|---|---|
|
|
56
|
+
| `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 |
|
|
57
|
+
| `ory-local-down` | Stops it, keeping your data volumes |
|
|
58
|
+
| `ory-temporal-up` | Starts a local Temporal dev server for the `ory-temporal-worker` scaffold |
|
|
37
59
|
|
|
38
|
-
|
|
60
|
+
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`.
|
|
61
|
+
|
|
62
|
+
So: ask Cline *"add Ory login to this app"* and it scaffolds the pages, starts a local Ory, and wires them together.
|
|
39
63
|
|
|
40
64
|
## What you get
|
|
41
65
|
|
|
42
|
-
|
|
66
|
+
Out of the box, every tool Cline runs produces a privacy-safe structured activity event in the unified local log.
|
|
67
|
+
|
|
68
|
+
Once you connect to Ory Agent Security, two more things happen automatically:
|
|
43
69
|
|
|
44
|
-
- **Who's driving.** You sign in once in your browser; Cline gets its own identity
|
|
45
|
-
- **What it's allowed to do.** Before a tool runs, Ory checks whether it's permitted. It starts in **
|
|
46
|
-
- **A record of everything.** Every decision (allowed, denied, skipped) is logged as a trace you can send to Jaeger, Honeycomb, Grafana, or just a file.
|
|
70
|
+
- **Who's driving.** You sign in once in your browser; each Cline session gets its own identity and registers it automatically on first use — no tokens to copy around. The "who acted on whose behalf" trail stays queryable later.
|
|
71
|
+
- **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.
|
|
47
72
|
|
|
48
73
|
If Ory is ever unreachable, the plugin gets out of the way and lets Cline keep working — so it can't lock you out.
|
|
49
74
|
|
|
50
75
|
### See what's happening
|
|
51
76
|
|
|
52
|
-
Everything the plugin does is observable out of the box
|
|
77
|
+
Everything the plugin does is observable out of the box:
|
|
53
78
|
|
|
54
|
-
- **
|
|
55
|
-
- **Live traces.** Every tool call is recorded as an OpenTelemetry-style span. Watch them stream as the agent works:
|
|
79
|
+
- **Activity log.** Privacy-safe activity is always appended to `~/.config/ory-agent-plugins/cline/ory-agent-debug.log`. View events, decisions, and errors live with:
|
|
56
80
|
|
|
57
81
|
```bash
|
|
58
82
|
npx -y -p @ory/cline ory-cline watch
|
|
59
83
|
```
|
|
60
84
|
|
|
61
|
-
|
|
62
|
-
- **
|
|
85
|
+
Set `ORY_AGENT_LOG_FILE` to override the path; set it empty to disable file persistence.
|
|
86
|
+
- **Live debug.** Launch Cline 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.
|
|
63
87
|
|
|
64
88
|
### Ready to enforce?
|
|
65
89
|
|
|
66
|
-
|
|
90
|
+
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.
|
|
67
91
|
|
|
68
92
|
```bash
|
|
69
|
-
npx -y -p @ory/cline ory-cline permissions
|
|
93
|
+
npx -y -p @ory/cline ory-cline permissions # what the project grants, and the live mode
|
|
70
94
|
```
|
|
71
95
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
## Also: add login to your own app
|
|
75
|
-
|
|
76
|
-
Beyond securing Cline, the plugin helps you build Ory into whatever you're working on. Ask Cline *"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. These are **skills**: just ask in plain language and the matching one takes over.
|
|
96
|
+
Then a denied tool is actually blocked and Cline shows why.
|
|
77
97
|
|
|
78
|
-
|
|
98
|
+
## Connect to Ory Agent Security
|
|
79
99
|
|
|
80
|
-
|
|
100
|
+
Copy the connection details from the [Ory Console](https://console.ory.sh) under **Agent Security**:
|
|
81
101
|
|
|
82
|
-
|
|
102
|
+
| Value | Flag | Environment variable |
|
|
103
|
+
|---|---|---|
|
|
104
|
+
| Project URL | `--project-url` | `ORY_PROJECT_URL` |
|
|
105
|
+
| Agent Security URL | `--agent-security-url` | `ORY_AGENT_SECURITY_URL` |
|
|
106
|
+
| Sign-in client id override (default `ory-agent-security-login`) | `--oauth2-client-id` | `ORY_OAUTH2_CLIENT_ID` |
|
|
83
107
|
|
|
84
108
|
```bash
|
|
85
109
|
npx -y -p @ory/cline ory-cline configure \
|
|
86
110
|
--project-url https://<slug>.projects.oryapis.com \
|
|
87
|
-
--
|
|
88
|
-
--user-login
|
|
111
|
+
--agent-security-url https://agents.console.ory.com
|
|
89
112
|
```
|
|
90
113
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
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.
|
|
94
|
-
|
|
95
|
-
<details>
|
|
96
|
-
<summary>How the integration works</summary>
|
|
97
|
-
|
|
98
|
-
Cline discovers hooks by directory — the presence of an executable script named after the event *is* the registration. `install` writes one thin shell shim per event into the workspace hooks directory `<project>/.clinerules/hooks/` (or `~/.cline/hooks/` with `--global`), makes each executable, and materializes the Ory skills. Each shim execs the published hook binary via `npx`. Install and uninstall respect hook scripts you authored: if an event name is already taken by a script Ory didn't write, install skips it and prints a warning; uninstall removes only the scripts Ory wrote.
|
|
114
|
+
`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`.
|
|
99
115
|
|
|
100
|
-
|
|
116
|
+
**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.
|
|
101
117
|
|
|
102
|
-
The
|
|
118
|
+
**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 Cline session registers its own identity automatically on first use.
|
|
103
119
|
|
|
104
|
-
-
|
|
105
|
-
- **PreToolUse** (`tool_call`) — checks the tool against your permissions; a deny blocks the call in enforce mode.
|
|
106
|
-
- **PostToolUse / TaskComplete / TaskCancel / TaskError / UserPromptSubmit / PreCompact / SessionShutdown** — audit-only trace spans.
|
|
120
|
+
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.
|
|
107
121
|
|
|
108
|
-
|
|
122
|
+
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.
|
|
109
123
|
|
|
110
124
|
## Commands
|
|
111
125
|
|
|
112
126
|
```
|
|
113
|
-
ory-cline install | uninstall Install
|
|
127
|
+
ory-cline install | uninstall Install (add --project-url to also connect Agent Security) / remove; --global installs for every project
|
|
114
128
|
ory-cline status Show configuration, identities, permission coverage, recent activity
|
|
115
|
-
ory-cline
|
|
116
|
-
ory-cline
|
|
117
|
-
ory-cline configure <flags> Point at a project by hand (--project-url, --oauth2-client-id, --user-login, --audit-only)
|
|
129
|
+
ory-cline permissions <cmd> status (read-only; grants + posture live in the Ory Console)
|
|
130
|
+
ory-cline configure <flags> Connect a project (--project-url) or --disconnect
|
|
118
131
|
ory-cline agent <status|unregister> Manage Cline's own auto-created identity
|
|
132
|
+
ory-cline local <up|down|status|…> Run / manage a local Ory in Docker
|
|
133
|
+
ory-cline version Print plugin, core, and Node versions (--json for machine-readable)
|
|
119
134
|
```
|
|
120
135
|
|
|
121
136
|
All prefixed with `npx -y -p @ory/cline`.
|
|
@@ -123,16 +138,14 @@ All prefixed with `npx -y -p @ory/cline`.
|
|
|
123
138
|
## Troubleshooting
|
|
124
139
|
|
|
125
140
|
- **Local Ory fails to start** — make sure Docker is running and ports `4000`, `4100`, `4455`, and `16686` are free.
|
|
126
|
-
- **Browser sign-in loops** — reset with `ory-cline agent unregister` and try again.
|
|
127
|
-
- **
|
|
141
|
+
- **Browser sign-in loops** (after connecting) — reset with `ory-cline agent unregister` and try again.
|
|
142
|
+
- **Running an older CLI than expected** — `npx -p @ory/cline` (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/cline ory-cline version`. To force the current release, clear the cache and reinstall: `rm -rf ~/.npm/_npx` then `npx -y -p @ory/cline ory-cline install`. Pinning an exact version (`@ory/cline@<version>`) also bypasses the cached copy.
|
|
128
143
|
- **A hook didn't install** — check the install output for a skipped-event warning; move or remove your own script at that event name and re-run install.
|
|
129
|
-
- **Want to see what's happening** — `npx -y -p @ory/cline ory-cline status` for a snapshot, `npx -y -p @ory/cline ory-cline 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/cline/` (see [See what's happening](#see-whats-happening)).
|
|
130
144
|
|
|
131
145
|
## Learn more
|
|
132
146
|
|
|
133
147
|
- [Ory documentation](https://www.ory.com/docs/) · [Ory Console](https://console.ory.sh) · [Ory Elements](https://github.com/ory/elements)
|
|
134
148
|
- [Cline repository](https://github.com/cline/cline)
|
|
135
|
-
- [Repo README](../../README.md) and [AGENTS.md](../../AGENTS.md) — full env-var and permission-mode reference
|
|
136
149
|
|
|
137
150
|
## License
|
|
138
151
|
|
package/dist/cli/main.d.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* npx ory-cline uninstall [--global] Remove the Ory hook scripts
|
|
8
8
|
* npx ory-cline configure Set or view Ory credentials
|
|
9
9
|
* npx ory-cline agent <cmd> Manage the agent's OAuth2 (DCR) identity
|
|
10
|
-
* npx ory-cline permissions
|
|
10
|
+
* npx ory-cline permissions Show permission mode and per-tool coverage
|
|
11
11
|
* npx ory-cline setup [options] Write hook scripts directly (fallback)
|
|
12
12
|
* npx ory-cline status Show plugin status
|
|
13
13
|
* npx ory-cline local <cmd> Manage the local Ory dev environment
|
package/dist/cli/main.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* npx ory-cline uninstall [--global] Remove the Ory hook scripts
|
|
9
9
|
* npx ory-cline configure Set or view Ory credentials
|
|
10
10
|
* npx ory-cline agent <cmd> Manage the agent's OAuth2 (DCR) identity
|
|
11
|
-
* npx ory-cline permissions
|
|
11
|
+
* npx ory-cline permissions Show permission mode and per-tool coverage
|
|
12
12
|
* npx ory-cline setup [options] Write hook scripts directly (fallback)
|
|
13
13
|
* npx ory-cline status Show plugin status
|
|
14
14
|
* npx ory-cline local <cmd> Manage the local Ory dev environment
|
|
@@ -54,6 +54,11 @@ const argus_1 = require("@ory/argus");
|
|
|
54
54
|
const PACKAGE_ROOT = path.resolve(__dirname, "..", "..");
|
|
55
55
|
function main() {
|
|
56
56
|
const [command, ...args] = process.argv.slice(2);
|
|
57
|
+
// `--help` after a matched subcommand: print usage, do nothing else.
|
|
58
|
+
// Without this the switch below ignores the flag and `install --help`
|
|
59
|
+
// performs a real install (#221).
|
|
60
|
+
if ((0, argus_1.shouldPrintHelp)(command, args))
|
|
61
|
+
return help();
|
|
57
62
|
switch (command) {
|
|
58
63
|
case "install":
|
|
59
64
|
(0, argus_1.beginDeferNextSteps)();
|
|
@@ -65,7 +70,10 @@ function main() {
|
|
|
65
70
|
break;
|
|
66
71
|
case "uninstall":
|
|
67
72
|
uninstall(args);
|
|
68
|
-
(0, argus_1.clearCredentialsForUninstall)(
|
|
73
|
+
(0, argus_1.clearCredentialsForUninstall)({
|
|
74
|
+
harness: "cline",
|
|
75
|
+
purge: args.includes("--purge"),
|
|
76
|
+
}).then(() => process.exit(0), (err) => {
|
|
69
77
|
console.error(err.message ?? err);
|
|
70
78
|
process.exit(1);
|
|
71
79
|
});
|
|
@@ -74,7 +82,7 @@ function main() {
|
|
|
74
82
|
(0, argus_1.runConfigureCommand)("ory-cline", args);
|
|
75
83
|
break;
|
|
76
84
|
case "agent":
|
|
77
|
-
(0, argus_1.runAgentCommand)("ory-cline", args).then((code) => process.exit(code), (err) => {
|
|
85
|
+
(0, argus_1.runAgentCommand)("ory-cline", "cline", args).then((code) => process.exit(code), (err) => {
|
|
78
86
|
console.error(err.message ?? err);
|
|
79
87
|
process.exit(1);
|
|
80
88
|
});
|
|
@@ -94,15 +102,15 @@ function main() {
|
|
|
94
102
|
process.exit(1);
|
|
95
103
|
});
|
|
96
104
|
break;
|
|
105
|
+
case "watch":
|
|
106
|
+
(0, argus_1.runWatchCommand)("ory-cline", "cline", args).then((code) => process.exit(code), (err) => { console.error(err.message ?? err); process.exit(1); });
|
|
107
|
+
break;
|
|
97
108
|
case "local":
|
|
98
109
|
(0, argus_1.runLocalCommand)("ory-cline", args).catch((err) => {
|
|
99
110
|
console.error(err.message ?? err);
|
|
100
111
|
process.exit(1);
|
|
101
112
|
});
|
|
102
113
|
break;
|
|
103
|
-
case "watch":
|
|
104
|
-
(0, argus_1.runWatchCommand)("cline", args);
|
|
105
|
-
break;
|
|
106
114
|
case "version":
|
|
107
115
|
case "--version":
|
|
108
116
|
case "-v":
|
|
@@ -163,10 +171,11 @@ Commands:
|
|
|
163
171
|
uninstall [--global] Remove the Ory hook scripts from Cline
|
|
164
172
|
configure Set or view Ory project URL and API key
|
|
165
173
|
agent <cmd> Manage the agent's OAuth2 (DCR) identity (status, unregister)
|
|
166
|
-
permissions
|
|
174
|
+
permissions Show the live permission mode and per-tool coverage
|
|
167
175
|
setup [options] Write hook scripts directly to the hooks directory (fallback)
|
|
168
176
|
status Show plugin status and configuration
|
|
169
|
-
watch [
|
|
177
|
+
watch [--json] [--lines <count>]
|
|
178
|
+
Follow the live activity/debug log
|
|
170
179
|
version Show version and the ory-agent-plugins build commit
|
|
171
180
|
local <cmd> Manage local Ory dev environment (up, down, status, seed, ...)
|
|
172
181
|
|
package/dist/cli/setup.d.ts
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
* filename matches the event name, found in a hooks directory. There is no
|
|
7
7
|
* per-hook config file — presence of the script IS the registration. We
|
|
8
8
|
* therefore write one thin shell shim per event into the hooks directory and
|
|
9
|
-
* `chmod 0755` it. Each shim execs the
|
|
10
|
-
* forwarding stdin so the JSON payload reaches the Node entry point.
|
|
9
|
+
* `chmod 0755` it. Each shim execs the Ory runtime shim resolved at install
|
|
10
|
+
* time, forwarding stdin so the JSON payload reaches the Node entry point.
|
|
11
11
|
*
|
|
12
12
|
* Hooks directory (verified against cline@3.0.29):
|
|
13
13
|
* workspace : <projectDir>/.clinerules/hooks/
|
package/dist/cli/setup.js
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
* filename matches the event name, found in a hooks directory. There is no
|
|
8
8
|
* per-hook config file — presence of the script IS the registration. We
|
|
9
9
|
* therefore write one thin shell shim per event into the hooks directory and
|
|
10
|
-
* `chmod 0755` it. Each shim execs the
|
|
11
|
-
* forwarding stdin so the JSON payload reaches the Node entry point.
|
|
10
|
+
* `chmod 0755` it. Each shim execs the Ory runtime shim resolved at install
|
|
11
|
+
* time, forwarding stdin so the JSON payload reaches the Node entry point.
|
|
12
12
|
*
|
|
13
13
|
* Hooks directory (verified against cline@3.0.29):
|
|
14
14
|
* workspace : <projectDir>/.clinerules/hooks/
|
|
@@ -61,37 +61,22 @@ const path = __importStar(require("node:path"));
|
|
|
61
61
|
const argus_1 = require("@ory/argus");
|
|
62
62
|
const assets_js_1 = require("./assets.js");
|
|
63
63
|
const shims_js_1 = require("./shims.js");
|
|
64
|
-
const
|
|
64
|
+
const PACKAGE_ROOT = path.resolve(__dirname, "..", "..");
|
|
65
65
|
/**
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
function readPackageVersion() {
|
|
74
|
-
// dist/cli/setup.js -> package root is two levels up.
|
|
75
|
-
const pkgPath = path.resolve(__dirname, "..", "..", "package.json");
|
|
76
|
-
try {
|
|
77
|
-
const parsed = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
78
|
-
return parsed.version ?? "latest";
|
|
79
|
-
}
|
|
80
|
-
catch {
|
|
81
|
-
return "latest";
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
/**
|
|
85
|
-
* Body of a per-event shim. A POSIX shell script that execs the hook binary,
|
|
86
|
-
* forwarding stdin/stdout transparently. `exec` replaces the shell so the
|
|
87
|
-
* hook's exit status and stdout flow straight back to Cline.
|
|
66
|
+
* Body of a per-event shim. A POSIX shell script that execs the Ory runtime
|
|
67
|
+
* entry, forwarding stdin/stdout transparently. `exec` replaces the shell so
|
|
68
|
+
* the hook's exit status and stdout flow straight back to Cline.
|
|
69
|
+
*
|
|
70
|
+
* The `${PACKAGE_NAME}` line is load-bearing beyond documentation: install and
|
|
71
|
+
* uninstall use it to tell our shims apart from hook scripts the user authored
|
|
72
|
+
* under the same event name (see `isOryShim`).
|
|
88
73
|
*/
|
|
89
|
-
function shimBody() {
|
|
74
|
+
function shimBody(hookCommand) {
|
|
90
75
|
return `#!/bin/sh
|
|
91
76
|
# Ory plugin hook shim for Cline. Generated by ${shims_js_1.PACKAGE_NAME} — do not edit.
|
|
92
77
|
# Forwards the event payload (stdin JSON) to the Ory hook and returns its
|
|
93
78
|
# JSON decision on stdout.
|
|
94
|
-
exec ${hookCommand
|
|
79
|
+
exec ${hookCommand}
|
|
95
80
|
`;
|
|
96
81
|
}
|
|
97
82
|
/**
|
|
@@ -128,8 +113,9 @@ function main() {
|
|
|
128
113
|
process.exit(0);
|
|
129
114
|
}
|
|
130
115
|
const args = (0, argus_1.parseSetupArgs)({ supportsGlobal: true });
|
|
131
|
-
const body = shimBody();
|
|
132
116
|
if (args.print) {
|
|
117
|
+
// Nothing is resolved or written on a --print run.
|
|
118
|
+
const body = shimBody('node "<runtime shim>"');
|
|
133
119
|
for (const event of shims_js_1.HOOK_EVENTS) {
|
|
134
120
|
console.log(`# ── ${event} ───────────────────────────────`);
|
|
135
121
|
console.log(body);
|
|
@@ -155,12 +141,29 @@ function main() {
|
|
|
155
141
|
}
|
|
156
142
|
(0, assets_js_1.uninstallClineOryAssets)(rulesRoot);
|
|
157
143
|
console.log(`Removed Ory skills from ${path.join(rulesRoot, "skills")}`);
|
|
144
|
+
(0, argus_1.removeRuntimeWiring)("cline");
|
|
145
|
+
for (const pruned of (0, argus_1.pruneRuntimeStores)()) {
|
|
146
|
+
console.log(` Removed runtime: ${pruned}`);
|
|
147
|
+
}
|
|
158
148
|
return;
|
|
159
149
|
}
|
|
150
|
+
const runtime = (0, argus_1.wireRuntime)({
|
|
151
|
+
harness: "cline",
|
|
152
|
+
packageName: shims_js_1.PACKAGE_NAME,
|
|
153
|
+
packageRoot: PACKAGE_ROOT,
|
|
154
|
+
installCommand: "npx -y -p @ory/cline ory-cline install",
|
|
155
|
+
args: process.argv.slice(2),
|
|
156
|
+
});
|
|
157
|
+
console.log(runtime.target.kind === "linked"
|
|
158
|
+
? `Runtime: linked to ${runtime.target.packageDir} (dev)`
|
|
159
|
+
: `Runtime: ${runtime.target.packageName}@${runtime.target.version} in ${runtime.target.storeDir}`);
|
|
160
|
+
for (const pruned of runtime.prunedStores) {
|
|
161
|
+
console.log(` Removed stale runtime: ${pruned}`);
|
|
162
|
+
}
|
|
160
163
|
// Write the per-event shims, skipping any event whose script the user
|
|
161
164
|
// authored (i.e. it exists but is not an Ory shim). Existing Ory shims
|
|
162
165
|
// are refreshed.
|
|
163
|
-
const { written, skipped } = (0, shims_js_1.writeHookShims)(hooksDir,
|
|
166
|
+
const { written, skipped } = (0, shims_js_1.writeHookShims)(hooksDir, shimBody((0, argus_1.requireHookCommand)(runtime)));
|
|
164
167
|
(0, assets_js_1.installClineOryAssets)(rulesRoot);
|
|
165
168
|
console.log(`Ory hook scripts installed to ${hooksDir}`);
|
|
166
169
|
console.log(` Scripts: ${written.length > 0 ? written.join(", ") : "(none)"}`);
|
|
@@ -180,7 +183,10 @@ main();
|
|
|
180
183
|
// stored Ory credentials. When required by the plugin's main CLI, that
|
|
181
184
|
// command owns the purge, so the `require.main` guard prevents a double run.
|
|
182
185
|
if (require.main === module && process.argv.includes("--uninstall")) {
|
|
183
|
-
(0, argus_1.clearCredentialsForUninstall)(
|
|
186
|
+
(0, argus_1.clearCredentialsForUninstall)({
|
|
187
|
+
harness: "cline",
|
|
188
|
+
purge: process.argv.includes("--purge"),
|
|
189
|
+
}).then(() => process.exit(0), (err) => {
|
|
184
190
|
console.error(err.message ?? err);
|
|
185
191
|
process.exit(1);
|
|
186
192
|
});
|
package/dist/cli/shims.d.ts
CHANGED
|
@@ -16,15 +16,15 @@ export declare const PACKAGE_NAME = "@ory/cline";
|
|
|
16
16
|
* carries the snake_case `hookName` the handler switches on:
|
|
17
17
|
*
|
|
18
18
|
* TaskStart → agent_start (auth gates)
|
|
19
|
-
* TaskResume → agent_resume (
|
|
20
|
-
* TaskCancel → agent_abort (
|
|
21
|
-
* TaskComplete → agent_end (
|
|
22
|
-
* TaskError → agent_error (
|
|
19
|
+
* TaskResume → agent_resume (activity-only)
|
|
20
|
+
* TaskCancel → agent_abort (activity-only)
|
|
21
|
+
* TaskComplete → agent_end (activity-only)
|
|
22
|
+
* TaskError → agent_error (activity-only)
|
|
23
23
|
* PreToolUse → tool_call (permission gate, blocking)
|
|
24
|
-
* PostToolUse → tool_result (
|
|
25
|
-
* UserPromptSubmit → prompt_submit (
|
|
26
|
-
* PreCompact → pre_compact (
|
|
27
|
-
* SessionShutdown → session_shutdown (
|
|
24
|
+
* PostToolUse → tool_result (activity-only)
|
|
25
|
+
* UserPromptSubmit → prompt_submit (activity-only)
|
|
26
|
+
* PreCompact → pre_compact (activity-only)
|
|
27
|
+
* SessionShutdown → session_shutdown (activity-only)
|
|
28
28
|
*/
|
|
29
29
|
export declare const HOOK_EVENTS: readonly ["TaskStart", "TaskResume", "TaskCancel", "TaskComplete", "TaskError", "PreToolUse", "PostToolUse", "UserPromptSubmit", "PreCompact", "SessionShutdown"];
|
|
30
30
|
export declare function isOryShim(file: string): boolean;
|
package/dist/cli/shims.js
CHANGED
|
@@ -57,15 +57,15 @@ exports.PACKAGE_NAME = "@ory/cline";
|
|
|
57
57
|
* carries the snake_case `hookName` the handler switches on:
|
|
58
58
|
*
|
|
59
59
|
* TaskStart → agent_start (auth gates)
|
|
60
|
-
* TaskResume → agent_resume (
|
|
61
|
-
* TaskCancel → agent_abort (
|
|
62
|
-
* TaskComplete → agent_end (
|
|
63
|
-
* TaskError → agent_error (
|
|
60
|
+
* TaskResume → agent_resume (activity-only)
|
|
61
|
+
* TaskCancel → agent_abort (activity-only)
|
|
62
|
+
* TaskComplete → agent_end (activity-only)
|
|
63
|
+
* TaskError → agent_error (activity-only)
|
|
64
64
|
* PreToolUse → tool_call (permission gate, blocking)
|
|
65
|
-
* PostToolUse → tool_result (
|
|
66
|
-
* UserPromptSubmit → prompt_submit (
|
|
67
|
-
* PreCompact → pre_compact (
|
|
68
|
-
* SessionShutdown → session_shutdown (
|
|
65
|
+
* PostToolUse → tool_result (activity-only)
|
|
66
|
+
* UserPromptSubmit → prompt_submit (activity-only)
|
|
67
|
+
* PreCompact → pre_compact (activity-only)
|
|
68
|
+
* SessionShutdown → session_shutdown (activity-only)
|
|
69
69
|
*/
|
|
70
70
|
exports.HOOK_EVENTS = [
|
|
71
71
|
"TaskStart",
|
package/dist/handlers.d.ts
CHANGED
|
@@ -1,20 +1,12 @@
|
|
|
1
|
-
import { OryAgentClient,
|
|
1
|
+
import { OryAgentClient, ensureAgentIdentity, ensureSubAgentIdentity, ensureUserAuthenticated } from "@ory/argus";
|
|
2
2
|
import type { ClineHookInput, ClineHookOutput } from "./types.js";
|
|
3
3
|
export interface HandleHookEventDeps {
|
|
4
|
-
/** Test injection point for the user login flow. */
|
|
5
4
|
userLogin?: typeof ensureUserAuthenticated;
|
|
6
|
-
/** Test injection point for the agent identity gate. */
|
|
7
5
|
agentGate?: typeof ensureAgentIdentity;
|
|
6
|
+
subAgentGate?: typeof ensureSubAgentIdentity;
|
|
8
7
|
}
|
|
9
8
|
/**
|
|
10
|
-
* Route a Cline file-hook event to
|
|
11
|
-
*
|
|
12
|
-
* Cline has no dedicated approval-ask hook, so authorization is enforced at
|
|
13
|
-
* the `tool_call` event. A deny becomes `{ cancel: true, errorMessage }`, which
|
|
14
|
-
* prevents the tool from running.
|
|
15
|
-
*
|
|
16
|
-
* The on-disk script filenames are PascalCase (TaskStart, PreToolUse, …) but
|
|
17
|
-
* the `hookName` field inside the stdin payload is the snake_case
|
|
18
|
-
* `HookEventName` enum (verified against cline@3.0.29) — we switch on that.
|
|
9
|
+
* Route a Cline file-hook event to Ory. Cline blocks a `tool_call` with
|
|
10
|
+
* `{ cancel: true, errorMessage }`.
|
|
19
11
|
*/
|
|
20
12
|
export declare function handleHookEvent(input: ClineHookInput, client: OryAgentClient, deps?: HandleHookEventDeps): Promise<ClineHookOutput>;
|
package/dist/handlers.js
CHANGED
|
@@ -2,16 +2,10 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.handleHookEvent = handleHookEvent;
|
|
4
4
|
const argus_1 = require("@ory/argus");
|
|
5
|
+
const HARNESS = "cline";
|
|
5
6
|
/**
|
|
6
|
-
* Route a Cline file-hook event to
|
|
7
|
-
*
|
|
8
|
-
* Cline has no dedicated approval-ask hook, so authorization is enforced at
|
|
9
|
-
* the `tool_call` event. A deny becomes `{ cancel: true, errorMessage }`, which
|
|
10
|
-
* prevents the tool from running.
|
|
11
|
-
*
|
|
12
|
-
* The on-disk script filenames are PascalCase (TaskStart, PreToolUse, …) but
|
|
13
|
-
* the `hookName` field inside the stdin payload is the snake_case
|
|
14
|
-
* `HookEventName` enum (verified against cline@3.0.29) — we switch on that.
|
|
7
|
+
* Route a Cline file-hook event to Ory. Cline blocks a `tool_call` with
|
|
8
|
+
* `{ cancel: true, errorMessage }`.
|
|
15
9
|
*/
|
|
16
10
|
async function handleHookEvent(input, client, deps = {}) {
|
|
17
11
|
const event = input.hookName;
|
|
@@ -20,324 +14,116 @@ async function handleHookEvent(input, client, deps = {}) {
|
|
|
20
14
|
taskId: input.taskId,
|
|
21
15
|
toolName: input.preToolUse?.toolName ?? input.postToolUse?.toolName,
|
|
22
16
|
});
|
|
23
|
-
|
|
24
|
-
client.tracer.setContext({
|
|
25
|
-
traceId: (0, argus_1.deriveTraceId)(input.taskId),
|
|
26
|
-
sessionId: input.taskId,
|
|
27
|
-
});
|
|
28
|
-
try {
|
|
17
|
+
return (0, argus_1.withHookContext)(client, { sessionId: input.taskId }, async () => {
|
|
29
18
|
switch (event) {
|
|
30
19
|
case "agent_start":
|
|
31
|
-
|
|
20
|
+
client.logger.info("lifecycle.task_start", {
|
|
21
|
+
taskId: input.taskId,
|
|
22
|
+
workspaceRoots: input.workspaceRoots,
|
|
23
|
+
});
|
|
24
|
+
await startTask(input, client, deps);
|
|
25
|
+
return {};
|
|
32
26
|
case "agent_resume":
|
|
33
|
-
|
|
27
|
+
client.logger.info("lifecycle.task_resume", { taskId: input.taskId });
|
|
28
|
+
await startTask(input, client, deps);
|
|
29
|
+
return {};
|
|
30
|
+
case "tool_call": {
|
|
31
|
+
const toolName = input.preToolUse?.toolName ?? "unknown";
|
|
32
|
+
const toolArgs = input.preToolUse?.parameters;
|
|
33
|
+
client.logger.info("lifecycle.pre_tool_use", {
|
|
34
|
+
taskId: input.taskId,
|
|
35
|
+
toolName,
|
|
36
|
+
toolInput: toolArgs,
|
|
37
|
+
});
|
|
38
|
+
const result = await (0, argus_1.gate)(client, {
|
|
39
|
+
harness: HARNESS,
|
|
40
|
+
toolName,
|
|
41
|
+
toolArgs,
|
|
42
|
+
subjectFallback: `session:${input.taskId}`,
|
|
43
|
+
principals: actingSubagentPrincipals(input),
|
|
44
|
+
});
|
|
45
|
+
return result.blocked
|
|
46
|
+
? { cancel: true, errorMessage: result.denialMessage }
|
|
47
|
+
: {};
|
|
48
|
+
}
|
|
49
|
+
case "tool_result": {
|
|
50
|
+
const toolName = input.postToolUse?.toolName ?? "unknown";
|
|
51
|
+
client.logger.info("lifecycle.post_tool_use", {
|
|
52
|
+
taskId: input.taskId,
|
|
53
|
+
toolName,
|
|
54
|
+
success: input.postToolUse?.success,
|
|
55
|
+
});
|
|
56
|
+
const terminal = {
|
|
57
|
+
toolName,
|
|
58
|
+
input: input.postToolUse?.parameters,
|
|
59
|
+
extraActivityAttributes: {
|
|
60
|
+
success: input.postToolUse?.success,
|
|
61
|
+
executionTimeMs: input.postToolUse?.executionTimeMs,
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
if (input.postToolUse?.success === false) {
|
|
65
|
+
(0, argus_1.fail)(client, { ...terminal, error: input.postToolUse.result });
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
(0, argus_1.complete)(client, { ...terminal, output: input.postToolUse?.result });
|
|
69
|
+
}
|
|
70
|
+
return {};
|
|
71
|
+
}
|
|
34
72
|
case "agent_abort":
|
|
35
|
-
return
|
|
73
|
+
return endTask(client, input, "ok", { reasonCode: "abort" }, "lifecycle.task_cancel");
|
|
36
74
|
case "agent_end":
|
|
37
|
-
return
|
|
75
|
+
return endTask(client, input, "ok", { reasonCode: "complete" }, "lifecycle.task_complete");
|
|
38
76
|
case "agent_error":
|
|
39
|
-
return
|
|
40
|
-
case "
|
|
41
|
-
return
|
|
42
|
-
case "tool_result":
|
|
43
|
-
return await handlePostToolUse(input, client);
|
|
77
|
+
return endTask(client, input, "error", { reasonCode: "error" }, "lifecycle.task_error");
|
|
78
|
+
case "session_shutdown":
|
|
79
|
+
return endTask(client, input, "ok", { stage: "session_shutdown" }, "lifecycle.session_shutdown");
|
|
44
80
|
case "prompt_submit":
|
|
45
|
-
|
|
81
|
+
client.logger.info("lifecycle.user_prompt_submit", { taskId: input.taskId });
|
|
82
|
+
client.logger.activity("user.prompt", "ok", {
|
|
83
|
+
attributes: { stage: "user_prompt_submit" },
|
|
84
|
+
});
|
|
85
|
+
return {};
|
|
46
86
|
case "pre_compact":
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
return
|
|
87
|
+
client.logger.info("lifecycle.pre_compact", { taskId: input.taskId });
|
|
88
|
+
client.logger.activity("compaction", "ok", { attributes: { stage: "pre_compact" } });
|
|
89
|
+
return {};
|
|
50
90
|
default:
|
|
51
91
|
client.logger.debug("hook.passthrough", { event });
|
|
52
|
-
client.
|
|
53
|
-
attributes: { event },
|
|
54
|
-
});
|
|
92
|
+
client.logger.activity("hook.passthrough", "skipped", { attributes: { event } });
|
|
55
93
|
return {};
|
|
56
94
|
}
|
|
57
|
-
}
|
|
58
|
-
finally {
|
|
59
|
-
client.tracer.clearContext();
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
// ─── agent_start (session.start) ───────────────────────────────────
|
|
63
|
-
async function handleTaskStart(input, client, deps) {
|
|
64
|
-
client.logger.info("lifecycle.task_start", {
|
|
65
|
-
taskId: input.taskId,
|
|
66
|
-
workspaceRoots: input.workspaceRoots,
|
|
67
|
-
});
|
|
68
|
-
client.tracer.record("session.start", "ok", {
|
|
69
|
-
attributes: { clineVersion: input.clineVersion },
|
|
70
|
-
});
|
|
71
|
-
// 1. User gate. agent_start is a subprocess hook that can carry a hard block
|
|
72
|
-
// (cancel:true), so allowBlock:true.
|
|
73
|
-
const userGate = deps.userLogin ?? argus_1.ensureUserAuthenticated;
|
|
74
|
-
const decision = await userGate(client, {
|
|
75
|
-
binName: "ory-cline",
|
|
76
|
-
harness: "cline",
|
|
77
|
-
allowBlock: true,
|
|
78
|
-
});
|
|
79
|
-
// 2. Agent gate. Never blocks; attaches the agent's bearer token to
|
|
80
|
-
// outgoing Ory API calls.
|
|
81
|
-
const agentGate = deps.agentGate ?? argus_1.ensureAgentIdentity;
|
|
82
|
-
await agentGate(client, {
|
|
83
|
-
projectUrl: (0, argus_1.resolveConfig)().projectUrl,
|
|
84
|
-
harness: "cline",
|
|
85
95
|
});
|
|
86
|
-
// 3. User → agent delegation tuple. Best-effort; never blocks. Written at
|
|
87
|
-
// most once per install.
|
|
88
|
-
await (0, argus_1.writeUserDelegatesAgent)(client);
|
|
89
|
-
if (!decision.proceed) {
|
|
90
|
-
return {
|
|
91
|
-
cancel: true,
|
|
92
|
-
errorMessage: decision.reason ?? "Ory user login was declined or could not complete.",
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
if (decision.mode !== "disabled") {
|
|
96
|
-
return {};
|
|
97
|
-
}
|
|
98
|
-
// Legacy verify/log path (ORY_USER_LOGIN unset).
|
|
99
|
-
const resolved = (0, argus_1.resolveConfig)();
|
|
100
|
-
if (resolved.auditOnly) {
|
|
101
|
-
client.logger.info("config.audit_only", {
|
|
102
|
-
message: "Audit-only mode enabled. Auth and permission checks are disabled.",
|
|
103
|
-
});
|
|
104
|
-
return {};
|
|
105
|
-
}
|
|
106
|
-
if (!resolved.projectUrl) {
|
|
107
|
-
client.logger.warn("config.not_configured", {
|
|
108
|
-
message: "Ory plugin is not configured. Auth and permission checks are disabled. " +
|
|
109
|
-
"Run 'npx ory-cline configure' to connect to an Ory project.",
|
|
110
|
-
});
|
|
111
|
-
return {};
|
|
112
|
-
}
|
|
113
|
-
const sessionToken = process.env.ORY_SESSION_TOKEN;
|
|
114
|
-
const oauth2Token = process.env.ORY_OAUTH2_TOKEN;
|
|
115
|
-
if (sessionToken) {
|
|
116
|
-
await verifySessionToken(sessionToken, client);
|
|
117
|
-
return {};
|
|
118
|
-
}
|
|
119
|
-
if (oauth2Token) {
|
|
120
|
-
await verifyOAuth2Token(oauth2Token, client);
|
|
121
|
-
return {};
|
|
122
|
-
}
|
|
123
|
-
client.logger.warn("session.no_credentials", {
|
|
124
|
-
message: "Neither ORY_SESSION_TOKEN nor ORY_OAUTH2_TOKEN is set. " +
|
|
125
|
-
"Skipping authentication.",
|
|
126
|
-
});
|
|
127
|
-
return {};
|
|
128
96
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
code: oryErr.code,
|
|
142
|
-
message: oryErr.message,
|
|
143
|
-
});
|
|
144
|
-
}
|
|
97
|
+
function actingSubagentPrincipals(input) {
|
|
98
|
+
if (!input.parent_agent_id || !input.agent_id)
|
|
99
|
+
return undefined;
|
|
100
|
+
const credential = (0, argus_1.loadSubAgentDynamicCredentials)(HARNESS, input.taskId, HARNESS);
|
|
101
|
+
if (!credential)
|
|
102
|
+
return undefined;
|
|
103
|
+
return {
|
|
104
|
+
subAgentClientId: credential.clientId,
|
|
105
|
+
subAgentType: HARNESS,
|
|
106
|
+
perSpawnId: input.agent_id,
|
|
107
|
+
sessionId: input.taskId,
|
|
108
|
+
};
|
|
145
109
|
}
|
|
146
|
-
async function
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
scope: tokenInfo.scope,
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
catch (err) {
|
|
162
|
-
const oryErr = err;
|
|
163
|
-
client.logger.warn("oauth2.introspect_failed", {
|
|
164
|
-
code: oryErr.code,
|
|
165
|
-
message: oryErr.message,
|
|
110
|
+
async function startTask(input, client, deps) {
|
|
111
|
+
await (0, argus_1.sessionStart)(client, {
|
|
112
|
+
harness: HARNESS,
|
|
113
|
+
userLogin: deps.userLogin,
|
|
114
|
+
agentGate: deps.agentGate,
|
|
115
|
+
});
|
|
116
|
+
if (input.parent_agent_id && input.agent_id) {
|
|
117
|
+
await (0, argus_1.registerSubagent)(client, {
|
|
118
|
+
harness: HARNESS,
|
|
119
|
+
subAgentType: HARNESS,
|
|
120
|
+
perSpawnId: input.agent_id,
|
|
121
|
+
subAgentGate: deps.subAgentGate,
|
|
166
122
|
});
|
|
167
123
|
}
|
|
168
124
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
const toolInput = input.preToolUse?.parameters;
|
|
173
|
-
client.logger.info("lifecycle.pre_tool_use", {
|
|
174
|
-
taskId: input.taskId,
|
|
175
|
-
toolName,
|
|
176
|
-
toolInput,
|
|
177
|
-
});
|
|
178
|
-
const inputSummary = (0, argus_1.summarizeToolInput)(toolName, toolInput);
|
|
179
|
-
// In audit-only mode, log the invocation but skip permission checks.
|
|
180
|
-
if ((0, argus_1.resolveConfig)().auditOnly) {
|
|
181
|
-
client.tracer.record("tool.invoke", "ok", {
|
|
182
|
-
attributes: { toolName, ...inputSummary },
|
|
183
|
-
});
|
|
184
|
-
return {};
|
|
185
|
-
}
|
|
186
|
-
const subject = (0, argus_1.resolveUserSubject)(client, `session:${input.taskId}`);
|
|
187
|
-
const subjectId = (0, argus_1.subjectLabel)(subject);
|
|
188
|
-
const namespace = resolveNamespace();
|
|
189
|
-
try {
|
|
190
|
-
const outcome = await (0, argus_1.gateToolCall)(client, {
|
|
191
|
-
harness: "cline",
|
|
192
|
-
toolName,
|
|
193
|
-
check: { namespace, object: toolName, relation: "use", ...subject },
|
|
194
|
-
spanAttributes: { toolName },
|
|
195
|
-
});
|
|
196
|
-
// Interactive tools (operator-extensible via ORY_INTERACTIVE_TOOLS):
|
|
197
|
-
// user.interaction span is already recorded; pass through.
|
|
198
|
-
if (outcome.kind === "interactive") {
|
|
199
|
-
return {};
|
|
200
|
-
}
|
|
201
|
-
const decision = outcome;
|
|
202
|
-
if (decision.kind === "fail_open") {
|
|
203
|
-
return handlePermissionError(decision.error, toolName, client);
|
|
204
|
-
}
|
|
205
|
-
const attrs = { toolName, ...inputSummary };
|
|
206
|
-
const decisionAttrs = decision.spanAttributes;
|
|
207
|
-
if (decision.kind === "allow") {
|
|
208
|
-
client.tracer.record("tool.invoke", "ok", {
|
|
209
|
-
attributes: { ...attrs, ...decisionAttrs, allowed: true },
|
|
210
|
-
});
|
|
211
|
-
return {};
|
|
212
|
-
}
|
|
213
|
-
if (decision.kind === "observe") {
|
|
214
|
-
client.tracer.record("tool.block", "denied", {
|
|
215
|
-
attributes: {
|
|
216
|
-
...attrs,
|
|
217
|
-
...decisionAttrs,
|
|
218
|
-
allowed: false,
|
|
219
|
-
...(0, argus_1.alertAttributes)(false),
|
|
220
|
-
},
|
|
221
|
-
});
|
|
222
|
-
client.tracer.record("tool.invoke", "ok", {
|
|
223
|
-
attributes: { ...attrs, ...decisionAttrs, allowed: false, observed: true },
|
|
224
|
-
});
|
|
225
|
-
return {};
|
|
226
|
-
}
|
|
227
|
-
// deny → cancel the tool.
|
|
228
|
-
client.tracer.record("tool.block", "denied", {
|
|
229
|
-
attributes: {
|
|
230
|
-
...attrs,
|
|
231
|
-
...decisionAttrs,
|
|
232
|
-
allowed: false,
|
|
233
|
-
...(0, argus_1.alertAttributes)(true),
|
|
234
|
-
},
|
|
235
|
-
});
|
|
236
|
-
return {
|
|
237
|
-
cancel: true,
|
|
238
|
-
errorMessage: (0, argus_1.formatDenialMessage)({ tool: toolName, subjectId, namespace }),
|
|
239
|
-
};
|
|
240
|
-
}
|
|
241
|
-
catch (err) {
|
|
242
|
-
return handlePermissionError(err, toolName, client);
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
// ─── tool_result (tool.after) ──────────────────────────────────────
|
|
246
|
-
async function handlePostToolUse(input, client) {
|
|
247
|
-
const toolName = input.postToolUse?.toolName ?? "unknown";
|
|
248
|
-
const toolInput = input.postToolUse?.parameters;
|
|
249
|
-
const toolResult = input.postToolUse?.result;
|
|
250
|
-
client.logger.info("lifecycle.post_tool_use", {
|
|
251
|
-
taskId: input.taskId,
|
|
252
|
-
toolName,
|
|
253
|
-
success: input.postToolUse?.success,
|
|
254
|
-
});
|
|
255
|
-
client.tracer.record("tool.complete", "ok", {
|
|
256
|
-
attributes: {
|
|
257
|
-
toolName,
|
|
258
|
-
success: input.postToolUse?.success,
|
|
259
|
-
executionTimeMs: input.postToolUse?.executionTimeMs,
|
|
260
|
-
...(0, argus_1.summarizeToolInput)(toolName, toolInput),
|
|
261
|
-
...(0, argus_1.summarizeToolOutput)(toolName, toolResult),
|
|
262
|
-
},
|
|
263
|
-
});
|
|
264
|
-
return {};
|
|
265
|
-
}
|
|
266
|
-
// ─── Trace-only lifecycle events ───────────────────────────────────
|
|
267
|
-
async function handleTaskResume(input, client) {
|
|
268
|
-
client.logger.info("lifecycle.task_resume", { taskId: input.taskId });
|
|
269
|
-
client.tracer.record("session.start", "ok", {
|
|
270
|
-
attributes: { resumed: true, clineVersion: input.clineVersion },
|
|
271
|
-
});
|
|
272
|
-
return {};
|
|
273
|
-
}
|
|
274
|
-
async function handleTaskCancel(input, client) {
|
|
275
|
-
client.logger.info("lifecycle.task_cancel", { taskId: input.taskId });
|
|
276
|
-
client.tracer.record("session.end", "ok", {
|
|
277
|
-
attributes: { reason: "abort" },
|
|
278
|
-
});
|
|
279
|
-
return {};
|
|
280
|
-
}
|
|
281
|
-
async function handleTaskComplete(input, client) {
|
|
282
|
-
client.logger.info("lifecycle.task_complete", { taskId: input.taskId });
|
|
283
|
-
client.tracer.record("session.end", "ok", {
|
|
284
|
-
attributes: { reason: "complete" },
|
|
285
|
-
});
|
|
286
|
-
return {};
|
|
287
|
-
}
|
|
288
|
-
async function handleTaskError(input, client) {
|
|
289
|
-
client.logger.info("lifecycle.task_error", { taskId: input.taskId });
|
|
290
|
-
client.tracer.record("session.end", "error", {
|
|
291
|
-
attributes: { reason: "error" },
|
|
292
|
-
});
|
|
293
|
-
return {};
|
|
294
|
-
}
|
|
295
|
-
async function handleSessionShutdown(input, client) {
|
|
296
|
-
client.logger.info("lifecycle.session_shutdown", { taskId: input.taskId });
|
|
297
|
-
client.tracer.record("session.end", "ok", {
|
|
298
|
-
attributes: { stage: "session_shutdown" },
|
|
299
|
-
});
|
|
300
|
-
return {};
|
|
301
|
-
}
|
|
302
|
-
async function handleUserPromptSubmit(input, client) {
|
|
303
|
-
client.logger.info("lifecycle.user_prompt_submit", { taskId: input.taskId });
|
|
304
|
-
// User-facing phase: the human is the decision-maker. Audit span only.
|
|
305
|
-
client.tracer.record("user.prompt", "ok", {
|
|
306
|
-
attributes: { stage: "user_prompt_submit" },
|
|
307
|
-
});
|
|
308
|
-
return {};
|
|
309
|
-
}
|
|
310
|
-
async function handlePreCompact(input, client) {
|
|
311
|
-
client.logger.info("lifecycle.pre_compact", { taskId: input.taskId });
|
|
312
|
-
client.tracer.record("compaction", "ok", {
|
|
313
|
-
attributes: { stage: "pre_compact" },
|
|
314
|
-
});
|
|
315
|
-
return {};
|
|
316
|
-
}
|
|
317
|
-
// ─── Helpers ────────────────────────────────────────────────────────
|
|
318
|
-
function resolveNamespace() {
|
|
319
|
-
return process.env.ORY_PERMISSION_NAMESPACE ?? "AgentTools";
|
|
320
|
-
}
|
|
321
|
-
function handlePermissionError(oryErr, toolName, client) {
|
|
322
|
-
if (oryErr.code === "network_error") {
|
|
323
|
-
client.logger.warn("permission.network_error", {
|
|
324
|
-
toolName,
|
|
325
|
-
message: "Ory unreachable, failing open",
|
|
326
|
-
});
|
|
327
|
-
return {};
|
|
328
|
-
}
|
|
329
|
-
if (oryErr.code === "rate_limited") {
|
|
330
|
-
client.logger.warn("permission.rate_limited", {
|
|
331
|
-
toolName,
|
|
332
|
-
message: "Ory rate limited, failing open",
|
|
333
|
-
});
|
|
334
|
-
return {};
|
|
335
|
-
}
|
|
336
|
-
// For other errors, also fail open but log prominently.
|
|
337
|
-
client.logger.error("permission.check.error", {
|
|
338
|
-
toolName,
|
|
339
|
-
code: oryErr.code,
|
|
340
|
-
message: oryErr.message,
|
|
341
|
-
});
|
|
125
|
+
function endTask(client, input, status, attributes, logEvent) {
|
|
126
|
+
client.logger.info(logEvent, { taskId: input.taskId });
|
|
127
|
+
client.logger.activity("session.end", status, { attributes });
|
|
342
128
|
return {};
|
|
343
129
|
}
|
package/dist/hook.js
CHANGED
|
@@ -18,6 +18,16 @@
|
|
|
18
18
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
19
|
const argus_1 = require("@ory/argus");
|
|
20
20
|
const handlers_js_1 = require("./handlers.js");
|
|
21
|
+
const EVENT_FLUSH_TIMEOUT_MS = 750;
|
|
22
|
+
let client;
|
|
23
|
+
async function flushEvents() {
|
|
24
|
+
try {
|
|
25
|
+
await client?.flushEvents(EVENT_FLUSH_TIMEOUT_MS);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
// The hook decision must not depend on activity delivery.
|
|
29
|
+
}
|
|
30
|
+
}
|
|
21
31
|
/**
|
|
22
32
|
* Read all of stdin as a string.
|
|
23
33
|
*
|
|
@@ -53,7 +63,7 @@ function readStdin() {
|
|
|
53
63
|
});
|
|
54
64
|
}
|
|
55
65
|
async function main() {
|
|
56
|
-
|
|
66
|
+
client = argus_1.OryAgentClient.fromEnv("cline");
|
|
57
67
|
const raw = await readStdin();
|
|
58
68
|
let input;
|
|
59
69
|
try {
|
|
@@ -61,7 +71,6 @@ async function main() {
|
|
|
61
71
|
}
|
|
62
72
|
catch {
|
|
63
73
|
client.logger.error("hook.stdin.parse_failed", { raw: raw.slice(0, 200) });
|
|
64
|
-
await client.tracer.shutdown();
|
|
65
74
|
process.exit(0); // Don't block on parse errors
|
|
66
75
|
}
|
|
67
76
|
const output = await (0, handlers_js_1.handleHookEvent)(input, client);
|
|
@@ -70,10 +79,11 @@ async function main() {
|
|
|
70
79
|
if (output.cancel || output.contextModification || output.errorMessage) {
|
|
71
80
|
process.stdout.write(JSON.stringify(output));
|
|
72
81
|
}
|
|
73
|
-
await
|
|
82
|
+
await flushEvents();
|
|
74
83
|
process.exit(0);
|
|
75
84
|
}
|
|
76
|
-
main().catch((err) => {
|
|
85
|
+
main().catch(async (err) => {
|
|
77
86
|
process.stderr.write(`[ory-agent] fatal: ${err}\n`);
|
|
87
|
+
await flushEvents();
|
|
78
88
|
process.exit(0); // Fail open
|
|
79
89
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ory/cline",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Ory plugin for Cline: 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",
|
|
@@ -35,8 +35,7 @@
|
|
|
35
35
|
"agent-security",
|
|
36
36
|
"guardrails",
|
|
37
37
|
"llm",
|
|
38
|
-
"
|
|
39
|
-
"distributed-tracing",
|
|
38
|
+
"activity-auditing",
|
|
40
39
|
"observability",
|
|
41
40
|
"kratos",
|
|
42
41
|
"keto",
|
|
@@ -47,6 +46,10 @@
|
|
|
47
46
|
"registry": "https://registry.npmjs.org/",
|
|
48
47
|
"provenance": true
|
|
49
48
|
},
|
|
49
|
+
"reova": {
|
|
50
|
+
"enabled": true,
|
|
51
|
+
"endpoint": "https://telemetry.reo.dev/data"
|
|
52
|
+
},
|
|
50
53
|
"main": "dist/index.js",
|
|
51
54
|
"types": "dist/index.d.ts",
|
|
52
55
|
"exports": {
|
|
@@ -62,12 +65,11 @@
|
|
|
62
65
|
},
|
|
63
66
|
"files": [
|
|
64
67
|
"dist",
|
|
65
|
-
"!dist/dev",
|
|
66
68
|
"!dist/**/*.tsbuildinfo"
|
|
67
69
|
],
|
|
68
70
|
"dependencies": {
|
|
69
|
-
"
|
|
70
|
-
"@ory/argus": "0.
|
|
71
|
+
"reova": "^0.7.0",
|
|
72
|
+
"@ory/argus": "1.0.0"
|
|
71
73
|
},
|
|
72
74
|
"devDependencies": {
|
|
73
75
|
"typescript": "^6.0.2",
|
|
@@ -81,7 +83,6 @@
|
|
|
81
83
|
"clean": "rm -rf dist *.tsbuildinfo",
|
|
82
84
|
"test": "vitest run",
|
|
83
85
|
"test:watch": "vitest",
|
|
84
|
-
"typecheck": "tsc --noEmit"
|
|
85
|
-
"dev": "node dist/dev/launcher.js"
|
|
86
|
+
"typecheck": "tsc --noEmit"
|
|
86
87
|
}
|
|
87
88
|
}
|