@ory/argus 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +134 -0
- package/assets/commands/local-down.md +19 -0
- package/assets/commands/local-up.md +27 -0
- package/assets/skills/auth-setup/SKILL.md +279 -0
- package/assets/skills/local-dev/SKILL.md +206 -0
- package/assets/skills/login-flow/SKILL.md +383 -0
- package/assets/skills/social-login/SKILL.md +312 -0
- package/dist/agent-auth.d.ts +204 -0
- package/dist/agent-auth.js +553 -0
- package/dist/auth-gate.d.ts +71 -0
- package/dist/auth-gate.js +308 -0
- package/dist/auth-store.d.ts +75 -0
- package/dist/auth-store.js +261 -0
- package/dist/auth.d.ts +93 -0
- package/dist/auth.js +323 -0
- package/dist/cli.d.ts +73 -0
- package/dist/cli.js +484 -0
- package/dist/client.d.ts +158 -0
- package/dist/client.js +679 -0
- package/dist/config.d.ts +135 -0
- package/dist/config.js +344 -0
- package/dist/denial.d.ts +79 -0
- package/dist/denial.js +103 -0
- package/dist/dev.d.ts +95 -0
- package/dist/dev.js +514 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +137 -0
- package/dist/local/cli.d.ts +12 -0
- package/dist/local/cli.js +95 -0
- package/dist/local/configs.d.ts +89 -0
- package/dist/local/configs.js +634 -0
- package/dist/local/health.d.ts +32 -0
- package/dist/local/health.js +65 -0
- package/dist/local/index.d.ts +6 -0
- package/dist/local/index.js +38 -0
- package/dist/local/jaeger-main.d.ts +13 -0
- package/dist/local/jaeger-main.js +85 -0
- package/dist/local/jaeger.d.ts +50 -0
- package/dist/local/jaeger.js +162 -0
- package/dist/local/main.d.ts +7 -0
- package/dist/local/main.js +14 -0
- package/dist/local/manager.d.ts +45 -0
- package/dist/local/manager.js +676 -0
- package/dist/local/seed.d.ts +71 -0
- package/dist/local/seed.js +237 -0
- package/dist/logger.d.ts +29 -0
- package/dist/logger.js +139 -0
- package/dist/mcp.d.ts +76 -0
- package/dist/mcp.js +122 -0
- package/dist/otel/exporter.d.ts +17 -0
- package/dist/otel/exporter.js +12 -0
- package/dist/otel/index.d.ts +2 -0
- package/dist/otel/index.js +8 -0
- package/dist/otel/otlp-http.d.ts +116 -0
- package/dist/otel/otlp-http.js +322 -0
- package/dist/registry/cli.d.ts +12 -0
- package/dist/registry/cli.js +76 -0
- package/dist/registry/config.d.ts +23 -0
- package/dist/registry/config.js +80 -0
- package/dist/registry/index.d.ts +3 -0
- package/dist/registry/index.js +21 -0
- package/dist/registry/main.d.ts +7 -0
- package/dist/registry/main.js +14 -0
- package/dist/registry/manager.d.ts +38 -0
- package/dist/registry/manager.js +674 -0
- package/dist/setup.d.ts +118 -0
- package/dist/setup.js +398 -0
- package/dist/skills.d.ts +78 -0
- package/dist/skills.js +264 -0
- package/dist/subject.d.ts +43 -0
- package/dist/subject.js +55 -0
- package/dist/tool-metadata.d.ts +41 -0
- package/dist/tool-metadata.js +127 -0
- package/dist/tracer.d.ts +172 -0
- package/dist/tracer.js +452 -0
- package/dist/types.d.ts +57 -0
- package/dist/types.js +3 -0
- package/dist/watch-sandbox.d.ts +9 -0
- package/dist/watch-sandbox.js +81 -0
- package/package.json +79 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.runConfigureCommand = runConfigureCommand;
|
|
37
|
+
exports.printOryConfig = printOryConfig;
|
|
38
|
+
exports.printEnvironment = printEnvironment;
|
|
39
|
+
exports.printLogTail = printLogTail;
|
|
40
|
+
exports.printEnvHelp = printEnvHelp;
|
|
41
|
+
exports.printTraceTail = printTraceTail;
|
|
42
|
+
exports.runWatchCommand = runWatchCommand;
|
|
43
|
+
exports.isTtyAvailable = isTtyAvailable;
|
|
44
|
+
exports.promptOnTty = promptOnTty;
|
|
45
|
+
exports.promptForProjectUrl = promptForProjectUrl;
|
|
46
|
+
exports.interactiveConfigPrompt = interactiveConfigPrompt;
|
|
47
|
+
exports.runAgentCommand = runAgentCommand;
|
|
48
|
+
const fs = __importStar(require("node:fs"));
|
|
49
|
+
const readline = __importStar(require("node:readline"));
|
|
50
|
+
const client_1 = require("@ory/client");
|
|
51
|
+
const config_js_1 = require("./config.js");
|
|
52
|
+
const agent_auth_js_1 = require("./agent-auth.js");
|
|
53
|
+
const tracer_js_1 = require("./tracer.js");
|
|
54
|
+
/**
|
|
55
|
+
* Shared `configure` command implementation for all plugin CLIs.
|
|
56
|
+
*
|
|
57
|
+
* Parses --project-url and --api-key from args. If neither is provided,
|
|
58
|
+
* shows the current configuration. Otherwise saves the provided values.
|
|
59
|
+
*/
|
|
60
|
+
function runConfigureCommand(binName, args) {
|
|
61
|
+
let projectUrl;
|
|
62
|
+
let apiKey;
|
|
63
|
+
let auditOnly = false;
|
|
64
|
+
for (let i = 0; i < args.length; i++) {
|
|
65
|
+
switch (args[i]) {
|
|
66
|
+
case "--project-url":
|
|
67
|
+
projectUrl = args[++i];
|
|
68
|
+
break;
|
|
69
|
+
case "--api-key":
|
|
70
|
+
apiKey = args[++i];
|
|
71
|
+
break;
|
|
72
|
+
case "--audit-only":
|
|
73
|
+
auditOnly = true;
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (!projectUrl && !apiKey && !auditOnly) {
|
|
78
|
+
const resolved = (0, config_js_1.resolveConfig)();
|
|
79
|
+
const configPath = (0, config_js_1.getConfigPath)();
|
|
80
|
+
console.log("Ory Plugin Configuration");
|
|
81
|
+
console.log("========================");
|
|
82
|
+
console.log("");
|
|
83
|
+
console.log(`Config file: ${configPath}`);
|
|
84
|
+
console.log(`Project URL: ${resolved.projectUrl ?? "(not set)"} [${resolved.projectUrlSource}]`);
|
|
85
|
+
console.log(`API Key: ${resolved.apiKey ? "(set)" : "(not set)"} [${resolved.apiKeySource}]`);
|
|
86
|
+
console.log(`Audit Only: ${resolved.auditOnly ? "yes" : "no"}`);
|
|
87
|
+
console.log("");
|
|
88
|
+
console.log("To configure:");
|
|
89
|
+
console.log(` npx ${binName} configure --project-url <URL> --api-key <KEY>`);
|
|
90
|
+
console.log("");
|
|
91
|
+
console.log("To enable audit logging only (no auth or permission checks):");
|
|
92
|
+
console.log(` npx ${binName} configure --audit-only`);
|
|
93
|
+
console.log("");
|
|
94
|
+
console.log("Or set environment variables:");
|
|
95
|
+
console.log(" export ORY_PROJECT_URL=https://your-project.projects.oryapis.com");
|
|
96
|
+
console.log(" export ORY_API_KEY=ory_pat_...");
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (auditOnly) {
|
|
100
|
+
// Audit-only mode: clear projectUrl/apiKey, set auditOnly flag
|
|
101
|
+
(0, config_js_1.saveConfig)({ auditOnly: true });
|
|
102
|
+
const configPath = (0, config_js_1.getConfigPath)();
|
|
103
|
+
console.log(`Configuration saved to ${configPath}`);
|
|
104
|
+
console.log(" Mode: audit-only (logging enabled, auth and permission checks disabled)");
|
|
105
|
+
console.log("");
|
|
106
|
+
console.log("This configuration is shared across all Ory agent plugins.");
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
const update = {};
|
|
110
|
+
if (projectUrl)
|
|
111
|
+
update.projectUrl = projectUrl;
|
|
112
|
+
if (apiKey)
|
|
113
|
+
update.apiKey = apiKey;
|
|
114
|
+
// Clear auditOnly when configuring with a project URL
|
|
115
|
+
update.auditOnly = false;
|
|
116
|
+
(0, config_js_1.saveConfig)(update);
|
|
117
|
+
const configPath = (0, config_js_1.getConfigPath)();
|
|
118
|
+
console.log(`Configuration saved to ${configPath}`);
|
|
119
|
+
if (projectUrl)
|
|
120
|
+
console.log(` Project URL: ${projectUrl}`);
|
|
121
|
+
if (apiKey)
|
|
122
|
+
console.log(` API Key: (set)`);
|
|
123
|
+
console.log("");
|
|
124
|
+
console.log("This configuration is shared across all Ory agent plugins.");
|
|
125
|
+
console.log("Environment variables (ORY_PROJECT_URL, ORY_API_KEY) take precedence when set.");
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Print the "Configuration:" block showing Ory project URL and API key status.
|
|
129
|
+
*/
|
|
130
|
+
function printOryConfig() {
|
|
131
|
+
const resolved = (0, config_js_1.resolveConfig)();
|
|
132
|
+
const configPath = (0, config_js_1.getConfigPath)();
|
|
133
|
+
console.log("Configuration:");
|
|
134
|
+
console.log(` Config file: ${configPath}`);
|
|
135
|
+
console.log(` Mode: ${resolved.auditOnly ? "audit-only" : "full"}`);
|
|
136
|
+
console.log(` Project URL: ${resolved.projectUrl ?? "NOT SET"} [source: ${resolved.projectUrlSource}]`);
|
|
137
|
+
console.log(` API Key: ${resolved.apiKey ? "(set)" : "NOT SET"} [source: ${resolved.apiKeySource}]`);
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Print the "Environment:" block showing Ory-related environment variables.
|
|
141
|
+
*/
|
|
142
|
+
function printEnvironment() {
|
|
143
|
+
console.log("");
|
|
144
|
+
console.log("Environment:");
|
|
145
|
+
const vars = [
|
|
146
|
+
["ORY_SESSION_TOKEN", process.env.ORY_SESSION_TOKEN ? "(set)" : undefined, false],
|
|
147
|
+
["ORY_AGENT_SUBJECT_ID", process.env.ORY_AGENT_SUBJECT_ID, false],
|
|
148
|
+
["ORY_AGENT_DEBUG", process.env.ORY_AGENT_DEBUG, false],
|
|
149
|
+
["ORY_AGENT_LOG_FILE", process.env.ORY_AGENT_LOG_FILE, false],
|
|
150
|
+
["ORY_AGENT_TRACE_FILE", process.env.ORY_AGENT_TRACE_FILE, false],
|
|
151
|
+
];
|
|
152
|
+
for (const [name, value, required] of vars) {
|
|
153
|
+
const display = value ? `${value}` : required ? "NOT SET (required)" : "not set";
|
|
154
|
+
const icon = value ? "+" : required ? "!" : "-";
|
|
155
|
+
console.log(` [${icon}] ${name}: ${display}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Print the last 5 debug log entries if ORY_AGENT_LOG_FILE is set and exists.
|
|
160
|
+
*/
|
|
161
|
+
function printLogTail() {
|
|
162
|
+
const logFile = process.env.ORY_AGENT_LOG_FILE;
|
|
163
|
+
if (logFile && fs.existsSync(logFile)) {
|
|
164
|
+
const lines = fs.readFileSync(logFile, "utf-8").trim().split("\n");
|
|
165
|
+
console.log("");
|
|
166
|
+
console.log(`Debug log: ${logFile} (${lines.length} events)`);
|
|
167
|
+
console.log("Last 5 events:");
|
|
168
|
+
for (const line of lines.slice(-5)) {
|
|
169
|
+
try {
|
|
170
|
+
const entry = JSON.parse(line);
|
|
171
|
+
console.log(` ${entry.timestamp} [${entry.level}] ${entry.event}`);
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
console.log(` ${line.slice(0, 120)}`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Print the "Configure your Ory credentials" help text.
|
|
181
|
+
*/
|
|
182
|
+
function printEnvHelp(binName) {
|
|
183
|
+
console.log("");
|
|
184
|
+
console.log("Configure your Ory credentials (one of these methods):");
|
|
185
|
+
console.log("");
|
|
186
|
+
console.log(" Option 1 — Save to config file (persists across sessions):");
|
|
187
|
+
console.log(` npx ${binName} configure --project-url https://your-project.projects.oryapis.com --api-key ory_pat_...`);
|
|
188
|
+
console.log("");
|
|
189
|
+
console.log(" Option 2 — Set environment variables:");
|
|
190
|
+
console.log(" export ORY_PROJECT_URL=https://your-project.projects.oryapis.com");
|
|
191
|
+
console.log(" export ORY_API_KEY=ory_pat_...");
|
|
192
|
+
console.log("");
|
|
193
|
+
console.log("If neither is set when a session starts, the agent will prompt for configuration.");
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Print the last 5 trace entries if ORY_AGENT_TRACE_FILE is set and exists.
|
|
197
|
+
*/
|
|
198
|
+
function printTraceTail() {
|
|
199
|
+
const traceFile = process.env.ORY_AGENT_TRACE_FILE;
|
|
200
|
+
if (traceFile && fs.existsSync(traceFile)) {
|
|
201
|
+
const lines = fs.readFileSync(traceFile, "utf-8").trim().split("\n");
|
|
202
|
+
console.log("");
|
|
203
|
+
console.log(`Trace file: ${traceFile} (${lines.length} spans)`);
|
|
204
|
+
console.log("Last 5 spans:");
|
|
205
|
+
for (const line of lines.slice(-5)) {
|
|
206
|
+
try {
|
|
207
|
+
const span = JSON.parse(line);
|
|
208
|
+
console.log(" " + (0, tracer_js_1.formatSpan)(span));
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
console.log(` ${line.slice(0, 120)}`);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Run the live trace watcher. Tails the NDJSON trace file and prints
|
|
218
|
+
* formatted spans to stdout as they arrive. Blocks until interrupted.
|
|
219
|
+
*/
|
|
220
|
+
function runWatchCommand(args) {
|
|
221
|
+
let traceFile = args[0];
|
|
222
|
+
if (!traceFile) {
|
|
223
|
+
traceFile = process.env.ORY_AGENT_TRACE_FILE ?? "";
|
|
224
|
+
}
|
|
225
|
+
if (!traceFile) {
|
|
226
|
+
console.error("Usage: watch <trace-file>\n" +
|
|
227
|
+
" Or set ORY_AGENT_TRACE_FILE environment variable.\n");
|
|
228
|
+
process.exit(1);
|
|
229
|
+
}
|
|
230
|
+
console.log(`Watching traces: ${traceFile}`);
|
|
231
|
+
console.log("Press Ctrl+C to stop.\n");
|
|
232
|
+
const stop = (0, tracer_js_1.watchTraceFile)(traceFile, (span) => {
|
|
233
|
+
console.log((0, tracer_js_1.formatSpan)(span));
|
|
234
|
+
});
|
|
235
|
+
process.on("SIGINT", () => {
|
|
236
|
+
stop();
|
|
237
|
+
console.log("\nStopped watching.");
|
|
238
|
+
process.exit(0);
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Returns true if `/dev/tty` can be opened for reading. Use this before
|
|
243
|
+
* attempting to prompt the user — the harness has already taken stdin so
|
|
244
|
+
* we cannot use `process.stdin`.
|
|
245
|
+
*/
|
|
246
|
+
function isTtyAvailable() {
|
|
247
|
+
try {
|
|
248
|
+
const fd = fs.openSync("/dev/tty", "r");
|
|
249
|
+
fs.closeSync(fd);
|
|
250
|
+
return true;
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Prompt the user to enter a value on /dev/tty. Returns the trimmed
|
|
258
|
+
* line, or null if no TTY is available. The prompt is written to stderr
|
|
259
|
+
* — never stdout.
|
|
260
|
+
*/
|
|
261
|
+
async function promptOnTty(prompt) {
|
|
262
|
+
let ttyFd;
|
|
263
|
+
try {
|
|
264
|
+
ttyFd = fs.openSync("/dev/tty", "r");
|
|
265
|
+
}
|
|
266
|
+
catch {
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
const ttyStream = fs.createReadStream("", { fd: ttyFd });
|
|
270
|
+
const rl = readline.createInterface({ input: ttyStream, output: process.stderr });
|
|
271
|
+
try {
|
|
272
|
+
const answer = await new Promise((resolve) => {
|
|
273
|
+
rl.question(prompt, resolve);
|
|
274
|
+
});
|
|
275
|
+
return answer.trim();
|
|
276
|
+
}
|
|
277
|
+
finally {
|
|
278
|
+
rl.close();
|
|
279
|
+
ttyStream.destroy();
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Inline prompt for the Ory project URL. Persists the entered URL to the
|
|
284
|
+
* shared config file and returns it. Returns null if the user cancels
|
|
285
|
+
* (empty input) or no TTY is available.
|
|
286
|
+
*
|
|
287
|
+
* This is the structured counterpart to {@link interactiveConfigPrompt},
|
|
288
|
+
* which only blocks until the user runs `configure` in another terminal.
|
|
289
|
+
*/
|
|
290
|
+
async function promptForProjectUrl(binName) {
|
|
291
|
+
if (!isTtyAvailable())
|
|
292
|
+
return null;
|
|
293
|
+
const message = [
|
|
294
|
+
"",
|
|
295
|
+
"--------------------------------------------",
|
|
296
|
+
" Ory Agent Plugin — Project URL Required",
|
|
297
|
+
"--------------------------------------------",
|
|
298
|
+
"",
|
|
299
|
+
"No Ory project URL is configured for this session.",
|
|
300
|
+
`Enter your Ory project URL to continue, or press Enter to skip and run \`npx ${binName} configure\` later.`,
|
|
301
|
+
"",
|
|
302
|
+
"Example: https://your-project.projects.oryapis.com",
|
|
303
|
+
"",
|
|
304
|
+
].join("\n");
|
|
305
|
+
process.stderr.write(message + "\n");
|
|
306
|
+
const answer = await promptOnTty("Project URL: ");
|
|
307
|
+
if (!answer) {
|
|
308
|
+
process.stderr.write("\nNo URL entered. Continuing without authentication.\n\n");
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
let url;
|
|
312
|
+
try {
|
|
313
|
+
const parsed = new URL(answer);
|
|
314
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
|
|
315
|
+
process.stderr.write(`\nIgnoring invalid project URL "${answer}".\n\n`);
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
url = parsed.toString().replace(/\/$/, "");
|
|
319
|
+
}
|
|
320
|
+
catch {
|
|
321
|
+
process.stderr.write(`\nIgnoring invalid project URL "${answer}".\n\n`);
|
|
322
|
+
return null;
|
|
323
|
+
}
|
|
324
|
+
(0, config_js_1.saveConfig)({ projectUrl: url, auditOnly: false });
|
|
325
|
+
process.stderr.write(`\nSaved project URL to ${(0, config_js_1.getConfigPath)()}.\n\n`);
|
|
326
|
+
return url;
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Display a friendly configuration prompt on stderr and block until the
|
|
330
|
+
* user presses Enter. After the user continues, re-checks config from
|
|
331
|
+
* disk and returns true if Ory is now configured.
|
|
332
|
+
*
|
|
333
|
+
* Falls back to non-interactive mode (returns false immediately) when
|
|
334
|
+
* no TTY is available (e.g. CI).
|
|
335
|
+
*/
|
|
336
|
+
async function interactiveConfigPrompt(binName) {
|
|
337
|
+
const configPath = (0, config_js_1.getConfigPath)();
|
|
338
|
+
const message = [
|
|
339
|
+
"",
|
|
340
|
+
"--------------------------------------------",
|
|
341
|
+
" Ory Agent Plugin — Configuration Required",
|
|
342
|
+
"--------------------------------------------",
|
|
343
|
+
"",
|
|
344
|
+
"The Ory agent plugin is installed but not yet configured.",
|
|
345
|
+
"Run one of the following commands in another terminal:",
|
|
346
|
+
"",
|
|
347
|
+
" Connect to Ory (enables auth & permission checks):",
|
|
348
|
+
` npx ${binName} configure --project-url <URL> --api-key <KEY>`,
|
|
349
|
+
"",
|
|
350
|
+
" Or, enable audit logging only (no auth or permission checks):",
|
|
351
|
+
` npx ${binName} configure --audit-only`,
|
|
352
|
+
"",
|
|
353
|
+
`Config is saved to ${configPath}`,
|
|
354
|
+
"and shared across all agent plugins.",
|
|
355
|
+
"",
|
|
356
|
+
"You can also set environment variables (requires session restart):",
|
|
357
|
+
" export ORY_PROJECT_URL=https://your-project.projects.oryapis.com",
|
|
358
|
+
" export ORY_API_KEY=ory_pat_...",
|
|
359
|
+
"",
|
|
360
|
+
].join("\n");
|
|
361
|
+
process.stderr.write(message + "\n");
|
|
362
|
+
// Try to open the terminal for blocking user input.
|
|
363
|
+
// stdin is consumed by the harness JSON pipe, so we read /dev/tty directly.
|
|
364
|
+
let ttyFd;
|
|
365
|
+
try {
|
|
366
|
+
ttyFd = fs.openSync("/dev/tty", "r");
|
|
367
|
+
}
|
|
368
|
+
catch {
|
|
369
|
+
// No TTY (CI, piped environment) — can't block for input
|
|
370
|
+
process.stderr.write("No interactive terminal available. Configure Ory and restart the session.\n\n");
|
|
371
|
+
return false;
|
|
372
|
+
}
|
|
373
|
+
const ttyStream = fs.createReadStream("", { fd: ttyFd });
|
|
374
|
+
const rl = readline.createInterface({ input: ttyStream, output: process.stderr });
|
|
375
|
+
try {
|
|
376
|
+
await new Promise((resolve) => {
|
|
377
|
+
rl.question("Press Enter after configuring to continue... ", resolve);
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
finally {
|
|
381
|
+
rl.close();
|
|
382
|
+
ttyStream.destroy();
|
|
383
|
+
}
|
|
384
|
+
// Re-read config from disk (env vars set in another shell won't be visible)
|
|
385
|
+
const resolved = (0, config_js_1.resolveConfig)();
|
|
386
|
+
if (resolved.projectUrl || resolved.auditOnly) {
|
|
387
|
+
process.stderr.write("\nConfiguration detected! Continuing session.\n\n");
|
|
388
|
+
return true;
|
|
389
|
+
}
|
|
390
|
+
process.stderr.write("\nNo configuration detected. Session blocked.\n");
|
|
391
|
+
process.stderr.write("Run one of the configure commands above and start a new session.\n\n");
|
|
392
|
+
return false;
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Run the `agent <subcommand>` family of CLI commands. These manage the
|
|
396
|
+
* agent's dynamic OAuth2 client registration (RFC 7591). Subcommands:
|
|
397
|
+
*
|
|
398
|
+
* - `status` — print the persisted DCR identity, if any.
|
|
399
|
+
* - `unregister` — best-effort RFC 7592 DELETE on the registered
|
|
400
|
+
* client, then clear the persisted block.
|
|
401
|
+
*
|
|
402
|
+
* Returns the process exit code (0 on success, non-zero on usage error).
|
|
403
|
+
*/
|
|
404
|
+
async function runAgentCommand(binName, args) {
|
|
405
|
+
const sub = args[0];
|
|
406
|
+
if (!sub || sub === "--help" || sub === "-h") {
|
|
407
|
+
console.log(`Usage: ${binName} agent <subcommand>`);
|
|
408
|
+
console.log("");
|
|
409
|
+
console.log("Subcommands:");
|
|
410
|
+
console.log(" status Show the registered agent OAuth2 client (if any)");
|
|
411
|
+
console.log(" unregister Delete the registered agent client and clear local state");
|
|
412
|
+
return sub ? 0 : 1;
|
|
413
|
+
}
|
|
414
|
+
switch (sub) {
|
|
415
|
+
case "status":
|
|
416
|
+
runAgentStatus();
|
|
417
|
+
return 0;
|
|
418
|
+
case "unregister":
|
|
419
|
+
return await runAgentUnregister();
|
|
420
|
+
default:
|
|
421
|
+
console.error(`Unknown agent subcommand: ${sub}`);
|
|
422
|
+
console.error(`Run "${binName} agent --help" for usage.`);
|
|
423
|
+
return 1;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
function runAgentStatus() {
|
|
427
|
+
const persisted = (0, agent_auth_js_1.loadAgentDynamicCredentials)();
|
|
428
|
+
console.log("Agent identity (Dynamic Client Registration)");
|
|
429
|
+
console.log("=============================================");
|
|
430
|
+
console.log("");
|
|
431
|
+
if (!persisted) {
|
|
432
|
+
console.log(" No registered agent client. The harness will register one on its");
|
|
433
|
+
console.log(" next session start using the user's bearer (or");
|
|
434
|
+
console.log(" ORY_AGENT_REGISTRATION_TOKEN) as the initial access token.");
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
console.log(` client_id: ${persisted.clientId}`);
|
|
438
|
+
console.log(` client_secret: ${persisted.clientSecret ? "(set)" : "(not set — public client)"}`);
|
|
439
|
+
console.log(` registered_at: ${new Date(persisted.registeredAt * 1000).toISOString()}`);
|
|
440
|
+
console.log(` project_url: ${persisted.projectUrl}`);
|
|
441
|
+
if (persisted.harness)
|
|
442
|
+
console.log(` harness: ${persisted.harness}`);
|
|
443
|
+
if (persisted.registrationClientUri) {
|
|
444
|
+
console.log(` management_uri: ${persisted.registrationClientUri}`);
|
|
445
|
+
}
|
|
446
|
+
if (persisted.registrationAccessToken) {
|
|
447
|
+
console.log(` registration_token: (set — used to manage/delete this client)`);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
async function runAgentUnregister() {
|
|
451
|
+
const persisted = (0, agent_auth_js_1.loadAgentDynamicCredentials)();
|
|
452
|
+
if (!persisted) {
|
|
453
|
+
console.log("No registered agent client to unregister. Nothing to do.");
|
|
454
|
+
return 0;
|
|
455
|
+
}
|
|
456
|
+
if (persisted.registrationClientUri && persisted.registrationAccessToken) {
|
|
457
|
+
const oidc = new client_1.OidcApi(new client_1.Configuration({
|
|
458
|
+
basePath: persisted.projectUrl,
|
|
459
|
+
accessToken: persisted.registrationAccessToken,
|
|
460
|
+
}));
|
|
461
|
+
try {
|
|
462
|
+
await oidc.deleteOidcDynamicClient({ id: persisted.clientId });
|
|
463
|
+
}
|
|
464
|
+
catch (err) {
|
|
465
|
+
const status = err?.response?.status;
|
|
466
|
+
if (status === 404) {
|
|
467
|
+
// Already gone server-side; nothing to warn about.
|
|
468
|
+
}
|
|
469
|
+
else if (typeof status === "number") {
|
|
470
|
+
console.warn(`Warning: server-side DELETE returned HTTP ${status}. Local state will be cleared anyway.`);
|
|
471
|
+
}
|
|
472
|
+
else {
|
|
473
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
474
|
+
console.warn(`Warning: server-side DELETE failed (${msg}). Local state will be cleared anyway.`);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
else {
|
|
479
|
+
console.warn("Warning: no registration_access_token was persisted, so we cannot DELETE the server-side client. Clearing local state only.");
|
|
480
|
+
}
|
|
481
|
+
(0, agent_auth_js_1.clearAgentDynamicCredentials)();
|
|
482
|
+
console.log(`Unregistered agent client ${persisted.clientId}.`);
|
|
483
|
+
return 0;
|
|
484
|
+
}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { DebugLogger } from "./logger.js";
|
|
2
|
+
import { Tracer } from "./tracer.js";
|
|
3
|
+
import type { SpanExporter } from "./otel/exporter.js";
|
|
4
|
+
import type { SessionInfo, OAuth2TokenInfo, PermissionCheck, PermissionResult, BatchPermissionResult, OryError } from "./types.js";
|
|
5
|
+
export interface OryAgentConfig {
|
|
6
|
+
/** Ory project URL, e.g. https://your-project.projects.oryapis.com */
|
|
7
|
+
projectUrl: string;
|
|
8
|
+
/** Ory API key for admin operations */
|
|
9
|
+
apiKey?: string;
|
|
10
|
+
/** Which harness this client is running in */
|
|
11
|
+
harness: string;
|
|
12
|
+
/** Optional log file path for debug output */
|
|
13
|
+
logFile?: string;
|
|
14
|
+
/** Optional NDJSON trace file path for structured span output */
|
|
15
|
+
traceFile?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Session cache TTL in milliseconds.
|
|
18
|
+
* Set to 0 to disable caching. Default: 60_000 (1 minute).
|
|
19
|
+
*/
|
|
20
|
+
sessionCacheTtlMs?: number;
|
|
21
|
+
/** Optional span exporter (OTLP, vendor SDK, …) attached to the tracer. */
|
|
22
|
+
exporter?: SpanExporter;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* A principal known to the client. Either the human user (interactive
|
|
26
|
+
* PKCE login) or the agent (machine credentials).
|
|
27
|
+
*/
|
|
28
|
+
export interface PrincipalIdentity {
|
|
29
|
+
/** Subject (sub claim or `session:<id>`) — used in audit spans. */
|
|
30
|
+
subject?: string;
|
|
31
|
+
/** Bearer token. Agent's token authenticates outgoing API calls. */
|
|
32
|
+
token?: string;
|
|
33
|
+
}
|
|
34
|
+
export declare class OryAgentClient {
|
|
35
|
+
private frontend;
|
|
36
|
+
private oauth2;
|
|
37
|
+
private permission;
|
|
38
|
+
private relationship;
|
|
39
|
+
private config;
|
|
40
|
+
readonly logger: DebugLogger;
|
|
41
|
+
readonly tracer: Tracer;
|
|
42
|
+
private sessionCache;
|
|
43
|
+
private sessionCacheTtlMs;
|
|
44
|
+
/**
|
|
45
|
+
* The human user — the principal whose permissions we check.
|
|
46
|
+
* Populated by `ensureUserAuthenticated` on session start.
|
|
47
|
+
*/
|
|
48
|
+
private _userPrincipal;
|
|
49
|
+
/**
|
|
50
|
+
* The AI agent process — the principal that authenticates outgoing
|
|
51
|
+
* Ory API calls. Populated by `ensureAgentIdentity`.
|
|
52
|
+
*/
|
|
53
|
+
private _agentPrincipal;
|
|
54
|
+
constructor(config: OryAgentConfig);
|
|
55
|
+
/** Build a fresh set of Ory API instances using the current agent token. */
|
|
56
|
+
private buildApis;
|
|
57
|
+
/** Snapshot of the current user principal. */
|
|
58
|
+
get userPrincipal(): PrincipalIdentity;
|
|
59
|
+
/** Snapshot of the current agent principal. */
|
|
60
|
+
get agentPrincipal(): PrincipalIdentity;
|
|
61
|
+
/**
|
|
62
|
+
* Set or update the human user principal. The user is the subject of
|
|
63
|
+
* permission checks but does not authenticate outgoing API calls.
|
|
64
|
+
* Pass `{}` (or fields set to undefined) to clear.
|
|
65
|
+
*/
|
|
66
|
+
setUserPrincipal(principal: PrincipalIdentity): void;
|
|
67
|
+
/**
|
|
68
|
+
* Set or update the AI agent principal. The agent's token (when
|
|
69
|
+
* present) is used in the Authorization header for all outgoing Ory
|
|
70
|
+
* API calls — so the audit log shows "agent X acting on behalf of
|
|
71
|
+
* user Y". Rebuilds the underlying Ory API instances when the token
|
|
72
|
+
* actually changes so subsequent calls pick it up; otherwise leaves
|
|
73
|
+
* the API instances alone (so test stubs survive a no-op update).
|
|
74
|
+
*/
|
|
75
|
+
setAgentPrincipal(principal: PrincipalIdentity): void;
|
|
76
|
+
/**
|
|
77
|
+
* Verify an existing session token. Returns cached result if available.
|
|
78
|
+
*/
|
|
79
|
+
verifySession(sessionToken: string): Promise<SessionInfo>;
|
|
80
|
+
/**
|
|
81
|
+
* Introspect an OAuth2 access token. Use this when agents authenticate
|
|
82
|
+
* via client credentials flow instead of session tokens.
|
|
83
|
+
*/
|
|
84
|
+
introspectToken(token: string, scope?: string): Promise<OAuth2TokenInfo>;
|
|
85
|
+
/**
|
|
86
|
+
* Check if a subject has permission to perform an action.
|
|
87
|
+
*
|
|
88
|
+
* `options.spanAttributes` are merged into the `permission.check` trace
|
|
89
|
+
* span so callers can attach harness-side context (toolName, mcpServer,
|
|
90
|
+
* etc.) that the wire-level `PermissionCheck` shape doesn't carry.
|
|
91
|
+
*/
|
|
92
|
+
checkPermission(check: PermissionCheck, options?: {
|
|
93
|
+
spanAttributes?: Record<string, unknown>;
|
|
94
|
+
}): Promise<PermissionResult>;
|
|
95
|
+
/**
|
|
96
|
+
* Check multiple permissions in a single request.
|
|
97
|
+
*
|
|
98
|
+
* `options.spanAttributes` are merged into the `permission.batch_check`
|
|
99
|
+
* span — same purpose as `checkPermission`'s spanAttributes.
|
|
100
|
+
*/
|
|
101
|
+
batchCheckPermissions(checks: PermissionCheck[], options?: {
|
|
102
|
+
spanAttributes?: Record<string, unknown>;
|
|
103
|
+
}): Promise<BatchPermissionResult>;
|
|
104
|
+
/**
|
|
105
|
+
* Create a relation tuple in Keto. Idempotent: a 409 (tuple already
|
|
106
|
+
* exists) is swallowed and reported as success. All other errors are
|
|
107
|
+
* classified and re-thrown — callers wrap in fail-open semantics if
|
|
108
|
+
* the tuple is non-critical (e.g. audit-only delegation tracking).
|
|
109
|
+
*
|
|
110
|
+
* `options.spanAttributes` are merged into the `relationship.create`
|
|
111
|
+
* trace span so callers can attach context like `delegation: "user→agent"`.
|
|
112
|
+
*/
|
|
113
|
+
createRelationship(check: PermissionCheck, options?: {
|
|
114
|
+
spanAttributes?: Record<string, unknown>;
|
|
115
|
+
}): Promise<{
|
|
116
|
+
created: boolean;
|
|
117
|
+
alreadyExisted: boolean;
|
|
118
|
+
}>;
|
|
119
|
+
/**
|
|
120
|
+
* Delete a relation tuple in Keto. Missing tuples (404) are treated as
|
|
121
|
+
* success so callers can call this idempotently when unwinding state.
|
|
122
|
+
*/
|
|
123
|
+
deleteRelationship(check: PermissionCheck, options?: {
|
|
124
|
+
spanAttributes?: Record<string, unknown>;
|
|
125
|
+
}): Promise<{
|
|
126
|
+
deleted: boolean;
|
|
127
|
+
notFound: boolean;
|
|
128
|
+
}>;
|
|
129
|
+
/**
|
|
130
|
+
* Span attributes describing which principals were attached to the
|
|
131
|
+
* client when the span was started. Useful for correlating audit logs
|
|
132
|
+
* across the user/agent split.
|
|
133
|
+
*/
|
|
134
|
+
private principalSpanAttributes;
|
|
135
|
+
/**
|
|
136
|
+
* Classify an error from any Ory API call into a structured OryError.
|
|
137
|
+
*/
|
|
138
|
+
classifyError(err: unknown): OryError;
|
|
139
|
+
private isAxiosError;
|
|
140
|
+
/**
|
|
141
|
+
* Create a client from environment variables.
|
|
142
|
+
*/
|
|
143
|
+
/**
|
|
144
|
+
* Create a client from environment variables, falling back to the
|
|
145
|
+
* shared config file at ~/.config/ory-agent-plugins/config.json.
|
|
146
|
+
*
|
|
147
|
+
* Resolution order for projectUrl and apiKey:
|
|
148
|
+
* 1. Environment variables (ORY_PROJECT_URL, ORY_API_KEY)
|
|
149
|
+
* 2. Config file (~/.config/ory-agent-plugins/config.json)
|
|
150
|
+
* 3. Placeholder (fail-open pass-through mode)
|
|
151
|
+
*/
|
|
152
|
+
static fromEnv(harness: string): OryAgentClient;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Pull the Ory backend request ID from a response headers bag (axios returns
|
|
156
|
+
* lowercase header names). Returns undefined when not present.
|
|
157
|
+
*/
|
|
158
|
+
export declare function extractOryRequestId(headers: unknown): string | undefined;
|