@ory/argus 0.13.3 → 0.13.4

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.
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "repo": "ory-agent-plugins",
3
- "commit": "c04784bfd67acdce0133aca0ebcd335e326b9251",
4
- "commitShort": "c04784b",
3
+ "commit": "2d5b9f7c6730c0189020f68b6a431348561c0c2a",
4
+ "commitShort": "2d5b9f7",
5
5
  "branch": "main",
6
- "commitDate": "2026-07-14T15:46:39-07:00",
6
+ "commitDate": "2026-07-15T06:07:39-07:00",
7
7
  "dirty": false,
8
- "builtAt": "2026-07-14T22:51:01.241Z"
8
+ "builtAt": "2026-07-15T13:11:41.044Z"
9
9
  }
package/dist/client.d.ts CHANGED
@@ -58,6 +58,23 @@ export declare class OryAgentClient {
58
58
  * Ory API calls. Populated by `ensureAgentIdentity`.
59
59
  */
60
60
  private _agentPrincipal;
61
+ /**
62
+ * Admin credential — an Ory Network **project API key** (`ory_pat_…`) —
63
+ * used to authenticate the Keto Permission and Relationship APIs.
64
+ *
65
+ * This is deliberately distinct from the agent principal's token. Ory
66
+ * Network's permission APIs authenticate with a project API key and
67
+ * **reject project-issued OAuth2 access tokens** (a DCR client-credentials
68
+ * token is answered with `401 "Access token is not active"`). So when the
69
+ * agent identity resolves to a DCR OAuth2 client — the default path — its
70
+ * token cannot authenticate permission checks. Holding the admin key here
71
+ * lets {@link buildApis} route Keto through it while the agent OAuth2 token
72
+ * still authenticates the OAuth2 / Frontend APIs and carries attribution.
73
+ *
74
+ * When unset, Keto calls fall back to the agent token (legacy
75
+ * single-credential behavior, unchanged for static-key deployments).
76
+ */
77
+ private _adminApiKey?;
61
78
  constructor(config: OryAgentConfig);
62
79
  /** Build a fresh set of Ory API instances using the current agent token. */
63
80
  private buildApis;
@@ -78,13 +95,25 @@ export declare class OryAgentClient {
78
95
  * Pass `{}` (or fields set to undefined) to clear.
79
96
  */
80
97
  setUserPrincipal(principal: PrincipalIdentity): void;
81
- /**
82
- * Set or update the AI agent principal. The agent's token (when
83
- * present) is used in the Authorization header for all outgoing Ory
84
- * API calls so the audit log shows "agent X acting on behalf of
85
- * user Y". Rebuilds the underlying Ory API instances when the token
86
- * actually changes so subsequent calls pick it up; otherwise leaves
87
- * the API instances alone (so test stubs survive a no-op update).
98
+ /** Whether an admin API key is set for the Keto APIs (never exposes it). */
99
+ get hasAdminApiKey(): boolean;
100
+ /**
101
+ * Set or update the admin API key (an Ory Network project API key) used to
102
+ * authenticate the Keto Permission / Relationship APIs. Rebuilds those API
103
+ * instances when the key changes; a no-op update leaves them alone so test
104
+ * stubs survive. See {@link _adminApiKey} for why Keto needs a credential
105
+ * distinct from the agent's OAuth2 token.
106
+ */
107
+ setAdminApiKey(apiKey: string | undefined): void;
108
+ /**
109
+ * Set or update the AI agent principal. The agent's token (when present)
110
+ * authenticates outgoing calls to the OAuth2 / Frontend APIs and carries
111
+ * audit attribution ("agent X acting on behalf of user Y"). Note the Keto
112
+ * Permission / Relationship APIs authenticate with {@link _adminApiKey}
113
+ * instead when one is set — Ory Network rejects OAuth2 tokens there.
114
+ * Rebuilds the underlying Ory API instances when the token actually changes
115
+ * so subsequent calls pick it up; otherwise leaves the API instances alone
116
+ * (so test stubs survive a no-op update).
88
117
  */
89
118
  setAgentPrincipal(principal: PrincipalIdentity): void;
90
119
  /**
@@ -146,6 +175,15 @@ export declare class OryAgentClient {
146
175
  * across the user/agent split.
147
176
  */
148
177
  private principalSpanAttributes;
178
+ /**
179
+ * When a Keto call is auth-rejected while it was authenticated by something
180
+ * other than an Ory Network project API key (`ory_pat_…`) — e.g. a DCR
181
+ * OAuth2 access token — the credential *type* is wrong, not merely expired.
182
+ * Ory Network's Permission / Relationship APIs only accept a project API
183
+ * key. Emit an actionable hint so the failure isn't misread as a stale
184
+ * session (the generic `session_inactive` classification of the raw 401).
185
+ */
186
+ private warnIfKetoCredentialMismatch;
149
187
  /**
150
188
  * Classify an error from any Ory API call into a structured OryError.
151
189
  */
package/dist/client.js CHANGED
@@ -63,6 +63,23 @@ class OryAgentClient {
63
63
  * Ory API calls. Populated by `ensureAgentIdentity`.
64
64
  */
65
65
  _agentPrincipal = {};
66
+ /**
67
+ * Admin credential — an Ory Network **project API key** (`ory_pat_…`) —
68
+ * used to authenticate the Keto Permission and Relationship APIs.
69
+ *
70
+ * This is deliberately distinct from the agent principal's token. Ory
71
+ * Network's permission APIs authenticate with a project API key and
72
+ * **reject project-issued OAuth2 access tokens** (a DCR client-credentials
73
+ * token is answered with `401 "Access token is not active"`). So when the
74
+ * agent identity resolves to a DCR OAuth2 client — the default path — its
75
+ * token cannot authenticate permission checks. Holding the admin key here
76
+ * lets {@link buildApis} route Keto through it while the agent OAuth2 token
77
+ * still authenticates the OAuth2 / Frontend APIs and carries attribution.
78
+ *
79
+ * When unset, Keto calls fall back to the agent token (legacy
80
+ * single-credential behavior, unchanged for static-key deployments).
81
+ */
82
+ _adminApiKey;
66
83
  constructor(config) {
67
84
  this.config = config;
68
85
  this.sessionCacheTtlMs = config.sessionCacheTtlMs ?? 60_000;
@@ -83,6 +100,13 @@ class OryAgentClient {
83
100
  // call sites keep working until they migrate to ensureAgentIdentity.
84
101
  if (config.apiKey)
85
102
  this._agentPrincipal.token = config.apiKey;
103
+ // The `apiKey` is also the admin credential for the Keto Permission /
104
+ // Relationship APIs. Seed it separately so that when `ensureAgentIdentity`
105
+ // later replaces the agent token with a DCR OAuth2 token, permission
106
+ // checks keep authenticating with the project API key rather than the
107
+ // OAuth2 token Ory Network rejects. See {@link _adminApiKey}.
108
+ if (config.apiKey)
109
+ this._adminApiKey = config.apiKey;
86
110
  const { frontend, oauth2, permission, relationship } = this.buildApis();
87
111
  this.frontend = frontend;
88
112
  this.oauth2 = oauth2;
@@ -96,11 +120,23 @@ class OryAgentClient {
96
120
  basePath: this.config.projectUrl,
97
121
  ...(accessToken ? { accessToken } : {}),
98
122
  });
123
+ // Keto (Permission + Relationship) authenticates with the admin project
124
+ // API key when one is set; otherwise it reuses the agent token (legacy
125
+ // single-credential behavior). When the two credentials are identical we
126
+ // reuse the same Configuration object so a single-credential deployment —
127
+ // and tests that stub one Configuration — see exactly one.
128
+ const ketoToken = this._adminApiKey ?? accessToken;
129
+ const ketoConfig = ketoToken === accessToken
130
+ ? apiConfig
131
+ : new client_1.Configuration({
132
+ basePath: this.config.projectUrl,
133
+ ...(ketoToken ? { accessToken: ketoToken } : {}),
134
+ });
99
135
  return {
100
136
  frontend: new client_1.FrontendApi(apiConfig),
101
137
  oauth2: new client_1.OAuth2Api(apiConfig),
102
- permission: new client_1.PermissionApi(apiConfig),
103
- relationship: new client_1.RelationshipApi(apiConfig),
138
+ permission: new client_1.PermissionApi(ketoConfig),
139
+ relationship: new client_1.RelationshipApi(ketoConfig),
104
140
  };
105
141
  }
106
142
  /** Snapshot of the current user principal. */
@@ -128,13 +164,36 @@ class OryAgentClient {
128
164
  setUserPrincipal(principal) {
129
165
  this._userPrincipal = { ...principal };
130
166
  }
167
+ /** Whether an admin API key is set for the Keto APIs (never exposes it). */
168
+ get hasAdminApiKey() {
169
+ return !!this._adminApiKey;
170
+ }
171
+ /**
172
+ * Set or update the admin API key (an Ory Network project API key) used to
173
+ * authenticate the Keto Permission / Relationship APIs. Rebuilds those API
174
+ * instances when the key changes; a no-op update leaves them alone so test
175
+ * stubs survive. See {@link _adminApiKey} for why Keto needs a credential
176
+ * distinct from the agent's OAuth2 token.
177
+ */
178
+ setAdminApiKey(apiKey) {
179
+ if (this._adminApiKey === apiKey)
180
+ return;
181
+ this._adminApiKey = apiKey;
182
+ const { frontend, oauth2, permission, relationship } = this.buildApis();
183
+ this.frontend = frontend;
184
+ this.oauth2 = oauth2;
185
+ this.permission = permission;
186
+ this.relationship = relationship;
187
+ }
131
188
  /**
132
- * Set or update the AI agent principal. The agent's token (when
133
- * present) is used in the Authorization header for all outgoing Ory
134
- * API calls — so the audit log shows "agent X acting on behalf of
135
- * user Y". Rebuilds the underlying Ory API instances when the token
136
- * actually changes so subsequent calls pick it up; otherwise leaves
137
- * the API instances alone (so test stubs survive a no-op update).
189
+ * Set or update the AI agent principal. The agent's token (when present)
190
+ * authenticates outgoing calls to the OAuth2 / Frontend APIs and carries
191
+ * audit attribution ("agent X acting on behalf of user Y"). Note the Keto
192
+ * Permission / Relationship APIs authenticate with {@link _adminApiKey}
193
+ * instead when one is set Ory Network rejects OAuth2 tokens there.
194
+ * Rebuilds the underlying Ory API instances when the token actually changes
195
+ * so subsequent calls pick it up; otherwise leaves the API instances alone
196
+ * (so test stubs survive a no-op update).
138
197
  */
139
198
  setAgentPrincipal(principal) {
140
199
  const tokenChanged = this._agentPrincipal.token !== principal.token;
@@ -320,6 +379,7 @@ class OryAgentClient {
320
379
  }
321
380
  catch (err) {
322
381
  const oryErr = this.classifyError(err);
382
+ this.warnIfKetoCredentialMismatch(oryErr);
323
383
  this.logger.error("permission.check.failed", {
324
384
  code: oryErr.code,
325
385
  status: oryErr.status,
@@ -394,6 +454,7 @@ class OryAgentClient {
394
454
  }
395
455
  catch (err) {
396
456
  const oryErr = this.classifyError(err);
457
+ this.warnIfKetoCredentialMismatch(oryErr);
397
458
  this.logger.error("permission.batch_check.failed", {
398
459
  code: oryErr.code,
399
460
  status: oryErr.status,
@@ -475,6 +536,7 @@ class OryAgentClient {
475
536
  });
476
537
  return { created: false, alreadyExisted: true };
477
538
  }
539
+ this.warnIfKetoCredentialMismatch(oryErr);
478
540
  this.logger.error("relationship.create.failed", {
479
541
  code: oryErr.code,
480
542
  status: oryErr.status,
@@ -534,6 +596,7 @@ class OryAgentClient {
534
596
  });
535
597
  return { deleted: false, notFound: true };
536
598
  }
599
+ this.warnIfKetoCredentialMismatch(oryErr);
537
600
  this.logger.error("relationship.delete.failed", {
538
601
  code: oryErr.code,
539
602
  status: oryErr.status,
@@ -560,6 +623,29 @@ class OryAgentClient {
560
623
  out.agentSubject = this._agentPrincipal.subject;
561
624
  return out;
562
625
  }
626
+ /**
627
+ * When a Keto call is auth-rejected while it was authenticated by something
628
+ * other than an Ory Network project API key (`ory_pat_…`) — e.g. a DCR
629
+ * OAuth2 access token — the credential *type* is wrong, not merely expired.
630
+ * Ory Network's Permission / Relationship APIs only accept a project API
631
+ * key. Emit an actionable hint so the failure isn't misread as a stale
632
+ * session (the generic `session_inactive` classification of the raw 401).
633
+ */
634
+ warnIfKetoCredentialMismatch(oryErr) {
635
+ if (oryErr.code !== "session_inactive" && oryErr.code !== "forbidden")
636
+ return;
637
+ const ketoToken = this._adminApiKey ?? this._agentPrincipal.token;
638
+ // A real project API key is already in use — treat as a genuine auth error.
639
+ if (typeof ketoToken === "string" && ketoToken.startsWith("ory_pat_"))
640
+ return;
641
+ this.logger.warn("permission.credential_mismatch", {
642
+ code: oryErr.code,
643
+ message: "Ory Network's Permission/Relationship APIs require a project API key " +
644
+ "(ory_pat_…); the agent's OAuth2 access token is not accepted. Create a " +
645
+ "key in the Ory Console (Project settings → API keys) and set it with " +
646
+ "`configure --api-key <key>` or the ORY_AGENT_API_KEY env var.",
647
+ });
648
+ }
563
649
  // ─── Error Classification ────────────────────────────────────────
564
650
  /**
565
651
  * Classify an error from any Ory API call into a structured OryError.
@@ -23,7 +23,10 @@
23
23
  * rather than re-implementing the Console API: the CLI already owns Network
24
24
  * browser login + account creation, workspace/project listing, and OAuth2
25
25
  * client creation, and the client it creates is exactly the one the plugin
26
- * READMEs document for manual setup.
26
+ * READMEs document for manual setup. The one exception is minting the project
27
+ * API key that runtime permission checks need — the CLI exposes no command for
28
+ * it, so that single step calls the Console API directly, reusing the CLI's
29
+ * stored session for auth (see {@link createProjectApiKeyViaConsole}).
27
30
  */
28
31
  import { OryAgentClient } from "./client.js";
29
32
  import { ensureUserAuthenticated } from "./user-login.js";
@@ -83,6 +86,20 @@ export interface InteractiveSetupDeps {
83
86
  * failed. Defaults to a real `npm install` of {@link ORY_CLI_NPM_SPEC}.
84
87
  */
85
88
  installOryCliFn?: () => Promise<OryCliRunner | null>;
89
+ /**
90
+ * Injectable project-API-key minter. Returns the `ory_pat_…` value or null on
91
+ * any failure. Defaults to {@link createProjectApiKeyViaConsole} (Ory Console
92
+ * API, authenticated with the `ory` CLI's stored session). Tests stub this to
93
+ * avoid real HTTP.
94
+ */
95
+ createProjectApiKeyFn?: (args: CreateProjectApiKeyArgs) => Promise<string | null>;
96
+ /**
97
+ * Injectable validity probe for an already-stored project API key. Returns
98
+ * true when the key still authenticates against Keto, false when it's been
99
+ * revoked/deleted (so provisioning re-mints one). Defaults to
100
+ * {@link apiKeyAuthenticates}. Tests stub this to avoid real HTTP.
101
+ */
102
+ validateApiKeyFn?: (apiKey: string, projectUrl: string) => Promise<boolean>;
86
103
  }
87
104
  export type InteractiveSetupOutcome = "skipped_flag" | "skipped_no_tty" | "skipped_configured" | "audit_only" | "local_configured" | "network_configured" | "network_fallback";
88
105
  export interface InteractiveSetupResult {
@@ -121,3 +138,28 @@ export declare function runInteractiveSetup(binName: string, harness: string, ar
121
138
  * silently misconfigure the plugin.
122
139
  */
123
140
  export declare function projectUrlFromSlug(slug: string): string;
141
+ export interface CreateProjectApiKeyArgs {
142
+ projectId: string;
143
+ /** Human-readable label shown in the Console's API-keys list. */
144
+ name: string;
145
+ }
146
+ /**
147
+ * Mint an Ory Network **project API key** via the Console API
148
+ * (`POST /projects/{id}/tokens`), authenticated with the `ory` CLI session.
149
+ * Returns the `ory_pat_…` value, or null on any failure (no session, non-2xx,
150
+ * missing value). Never throws — provisioning is best-effort.
151
+ *
152
+ * This is the one place the wizard reaches past the `ory` CLI to the Console
153
+ * API: the CLI (as of v1.3) exposes no project-API-key command, and Keto
154
+ * permission checks can't authenticate without one.
155
+ */
156
+ export declare function createProjectApiKeyViaConsole(args: CreateProjectApiKeyArgs): Promise<string | null>;
157
+ /**
158
+ * Best-effort check that an already-stored project API key still authenticates
159
+ * against Keto. Runs one permission check with the key: if it completes (any
160
+ * `allowed` value) the key is valid; an auth rejection (`session_inactive` /
161
+ * `forbidden` / `session_aal2_required`) means it was revoked or deleted, so we
162
+ * return false and the caller re-provisions. Transient failures (network /
163
+ * rate-limit / unknown) return true so we don't re-mint on a blip. Never throws.
164
+ */
165
+ export declare function apiKeyAuthenticates(apiKey: string, projectUrl: string): Promise<boolean>;
@@ -24,7 +24,10 @@
24
24
  * rather than re-implementing the Console API: the CLI already owns Network
25
25
  * browser login + account creation, workspace/project listing, and OAuth2
26
26
  * client creation, and the client it creates is exactly the one the plugin
27
- * READMEs document for manual setup.
27
+ * READMEs document for manual setup. The one exception is minting the project
28
+ * API key that runtime permission checks need — the CLI exposes no command for
29
+ * it, so that single step calls the Console API directly, reusing the CLI's
30
+ * stored session for auth (see {@link createProjectApiKeyViaConsole}).
28
31
  */
29
32
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
30
33
  if (k2 === undefined) k2 = k;
@@ -65,6 +68,8 @@ exports.createOryRunner = createOryRunner;
65
68
  exports.runPostInstall = runPostInstall;
66
69
  exports.runInteractiveSetup = runInteractiveSetup;
67
70
  exports.projectUrlFromSlug = projectUrlFromSlug;
71
+ exports.createProjectApiKeyViaConsole = createProjectApiKeyViaConsole;
72
+ exports.apiKeyAuthenticates = apiKeyAuthenticates;
68
73
  const node_child_process_1 = require("node:child_process");
69
74
  const fs = __importStar(require("node:fs"));
70
75
  const os = __importStar(require("node:os"));
@@ -601,6 +606,12 @@ async function configureNetwork(binName, harness, ctx) {
601
606
  if (subject) {
602
607
  await bootstrapPermissionsViaOry(runner, project.id, harness, subject, prompt);
603
608
  }
609
+ // 10. Provision the project API key runtime permission *checks* need. Same
610
+ // root cause as bootstrap's admin path — the DCR OAuth2 token can't
611
+ // authenticate Ory Network's Permission API — but this credential is used
612
+ // at runtime, so it's persisted to config. Placed last so its prompt
613
+ // doesn't consume answers meant for earlier steps.
614
+ await maybeProvisionProjectApiKey(binName, harness, project.id, projectUrl, prompt, ctx.deps.createProjectApiKeyFn ?? createProjectApiKeyViaConsole, ctx.deps.validateApiKeyFn ?? apiKeyAuthenticates);
604
615
  return {
605
616
  outcome: "network_configured",
606
617
  projectUrl,
@@ -1317,3 +1328,171 @@ function printManualOAuth2ClientHelp(binName, projectId) {
1317
1328
  ui.blank();
1318
1329
  ui.command(`${(0, cli_invocation_js_1.oryNpx)(binName)} configure --oauth2-client-id <CLIENT_ID>`);
1319
1330
  }
1331
+ /** Ory Console API base. The `ory` CLI has no project-API-key command, so key
1332
+ * provisioning talks to the Console API directly, authenticated with the CLI's
1333
+ * stored Ory Network session. Override with `ORY_CONSOLE_API_URL`. */
1334
+ const CONSOLE_API_URL = process.env.ORY_CONSOLE_API_URL?.trim() || "https://api.console.ory.sh";
1335
+ /** Path to the session the `ory` CLI persists after `ory auth`. */
1336
+ const ORY_CLI_SESSION_FILE = ".ory-cloud.json";
1337
+ /**
1338
+ * Read the Ory Network session bearer the `ory` CLI stores at
1339
+ * `~/.ory-cloud.json`. Returns null when the file is absent, unparseable, has
1340
+ * no token, or the token has expired. Never throws.
1341
+ */
1342
+ function readConsoleSessionToken() {
1343
+ try {
1344
+ const raw = fs.readFileSync(path.join(os.homedir(), ORY_CLI_SESSION_FILE), "utf-8");
1345
+ const parsed = JSON.parse(raw);
1346
+ const token = parsed.access_token?.access_token;
1347
+ if (!token)
1348
+ return null;
1349
+ const expiry = parsed.access_token?.expiry;
1350
+ if (expiry) {
1351
+ const expMs = Date.parse(expiry);
1352
+ if (Number.isFinite(expMs) && expMs <= Date.now())
1353
+ return null;
1354
+ }
1355
+ return token;
1356
+ }
1357
+ catch {
1358
+ return null;
1359
+ }
1360
+ }
1361
+ /**
1362
+ * Mint an Ory Network **project API key** via the Console API
1363
+ * (`POST /projects/{id}/tokens`), authenticated with the `ory` CLI session.
1364
+ * Returns the `ory_pat_…` value, or null on any failure (no session, non-2xx,
1365
+ * missing value). Never throws — provisioning is best-effort.
1366
+ *
1367
+ * This is the one place the wizard reaches past the `ory` CLI to the Console
1368
+ * API: the CLI (as of v1.3) exposes no project-API-key command, and Keto
1369
+ * permission checks can't authenticate without one.
1370
+ */
1371
+ async function createProjectApiKeyViaConsole(args) {
1372
+ const token = readConsoleSessionToken();
1373
+ if (!token)
1374
+ return null;
1375
+ try {
1376
+ const res = await fetch(`${CONSOLE_API_URL}/projects/${args.projectId}/tokens`, {
1377
+ method: "POST",
1378
+ headers: {
1379
+ Authorization: `Bearer ${token}`,
1380
+ "Content-Type": "application/json",
1381
+ },
1382
+ body: JSON.stringify({ name: args.name }),
1383
+ });
1384
+ if (!res.ok)
1385
+ return null;
1386
+ const body = (await res.json());
1387
+ return typeof body.value === "string" && body.value.length > 0 ? body.value : null;
1388
+ }
1389
+ catch {
1390
+ return null;
1391
+ }
1392
+ }
1393
+ /**
1394
+ * Best-effort check that an already-stored project API key still authenticates
1395
+ * against Keto. Runs one permission check with the key: if it completes (any
1396
+ * `allowed` value) the key is valid; an auth rejection (`session_inactive` /
1397
+ * `forbidden` / `session_aal2_required`) means it was revoked or deleted, so we
1398
+ * return false and the caller re-provisions. Transient failures (network /
1399
+ * rate-limit / unknown) return true so we don't re-mint on a blip. Never throws.
1400
+ */
1401
+ async function apiKeyAuthenticates(apiKey, projectUrl) {
1402
+ try {
1403
+ // Direct construction (no fromEnv) seeds the key as both agent token and
1404
+ // admin key and skips DCR, so the probe uses exactly this key. No trace
1405
+ // file is configured, so nothing is written to disk.
1406
+ const probe = new client_js_1.OryAgentClient({ projectUrl, apiKey, harness: "install-probe" });
1407
+ await probe.checkPermission({
1408
+ namespace: process.env.ORY_PERMISSION_NAMESPACE ?? "AgentTools",
1409
+ object: "__ory_agent_key_probe__",
1410
+ relation: "__ory_agent_key_probe__",
1411
+ subjectId: "__ory_agent_key_probe__",
1412
+ });
1413
+ return true;
1414
+ }
1415
+ catch (err) {
1416
+ const code = err?.code;
1417
+ if (code === "session_inactive" || code === "forbidden" || code === "session_aal2_required") {
1418
+ return false;
1419
+ }
1420
+ return true;
1421
+ }
1422
+ }
1423
+ /**
1424
+ * Manual fallback steps for the project API key, shown when auto-provisioning
1425
+ * is declined or fails. The `configure --api-key` command is the stable part;
1426
+ * the Console link is a convenience.
1427
+ */
1428
+ function printManualApiKeySteps(binName, projectId) {
1429
+ ui.info("Create one in the Ory Console (Project settings → API keys):");
1430
+ ui.command(`https://console.ory.sh/projects/${projectId}/settings`);
1431
+ ui.info("then store it so permission checks authenticate with it:");
1432
+ ui.command(`${(0, cli_invocation_js_1.oryNpx)(binName)} configure --api-key <ory_pat_…>`);
1433
+ ui.hint("Observe mode works without it; enforce mode needs it to allow tools.");
1434
+ }
1435
+ /**
1436
+ * Provision the project API key that runtime Keto permission checks
1437
+ * authenticate with. The agent's DCR OAuth2 token can't — Ory Network rejects
1438
+ * it — so `enforce` mode needs a project API key. Best-effort and skippable:
1439
+ * offers to mint one via the Console API and persist it, and on decline / no
1440
+ * TTY / any failure falls back to manual guidance. No-ops when a key is already
1441
+ * configured (env or config file).
1442
+ */
1443
+ async function maybeProvisionProjectApiKey(binName, harness, projectId, projectUrl, prompt, createFn, validateFn) {
1444
+ const resolved = (0, config_js_1.resolveConfig)();
1445
+ if (resolved.apiKey) {
1446
+ // An operator-set env key is authoritative — never touch it.
1447
+ if (resolved.apiKeySource === "env")
1448
+ return;
1449
+ // A stored (config-file) key that no longer authenticates — revoked or
1450
+ // deleted — must not wedge setup: validate it and re-provision if it's
1451
+ // dead. Presence alone isn't enough (a dangling key still fails at runtime
1452
+ // with session_inactive).
1453
+ if (await validateFn(resolved.apiKey, projectUrl))
1454
+ return;
1455
+ ui.heading("Project API key");
1456
+ ui.warning("The stored project API key is no longer valid (revoked or deleted).");
1457
+ }
1458
+ else {
1459
+ ui.heading("Project API key");
1460
+ ui.info("Runtime permission checks call Ory's Permission API, which authenticates");
1461
+ ui.hint("with a project API key — the agent's OAuth2 token isn't accepted there.");
1462
+ ui.hint("(Observe mode works without it; enforce mode needs it to allow tools.)");
1463
+ }
1464
+ ui.blank();
1465
+ const answer = await prompt(ui.promptLine("Create a project API key now and store it?", { hint: "[Y/n]" }));
1466
+ // `null` = no TTY / cancelled; treat as skip so we never mint without consent.
1467
+ if (answer !== null && !isNo(answer)) {
1468
+ ui.step("Creating a project API key via the Ory Console…");
1469
+ let key = null;
1470
+ try {
1471
+ // Name each key uniquely (harness + host + timestamp) so keys are
1472
+ // distinguishable in the Console and a re-provision never collides with
1473
+ // an existing entry.
1474
+ key = await createFn({ projectId, name: projectApiKeyName(harness) });
1475
+ }
1476
+ catch {
1477
+ key = null;
1478
+ }
1479
+ if (key) {
1480
+ (0, config_js_1.saveConfig)({ apiKey: key });
1481
+ ui.success("Project API key created and stored — permission checks will use it.");
1482
+ return;
1483
+ }
1484
+ ui.warning("Couldn't create a project API key automatically (Console session may be missing or expired).");
1485
+ }
1486
+ printManualApiKeySteps(binName, projectId);
1487
+ }
1488
+ /** Distinguishable, collision-free label for a provisioned project API key. */
1489
+ function projectApiKeyName(harness) {
1490
+ let host = "unknown-host";
1491
+ try {
1492
+ host = os.hostname();
1493
+ }
1494
+ catch {
1495
+ /* keep fallback */
1496
+ }
1497
+ return `ory-agent-plugins ${harness} ${host} ${new Date().toISOString()}`;
1498
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ory/argus",
3
- "version": "0.13.3",
3
+ "version": "0.13.4",
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",