@ory/argus 0.13.8 → 0.14.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.
Files changed (69) hide show
  1. package/README.md +2 -1
  2. package/assets/commands/dashboard.md +34 -0
  3. package/assets/skills/ory-build-agent/SKILL.md +11 -13
  4. package/assets/skills/ory-e2b-sandbox/SKILL.md +0 -1
  5. package/assets/skills/ory-temporal-worker/SKILL.md +8 -10
  6. package/assets/skills/permissions-onboarding/SKILL.md +3 -3
  7. package/dist/adapters.d.ts +27 -4
  8. package/dist/adapters.js +132 -32
  9. package/dist/agent-auth.d.ts +34 -3
  10. package/dist/agent-auth.js +58 -8
  11. package/dist/auth.d.ts +7 -0
  12. package/dist/auth.js +90 -5
  13. package/dist/branding.d.ts +67 -0
  14. package/dist/branding.js +81 -0
  15. package/dist/build-info.json +4 -4
  16. package/dist/cli.d.ts +3 -3
  17. package/dist/cli.js +13 -52
  18. package/dist/client.d.ts +31 -0
  19. package/dist/client.js +58 -0
  20. package/dist/config.d.ts +48 -19
  21. package/dist/config.js +31 -26
  22. package/dist/contract-suite.d.ts +5 -3
  23. package/dist/contract-suite.js +13 -22
  24. package/dist/dashboard-cli.d.ts +8 -0
  25. package/dist/dashboard-cli.js +70 -0
  26. package/dist/dev.js +3 -4
  27. package/dist/index.d.ts +12 -5
  28. package/dist/index.js +41 -3
  29. package/dist/interactive-setup.d.ts +144 -23
  30. package/dist/interactive-setup.js +412 -224
  31. package/dist/local/health.d.ts +14 -0
  32. package/dist/local/health.js +46 -0
  33. package/dist/local/manager.js +1 -3
  34. package/dist/local/seed.d.ts +9 -20
  35. package/dist/local/seed.js +26 -18
  36. package/dist/permissions-cli.js +12 -4
  37. package/dist/project-api-key.d.ts +69 -0
  38. package/dist/project-api-key.js +147 -0
  39. package/dist/registry/manager.js +62 -18
  40. package/dist/setup-actions.d.ts +232 -0
  41. package/dist/setup-actions.js +507 -0
  42. package/dist/setup.d.ts +19 -0
  43. package/dist/setup.js +44 -14
  44. package/dist/skills.js +7 -0
  45. package/dist/status-cli.d.ts +2 -2
  46. package/dist/status-cli.js +4 -30
  47. package/dist/status-data.d.ts +96 -0
  48. package/dist/status-data.js +250 -0
  49. package/dist/status-system.d.ts +24 -0
  50. package/dist/status-system.js +56 -0
  51. package/dist/testing.d.ts +8 -0
  52. package/dist/testing.js +20 -1
  53. package/dist/uninstall.d.ts +33 -15
  54. package/dist/uninstall.js +65 -22
  55. package/dist/user-login.d.ts +16 -9
  56. package/dist/user-login.js +18 -28
  57. package/dist/web/api.d.ts +33 -0
  58. package/dist/web/api.js +294 -0
  59. package/dist/web/launch.d.ts +11 -0
  60. package/dist/web/launch.js +96 -0
  61. package/dist/web/server.d.ts +20 -0
  62. package/dist/web/server.js +233 -0
  63. package/dist/web/types.d.ts +65 -0
  64. package/dist/web/types.js +2 -0
  65. package/dist/webapp/assets/index-Wucl4SZs.css +1 -0
  66. package/dist/webapp/assets/index-m-GtEdq0.js +49 -0
  67. package/dist/webapp/favicon.ico +0 -0
  68. package/dist/webapp/index.html +15 -0
  69. package/package.json +17 -2
@@ -26,21 +26,6 @@ const cli_invocation_js_1 = require("./cli-invocation.js");
26
26
  function resolveNamespace() {
27
27
  return process.env.ORY_PERMISSION_NAMESPACE ?? "AgentTools";
28
28
  }
29
- /**
30
- * Human-readable annotation for where the resolved user-login flag came
31
- * from, so `status` readers know whether to look at the env var or the
32
- * config file to change it.
33
- */
34
- function formatUserLoginSource(source) {
35
- switch (source) {
36
- case "env":
37
- return " (ORY_USER_LOGIN env var)";
38
- case "config":
39
- return " (saved in config; change with: configure --user-login|--no-user-login)";
40
- case "default":
41
- return "";
42
- }
43
- }
44
29
  /**
45
30
  * Format a relative duration like "4h 32m" or "12s" for a future deadline.
46
31
  * Returns "expired" when the deadline has already passed.
@@ -61,27 +46,16 @@ function formatExpiresIn(expiresAtSec, nowMs = Date.now()) {
61
46
  return `${remaining}s`;
62
47
  }
63
48
  /**
64
- * Render the "User identity" block. Shows the `ORY_USER_LOGIN` state and,
65
- * when a PKCE token is cached, the resolved subject and expiry.
49
+ * Render the "User identity" block. The interactive PKCE login runs every
50
+ * session; this shows the cached token's subject and expiry, if any.
66
51
  */
67
52
  function printUserIdentitySection() {
68
- const resolved = (0, config_js_1.resolveConfig)();
69
- const loginOn = resolved.userLogin;
70
53
  const tokens = (0, auth_store_js_1.loadTokens)();
71
54
  console.log("");
72
55
  console.log("User identity (interactive PKCE login):");
73
- const loginState = loginOn
74
- ? `on${formatUserLoginSource(resolved.userLoginSource)}`
75
- : "off (enable with: configure --user-login, or ORY_USER_LOGIN=true)";
76
- console.log(` Login: ${loginState}`);
77
56
  if (!tokens) {
78
57
  console.log(" Token cache: empty");
79
- if (loginOn) {
80
- console.log(" Subject: (will resolve at next session start)");
81
- }
82
- else {
83
- console.log(" Subject: (login disabled — no PKCE flow will run)");
84
- }
58
+ console.log(" Subject: (will resolve at next session start)");
85
59
  return;
86
60
  }
87
61
  const expired = (0, auth_store_js_1.isExpired)(tokens);
@@ -225,7 +199,7 @@ async function printPermissionsSection(binName, harness) {
225
199
  }
226
200
  const hasUser = !!process.env.ORY_USER_SUBJECT_ID || isUserTokenUsable();
227
201
  if (!hasUser) {
228
- console.log(` Coverage: n/a (no cached user identity — run with ORY_USER_LOGIN=true once,`);
202
+ console.log(` Coverage: n/a (no cached user identity — start a session once to sign in,`);
229
203
  console.log(` or set ORY_USER_SUBJECT_ID to probe a known subject)`);
230
204
  return;
231
205
  }
@@ -0,0 +1,96 @@
1
+ import { type StatusSystemSection } from "./status-system.js";
2
+ export interface PermissionsCoverage {
3
+ total: number;
4
+ allowed: number;
5
+ denied: number;
6
+ errored: number;
7
+ errorCode?: string;
8
+ }
9
+ /**
10
+ * Probe the harness's built-in tool catalog against Ory, returning how many
11
+ * tools the resolved user subject is allowed / denied / errored on. Returns
12
+ * undefined when a probe can't run (no project URL, empty catalog, no user
13
+ * identity). Never throws.
14
+ */
15
+ export declare function probePermissionsCoverage(harness: string): Promise<PermissionsCoverage | undefined>;
16
+ export interface StatusConfigSection {
17
+ configPath: string;
18
+ /** Whether a config file actually exists at `configPath` (vs. where one would be written). */
19
+ configExists: boolean;
20
+ projectUrl?: string;
21
+ projectUrlSource: "env" | "config" | "none";
22
+ projectId?: string;
23
+ projectIdSource: "env" | "config" | "none";
24
+ /** Human-readable project / workspace names captured at install (display only). */
25
+ projectName?: string;
26
+ workspaceName?: string;
27
+ apiKeyConfigured: boolean;
28
+ apiKeySource: "env" | "config" | "none";
29
+ oauth2ClientId?: string;
30
+ oauth2ClientIdSource: "env" | "config" | "none";
31
+ /** Display name of the shared PKCE user-login client. Deterministic from
32
+ * branding; present only when a client id is configured. */
33
+ oauth2ClientName?: string;
34
+ auditOnly: boolean;
35
+ permissionMode: "observe" | "enforce";
36
+ permissionModeSource: "env" | "config" | "default";
37
+ userSubjectNamespace?: string;
38
+ userSubjectNamespaceSource: "env" | "config" | "none";
39
+ namespace: string;
40
+ /** Whether debug logging is on (`ORY_AGENT_DEBUG=true`). Env-only, read at
41
+ * process start by the logger — not persisted config, so it's shown read-only. */
42
+ debugEnabled: boolean;
43
+ }
44
+ export interface StatusUserSection {
45
+ tokenCache: "empty" | "present" | "stale";
46
+ subject?: string;
47
+ expiresInSeconds?: number;
48
+ clientId?: string;
49
+ }
50
+ export type AgentIdentitySource = "static_api_key" | "static_client_credentials" | "dcr" | "unregistered";
51
+ export interface StatusAgentSection {
52
+ source: AgentIdentitySource;
53
+ clientId?: string;
54
+ subject?: string;
55
+ registeredAt?: string;
56
+ projectUrlMismatch?: {
57
+ registeredAgainst: string;
58
+ current: string;
59
+ };
60
+ }
61
+ export type PermissionsCoverageStatus = "audit_only" | "no_project" | "no_catalog" | "no_user" | "bootstrapped" | "partial" | "not_bootstrapped" | "probe_failed";
62
+ export interface StatusPermissionsSection {
63
+ mode: "observe" | "enforce";
64
+ namespace: string;
65
+ coverageStatus: PermissionsCoverageStatus;
66
+ coverage?: PermissionsCoverage;
67
+ knownHarnesses?: string[];
68
+ }
69
+ export interface StatusActivityEntry {
70
+ raw: string;
71
+ parsed?: Record<string, unknown>;
72
+ }
73
+ export interface StatusActivitySection {
74
+ traceFile?: string;
75
+ traceCount: number;
76
+ recentSpans: StatusActivityEntry[];
77
+ logFile?: string;
78
+ logCount: number;
79
+ recentLogs: StatusActivityEntry[];
80
+ }
81
+ export interface StatusReport {
82
+ harness: string;
83
+ config: StatusConfigSection;
84
+ user: StatusUserSection;
85
+ agent: StatusAgentSection;
86
+ permissions: StatusPermissionsSection;
87
+ activity: StatusActivitySection;
88
+ /** Core Ory service health (Kratos, Hydra, Keto) — the dashboard's "System information". */
89
+ system: StatusSystemSection;
90
+ }
91
+ /**
92
+ * Gather the full structured status report for a harness. The permissions
93
+ * section runs a live network probe when a project URL and user identity are
94
+ * available (same conditions as the text `status` command).
95
+ */
96
+ export declare function collectStatusReport(harness: string): Promise<StatusReport>;
@@ -0,0 +1,250 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.probePermissionsCoverage = probePermissionsCoverage;
37
+ exports.collectStatusReport = collectStatusReport;
38
+ /**
39
+ * Structured status data — the data-gathering half of the `status` command,
40
+ * shared by the text renderer ({@link file://./status-cli.ts}) and the local
41
+ * web dashboard. `collectStatusReport(harness)` returns a plain, serializable
42
+ * object mirroring every section `status` prints (configuration, user identity,
43
+ * agent identity, permissions coverage, recent activity), so the two surfaces
44
+ * never drift.
45
+ *
46
+ * The live permission-coverage probe ({@link probePermissionsCoverage}) lives
47
+ * here too and is imported back by `status-cli.ts`, so there is a single probe
48
+ * implementation.
49
+ */
50
+ const config_js_1 = require("./config.js");
51
+ const auth_store_js_1 = require("./auth-store.js");
52
+ const agent_auth_js_1 = require("./agent-auth.js");
53
+ const client_js_1 = require("./client.js");
54
+ const subject_js_1 = require("./subject.js");
55
+ const branding_js_1 = require("./branding.js");
56
+ const tool_catalog_js_1 = require("./tool-catalog.js");
57
+ const fs = __importStar(require("node:fs"));
58
+ // Additive dashboard section: core Ory service health (see status-system.ts).
59
+ const status_system_js_1 = require("./status-system.js");
60
+ function resolveNamespace() {
61
+ return process.env.ORY_PERMISSION_NAMESPACE ?? "AgentTools";
62
+ }
63
+ /**
64
+ * Probe the harness's built-in tool catalog against Ory, returning how many
65
+ * tools the resolved user subject is allowed / denied / errored on. Returns
66
+ * undefined when a probe can't run (no project URL, empty catalog, no user
67
+ * identity). Never throws.
68
+ */
69
+ async function probePermissionsCoverage(harness) {
70
+ const resolved = (0, config_js_1.resolveConfig)();
71
+ if (!resolved.projectUrl)
72
+ return undefined;
73
+ const catalog = (0, tool_catalog_js_1.getToolCatalog)(harness);
74
+ if (catalog.length === 0)
75
+ return undefined;
76
+ const tokens = (0, auth_store_js_1.loadTokens)();
77
+ const subjectOverride = process.env.ORY_USER_SUBJECT_ID;
78
+ if (!subjectOverride && (!tokens || (0, auth_store_js_1.isExpired)(tokens))) {
79
+ return undefined;
80
+ }
81
+ const client = client_js_1.OryAgentClient.fromEnv(harness);
82
+ if (tokens && !(0, auth_store_js_1.isExpired)(tokens)) {
83
+ client.setUserPrincipal({ subject: tokens.subject, token: tokens.accessToken });
84
+ }
85
+ await (0, agent_auth_js_1.ensureAgentIdentity)(client, { projectUrl: resolved.projectUrl }).catch(() => {
86
+ /* best-effort; the probe still runs with whatever credentials we have */
87
+ });
88
+ const subject = (0, subject_js_1.resolveUserSubject)(client);
89
+ if ((0, subject_js_1.subjectLabel)(subject) === "agent:unknown")
90
+ return undefined;
91
+ const namespace = resolveNamespace();
92
+ const result = { total: catalog.length, allowed: 0, denied: 0, errored: 0 };
93
+ for (const tool of catalog) {
94
+ const check = { namespace, object: tool, relation: "use", ...subject };
95
+ try {
96
+ const probe = await client.checkPermission(check, {
97
+ spanAttributes: { toolName: tool, source: "status_coverage" },
98
+ });
99
+ if (probe.allowed)
100
+ result.allowed++;
101
+ else
102
+ result.denied++;
103
+ }
104
+ catch (err) {
105
+ const ory = err;
106
+ result.errored++;
107
+ result.errorCode = result.errorCode ?? ory.code ?? "unknown";
108
+ }
109
+ }
110
+ return result;
111
+ }
112
+ function collectConfig(harness) {
113
+ const r = (0, config_js_1.resolveConfig)();
114
+ const configPath = (0, config_js_1.getConfigPath)();
115
+ return {
116
+ configPath,
117
+ configExists: fs.existsSync(configPath),
118
+ projectUrl: r.projectUrl,
119
+ projectUrlSource: r.projectUrlSource,
120
+ projectId: r.projectId,
121
+ projectIdSource: r.projectIdSource,
122
+ projectName: r.projectName,
123
+ workspaceName: r.workspaceName,
124
+ apiKeyConfigured: !!r.apiKey,
125
+ apiKeySource: r.apiKeySource,
126
+ oauth2ClientId: r.oauth2ClientId,
127
+ oauth2ClientIdSource: r.oauth2ClientIdSource,
128
+ oauth2ClientName: r.oauth2ClientId ? branding_js_1.USER_LOGIN_CLIENT_NAME : undefined,
129
+ auditOnly: r.auditOnly,
130
+ permissionMode: r.permissionMode,
131
+ permissionModeSource: r.permissionModeSource,
132
+ userSubjectNamespace: r.userSubjectNamespace,
133
+ userSubjectNamespaceSource: r.userSubjectNamespaceSource,
134
+ namespace: resolveNamespace(),
135
+ debugEnabled: process.env.ORY_AGENT_DEBUG === "true",
136
+ };
137
+ }
138
+ function collectUser() {
139
+ const tokens = (0, auth_store_js_1.loadTokens)();
140
+ if (!tokens) {
141
+ return { tokenCache: "empty" };
142
+ }
143
+ const nowSec = Math.floor(Date.now() / 1000);
144
+ return {
145
+ tokenCache: (0, auth_store_js_1.isExpired)(tokens) ? "stale" : "present",
146
+ subject: tokens.subject,
147
+ expiresInSeconds: tokens.expiresAt - nowSec,
148
+ clientId: tokens.clientId,
149
+ };
150
+ }
151
+ function collectAgent() {
152
+ if (process.env.ORY_AGENT_API_KEY) {
153
+ return { source: "static_api_key", subject: process.env.ORY_AGENT_SUBJECT_ID };
154
+ }
155
+ if (process.env.ORY_AGENT_CLIENT_ID && process.env.ORY_AGENT_CLIENT_SECRET) {
156
+ return {
157
+ source: "static_client_credentials",
158
+ clientId: process.env.ORY_AGENT_CLIENT_ID,
159
+ subject: process.env.ORY_AGENT_SUBJECT_ID ?? process.env.ORY_AGENT_CLIENT_ID,
160
+ };
161
+ }
162
+ const persisted = (0, agent_auth_js_1.loadAgentDynamicCredentials)();
163
+ if (!persisted)
164
+ return { source: "unregistered" };
165
+ const resolved = (0, config_js_1.resolveConfig)();
166
+ const mismatch = resolved.projectUrl && persisted.projectUrl !== resolved.projectUrl
167
+ ? { registeredAgainst: persisted.projectUrl, current: resolved.projectUrl }
168
+ : undefined;
169
+ return {
170
+ source: "dcr",
171
+ clientId: persisted.clientId,
172
+ subject: process.env.ORY_AGENT_SUBJECT_ID ?? persisted.clientId,
173
+ registeredAt: new Date(persisted.registeredAt * 1000).toISOString(),
174
+ projectUrlMismatch: mismatch,
175
+ };
176
+ }
177
+ async function collectPermissions(harness) {
178
+ const resolved = (0, config_js_1.resolveConfig)();
179
+ const namespace = resolveNamespace();
180
+ const catalog = (0, tool_catalog_js_1.getToolCatalog)(harness);
181
+ const base = { mode: resolved.permissionMode, namespace };
182
+ if (resolved.auditOnly)
183
+ return { ...base, coverageStatus: "audit_only" };
184
+ if (!resolved.projectUrl)
185
+ return { ...base, coverageStatus: "no_project" };
186
+ if (catalog.length === 0) {
187
+ return { ...base, coverageStatus: "no_catalog", knownHarnesses: [...tool_catalog_js_1.KNOWN_HARNESSES] };
188
+ }
189
+ const hasUser = !!process.env.ORY_USER_SUBJECT_ID || isUserTokenUsable();
190
+ if (!hasUser)
191
+ return { ...base, coverageStatus: "no_user" };
192
+ const coverage = await probePermissionsCoverage(harness);
193
+ if (!coverage)
194
+ return { ...base, coverageStatus: "no_user" };
195
+ if (coverage.errored > 0 && coverage.allowed === 0 && coverage.denied === 0) {
196
+ return { ...base, coverageStatus: "probe_failed", coverage };
197
+ }
198
+ const status = coverage.allowed === coverage.total
199
+ ? "bootstrapped"
200
+ : coverage.allowed === 0
201
+ ? "not_bootstrapped"
202
+ : "partial";
203
+ return { ...base, coverageStatus: status, coverage };
204
+ }
205
+ function isUserTokenUsable() {
206
+ const tokens = (0, auth_store_js_1.loadTokens)();
207
+ return !!tokens && !(0, auth_store_js_1.isExpired)(tokens);
208
+ }
209
+ function readTail(file, limit) {
210
+ if (!file || !fs.existsSync(file))
211
+ return { file, count: 0, recent: [] };
212
+ const lines = fs.readFileSync(file, "utf-8").trim().split("\n").filter(Boolean);
213
+ const recent = lines.slice(-limit).map((raw) => {
214
+ try {
215
+ return { raw, parsed: JSON.parse(raw) };
216
+ }
217
+ catch {
218
+ return { raw };
219
+ }
220
+ });
221
+ return { file, count: lines.length, recent };
222
+ }
223
+ function collectActivity(harness) {
224
+ const trace = readTail((0, client_js_1.resolveTraceFilePath)(harness), 10);
225
+ const log = readTail((0, client_js_1.resolveDebugLogPath)(harness), 10);
226
+ return {
227
+ traceFile: trace.file,
228
+ traceCount: trace.count,
229
+ recentSpans: trace.recent,
230
+ logFile: log.file,
231
+ logCount: log.count,
232
+ recentLogs: log.recent,
233
+ };
234
+ }
235
+ /**
236
+ * Gather the full structured status report for a harness. The permissions
237
+ * section runs a live network probe when a project URL and user identity are
238
+ * available (same conditions as the text `status` command).
239
+ */
240
+ async function collectStatusReport(harness) {
241
+ return {
242
+ harness,
243
+ config: collectConfig(harness),
244
+ user: collectUser(),
245
+ agent: collectAgent(),
246
+ permissions: await collectPermissions(harness),
247
+ activity: collectActivity(harness),
248
+ system: await (0, status_system_js_1.collectSystemServices)(),
249
+ };
250
+ }
@@ -0,0 +1,24 @@
1
+ export interface StatusSystemService {
2
+ /** Service name, e.g. "Kratos". */
3
+ name: string;
4
+ /** One-line role, e.g. "Identities & sessions". */
5
+ role: string;
6
+ /** Published host port. */
7
+ port: number;
8
+ /** Base URL for the service. */
9
+ url: string;
10
+ /** The endpoint probed to decide health. */
11
+ healthUrl: string;
12
+ /** true = healthy, false = unreachable/error, null = not probed. */
13
+ healthy: boolean | null;
14
+ /** Human-readable detail (HTTP status or error) when unhealthy. */
15
+ detail?: string;
16
+ }
17
+ export interface StatusSystemSection {
18
+ services: StatusSystemService[];
19
+ }
20
+ /**
21
+ * Probe the core Ory services' readiness endpoints and return their health.
22
+ * Never throws — an unreachable service is reported as `healthy: false`.
23
+ */
24
+ export declare function collectSystemServices(): Promise<StatusSystemSection>;
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.collectSystemServices = collectSystemServices;
4
+ /**
5
+ * System-information probe for the dashboard: the health of the core Ory
6
+ * backend services the plugin authenticates and authorizes against — Kratos
7
+ * (identities), Hydra (OAuth2), and Keto (permissions).
8
+ *
9
+ * This is additive to the shared {@link file://./status-data.ts} report: the
10
+ * dashboard renders it as a "System information" section, while the installer
11
+ * simply ignores it. Ports and health endpoints are the same constants the
12
+ * local-stack manager and `local status` use, so the surfaces never disagree.
13
+ */
14
+ const configs_js_1 = require("./local/configs.js");
15
+ /** The three core Ory services, in display order. */
16
+ function systemServiceSpecs() {
17
+ return [
18
+ { name: "Kratos", role: "Identities & sessions", port: configs_js_1.KRATOS_PUBLIC_PORT, healthPath: "/health/ready" },
19
+ { name: "Hydra", role: "OAuth2 & OIDC", port: configs_js_1.HYDRA_PUBLIC_PORT, healthPath: "/health/ready" },
20
+ { name: "Keto", role: "Permissions (Zanzibar relation checks)", port: configs_js_1.KETO_READ_PORT, healthPath: "/health/ready" },
21
+ ];
22
+ }
23
+ async function probe(healthUrl) {
24
+ try {
25
+ const res = await fetch(healthUrl, { signal: AbortSignal.timeout(2500) });
26
+ if (res.ok)
27
+ return { healthy: true };
28
+ return { healthy: false, detail: `HTTP ${res.status}` };
29
+ }
30
+ catch (err) {
31
+ return { healthy: false, detail: err instanceof Error ? err.message : String(err) };
32
+ }
33
+ }
34
+ /**
35
+ * Probe the core Ory services' readiness endpoints and return their health.
36
+ * Never throws — an unreachable service is reported as `healthy: false`.
37
+ */
38
+ async function collectSystemServices() {
39
+ const specs = systemServiceSpecs();
40
+ const services = await Promise.all(specs.map(async (spec) => {
41
+ const url = `http://localhost:${spec.port}`;
42
+ const healthUrl = `${url}${spec.healthPath}`;
43
+ const { healthy, detail } = await probe(healthUrl);
44
+ const service = {
45
+ name: spec.name,
46
+ role: spec.role,
47
+ port: spec.port,
48
+ url,
49
+ healthUrl,
50
+ healthy,
51
+ detail,
52
+ };
53
+ return service;
54
+ }));
55
+ return { services };
56
+ }
package/dist/testing.d.ts CHANGED
@@ -14,6 +14,14 @@ export { runHarnessContractSuite, type HarnessContractAdapter, type ContractCont
14
14
  * Pass overrides to customize (e.g. a different harness name).
15
15
  */
16
16
  export declare function createMockClient(overrides?: Partial<ConstructorParameters<typeof OryAgentClient>[0]>): OryAgentClient;
17
+ /**
18
+ * Control what the exact-tuple existence probe ({@link OryAgentClient.relationshipExists},
19
+ * used by `ensureRelationship`) reports. Pass `true` to model a tuple that is
20
+ * already stored (the write is then skipped), `false` for absent (the write
21
+ * proceeds). Returns the spy so callers can e.g. `.mockRejectedValue(...)` to
22
+ * model a failed probe.
23
+ */
24
+ export declare function stubRelationshipExists(client: OryAgentClient, exists: boolean): import("vitest").Mock<(check: import("./types.js").PermissionCheck) => Promise<boolean>>;
17
25
  /**
18
26
  * Stub an internal API instance method on the client.
19
27
  * Returns a vi.fn mock so callers can assert on calls.
package/dist/testing.js CHANGED
@@ -42,6 +42,7 @@ var __importStar = (this && this.__importStar) || (function () {
42
42
  Object.defineProperty(exports, "__esModule", { value: true });
43
43
  exports.BATCH_SERVER_DENIED = exports.BATCH_SERVER_ALLOWED_TOOL_DENIED = exports.BATCH_BOTH_ALLOWED = exports.PERMISSION_DENIED = exports.PERMISSION_ALLOWED = exports.MOCK_INACTIVE_OAUTH2_RESPONSE = exports.MOCK_OAUTH2_RESPONSE = exports.MOCK_INACTIVE_SESSION_RESPONSE = exports.MOCK_SESSION_RESPONSE = exports.runHarnessContractSuite = void 0;
44
44
  exports.createMockClient = createMockClient;
45
+ exports.stubRelationshipExists = stubRelationshipExists;
45
46
  exports.stubApi = stubApi;
46
47
  exports.makeAxiosError = makeAxiosError;
47
48
  exports.makeNetworkError = makeNetworkError;
@@ -85,13 +86,31 @@ Object.defineProperty(exports, "runHarnessContractSuite", { enumerable: true, ge
85
86
  * Pass overrides to customize (e.g. a different harness name).
86
87
  */
87
88
  function createMockClient(overrides) {
88
- return new client_js_1.OryAgentClient({
89
+ const client = new client_js_1.OryAgentClient({
89
90
  projectUrl: "https://test.projects.oryapis.com",
90
91
  apiKey: "test-api-key",
91
92
  harness: "test",
92
93
  sessionCacheTtlMs: 0,
93
94
  ...overrides,
94
95
  });
96
+ // `ensureRelationship` probes for the exact tuple before writing. Default
97
+ // to "no tuple exists" so a plain createRelationship-asserting test sees the
98
+ // write proceed; tests that model an already-present tuple override this via
99
+ // `stubRelationshipExists`. We spy the public `relationshipExists` method
100
+ // (not the internal getRelationships API) because setAgentPrincipal rebuilds
101
+ // the Keto API instance and would discard an API-level stub.
102
+ vitest_1.vi.spyOn(client, "relationshipExists").mockResolvedValue(false);
103
+ return client;
104
+ }
105
+ /**
106
+ * Control what the exact-tuple existence probe ({@link OryAgentClient.relationshipExists},
107
+ * used by `ensureRelationship`) reports. Pass `true` to model a tuple that is
108
+ * already stored (the write is then skipped), `false` for absent (the write
109
+ * proceeds). Returns the spy so callers can e.g. `.mockRejectedValue(...)` to
110
+ * model a failed probe.
111
+ */
112
+ function stubRelationshipExists(client, exists) {
113
+ return vitest_1.vi.spyOn(client, "relationshipExists").mockResolvedValue(exists);
95
114
  }
96
115
  /**
97
116
  * Stub an internal API instance method on the client.
@@ -1,25 +1,37 @@
1
1
  /**
2
- * Credential teardown shared by every plugin's `uninstall` command.
2
+ * Config teardown shared by every plugin's `uninstall` command.
3
3
  *
4
- * Uninstalling a plugin removes all *authentication* state from the shared
5
- * config file while preserving *configuration* (project URL, OAuth2 client
6
- * id, audit mode, permission mode, user-login flag). The intent is that a
7
- * later re-install re-authenticates from scratch the user logs in again
8
- * and the agent registers a fresh dynamic client rather than silently
9
- * inheriting stale credentials.
4
+ * Uninstalling a plugin wipes the shared config file completely — every
5
+ * value is removed, both *authentication* state (user tokens, agent DCR
6
+ * credentials, static API key) and *configuration* (project URL, OAuth2
7
+ * client id, audit mode, permission mode, user-login flag). The file is
8
+ * left as an empty object, so a later re-install starts from a clean
9
+ * slate rather than inheriting any stale state.
10
10
  *
11
11
  * What is removed:
12
12
  * - the user's persisted OAuth2 tokens (`user`)
13
13
  * - the agent's dynamic-registration credentials and every sub-agent
14
14
  * registration (`agent`)
15
15
  * - the operator-supplied static agent API key (`apiKey`)
16
+ * - all configuration values (`projectUrl`, `projectId`, `projectName`,
17
+ * `workspaceName`, `oauth2ClientId`, `auditOnly`, `permissionMode`,
18
+ * `userLogin`, `userSubjectNamespace`)
16
19
  * - any dangling PKCE-flight lock
17
20
  *
18
- * Before clearing local state, each dynamically-registered client (the
19
- * agent and each sub-agent) is revoked server-side via RFC 7592 so the
20
- * Ory project isn't left with orphaned OAuth2 clients accumulating across
21
- * install/uninstall cycles. Revocation is best-effort: failures warn and
22
- * local state is cleared regardless.
21
+ * Server-side teardown runs first, best-effort:
22
+ * - Each dynamically-registered OAuth2 client (the agent and every
23
+ * sub-agent) is revoked via RFC 7592, so the Ory project isn't left
24
+ * with orphaned clients accumulating across install/uninstall cycles.
25
+ * - The project API key the installer minted is deleted via the Ory
26
+ * Console API (addressed by the persisted token id).
27
+ *
28
+ * The one server-side resource deliberately **left behind** is the shared
29
+ * user-login PKCE OAuth2 client (`oauth2ClientId`): it is a public,
30
+ * identity-free client that every user and every harness install on the
31
+ * project reuses, so deleting it would break other installations. It is
32
+ * only forgotten locally (its id is wiped from the config file). Every
33
+ * server-side step is best-effort: failures warn and local state is
34
+ * cleared regardless.
23
35
  */
24
36
  export interface ClearCredentialsOptions {
25
37
  /**
@@ -38,10 +50,16 @@ export interface ClearCredentialsResult {
38
50
  revoked: number;
39
51
  /** Number of dynamic clients whose server-side revoke failed. */
40
52
  revokeFailures: number;
53
+ /**
54
+ * Whether the project API key was deleted server-side (includes an
55
+ * already-gone key). `false` when there was none to delete, its token id
56
+ * wasn't tracked, or the deletion failed/was skipped.
57
+ */
58
+ apiKeyDeleted: boolean;
41
59
  }
42
60
  /**
43
- * Remove all authentication state from the shared config file, revoking
44
- * dynamically-registered clients server-side first. Preserves every
45
- * non-credential configuration value. Never throws.
61
+ * Wipe the shared config file completely every value, both credentials
62
+ * and configuration — revoking dynamically-registered clients server-side
63
+ * first. The file is left as an empty object. Never throws.
46
64
  */
47
65
  export declare function clearCredentialsForUninstall(options?: ClearCredentialsOptions): Promise<ClearCredentialsResult>;