@ory/argus 0.2.1 → 0.4.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/dist/cli.js +36 -10
- package/dist/client.js +5 -0
- package/dist/config.js +4 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +7 -2
- package/dist/permissions.d.ts +23 -0
- package/dist/permissions.js +28 -8
- package/dist/setup.js +12 -5
- package/dist/status-cli.d.ts +37 -0
- package/dist/status-cli.js +285 -0
- package/dist/tracer.d.ts +18 -0
- package/dist/tracer.js +31 -2
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -93,7 +93,7 @@ function runConfigureCommand(binName, args) {
|
|
|
93
93
|
console.log("");
|
|
94
94
|
console.log("Or set environment variables:");
|
|
95
95
|
console.log(" export ORY_PROJECT_URL=https://your-project.projects.oryapis.com");
|
|
96
|
-
console.log(" export
|
|
96
|
+
console.log(" export ORY_AGENT_API_KEY=ory_pat_...");
|
|
97
97
|
return;
|
|
98
98
|
}
|
|
99
99
|
if (auditOnly) {
|
|
@@ -122,7 +122,7 @@ function runConfigureCommand(binName, args) {
|
|
|
122
122
|
console.log(` API Key: (set)`);
|
|
123
123
|
console.log("");
|
|
124
124
|
console.log("This configuration is shared across all Ory agent plugins.");
|
|
125
|
-
console.log("Environment variables (ORY_PROJECT_URL,
|
|
125
|
+
console.log("Environment variables (ORY_PROJECT_URL, ORY_AGENT_API_KEY) take precedence when set.");
|
|
126
126
|
}
|
|
127
127
|
/**
|
|
128
128
|
* Print the "Configuration:" block showing Ory project URL and API key status.
|
|
@@ -130,11 +130,14 @@ function runConfigureCommand(binName, args) {
|
|
|
130
130
|
function printOryConfig() {
|
|
131
131
|
const resolved = (0, config_js_1.resolveConfig)();
|
|
132
132
|
const configPath = (0, config_js_1.getConfigPath)();
|
|
133
|
+
const namespace = process.env.ORY_PERMISSION_NAMESPACE || "AgentTools";
|
|
133
134
|
console.log("Configuration:");
|
|
134
|
-
console.log(` Config file:
|
|
135
|
-
console.log(` Mode:
|
|
136
|
-
console.log(` Project URL:
|
|
137
|
-
console.log(` API Key:
|
|
135
|
+
console.log(` Config file: ${configPath}`);
|
|
136
|
+
console.log(` Mode: ${resolved.auditOnly ? "audit-only" : "full"}`);
|
|
137
|
+
console.log(` Project URL: ${resolved.projectUrl ?? "NOT SET"} [source: ${resolved.projectUrlSource}]`);
|
|
138
|
+
console.log(` API Key: ${resolved.apiKey ? "(set)" : "NOT SET"} [source: ${resolved.apiKeySource}]`);
|
|
139
|
+
console.log(` Permission mode: ${resolved.permissionMode} [source: ${resolved.permissionModeSource}]`);
|
|
140
|
+
console.log(` Namespace: ${namespace}`);
|
|
138
141
|
}
|
|
139
142
|
/**
|
|
140
143
|
* Print the "Environment:" block showing Ory-related environment variables.
|
|
@@ -143,14 +146,37 @@ function printEnvironment() {
|
|
|
143
146
|
console.log("");
|
|
144
147
|
console.log("Environment:");
|
|
145
148
|
const vars = [
|
|
146
|
-
["
|
|
149
|
+
["ORY_AUTH_GATE", process.env.ORY_AUTH_GATE, false],
|
|
150
|
+
[
|
|
151
|
+
"ORY_AGENT_API_KEY",
|
|
152
|
+
process.env.ORY_AGENT_API_KEY ? "(set)" : undefined,
|
|
153
|
+
false,
|
|
154
|
+
],
|
|
155
|
+
["ORY_AGENT_CLIENT_ID", process.env.ORY_AGENT_CLIENT_ID, false],
|
|
156
|
+
[
|
|
157
|
+
"ORY_USER_SESSION_TOKEN",
|
|
158
|
+
process.env.ORY_USER_SESSION_TOKEN ? "(set)" : undefined,
|
|
159
|
+
false,
|
|
160
|
+
],
|
|
161
|
+
["ORY_USER_SUBJECT_ID", process.env.ORY_USER_SUBJECT_ID, false],
|
|
147
162
|
["ORY_AGENT_SUBJECT_ID", process.env.ORY_AGENT_SUBJECT_ID, false],
|
|
148
163
|
["ORY_AGENT_DEBUG", process.env.ORY_AGENT_DEBUG, false],
|
|
149
164
|
["ORY_AGENT_LOG_FILE", process.env.ORY_AGENT_LOG_FILE, false],
|
|
150
165
|
["ORY_AGENT_TRACE_FILE", process.env.ORY_AGENT_TRACE_FILE, false],
|
|
151
166
|
];
|
|
167
|
+
if (process.env.ORY_API_KEY && !process.env.ORY_AGENT_API_KEY) {
|
|
168
|
+
vars.splice(2, 0, [
|
|
169
|
+
"ORY_API_KEY",
|
|
170
|
+
"(set, DEPRECATED — use ORY_AGENT_API_KEY)",
|
|
171
|
+
false,
|
|
172
|
+
]);
|
|
173
|
+
}
|
|
152
174
|
for (const [name, value, required] of vars) {
|
|
153
|
-
const display = value
|
|
175
|
+
const display = value
|
|
176
|
+
? value
|
|
177
|
+
: required
|
|
178
|
+
? "NOT SET (required)"
|
|
179
|
+
: "not set";
|
|
154
180
|
const icon = value ? "+" : required ? "!" : "-";
|
|
155
181
|
console.log(` [${icon}] ${name}: ${display}`);
|
|
156
182
|
}
|
|
@@ -188,7 +214,7 @@ function printEnvHelp(binName) {
|
|
|
188
214
|
console.log("");
|
|
189
215
|
console.log(" Option 2 — Set environment variables:");
|
|
190
216
|
console.log(" export ORY_PROJECT_URL=https://your-project.projects.oryapis.com");
|
|
191
|
-
console.log(" export
|
|
217
|
+
console.log(" export ORY_AGENT_API_KEY=ory_pat_...");
|
|
192
218
|
console.log("");
|
|
193
219
|
console.log("If neither is set when a session starts, the agent will prompt for configuration.");
|
|
194
220
|
}
|
|
@@ -355,7 +381,7 @@ async function interactiveConfigPrompt(binName) {
|
|
|
355
381
|
"",
|
|
356
382
|
"You can also set environment variables (requires session restart):",
|
|
357
383
|
" export ORY_PROJECT_URL=https://your-project.projects.oryapis.com",
|
|
358
|
-
" export
|
|
384
|
+
" export ORY_AGENT_API_KEY=ory_pat_...",
|
|
359
385
|
"",
|
|
360
386
|
].join("\n");
|
|
361
387
|
process.stderr.write(message + "\n");
|
package/dist/client.js
CHANGED
|
@@ -38,6 +38,11 @@ class OryAgentClient {
|
|
|
38
38
|
traceFile: config.traceFile,
|
|
39
39
|
...(config.exporter ? { exporter: config.exporter } : {}),
|
|
40
40
|
});
|
|
41
|
+
// Every span — including direct `tracer.record(...)` calls from
|
|
42
|
+
// plugin handlers — gets userSubject / agentSubject merged in, so
|
|
43
|
+
// tool.invoke / tool.block / permission.observe_deny spans carry
|
|
44
|
+
// the same principal attribution as the API-call spans.
|
|
45
|
+
this.tracer.setAttributeEnricher(() => this.principalSpanAttributes());
|
|
41
46
|
// Seed agent principal from the constructor `apiKey` so existing
|
|
42
47
|
// call sites keep working until they migrate to ensureAgentIdentity.
|
|
43
48
|
if (config.apiKey)
|
package/dist/config.js
CHANGED
|
@@ -323,7 +323,10 @@ function writeConfigAtomic(config) {
|
|
|
323
323
|
function resolveConfig() {
|
|
324
324
|
const file = loadConfig();
|
|
325
325
|
const envProjectUrl = process.env.ORY_PROJECT_URL;
|
|
326
|
-
|
|
326
|
+
// ORY_AGENT_API_KEY is the documented name; ORY_API_KEY is the deprecated
|
|
327
|
+
// alias kept for back-compat (agent-auth emits a warning when only the
|
|
328
|
+
// legacy form is set).
|
|
329
|
+
const envApiKey = process.env.ORY_AGENT_API_KEY ?? process.env.ORY_API_KEY;
|
|
327
330
|
const envMode = parsePermissionMode(process.env.ORY_PERMISSION_MODE);
|
|
328
331
|
const permissionMode = envMode ?? file.permissionMode ?? "observe";
|
|
329
332
|
const permissionModeSource = envMode
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { OryAgentClient, type OryAgentConfig, type PrincipalIdentity, } from "./client.js";
|
|
2
2
|
export { DebugLogger, redactLogData, type LogEntry, type LogLevel, } from "./logger.js";
|
|
3
|
-
export { Tracer, ActiveSpan, deriveTraceId, formatSpan, watchTraceFile, type TraceEvent, type SpanStatus, type TraceSpan, type SpanOptions, type TracerOptions, type TracerContext, } from "./tracer.js";
|
|
3
|
+
export { Tracer, ActiveSpan, deriveTraceId, formatSpan, watchTraceFile, type TraceEvent, type SpanStatus, type TraceSpan, type SpanOptions, type TracerOptions, type TracerContext, type SpanAttributeEnricher, } from "./tracer.js";
|
|
4
4
|
export { loadConfig, saveConfig, resolveConfig, mutateConfig, getConfigPath, getDataDir, getHarnessDataDir, type OryPluginConfig, type OryOAuth2Tokens, type OryUserCredentials, type OryAgentCredentialsBlock, type OryAgentDynamicCredentials, type PermissionMode, } from "./config.js";
|
|
5
5
|
export { pkceLogin, refreshAccessToken, detectHeadless, generateCodeVerifier, sha256Base64Url, buildAuthorizeUrl, LOOPBACK_PORTS, DEFAULT_LOGIN_TIMEOUT_MS, type PkceLoginOptions, type PkceLoginOutcome, type PkceDeclineReason, } from "./auth.js";
|
|
6
6
|
export { loadTokens, saveTokens, clearTokens, isExpired, refreshAndSave, tryAcquirePkceFlightLock, clearPkceFlightLock, waitForPeerTokens, waitForPeerTokensSync, TOKEN_EXPIRY_SKEW_SEC, type PkceFlightLock, } from "./auth-store.js";
|
|
@@ -9,12 +9,13 @@ export { resolveAgentCredentials, ensureAgentIdentity, ensureSubAgentIdentity, f
|
|
|
9
9
|
export { type SessionInfo, type OAuth2TokenInfo, type PermissionCheck, type PermissionResult, type BatchPermissionResult, type OryError, type OryErrorCode, } from "./types.js";
|
|
10
10
|
export { runConfigureCommand, runAgentCommand, printOryConfig, printEnvironment, printLogTail, printEnvHelp, printTraceTail, runWatchCommand, isTtyAvailable, promptOnTty, promptForProjectUrl, interactiveConfigPrompt, } from "./cli.js";
|
|
11
11
|
export { runPermissionsCommand, isUserIdentityCached, maybeAutoBootstrap, printPermissionsOnboardingHelp, } from "./permissions-cli.js";
|
|
12
|
+
export { runStatusCommand, printUserIdentitySection, printAgentIdentitySection, printPermissionsSection, type StatusCommandOptions, } from "./status-cli.js";
|
|
12
13
|
export { parseSetupArgs, readJsonFile, writeJsonFile, isOryHookCommand, resolveHookCommand, matcherHookEntry, mergeMatcherHooks, removeMatcherHooks, flatHookEntry, mergeFlatHooks, removeFlatHooks, printSetupHelp, printNextSteps, resolveMcpServerCommand, mcpServerEntry, mergeMcpServer, removeMcpServer, registerPlugin, unregisterPlugin, type SetupArgs, type HookCommand, type MatcherEntry, } from "./setup.js";
|
|
13
14
|
export { runDevLauncher, type DevLauncherConfig, type InstallContext, } from "./dev.js";
|
|
14
15
|
export { renderOrySkills, renderOryCommands, commandToSkill, commandToToml, commandToFrontmatterMarkdown, commandToPlainMarkdown, toSkillMarkdown, writeSkillTree, removeSkillDirs, ORY_SKILL_NAMES, ORY_COMMAND_SKILL_NAMES, ORY_COMMAND_SLUGS, type RenderedSkill, type RenderedCommand, type RenderProfileOptions, } from "./skills.js";
|
|
15
16
|
export { runLocalCommand, ensureDevJaeger, stopDevJaeger, DEV_JAEGER_CONTAINER, type EnsureDevJaegerResult, type StopDevJaegerResult, } from "./local/index.js";
|
|
16
17
|
export { runRegistryCommand } from "./registry/index.js";
|
|
17
|
-
export { checkAndDecide, applyPermissionMode, type PermissionDecision, type ModeDecision, type CheckAndDecideOptions, type ApplyPermissionModeContext, } from "./permissions.js";
|
|
18
|
+
export { checkAndDecide, applyPermissionMode, type PermissionDecision, type ModeDecision, type DecisionSpanAttributes, type CheckAndDecideOptions, type ApplyPermissionModeContext, } from "./permissions.js";
|
|
18
19
|
export { HARNESS_TOOL_CATALOG, KNOWN_HARNESSES, ALL_TOOLS, getToolCatalog, type KnownHarness, } from "./tool-catalog.js";
|
|
19
20
|
export { parseClaudeCodeMcpTool, parseGeminiMcpTool, parseMcpToolGeneric, checkMcpPermission, type McpToolIdentifier, type McpPermissionCheckOptions, type McpPermissionResult, } from "./mcp.js";
|
|
20
21
|
export { resolveUserSubject, subjectLabel, type UserSubjectRef, } from "./subject.js";
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.printOryConfig = exports.runAgentCommand = exports.runConfigureCommand = exports.AGENT_TOKEN_EXPIRY_SKEW_SEC = exports.clearSubAgentDynamicCredentials = exports.saveSubAgentDynamicCredentials = exports.loadSubAgentDynamicCredentials = exports.clearAgentDynamicCredentials = exports.saveAgentDynamicCredentials = exports.loadAgentDynamicCredentials = exports.registerAgentClient = exports.fetchClientCredentialsToken = exports.ensureSubAgentIdentity = exports.ensureAgentIdentity = exports.resolveAgentCredentials = exports.ensureAuthenticated = exports.ensureUserAuthenticated = exports.TOKEN_EXPIRY_SKEW_SEC = exports.waitForPeerTokensSync = exports.waitForPeerTokens = exports.clearPkceFlightLock = exports.tryAcquirePkceFlightLock = exports.refreshAndSave = exports.isExpired = exports.clearTokens = exports.saveTokens = exports.loadTokens = exports.DEFAULT_LOGIN_TIMEOUT_MS = exports.LOOPBACK_PORTS = exports.buildAuthorizeUrl = exports.sha256Base64Url = exports.generateCodeVerifier = exports.detectHeadless = exports.refreshAccessToken = exports.pkceLogin = exports.getHarnessDataDir = exports.getDataDir = exports.getConfigPath = exports.mutateConfig = exports.resolveConfig = exports.saveConfig = exports.loadConfig = exports.watchTraceFile = exports.formatSpan = exports.deriveTraceId = exports.ActiveSpan = exports.Tracer = exports.redactLogData = exports.DebugLogger = exports.OryAgentClient = void 0;
|
|
4
|
-
exports.
|
|
5
|
-
exports.parseKeyValueList = exports.otlpExporterFromEnv = exports.OtlpHttpExporter = exports.summarizeToolOutput = exports.summarizeToolInput = exports.OryDenialError = exports.alertAttributes = exports.formatAlertSummary = exports.formatAlertMessage = exports.formatDenialSummary = exports.formatDenialMessage = exports.subjectLabel = exports.resolveUserSubject = exports.checkMcpPermission = exports.parseMcpToolGeneric = exports.parseGeminiMcpTool = exports.parseClaudeCodeMcpTool = exports.getToolCatalog = exports.ALL_TOOLS = exports.KNOWN_HARNESSES = exports.HARNESS_TOOL_CATALOG = exports.applyPermissionMode = exports.checkAndDecide = void 0;
|
|
4
|
+
exports.runLocalCommand = exports.ORY_COMMAND_SLUGS = exports.ORY_COMMAND_SKILL_NAMES = exports.ORY_SKILL_NAMES = exports.removeSkillDirs = exports.writeSkillTree = exports.toSkillMarkdown = exports.commandToPlainMarkdown = exports.commandToFrontmatterMarkdown = exports.commandToToml = exports.commandToSkill = exports.renderOryCommands = exports.renderOrySkills = exports.runDevLauncher = exports.unregisterPlugin = exports.registerPlugin = exports.removeMcpServer = exports.mergeMcpServer = exports.mcpServerEntry = exports.resolveMcpServerCommand = exports.printNextSteps = exports.printSetupHelp = exports.removeFlatHooks = exports.mergeFlatHooks = exports.flatHookEntry = exports.removeMatcherHooks = exports.mergeMatcherHooks = exports.matcherHookEntry = exports.resolveHookCommand = exports.isOryHookCommand = exports.writeJsonFile = exports.readJsonFile = exports.parseSetupArgs = exports.printPermissionsSection = exports.printAgentIdentitySection = exports.printUserIdentitySection = exports.runStatusCommand = exports.printPermissionsOnboardingHelp = exports.maybeAutoBootstrap = exports.isUserIdentityCached = exports.runPermissionsCommand = exports.interactiveConfigPrompt = exports.promptForProjectUrl = exports.promptOnTty = exports.isTtyAvailable = exports.runWatchCommand = exports.printTraceTail = exports.printEnvHelp = exports.printLogTail = exports.printEnvironment = void 0;
|
|
5
|
+
exports.parseKeyValueList = exports.otlpExporterFromEnv = exports.OtlpHttpExporter = exports.summarizeToolOutput = exports.summarizeToolInput = exports.OryDenialError = exports.alertAttributes = exports.formatAlertSummary = exports.formatAlertMessage = exports.formatDenialSummary = exports.formatDenialMessage = exports.subjectLabel = exports.resolveUserSubject = exports.checkMcpPermission = exports.parseMcpToolGeneric = exports.parseGeminiMcpTool = exports.parseClaudeCodeMcpTool = exports.getToolCatalog = exports.ALL_TOOLS = exports.KNOWN_HARNESSES = exports.HARNESS_TOOL_CATALOG = exports.applyPermissionMode = exports.checkAndDecide = exports.runRegistryCommand = exports.DEV_JAEGER_CONTAINER = exports.stopDevJaeger = exports.ensureDevJaeger = void 0;
|
|
6
6
|
var client_js_1 = require("./client.js");
|
|
7
7
|
Object.defineProperty(exports, "OryAgentClient", { enumerable: true, get: function () { return client_js_1.OryAgentClient; } });
|
|
8
8
|
var logger_js_1 = require("./logger.js");
|
|
@@ -76,6 +76,11 @@ Object.defineProperty(exports, "runPermissionsCommand", { enumerable: true, get:
|
|
|
76
76
|
Object.defineProperty(exports, "isUserIdentityCached", { enumerable: true, get: function () { return permissions_cli_js_1.isUserIdentityCached; } });
|
|
77
77
|
Object.defineProperty(exports, "maybeAutoBootstrap", { enumerable: true, get: function () { return permissions_cli_js_1.maybeAutoBootstrap; } });
|
|
78
78
|
Object.defineProperty(exports, "printPermissionsOnboardingHelp", { enumerable: true, get: function () { return permissions_cli_js_1.printPermissionsOnboardingHelp; } });
|
|
79
|
+
var status_cli_js_1 = require("./status-cli.js");
|
|
80
|
+
Object.defineProperty(exports, "runStatusCommand", { enumerable: true, get: function () { return status_cli_js_1.runStatusCommand; } });
|
|
81
|
+
Object.defineProperty(exports, "printUserIdentitySection", { enumerable: true, get: function () { return status_cli_js_1.printUserIdentitySection; } });
|
|
82
|
+
Object.defineProperty(exports, "printAgentIdentitySection", { enumerable: true, get: function () { return status_cli_js_1.printAgentIdentitySection; } });
|
|
83
|
+
Object.defineProperty(exports, "printPermissionsSection", { enumerable: true, get: function () { return status_cli_js_1.printPermissionsSection; } });
|
|
79
84
|
var setup_js_1 = require("./setup.js");
|
|
80
85
|
Object.defineProperty(exports, "parseSetupArgs", { enumerable: true, get: function () { return setup_js_1.parseSetupArgs; } });
|
|
81
86
|
Object.defineProperty(exports, "readJsonFile", { enumerable: true, get: function () { return setup_js_1.readJsonFile; } });
|
package/dist/permissions.d.ts
CHANGED
|
@@ -23,22 +23,40 @@
|
|
|
23
23
|
import type { OryAgentClient } from "./client.js";
|
|
24
24
|
import { type PermissionMode } from "./config.js";
|
|
25
25
|
import type { OryError, PermissionCheck, PermissionResult } from "./types.js";
|
|
26
|
+
/**
|
|
27
|
+
* Attributes describing *what was checked* and *under which posture*.
|
|
28
|
+
* Plugins spread this onto their `tool.invoke` / `tool.block` spans so
|
|
29
|
+
* the audit trail makes the observe-vs-enforce posture and the checked
|
|
30
|
+
* subject visible at a glance — without that, a span with `allowed=false`
|
|
31
|
+
* status=ok is ambiguous (observe pass-through? fail-open? bug?).
|
|
32
|
+
*/
|
|
33
|
+
export interface DecisionSpanAttributes {
|
|
34
|
+
permissionMode: PermissionMode;
|
|
35
|
+
/** Direct subject ID when the check used `subjectId`. */
|
|
36
|
+
subjectId?: string;
|
|
37
|
+
/** SubjectSet rendered as `<namespace>:<object>#<relation>` when used. */
|
|
38
|
+
subjectSet?: string;
|
|
39
|
+
}
|
|
26
40
|
export type PermissionDecision = {
|
|
27
41
|
kind: "allow";
|
|
28
42
|
result: PermissionResult;
|
|
29
43
|
mode: PermissionMode;
|
|
44
|
+
spanAttributes: DecisionSpanAttributes;
|
|
30
45
|
} | {
|
|
31
46
|
kind: "deny";
|
|
32
47
|
result: PermissionResult;
|
|
33
48
|
mode: "enforce";
|
|
49
|
+
spanAttributes: DecisionSpanAttributes;
|
|
34
50
|
} | {
|
|
35
51
|
kind: "observe";
|
|
36
52
|
result: PermissionResult;
|
|
37
53
|
mode: "observe";
|
|
54
|
+
spanAttributes: DecisionSpanAttributes;
|
|
38
55
|
} | {
|
|
39
56
|
kind: "fail_open";
|
|
40
57
|
error: OryError;
|
|
41
58
|
mode: PermissionMode;
|
|
59
|
+
spanAttributes: DecisionSpanAttributes;
|
|
42
60
|
};
|
|
43
61
|
/**
|
|
44
62
|
* The "what should the caller do?" half of a decision, independent of
|
|
@@ -49,12 +67,15 @@ export type PermissionDecision = {
|
|
|
49
67
|
export type ModeDecision = {
|
|
50
68
|
kind: "allow";
|
|
51
69
|
mode: PermissionMode;
|
|
70
|
+
spanAttributes: DecisionSpanAttributes;
|
|
52
71
|
} | {
|
|
53
72
|
kind: "deny";
|
|
54
73
|
mode: "enforce";
|
|
74
|
+
spanAttributes: DecisionSpanAttributes;
|
|
55
75
|
} | {
|
|
56
76
|
kind: "observe";
|
|
57
77
|
mode: "observe";
|
|
78
|
+
spanAttributes: DecisionSpanAttributes;
|
|
58
79
|
};
|
|
59
80
|
export interface CheckAndDecideOptions {
|
|
60
81
|
/**
|
|
@@ -81,6 +102,8 @@ export interface ApplyPermissionModeContext {
|
|
|
81
102
|
relation?: string;
|
|
82
103
|
/** Subject ID for the observe-deny log line, if available. */
|
|
83
104
|
subjectId?: string;
|
|
105
|
+
/** SubjectSet for the observe-deny log line, if available. */
|
|
106
|
+
subjectSet?: PermissionCheck["subjectSet"];
|
|
84
107
|
/** Attributes merged into the `permission.observe_deny` span. */
|
|
85
108
|
spanAttributes?: Record<string, unknown>;
|
|
86
109
|
/** Override the resolved mode (tests). */
|
package/dist/permissions.js
CHANGED
|
@@ -25,6 +25,22 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
25
25
|
exports.applyPermissionMode = applyPermissionMode;
|
|
26
26
|
exports.checkAndDecide = checkAndDecide;
|
|
27
27
|
const config_js_1 = require("./config.js");
|
|
28
|
+
function formatSubjectSet(set) {
|
|
29
|
+
if (!set)
|
|
30
|
+
return undefined;
|
|
31
|
+
return `${set.namespace}:${set.object}#${set.relation}`;
|
|
32
|
+
}
|
|
33
|
+
function buildDecisionAttributes(mode, subjectId, subjectSet) {
|
|
34
|
+
const set = formatSubjectSet(subjectSet);
|
|
35
|
+
// SubjectSet supersedes subjectId in the span (the printable label is
|
|
36
|
+
// derivable from it); avoid emitting both so consumers don't need to
|
|
37
|
+
// dedupe.
|
|
38
|
+
if (set)
|
|
39
|
+
return { permissionMode: mode, subjectSet: set };
|
|
40
|
+
if (subjectId)
|
|
41
|
+
return { permissionMode: mode, subjectId };
|
|
42
|
+
return { permissionMode: mode };
|
|
43
|
+
}
|
|
28
44
|
/**
|
|
29
45
|
* Map an `allowed` boolean from any permission check (plain or MCP)
|
|
30
46
|
* onto a {@link ModeDecision}. When `allowed === false` and the mode
|
|
@@ -34,14 +50,16 @@ const config_js_1 = require("./config.js");
|
|
|
34
50
|
*/
|
|
35
51
|
function applyPermissionMode(client, allowed, context = {}) {
|
|
36
52
|
const mode = context.modeOverride ?? (0, config_js_1.resolveConfig)().permissionMode;
|
|
53
|
+
const spanAttributes = buildDecisionAttributes(mode, context.subjectId, context.subjectSet);
|
|
37
54
|
if (allowed)
|
|
38
|
-
return { kind: "allow", mode };
|
|
55
|
+
return { kind: "allow", mode, spanAttributes };
|
|
39
56
|
if (mode === "observe") {
|
|
40
57
|
client.logger.warn("permission.observe_deny", {
|
|
41
58
|
namespace: context.namespace,
|
|
42
59
|
object: context.object,
|
|
43
60
|
relation: context.relation,
|
|
44
61
|
subjectId: context.subjectId,
|
|
62
|
+
subjectSet: spanAttributes.subjectSet,
|
|
45
63
|
note: "denied by Ory; allowed by plugin in observe mode",
|
|
46
64
|
});
|
|
47
65
|
client.tracer.record("permission.observe_deny", "denied", {
|
|
@@ -49,13 +67,13 @@ function applyPermissionMode(client, allowed, context = {}) {
|
|
|
49
67
|
namespace: context.namespace,
|
|
50
68
|
object: context.object,
|
|
51
69
|
relation: context.relation,
|
|
52
|
-
|
|
70
|
+
...spanAttributes,
|
|
53
71
|
...context.spanAttributes,
|
|
54
72
|
},
|
|
55
73
|
});
|
|
56
|
-
return { kind: "observe", mode: "observe" };
|
|
74
|
+
return { kind: "observe", mode: "observe", spanAttributes };
|
|
57
75
|
}
|
|
58
|
-
return { kind: "deny", mode: "enforce" };
|
|
76
|
+
return { kind: "deny", mode: "enforce", spanAttributes };
|
|
59
77
|
}
|
|
60
78
|
/**
|
|
61
79
|
* Run a permission check and resolve the configured mode against the
|
|
@@ -64,6 +82,7 @@ function applyPermissionMode(client, allowed, context = {}) {
|
|
|
64
82
|
*/
|
|
65
83
|
async function checkAndDecide(client, check, opts = {}) {
|
|
66
84
|
const mode = opts.modeOverride ?? (0, config_js_1.resolveConfig)().permissionMode;
|
|
85
|
+
const spanAttributes = buildDecisionAttributes(mode, check.subjectId, check.subjectSet);
|
|
67
86
|
let result;
|
|
68
87
|
try {
|
|
69
88
|
result = await client.checkPermission(check, {
|
|
@@ -71,19 +90,20 @@ async function checkAndDecide(client, check, opts = {}) {
|
|
|
71
90
|
});
|
|
72
91
|
}
|
|
73
92
|
catch (err) {
|
|
74
|
-
return { kind: "fail_open", error: err, mode };
|
|
93
|
+
return { kind: "fail_open", error: err, mode, spanAttributes };
|
|
75
94
|
}
|
|
76
95
|
const inner = applyPermissionMode(client, result.allowed, {
|
|
77
96
|
namespace: check.namespace,
|
|
78
97
|
object: check.object,
|
|
79
98
|
relation: check.relation,
|
|
80
99
|
subjectId: check.subjectId,
|
|
100
|
+
subjectSet: check.subjectSet,
|
|
81
101
|
spanAttributes: opts.spanAttributes,
|
|
82
102
|
modeOverride: opts.modeOverride,
|
|
83
103
|
});
|
|
84
104
|
if (inner.kind === "allow")
|
|
85
|
-
return { kind: "allow", result, mode: inner.mode };
|
|
105
|
+
return { kind: "allow", result, mode: inner.mode, spanAttributes: inner.spanAttributes };
|
|
86
106
|
if (inner.kind === "observe")
|
|
87
|
-
return { kind: "observe", result, mode: "observe" };
|
|
88
|
-
return { kind: "deny", result, mode: "enforce" };
|
|
107
|
+
return { kind: "observe", result, mode: "observe", spanAttributes: inner.spanAttributes };
|
|
108
|
+
return { kind: "deny", result, mode: "enforce", spanAttributes: inner.spanAttributes };
|
|
89
109
|
}
|
package/dist/setup.js
CHANGED
|
@@ -372,8 +372,10 @@ Options:
|
|
|
372
372
|
|
|
373
373
|
Environment variables:
|
|
374
374
|
ORY_PROJECT_URL Your Ory project URL (required at runtime)
|
|
375
|
-
|
|
376
|
-
|
|
375
|
+
ORY_AGENT_API_KEY Agent API key / OAuth2 bearer (preferred name;
|
|
376
|
+
ORY_API_KEY is honored as a deprecated alias)
|
|
377
|
+
ORY_AUTH_GATE Set to "1" to enable the interactive user login gate
|
|
378
|
+
ORY_PERMISSION_MODE "observe" (default) or "enforce" — what to do on deny
|
|
377
379
|
ORY_AGENT_DEBUG Set to "true" for debug logging
|
|
378
380
|
ORY_AGENT_LOG_FILE Path to write debug logs
|
|
379
381
|
ORY_AGENT_SUBJECT_ID Override the subject ID for permission checks
|
|
@@ -387,12 +389,17 @@ function printNextSteps(harnessName, uninstallCmd) {
|
|
|
387
389
|
console.log("Next steps:");
|
|
388
390
|
console.log(" 1. Set required environment variables:");
|
|
389
391
|
console.log(" export ORY_PROJECT_URL=https://your-project.projects.oryapis.com");
|
|
390
|
-
console.log(" export
|
|
392
|
+
console.log(" export ORY_AGENT_API_KEY=ory_pat_...");
|
|
391
393
|
console.log("");
|
|
392
|
-
console.log(" 2.
|
|
394
|
+
console.log(" 2. Turn on the interactive user login gate (opt-in):");
|
|
395
|
+
console.log(" export ORY_AUTH_GATE=1");
|
|
396
|
+
console.log(` Without this, ${harnessName} runs without a human Ory identity attached`);
|
|
397
|
+
console.log(" to the session and permission checks fall back to a session:<id> subject.");
|
|
398
|
+
console.log("");
|
|
399
|
+
console.log(" 3. Optionally enable debug logging:");
|
|
393
400
|
console.log(" export ORY_AGENT_DEBUG=true");
|
|
394
401
|
console.log("");
|
|
395
|
-
console.log(`
|
|
402
|
+
console.log(` 4. Start ${harnessName} in your project directory.`);
|
|
396
403
|
console.log("");
|
|
397
404
|
console.log(`To uninstall: ${uninstallCmd}`);
|
|
398
405
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Render the "User identity" block. Shows the `ORY_AUTH_GATE` state and,
|
|
3
|
+
* when a PKCE token is cached, the resolved subject and expiry.
|
|
4
|
+
*/
|
|
5
|
+
export declare function printUserIdentitySection(): void;
|
|
6
|
+
/**
|
|
7
|
+
* Render the "Agent identity" block. Summarizes the resolved machine
|
|
8
|
+
* credential — static env override, persisted DCR, or unconfigured.
|
|
9
|
+
*/
|
|
10
|
+
export declare function printAgentIdentitySection(): void;
|
|
11
|
+
/**
|
|
12
|
+
* Render the "Permissions" block. Always prints the configured mode and
|
|
13
|
+
* namespace; runs a network probe of the harness's built-in tool catalog
|
|
14
|
+
* only when a project URL is configured *and* a user identity is
|
|
15
|
+
* available (cached PKCE tokens or `ORY_USER_SUBJECT_ID`).
|
|
16
|
+
*/
|
|
17
|
+
export declare function printPermissionsSection(binName: string, harness: string): Promise<void>;
|
|
18
|
+
export interface StatusCommandOptions {
|
|
19
|
+
/** Display title — typically the harness's human-friendly name, e.g. "Claude Code". */
|
|
20
|
+
title: string;
|
|
21
|
+
/**
|
|
22
|
+
* Optional renderer for the "Hooks & plugin" section. Each harness owns
|
|
23
|
+
* the layout for its plugin-specific registration data (extension dir,
|
|
24
|
+
* marketplace plugin ID, hook script path, etc.).
|
|
25
|
+
*/
|
|
26
|
+
printPluginSection?: () => void;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Compose the unified `status` output for a plugin CLI. Sections render
|
|
30
|
+
* in this order: title → configuration → user identity → agent identity
|
|
31
|
+
* → permissions → hooks & plugin → environment → recent log tail.
|
|
32
|
+
*
|
|
33
|
+
* Each section degrades gracefully — missing project URL, audit-only
|
|
34
|
+
* mode, and empty token caches all surface as inline "n/a" lines rather
|
|
35
|
+
* than aborting the command.
|
|
36
|
+
*/
|
|
37
|
+
export declare function runStatusCommand(binName: string, harness: string, options: StatusCommandOptions): Promise<void>;
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.printUserIdentitySection = printUserIdentitySection;
|
|
4
|
+
exports.printAgentIdentitySection = printAgentIdentitySection;
|
|
5
|
+
exports.printPermissionsSection = printPermissionsSection;
|
|
6
|
+
exports.runStatusCommand = runStatusCommand;
|
|
7
|
+
/**
|
|
8
|
+
* Unified `status` CLI implementation. Each harness's `cli/main.ts` `status`
|
|
9
|
+
* subcommand delegates here so onboarding readers see configuration, user
|
|
10
|
+
* identity, agent identity, permissions, and the harness-specific plugin
|
|
11
|
+
* registration block in a single pass.
|
|
12
|
+
*
|
|
13
|
+
* The drill-down commands (`agent status`, `permissions status`) remain
|
|
14
|
+
* unchanged — this composes their inputs without altering their output.
|
|
15
|
+
*/
|
|
16
|
+
const config_js_1 = require("./config.js");
|
|
17
|
+
const auth_store_js_1 = require("./auth-store.js");
|
|
18
|
+
const agent_auth_js_1 = require("./agent-auth.js");
|
|
19
|
+
const client_js_1 = require("./client.js");
|
|
20
|
+
const agent_auth_js_2 = require("./agent-auth.js");
|
|
21
|
+
const subject_js_1 = require("./subject.js");
|
|
22
|
+
const tool_catalog_js_1 = require("./tool-catalog.js");
|
|
23
|
+
const cli_js_1 = require("./cli.js");
|
|
24
|
+
function resolveNamespace() {
|
|
25
|
+
return process.env.ORY_PERMISSION_NAMESPACE ?? "AgentTools";
|
|
26
|
+
}
|
|
27
|
+
const AUTH_GATE_ENABLED = new Set(["1", "true", "yes", "on"]);
|
|
28
|
+
function isAuthGateOn() {
|
|
29
|
+
const v = process.env.ORY_AUTH_GATE?.toLowerCase();
|
|
30
|
+
return !!v && AUTH_GATE_ENABLED.has(v);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Format a relative duration like "4h 32m" or "12s" for a future deadline.
|
|
34
|
+
* Returns "expired" when the deadline has already passed.
|
|
35
|
+
*/
|
|
36
|
+
function formatExpiresIn(expiresAtSec, nowMs = Date.now()) {
|
|
37
|
+
const remaining = expiresAtSec - Math.floor(nowMs / 1000);
|
|
38
|
+
if (remaining <= 0)
|
|
39
|
+
return "expired";
|
|
40
|
+
const days = Math.floor(remaining / 86400);
|
|
41
|
+
const hours = Math.floor((remaining % 86400) / 3600);
|
|
42
|
+
const minutes = Math.floor((remaining % 3600) / 60);
|
|
43
|
+
if (days > 0)
|
|
44
|
+
return `${days}d ${hours}h`;
|
|
45
|
+
if (hours > 0)
|
|
46
|
+
return `${hours}h ${minutes}m`;
|
|
47
|
+
if (minutes > 0)
|
|
48
|
+
return `${minutes}m`;
|
|
49
|
+
return `${remaining}s`;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Render the "User identity" block. Shows the `ORY_AUTH_GATE` state and,
|
|
53
|
+
* when a PKCE token is cached, the resolved subject and expiry.
|
|
54
|
+
*/
|
|
55
|
+
function printUserIdentitySection() {
|
|
56
|
+
const gateOn = isAuthGateOn();
|
|
57
|
+
const tokens = (0, auth_store_js_1.loadTokens)();
|
|
58
|
+
console.log("");
|
|
59
|
+
console.log("User identity (interactive PKCE login):");
|
|
60
|
+
console.log(` Gate: ${gateOn ? "on (ORY_AUTH_GATE)" : "off (set ORY_AUTH_GATE=1 to enable)"}`);
|
|
61
|
+
if (!tokens) {
|
|
62
|
+
console.log(" Token cache: empty");
|
|
63
|
+
if (gateOn) {
|
|
64
|
+
console.log(" Subject: (will resolve at next session start)");
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
console.log(" Subject: (gate disabled — no PKCE login will run)");
|
|
68
|
+
}
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const expired = (0, auth_store_js_1.isExpired)(tokens);
|
|
72
|
+
if (expired) {
|
|
73
|
+
console.log(` Token cache: stale (${formatExpiresIn(tokens.expiresAt)}) — will refresh on next session start`);
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
console.log(` Token cache: present (expires in ${formatExpiresIn(tokens.expiresAt)})`);
|
|
77
|
+
}
|
|
78
|
+
const subject = tokens.subject ? `user:${tokens.subject}` : "(no subject claim on cached token)";
|
|
79
|
+
console.log(` Subject: ${subject}`);
|
|
80
|
+
if (tokens.clientId) {
|
|
81
|
+
console.log(` Client ID: ${tokens.clientId}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Render the "Agent identity" block. Summarizes the resolved machine
|
|
86
|
+
* credential — static env override, persisted DCR, or unconfigured.
|
|
87
|
+
*/
|
|
88
|
+
function printAgentIdentitySection() {
|
|
89
|
+
console.log("");
|
|
90
|
+
console.log("Agent identity (non-interactive):");
|
|
91
|
+
// Static overrides take precedence — surface them so the reader knows
|
|
92
|
+
// why DCR isn't running.
|
|
93
|
+
if (process.env.ORY_AGENT_API_KEY) {
|
|
94
|
+
console.log(" Source: static API key (ORY_AGENT_API_KEY)");
|
|
95
|
+
if (process.env.ORY_AGENT_SUBJECT_ID) {
|
|
96
|
+
console.log(` Subject: ${process.env.ORY_AGENT_SUBJECT_ID}`);
|
|
97
|
+
}
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (process.env.ORY_AGENT_CLIENT_ID && process.env.ORY_AGENT_CLIENT_SECRET) {
|
|
101
|
+
console.log(" Source: static client_credentials (ORY_AGENT_CLIENT_ID/SECRET)");
|
|
102
|
+
console.log(` Client ID: ${process.env.ORY_AGENT_CLIENT_ID}`);
|
|
103
|
+
const subject = process.env.ORY_AGENT_SUBJECT_ID ?? process.env.ORY_AGENT_CLIENT_ID;
|
|
104
|
+
console.log(` Subject: ${subject}`);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const persisted = (0, agent_auth_js_1.loadAgentDynamicCredentials)();
|
|
108
|
+
if (!persisted) {
|
|
109
|
+
console.log(" Source: not registered yet");
|
|
110
|
+
console.log(" Status: first session start will register an OAuth2 client (RFC 7591)");
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const resolved = (0, config_js_1.resolveConfig)();
|
|
114
|
+
const mismatched = resolved.projectUrl && persisted.projectUrl !== resolved.projectUrl;
|
|
115
|
+
console.log(" Source: OAuth2 Dynamic Client Registration (RFC 7591)");
|
|
116
|
+
console.log(` Status: registered`);
|
|
117
|
+
console.log(` Client ID: ${persisted.clientId}`);
|
|
118
|
+
const subject = process.env.ORY_AGENT_SUBJECT_ID ?? persisted.clientId;
|
|
119
|
+
console.log(` Subject: ${subject}`);
|
|
120
|
+
console.log(` Registered: ${new Date(persisted.registeredAt * 1000).toISOString()}`);
|
|
121
|
+
if (mismatched) {
|
|
122
|
+
console.log(` Warning: registered against ${persisted.projectUrl}, but current project URL is ${resolved.projectUrl}`);
|
|
123
|
+
console.log(" run `agent unregister` to clear stale credentials");
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
async function probePermissionsCoverage(harness) {
|
|
127
|
+
const resolved = (0, config_js_1.resolveConfig)();
|
|
128
|
+
if (!resolved.projectUrl)
|
|
129
|
+
return undefined;
|
|
130
|
+
const catalog = (0, tool_catalog_js_1.getToolCatalog)(harness);
|
|
131
|
+
if (catalog.length === 0)
|
|
132
|
+
return undefined;
|
|
133
|
+
const tokens = (0, auth_store_js_1.loadTokens)();
|
|
134
|
+
const subjectOverride = process.env.ORY_USER_SUBJECT_ID;
|
|
135
|
+
if (!subjectOverride && (!tokens || (0, auth_store_js_1.isExpired)(tokens))) {
|
|
136
|
+
return undefined;
|
|
137
|
+
}
|
|
138
|
+
const client = client_js_1.OryAgentClient.fromEnv(harness);
|
|
139
|
+
if (tokens && !(0, auth_store_js_1.isExpired)(tokens)) {
|
|
140
|
+
client.setUserPrincipal({
|
|
141
|
+
subject: tokens.subject,
|
|
142
|
+
token: tokens.accessToken,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
await (0, agent_auth_js_2.ensureAgentIdentity)(client, { projectUrl: resolved.projectUrl }).catch(() => {
|
|
146
|
+
/* best-effort; the probe still runs with whatever credentials we have */
|
|
147
|
+
});
|
|
148
|
+
const subject = (0, subject_js_1.resolveUserSubject)(client);
|
|
149
|
+
const subjectId = (0, subject_js_1.subjectLabel)(subject);
|
|
150
|
+
if (subjectId === "agent:unknown")
|
|
151
|
+
return undefined;
|
|
152
|
+
const namespace = resolveNamespace();
|
|
153
|
+
const result = {
|
|
154
|
+
total: catalog.length,
|
|
155
|
+
allowed: 0,
|
|
156
|
+
denied: 0,
|
|
157
|
+
errored: 0,
|
|
158
|
+
};
|
|
159
|
+
for (const tool of catalog) {
|
|
160
|
+
const check = {
|
|
161
|
+
namespace,
|
|
162
|
+
object: tool,
|
|
163
|
+
relation: "use",
|
|
164
|
+
...subject,
|
|
165
|
+
};
|
|
166
|
+
try {
|
|
167
|
+
const probe = await client.checkPermission(check, {
|
|
168
|
+
spanAttributes: { toolName: tool, source: "status_coverage" },
|
|
169
|
+
});
|
|
170
|
+
if (probe.allowed)
|
|
171
|
+
result.allowed++;
|
|
172
|
+
else
|
|
173
|
+
result.denied++;
|
|
174
|
+
}
|
|
175
|
+
catch (err) {
|
|
176
|
+
const ory = err;
|
|
177
|
+
result.errored++;
|
|
178
|
+
result.errorCode = result.errorCode ?? ory.code ?? "unknown";
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return result;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Render the "Permissions" block. Always prints the configured mode and
|
|
185
|
+
* namespace; runs a network probe of the harness's built-in tool catalog
|
|
186
|
+
* only when a project URL is configured *and* a user identity is
|
|
187
|
+
* available (cached PKCE tokens or `ORY_USER_SUBJECT_ID`).
|
|
188
|
+
*/
|
|
189
|
+
async function printPermissionsSection(binName, harness) {
|
|
190
|
+
const resolved = (0, config_js_1.resolveConfig)();
|
|
191
|
+
const namespace = resolveNamespace();
|
|
192
|
+
const catalog = (0, tool_catalog_js_1.getToolCatalog)(harness);
|
|
193
|
+
console.log("");
|
|
194
|
+
console.log("Permissions:");
|
|
195
|
+
console.log(` Mode: ${resolved.permissionMode}${formatModeSuffix(resolved.permissionModeSource)}`);
|
|
196
|
+
console.log(` Namespace: ${namespace}`);
|
|
197
|
+
if (resolved.auditOnly) {
|
|
198
|
+
console.log(" Coverage: n/a (audit-only mode — Ory checks disabled)");
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
if (!resolved.projectUrl) {
|
|
202
|
+
console.log(" Coverage: n/a (no project URL configured)");
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
if (catalog.length === 0) {
|
|
206
|
+
console.log(` Coverage: n/a (no built-in catalog for harness "${harness}")`);
|
|
207
|
+
console.log(` known harnesses: ${tool_catalog_js_1.KNOWN_HARNESSES.join(", ")}`);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
const hasUser = !!process.env.ORY_USER_SUBJECT_ID || isUserTokenUsable();
|
|
211
|
+
if (!hasUser) {
|
|
212
|
+
console.log(` Coverage: n/a (no cached user identity — run with ORY_AUTH_GATE=1 once,`);
|
|
213
|
+
console.log(` or set ORY_USER_SUBJECT_ID to probe a known subject)`);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
process.stdout.write(" Coverage: checking…\r");
|
|
217
|
+
const probe = await probePermissionsCoverage(harness);
|
|
218
|
+
// Clear the "checking…" line by overwriting it.
|
|
219
|
+
process.stdout.write(" ".repeat(40) + "\r");
|
|
220
|
+
if (!probe) {
|
|
221
|
+
console.log(" Coverage: unavailable (no user identity could be resolved)");
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
const { total, allowed, denied, errored } = probe;
|
|
225
|
+
if (errored > 0 && allowed === 0 && denied === 0) {
|
|
226
|
+
console.log(` Coverage: probe failed (${probe.errorCode ?? "unknown"})`);
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
const status = allowed === total
|
|
230
|
+
? `bootstrapped (${allowed}/${total} built-in tools)`
|
|
231
|
+
: allowed === 0
|
|
232
|
+
? `not bootstrapped (0/${total} built-in tools)`
|
|
233
|
+
: `partial (${allowed}/${total} built-in tools allowed)`;
|
|
234
|
+
console.log(` Coverage: ${status}`);
|
|
235
|
+
if (denied > 0) {
|
|
236
|
+
console.log(` run "npx ${binName} permissions bootstrap" to grant the missing tools`);
|
|
237
|
+
}
|
|
238
|
+
else if (errored > 0) {
|
|
239
|
+
console.log(` ${errored} probe error(s) — run "npx ${binName} permissions status" for details`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
function isUserTokenUsable() {
|
|
243
|
+
const tokens = (0, auth_store_js_1.loadTokens)();
|
|
244
|
+
return !!tokens && !(0, auth_store_js_1.isExpired)(tokens);
|
|
245
|
+
}
|
|
246
|
+
function formatModeSuffix(source) {
|
|
247
|
+
switch (source) {
|
|
248
|
+
case "env":
|
|
249
|
+
return " (from ORY_PERMISSION_MODE env)";
|
|
250
|
+
case "config":
|
|
251
|
+
return " (from config — change with `permissions observe|enforce`)";
|
|
252
|
+
case "default":
|
|
253
|
+
return " (default)";
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Compose the unified `status` output for a plugin CLI. Sections render
|
|
258
|
+
* in this order: title → configuration → user identity → agent identity
|
|
259
|
+
* → permissions → hooks & plugin → environment → recent log tail.
|
|
260
|
+
*
|
|
261
|
+
* Each section degrades gracefully — missing project URL, audit-only
|
|
262
|
+
* mode, and empty token caches all surface as inline "n/a" lines rather
|
|
263
|
+
* than aborting the command.
|
|
264
|
+
*/
|
|
265
|
+
async function runStatusCommand(binName, harness, options) {
|
|
266
|
+
const heading = `Ory Agent Plugin Status (${options.title})`;
|
|
267
|
+
console.log(heading);
|
|
268
|
+
console.log("=".repeat(heading.length));
|
|
269
|
+
console.log("");
|
|
270
|
+
(0, cli_js_1.printOryConfig)();
|
|
271
|
+
printUserIdentitySection();
|
|
272
|
+
printAgentIdentitySection();
|
|
273
|
+
await printPermissionsSection(binName, harness);
|
|
274
|
+
if (options.printPluginSection) {
|
|
275
|
+
console.log("");
|
|
276
|
+
options.printPluginSection();
|
|
277
|
+
}
|
|
278
|
+
(0, cli_js_1.printEnvironment)();
|
|
279
|
+
(0, cli_js_1.printLogTail)();
|
|
280
|
+
console.log("");
|
|
281
|
+
console.log(`Drill into any section with:`);
|
|
282
|
+
console.log(` npx ${binName} agent status`);
|
|
283
|
+
console.log(` npx ${binName} permissions status`);
|
|
284
|
+
console.log(` npx ${binName} configure`);
|
|
285
|
+
}
|
package/dist/tracer.d.ts
CHANGED
|
@@ -99,6 +99,14 @@ export interface ProcessMetadata {
|
|
|
99
99
|
gitBranch?: string;
|
|
100
100
|
gitCommit?: string;
|
|
101
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* Returns a record of attributes to merge into every span's attribute
|
|
104
|
+
* bag at span-end. Used by `OryAgentClient` to inject the current
|
|
105
|
+
* `userSubject` / `agentSubject` onto every span without each handler
|
|
106
|
+
* having to remember to attach them. Must not throw and should be cheap;
|
|
107
|
+
* it is invoked on every span.
|
|
108
|
+
*/
|
|
109
|
+
export type SpanAttributeEnricher = () => Record<string, unknown> | undefined;
|
|
102
110
|
export declare class Tracer extends EventEmitter {
|
|
103
111
|
readonly harness: string;
|
|
104
112
|
readonly processMetadata: ProcessMetadata;
|
|
@@ -106,6 +114,7 @@ export declare class Tracer extends EventEmitter {
|
|
|
106
114
|
private readonly _spans;
|
|
107
115
|
private _context;
|
|
108
116
|
private _exporter;
|
|
117
|
+
private _enricher;
|
|
109
118
|
constructor(opts: TracerOptions);
|
|
110
119
|
/** Backwards-compatible accessors for the most common metadata fields. */
|
|
111
120
|
get hostname(): string;
|
|
@@ -135,6 +144,15 @@ export declare class Tracer extends EventEmitter {
|
|
|
135
144
|
*/
|
|
136
145
|
setExporter(exporter: SpanExporter | null): void;
|
|
137
146
|
get exporter(): SpanExporter | null;
|
|
147
|
+
/**
|
|
148
|
+
* Install (or replace) an attribute enricher. The enricher is invoked
|
|
149
|
+
* on every span at end-time and its return value is merged into the
|
|
150
|
+
* span's attributes (with caller-supplied attributes taking precedence).
|
|
151
|
+
* Pass `null` to clear.
|
|
152
|
+
*/
|
|
153
|
+
setAttributeEnricher(enricher: SpanAttributeEnricher | null): void;
|
|
154
|
+
/** @internal — invoked by `ActiveSpan.end()`. Never throws. */
|
|
155
|
+
_callEnricher(): Record<string, unknown> | undefined;
|
|
138
156
|
/**
|
|
139
157
|
* Flush any pending exports. Call before the host process exits so
|
|
140
158
|
* the OTLP HTTP request has a chance to complete.
|
package/dist/tracer.js
CHANGED
|
@@ -98,8 +98,12 @@ class ActiveSpan {
|
|
|
98
98
|
}
|
|
99
99
|
this._ended = true;
|
|
100
100
|
const durationMs = Math.round(performance.now() - this.startMs);
|
|
101
|
-
|
|
102
|
-
|
|
101
|
+
// Pull enricher attributes (e.g. userSubject / agentSubject from the
|
|
102
|
+
// client's principals) at span-end so every span gets them, including
|
|
103
|
+
// direct `tracer.record(...)` calls from plugin handlers.
|
|
104
|
+
const enriched = this.tracer._callEnricher();
|
|
105
|
+
const merged = enriched || this.baseAttributes || attributes
|
|
106
|
+
? { ...enriched, ...this.baseAttributes, ...attributes }
|
|
103
107
|
: undefined;
|
|
104
108
|
const meta = this.tracer.processMetadata;
|
|
105
109
|
const span = {
|
|
@@ -137,6 +141,7 @@ class Tracer extends node_events_1.EventEmitter {
|
|
|
137
141
|
_spans = [];
|
|
138
142
|
_context = {};
|
|
139
143
|
_exporter;
|
|
144
|
+
_enricher = null;
|
|
140
145
|
constructor(opts) {
|
|
141
146
|
super();
|
|
142
147
|
this.harness = opts.harness;
|
|
@@ -212,6 +217,30 @@ class Tracer extends node_events_1.EventEmitter {
|
|
|
212
217
|
get exporter() {
|
|
213
218
|
return this._exporter;
|
|
214
219
|
}
|
|
220
|
+
/**
|
|
221
|
+
* Install (or replace) an attribute enricher. The enricher is invoked
|
|
222
|
+
* on every span at end-time and its return value is merged into the
|
|
223
|
+
* span's attributes (with caller-supplied attributes taking precedence).
|
|
224
|
+
* Pass `null` to clear.
|
|
225
|
+
*/
|
|
226
|
+
setAttributeEnricher(enricher) {
|
|
227
|
+
this._enricher = enricher;
|
|
228
|
+
}
|
|
229
|
+
/** @internal — invoked by `ActiveSpan.end()`. Never throws. */
|
|
230
|
+
_callEnricher() {
|
|
231
|
+
if (!this._enricher)
|
|
232
|
+
return undefined;
|
|
233
|
+
try {
|
|
234
|
+
const out = this._enricher();
|
|
235
|
+
if (!out || Object.keys(out).length === 0)
|
|
236
|
+
return undefined;
|
|
237
|
+
return out;
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
// Enricher contract: never throw. Swallow defensively.
|
|
241
|
+
return undefined;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
215
244
|
/**
|
|
216
245
|
* Flush any pending exports. Call before the host process exits so
|
|
217
246
|
* the OTLP HTTP request has a chance to complete.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ory/argus",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Ory Argus: the core API for building authentication, authorization, and audit into AI agent harness plugins, extensions, and custom integrations",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://ory.com",
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
"open": "^11.0.0"
|
|
70
70
|
},
|
|
71
71
|
"engines": {
|
|
72
|
-
"node": ">=
|
|
72
|
+
"node": ">=22"
|
|
73
73
|
},
|
|
74
74
|
"scripts": {
|
|
75
75
|
"build": "tsc",
|