@ory/argus 0.13.6 → 0.13.8

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.
@@ -48,6 +48,14 @@ export interface AgentCredentials {
48
48
  * via deprecated alias). Never blocks resolution.
49
49
  */
50
50
  warnings: string[];
51
+ /**
52
+ * Set on a `kind: "none"` result when the token endpoint rejected the
53
+ * credentials for an auth reason (401/403) — i.e. the client is genuinely
54
+ * dead (revoked/invalid). A transient failure (network, rate-limit, 5xx)
55
+ * leaves this false so callers don't discard otherwise-good persisted
56
+ * credentials. See the persisted-DCR branch in {@link resolveAgentCredentials}.
57
+ */
58
+ authRejected?: boolean;
51
59
  }
52
60
  export interface ResolveAgentCredentialsOptions {
53
61
  /** Ory project URL — required for client_credentials grant + DCR. */
@@ -275,10 +275,20 @@ async function resolveAgentCredentials(options = {}) {
275
275
  });
276
276
  if (result.kind !== "none")
277
277
  return result;
278
- // Server rejected the persisted credsclear and fall through to
279
- // a fresh registration attempt.
280
- warnings.push(`Persisted dynamic agent credentials rejected (${result.reason}); re-registering.`);
281
- clearDynamicFn();
278
+ // Only a genuine auth rejection (401/403 client revoked/invalid) means
279
+ // the persisted creds are dead. Clear them and fall through to a fresh
280
+ // registration. A transient failure (network, rate-limit, 5xx) must NOT
281
+ // discard good creds: doing so would orphan the server-side client
282
+ // (clearDynamicFn never revokes it) and register a brand-new one on every
283
+ // blip. Keep the persisted creds and report the transient failure instead.
284
+ if (result.authRejected) {
285
+ warnings.push(`Persisted dynamic agent credentials rejected (${result.reason}); re-registering.`);
286
+ clearDynamicFn();
287
+ }
288
+ else {
289
+ warnings.push(`Persisted dynamic agent credentials temporarily unavailable (${result.reason}); keeping them for the next session.`);
290
+ return { ...result, warnings };
291
+ }
282
292
  }
283
293
  }
284
294
  // 4. Fresh dynamic registration — bootstrap using the user's bearer
@@ -377,11 +387,27 @@ async function resolveClientCredentials(args) {
377
387
  catch (err) {
378
388
  return {
379
389
  kind: "none",
390
+ authRejected: isAuthRejectionError(err),
380
391
  reason: `${reasonNoun} client_credentials grant failed: ${err instanceof Error ? err.message : String(err)}`,
381
392
  warnings: args.warnings,
382
393
  };
383
394
  }
384
395
  }
396
+ /**
397
+ * Whether a token-grant error is an auth rejection (401/403) — meaning the
398
+ * credentials themselves are dead — as opposed to a transient failure
399
+ * (network, rate-limit, 5xx). Reads a `status` property when the thrower
400
+ * attaches one ({@link fetchClientCredentialsToken} does), and falls back to
401
+ * matching `HTTP 401`/`HTTP 403` in the message so injected/stubbed grant
402
+ * functions that throw a plain `Error` are classified correctly too.
403
+ */
404
+ function isAuthRejectionError(err) {
405
+ const status = err?.status;
406
+ if (status === 401 || status === 403)
407
+ return true;
408
+ const msg = err instanceof Error ? err.message : String(err);
409
+ return /\bHTTP 40[13]\b/.test(msg);
410
+ }
385
411
  // Kept as raw fetch (rather than @ory/client's OAuth2Api.oauth2TokenExchange)
386
412
  // because the SDK method's signature does not expose client_secret, audience,
387
413
  // or scope — all of which this grant needs. Ory's own SDK docstring on that
@@ -404,7 +430,9 @@ async function fetchClientCredentialsToken(args) {
404
430
  body: body.toString(),
405
431
  });
406
432
  if (!res.ok) {
407
- throw new Error(`HTTP ${res.status}`);
433
+ const err = new Error(`HTTP ${res.status}`);
434
+ err.status = res.status;
435
+ throw err;
408
436
  }
409
437
  const json = (await res.json());
410
438
  const accessToken = typeof json.access_token === "string" ? json.access_token : "";
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "repo": "ory-agent-plugins",
3
- "commit": "255043db833b8ebda7cbec0dee740eda045f064e",
4
- "commitShort": "255043d",
3
+ "commit": "ff55031f181ceec7e06f8d7f3a6b2555c130a091",
4
+ "commitShort": "ff55031",
5
5
  "branch": "main",
6
- "commitDate": "2026-07-15T09:35:21-07:00",
6
+ "commitDate": "2026-07-15T13:59:17-07:00",
7
7
  "dirty": false,
8
- "builtAt": "2026-07-15T16:39:31.503Z"
8
+ "builtAt": "2026-07-15T21:03:15.277Z"
9
9
  }
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { OryAgentClient, type OryAgentConfig, type PrincipalIdentity, } from "./client.js";
2
- export { DebugLogger, redactLogData, type LogEntry, type LogLevel, } from "./logger.js";
2
+ export { DebugLogger, redactLogData, withQuietStderr, type LogEntry, type LogLevel, } from "./logger.js";
3
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";
package/dist/index.js CHANGED
@@ -1,14 +1,15 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.runConfigureCommand = exports.clearCredentialsForUninstall = exports.AGENT_TOKEN_EXPIRY_SKEW_SEC = exports.revokeAgentDynamicClient = 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.runDevLauncher = exports.unregisterPlugin = exports.registerPlugin = exports.removeMcpServer = exports.mergeMcpServer = exports.mcpServerEntry = exports.resolveMcpServerCommand = exports.emitDeferredNextSteps = exports.beginDeferNextSteps = exports.nextStepsSink = 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.resolveBuildInfo = exports.collectVersionInfo = exports.runVersionCommand = exports.printPermissionsSection = exports.printAgentIdentitySection = exports.printUserIdentitySection = exports.runStatusCommand = exports.LOOPBACK_REDIRECT_URIS = exports.projectUrlFromSlug = exports.runPostInstall = exports.runInteractiveSetup = exports.printPermissionsOnboardingHelp = exports.maybeAutoBootstrap = exports.isUserIdentityCached = exports.runPermissionsCommand = exports.oryNpx = exports.interactiveConfigPrompt = exports.promptForProjectUrl = exports.promptOnTty = exports.isTtyAvailable = exports.runWatchCommand = exports.printTraceTail = exports.printEnvHelp = exports.printLogTail = exports.printEnvironment = exports.printOryConfig = exports.runAgentCommand = void 0;
5
- exports.otlpExporterFromEnv = exports.OtlpExporter = exports.summarizeToolOutput = exports.summarizeToolInput = exports.OryDenialError = exports.alertAttributes = exports.formatAlertSummary = exports.formatAlertMessage = exports.formatDenialSummary = exports.formatDenialMessage = exports.subjectLabel = exports.runWithUserSubject = exports.resolveUserSubject = exports.checkMcpPermission = exports.parseMcpToolGeneric = exports.parseGeminiMcpTool = exports.parseClaudeCodeMcpTool = exports.TOOL_EXECUTION_PHASES = exports.USER_FACING_PHASES = exports.HARNESS_LIFECYCLE_MAP = exports.isToolExecutionPhase = exports.isUserFacingPhase = exports.classifyLifecycle = exports.isInteractiveTool = exports.getInteractiveToolCatalog = exports.INTERACTIVE_TOOL_CATALOG = exports.getToolCatalog = exports.ALL_TOOLS = exports.KNOWN_HARNESSES = exports.HARNESS_TOOL_CATALOG = exports.gateToolCall = exports.applyPermissionMode = exports.checkAndDecide = exports.runRegistryCommand = exports.DEV_JAEGER_CONTAINER = exports.stopDevJaeger = exports.ensureDevJaeger = 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 = void 0;
6
- exports.wrapTool = exports.registerSubagent = exports.complete = exports.gate = exports.sessionStart = exports.resolveNamespace = exports.parseKeyValueList = void 0;
3
+ exports.clearCredentialsForUninstall = exports.AGENT_TOKEN_EXPIRY_SKEW_SEC = exports.revokeAgentDynamicClient = 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.withQuietStderr = exports.redactLogData = exports.DebugLogger = exports.OryAgentClient = void 0;
4
+ exports.unregisterPlugin = exports.registerPlugin = exports.removeMcpServer = exports.mergeMcpServer = exports.mcpServerEntry = exports.resolveMcpServerCommand = exports.emitDeferredNextSteps = exports.beginDeferNextSteps = exports.nextStepsSink = 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.resolveBuildInfo = exports.collectVersionInfo = exports.runVersionCommand = exports.printPermissionsSection = exports.printAgentIdentitySection = exports.printUserIdentitySection = exports.runStatusCommand = exports.LOOPBACK_REDIRECT_URIS = exports.projectUrlFromSlug = exports.runPostInstall = exports.runInteractiveSetup = exports.printPermissionsOnboardingHelp = exports.maybeAutoBootstrap = exports.isUserIdentityCached = exports.runPermissionsCommand = exports.oryNpx = exports.interactiveConfigPrompt = exports.promptForProjectUrl = exports.promptOnTty = exports.isTtyAvailable = exports.runWatchCommand = exports.printTraceTail = exports.printEnvHelp = exports.printLogTail = exports.printEnvironment = exports.printOryConfig = exports.runAgentCommand = exports.runConfigureCommand = void 0;
5
+ exports.OtlpExporter = exports.summarizeToolOutput = exports.summarizeToolInput = exports.OryDenialError = exports.alertAttributes = exports.formatAlertSummary = exports.formatAlertMessage = exports.formatDenialSummary = exports.formatDenialMessage = exports.subjectLabel = exports.runWithUserSubject = exports.resolveUserSubject = exports.checkMcpPermission = exports.parseMcpToolGeneric = exports.parseGeminiMcpTool = exports.parseClaudeCodeMcpTool = exports.TOOL_EXECUTION_PHASES = exports.USER_FACING_PHASES = exports.HARNESS_LIFECYCLE_MAP = exports.isToolExecutionPhase = exports.isUserFacingPhase = exports.classifyLifecycle = exports.isInteractiveTool = exports.getInteractiveToolCatalog = exports.INTERACTIVE_TOOL_CATALOG = exports.getToolCatalog = exports.ALL_TOOLS = exports.KNOWN_HARNESSES = exports.HARNESS_TOOL_CATALOG = exports.gateToolCall = exports.applyPermissionMode = exports.checkAndDecide = exports.runRegistryCommand = exports.DEV_JAEGER_CONTAINER = exports.stopDevJaeger = exports.ensureDevJaeger = 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 = void 0;
6
+ exports.wrapTool = exports.registerSubagent = exports.complete = exports.gate = exports.sessionStart = exports.resolveNamespace = exports.parseKeyValueList = exports.otlpExporterFromEnv = void 0;
7
7
  var client_js_1 = require("./client.js");
8
8
  Object.defineProperty(exports, "OryAgentClient", { enumerable: true, get: function () { return client_js_1.OryAgentClient; } });
9
9
  var logger_js_1 = require("./logger.js");
10
10
  Object.defineProperty(exports, "DebugLogger", { enumerable: true, get: function () { return logger_js_1.DebugLogger; } });
11
11
  Object.defineProperty(exports, "redactLogData", { enumerable: true, get: function () { return logger_js_1.redactLogData; } });
12
+ Object.defineProperty(exports, "withQuietStderr", { enumerable: true, get: function () { return logger_js_1.withQuietStderr; } });
12
13
  var tracer_js_1 = require("./tracer.js");
13
14
  Object.defineProperty(exports, "Tracer", { enumerable: true, get: function () { return tracer_js_1.Tracer; } });
14
15
  Object.defineProperty(exports, "ActiveSpan", { enumerable: true, get: function () { return tracer_js_1.ActiveSpan; } });
@@ -82,6 +82,7 @@ const cli_js_1 = require("./cli.js");
82
82
  const cli_invocation_js_1 = require("./cli-invocation.js");
83
83
  const auth_js_1 = require("./auth.js");
84
84
  const client_js_1 = require("./client.js");
85
+ const logger_js_1 = require("./logger.js");
85
86
  const user_login_js_1 = require("./user-login.js");
86
87
  const agent_auth_js_1 = require("./agent-auth.js");
87
88
  const tool_catalog_js_1 = require("./tool-catalog.js");
@@ -261,8 +262,12 @@ async function runInteractiveSetup(binName, harness, args = [], deps = {}) {
261
262
  ttyPrompter = createTtyPrompter();
262
263
  }
263
264
  };
265
+ // Quiet the structured stderr firehose for the duration of the wizard so
266
+ // `[ory-agent] {...}` debug lines don't interleave with the human-facing UI
267
+ // when a session runs with ORY_AGENT_DEBUG=true (the dev launcher always
268
+ // sets it). Debug still lands in the log file.
264
269
  try {
265
- return await runWizard(binName, harness, { runner, prompt, deps, runInteractive });
270
+ return await (0, logger_js_1.withQuietStderr)(() => runWizard(binName, harness, { runner, prompt, deps, runInteractive }));
266
271
  }
267
272
  catch (err) {
268
273
  const msg = err instanceof Error ? err.message : String(err);
@@ -568,10 +573,20 @@ async function configureNetwork(binName, harness, ctx) {
568
573
  // self-register at runtime. Ory Network projects ship with DCR disabled,
569
574
  // so without this the default agent-identity path would never resolve.
570
575
  await maybeEnableDcr(runner, prompt, project.id);
571
- // 6. Create the public OAuth2 client for the PKCE user login.
576
+ // 6. Provision the public OAuth2 client for the PKCE user login. Reuse an
577
+ // existing one on this project first — a re-run (`install --reconfigure`)
578
+ // against the same project must not mint a duplicate public client and
579
+ // orphan the old one (the CLI-created client is never cleaned up by
580
+ // uninstall). Only create when no prior client is found.
572
581
  ui.heading("OAuth2 client");
573
- ui.step("Creating the OAuth2 client for user login...");
574
- const clientId = createOAuth2Client(runner, project.id);
582
+ let clientId = findExistingOAuth2Client(runner, project.id);
583
+ if (clientId) {
584
+ ui.step(`Reusing the existing OAuth2 client for user login (${clientId}).`);
585
+ }
586
+ else {
587
+ ui.step("Creating the OAuth2 client for user login...");
588
+ clientId = createOAuth2Client(runner, project.id);
589
+ }
575
590
  if (!clientId) {
576
591
  ui.warning("Could not create the OAuth2 client automatically.");
577
592
  ui.warnDetail(`Project URL resolved: ${projectUrl}`);
@@ -604,6 +619,14 @@ async function configureNetwork(binName, harness, ctx) {
604
619
  // runtime agent/user tokens can't write relation tuples on a hosted
605
620
  // project (that's a project-admin operation), but the `ory` session can.
606
621
  if (subject) {
622
+ // Persist the subject *shape* this grant used. If the install resolved a
623
+ // SubjectSet (ORY_USER_SUBJECT_NAMESPACE was set), a later runtime session
624
+ // that lacks the env var would otherwise resolve a direct subject_id and
625
+ // check/write a divergent tuple for the same user. Persisting the namespace
626
+ // keeps install-time and runtime writes on one canonical subject shape.
627
+ if ("subjectSet" in subject) {
628
+ (0, config_js_1.saveConfig)({ userSubjectNamespace: subject.subjectSet.namespace });
629
+ }
607
630
  await bootstrapPermissionsViaOry(runner, project.id, harness, subject, prompt);
608
631
  }
609
632
  // 10. Provision the project API key runtime permission *checks* need. Same
@@ -1166,6 +1189,31 @@ async function maybeEnableDcr(runner, prompt, projectId) {
1166
1189
  ui.warnDetail(`Enable it later with: ${manual}`);
1167
1190
  }
1168
1191
  }
1192
+ /** The `client_name` every plugin-provisioned public PKCE client carries. */
1193
+ const OAUTH2_CLIENT_NAME = "ory-agent-plugin";
1194
+ /**
1195
+ * Look for an already-provisioned public PKCE client on the project so a
1196
+ * re-run reuses it instead of minting a duplicate. Matches by the fixed
1197
+ * `client_name` {@link createOAuth2Client} assigns. Best-effort: any CLI or
1198
+ * parse failure returns null and the caller falls back to creating one.
1199
+ */
1200
+ function findExistingOAuth2Client(runner, projectId) {
1201
+ const r = runner.exec([
1202
+ "list",
1203
+ "oauth2-clients",
1204
+ "--project",
1205
+ projectId,
1206
+ "--format",
1207
+ "json",
1208
+ ]);
1209
+ if (r.status !== 0)
1210
+ return null;
1211
+ const clients = parseList(r.stdout, "items");
1212
+ if (!clients)
1213
+ return null;
1214
+ const match = clients.find((c) => c.client_name === OAUTH2_CLIENT_NAME && c.client_id);
1215
+ return match?.client_id ?? null;
1216
+ }
1169
1217
  function createOAuth2Client(runner, projectId) {
1170
1218
  const args = [
1171
1219
  "create",
@@ -1173,7 +1221,7 @@ function createOAuth2Client(runner, projectId) {
1173
1221
  "--project",
1174
1222
  projectId,
1175
1223
  "--name",
1176
- "ory-agent-plugin",
1224
+ OAUTH2_CLIENT_NAME,
1177
1225
  "--grant-type",
1178
1226
  "authorization_code,refresh_token",
1179
1227
  "--response-type",
package/dist/logger.d.ts CHANGED
@@ -12,6 +12,15 @@ export interface LogEntry {
12
12
  */
13
13
  export declare function redactLogData(value: unknown): unknown;
14
14
  export declare class DebugLogger {
15
+ /**
16
+ * Process-global switch to suppress the stderr firehose while still writing
17
+ * to the log file. The interactive installer sets this so structured debug
18
+ * JSON (`[ory-agent] {...}`) doesn't interleave with the wizard's
19
+ * human-facing UI when a session runs with `ORY_AGENT_DEBUG=true` (the dev
20
+ * launcher, for one, always sets it). File logging — the real observability
21
+ * channel — is unaffected.
22
+ */
23
+ static stderrSuppressed: boolean;
15
24
  enabled: boolean;
16
25
  harness: string;
17
26
  logFile: string | null;
@@ -27,3 +36,15 @@ export declare class DebugLogger {
27
36
  warn(event: string, data?: Record<string, unknown>): void;
28
37
  error(event: string, data?: Record<string, unknown>): void;
29
38
  }
39
+ /**
40
+ * Run `fn` with the structured stderr firehose suppressed, restoring the
41
+ * previous state afterward (even on throw). File logging is unaffected.
42
+ *
43
+ * Human-facing CLI commands (the interactive installer, `status`,
44
+ * `permissions status`/`bootstrap`) spin up a client and emit debug/info
45
+ * events; when the process runs with `ORY_AGENT_DEBUG=true` (the dev launcher
46
+ * always sets it) those `[ory-agent] {...}` lines would interleave with the
47
+ * command's polished output. Wrapping the command body in this keeps the
48
+ * terminal clean while the log file still captures everything.
49
+ */
50
+ export declare function withQuietStderr<T>(fn: () => Promise<T> | T): Promise<T>;
package/dist/logger.js CHANGED
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.DebugLogger = void 0;
37
37
  exports.redactLogData = redactLogData;
38
+ exports.withQuietStderr = withQuietStderr;
38
39
  const fs = __importStar(require("node:fs"));
39
40
  const path = __importStar(require("node:path"));
40
41
  /**
@@ -82,6 +83,15 @@ function redactLogData(value) {
82
83
  return value;
83
84
  }
84
85
  class DebugLogger {
86
+ /**
87
+ * Process-global switch to suppress the stderr firehose while still writing
88
+ * to the log file. The interactive installer sets this so structured debug
89
+ * JSON (`[ory-agent] {...}`) doesn't interleave with the wizard's
90
+ * human-facing UI when a session runs with `ORY_AGENT_DEBUG=true` (the dev
91
+ * launcher, for one, always sets it). File logging — the real observability
92
+ * channel — is unaffected.
93
+ */
94
+ static stderrSuppressed = false;
85
95
  enabled;
86
96
  harness;
87
97
  logFile;
@@ -113,8 +123,13 @@ class DebugLogger {
113
123
  ...(safeData ? { data: safeData } : {}),
114
124
  };
115
125
  const line = JSON.stringify(entry);
116
- // Always write to stderr so we don't interfere with hook stdout
117
- process.stderr.write(`[ory-agent] ${line}\n`);
126
+ // Write to stderr (never stdout) so we don't interfere with hook stdout,
127
+ // unless the stderr firehose is globally suppressed (e.g. during the
128
+ // interactive installer, so it doesn't clutter the wizard UI). The log
129
+ // file below still captures everything.
130
+ if (!DebugLogger.stderrSuppressed) {
131
+ process.stderr.write(`[ory-agent] ${line}\n`);
132
+ }
118
133
  if (this.logFile) {
119
134
  const dir = path.dirname(this.logFile);
120
135
  if (!fs.existsSync(dir)) {
@@ -137,3 +152,24 @@ class DebugLogger {
137
152
  }
138
153
  }
139
154
  exports.DebugLogger = DebugLogger;
155
+ /**
156
+ * Run `fn` with the structured stderr firehose suppressed, restoring the
157
+ * previous state afterward (even on throw). File logging is unaffected.
158
+ *
159
+ * Human-facing CLI commands (the interactive installer, `status`,
160
+ * `permissions status`/`bootstrap`) spin up a client and emit debug/info
161
+ * events; when the process runs with `ORY_AGENT_DEBUG=true` (the dev launcher
162
+ * always sets it) those `[ory-agent] {...}` lines would interleave with the
163
+ * command's polished output. Wrapping the command body in this keeps the
164
+ * terminal clean while the log file still captures everything.
165
+ */
166
+ async function withQuietStderr(fn) {
167
+ const prev = DebugLogger.stderrSuppressed;
168
+ DebugLogger.stderrSuppressed = true;
169
+ try {
170
+ return await fn();
171
+ }
172
+ finally {
173
+ DebugLogger.stderrSuppressed = prev;
174
+ }
175
+ }
@@ -61,6 +61,7 @@ exports.printPermissionsOnboardingHelp = printPermissionsOnboardingHelp;
61
61
  exports.maybeAutoBootstrap = maybeAutoBootstrap;
62
62
  const config_js_1 = require("./config.js");
63
63
  const client_js_1 = require("./client.js");
64
+ const logger_js_1 = require("./logger.js");
64
65
  const agent_auth_js_1 = require("./agent-auth.js");
65
66
  const auth_store_js_1 = require("./auth-store.js");
66
67
  const tool_catalog_js_1 = require("./tool-catalog.js");
@@ -85,10 +86,14 @@ async function runPermissionsCommand(binName, harness, args) {
85
86
  return sub ? 0 : 1;
86
87
  }
87
88
  switch (sub) {
89
+ // `status` and `bootstrap` spin up a client and probe/grant per tool,
90
+ // emitting debug/info events. Quiet the stderr firehose so those don't
91
+ // interleave with the command's human-facing output under
92
+ // ORY_AGENT_DEBUG=true; the log file still captures everything.
88
93
  case "status":
89
- return await runPermissionsStatus(binName, harness);
94
+ return await (0, logger_js_1.withQuietStderr)(() => runPermissionsStatus(binName, harness));
90
95
  case "bootstrap":
91
- return await runPermissionsBootstrap(binName, harness, args.slice(1));
96
+ return await (0, logger_js_1.withQuietStderr)(() => runPermissionsBootstrap(binName, harness, args.slice(1)));
92
97
  case "observe":
93
98
  return runPermissionsSetMode(binName, "observe");
94
99
  case "enforce":
@@ -17,6 +17,7 @@ const config_js_1 = require("./config.js");
17
17
  const auth_store_js_1 = require("./auth-store.js");
18
18
  const agent_auth_js_1 = require("./agent-auth.js");
19
19
  const client_js_1 = require("./client.js");
20
+ const logger_js_1 = require("./logger.js");
20
21
  const agent_auth_js_2 = require("./agent-auth.js");
21
22
  const subject_js_1 = require("./subject.js");
22
23
  const tool_catalog_js_1 = require("./tool-catalog.js");
@@ -279,28 +280,35 @@ function formatModeSuffix(source) {
279
280
  * than aborting the command.
280
281
  */
281
282
  async function runStatusCommand(binName, harness, options) {
282
- const heading = `Ory Agent Plugin Status (${options.title})`;
283
- console.log(heading);
284
- console.log("=".repeat(heading.length));
285
- console.log("");
286
- (0, cli_js_1.printOryConfig)();
287
- printUserIdentitySection();
288
- printAgentIdentitySection();
289
- await printPermissionsSection(binName, harness);
290
- if (options.printPluginSection) {
283
+ // Quiet the structured stderr firehose for the duration of the report so
284
+ // `[ory-agent] {...}` debug lines (the permissions probe runs a client and
285
+ // per-tool checks) don't interleave with this human-facing output when the
286
+ // command runs with ORY_AGENT_DEBUG=true. Debug still lands in the log file,
287
+ // and the "Recent activity" section below surfaces its tail regardless.
288
+ await (0, logger_js_1.withQuietStderr)(async () => {
289
+ const heading = `Ory Agent Plugin Status (${options.title})`;
290
+ console.log(heading);
291
+ console.log("=".repeat(heading.length));
291
292
  console.log("");
292
- options.printPluginSection();
293
- }
294
- (0, cli_js_1.printEnvironment)();
295
- console.log("");
296
- console.log("Recent activity:");
297
- (0, cli_js_1.printTraceTail)(harness);
298
- (0, cli_js_1.printLogTail)(harness);
299
- console.log("");
300
- console.log(` Watch traces live: ${(0, cli_invocation_js_1.oryNpx)(binName)} watch`);
301
- console.log("");
302
- console.log(`Drill into any section with:`);
303
- console.log(` ${(0, cli_invocation_js_1.oryNpx)(binName)} agent status`);
304
- console.log(` ${(0, cli_invocation_js_1.oryNpx)(binName)} permissions status`);
305
- console.log(` ${(0, cli_invocation_js_1.oryNpx)(binName)} configure`);
293
+ (0, cli_js_1.printOryConfig)();
294
+ printUserIdentitySection();
295
+ printAgentIdentitySection();
296
+ await printPermissionsSection(binName, harness);
297
+ if (options.printPluginSection) {
298
+ console.log("");
299
+ options.printPluginSection();
300
+ }
301
+ (0, cli_js_1.printEnvironment)();
302
+ console.log("");
303
+ console.log("Recent activity:");
304
+ (0, cli_js_1.printTraceTail)(harness);
305
+ (0, cli_js_1.printLogTail)(harness);
306
+ console.log("");
307
+ console.log(` Watch traces live: ${(0, cli_invocation_js_1.oryNpx)(binName)} watch`);
308
+ console.log("");
309
+ console.log(`Drill into any section with:`);
310
+ console.log(` ${(0, cli_invocation_js_1.oryNpx)(binName)} agent status`);
311
+ console.log(` ${(0, cli_invocation_js_1.oryNpx)(binName)} permissions status`);
312
+ console.log(` ${(0, cli_invocation_js_1.oryNpx)(binName)} configure`);
313
+ });
306
314
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ory/argus",
3
- "version": "0.13.6",
3
+ "version": "0.13.8",
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",