@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
@@ -0,0 +1,507 @@
1
+ "use strict";
2
+ /**
3
+ * Headless setup actions — the prompt-free, presentation-free core of the
4
+ * install walkthrough, driven by the local web installer's JSON API (and
5
+ * reusable anywhere the interactive TTY wizard's step *decisions* live outside
6
+ * the process, e.g. in a browser).
7
+ *
8
+ * Each function performs exactly one step of the same flow the TTY wizard in
9
+ * {@link file://./interactive-setup.ts} runs — sign in, pick / create a
10
+ * workspace and project, enable DCR, provision the OAuth2 client, grant tool
11
+ * permissions, provision the project API key — but returns a plain result
12
+ * object instead of prompting or printing. The heavy lifting (CLI arg shapes,
13
+ * OPL model inspection/merge, Console-API key minting) is imported from
14
+ * `interactive-setup.ts` so there is a single source of truth for it; only the
15
+ * thin, prompt-free orchestration lives here.
16
+ *
17
+ * Everything is fail-open: an action never throws for an expected failure
18
+ * (CLI error, network blip, missing session) — it returns `{ ok: false }` with
19
+ * a short message the UI can surface.
20
+ */
21
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
22
+ if (k2 === undefined) k2 = k;
23
+ var desc = Object.getOwnPropertyDescriptor(m, k);
24
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
25
+ desc = { enumerable: true, get: function() { return m[k]; } };
26
+ }
27
+ Object.defineProperty(o, k2, desc);
28
+ }) : (function(o, m, k, k2) {
29
+ if (k2 === undefined) k2 = k;
30
+ o[k2] = m[k];
31
+ }));
32
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
33
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
34
+ }) : function(o, v) {
35
+ o["default"] = v;
36
+ });
37
+ var __importStar = (this && this.__importStar) || (function () {
38
+ var ownKeys = function(o) {
39
+ ownKeys = Object.getOwnPropertyNames || function (o) {
40
+ var ar = [];
41
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
42
+ return ar;
43
+ };
44
+ return ownKeys(o);
45
+ };
46
+ return function (mod) {
47
+ if (mod && mod.__esModule) return mod;
48
+ var result = {};
49
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
50
+ __setModuleDefault(result, mod);
51
+ return result;
52
+ };
53
+ })();
54
+ Object.defineProperty(exports, "__esModule", { value: true });
55
+ exports.PROJECT_CAP = void 0;
56
+ exports.resolveNamespace = resolveNamespace;
57
+ exports.ensureOryCli = ensureOryCli;
58
+ exports.isSignedIn = isSignedIn;
59
+ exports.startSignIn = startSignIn;
60
+ exports.listWorkspaces = listWorkspaces;
61
+ exports.createWorkspace = createWorkspace;
62
+ exports.listProjects = listProjects;
63
+ exports.createProject = createProject;
64
+ exports.resolveProjectUrl = resolveProjectUrl;
65
+ exports.getDcrState = getDcrState;
66
+ exports.enableDcrAction = enableDcrAction;
67
+ exports.ensureManagedLoginUi = ensureManagedLoginUi;
68
+ exports.resolveIdentityName = resolveIdentityName;
69
+ exports.resourceUserLabel = resourceUserLabel;
70
+ exports.provisionOAuth2Client = provisionOAuth2Client;
71
+ exports.saveNetworkConfig = saveNetworkConfig;
72
+ exports.runFullSetup = runFullSetup;
73
+ exports.inspectPermissions = inspectPermissions;
74
+ exports.provisionPermissions = provisionPermissions;
75
+ exports.grantToolPermissions = grantToolPermissions;
76
+ exports.provisionProjectApiKey = provisionProjectApiKey;
77
+ exports.configureAuditOnly = configureAuditOnly;
78
+ exports.setPermissionMode = setPermissionMode;
79
+ exports.configureLocalStack = configureLocalStack;
80
+ const node_child_process_1 = require("node:child_process");
81
+ const interactive_setup_js_1 = require("./interactive-setup.js");
82
+ const project_api_key_js_1 = require("./project-api-key.js");
83
+ const config_js_1 = require("./config.js");
84
+ const branding_js_1 = require("./branding.js");
85
+ const auth_js_1 = require("./auth.js");
86
+ const auth_store_js_1 = require("./auth-store.js");
87
+ const tool_catalog_js_1 = require("./tool-catalog.js");
88
+ const user_login_js_1 = require("./user-login.js");
89
+ const agent_auth_js_1 = require("./agent-auth.js");
90
+ const client_js_1 = require("./client.js");
91
+ const subject_js_1 = require("./subject.js");
92
+ const index_js_1 = require("./local/index.js");
93
+ const os = __importStar(require("node:os"));
94
+ function resolveNamespace() {
95
+ return process.env.ORY_PERMISSION_NAMESPACE ?? "AgentTools";
96
+ }
97
+ /**
98
+ * Ensure the `ory` CLI is available. Probes the default (or injected) runner;
99
+ * if the binary isn't on `PATH`, installs `@ory/cli` into a plugin-managed dir
100
+ * and returns a runner bound to it. Never throws.
101
+ */
102
+ async function ensureOryCli(deps = {}) {
103
+ const runner = deps.runner ?? (0, interactive_setup_js_1.createOryRunner)("ory");
104
+ if ((0, interactive_setup_js_1.oryCliPresent)(runner))
105
+ return { present: true, installed: false, runner };
106
+ const install = deps.installOryCliFn ?? interactive_setup_js_1.installOryCli;
107
+ let installed;
108
+ try {
109
+ installed = await install();
110
+ }
111
+ catch {
112
+ installed = null;
113
+ }
114
+ return { present: !!installed, installed: !!installed, runner: installed };
115
+ }
116
+ // ─── sign in ──────────────────────────────────────────────────────────────
117
+ /** Whether the `ory` CLI already holds a valid Ory Network session. */
118
+ function isSignedIn(runner) {
119
+ return runner.exec(["list", "workspaces", "--format", "json"]).status === 0;
120
+ }
121
+ /**
122
+ * Launch the interactive `ory auth` browser sign-in as a detached child so the
123
+ * HTTP server's event loop stays free (unlike `runner.exec`, which is
124
+ * spawnSync). The Ory CLI opens its own browser window and runs a local
125
+ * callback listener; we only watch its exit code. Returns a handle whose
126
+ * `done` promise resolves when sign-in completes. Requires `runner.bin`.
127
+ */
128
+ function startSignIn(runner) {
129
+ const bin = runner.bin;
130
+ if (!bin) {
131
+ return { done: Promise.resolve(false), cancel() { } };
132
+ }
133
+ const child = (0, node_child_process_1.spawn)(bin, ["auth"], { stdio: "ignore" });
134
+ const done = new Promise((resolve) => {
135
+ child.on("exit", (code) => resolve(code === 0));
136
+ child.on("error", () => resolve(false));
137
+ });
138
+ return { done, cancel: () => child.kill() };
139
+ }
140
+ // ─── workspaces & projects ──────────────────────────────────────────────────
141
+ function listWorkspaces(runner) {
142
+ const r = runner.exec(["list", "workspaces", "--format", "json"]);
143
+ if (r.status !== 0)
144
+ return null;
145
+ return (0, interactive_setup_js_1.parseList)(r.stdout, "workspaces");
146
+ }
147
+ function createWorkspace(runner, name) {
148
+ const r = runner.exec(["create", "workspace", "--name", name, "--format", "json", "--yes"]);
149
+ if (r.status !== 0)
150
+ return { ok: false, error: r.stderr.trim() || "unknown error" };
151
+ const workspace = (0, interactive_setup_js_1.parseObject)(r.stdout);
152
+ if (!workspace?.id)
153
+ return { ok: false, error: "created workspace had no id" };
154
+ return { ok: true, value: workspace };
155
+ }
156
+ function listProjects(runner, workspaceId) {
157
+ return (0, interactive_setup_js_1.listProjects)(runner, workspaceId);
158
+ }
159
+ function createProject(runner, workspaceId, name) {
160
+ const r = runner.exec([
161
+ "create",
162
+ "project",
163
+ "--name",
164
+ name,
165
+ "--workspace",
166
+ workspaceId,
167
+ "--format",
168
+ "json",
169
+ "--yes",
170
+ ]);
171
+ if (r.status !== 0)
172
+ return { ok: false, error: r.stderr.trim() || "unknown error" };
173
+ const project = (0, interactive_setup_js_1.parseObject)(r.stdout);
174
+ if (!project?.id)
175
+ return { ok: false, error: "created project had no id" };
176
+ return { ok: true, value: project };
177
+ }
178
+ /** Ory Network Developer-plan project cap per workspace. */
179
+ exports.PROJECT_CAP = 2;
180
+ /**
181
+ * Resolve a project's canonical SDK URL from its slug, refusing a missing slug
182
+ * or one that is actually the project id (a UUID) — either would build a URL
183
+ * that points nowhere.
184
+ */
185
+ function resolveProjectUrl(project) {
186
+ if (!project.slug || (0, config_js_1.looksLikeProjectId)(project.slug)) {
187
+ return { ok: false, error: "could not resolve the project slug (needed for the SDK URL)" };
188
+ }
189
+ try {
190
+ return { ok: true, value: (0, interactive_setup_js_1.projectUrlFromSlug)(project.slug) };
191
+ }
192
+ catch (err) {
193
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
194
+ }
195
+ }
196
+ // ─── DCR & OAuth2 client ──────────────────────────────────────────────────
197
+ /** Current DCR state: true/false when known, null when it couldn't be read. */
198
+ function getDcrState(runner, projectId) {
199
+ return (0, interactive_setup_js_1.dcrEnabled)(runner, projectId);
200
+ }
201
+ /** Enable DCR on the project. Returns true on success. */
202
+ function enableDcrAction(runner, projectId) {
203
+ return (0, interactive_setup_js_1.enableDcr)(runner, projectId);
204
+ }
205
+ /**
206
+ * Ensure the project's OAuth2 login/consent UI works for a hosted browser
207
+ * login. A CLI-created project defaults these to the `localhost:3000`
208
+ * quickstart sample; this clears them so the Ory Managed Account Experience
209
+ * renders the login instead. Returns the resulting state so the UI can report
210
+ * it: `managed` (fixed or already managed), `custom_external` (a deliberate
211
+ * custom UI, left as-is), or `unknown` (couldn't read the config).
212
+ */
213
+ function ensureManagedLoginUi(runner, projectId) {
214
+ const state = (0, interactive_setup_js_1.projectLoginUiState)(runner, projectId);
215
+ if (state === "loopback") {
216
+ return (0, interactive_setup_js_1.useManagedAccountExperience)(runner, projectId) ? "managed" : "unknown";
217
+ }
218
+ return state;
219
+ }
220
+ /**
221
+ * Best-effort human-readable name for a signed-in identity, resolved via the
222
+ * admin `ory` CLI (`ory get identity`). The PKCE login's id_token carries only
223
+ * an opaque subject id under the default `openid` scope, so rather than show a
224
+ * UUID we look the identity up by id and read a display trait (email → username
225
+ * → name). Returns undefined on any CLI/parse failure so callers fall back to
226
+ * the id.
227
+ */
228
+ function resolveIdentityName(runner, projectId, subjectId) {
229
+ const r = runner.exec([
230
+ "get",
231
+ "identity",
232
+ subjectId,
233
+ "--project",
234
+ projectId,
235
+ "--format",
236
+ "json",
237
+ ]);
238
+ if (r.status !== 0)
239
+ return undefined;
240
+ const identity = (0, interactive_setup_js_1.parseObject)(r.stdout);
241
+ return displayNameFromTraits(identity?.traits);
242
+ }
243
+ /** Pull a display name out of a Kratos identity's traits (best-effort). */
244
+ function displayNameFromTraits(traits) {
245
+ if (!traits || typeof traits !== "object")
246
+ return undefined;
247
+ const t = traits;
248
+ if (typeof t.email === "string" && t.email.trim())
249
+ return t.email.trim();
250
+ if (typeof t.username === "string" && t.username.trim())
251
+ return t.username.trim();
252
+ const name = t.name;
253
+ if (typeof name === "string" && name.trim())
254
+ return name.trim();
255
+ if (name && typeof name === "object") {
256
+ const n = name;
257
+ const parts = [n.first, n.last].filter((x) => typeof x === "string" && x.trim().length > 0);
258
+ if (parts.length)
259
+ return parts.join(" ");
260
+ }
261
+ return undefined;
262
+ }
263
+ /**
264
+ * Readable label for a signed-in user when naming a resource (e.g. a project
265
+ * API key), so a shared project's resource list shows an email/username rather
266
+ * than an opaque `user:<uuid>`. Looks the identity up via the admin CLI and
267
+ * falls back to the subject label when no name resolves (no runner, or the
268
+ * lookup fails). The raw subject id is read from either subject shape.
269
+ */
270
+ function resourceUserLabel(runner, projectId, subject) {
271
+ const sid = "subjectId" in subject ? subject.subjectId : subject.subjectSet.object;
272
+ const name = runner ? resolveIdentityName(runner, projectId, sid) : undefined;
273
+ return name ?? (0, subject_js_1.subjectLabel)(subject);
274
+ }
275
+ /**
276
+ * Find or create the public PKCE OAuth2 client the user login needs. Reuses an
277
+ * existing plugin-provisioned client on the project so a re-run never mints a
278
+ * duplicate.
279
+ */
280
+ function provisionOAuth2Client(runner, projectId) {
281
+ const existing = (0, interactive_setup_js_1.findExistingOAuth2Client)(runner, projectId);
282
+ if (existing)
283
+ return { ok: true, clientId: existing, name: branding_js_1.USER_LOGIN_CLIENT_NAME, reused: true };
284
+ const clientId = (0, interactive_setup_js_1.createOAuth2Client)(runner, projectId);
285
+ return {
286
+ ok: !!clientId,
287
+ clientId: clientId ?? undefined,
288
+ name: branding_js_1.USER_LOGIN_CLIENT_NAME,
289
+ reused: false,
290
+ };
291
+ }
292
+ /** Persist the resolved Ory Network connection and enable the user login. */
293
+ function saveNetworkConfig(input) {
294
+ (0, config_js_1.saveConfig)({
295
+ projectUrl: input.projectUrl,
296
+ projectId: input.projectId,
297
+ projectName: input.projectName,
298
+ workspaceName: input.workspaceName,
299
+ oauth2ClientId: input.oauth2ClientId,
300
+ auditOnly: false,
301
+ });
302
+ }
303
+ /**
304
+ * Run the PKCE user login (opens a browser and awaits the loopback callback)
305
+ * and resolve the agent DCR identity, so the next session is fully wired.
306
+ * Best-effort: a declined or failed login just reports `authenticated: false`.
307
+ *
308
+ * `target` pins the project the wizard just selected. The login gate resolves
309
+ * its URL/client id through `resolveConfig()`, which is **env-first** — so a
310
+ * stale `ORY_PROJECT_URL` / `ORY_SDK_URL` / `ORY_OAUTH2_CLIENT_ID` left in the
311
+ * user's shell (e.g. `http://localhost:3000` from a local app) would otherwise
312
+ * override the project they just picked and launch the browser at the wrong
313
+ * URL. During install the fresh selection is ground truth, so we pin it into
314
+ * the environment for the login. (The process exits after install, so this
315
+ * doesn't leak into a long-lived session.)
316
+ */
317
+ async function runFullSetup(binName, harness, deps = {}, target = {}) {
318
+ const clientFactory = deps.clientFactory ?? client_js_1.OryAgentClient.fromEnv;
319
+ const userLogin = deps.userLoginFn ?? user_login_js_1.ensureUserAuthenticated;
320
+ const agentGate = deps.agentGateFn ?? agent_auth_js_1.ensureAgentIdentity;
321
+ // Scoped to the login call (restored after) so a stale ORY_PROJECT_URL /
322
+ // ORY_OAUTH2_CLIENT_ID can't override the selected project, and the override
323
+ // doesn't leak past the login — later steps address the project by id.
324
+ const prevUrl = process.env.ORY_PROJECT_URL;
325
+ const prevClient = process.env.ORY_OAUTH2_CLIENT_ID;
326
+ if (target.projectUrl)
327
+ process.env.ORY_PROJECT_URL = target.projectUrl;
328
+ if (target.oauth2ClientId)
329
+ process.env.ORY_OAUTH2_CLIENT_ID = target.oauth2ClientId;
330
+ try {
331
+ const client = clientFactory(harness);
332
+ const decision = await userLogin(client, { binName, harness });
333
+ await agentGate(client, { harness });
334
+ const authenticated = decision.proceed && !!decision.subject;
335
+ const subject = authenticated ? (0, subject_js_1.resolveUserSubject)(client) : null;
336
+ const usable = subject && (0, subject_js_1.subjectLabel)(subject) !== "agent:unknown" ? subject : null;
337
+ // Prefer a human-readable name (email/name) from the login's id_token over
338
+ // the opaque subject id, so the UI shows who signed in rather than a UUID.
339
+ const displayName = usable ? (0, auth_js_1.displayNameFromIdToken)((0, auth_store_js_1.loadTokens)()?.idToken) : undefined;
340
+ return {
341
+ authenticated,
342
+ subject: usable,
343
+ subjectLabel: usable ? (displayName ?? (0, subject_js_1.subjectLabel)(usable)) : null,
344
+ };
345
+ }
346
+ catch {
347
+ return { authenticated: false, subject: null, subjectLabel: null };
348
+ }
349
+ finally {
350
+ if (prevUrl === undefined)
351
+ delete process.env.ORY_PROJECT_URL;
352
+ else
353
+ process.env.ORY_PROJECT_URL = prevUrl;
354
+ if (prevClient === undefined)
355
+ delete process.env.ORY_OAUTH2_CLIENT_ID;
356
+ else
357
+ process.env.ORY_OAUTH2_CLIENT_ID = prevClient;
358
+ }
359
+ }
360
+ /** Inspect how the project's permission model relates to the namespace. */
361
+ async function inspectPermissions(runner, projectId, namespace = resolveNamespace()) {
362
+ const model = await (0, interactive_setup_js_1.inspectPermissionModel)(runner, projectId, namespace);
363
+ return model.kind === "merge" ? { model: "merge", opl: model.opl } : { model: model.kind };
364
+ }
365
+ /**
366
+ * Provision (or merge) the namespace into the project's permission model.
367
+ * Pass `opl` (from a prior `inspectPermissions` "merge" result) to append the
368
+ * namespace non-destructively; omit it to create a fresh minimal model.
369
+ */
370
+ function provisionPermissions(runner, projectId, namespace = resolveNamespace(), opl) {
371
+ return (0, interactive_setup_js_1.provisionPermissionModel)(runner, projectId, namespace, opl);
372
+ }
373
+ /**
374
+ * Grant `<subject>` the `use` relation on every built-in tool by writing the
375
+ * relation tuples through the admin-authenticated `ory` CLI. Assumes the
376
+ * namespace already exists (call {@link inspectPermissions} /
377
+ * {@link provisionPermissions} first).
378
+ */
379
+ function grantToolPermissions(runner, projectId, harness, subject, namespace = resolveNamespace()) {
380
+ const tools = (0, tool_catalog_js_1.getToolCatalog)(harness);
381
+ if (tools.length === 0)
382
+ return { ok: true, count: 0 };
383
+ const subjectPatch = "subjectSet" in subject
384
+ ? {
385
+ subject_set: {
386
+ namespace: subject.subjectSet.namespace,
387
+ object: subject.subjectSet.object,
388
+ relation: subject.subjectSet.relation,
389
+ },
390
+ }
391
+ : { subject_id: subject.subjectId };
392
+ const tuples = tools.map((object) => ({ namespace, object, relation: "use", ...subjectPatch }));
393
+ const r = runner.exec(["create", "relationships", "--project", projectId, "--format", "json", "-f", "-", "--yes"], { input: JSON.stringify(tuples) });
394
+ if (r.status === 0)
395
+ return { ok: true, count: tools.length };
396
+ return { ok: false, count: 0, error: r.stderr.trim().split("\n")[0] || "unknown error" };
397
+ }
398
+ /**
399
+ * Ensure a valid project API key is stored for runtime permission *checks*.
400
+ * No-ops for an env-provided key; validates a stored key and re-mints if it is
401
+ * dead; otherwise mints one via the Console API and persists it. Best-effort.
402
+ */
403
+ async function provisionProjectApiKey(input, deps = {}) {
404
+ const createFn = deps.createProjectApiKeyFn ?? project_api_key_js_1.createProjectApiKeyViaConsole;
405
+ const validateFn = deps.validateApiKeyFn ?? interactive_setup_js_1.apiKeyAuthenticates;
406
+ const resolved = (0, config_js_1.resolveConfig)();
407
+ if (resolved.apiKey) {
408
+ if (resolved.apiKeySource === "env")
409
+ return { status: "env" };
410
+ if (await validateFn(resolved.apiKey, input.projectUrl))
411
+ return { status: "valid_existing" };
412
+ }
413
+ let host = "unknown-host";
414
+ try {
415
+ host = os.hostname();
416
+ }
417
+ catch {
418
+ /* keep fallback */
419
+ }
420
+ let created = null;
421
+ try {
422
+ // Named for the signed-in user + host so a shared project's key list (every
423
+ // user mints their own) stays attributable and prunable; the Console's
424
+ // "Date Added" column supplies the timestamp.
425
+ created = await createFn({
426
+ projectId: input.projectId,
427
+ name: (0, branding_js_1.projectApiKeyName)({ host, user: input.user }),
428
+ });
429
+ }
430
+ catch {
431
+ created = null;
432
+ }
433
+ if (created) {
434
+ // Persist the token id too so uninstall can delete the key server-side.
435
+ (0, config_js_1.saveConfig)({ apiKey: created.value, apiKeyId: created.id ?? null });
436
+ return { status: "created" };
437
+ }
438
+ return { status: "failed" };
439
+ }
440
+ // ─── audit-only & local stack ─────────────────────────────────────────────
441
+ /** Configure audit-only mode (no project, no auth, no checks). */
442
+ function configureAuditOnly() {
443
+ (0, config_js_1.saveConfig)({ auditOnly: true });
444
+ }
445
+ // ─── dashboard toggles ────────────────────────────────────────────────────
446
+ /** Set the permission deny posture — `observe` (log) vs `enforce` (block). */
447
+ function setPermissionMode(mode) {
448
+ (0, config_js_1.saveConfig)({ permissionMode: mode });
449
+ }
450
+ /**
451
+ * Bring up and seed the local Ory stack, persisting the (stable) local
452
+ * connection details regardless of whether Docker came up. Mirrors the TTY
453
+ * wizard's local branch minus the login (the UI drives {@link runFullSetup}
454
+ * separately). Best-effort throughout.
455
+ */
456
+ async function configureLocalStack(deps = {}) {
457
+ const bringUp = deps.localStackFn ?? index_js_1.ensureLocalOryStack;
458
+ const seed = deps.seedFn ?? index_js_1.seedLocalEnvironment;
459
+ let status;
460
+ let detail;
461
+ try {
462
+ const stack = await bringUp();
463
+ status = stack.status;
464
+ detail = stack.detail;
465
+ }
466
+ catch (err) {
467
+ status = "compose-failed";
468
+ detail = err instanceof Error ? err.message : String(err);
469
+ }
470
+ const running = status === "started" || status === "already-running";
471
+ (0, config_js_1.saveConfig)({
472
+ projectUrl: index_js_1.GATEWAY_URL,
473
+ oauth2ClientId: index_js_1.USER_CLIENT_ID,
474
+ userSubjectNamespace: index_js_1.USER_SUBJECT_NAMESPACE,
475
+ auditOnly: false,
476
+ // The local stack is not an Ory Network project — clear any workspace /
477
+ // project identifiers left over from a previous Network connection so they
478
+ // don't linger in config or surface in the dashboard (null deletes the key).
479
+ projectId: null,
480
+ projectName: null,
481
+ workspaceName: null,
482
+ });
483
+ const base = {
484
+ running,
485
+ detail: running ? undefined : detail ?? status,
486
+ seeded: false,
487
+ projectUrl: index_js_1.GATEWAY_URL,
488
+ oauth2ClientId: index_js_1.USER_CLIENT_ID,
489
+ subjectNamespace: index_js_1.USER_SUBJECT_NAMESPACE,
490
+ };
491
+ if (!running)
492
+ return base;
493
+ try {
494
+ const result = await seed();
495
+ return {
496
+ ...base,
497
+ seeded: true,
498
+ credentials: {
499
+ email: result.user.identity.email,
500
+ password: result.user.password,
501
+ },
502
+ };
503
+ }
504
+ catch (err) {
505
+ return { ...base, seedError: err instanceof Error ? err.message : String(err) };
506
+ }
507
+ }
package/dist/setup.d.ts CHANGED
@@ -35,6 +35,25 @@ export declare function isOryHookCommand(h: HookCommand, binName: string): boole
35
35
  * Tries require.resolve first, falls back to the global bin name.
36
36
  */
37
37
  export declare function resolveHookCommand(packageName: string, binName: string): string;
38
+ /**
39
+ * Pin an `npx` hook command to the local dev registry when the plugin is being
40
+ * installed against it.
41
+ *
42
+ * A runtime hook of `npx -y -p @ory/<h>[@ver] <bin>` re-resolves the plugin on
43
+ * every invocation. If the harness is launched by hand (not via
44
+ * `pnpm dev:<harness>`, which sets `npm_config_registry`), that npx defaults to
45
+ * npmjs.org and runs the last *published* release — so local changes never take
46
+ * effect. When the install itself is running against the local Verdaccio
47
+ * registry (detected via `npm_config_registry`, set by both `npx --registry
48
+ * <local>` and the dev launcher), this stamps `--registry <local>
49
+ * --min-release-age=0` into the hook so it resolves the freshly published dev
50
+ * build regardless of how the harness is started.
51
+ *
52
+ * A no-op for production installs (npmjs / unset registry) and for non-`npx`
53
+ * hook commands (e.g. a `node "<path>"` command already runs resolved local
54
+ * code), so shipped hooks are never altered.
55
+ */
56
+ export declare function pinHookToLocalRegistry(hookCommand: string): string;
38
57
  /**
39
58
  * Build a matcher-based hook entry: `[{ matcher: "", hooks: [{ type: "command", command }] }]`
40
59
  */
package/dist/setup.js CHANGED
@@ -38,6 +38,7 @@ exports.readJsonFile = readJsonFile;
38
38
  exports.writeJsonFile = writeJsonFile;
39
39
  exports.isOryHookCommand = isOryHookCommand;
40
40
  exports.resolveHookCommand = resolveHookCommand;
41
+ exports.pinHookToLocalRegistry = pinHookToLocalRegistry;
41
42
  exports.matcherHookEntry = matcherHookEntry;
42
43
  exports.mergeMatcherHooks = mergeMatcherHooks;
43
44
  exports.removeMatcherHooks = removeMatcherHooks;
@@ -60,6 +61,7 @@ const os = __importStar(require("node:os"));
60
61
  const path = __importStar(require("node:path"));
61
62
  const cli_invocation_js_1 = require("./cli-invocation.js");
62
63
  const config_js_1 = require("./config.js");
64
+ const config_js_2 = require("./registry/config.js");
63
65
  const ui = __importStar(require("./ui.js"));
64
66
  // ─── Arg Parsing ───────────────────────────────────────────────────
65
67
  /**
@@ -139,6 +141,37 @@ function resolveHookCommand(packageName, binName) {
139
141
  return binName;
140
142
  }
141
143
  }
144
+ /**
145
+ * Pin an `npx` hook command to the local dev registry when the plugin is being
146
+ * installed against it.
147
+ *
148
+ * A runtime hook of `npx -y -p @ory/<h>[@ver] <bin>` re-resolves the plugin on
149
+ * every invocation. If the harness is launched by hand (not via
150
+ * `pnpm dev:<harness>`, which sets `npm_config_registry`), that npx defaults to
151
+ * npmjs.org and runs the last *published* release — so local changes never take
152
+ * effect. When the install itself is running against the local Verdaccio
153
+ * registry (detected via `npm_config_registry`, set by both `npx --registry
154
+ * <local>` and the dev launcher), this stamps `--registry <local>
155
+ * --min-release-age=0` into the hook so it resolves the freshly published dev
156
+ * build regardless of how the harness is started.
157
+ *
158
+ * A no-op for production installs (npmjs / unset registry) and for non-`npx`
159
+ * hook commands (e.g. a `node "<path>"` command already runs resolved local
160
+ * code), so shipped hooks are never altered.
161
+ */
162
+ function pinHookToLocalRegistry(hookCommand) {
163
+ const normalize = (u) => u.replace(/\/+$/, "");
164
+ const active = process.env.npm_config_registry?.trim();
165
+ if (!active || normalize(active) !== normalize(config_js_2.REGISTRY_URL))
166
+ return hookCommand;
167
+ const trimmed = hookCommand.trimStart();
168
+ if (!/^npx(\s|$)/.test(trimmed))
169
+ return hookCommand;
170
+ if (trimmed.includes("--registry"))
171
+ return hookCommand;
172
+ // Insert the registry flags right after `npx` (and its optional `-y`).
173
+ return hookCommand.replace(/^(\s*npx(?:\s+-y)?)(\s)/, `$1 --registry ${config_js_2.REGISTRY_URL} --min-release-age=0$2`);
174
+ }
142
175
  // ─── Matcher-Based Hooks (Claude Code, Gemini CLI) ─────────────────
143
176
  /**
144
177
  * Build a matcher-based hook entry: `[{ matcher: "", hooks: [{ type: "command", command }] }]`
@@ -381,9 +414,7 @@ Environment variables:
381
414
  ORY_AGENT_API_KEY Agent API key / OAuth2 bearer (preferred name;
382
415
  ORY_API_KEY is honored as a deprecated alias)
383
416
  ORY_OAUTH2_CLIENT_ID Public OAuth2 client id for the user PKCE flow
384
- (required when ORY_USER_LOGIN=true)
385
- ORY_USER_LOGIN Set to "true" to enable the interactive user login
386
- (or persist it with: configure --user-login)
417
+ (the interactive user login runs every session)
387
418
  ORY_PERMISSION_MODE "observe" (default) or "enforce" — what to do on deny
388
419
  ORY_AGENT_DEBUG Set to "true" for debug logging
389
420
  ORY_AGENT_LOG_FILE Path to write debug logs
@@ -454,9 +485,9 @@ function printConfiguredNextStepsNow(harnessName, uninstallCmd, opts) {
454
485
  if (auditOnly) {
455
486
  ui.hint("Audit-only mode: tool calls are traced locally — no checks run, nothing is blocked.");
456
487
  ui.blank();
457
- ui.bullet("1. See what's been traced:");
458
- ui.command(`${npx} status`);
459
- ui.hint(` ...or watch it live: ${ui.inlineCommand(`${npx} watch`)}`);
488
+ ui.bullet("1. Open the dashboard — a live view of config, permissions, and activity:");
489
+ ui.command(`${npx} dashboard`);
490
+ ui.hint(` ...or from the terminal: ${ui.inlineCommand(`${npx} status`)} / ${ui.inlineCommand(`${npx} watch`)}`);
460
491
  ui.blank();
461
492
  ui.bullet("2. Want more detail? Turn on debug logging:");
462
493
  ui.command("export ORY_AGENT_DEBUG=true");
@@ -464,9 +495,9 @@ function printConfiguredNextStepsNow(harnessName, uninstallCmd, opts) {
464
495
  else {
465
496
  ui.hint("It starts in watch mode: every tool call is checked, but nothing is blocked yet.");
466
497
  ui.blank();
467
- ui.bullet("1. See what Ory is doing config, permission coverage, recent activity:");
468
- ui.command(`${npx} status`);
469
- ui.hint(` ...or watch the trace stream live: ${ui.inlineCommand(`${npx} watch`)}`);
498
+ ui.bullet("1. Open the dashboard a live view of config, permission coverage, and activity:");
499
+ ui.command(`${npx} dashboard`);
500
+ ui.hint(` ...or from the terminal: ${ui.inlineCommand(`${npx} status`)} / ${ui.inlineCommand(`${npx} watch`)}`);
470
501
  ui.blank();
471
502
  ui.bullet("2. Turn on enforcement once the watch-mode traces look right:");
472
503
  ui.command(`${npx} permissions enforce`);
@@ -484,12 +515,11 @@ function printNextStepsNow(harnessName, uninstallCmd) {
484
515
  ui.command("export ORY_PROJECT_URL=https://your-project.projects.oryapis.com");
485
516
  ui.command("export ORY_AGENT_API_KEY=ory_pat_...");
486
517
  ui.blank();
487
- ui.bullet("2. Turn on the interactive user login (opt-in). Either persist it:");
488
- ui.command("configure --user-login --oauth2-client-id <public OAuth2 client id>");
489
- ui.hint(" ...or set it per-session via env vars:");
490
- ui.command("export ORY_USER_LOGIN=true");
518
+ ui.bullet("2. Provide the public OAuth2 client id for the user login (runs every session):");
519
+ ui.command("configure --oauth2-client-id <public OAuth2 client id>");
520
+ ui.hint(" ...or set it per-session via an env var:");
491
521
  ui.command("export ORY_OAUTH2_CLIENT_ID=<public OAuth2 client id>");
492
- ui.hint(` Without these, ${harnessName} runs without a human Ory identity attached`);
522
+ ui.hint(` Without it, ${harnessName} runs without a human Ory identity attached`);
493
523
  ui.hint(" to the session and permission checks fall back to a session:<id> subject.");
494
524
  ui.hint(" The OAuth2 client id is the public client (no secret) you registered in");
495
525
  ui.hint(" your Ory project with the four loopback redirect URIs.");
package/dist/skills.js CHANGED
@@ -122,6 +122,13 @@ const COMMAND_SOURCES = [
122
122
  description: "Start the local Temporal TypeScript dev server (Temporal Server + Web UI) for the Ory-authed worker scaffold.",
123
123
  file: "commands/temporal-up.md",
124
124
  },
125
+ {
126
+ id: "dashboard",
127
+ name: "ory-dashboard",
128
+ slug: "dashboard",
129
+ description: "Open a live browser dashboard of the plugin's configuration and the local Ory stack's service health.",
130
+ file: "commands/dashboard.md",
131
+ },
125
132
  ];
126
133
  /** Names of the skills materialized by the plugins (for uninstall cleanup). */
127
134
  exports.ORY_SKILL_NAMES = SKILL_SOURCES.map((s) => s.name);
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Render the "User identity" block. Shows the `ORY_USER_LOGIN` state and,
3
- * when a PKCE token is cached, the resolved subject and expiry.
2
+ * Render the "User identity" block. The interactive PKCE login runs every
3
+ * session; this shows the cached token's subject and expiry, if any.
4
4
  */
5
5
  export declare function printUserIdentitySection(): void;
6
6
  /**