@ory/argus 0.13.9 → 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 (65) 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 +12 -5
  8. package/dist/adapters.js +59 -10
  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/config.d.ts +48 -19
  19. package/dist/config.js +31 -26
  20. package/dist/contract-suite.d.ts +5 -3
  21. package/dist/contract-suite.js +13 -22
  22. package/dist/dashboard-cli.d.ts +8 -0
  23. package/dist/dashboard-cli.js +70 -0
  24. package/dist/dev.js +3 -4
  25. package/dist/index.d.ts +11 -4
  26. package/dist/index.js +39 -3
  27. package/dist/interactive-setup.d.ts +144 -23
  28. package/dist/interactive-setup.js +412 -224
  29. package/dist/local/health.d.ts +14 -0
  30. package/dist/local/health.js +46 -0
  31. package/dist/local/manager.js +1 -3
  32. package/dist/local/seed.d.ts +9 -20
  33. package/dist/local/seed.js +26 -18
  34. package/dist/permissions-cli.js +1 -1
  35. package/dist/project-api-key.d.ts +69 -0
  36. package/dist/project-api-key.js +147 -0
  37. package/dist/registry/manager.js +62 -18
  38. package/dist/setup-actions.d.ts +232 -0
  39. package/dist/setup-actions.js +507 -0
  40. package/dist/setup.d.ts +19 -0
  41. package/dist/setup.js +44 -14
  42. package/dist/skills.js +7 -0
  43. package/dist/status-cli.d.ts +2 -2
  44. package/dist/status-cli.js +4 -30
  45. package/dist/status-data.d.ts +96 -0
  46. package/dist/status-data.js +250 -0
  47. package/dist/status-system.d.ts +24 -0
  48. package/dist/status-system.js +56 -0
  49. package/dist/uninstall.d.ts +33 -15
  50. package/dist/uninstall.js +65 -22
  51. package/dist/user-login.d.ts +16 -9
  52. package/dist/user-login.js +18 -28
  53. package/dist/web/api.d.ts +33 -0
  54. package/dist/web/api.js +294 -0
  55. package/dist/web/launch.d.ts +11 -0
  56. package/dist/web/launch.js +96 -0
  57. package/dist/web/server.d.ts +20 -0
  58. package/dist/web/server.js +233 -0
  59. package/dist/web/types.d.ts +65 -0
  60. package/dist/web/types.js +2 -0
  61. package/dist/webapp/assets/index-Wucl4SZs.css +1 -0
  62. package/dist/webapp/assets/index-m-GtEdq0.js +49 -0
  63. package/dist/webapp/favicon.ico +0 -0
  64. package/dist/webapp/index.html +15 -0
  65. package/package.json +17 -2
package/dist/uninstall.js CHANGED
@@ -1,36 +1,49 @@
1
1
  "use strict";
2
2
  /**
3
- * Credential teardown shared by every plugin's `uninstall` command.
3
+ * Config teardown shared by every plugin's `uninstall` command.
4
4
  *
5
- * Uninstalling a plugin removes all *authentication* state from the shared
6
- * config file while preserving *configuration* (project URL, OAuth2 client
7
- * id, audit mode, permission mode, user-login flag). The intent is that a
8
- * later re-install re-authenticates from scratch the user logs in again
9
- * and the agent registers a fresh dynamic client rather than silently
10
- * inheriting stale credentials.
5
+ * Uninstalling a plugin wipes the shared config file completely — every
6
+ * value is removed, both *authentication* state (user tokens, agent DCR
7
+ * credentials, static API key) and *configuration* (project URL, OAuth2
8
+ * client id, audit mode, permission mode, user-login flag). The file is
9
+ * left as an empty object, so a later re-install starts from a clean
10
+ * slate rather than inheriting any stale state.
11
11
  *
12
12
  * What is removed:
13
13
  * - the user's persisted OAuth2 tokens (`user`)
14
14
  * - the agent's dynamic-registration credentials and every sub-agent
15
15
  * registration (`agent`)
16
16
  * - the operator-supplied static agent API key (`apiKey`)
17
+ * - all configuration values (`projectUrl`, `projectId`, `projectName`,
18
+ * `workspaceName`, `oauth2ClientId`, `auditOnly`, `permissionMode`,
19
+ * `userLogin`, `userSubjectNamespace`)
17
20
  * - any dangling PKCE-flight lock
18
21
  *
19
- * Before clearing local state, each dynamically-registered client (the
20
- * agent and each sub-agent) is revoked server-side via RFC 7592 so the
21
- * Ory project isn't left with orphaned OAuth2 clients accumulating across
22
- * install/uninstall cycles. Revocation is best-effort: failures warn and
23
- * local state is cleared regardless.
22
+ * Server-side teardown runs first, best-effort:
23
+ * - Each dynamically-registered OAuth2 client (the agent and every
24
+ * sub-agent) is revoked via RFC 7592, so the Ory project isn't left
25
+ * with orphaned clients accumulating across install/uninstall cycles.
26
+ * - The project API key the installer minted is deleted via the Ory
27
+ * Console API (addressed by the persisted token id).
28
+ *
29
+ * The one server-side resource deliberately **left behind** is the shared
30
+ * user-login PKCE OAuth2 client (`oauth2ClientId`): it is a public,
31
+ * identity-free client that every user and every harness install on the
32
+ * project reuses, so deleting it would break other installations. It is
33
+ * only forgotten locally (its id is wiped from the config file). Every
34
+ * server-side step is best-effort: failures warn and local state is
35
+ * cleared regardless.
24
36
  */
25
37
  Object.defineProperty(exports, "__esModule", { value: true });
26
38
  exports.clearCredentialsForUninstall = clearCredentialsForUninstall;
27
39
  const config_js_1 = require("./config.js");
28
40
  const agent_auth_js_1 = require("./agent-auth.js");
29
41
  const auth_store_js_1 = require("./auth-store.js");
42
+ const project_api_key_js_1 = require("./project-api-key.js");
30
43
  /**
31
- * Remove all authentication state from the shared config file, revoking
32
- * dynamically-registered clients server-side first. Preserves every
33
- * non-credential configuration value. Never throws.
44
+ * Wipe the shared config file completely every value, both credentials
45
+ * and configuration — revoking dynamically-registered clients server-side
46
+ * first. The file is left as an empty object. Never throws.
34
47
  */
35
48
  async function clearCredentialsForUninstall(options = {}) {
36
49
  const log = options.log ?? ((msg) => console.log(msg));
@@ -40,12 +53,13 @@ async function clearCredentialsForUninstall(options = {}) {
40
53
  const subAgents = config.agent?.subAgents ?? {};
41
54
  const subAgentEntries = Object.entries(subAgents);
42
55
  const hadCredentials = Boolean(config.user || dynamic || subAgentEntries.length > 0 || config.apiKey);
43
- if (!hadCredentials) {
44
- log("No stored Ory credentials to remove.");
45
- return { hadCredentials: false, revoked: 0, revokeFailures: 0 };
56
+ if (Object.keys(config).length === 0) {
57
+ log("No Ory configuration to remove.");
58
+ return { hadCredentials: false, revoked: 0, revokeFailures: 0, apiKeyDeleted: false };
46
59
  }
47
60
  let revoked = 0;
48
61
  let revokeFailures = 0;
62
+ let apiKeyDeleted = false;
49
63
  if (revoke) {
50
64
  const targets = [];
51
65
  if (dynamic) {
@@ -81,10 +95,39 @@ async function clearCredentialsForUninstall(options = {}) {
81
95
  log(` Warning: server-side revoke of ${target.label} ${target.clientId} failed (${detail}). Clearing local state anyway.`);
82
96
  }
83
97
  }
98
+ // Delete the project API key the installer minted. Deletion addresses the
99
+ // token by its id (its secret value can't be used), so it needs both the
100
+ // project id and the persisted token id. A key present without a tracked id
101
+ // (minted before id-tracking, or supplied by hand via `configure --api-key`)
102
+ // can't be removed automatically — warn so it can be pruned in the Console.
103
+ if (config.apiKey) {
104
+ if (config.projectId && config.apiKeyId) {
105
+ const outcome = await (0, project_api_key_js_1.deleteProjectApiKeyViaConsole)({
106
+ projectId: config.projectId,
107
+ tokenId: config.apiKeyId,
108
+ });
109
+ if (outcome.status === "deleted") {
110
+ apiKeyDeleted = true;
111
+ log(" Deleted the project API key.");
112
+ }
113
+ else if (outcome.status === "skipped") {
114
+ log(` Could not delete the project API key (${outcome.message}). Clearing local state anyway.`);
115
+ }
116
+ else {
117
+ const detail = typeof outcome.httpStatus === "number"
118
+ ? `HTTP ${outcome.httpStatus}`
119
+ : (outcome.message ?? "unknown error");
120
+ log(` Warning: server-side deletion of the project API key failed (${detail}). Clearing local state anyway.`);
121
+ }
122
+ }
123
+ else {
124
+ log(" The stored project API key has no tracked token id — remove it manually in the Ory Console if it's no longer needed. Clearing local state anyway.");
125
+ }
126
+ }
84
127
  }
85
- // Drop every credential block; keep all configuration values.
86
- (0, config_js_1.saveConfig)({ user: null, agent: null, apiKey: null });
128
+ // Wipe the entire config file credentials and configuration alike.
129
+ (0, config_js_1.mutateConfig)(() => ({}));
87
130
  (0, auth_store_js_1.clearPkceFlightLock)();
88
- log("Removed stored Ory credentials (user login, agent identity, API key).");
89
- return { hadCredentials: true, revoked, revokeFailures };
131
+ log("Removed all Ory configuration and credentials.");
132
+ return { hadCredentials, revoked, revokeFailures, apiKeyDeleted };
90
133
  }
@@ -17,11 +17,15 @@
17
17
  * Every terminal path emits exactly one `user.auth` trace span so that
18
18
  * the audit trail is complete regardless of outcome.
19
19
  *
20
- * The whole flow is a no-op (mode `disabled`) unless user login is
21
- * enabled either via the `ORY_USER_LOGIN` env var (values `1`, `true`,
22
- * `yes`, `on`) or persisted in the shared config file (`userLogin: true`,
23
- * set with `configure --user-login`). The env var overrides the config
24
- * file when set.
20
+ * The flow runs on **every** session and is **never** blocking: it
21
+ * establishes or refreshes the user identity for attribution, delegation,
22
+ * and permission-subject resolution, but a failure to authenticate never
23
+ * stops the session. Enforcement is governed solely by `permissionMode`
24
+ * (observe/enforce) at tool-call time — see `checkAndDecide`. The one
25
+ * exception is `auditOnly`, which disables auth entirely (mode
26
+ * `audit_only`). When the user can't be authenticated the session still
27
+ * proceeds; under `enforce` a missing user identity naturally denies at
28
+ * the tool (no matching tuples), and under `observe` it is audited.
25
29
  *
26
30
  * This authenticates the *user* (the human at the keyboard). The
27
31
  * separate agent identity (the AI process making the calls) is resolved
@@ -32,14 +36,12 @@ import { OryAgentClient } from "./client.js";
32
36
  import { type OryOAuth2Tokens } from "./config.js";
33
37
  import { promptForProjectUrl } from "./cli.js";
34
38
  import { pkceLogin } from "./auth.js";
35
- export type UserLoginMode = "disabled" | "audit_only" | "env_token" | "ok" | "refreshed" | "skipped" | "declined" | "error";
39
+ export type UserLoginMode = "audit_only" | "env_token" | "ok" | "refreshed" | "skipped" | "declined" | "error";
36
40
  export interface UserLoginOptions {
37
41
  /** Bin name used in user-facing prompts (e.g. "ory-claude"). */
38
42
  binName: string;
39
43
  /** Logical harness name (used by the tracer). */
40
44
  harness: string;
41
- /** Whether the caller can hard-block on auth failure. */
42
- allowBlock: boolean;
43
45
  /**
44
46
  * Optional override of the PKCE login function — used by tests and by
45
47
  * harnesses that want to plug in a different flow.
@@ -51,7 +53,12 @@ export interface UserLoginOptions {
51
53
  promptForProjectUrlFn?: typeof promptForProjectUrl;
52
54
  }
53
55
  export interface UserLoginDecision {
54
- /** Whether the caller should let the session proceed. */
56
+ /**
57
+ * Whether the caller should let the session proceed. User authentication
58
+ * is non-blocking, so this is **always** `true` — it is retained so the
59
+ * session-start adapters keep a stable shape. Enforcement lives in
60
+ * `permissionMode` at tool-call time, not here.
61
+ */
55
62
  proceed: boolean;
56
63
  /** Stable mode identifier for telemetry and tests. */
57
64
  mode: UserLoginMode;
@@ -18,11 +18,15 @@
18
18
  * Every terminal path emits exactly one `user.auth` trace span so that
19
19
  * the audit trail is complete regardless of outcome.
20
20
  *
21
- * The whole flow is a no-op (mode `disabled`) unless user login is
22
- * enabled either via the `ORY_USER_LOGIN` env var (values `1`, `true`,
23
- * `yes`, `on`) or persisted in the shared config file (`userLogin: true`,
24
- * set with `configure --user-login`). The env var overrides the config
25
- * file when set.
21
+ * The flow runs on **every** session and is **never** blocking: it
22
+ * establishes or refreshes the user identity for attribution, delegation,
23
+ * and permission-subject resolution, but a failure to authenticate never
24
+ * stops the session. Enforcement is governed solely by `permissionMode`
25
+ * (observe/enforce) at tool-call time — see `checkAndDecide`. The one
26
+ * exception is `auditOnly`, which disables auth entirely (mode
27
+ * `audit_only`). When the user can't be authenticated the session still
28
+ * proceeds; under `enforce` a missing user identity naturally denies at
29
+ * the tool (no matching tuples), and under `observe` it is audited.
26
30
  *
27
31
  * This authenticates the *user* (the human at the keyboard). The
28
32
  * separate agent identity (the AI process making the calls) is resolved
@@ -37,12 +41,6 @@ const cli_js_1 = require("./cli.js");
37
41
  const cli_invocation_js_1 = require("./cli-invocation.js");
38
42
  const auth_store_js_1 = require("./auth-store.js");
39
43
  const auth_js_1 = require("./auth.js");
40
- function isUserLoginEnabled() {
41
- // Resolves ORY_USER_LOGIN (env, when set) over the persisted
42
- // `userLogin` config flag. Centralized in resolveConfig so the gate,
43
- // status CLI, and configure command stay in lockstep.
44
- return (0, config_js_1.resolveConfig)().userLogin;
45
- }
46
44
  function clientId() {
47
45
  // resolveConfig already merges env (ORY_OAUTH2_CLIENT_ID) with the
48
46
  // persisted value, env winning. Centralizing here keeps the user gate,
@@ -71,15 +69,6 @@ function recordAuthSpan(client, decision) {
71
69
  * to authenticate the human user. Always returns a decision; never throws.
72
70
  */
73
71
  async function ensureUserAuthenticated(client, options) {
74
- if (!isUserLoginEnabled()) {
75
- const decision = {
76
- proceed: true,
77
- mode: "disabled",
78
- reason: "User login is not enabled (ORY_USER_LOGIN env / userLogin config)",
79
- };
80
- recordAuthSpan(client, decision);
81
- return decision;
82
- }
83
72
  try {
84
73
  const decision = await runUserLogin(client, options);
85
74
  attachUserPrincipal(client, decision);
@@ -88,7 +77,7 @@ async function ensureUserAuthenticated(client, options) {
88
77
  }
89
78
  catch (err) {
90
79
  const decision = {
91
- proceed: !options.allowBlock,
80
+ proceed: true,
92
81
  mode: "error",
93
82
  reason: err instanceof Error ? err.message : String(err),
94
83
  };
@@ -146,7 +135,7 @@ async function runUserLogin(client, options) {
146
135
  if (!resolved.projectUrl) {
147
136
  if (!tty) {
148
137
  return {
149
- proceed: !options.allowBlock,
138
+ proceed: true,
150
139
  mode: "skipped",
151
140
  reason: "No ORY_PROJECT_URL configured and no TTY to prompt",
152
141
  };
@@ -155,7 +144,7 @@ async function runUserLogin(client, options) {
155
144
  const url = await promptFn(options.binName);
156
145
  if (!url) {
157
146
  return {
158
- proceed: !options.allowBlock,
147
+ proceed: true,
159
148
  mode: "declined",
160
149
  reason: "User declined to enter an Ory project URL",
161
150
  };
@@ -163,7 +152,7 @@ async function runUserLogin(client, options) {
163
152
  resolved = (0, config_js_1.resolveConfig)();
164
153
  if (!resolved.projectUrl) {
165
154
  return {
166
- proceed: !options.allowBlock,
155
+ proceed: true,
167
156
  mode: "error",
168
157
  reason: "Project URL was entered but config did not persist",
169
158
  };
@@ -220,7 +209,7 @@ async function runUserLogin(client, options) {
220
209
  const cid = clientId();
221
210
  if (!cid) {
222
211
  return {
223
- proceed: !options.allowBlock,
212
+ proceed: true,
224
213
  mode: "skipped",
225
214
  reason: "ORY_OAUTH2_CLIENT_ID is not set; cannot start browser login (run `" +
226
215
  (0, cli_invocation_js_1.oryNpx)(options.binName) +
@@ -236,7 +225,8 @@ async function runUserLogin(client, options) {
236
225
  // complete sign-in by pasting the URL into any browser that can reach
237
226
  // 127.0.0.1. Truly unattended runs (CI=true) are short-circuited by
238
227
  // `pkceLogin` itself via `detectHeadless`, and operators can bypass
239
- // login entirely with `ORY_USER_SESSION_TOKEN` or `ORY_USER_LOGIN=false`.
228
+ // login entirely by supplying `ORY_USER_SESSION_TOKEN`, and enforcement
229
+ // consequences are governed by `permissionMode`, not by this gate.
240
230
  // PKCE-flight lock so concurrent processes share a single browser flow.
241
231
  const lock = (0, auth_store_js_1.tryAcquirePkceFlightLock)();
242
232
  if (!lock) {
@@ -251,7 +241,7 @@ async function runUserLogin(client, options) {
251
241
  };
252
242
  }
253
243
  return {
254
- proceed: !options.allowBlock,
244
+ proceed: true,
255
245
  mode: "skipped",
256
246
  reason: "Another agent process is mid-login; gave up waiting",
257
247
  };
@@ -272,7 +262,7 @@ async function runUserLogin(client, options) {
272
262
  };
273
263
  }
274
264
  return {
275
- proceed: !options.allowBlock,
265
+ proceed: true,
276
266
  mode: declineToMode(outcome.reason),
277
267
  reason: declineMessage(outcome.reason),
278
268
  };
@@ -0,0 +1,33 @@
1
+ /**
2
+ * JSON API for the local web installer / status dashboard. Each route is a
3
+ * thin wrapper over a headless action in {@link file://../setup-actions.ts} or
4
+ * the status data in {@link file://../status-data.ts}. All state for one
5
+ * install run (the resolved `ory` runner, the selected workspace/project, and
6
+ * the in-flight browser sub-flows) lives in a single {@link SetupSession} held
7
+ * by the server for its lifetime.
8
+ *
9
+ * Handlers never throw for expected failures — they return an `{ error }`
10
+ * envelope with an appropriate HTTP status so the SPA can surface it and the
11
+ * user can retry, exactly like the TTY wizard's fail-open steps.
12
+ */
13
+ import { type SetupActionDeps } from "../setup-actions.js";
14
+ import type { WebAppMode } from "./types.js";
15
+ export interface ApiResult {
16
+ status: number;
17
+ json: unknown;
18
+ }
19
+ export interface ApiContext {
20
+ mode: WebAppMode;
21
+ harness: string;
22
+ binName: string;
23
+ deps: SetupActionDeps;
24
+ /** Called when the SPA POSTs /api/shutdown. */
25
+ onShutdown: () => void;
26
+ }
27
+ /**
28
+ * Build the API request handler bound to one server run. Returns
29
+ * `handle(method, path, body)` producing an {@link ApiResult}.
30
+ */
31
+ export declare function createApi(ctx: ApiContext): {
32
+ handle: (method: string, path: string, body: Record<string, unknown>) => Promise<ApiResult>;
33
+ };
@@ -0,0 +1,294 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createApi = createApi;
4
+ /**
5
+ * JSON API for the local web installer / status dashboard. Each route is a
6
+ * thin wrapper over a headless action in {@link file://../setup-actions.ts} or
7
+ * the status data in {@link file://../status-data.ts}. All state for one
8
+ * install run (the resolved `ory` runner, the selected workspace/project, and
9
+ * the in-flight browser sub-flows) lives in a single {@link SetupSession} held
10
+ * by the server for its lifetime.
11
+ *
12
+ * Handlers never throw for expected failures — they return an `{ error }`
13
+ * envelope with an appropriate HTTP status so the SPA can surface it and the
14
+ * user can retry, exactly like the TTY wizard's fail-open steps.
15
+ */
16
+ const setup_actions_js_1 = require("../setup-actions.js");
17
+ const status_data_js_1 = require("../status-data.js");
18
+ const config_js_1 = require("../config.js");
19
+ const version_cli_js_1 = require("../version-cli.js");
20
+ const ok = (json = { ok: true }) => ({ status: 200, json });
21
+ const bad = (error, status = 400) => ({ status, json: { error } });
22
+ /**
23
+ * Build the API request handler bound to one server run. Returns
24
+ * `handle(method, path, body)` producing an {@link ApiResult}.
25
+ */
26
+ function createApi(ctx) {
27
+ const session = {
28
+ runner: ctx.deps.runner ?? null,
29
+ subject: null,
30
+ };
31
+ /** The runner to use, failing loudly if `POST /api/ory-cli` hasn't run. */
32
+ function runner() {
33
+ return session.runner;
34
+ }
35
+ async function handle(method, path, body) {
36
+ const route = `${method} ${path}`;
37
+ switch (route) {
38
+ case "GET /api/bootstrap": {
39
+ const cfg = (0, config_js_1.resolveConfig)();
40
+ const payload = {
41
+ mode: ctx.mode,
42
+ harness: ctx.harness,
43
+ binName: ctx.binName,
44
+ version: (0, version_cli_js_1.resolveCoreVersion)(),
45
+ alreadyConfigured: !!cfg.projectUrl || cfg.auditOnly,
46
+ auditOnly: cfg.auditOnly,
47
+ projectUrl: cfg.projectUrl,
48
+ };
49
+ return ok(payload);
50
+ }
51
+ case "GET /api/status":
52
+ return ok(await (0, status_data_js_1.collectStatusReport)(ctx.harness));
53
+ case "POST /api/ory-cli": {
54
+ const res = await (0, setup_actions_js_1.ensureOryCli)(ctx.deps);
55
+ session.runner = res.runner;
56
+ return ok({ present: res.present, installed: res.installed });
57
+ }
58
+ case "GET /api/signin": {
59
+ const r = runner();
60
+ return ok({ signedIn: r ? (0, setup_actions_js_1.isSignedIn)(r) : false });
61
+ }
62
+ case "POST /api/signin": {
63
+ const r = runner();
64
+ if (!r)
65
+ return bad("Ory CLI is not ready", 409);
66
+ if ((0, setup_actions_js_1.isSignedIn)(r))
67
+ return ok({ started: false, signedIn: true });
68
+ const handle = (0, setup_actions_js_1.startSignIn)(r);
69
+ const task = { handle, state: "running" };
70
+ session.signIn = task;
71
+ handle.done.then((success) => {
72
+ task.state = success ? "done" : "failed";
73
+ });
74
+ return ok({ started: true, signedIn: false });
75
+ }
76
+ case "GET /api/signin/status": {
77
+ const r = runner();
78
+ const task = session.signIn;
79
+ const state = task?.state ?? "idle";
80
+ // Treat a live session as done even if we never launched `ory auth`
81
+ // (the CLI may already have been signed in).
82
+ const signedIn = state === "done" || (!!r && (0, setup_actions_js_1.isSignedIn)(r));
83
+ return ok({ state: signedIn ? "done" : state, signedIn });
84
+ }
85
+ case "GET /api/workspaces": {
86
+ const r = runner();
87
+ if (!r)
88
+ return bad("Ory CLI is not ready", 409);
89
+ const workspaces = (0, setup_actions_js_1.listWorkspaces)(r);
90
+ if (workspaces === null)
91
+ return bad("Could not list workspaces", 502);
92
+ session.workspaces = workspaces;
93
+ return ok({ workspaces });
94
+ }
95
+ case "POST /api/workspaces": {
96
+ const r = runner();
97
+ if (!r)
98
+ return bad("Ory CLI is not ready", 409);
99
+ const name = str(body.name);
100
+ if (!name)
101
+ return bad("A workspace name is required");
102
+ const res = (0, setup_actions_js_1.createWorkspace)(r, name);
103
+ if (!res.ok)
104
+ return bad(res.error ?? "Could not create workspace", 502);
105
+ return ok({ workspace: res.value });
106
+ }
107
+ case "GET /api/projects": {
108
+ const r = runner();
109
+ if (!r)
110
+ return bad("Ory CLI is not ready", 409);
111
+ const workspaceId = str(body.workspaceId);
112
+ if (!workspaceId)
113
+ return bad("A workspaceId is required");
114
+ session.workspaceId = workspaceId;
115
+ session.workspaceName = session.workspaces?.find((w) => w.id === workspaceId)?.name;
116
+ const projects = (0, setup_actions_js_1.listProjects)(r, workspaceId);
117
+ if (projects === null)
118
+ return bad("Could not list projects", 502);
119
+ return ok({ projects, cap: setup_actions_js_1.PROJECT_CAP, canCreate: projects.length < setup_actions_js_1.PROJECT_CAP });
120
+ }
121
+ case "POST /api/projects": {
122
+ const r = runner();
123
+ if (!r)
124
+ return bad("Ory CLI is not ready", 409);
125
+ const workspaceId = str(body.workspaceId) ?? session.workspaceId;
126
+ const name = str(body.name);
127
+ if (!workspaceId || !name)
128
+ return bad("workspaceId and name are required");
129
+ const res = (0, setup_actions_js_1.createProject)(r, workspaceId, name);
130
+ if (!res.ok)
131
+ return bad(res.error ?? "Could not create project", 502);
132
+ return ok({ project: res.value });
133
+ }
134
+ case "POST /api/select-project": {
135
+ const r = runner();
136
+ if (!r)
137
+ return bad("Ory CLI is not ready", 409);
138
+ const project = body.project;
139
+ if (!project?.id)
140
+ return bad("A project is required");
141
+ const url = (0, setup_actions_js_1.resolveProjectUrl)(project);
142
+ if (!url.ok)
143
+ return bad(url.error ?? "Could not resolve project URL", 422);
144
+ session.project = project;
145
+ session.projectUrl = url.value;
146
+ // Ensure the OAuth2 login/consent UI is the Ory Managed Account
147
+ // Experience so the PKCE user login (step 7) renders a working page —
148
+ // a CLI-created project defaults these to the localhost:3000 sample.
149
+ const loginUi = (0, setup_actions_js_1.ensureManagedLoginUi)(r, project.id);
150
+ return ok({ projectUrl: url.value, dcrEnabled: (0, setup_actions_js_1.getDcrState)(r, project.id), loginUi });
151
+ }
152
+ case "POST /api/enable-dcr": {
153
+ const r = runner();
154
+ if (!r || !session.project)
155
+ return bad("No project selected", 409);
156
+ return ok({ ok: (0, setup_actions_js_1.enableDcrAction)(r, session.project.id) });
157
+ }
158
+ case "POST /api/oauth2-client": {
159
+ const r = runner();
160
+ if (!r || !session.project)
161
+ return bad("No project selected", 409);
162
+ const res = (0, setup_actions_js_1.provisionOAuth2Client)(r, session.project.id);
163
+ if (!res.ok || !res.clientId)
164
+ return bad("Could not provision the OAuth2 client", 502);
165
+ session.oauth2ClientId = res.clientId;
166
+ return ok({ clientId: res.clientId, reused: res.reused, name: res.name });
167
+ }
168
+ case "POST /api/save": {
169
+ if (!session.project || !session.projectUrl || !session.oauth2ClientId) {
170
+ return bad("Setup is incomplete (missing project / client)", 409);
171
+ }
172
+ (0, setup_actions_js_1.saveNetworkConfig)({
173
+ projectUrl: session.projectUrl,
174
+ projectId: session.project.id,
175
+ oauth2ClientId: session.oauth2ClientId,
176
+ projectName: session.project.name ?? session.project.slug,
177
+ workspaceName: session.workspaceName,
178
+ });
179
+ return ok();
180
+ }
181
+ case "POST /api/login": {
182
+ if (session.login?.state === "running")
183
+ return ok({ started: false });
184
+ // Pin the project the wizard resolved so a stale ORY_PROJECT_URL in the
185
+ // user's shell can't send the login to the wrong URL (e.g. :3000).
186
+ const task = {
187
+ promise: (0, setup_actions_js_1.runFullSetup)(ctx.binName, ctx.harness, ctx.deps, {
188
+ projectUrl: session.projectUrl,
189
+ oauth2ClientId: session.oauth2ClientId,
190
+ }),
191
+ state: "running",
192
+ };
193
+ session.login = task;
194
+ task.promise.then((result) => {
195
+ task.state = result.authenticated ? "done" : "failed";
196
+ task.result = result;
197
+ if (result.subject) {
198
+ session.subject = result.subject;
199
+ // The login only resolves an opaque subject id; look the identity up
200
+ // via the admin CLI to show a readable name (email) instead of a UUID.
201
+ const sid = "subjectId" in result.subject
202
+ ? result.subject.subjectId
203
+ : result.subject.subjectSet.object;
204
+ const r = session.runner;
205
+ if (r && session.project && sid) {
206
+ const name = (0, setup_actions_js_1.resolveIdentityName)(r, session.project.id, sid);
207
+ if (name)
208
+ task.result = { ...result, subjectLabel: name };
209
+ }
210
+ }
211
+ });
212
+ return ok({ started: true });
213
+ }
214
+ case "GET /api/login/status": {
215
+ const task = session.login;
216
+ return ok({
217
+ state: task?.state ?? "idle",
218
+ authenticated: task?.result?.authenticated ?? false,
219
+ subjectLabel: task?.result?.subjectLabel ?? null,
220
+ });
221
+ }
222
+ case "GET /api/permissions/model": {
223
+ const r = runner();
224
+ if (!r || !session.project)
225
+ return bad("No project selected", 409);
226
+ return ok(await (0, setup_actions_js_1.inspectPermissions)(r, session.project.id));
227
+ }
228
+ case "POST /api/permissions/provision": {
229
+ const r = runner();
230
+ if (!r || !session.project)
231
+ return bad("No project selected", 409);
232
+ const opl = str(body.opl);
233
+ return ok({ ok: (0, setup_actions_js_1.provisionPermissions)(r, session.project.id, undefined, opl) });
234
+ }
235
+ case "POST /api/permissions/grant": {
236
+ const r = runner();
237
+ if (!r || !session.project)
238
+ return bad("No project selected", 409);
239
+ if (!session.subject)
240
+ return bad("Sign in first so permissions can be granted to you", 409);
241
+ const res = (0, setup_actions_js_1.grantToolPermissions)(r, session.project.id, ctx.harness, session.subject);
242
+ return ok(res);
243
+ }
244
+ case "POST /api/api-key": {
245
+ if (!session.project || !session.projectUrl)
246
+ return bad("No project selected", 409);
247
+ const res = await (0, setup_actions_js_1.provisionProjectApiKey)({
248
+ projectId: session.project.id,
249
+ projectUrl: session.projectUrl,
250
+ harness: ctx.harness,
251
+ user: session.subject
252
+ ? (0, setup_actions_js_1.resourceUserLabel)(runner(), session.project.id, session.subject)
253
+ : undefined,
254
+ }, ctx.deps);
255
+ return ok(res);
256
+ }
257
+ case "POST /api/audit-only": {
258
+ (0, setup_actions_js_1.configureAuditOnly)();
259
+ return ok();
260
+ }
261
+ // Live toggle from the dashboard (permission posture).
262
+ case "POST /api/config": {
263
+ const patch = {};
264
+ if ("permissionMode" in body) {
265
+ if (body.permissionMode !== "observe" && body.permissionMode !== "enforce") {
266
+ return bad("permissionMode must be 'observe' or 'enforce'");
267
+ }
268
+ (0, setup_actions_js_1.setPermissionMode)(body.permissionMode);
269
+ patch.permissionMode = body.permissionMode;
270
+ }
271
+ if (Object.keys(patch).length === 0)
272
+ return bad("No supported config fields in request");
273
+ return ok(patch);
274
+ }
275
+ case "POST /api/local": {
276
+ const res = await (0, setup_actions_js_1.configureLocalStack)(ctx.deps);
277
+ // Record the local connection so the subsequent login pins to it too.
278
+ session.projectUrl = res.projectUrl;
279
+ session.oauth2ClientId = res.oauth2ClientId;
280
+ return ok(res);
281
+ }
282
+ case "POST /api/shutdown": {
283
+ ctx.onShutdown();
284
+ return ok();
285
+ }
286
+ default:
287
+ return bad(`Unknown route: ${route}`, 404);
288
+ }
289
+ }
290
+ return { handle };
291
+ }
292
+ function str(v) {
293
+ return typeof v === "string" && v.trim() ? v.trim() : undefined;
294
+ }
@@ -0,0 +1,11 @@
1
+ import type { SetupActionDeps } from "../setup-actions.js";
2
+ /**
3
+ * Heuristic for whether we can meaningfully open a browser for the user:
4
+ * an interactive TTY, not CI, not opted-out, and — on Linux — an actual
5
+ * display server. macOS/Windows are assumed to have a default browser.
6
+ */
7
+ export declare function canLaunchBrowser(): boolean;
8
+ /** Open the guided install/setup wizard in the browser. */
9
+ export declare function launchWebInstaller(binName: string, harness: string, deps?: SetupActionDeps): Promise<boolean>;
10
+ /** Open the status dashboard in the browser. */
11
+ export declare function launchWebDashboard(binName: string, harness: string, deps?: SetupActionDeps): Promise<boolean>;