@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.
- package/README.md +2 -1
- package/assets/commands/dashboard.md +34 -0
- package/assets/skills/ory-build-agent/SKILL.md +11 -13
- package/assets/skills/ory-e2b-sandbox/SKILL.md +0 -1
- package/assets/skills/ory-temporal-worker/SKILL.md +8 -10
- package/assets/skills/permissions-onboarding/SKILL.md +3 -3
- package/dist/adapters.d.ts +27 -4
- package/dist/adapters.js +132 -32
- package/dist/agent-auth.d.ts +34 -3
- package/dist/agent-auth.js +58 -8
- package/dist/auth.d.ts +7 -0
- package/dist/auth.js +90 -5
- package/dist/branding.d.ts +67 -0
- package/dist/branding.js +81 -0
- package/dist/build-info.json +4 -4
- package/dist/cli.d.ts +3 -3
- package/dist/cli.js +13 -52
- package/dist/client.d.ts +31 -0
- package/dist/client.js +58 -0
- package/dist/config.d.ts +48 -19
- package/dist/config.js +31 -26
- package/dist/contract-suite.d.ts +5 -3
- package/dist/contract-suite.js +13 -22
- package/dist/dashboard-cli.d.ts +8 -0
- package/dist/dashboard-cli.js +70 -0
- package/dist/dev.js +3 -4
- package/dist/index.d.ts +12 -5
- package/dist/index.js +41 -3
- package/dist/interactive-setup.d.ts +144 -23
- package/dist/interactive-setup.js +412 -224
- package/dist/local/health.d.ts +14 -0
- package/dist/local/health.js +46 -0
- package/dist/local/manager.js +1 -3
- package/dist/local/seed.d.ts +9 -20
- package/dist/local/seed.js +26 -18
- package/dist/permissions-cli.js +12 -4
- package/dist/project-api-key.d.ts +69 -0
- package/dist/project-api-key.js +147 -0
- package/dist/registry/manager.js +62 -18
- package/dist/setup-actions.d.ts +232 -0
- package/dist/setup-actions.js +507 -0
- package/dist/setup.d.ts +19 -0
- package/dist/setup.js +44 -14
- package/dist/skills.js +7 -0
- package/dist/status-cli.d.ts +2 -2
- package/dist/status-cli.js +4 -30
- package/dist/status-data.d.ts +96 -0
- package/dist/status-data.js +250 -0
- package/dist/status-system.d.ts +24 -0
- package/dist/status-system.js +56 -0
- package/dist/testing.d.ts +8 -0
- package/dist/testing.js +20 -1
- package/dist/uninstall.d.ts +33 -15
- package/dist/uninstall.js +65 -22
- package/dist/user-login.d.ts +16 -9
- package/dist/user-login.js +18 -28
- package/dist/web/api.d.ts +33 -0
- package/dist/web/api.js +294 -0
- package/dist/web/launch.d.ts +11 -0
- package/dist/web/launch.js +96 -0
- package/dist/web/server.d.ts +20 -0
- package/dist/web/server.js +233 -0
- package/dist/web/types.d.ts +65 -0
- package/dist/web/types.js +2 -0
- package/dist/webapp/assets/index-Wucl4SZs.css +1 -0
- package/dist/webapp/assets/index-m-GtEdq0.js +49 -0
- package/dist/webapp/favicon.ico +0 -0
- package/dist/webapp/index.html +15 -0
- 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
|
-
(
|
|
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.
|
|
458
|
-
ui.command(`${npx}
|
|
459
|
-
ui.hint(` ...or
|
|
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.
|
|
468
|
-
ui.command(`${npx}
|
|
469
|
-
ui.hint(` ...or
|
|
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.
|
|
488
|
-
ui.command("configure --
|
|
489
|
-
ui.hint(" ...or set it per-session via env
|
|
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
|
|
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);
|
package/dist/status-cli.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Render the "User identity" block.
|
|
3
|
-
*
|
|
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
|
/**
|