@ory/cline 0.10.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 +115 -0
- package/dist/cli/assets.d.ts +15 -0
- package/dist/cli/assets.js +89 -0
- package/dist/cli/main.d.ts +15 -0
- package/dist/cli/main.js +176 -0
- package/dist/cli/setup.d.ts +23 -0
- package/dist/cli/setup.js +215 -0
- package/dist/handlers.d.ts +20 -0
- package/dist/handlers.js +367 -0
- package/dist/hook.d.ts +17 -0
- package/dist/hook.js +79 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +5 -0
- package/dist/types.d.ts +81 -0
- package/dist/types.js +18 -0
- package/package.json +86 -0
package/README.md
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Ory Agent Plugin: Cline
|
|
2
|
+
|
|
3
|
+
[Ory](https://ory.com) bundled into [Cline](https://github.com/cline/cline): skills that scaffold Ory authentication into your codebase, a local Ory stack you can spin up in one command, and (when pointed at an Ory project) authentication, authorization, and audit for every tool Cline runs.
|
|
4
|
+
|
|
5
|
+
You don't need an Ory account or any prior Ory experience to start.
|
|
6
|
+
|
|
7
|
+
This integration uses Cline's **file-based hooks** (not the in-process SDK) and is verified against cline@3.0.29.
|
|
8
|
+
|
|
9
|
+
## New to Ory?
|
|
10
|
+
|
|
11
|
+
[Ory](https://www.ory.com/docs/) is an open-source identity and access platform — login, registration, sessions, social sign-in, multi-factor auth, and fine-grained permissions, so you don't have to build any of that yourself. Two things make it easy to try with no prior experience:
|
|
12
|
+
|
|
13
|
+
- **Ory Elements** are prebuilt, themeable UI components for the auth pages. The scaffolding skills wire them into your app for you.
|
|
14
|
+
- **The local Ory stack** is a complete Ory running on your laptop in Docker — no account, no signup, no API key.
|
|
15
|
+
|
|
16
|
+
## What this plugin does
|
|
17
|
+
|
|
18
|
+
Two independent things, and you can use either on its own:
|
|
19
|
+
|
|
20
|
+
1. **Build auth into your app.** Have Cline scaffold Ory login, registration, social sign-in, and permissions into the project you're working on, backed by the local stack. This needs nothing but Docker.
|
|
21
|
+
2. **Govern the agent itself.** Authenticate Cline's own session and authorize every tool it runs against Ory Permissions, with a full audit trail. See [Agent security](#agent-security).
|
|
22
|
+
|
|
23
|
+
## Prerequisites
|
|
24
|
+
|
|
25
|
+
- [Cline](https://github.com/cline/cline) installed
|
|
26
|
+
- Node.js **≥ 22**
|
|
27
|
+
- [Docker](https://docs.docker.com/get-docker/) (only needed for the local Ory stack)
|
|
28
|
+
- macOS or Linux. Windows works via WSL2.
|
|
29
|
+
|
|
30
|
+
## Install
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npx -y -p @ory/cline ory-cline install
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
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`), `chmod 0755`s each, and materializes the Ory skills. Each shim execs the published hook binary via `npx`.
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npx -y -p @ory/cline ory-cline install --global # install for all projects
|
|
40
|
+
npx -y -p @ory/cline ory-cline status # confirm what landed
|
|
41
|
+
npx -y -p @ory/cline ory-cline uninstall # remove only the Ory shims + skills
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Uninstall removes only the scripts the Ory installer wrote (matched by a marker comment) — it never touches a hook script you authored that happens to share an event name.
|
|
45
|
+
|
|
46
|
+
## Quickstart (≈ 3 minutes)
|
|
47
|
+
|
|
48
|
+
From any project where you'd like Ory authentication, inside Cline:
|
|
49
|
+
|
|
50
|
+
1. **Start a local Ory instance.** Ask Cline *"start the local Ory stack"* or run `/local-up`. A banner prints the seeded test user's email and password — note them.
|
|
51
|
+
2. **Scaffold Ory into your project.** Ask Cline *"add Ory auth to this app"* (the `ory-auth-setup` skill). It installs Ory Elements, wires the SDK, and generates the login / registration / recovery / settings pages, all targeting the local stack.
|
|
52
|
+
3. **Sign in.** Start your app, visit the login page, and sign in with the seeded credentials. You now have a real Ory session backed by a real Ory stack — locally, offline, zero configuration.
|
|
53
|
+
|
|
54
|
+
Continue to [Agent security](#agent-security) when you're ready to enforce.
|
|
55
|
+
|
|
56
|
+
## How the integration works
|
|
57
|
+
|
|
58
|
+
Cline runs each hook script as a **subprocess**, passing the event payload as JSON on stdin and reading a JSON decision on stdout. A hook blocks a tool call by returning `{"cancel":true}`. Cline does **not** expose a separate approval-ask hook, so the plugin gates at the tool-call event. Hooks have a 30-second timeout.
|
|
59
|
+
|
|
60
|
+
The hook script filenames are PascalCase (`TaskStart`, `PreToolUse`, …); the payload Cline delivers on stdin carries the matching snake_case event name (`agent_start`, `tool_call`, …) the plugin acts on. The shims wire into these phases:
|
|
61
|
+
|
|
62
|
+
- **TaskStart** (`agent_start`) — run the user and agent auth gates.
|
|
63
|
+
- **PreToolUse** (`tool_call`) — check the tool against Ory Permissions; deny blocks the call (`{"cancel":true}`) in enforce mode.
|
|
64
|
+
- **PostToolUse / TaskComplete / TaskCancel / TaskError / UserPromptSubmit / PreCompact / SessionShutdown** — trace-only audit spans.
|
|
65
|
+
|
|
66
|
+
The plugin is **fail-open** on its own infrastructure failures (network errors, rate limits, missing config): the agent always starts, and enforcement is only as strong as your permission grants.
|
|
67
|
+
|
|
68
|
+
## Agent security
|
|
69
|
+
|
|
70
|
+
Once pointed at an Ory project (local or hosted), Cline's task and every tool call can be governed by Ory.
|
|
71
|
+
|
|
72
|
+
- **Authentication.** The human at the keyboard (the **user**) authenticates interactively via Ory Identities when user login is on (`ORY_USER_LOGIN=true`, off by default — browser PKCE flow on first session, persisted thereafter). The Cline process (the **agent**) gets its own OAuth2 identity via [Dynamic Client Registration (RFC 7591)](https://datatracker.ietf.org/doc/html/rfc7591) on first run.
|
|
73
|
+
- **Authorization.** Before any tool runs, the plugin checks [Ory Permissions](https://www.ory.com/docs/keto) (Zanzibar-style relations) against the user's subject and blocks on `deny`.
|
|
74
|
+
- **Audit.** Every decision is recorded as a structured trace span (NDJSON file and/or OTLP export).
|
|
75
|
+
|
|
76
|
+
### Permission modes: observe → enforce
|
|
77
|
+
|
|
78
|
+
After install the plugin runs in **observe mode**: every tool call is checked, but a deny is recorded as a `permission.observe_deny` audit span and the tool runs anyway.
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
# Grant the current user `use` on every built-in tool (idempotent):
|
|
82
|
+
npx -y -p @ory/cline ory-cline permissions bootstrap
|
|
83
|
+
|
|
84
|
+
# See allowed/denied per tool:
|
|
85
|
+
npx -y -p @ory/cline ory-cline permissions status
|
|
86
|
+
|
|
87
|
+
# Turn on hard blocking once the observe-mode logs look right:
|
|
88
|
+
npx -y -p @ory/cline ory-cline permissions enforce
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Switch back any time with `permissions observe`. To disable Ory entirely (audit logging only), run `ory-cline configure --audit-only`.
|
|
92
|
+
|
|
93
|
+
## Pointing at a real Ory project
|
|
94
|
+
|
|
95
|
+
The Quickstart uses the local stack. To point at a hosted [Ory Network](https://console.ory.sh) project:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
npx -y -p @ory/cline ory-cline configure \
|
|
99
|
+
--project-url https://<id>.projects.oryapis.com \
|
|
100
|
+
--oauth2-client-id <public OAuth2 client id>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
`--oauth2-client-id` is required whenever `--project-url` is set — the user PKCE flow needs a public OAuth2 client registered with the four loopback redirect URIs (`http://127.0.0.1:47823..47826/callback`). See the [repo README](../../README.md) and [`AGENTS.md`](../../AGENTS.md) for the full environment-variable reference and permission-mode semantics. Config is shared across every Ory agent plugin at `~/.config/ory-agent-plugins/config.json`.
|
|
104
|
+
|
|
105
|
+
Without configuration the plugin still loads cleanly and runs in **pass-through mode**: skills work, but nothing is blocked.
|
|
106
|
+
|
|
107
|
+
## Links
|
|
108
|
+
|
|
109
|
+
- [Ory documentation](https://www.ory.com/docs/)
|
|
110
|
+
- [Cline repository](https://github.com/cline/cline)
|
|
111
|
+
- [Repo README](../../README.md) and [AGENTS.md](../../AGENTS.md) — full env-var and permission-mode reference
|
|
112
|
+
|
|
113
|
+
## License
|
|
114
|
+
|
|
115
|
+
Apache-2.0
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Materialize / clean the Ory skill catalog and command-skills for the Cline
|
|
3
|
+
* plugin.
|
|
4
|
+
*
|
|
5
|
+
* Cline has no documented native slash-command system we can rely on, so we
|
|
6
|
+
* follow the Codex/OpenClaw pattern (AGENTS.md Step 7.5): the guide skills
|
|
7
|
+
* plus the local-stack commands (rendered as user-invocable skills) all land
|
|
8
|
+
* under a single `skills/<name>/SKILL.md` tree.
|
|
9
|
+
*
|
|
10
|
+
* Verified against cline@3.0.29: skills live alongside Cline's rules under
|
|
11
|
+
* `.clinerules/skills/` (workspace) or `~/.cline/skills/` (global).
|
|
12
|
+
*/
|
|
13
|
+
export declare function installClineOryAssets(rulesRoot: string): void;
|
|
14
|
+
/** Remove every skill + command-skill this plugin installed under the root. */
|
|
15
|
+
export declare function uninstallClineOryAssets(rulesRoot: string): void;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Materialize / clean the Ory skill catalog and command-skills for the Cline
|
|
4
|
+
* plugin.
|
|
5
|
+
*
|
|
6
|
+
* Cline has no documented native slash-command system we can rely on, so we
|
|
7
|
+
* follow the Codex/OpenClaw pattern (AGENTS.md Step 7.5): the guide skills
|
|
8
|
+
* plus the local-stack commands (rendered as user-invocable skills) all land
|
|
9
|
+
* under a single `skills/<name>/SKILL.md` tree.
|
|
10
|
+
*
|
|
11
|
+
* Verified against cline@3.0.29: skills live alongside Cline's rules under
|
|
12
|
+
* `.clinerules/skills/` (workspace) or `~/.cline/skills/` (global).
|
|
13
|
+
*/
|
|
14
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
17
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
18
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
19
|
+
}
|
|
20
|
+
Object.defineProperty(o, k2, desc);
|
|
21
|
+
}) : (function(o, m, k, k2) {
|
|
22
|
+
if (k2 === undefined) k2 = k;
|
|
23
|
+
o[k2] = m[k];
|
|
24
|
+
}));
|
|
25
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
26
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
27
|
+
}) : function(o, v) {
|
|
28
|
+
o["default"] = v;
|
|
29
|
+
});
|
|
30
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
31
|
+
var ownKeys = function(o) {
|
|
32
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
33
|
+
var ar = [];
|
|
34
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
35
|
+
return ar;
|
|
36
|
+
};
|
|
37
|
+
return ownKeys(o);
|
|
38
|
+
};
|
|
39
|
+
return function (mod) {
|
|
40
|
+
if (mod && mod.__esModule) return mod;
|
|
41
|
+
var result = {};
|
|
42
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
43
|
+
__setModuleDefault(result, mod);
|
|
44
|
+
return result;
|
|
45
|
+
};
|
|
46
|
+
})();
|
|
47
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
48
|
+
exports.installClineOryAssets = installClineOryAssets;
|
|
49
|
+
exports.uninstallClineOryAssets = uninstallClineOryAssets;
|
|
50
|
+
const fs = __importStar(require("node:fs"));
|
|
51
|
+
const path = __importStar(require("node:path"));
|
|
52
|
+
const argus_1 = require("@ory/argus");
|
|
53
|
+
const RENDER_OPTS = {
|
|
54
|
+
binName: "ory-cline",
|
|
55
|
+
packageName: "@ory/cline",
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Skills directory, relative to the Cline rules root the install CLI passes
|
|
59
|
+
* in (`.clinerules/` for workspace installs, `~/.cline/` for
|
|
60
|
+
* global). The guide skills and command-skills share this one tree.
|
|
61
|
+
*/
|
|
62
|
+
function skillsDir(rulesRoot) {
|
|
63
|
+
return path.join(rulesRoot, "skills");
|
|
64
|
+
}
|
|
65
|
+
function installClineOryAssets(rulesRoot) {
|
|
66
|
+
const dir = skillsDir(rulesRoot);
|
|
67
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
68
|
+
const guideSkills = (0, argus_1.renderOrySkills)("cline", RENDER_OPTS);
|
|
69
|
+
const commandSkills = (0, argus_1.renderOryCommands)("cline", RENDER_OPTS).map(argus_1.commandToSkill);
|
|
70
|
+
(0, argus_1.writeSkillTree)(dir, [...guideSkills, ...commandSkills]);
|
|
71
|
+
}
|
|
72
|
+
function safeRmEmpty(dir) {
|
|
73
|
+
try {
|
|
74
|
+
if (fs.existsSync(dir) && fs.readdirSync(dir).length === 0) {
|
|
75
|
+
fs.rmdirSync(dir);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
/* ignore — leave the dir if removal fails. */
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/** Remove every skill + command-skill this plugin installed under the root. */
|
|
83
|
+
function uninstallClineOryAssets(rulesRoot) {
|
|
84
|
+
const dir = skillsDir(rulesRoot);
|
|
85
|
+
if (fs.existsSync(dir)) {
|
|
86
|
+
(0, argus_1.removeSkillDirs)(dir, [...argus_1.ORY_SKILL_NAMES, ...argus_1.ORY_COMMAND_SKILL_NAMES]);
|
|
87
|
+
safeRmEmpty(dir);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Main CLI for @ory/cline.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* npx ory-cline install [--global] Install Ory hook scripts into Cline
|
|
7
|
+
* npx ory-cline uninstall [--global] Remove the Ory hook scripts
|
|
8
|
+
* npx ory-cline configure Set or view Ory credentials
|
|
9
|
+
* npx ory-cline agent <cmd> Manage the agent's OAuth2 (DCR) identity
|
|
10
|
+
* npx ory-cline permissions <cmd> Manage permission mode and tool permissions
|
|
11
|
+
* npx ory-cline setup [options] Write hook scripts directly (fallback)
|
|
12
|
+
* npx ory-cline status Show plugin status
|
|
13
|
+
* npx ory-cline local <cmd> Manage the local Ory dev environment
|
|
14
|
+
*/
|
|
15
|
+
export {};
|
package/dist/cli/main.js
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* Main CLI for @ory/cline.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* npx ory-cline install [--global] Install Ory hook scripts into Cline
|
|
8
|
+
* npx ory-cline uninstall [--global] Remove the Ory hook scripts
|
|
9
|
+
* npx ory-cline configure Set or view Ory credentials
|
|
10
|
+
* npx ory-cline agent <cmd> Manage the agent's OAuth2 (DCR) identity
|
|
11
|
+
* npx ory-cline permissions <cmd> Manage permission mode and tool permissions
|
|
12
|
+
* npx ory-cline setup [options] Write hook scripts directly (fallback)
|
|
13
|
+
* npx ory-cline status Show plugin status
|
|
14
|
+
* npx ory-cline local <cmd> Manage the local Ory dev environment
|
|
15
|
+
*/
|
|
16
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
17
|
+
if (k2 === undefined) k2 = k;
|
|
18
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
19
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
20
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
21
|
+
}
|
|
22
|
+
Object.defineProperty(o, k2, desc);
|
|
23
|
+
}) : (function(o, m, k, k2) {
|
|
24
|
+
if (k2 === undefined) k2 = k;
|
|
25
|
+
o[k2] = m[k];
|
|
26
|
+
}));
|
|
27
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
28
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
29
|
+
}) : function(o, v) {
|
|
30
|
+
o["default"] = v;
|
|
31
|
+
});
|
|
32
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
33
|
+
var ownKeys = function(o) {
|
|
34
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
35
|
+
var ar = [];
|
|
36
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
37
|
+
return ar;
|
|
38
|
+
};
|
|
39
|
+
return ownKeys(o);
|
|
40
|
+
};
|
|
41
|
+
return function (mod) {
|
|
42
|
+
if (mod && mod.__esModule) return mod;
|
|
43
|
+
var result = {};
|
|
44
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
45
|
+
__setModuleDefault(result, mod);
|
|
46
|
+
return result;
|
|
47
|
+
};
|
|
48
|
+
})();
|
|
49
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
50
|
+
const path = __importStar(require("node:path"));
|
|
51
|
+
const fs = __importStar(require("node:fs"));
|
|
52
|
+
const os = __importStar(require("node:os"));
|
|
53
|
+
const argus_1 = require("@ory/argus");
|
|
54
|
+
const PACKAGE_ROOT = path.resolve(__dirname, "..", "..");
|
|
55
|
+
function main() {
|
|
56
|
+
const [command, ...args] = process.argv.slice(2);
|
|
57
|
+
switch (command) {
|
|
58
|
+
case "install":
|
|
59
|
+
install(args);
|
|
60
|
+
postInstallPermissions("ory-cline", "cline").then(() => process.exit(0), (err) => {
|
|
61
|
+
console.error(err.message ?? err);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
});
|
|
64
|
+
break;
|
|
65
|
+
case "uninstall":
|
|
66
|
+
uninstall(args);
|
|
67
|
+
break;
|
|
68
|
+
case "configure":
|
|
69
|
+
(0, argus_1.runConfigureCommand)("ory-cline", args);
|
|
70
|
+
break;
|
|
71
|
+
case "agent":
|
|
72
|
+
(0, argus_1.runAgentCommand)("ory-cline", args).then((code) => process.exit(code), (err) => {
|
|
73
|
+
console.error(err.message ?? err);
|
|
74
|
+
process.exit(1);
|
|
75
|
+
});
|
|
76
|
+
break;
|
|
77
|
+
case "permissions":
|
|
78
|
+
(0, argus_1.runPermissionsCommand)("ory-cline", "cline", args).then((code) => process.exit(code), (err) => {
|
|
79
|
+
console.error(err.message ?? err);
|
|
80
|
+
process.exit(1);
|
|
81
|
+
});
|
|
82
|
+
break;
|
|
83
|
+
case "setup":
|
|
84
|
+
runSetup(args);
|
|
85
|
+
break;
|
|
86
|
+
case "status":
|
|
87
|
+
status().then(() => process.exit(0), (err) => {
|
|
88
|
+
console.error(err.message ?? err);
|
|
89
|
+
process.exit(1);
|
|
90
|
+
});
|
|
91
|
+
break;
|
|
92
|
+
case "local":
|
|
93
|
+
(0, argus_1.runLocalCommand)("ory-cline", args).catch((err) => {
|
|
94
|
+
console.error(err.message ?? err);
|
|
95
|
+
process.exit(1);
|
|
96
|
+
});
|
|
97
|
+
break;
|
|
98
|
+
case "help":
|
|
99
|
+
case "--help":
|
|
100
|
+
case "-h":
|
|
101
|
+
case undefined:
|
|
102
|
+
help();
|
|
103
|
+
break;
|
|
104
|
+
default:
|
|
105
|
+
console.error(`Unknown command: ${command}`);
|
|
106
|
+
help();
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Cline discovers hooks by directory, with no native plugin-install command,
|
|
112
|
+
* so install IS the setup step: write the per-event hook scripts and
|
|
113
|
+
* materialize the Ory skills. The setup module reads its arguments from
|
|
114
|
+
* `process.argv`.
|
|
115
|
+
*/
|
|
116
|
+
function install(args) {
|
|
117
|
+
console.log("Installing Ory hook scripts and skills for Cline...");
|
|
118
|
+
runSetup(args);
|
|
119
|
+
(0, argus_1.printEnvHelp)("ory-cline");
|
|
120
|
+
}
|
|
121
|
+
function uninstall(args) {
|
|
122
|
+
runSetup(["--uninstall", ...args]);
|
|
123
|
+
}
|
|
124
|
+
function runSetup(args) {
|
|
125
|
+
process.argv = ["node", "setup.js", ...args];
|
|
126
|
+
require("./setup.js");
|
|
127
|
+
}
|
|
128
|
+
async function postInstallPermissions(binName, harness) {
|
|
129
|
+
const bootstrapped = await (0, argus_1.maybeAutoBootstrap)(binName, harness);
|
|
130
|
+
(0, argus_1.printPermissionsOnboardingHelp)(binName, harness, {
|
|
131
|
+
bootstrappedAutomatically: bootstrapped,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
async function status() {
|
|
135
|
+
await (0, argus_1.runStatusCommand)("ory-cline", "cline", {
|
|
136
|
+
title: "Cline",
|
|
137
|
+
printPluginSection: () => {
|
|
138
|
+
const workspaceHooks = path.join(process.cwd(), ".clinerules", "hooks");
|
|
139
|
+
const globalHooks = path.join(os.homedir(), ".cline", "hooks");
|
|
140
|
+
console.log("Hooks & plugin:");
|
|
141
|
+
console.log(` Hook script: ${fs.existsSync(path.join(PACKAGE_ROOT, "dist", "hook.js")) ? "built" : "NOT BUILT (run pnpm build)"}`);
|
|
142
|
+
console.log(` Workspace dir: ${fs.existsSync(workspaceHooks) ? `installed (${workspaceHooks})` : "not installed"}`);
|
|
143
|
+
console.log(` Global dir: ${fs.existsSync(globalHooks) ? `installed (${globalHooks})` : "not installed"}`);
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
function help() {
|
|
148
|
+
console.log(`
|
|
149
|
+
ory-cline — Ory plugin for Cline
|
|
150
|
+
|
|
151
|
+
Usage:
|
|
152
|
+
npx ory-cline <command> [options]
|
|
153
|
+
|
|
154
|
+
Commands:
|
|
155
|
+
install [--global] Install the Ory hook scripts into Cline
|
|
156
|
+
uninstall [--global] Remove the Ory hook scripts from Cline
|
|
157
|
+
configure Set or view Ory project URL and API key
|
|
158
|
+
agent <cmd> Manage the agent's OAuth2 (DCR) identity (status, unregister)
|
|
159
|
+
permissions <cmd> Manage permission mode and tool permissions (status, bootstrap, observe, enforce)
|
|
160
|
+
setup [options] Write hook scripts directly to the hooks directory (fallback)
|
|
161
|
+
status Show plugin status and configuration
|
|
162
|
+
local <cmd> Manage local Ory dev environment (up, down, status, seed, ...)
|
|
163
|
+
|
|
164
|
+
After installing, the per-event hook scripts wire into these Cline lifecycle events:
|
|
165
|
+
TaskStart Verify the user and agent identities via Ory
|
|
166
|
+
PreToolUse Check tool permissions via Ory Permissions (blocking)
|
|
167
|
+
PostToolUse Audit log tool executions
|
|
168
|
+
|
|
169
|
+
Examples:
|
|
170
|
+
npx ory-cline install # Install hook scripts into ./.clinerules/hooks
|
|
171
|
+
npx ory-cline install --global # Install into ~/.cline/hooks
|
|
172
|
+
npx ory-cline status # Check configuration
|
|
173
|
+
npx ory-cline uninstall # Remove hook scripts
|
|
174
|
+
`);
|
|
175
|
+
}
|
|
176
|
+
main();
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Setup CLI for the Ory Cline plugin.
|
|
4
|
+
*
|
|
5
|
+
* Cline discovers hooks by directory: it runs executable scripts whose
|
|
6
|
+
* filename matches the event name, found in a hooks directory. There is no
|
|
7
|
+
* per-hook config file — presence of the script IS the registration. We
|
|
8
|
+
* therefore write one thin shell shim per event into the hooks directory and
|
|
9
|
+
* `chmod 0755` it. Each shim execs the published hook binary via npx,
|
|
10
|
+
* forwarding stdin so the JSON payload reaches the Node entry point.
|
|
11
|
+
*
|
|
12
|
+
* Hooks directory (verified against cline@3.0.29):
|
|
13
|
+
* workspace : <projectDir>/.clinerules/hooks/
|
|
14
|
+
* global : ~/.cline/hooks/ (--global, --hooks-dir default)
|
|
15
|
+
*
|
|
16
|
+
* Usage:
|
|
17
|
+
* npx ory-cline-setup # workspace install (cwd)
|
|
18
|
+
* npx ory-cline-setup --project-dir /repo
|
|
19
|
+
* npx ory-cline-setup --global # install to ~/.cline/hooks
|
|
20
|
+
* npx ory-cline-setup --print # print the shim scripts to stdout, write nothing
|
|
21
|
+
* npx ory-cline-setup --uninstall # remove only the Ory-written scripts
|
|
22
|
+
*/
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* Setup CLI for the Ory Cline plugin.
|
|
5
|
+
*
|
|
6
|
+
* Cline discovers hooks by directory: it runs executable scripts whose
|
|
7
|
+
* filename matches the event name, found in a hooks directory. There is no
|
|
8
|
+
* per-hook config file — presence of the script IS the registration. We
|
|
9
|
+
* therefore write one thin shell shim per event into the hooks directory and
|
|
10
|
+
* `chmod 0755` it. Each shim execs the published hook binary via npx,
|
|
11
|
+
* forwarding stdin so the JSON payload reaches the Node entry point.
|
|
12
|
+
*
|
|
13
|
+
* Hooks directory (verified against cline@3.0.29):
|
|
14
|
+
* workspace : <projectDir>/.clinerules/hooks/
|
|
15
|
+
* global : ~/.cline/hooks/ (--global, --hooks-dir default)
|
|
16
|
+
*
|
|
17
|
+
* Usage:
|
|
18
|
+
* npx ory-cline-setup # workspace install (cwd)
|
|
19
|
+
* npx ory-cline-setup --project-dir /repo
|
|
20
|
+
* npx ory-cline-setup --global # install to ~/.cline/hooks
|
|
21
|
+
* npx ory-cline-setup --print # print the shim scripts to stdout, write nothing
|
|
22
|
+
* npx ory-cline-setup --uninstall # remove only the Ory-written scripts
|
|
23
|
+
*/
|
|
24
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
25
|
+
if (k2 === undefined) k2 = k;
|
|
26
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
27
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
28
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
29
|
+
}
|
|
30
|
+
Object.defineProperty(o, k2, desc);
|
|
31
|
+
}) : (function(o, m, k, k2) {
|
|
32
|
+
if (k2 === undefined) k2 = k;
|
|
33
|
+
o[k2] = m[k];
|
|
34
|
+
}));
|
|
35
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
36
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
37
|
+
}) : function(o, v) {
|
|
38
|
+
o["default"] = v;
|
|
39
|
+
});
|
|
40
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
41
|
+
var ownKeys = function(o) {
|
|
42
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
43
|
+
var ar = [];
|
|
44
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
45
|
+
return ar;
|
|
46
|
+
};
|
|
47
|
+
return ownKeys(o);
|
|
48
|
+
};
|
|
49
|
+
return function (mod) {
|
|
50
|
+
if (mod && mod.__esModule) return mod;
|
|
51
|
+
var result = {};
|
|
52
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
53
|
+
__setModuleDefault(result, mod);
|
|
54
|
+
return result;
|
|
55
|
+
};
|
|
56
|
+
})();
|
|
57
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
58
|
+
const fs = __importStar(require("node:fs"));
|
|
59
|
+
const os = __importStar(require("node:os"));
|
|
60
|
+
const path = __importStar(require("node:path"));
|
|
61
|
+
const argus_1 = require("@ory/argus");
|
|
62
|
+
const assets_js_1 = require("./assets.js");
|
|
63
|
+
const PACKAGE_NAME = "@ory/cline";
|
|
64
|
+
const HOOK_BIN = "ory-cline-hook";
|
|
65
|
+
/**
|
|
66
|
+
* Cline hook events we register. Each maps to a script whose filename matches
|
|
67
|
+
* the PascalCase event name in the hooks directory (verified against
|
|
68
|
+
* cline@3.0.29). At runtime, the payload Cline writes to the script's stdin
|
|
69
|
+
* carries the snake_case `hookName` the handler switches on:
|
|
70
|
+
*
|
|
71
|
+
* TaskStart → agent_start (auth gates)
|
|
72
|
+
* TaskResume → agent_resume (trace-only)
|
|
73
|
+
* TaskCancel → agent_abort (trace-only)
|
|
74
|
+
* TaskComplete → agent_end (trace-only)
|
|
75
|
+
* TaskError → agent_error (trace-only)
|
|
76
|
+
* PreToolUse → tool_call (permission gate, blocking)
|
|
77
|
+
* PostToolUse → tool_result (trace-only)
|
|
78
|
+
* UserPromptSubmit → prompt_submit (trace-only)
|
|
79
|
+
* PreCompact → pre_compact (trace-only)
|
|
80
|
+
* SessionShutdown → session_shutdown (trace-only)
|
|
81
|
+
*/
|
|
82
|
+
const HOOK_EVENTS = [
|
|
83
|
+
"TaskStart",
|
|
84
|
+
"TaskResume",
|
|
85
|
+
"TaskCancel",
|
|
86
|
+
"TaskComplete",
|
|
87
|
+
"TaskError",
|
|
88
|
+
"PreToolUse",
|
|
89
|
+
"PostToolUse",
|
|
90
|
+
"UserPromptSubmit",
|
|
91
|
+
"PreCompact",
|
|
92
|
+
"SessionShutdown",
|
|
93
|
+
];
|
|
94
|
+
/**
|
|
95
|
+
* The version-pinned npx command that each shim execs. Resolving via npx
|
|
96
|
+
* keeps the binary off the PATH requirement and pins the version the shim was
|
|
97
|
+
* generated with (read from this package's own package.json).
|
|
98
|
+
*/
|
|
99
|
+
function hookCommand() {
|
|
100
|
+
return `npx -y -p ${PACKAGE_NAME}@${readPackageVersion()} ${HOOK_BIN}`;
|
|
101
|
+
}
|
|
102
|
+
function readPackageVersion() {
|
|
103
|
+
// dist/cli/setup.js -> package root is two levels up.
|
|
104
|
+
const pkgPath = path.resolve(__dirname, "..", "..", "package.json");
|
|
105
|
+
try {
|
|
106
|
+
const parsed = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
107
|
+
return parsed.version ?? "latest";
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return "latest";
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Body of a per-event shim. A POSIX shell script that execs the hook binary,
|
|
115
|
+
* forwarding stdin/stdout transparently. `exec` replaces the shell so the
|
|
116
|
+
* hook's exit status and stdout flow straight back to Cline.
|
|
117
|
+
*/
|
|
118
|
+
function shimBody() {
|
|
119
|
+
return `#!/bin/sh
|
|
120
|
+
# Ory plugin hook shim for Cline. Generated by ${PACKAGE_NAME} — do not edit.
|
|
121
|
+
# Forwards the event payload (stdin JSON) to the Ory hook and returns its
|
|
122
|
+
# JSON decision on stdout.
|
|
123
|
+
exec ${hookCommand()}
|
|
124
|
+
`;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Resolve the hooks directory based on --global / --project-dir.
|
|
128
|
+
*
|
|
129
|
+
* Verified against `cline --help` (2026-06-28): the global hooks dir is
|
|
130
|
+
* `~/.cline/hooks` (the `--hooks-dir` default). The workspace hooks dir is
|
|
131
|
+
* `<projectDir>/.clinerules/hooks` (the string `clinerules/hooks` is present
|
|
132
|
+
* in the cline binary).
|
|
133
|
+
*/
|
|
134
|
+
function getHooksDir(args) {
|
|
135
|
+
if (args.global) {
|
|
136
|
+
return path.join(os.homedir(), ".cline", "hooks");
|
|
137
|
+
}
|
|
138
|
+
return path.join(args.projectDir, ".clinerules", "hooks");
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Rules root (parent of the hooks dir) where skills are materialized:
|
|
142
|
+
* `.clinerules/` for workspace installs, `~/.cline/` for global installs.
|
|
143
|
+
* Verified against cline@3.0.29.
|
|
144
|
+
*/
|
|
145
|
+
function getRulesRoot(args) {
|
|
146
|
+
if (args.global) {
|
|
147
|
+
return path.join(os.homedir(), ".cline");
|
|
148
|
+
}
|
|
149
|
+
return path.join(args.projectDir, ".clinerules");
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Marker so uninstall only removes scripts we wrote — never a hook script the
|
|
153
|
+
* user authored that happens to share an event name.
|
|
154
|
+
*/
|
|
155
|
+
const ORY_MARKER = `${PACKAGE_NAME}`;
|
|
156
|
+
function isOryShim(file) {
|
|
157
|
+
try {
|
|
158
|
+
return fs.readFileSync(file, "utf-8").includes(ORY_MARKER);
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
function main() {
|
|
165
|
+
if (process.argv.includes("--help") || process.argv.includes("-h")) {
|
|
166
|
+
(0, argus_1.printSetupHelp)("ory-cline-setup", "Cline", {
|
|
167
|
+
supportsGlobal: true,
|
|
168
|
+
globalPath: "~/.cline/hooks",
|
|
169
|
+
});
|
|
170
|
+
process.exit(0);
|
|
171
|
+
}
|
|
172
|
+
const args = (0, argus_1.parseSetupArgs)({ supportsGlobal: true });
|
|
173
|
+
const body = shimBody();
|
|
174
|
+
if (args.print) {
|
|
175
|
+
for (const event of HOOK_EVENTS) {
|
|
176
|
+
console.log(`# ── ${event} ───────────────────────────────`);
|
|
177
|
+
console.log(body);
|
|
178
|
+
}
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
const hooksDir = getHooksDir(args);
|
|
182
|
+
const rulesRoot = getRulesRoot(args);
|
|
183
|
+
if (args.uninstall) {
|
|
184
|
+
if (!fs.existsSync(hooksDir)) {
|
|
185
|
+
console.log("No hooks directory found. Nothing to uninstall.");
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
let removed = 0;
|
|
189
|
+
for (const event of HOOK_EVENTS) {
|
|
190
|
+
const file = path.join(hooksDir, event);
|
|
191
|
+
if (fs.existsSync(file) && isOryShim(file)) {
|
|
192
|
+
fs.unlinkSync(file);
|
|
193
|
+
removed++;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
console.log(`Removed ${removed} Ory hook script(s) from ${hooksDir}`);
|
|
197
|
+
}
|
|
198
|
+
(0, assets_js_1.uninstallClineOryAssets)(rulesRoot);
|
|
199
|
+
console.log(`Removed Ory skills from ${path.join(rulesRoot, "skills")}`);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
fs.mkdirSync(hooksDir, { recursive: true });
|
|
203
|
+
for (const event of HOOK_EVENTS) {
|
|
204
|
+
const file = path.join(hooksDir, event);
|
|
205
|
+
fs.writeFileSync(file, body, { mode: 0o755 });
|
|
206
|
+
// Re-assert the mode in case a pre-existing file ignored the create mode.
|
|
207
|
+
fs.chmodSync(file, 0o755);
|
|
208
|
+
}
|
|
209
|
+
(0, assets_js_1.installClineOryAssets)(rulesRoot);
|
|
210
|
+
console.log(`Ory hook scripts installed to ${hooksDir}`);
|
|
211
|
+
console.log(` Scripts: ${HOOK_EVENTS.join(", ")}`);
|
|
212
|
+
console.log(`Ory skills installed to ${path.join(rulesRoot, "skills")}`);
|
|
213
|
+
(0, argus_1.printNextSteps)("Cline", "npx ory-cline-setup --uninstall");
|
|
214
|
+
}
|
|
215
|
+
main();
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { OryAgentClient, ensureUserAuthenticated, ensureAgentIdentity } from "@ory/argus";
|
|
2
|
+
import type { ClineHookInput, ClineHookOutput } from "./types.js";
|
|
3
|
+
export interface HandleHookEventDeps {
|
|
4
|
+
/** Test injection point for the user login flow. */
|
|
5
|
+
userLogin?: typeof ensureUserAuthenticated;
|
|
6
|
+
/** Test injection point for the agent identity gate. */
|
|
7
|
+
agentGate?: typeof ensureAgentIdentity;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Route a Cline file-hook event to the appropriate Ory integration.
|
|
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.
|
|
19
|
+
*/
|
|
20
|
+
export declare function handleHookEvent(input: ClineHookInput, client: OryAgentClient, deps?: HandleHookEventDeps): Promise<ClineHookOutput>;
|
package/dist/handlers.js
ADDED
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.handleHookEvent = handleHookEvent;
|
|
4
|
+
const argus_1 = require("@ory/argus");
|
|
5
|
+
/**
|
|
6
|
+
* Route a Cline file-hook event to the appropriate Ory integration.
|
|
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.
|
|
15
|
+
*/
|
|
16
|
+
async function handleHookEvent(input, client, deps = {}) {
|
|
17
|
+
const event = input.hookName;
|
|
18
|
+
client.logger.debug("hook.received", {
|
|
19
|
+
event,
|
|
20
|
+
taskId: input.taskId,
|
|
21
|
+
toolName: input.preToolUse?.toolName ?? input.postToolUse?.toolName,
|
|
22
|
+
});
|
|
23
|
+
// Set trace context so all spans within this hook invocation correlate.
|
|
24
|
+
client.tracer.setContext({
|
|
25
|
+
traceId: (0, argus_1.deriveTraceId)(input.taskId),
|
|
26
|
+
sessionId: input.taskId,
|
|
27
|
+
});
|
|
28
|
+
try {
|
|
29
|
+
switch (event) {
|
|
30
|
+
case "agent_start":
|
|
31
|
+
return await handleTaskStart(input, client, deps);
|
|
32
|
+
case "agent_resume":
|
|
33
|
+
return await handleTaskResume(input, client);
|
|
34
|
+
case "agent_abort":
|
|
35
|
+
return await handleTaskCancel(input, client);
|
|
36
|
+
case "agent_end":
|
|
37
|
+
return await handleTaskComplete(input, client);
|
|
38
|
+
case "agent_error":
|
|
39
|
+
return await handleTaskError(input, client);
|
|
40
|
+
case "tool_call":
|
|
41
|
+
return await handlePreToolUse(input, client);
|
|
42
|
+
case "tool_result":
|
|
43
|
+
return await handlePostToolUse(input, client);
|
|
44
|
+
case "prompt_submit":
|
|
45
|
+
return await handleUserPromptSubmit(input, client);
|
|
46
|
+
case "pre_compact":
|
|
47
|
+
return await handlePreCompact(input, client);
|
|
48
|
+
case "session_shutdown":
|
|
49
|
+
return await handleSessionShutdown(input, client);
|
|
50
|
+
default:
|
|
51
|
+
client.logger.debug("hook.passthrough", { event });
|
|
52
|
+
client.tracer.record("hook.passthrough", "skipped", {
|
|
53
|
+
attributes: { event },
|
|
54
|
+
});
|
|
55
|
+
return {};
|
|
56
|
+
}
|
|
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
|
+
});
|
|
86
|
+
// 3. User → agent delegation tuple. Best-effort; never blocks.
|
|
87
|
+
await writeDelegation(client);
|
|
88
|
+
if (!decision.proceed) {
|
|
89
|
+
return {
|
|
90
|
+
cancel: true,
|
|
91
|
+
errorMessage: decision.reason ?? "Ory user login was declined or could not complete.",
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
if (decision.mode !== "disabled") {
|
|
95
|
+
return {};
|
|
96
|
+
}
|
|
97
|
+
// Legacy verify/log path (ORY_USER_LOGIN unset).
|
|
98
|
+
const resolved = (0, argus_1.resolveConfig)();
|
|
99
|
+
if (resolved.auditOnly) {
|
|
100
|
+
client.logger.info("config.audit_only", {
|
|
101
|
+
message: "Audit-only mode enabled. Auth and permission checks are disabled.",
|
|
102
|
+
});
|
|
103
|
+
return {};
|
|
104
|
+
}
|
|
105
|
+
if (!resolved.projectUrl) {
|
|
106
|
+
client.logger.warn("config.not_configured", {
|
|
107
|
+
message: "Ory plugin is not configured. Auth and permission checks are disabled. " +
|
|
108
|
+
"Run 'npx ory-cline configure' to connect to an Ory project.",
|
|
109
|
+
});
|
|
110
|
+
return {};
|
|
111
|
+
}
|
|
112
|
+
const sessionToken = process.env.ORY_SESSION_TOKEN;
|
|
113
|
+
const oauth2Token = process.env.ORY_OAUTH2_TOKEN;
|
|
114
|
+
if (sessionToken) {
|
|
115
|
+
await verifySessionToken(sessionToken, client);
|
|
116
|
+
return {};
|
|
117
|
+
}
|
|
118
|
+
if (oauth2Token) {
|
|
119
|
+
await verifyOAuth2Token(oauth2Token, client);
|
|
120
|
+
return {};
|
|
121
|
+
}
|
|
122
|
+
client.logger.warn("session.no_credentials", {
|
|
123
|
+
message: "Neither ORY_SESSION_TOKEN nor ORY_OAUTH2_TOKEN is set. " +
|
|
124
|
+
"Skipping authentication.",
|
|
125
|
+
});
|
|
126
|
+
return {};
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Write the user → agent delegation tuple once both principal subjects are
|
|
130
|
+
* populated. Idempotent (409s absorbed by the core). Failures are logged and
|
|
131
|
+
* swallowed — delegation tuples are audit-trail only.
|
|
132
|
+
*/
|
|
133
|
+
async function writeDelegation(client) {
|
|
134
|
+
const userSubject = client.userPrincipal?.subject;
|
|
135
|
+
const agentSubject = client.agentPrincipal?.subject;
|
|
136
|
+
if (!userSubject || !agentSubject)
|
|
137
|
+
return;
|
|
138
|
+
const namespace = resolveNamespace();
|
|
139
|
+
try {
|
|
140
|
+
await client.createRelationship({
|
|
141
|
+
namespace,
|
|
142
|
+
object: `agent:${agentSubject}`,
|
|
143
|
+
relation: "delegate",
|
|
144
|
+
subjectId: `user:${userSubject}`,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
catch (err) {
|
|
148
|
+
client.logger.warn("delegation.write_failed", {
|
|
149
|
+
message: err?.message ?? String(err),
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
async function verifySessionToken(token, client) {
|
|
154
|
+
try {
|
|
155
|
+
const session = await client.verifySession(token);
|
|
156
|
+
if (!session.active) {
|
|
157
|
+
client.logger.warn("session.inactive", {
|
|
158
|
+
message: "Ory session is not active. Re-authenticate to enable auth checks.",
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
catch (err) {
|
|
163
|
+
const oryErr = err;
|
|
164
|
+
client.logger.warn("session.verify_failed", {
|
|
165
|
+
code: oryErr.code,
|
|
166
|
+
message: oryErr.message,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
async function verifyOAuth2Token(token, client) {
|
|
171
|
+
try {
|
|
172
|
+
const tokenInfo = await client.introspectToken(token);
|
|
173
|
+
if (!tokenInfo.active) {
|
|
174
|
+
client.logger.warn("oauth2.token_inactive", {
|
|
175
|
+
message: "Ory OAuth2 token is not active. Obtain a new token to enable auth checks.",
|
|
176
|
+
});
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
client.logger.info("oauth2.session_authenticated", {
|
|
180
|
+
clientId: tokenInfo.clientId,
|
|
181
|
+
subject: tokenInfo.subject,
|
|
182
|
+
scope: tokenInfo.scope,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
catch (err) {
|
|
186
|
+
const oryErr = err;
|
|
187
|
+
client.logger.warn("oauth2.introspect_failed", {
|
|
188
|
+
code: oryErr.code,
|
|
189
|
+
message: oryErr.message,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
// ─── tool_call (tool.before, blocking) ─────────────────────────────
|
|
194
|
+
async function handlePreToolUse(input, client) {
|
|
195
|
+
const toolName = input.preToolUse?.toolName ?? "unknown";
|
|
196
|
+
const toolInput = input.preToolUse?.parameters;
|
|
197
|
+
client.logger.info("lifecycle.pre_tool_use", {
|
|
198
|
+
taskId: input.taskId,
|
|
199
|
+
toolName,
|
|
200
|
+
toolInput,
|
|
201
|
+
});
|
|
202
|
+
const inputSummary = (0, argus_1.summarizeToolInput)(toolName, toolInput);
|
|
203
|
+
// In audit-only mode, log the invocation but skip permission checks.
|
|
204
|
+
if ((0, argus_1.resolveConfig)().auditOnly) {
|
|
205
|
+
client.tracer.record("tool.invoke", "ok", {
|
|
206
|
+
attributes: { toolName, ...inputSummary },
|
|
207
|
+
});
|
|
208
|
+
return {};
|
|
209
|
+
}
|
|
210
|
+
const subject = (0, argus_1.resolveUserSubject)(client, `session:${input.taskId}`);
|
|
211
|
+
const subjectId = (0, argus_1.subjectLabel)(subject);
|
|
212
|
+
const namespace = resolveNamespace();
|
|
213
|
+
try {
|
|
214
|
+
const outcome = await (0, argus_1.gateToolCall)(client, {
|
|
215
|
+
harness: "cline",
|
|
216
|
+
toolName,
|
|
217
|
+
check: { namespace, object: toolName, relation: "use", ...subject },
|
|
218
|
+
spanAttributes: { toolName },
|
|
219
|
+
});
|
|
220
|
+
// Interactive tools (operator-extensible via ORY_INTERACTIVE_TOOLS):
|
|
221
|
+
// user.interaction span is already recorded; pass through.
|
|
222
|
+
if (outcome.kind === "interactive") {
|
|
223
|
+
return {};
|
|
224
|
+
}
|
|
225
|
+
const decision = outcome;
|
|
226
|
+
if (decision.kind === "fail_open") {
|
|
227
|
+
return handlePermissionError(decision.error, toolName, client);
|
|
228
|
+
}
|
|
229
|
+
const attrs = { toolName, ...inputSummary };
|
|
230
|
+
const decisionAttrs = decision.spanAttributes;
|
|
231
|
+
if (decision.kind === "allow") {
|
|
232
|
+
client.tracer.record("tool.invoke", "ok", {
|
|
233
|
+
attributes: { ...attrs, ...decisionAttrs, allowed: true },
|
|
234
|
+
});
|
|
235
|
+
return {};
|
|
236
|
+
}
|
|
237
|
+
if (decision.kind === "observe") {
|
|
238
|
+
client.tracer.record("tool.block", "denied", {
|
|
239
|
+
attributes: {
|
|
240
|
+
...attrs,
|
|
241
|
+
...decisionAttrs,
|
|
242
|
+
allowed: false,
|
|
243
|
+
...(0, argus_1.alertAttributes)(false),
|
|
244
|
+
},
|
|
245
|
+
});
|
|
246
|
+
client.tracer.record("tool.invoke", "ok", {
|
|
247
|
+
attributes: { ...attrs, ...decisionAttrs, allowed: false, observed: true },
|
|
248
|
+
});
|
|
249
|
+
return {};
|
|
250
|
+
}
|
|
251
|
+
// deny → cancel the tool.
|
|
252
|
+
client.tracer.record("tool.block", "denied", {
|
|
253
|
+
attributes: {
|
|
254
|
+
...attrs,
|
|
255
|
+
...decisionAttrs,
|
|
256
|
+
allowed: false,
|
|
257
|
+
...(0, argus_1.alertAttributes)(true),
|
|
258
|
+
},
|
|
259
|
+
});
|
|
260
|
+
return {
|
|
261
|
+
cancel: true,
|
|
262
|
+
errorMessage: (0, argus_1.formatDenialMessage)({ tool: toolName, subjectId, namespace }),
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
catch (err) {
|
|
266
|
+
return handlePermissionError(err, toolName, client);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
// ─── tool_result (tool.after) ──────────────────────────────────────
|
|
270
|
+
async function handlePostToolUse(input, client) {
|
|
271
|
+
const toolName = input.postToolUse?.toolName ?? "unknown";
|
|
272
|
+
const toolInput = input.postToolUse?.parameters;
|
|
273
|
+
const toolResult = input.postToolUse?.result;
|
|
274
|
+
client.logger.info("lifecycle.post_tool_use", {
|
|
275
|
+
taskId: input.taskId,
|
|
276
|
+
toolName,
|
|
277
|
+
success: input.postToolUse?.success,
|
|
278
|
+
});
|
|
279
|
+
client.tracer.record("tool.complete", "ok", {
|
|
280
|
+
attributes: {
|
|
281
|
+
toolName,
|
|
282
|
+
success: input.postToolUse?.success,
|
|
283
|
+
executionTimeMs: input.postToolUse?.executionTimeMs,
|
|
284
|
+
...(0, argus_1.summarizeToolInput)(toolName, toolInput),
|
|
285
|
+
...(0, argus_1.summarizeToolOutput)(toolName, toolResult),
|
|
286
|
+
},
|
|
287
|
+
});
|
|
288
|
+
return {};
|
|
289
|
+
}
|
|
290
|
+
// ─── Trace-only lifecycle events ───────────────────────────────────
|
|
291
|
+
async function handleTaskResume(input, client) {
|
|
292
|
+
client.logger.info("lifecycle.task_resume", { taskId: input.taskId });
|
|
293
|
+
client.tracer.record("session.start", "ok", {
|
|
294
|
+
attributes: { resumed: true, clineVersion: input.clineVersion },
|
|
295
|
+
});
|
|
296
|
+
return {};
|
|
297
|
+
}
|
|
298
|
+
async function handleTaskCancel(input, client) {
|
|
299
|
+
client.logger.info("lifecycle.task_cancel", { taskId: input.taskId });
|
|
300
|
+
client.tracer.record("session.end", "ok", {
|
|
301
|
+
attributes: { reason: "abort" },
|
|
302
|
+
});
|
|
303
|
+
return {};
|
|
304
|
+
}
|
|
305
|
+
async function handleTaskComplete(input, client) {
|
|
306
|
+
client.logger.info("lifecycle.task_complete", { taskId: input.taskId });
|
|
307
|
+
client.tracer.record("session.end", "ok", {
|
|
308
|
+
attributes: { reason: "complete" },
|
|
309
|
+
});
|
|
310
|
+
return {};
|
|
311
|
+
}
|
|
312
|
+
async function handleTaskError(input, client) {
|
|
313
|
+
client.logger.info("lifecycle.task_error", { taskId: input.taskId });
|
|
314
|
+
client.tracer.record("session.end", "error", {
|
|
315
|
+
attributes: { reason: "error" },
|
|
316
|
+
});
|
|
317
|
+
return {};
|
|
318
|
+
}
|
|
319
|
+
async function handleSessionShutdown(input, client) {
|
|
320
|
+
client.logger.info("lifecycle.session_shutdown", { taskId: input.taskId });
|
|
321
|
+
client.tracer.record("session.end", "ok", {
|
|
322
|
+
attributes: { stage: "session_shutdown" },
|
|
323
|
+
});
|
|
324
|
+
return {};
|
|
325
|
+
}
|
|
326
|
+
async function handleUserPromptSubmit(input, client) {
|
|
327
|
+
client.logger.info("lifecycle.user_prompt_submit", { taskId: input.taskId });
|
|
328
|
+
// User-facing phase: the human is the decision-maker. Audit span only.
|
|
329
|
+
client.tracer.record("user.prompt", "ok", {
|
|
330
|
+
attributes: { stage: "user_prompt_submit" },
|
|
331
|
+
});
|
|
332
|
+
return {};
|
|
333
|
+
}
|
|
334
|
+
async function handlePreCompact(input, client) {
|
|
335
|
+
client.logger.info("lifecycle.pre_compact", { taskId: input.taskId });
|
|
336
|
+
client.tracer.record("compaction", "ok", {
|
|
337
|
+
attributes: { stage: "pre_compact" },
|
|
338
|
+
});
|
|
339
|
+
return {};
|
|
340
|
+
}
|
|
341
|
+
// ─── Helpers ────────────────────────────────────────────────────────
|
|
342
|
+
function resolveNamespace() {
|
|
343
|
+
return process.env.ORY_PERMISSION_NAMESPACE ?? "AgentTools";
|
|
344
|
+
}
|
|
345
|
+
function handlePermissionError(oryErr, toolName, client) {
|
|
346
|
+
if (oryErr.code === "network_error") {
|
|
347
|
+
client.logger.warn("permission.network_error", {
|
|
348
|
+
toolName,
|
|
349
|
+
message: "Ory unreachable, failing open",
|
|
350
|
+
});
|
|
351
|
+
return {};
|
|
352
|
+
}
|
|
353
|
+
if (oryErr.code === "rate_limited") {
|
|
354
|
+
client.logger.warn("permission.rate_limited", {
|
|
355
|
+
toolName,
|
|
356
|
+
message: "Ory rate limited, failing open",
|
|
357
|
+
});
|
|
358
|
+
return {};
|
|
359
|
+
}
|
|
360
|
+
// For other errors, also fail open but log prominently.
|
|
361
|
+
client.logger.error("permission.check.error", {
|
|
362
|
+
toolName,
|
|
363
|
+
code: oryErr.code,
|
|
364
|
+
message: oryErr.message,
|
|
365
|
+
});
|
|
366
|
+
return {};
|
|
367
|
+
}
|
package/dist/hook.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Cline file-hook entry point.
|
|
4
|
+
*
|
|
5
|
+
* Cline invokes this script (named after the event, e.g. PreToolUse) for each
|
|
6
|
+
* hook event.
|
|
7
|
+
* Input: JSON on stdin
|
|
8
|
+
* Output: JSON on stdout
|
|
9
|
+
*
|
|
10
|
+
* Blocking model: a tool_call response of `{ "cancel": true, ... }` prevents
|
|
11
|
+
* the tool from running. The `cancel` flag is the signal — exit code 0 is
|
|
12
|
+
* correct in all cases. (Cline also treats exit code 130 as a cancellation,
|
|
13
|
+
* but we deliberately do NOT rely on exit codes here.)
|
|
14
|
+
*
|
|
15
|
+
* Verified against cline@3.0.29.
|
|
16
|
+
*/
|
|
17
|
+
export {};
|
package/dist/hook.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* Cline file-hook entry point.
|
|
5
|
+
*
|
|
6
|
+
* Cline invokes this script (named after the event, e.g. PreToolUse) for each
|
|
7
|
+
* hook event.
|
|
8
|
+
* Input: JSON on stdin
|
|
9
|
+
* Output: JSON on stdout
|
|
10
|
+
*
|
|
11
|
+
* Blocking model: a tool_call response of `{ "cancel": true, ... }` prevents
|
|
12
|
+
* the tool from running. The `cancel` flag is the signal — exit code 0 is
|
|
13
|
+
* correct in all cases. (Cline also treats exit code 130 as a cancellation,
|
|
14
|
+
* but we deliberately do NOT rely on exit codes here.)
|
|
15
|
+
*
|
|
16
|
+
* Verified against cline@3.0.29.
|
|
17
|
+
*/
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
const argus_1 = require("@ory/argus");
|
|
20
|
+
const handlers_js_1 = require("./handlers.js");
|
|
21
|
+
/**
|
|
22
|
+
* Read all of stdin as a string.
|
|
23
|
+
*
|
|
24
|
+
* Uses event listeners instead of `for await` so that we don't depend on the
|
|
25
|
+
* parent process closing stdin (sending EOF). If no EOF arrives, we resolve
|
|
26
|
+
* after a short idle gap once data has been received.
|
|
27
|
+
*/
|
|
28
|
+
function readStdin() {
|
|
29
|
+
return new Promise((resolve, reject) => {
|
|
30
|
+
const chunks = [];
|
|
31
|
+
let resolved = false;
|
|
32
|
+
function done() {
|
|
33
|
+
if (!resolved) {
|
|
34
|
+
resolved = true;
|
|
35
|
+
resolve(Buffer.concat(chunks).toString("utf-8"));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
process.stdin.on("data", (chunk) => {
|
|
39
|
+
chunks.push(chunk);
|
|
40
|
+
// Reset idle timer on every chunk. Once data stops arriving for 100 ms
|
|
41
|
+
// we assume the full payload has been delivered.
|
|
42
|
+
clearTimeout(idleTimer);
|
|
43
|
+
idleTimer = setTimeout(done, 100);
|
|
44
|
+
});
|
|
45
|
+
process.stdin.on("end", done);
|
|
46
|
+
process.stdin.on("error", (err) => {
|
|
47
|
+
if (!resolved) {
|
|
48
|
+
resolved = true;
|
|
49
|
+
reject(err);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
let idleTimer;
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
async function main() {
|
|
56
|
+
const client = argus_1.OryAgentClient.fromEnv("cline");
|
|
57
|
+
const raw = await readStdin();
|
|
58
|
+
let input;
|
|
59
|
+
try {
|
|
60
|
+
input = JSON.parse(raw);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
client.logger.error("hook.stdin.parse_failed", { raw: raw.slice(0, 200) });
|
|
64
|
+
await client.tracer.shutdown();
|
|
65
|
+
process.exit(0); // Don't block on parse errors
|
|
66
|
+
}
|
|
67
|
+
const output = await (0, handlers_js_1.handleHookEvent)(input, client);
|
|
68
|
+
// `cancel: true` is the block signal. Emit the decision and exit 0 — Cline
|
|
69
|
+
// reads the JSON, not the exit code.
|
|
70
|
+
if (output.cancel || output.contextModification || output.errorMessage) {
|
|
71
|
+
process.stdout.write(JSON.stringify(output));
|
|
72
|
+
}
|
|
73
|
+
await client.tracer.shutdown();
|
|
74
|
+
process.exit(0);
|
|
75
|
+
}
|
|
76
|
+
main().catch((err) => {
|
|
77
|
+
process.stderr.write(`[ory-agent] fatal: ${err}\n`);
|
|
78
|
+
process.exit(0); // Fail open
|
|
79
|
+
});
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.handleHookEvent = void 0;
|
|
4
|
+
var handlers_js_1 = require("./handlers.js");
|
|
5
|
+
Object.defineProperty(exports, "handleHookEvent", { enumerable: true, get: function () { return handlers_js_1.handleHookEvent; } });
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cline file-hook event payload delivered on stdin.
|
|
3
|
+
*
|
|
4
|
+
* Cline invokes hooks as executable scripts whose name matches the event in
|
|
5
|
+
* PascalCase (`TaskStart`, `PreToolUse`, …), placed in a hooks directory. Each
|
|
6
|
+
* script receives the event payload as JSON on stdin and may write a JSON
|
|
7
|
+
* decision to stdout. A tool-call decision of `{ "cancel": true }` prevents the
|
|
8
|
+
* tool. Hooks have a 30s timeout and `contextModification` is capped at 50k
|
|
9
|
+
* characters.
|
|
10
|
+
*
|
|
11
|
+
* The on-disk script filenames are PascalCase, but the `hookName` field
|
|
12
|
+
* carried *inside* the stdin payload is the snake_case `HookEventName` enum.
|
|
13
|
+
* Tool-specific fields are nested under per-event sub-objects, not flat.
|
|
14
|
+
*
|
|
15
|
+
* Verified against cline@3.0.29 (@cline/shared/dist/hooks/events.d.ts).
|
|
16
|
+
*/
|
|
17
|
+
/** Snake_case hook event names as delivered inside the stdin payload. */
|
|
18
|
+
export type ClineHookEventName = "agent_start" | "agent_resume" | "agent_abort" | "agent_end" | "agent_error" | "tool_call" | "tool_result" | "prompt_submit" | "pre_compact" | "session_shutdown";
|
|
19
|
+
/** Nested payload for the `tool_call` event. */
|
|
20
|
+
export interface ClinePreToolUsePayload {
|
|
21
|
+
toolName: string;
|
|
22
|
+
parameters: Record<string, string>;
|
|
23
|
+
}
|
|
24
|
+
/** Nested payload for the `tool_result` event. */
|
|
25
|
+
export interface ClinePostToolUsePayload {
|
|
26
|
+
toolName: string;
|
|
27
|
+
parameters: Record<string, string>;
|
|
28
|
+
result?: string;
|
|
29
|
+
success?: boolean;
|
|
30
|
+
executionTimeMs?: number;
|
|
31
|
+
}
|
|
32
|
+
/** Nested payload for the `prompt_submit` event. */
|
|
33
|
+
export interface ClineUserPromptSubmitPayload {
|
|
34
|
+
prompt: string;
|
|
35
|
+
attachments?: unknown[];
|
|
36
|
+
}
|
|
37
|
+
/** Nested payload for the `pre_compact` event. */
|
|
38
|
+
export interface ClinePreCompactPayload {
|
|
39
|
+
[key: string]: unknown;
|
|
40
|
+
}
|
|
41
|
+
export interface ClineHookInput {
|
|
42
|
+
/** Snake_case event name carried in the payload (NOT the script filename). */
|
|
43
|
+
hookName: ClineHookEventName | string;
|
|
44
|
+
/** Stable identifier for the current Cline task/session. */
|
|
45
|
+
taskId: string;
|
|
46
|
+
/** Workspace root paths Cline is operating against. */
|
|
47
|
+
workspaceRoots?: string[];
|
|
48
|
+
/** Version string reported by the Cline binary. */
|
|
49
|
+
clineVersion?: string;
|
|
50
|
+
/** ISO-8601 timestamp of the event. */
|
|
51
|
+
timestamp?: string;
|
|
52
|
+
/** Identity of the user driving the session, when known. */
|
|
53
|
+
userId?: string;
|
|
54
|
+
/** Identity of the agent, when known. */
|
|
55
|
+
agent_id?: string;
|
|
56
|
+
/** Parent agent identity for sub-agent invocations, when known. */
|
|
57
|
+
parent_agent_id?: string;
|
|
58
|
+
/** Present on `tool_call`. */
|
|
59
|
+
preToolUse?: ClinePreToolUsePayload;
|
|
60
|
+
/** Present on `tool_result`. */
|
|
61
|
+
postToolUse?: ClinePostToolUsePayload;
|
|
62
|
+
/** Present on `prompt_submit`. */
|
|
63
|
+
userPromptSubmit?: ClineUserPromptSubmitPayload;
|
|
64
|
+
/** Present on `pre_compact`. */
|
|
65
|
+
preCompact?: ClinePreCompactPayload;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Cline file-hook output written to stdout.
|
|
69
|
+
*
|
|
70
|
+
* For `tool_call`, `cancel: true` prevents the tool from running.
|
|
71
|
+
* `errorMessage` is surfaced to the user; `contextModification` is injected
|
|
72
|
+
* into the model context (50k char cap). All fields are optional — emitting
|
|
73
|
+
* `{}` (or nothing) lets the action proceed.
|
|
74
|
+
*
|
|
75
|
+
* Verified against cline@3.0.29.
|
|
76
|
+
*/
|
|
77
|
+
export interface ClineHookOutput {
|
|
78
|
+
cancel?: boolean;
|
|
79
|
+
errorMessage?: string;
|
|
80
|
+
contextModification?: string;
|
|
81
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Cline file-hook event payload delivered on stdin.
|
|
4
|
+
*
|
|
5
|
+
* Cline invokes hooks as executable scripts whose name matches the event in
|
|
6
|
+
* PascalCase (`TaskStart`, `PreToolUse`, …), placed in a hooks directory. Each
|
|
7
|
+
* script receives the event payload as JSON on stdin and may write a JSON
|
|
8
|
+
* decision to stdout. A tool-call decision of `{ "cancel": true }` prevents the
|
|
9
|
+
* tool. Hooks have a 30s timeout and `contextModification` is capped at 50k
|
|
10
|
+
* characters.
|
|
11
|
+
*
|
|
12
|
+
* The on-disk script filenames are PascalCase, but the `hookName` field
|
|
13
|
+
* carried *inside* the stdin payload is the snake_case `HookEventName` enum.
|
|
14
|
+
* Tool-specific fields are nested under per-event sub-objects, not flat.
|
|
15
|
+
*
|
|
16
|
+
* Verified against cline@3.0.29 (@cline/shared/dist/hooks/events.d.ts).
|
|
17
|
+
*/
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
package/package.json
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ory/cline",
|
|
3
|
+
"version": "0.10.0",
|
|
4
|
+
"description": "Ory plugin for Cline: scaffolding skills, a local Ory instance, and authentication, authorization, and audit for every tool call",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"homepage": "https://ory.com",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"ory",
|
|
9
|
+
"cline",
|
|
10
|
+
"cline-cli",
|
|
11
|
+
"hooks",
|
|
12
|
+
"plugin",
|
|
13
|
+
"extension",
|
|
14
|
+
"identity",
|
|
15
|
+
"identity-management",
|
|
16
|
+
"iam",
|
|
17
|
+
"authentication",
|
|
18
|
+
"authorization",
|
|
19
|
+
"access-control",
|
|
20
|
+
"permissions",
|
|
21
|
+
"rbac",
|
|
22
|
+
"zanzibar",
|
|
23
|
+
"oauth",
|
|
24
|
+
"oauth2",
|
|
25
|
+
"openid-connect",
|
|
26
|
+
"oidc",
|
|
27
|
+
"session",
|
|
28
|
+
"mfa",
|
|
29
|
+
"sso",
|
|
30
|
+
"audit",
|
|
31
|
+
"audit-log",
|
|
32
|
+
"compliance",
|
|
33
|
+
"agent",
|
|
34
|
+
"ai-agent",
|
|
35
|
+
"agent-security",
|
|
36
|
+
"guardrails",
|
|
37
|
+
"llm",
|
|
38
|
+
"tracing",
|
|
39
|
+
"distributed-tracing",
|
|
40
|
+
"observability",
|
|
41
|
+
"kratos",
|
|
42
|
+
"keto",
|
|
43
|
+
"hydra"
|
|
44
|
+
],
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"access": "public",
|
|
47
|
+
"registry": "https://registry.npmjs.org/",
|
|
48
|
+
"provenance": true
|
|
49
|
+
},
|
|
50
|
+
"main": "dist/index.js",
|
|
51
|
+
"types": "dist/index.d.ts",
|
|
52
|
+
"exports": {
|
|
53
|
+
".": {
|
|
54
|
+
"types": "./dist/index.d.ts",
|
|
55
|
+
"default": "./dist/index.js"
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"bin": {
|
|
59
|
+
"ory-cline": "dist/cli/main.js",
|
|
60
|
+
"ory-cline-hook": "dist/hook.js",
|
|
61
|
+
"ory-cline-setup": "dist/cli/setup.js"
|
|
62
|
+
},
|
|
63
|
+
"files": [
|
|
64
|
+
"dist",
|
|
65
|
+
"!dist/dev",
|
|
66
|
+
"!dist/**/*.tsbuildinfo"
|
|
67
|
+
],
|
|
68
|
+
"dependencies": {
|
|
69
|
+
"@ory/argus": "0.10.0"
|
|
70
|
+
},
|
|
71
|
+
"devDependencies": {
|
|
72
|
+
"typescript": "^6.0.2",
|
|
73
|
+
"vitest": "4.1.4"
|
|
74
|
+
},
|
|
75
|
+
"engines": {
|
|
76
|
+
"node": ">=22"
|
|
77
|
+
},
|
|
78
|
+
"scripts": {
|
|
79
|
+
"build": "tsc",
|
|
80
|
+
"clean": "rm -rf dist *.tsbuildinfo",
|
|
81
|
+
"test": "vitest run",
|
|
82
|
+
"test:watch": "vitest",
|
|
83
|
+
"typecheck": "tsc --noEmit",
|
|
84
|
+
"dev": "node dist/dev/launcher.js"
|
|
85
|
+
}
|
|
86
|
+
}
|