@openape/apes 0.6.0 → 0.7.1
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 +206 -0
- package/dist/auth-lock-SRUFWJC3.js +41 -0
- package/dist/auth-lock-SRUFWJC3.js.map +1 -0
- package/dist/chunk-B32ZQP5K.js +1280 -0
- package/dist/chunk-B32ZQP5K.js.map +1 -0
- package/dist/{chunk-KVBHBOED.js → chunk-ION3CWD5.js} +14 -2
- package/dist/chunk-ION3CWD5.js.map +1 -0
- package/dist/chunk-TBYYREL6.js +133 -0
- package/dist/chunk-TBYYREL6.js.map +1 -0
- package/dist/cli.js +927 -246
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +216 -2
- package/dist/index.js +32 -24
- package/dist/index.js.map +1 -1
- package/dist/orchestrator-JAMWD6DD.js +637 -0
- package/dist/orchestrator-JAMWD6DD.js.map +1 -0
- package/dist/{server-IYR5LM63.js → server-UTCZSPCU.js} +13 -14
- package/dist/server-UTCZSPCU.js.map +1 -0
- package/dist/ssh-key-YBNNG5K5.js +10 -0
- package/package.json +15 -9
- package/dist/chunk-KVBHBOED.js.map +0 -1
- package/dist/chunk-KXESKY4X.js +0 -278
- package/dist/chunk-KXESKY4X.js.map +0 -1
- package/dist/server-IYR5LM63.js.map +0 -1
- package/dist/ssh-key-Q7KG4K25.js +0 -8
- /package/dist/{ssh-key-Q7KG4K25.js.map → ssh-key-YBNNG5K5.js.map} +0 -0
package/dist/cli.js
CHANGED
|
@@ -5,35 +5,154 @@ import {
|
|
|
5
5
|
parseDuration
|
|
6
6
|
} from "./chunk-ZSJU7IXE.js";
|
|
7
7
|
import {
|
|
8
|
-
loadEd25519PrivateKey
|
|
9
|
-
|
|
8
|
+
loadEd25519PrivateKey,
|
|
9
|
+
readPublicKeyComment
|
|
10
|
+
} from "./chunk-ION3CWD5.js";
|
|
10
11
|
import {
|
|
11
12
|
ApiError,
|
|
12
13
|
apiFetch,
|
|
13
|
-
|
|
14
|
+
buildStructuredCliGrantRequest,
|
|
15
|
+
createShapesGrant,
|
|
16
|
+
extractOption,
|
|
17
|
+
extractShellCommandString,
|
|
18
|
+
extractWrappedCommand,
|
|
19
|
+
fetchGrantToken,
|
|
20
|
+
fetchRegistry,
|
|
21
|
+
findAdapter,
|
|
22
|
+
findConflictingAdapters,
|
|
23
|
+
findExistingGrant,
|
|
14
24
|
getAgentAuthenticateEndpoint,
|
|
15
25
|
getAgentChallengeEndpoint,
|
|
16
|
-
getAuthToken,
|
|
17
26
|
getDelegationsEndpoint,
|
|
18
27
|
getGrantsEndpoint,
|
|
28
|
+
getInstalledDigest,
|
|
29
|
+
installAdapter,
|
|
30
|
+
isInstalled,
|
|
31
|
+
loadAdapter,
|
|
32
|
+
loadOrInstallAdapter,
|
|
33
|
+
parseShellCommand,
|
|
34
|
+
removeAdapter,
|
|
35
|
+
resolveCapabilityRequest,
|
|
36
|
+
resolveCommand,
|
|
37
|
+
searchAdapters,
|
|
38
|
+
verifyAndExecute,
|
|
39
|
+
waitForGrantStatus
|
|
40
|
+
} from "./chunk-B32ZQP5K.js";
|
|
41
|
+
import {
|
|
42
|
+
clearAuth,
|
|
43
|
+
getAuthToken,
|
|
19
44
|
getIdpUrl,
|
|
20
45
|
loadAuth,
|
|
21
46
|
loadConfig,
|
|
22
47
|
saveAuth,
|
|
23
48
|
saveConfig
|
|
24
|
-
} from "./chunk-
|
|
49
|
+
} from "./chunk-TBYYREL6.js";
|
|
25
50
|
|
|
26
51
|
// src/cli.ts
|
|
27
|
-
import
|
|
28
|
-
|
|
52
|
+
import consola26 from "consola";
|
|
53
|
+
|
|
54
|
+
// src/ape-shell.ts
|
|
55
|
+
import path from "path";
|
|
56
|
+
function rewriteApeShellArgs(argv, argv0) {
|
|
57
|
+
const rawInvokedAs = argv[1] ?? "";
|
|
58
|
+
const dashFromArgv1 = rawInvokedAs.startsWith("-");
|
|
59
|
+
const dashFromArgv0 = typeof argv0 === "string" && argv0.startsWith("-");
|
|
60
|
+
const looksLikeLoginShell = dashFromArgv1 || dashFromArgv0;
|
|
61
|
+
const normalizedInvokedAs = dashFromArgv1 ? rawInvokedAs.slice(1) : rawInvokedAs;
|
|
62
|
+
const invokedAs = path.basename(normalizedInvokedAs);
|
|
63
|
+
const wrapperEnv = typeof process !== "undefined" && process.env?.APES_SHELL_WRAPPER === "1";
|
|
64
|
+
const argvMatch = invokedAs === "ape-shell" || invokedAs === "ape-shell.js";
|
|
65
|
+
if (!wrapperEnv && !argvMatch)
|
|
66
|
+
return null;
|
|
67
|
+
const shellArgs = argv.slice(2);
|
|
68
|
+
if (shellArgs[0] === "-c" && shellArgs.length > 1) {
|
|
69
|
+
return { action: "rewrite", argv: [argv[0], argv[1], "run", "--shell", "--", "bash", "-c", ...shellArgs.slice(1)] };
|
|
70
|
+
}
|
|
71
|
+
if (shellArgs[0] === "--version" || shellArgs[0] === "-v")
|
|
72
|
+
return { action: "version" };
|
|
73
|
+
if (shellArgs[0] === "--help" || shellArgs[0] === "-h")
|
|
74
|
+
return { action: "help" };
|
|
75
|
+
if (shellArgs.length === 0 || shellArgs[0] === "-i" || shellArgs[0] === "-l" || shellArgs[0] === "--login" || looksLikeLoginShell) {
|
|
76
|
+
return { action: "interactive" };
|
|
77
|
+
}
|
|
78
|
+
return { action: "error" };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// src/cli.ts
|
|
82
|
+
import { defineCommand as defineCommand31, runMain } from "citty";
|
|
29
83
|
|
|
30
84
|
// src/commands/auth/login.ts
|
|
31
85
|
import { Buffer } from "buffer";
|
|
32
86
|
import { execFile } from "child_process";
|
|
33
87
|
import { createServer } from "http";
|
|
88
|
+
import { homedir as homedir2 } from "os";
|
|
89
|
+
import { resolve as resolvePath } from "path";
|
|
34
90
|
import { defineCommand } from "citty";
|
|
35
91
|
import { generateCodeChallenge, generateCodeVerifier } from "@openape/core";
|
|
92
|
+
import consola2 from "consola";
|
|
93
|
+
|
|
94
|
+
// src/commands/auth/resolve-login.ts
|
|
95
|
+
import { existsSync } from "fs";
|
|
96
|
+
import { homedir } from "os";
|
|
97
|
+
import { join } from "path";
|
|
98
|
+
import { resolveDDISA } from "@openape/core";
|
|
36
99
|
import consola from "consola";
|
|
100
|
+
var DEFAULT_KEY = join(homedir(), ".ssh", "id_ed25519");
|
|
101
|
+
async function resolveLoginInputs(flags) {
|
|
102
|
+
const config = loadConfig();
|
|
103
|
+
let keyPath;
|
|
104
|
+
if (!flags.browser) {
|
|
105
|
+
if (flags.key) {
|
|
106
|
+
keyPath = flags.key;
|
|
107
|
+
} else if (process.env.APES_KEY) {
|
|
108
|
+
keyPath = process.env.APES_KEY;
|
|
109
|
+
consola.info(`Using key from APES_KEY: ${keyPath}`);
|
|
110
|
+
} else if (config.agent?.key) {
|
|
111
|
+
keyPath = config.agent.key;
|
|
112
|
+
consola.info(`Using key from config: ${keyPath}`);
|
|
113
|
+
} else if (existsSync(DEFAULT_KEY)) {
|
|
114
|
+
keyPath = DEFAULT_KEY;
|
|
115
|
+
consola.info(`Using default key: ${keyPath}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
let email;
|
|
119
|
+
if (flags.email) {
|
|
120
|
+
email = flags.email;
|
|
121
|
+
} else if (process.env.APES_EMAIL) {
|
|
122
|
+
email = process.env.APES_EMAIL;
|
|
123
|
+
} else if (config.agent?.email) {
|
|
124
|
+
email = config.agent.email;
|
|
125
|
+
} else if (keyPath) {
|
|
126
|
+
const comment = readPublicKeyComment(`${keyPath}.pub`);
|
|
127
|
+
if (comment && comment.includes("@")) {
|
|
128
|
+
email = comment;
|
|
129
|
+
consola.info(`Using email from ${keyPath}.pub comment: ${email}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
let idp;
|
|
133
|
+
if (flags.idp) {
|
|
134
|
+
idp = flags.idp;
|
|
135
|
+
} else if (process.env.APES_IDP) {
|
|
136
|
+
idp = process.env.APES_IDP;
|
|
137
|
+
} else if (process.env.GRAPES_IDP) {
|
|
138
|
+
idp = process.env.GRAPES_IDP;
|
|
139
|
+
} else if (config.defaults?.idp) {
|
|
140
|
+
idp = config.defaults.idp;
|
|
141
|
+
} else if (email && email.includes("@")) {
|
|
142
|
+
const domain = email.split("@")[1];
|
|
143
|
+
try {
|
|
144
|
+
const record = await resolveDDISA(domain);
|
|
145
|
+
if (record?.idp) {
|
|
146
|
+
idp = record.idp;
|
|
147
|
+
consola.info(`Discovered IdP via DDISA (_ddisa.${domain}): ${idp}`);
|
|
148
|
+
}
|
|
149
|
+
} catch {
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return { keyPath, email, idp };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// src/commands/auth/login.ts
|
|
37
156
|
var CALLBACK_PORT = 9876;
|
|
38
157
|
var CLIENT_ID = "grapes-cli";
|
|
39
158
|
var loginCommand = defineCommand({
|
|
@@ -44,27 +163,56 @@ var loginCommand = defineCommand({
|
|
|
44
163
|
args: {
|
|
45
164
|
idp: {
|
|
46
165
|
type: "string",
|
|
47
|
-
description: "IdP URL (e.g. https://id.openape.at)"
|
|
166
|
+
description: "IdP URL (e.g. https://id.openape.at). Auto-discovered via DDISA DNS if omitted."
|
|
48
167
|
},
|
|
49
168
|
key: {
|
|
50
169
|
type: "string",
|
|
51
|
-
description: "Path to agent private key
|
|
170
|
+
description: "Path to agent private key. Defaults to ~/.ssh/id_ed25519 if present."
|
|
52
171
|
},
|
|
53
172
|
email: {
|
|
54
173
|
type: "string",
|
|
55
|
-
description: "Agent email
|
|
174
|
+
description: "Agent email. Extracted from <key>.pub comment if omitted."
|
|
175
|
+
},
|
|
176
|
+
browser: {
|
|
177
|
+
type: "boolean",
|
|
178
|
+
description: "Force browser (PKCE) login even if an SSH key exists"
|
|
56
179
|
}
|
|
57
180
|
},
|
|
58
181
|
async run({ args }) {
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
182
|
+
const resolved = await resolveLoginInputs({
|
|
183
|
+
key: args.key,
|
|
184
|
+
idp: args.idp,
|
|
185
|
+
email: args.email,
|
|
186
|
+
browser: args.browser
|
|
187
|
+
});
|
|
188
|
+
if (resolved.keyPath) {
|
|
189
|
+
if (!resolved.email) {
|
|
190
|
+
throw new CliError(
|
|
191
|
+
`Agent email required for key-based login. Add an email comment to ${resolved.keyPath}.pub (ssh-keygen -C <email>) or pass --email <agent-email>.`
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
if (!resolved.idp) {
|
|
195
|
+
const domain = resolved.email.split("@")[1];
|
|
196
|
+
throw new CliError(
|
|
197
|
+
`No IdP found for ${resolved.email}.
|
|
198
|
+
|
|
199
|
+
There is no DDISA TXT record for ${domain} and no --idp was provided.
|
|
200
|
+
|
|
201
|
+
Options:
|
|
202
|
+
\u2022 Run your own IdP (recommended for production)
|
|
203
|
+
See: https://docs.openape.at
|
|
204
|
+
\u2022 Publish a DDISA TXT record for your domain:
|
|
205
|
+
_ddisa.${domain} TXT "v=ddisa1 idp=https://your-idp.example"
|
|
206
|
+
\u2022 Use OpenApe's free hosted IdP for testing:
|
|
207
|
+
apes login --idp https://id.openape.at`
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
await loginWithKey(resolved.idp, resolved.keyPath, resolved.email);
|
|
66
211
|
} else {
|
|
67
|
-
|
|
212
|
+
if (!resolved.idp) {
|
|
213
|
+
throw new CliError("IdP URL required for browser login. Use --idp <url> or set APES_IDP.");
|
|
214
|
+
}
|
|
215
|
+
await loginWithPKCE(resolved.idp);
|
|
68
216
|
}
|
|
69
217
|
}
|
|
70
218
|
});
|
|
@@ -88,7 +236,7 @@ async function loginWithPKCE(idp) {
|
|
|
88
236
|
authUrl.searchParams.set("state", state);
|
|
89
237
|
authUrl.searchParams.set("nonce", nonce);
|
|
90
238
|
authUrl.searchParams.set("scope", "openid email profile offline_access");
|
|
91
|
-
const code = await new Promise((
|
|
239
|
+
const code = await new Promise((resolve3, reject) => {
|
|
92
240
|
const server = createServer((req, res) => {
|
|
93
241
|
const url = new URL(req.url, `http://localhost:${CALLBACK_PORT}`);
|
|
94
242
|
if (url.pathname === "/callback") {
|
|
@@ -105,7 +253,7 @@ async function loginWithPKCE(idp) {
|
|
|
105
253
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
106
254
|
res.end("<h1>Login successful!</h1><p>You can close this window.</p>");
|
|
107
255
|
server.close();
|
|
108
|
-
|
|
256
|
+
resolve3(authCode);
|
|
109
257
|
return;
|
|
110
258
|
}
|
|
111
259
|
res.writeHead(400);
|
|
@@ -116,7 +264,12 @@ async function loginWithPKCE(idp) {
|
|
|
116
264
|
}
|
|
117
265
|
});
|
|
118
266
|
server.listen(CALLBACK_PORT, () => {
|
|
119
|
-
|
|
267
|
+
console.log("");
|
|
268
|
+
console.log("Visit the following URL in a browser to authenticate:");
|
|
269
|
+
console.log("");
|
|
270
|
+
console.log(` ${authUrl.toString()}`);
|
|
271
|
+
console.log("");
|
|
272
|
+
consola2.info(`Waiting for authentication callback on ${redirectUri} ...`);
|
|
120
273
|
openBrowser(authUrl.toString());
|
|
121
274
|
});
|
|
122
275
|
const timeout = setTimeout(() => {
|
|
@@ -153,16 +306,12 @@ async function loginWithPKCE(idp) {
|
|
|
153
306
|
email: payload.email || payload.sub,
|
|
154
307
|
expires_at: Math.floor(Date.now() / 1e3) + (tokens.expires_in || 3600)
|
|
155
308
|
});
|
|
156
|
-
|
|
309
|
+
consola2.success(`Logged in as ${payload.email || payload.sub}`);
|
|
157
310
|
}
|
|
158
|
-
async function loginWithKey(idp, keyPath,
|
|
159
|
-
const { readFileSync:
|
|
311
|
+
async function loginWithKey(idp, keyPath, agentEmail) {
|
|
312
|
+
const { readFileSync: readFileSync4 } = await import("fs");
|
|
160
313
|
const { sign: sign2 } = await import("crypto");
|
|
161
|
-
const { loadEd25519PrivateKey: loadEd25519PrivateKey2 } = await import("./ssh-key-
|
|
162
|
-
const agentEmail = email;
|
|
163
|
-
if (!agentEmail) {
|
|
164
|
-
throw new CliError("Agent email required for key-based login. Use --email <agent-email>");
|
|
165
|
-
}
|
|
314
|
+
const { loadEd25519PrivateKey: loadEd25519PrivateKey2 } = await import("./ssh-key-YBNNG5K5.js");
|
|
166
315
|
const challengeUrl = await getAgentChallengeEndpoint(idp);
|
|
167
316
|
const challengeResp = await fetch(challengeUrl, {
|
|
168
317
|
method: "POST",
|
|
@@ -173,7 +322,7 @@ async function loginWithKey(idp, keyPath, email) {
|
|
|
173
322
|
throw new CliError(`Challenge failed: ${await challengeResp.text()}`);
|
|
174
323
|
}
|
|
175
324
|
const { challenge } = await challengeResp.json();
|
|
176
|
-
const keyContent =
|
|
325
|
+
const keyContent = readFileSync4(keyPath, "utf-8");
|
|
177
326
|
const privateKey = loadEd25519PrivateKey2(keyContent);
|
|
178
327
|
const signature = sign2(null, Buffer.from(challenge), privateKey).toString("base64");
|
|
179
328
|
const authenticateUrl = await getAgentAuthenticateEndpoint(idp);
|
|
@@ -196,12 +345,23 @@ async function loginWithKey(idp, keyPath, email) {
|
|
|
196
345
|
email: agentEmail,
|
|
197
346
|
expires_at: Math.floor(Date.now() / 1e3) + (expires_in || 3600)
|
|
198
347
|
});
|
|
199
|
-
|
|
348
|
+
const absoluteKeyPath = resolvePath(keyPath.replace(/^~/, homedir2()));
|
|
349
|
+
const existingConfig = loadConfig();
|
|
350
|
+
saveConfig({
|
|
351
|
+
...existingConfig,
|
|
352
|
+
agent: {
|
|
353
|
+
...existingConfig.agent,
|
|
354
|
+
key: absoluteKeyPath,
|
|
355
|
+
email: agentEmail
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
consola2.success(`Logged in as ${agentEmail}`);
|
|
359
|
+
consola2.info(`Auto-refresh enabled (key path saved to ~/.config/apes/config.toml)`);
|
|
200
360
|
}
|
|
201
361
|
|
|
202
362
|
// src/commands/auth/logout.ts
|
|
203
363
|
import { defineCommand as defineCommand2 } from "citty";
|
|
204
|
-
import
|
|
364
|
+
import consola3 from "consola";
|
|
205
365
|
var logoutCommand = defineCommand2({
|
|
206
366
|
meta: {
|
|
207
367
|
name: "logout",
|
|
@@ -209,13 +369,13 @@ var logoutCommand = defineCommand2({
|
|
|
209
369
|
},
|
|
210
370
|
run() {
|
|
211
371
|
clearAuth();
|
|
212
|
-
|
|
372
|
+
consola3.success("Logged out.");
|
|
213
373
|
}
|
|
214
374
|
});
|
|
215
375
|
|
|
216
376
|
// src/commands/auth/whoami.ts
|
|
217
377
|
import { defineCommand as defineCommand3 } from "citty";
|
|
218
|
-
import
|
|
378
|
+
import consola4 from "consola";
|
|
219
379
|
var whoamiCommand = defineCommand3({
|
|
220
380
|
meta: {
|
|
221
381
|
name: "whoami",
|
|
@@ -234,14 +394,14 @@ var whoamiCommand = defineCommand3({
|
|
|
234
394
|
console.log(`IdP: ${auth.idp}`);
|
|
235
395
|
console.log(`Token: ${isExpired ? "\u26A0 EXPIRED" : "valid"} (until ${expiresAt})`);
|
|
236
396
|
if (isExpired) {
|
|
237
|
-
|
|
397
|
+
consola4.warn("Token is expired. Run `apes login` to re-authenticate.");
|
|
238
398
|
}
|
|
239
399
|
}
|
|
240
400
|
});
|
|
241
401
|
|
|
242
402
|
// src/commands/grants/list.ts
|
|
243
403
|
import { defineCommand as defineCommand4 } from "citty";
|
|
244
|
-
import
|
|
404
|
+
import consola5 from "consola";
|
|
245
405
|
var listCommand = defineCommand4({
|
|
246
406
|
meta: {
|
|
247
407
|
name: "list",
|
|
@@ -290,7 +450,7 @@ var listCommand = defineCommand4({
|
|
|
290
450
|
return;
|
|
291
451
|
}
|
|
292
452
|
if (grants.length === 0) {
|
|
293
|
-
|
|
453
|
+
consola5.info(args.all ? "No grants found." : "No grants found. Use --all to see all visible grants.");
|
|
294
454
|
return;
|
|
295
455
|
}
|
|
296
456
|
for (const grant of grants) {
|
|
@@ -302,14 +462,14 @@ var listCommand = defineCommand4({
|
|
|
302
462
|
}
|
|
303
463
|
}
|
|
304
464
|
if (response.pagination.has_more) {
|
|
305
|
-
|
|
465
|
+
consola5.info("More results available. Use --limit or pagination cursor.");
|
|
306
466
|
}
|
|
307
467
|
}
|
|
308
468
|
});
|
|
309
469
|
|
|
310
470
|
// src/commands/grants/inbox.ts
|
|
311
471
|
import { defineCommand as defineCommand5 } from "citty";
|
|
312
|
-
import
|
|
472
|
+
import consola6 from "consola";
|
|
313
473
|
var inboxCommand = defineCommand5({
|
|
314
474
|
meta: {
|
|
315
475
|
name: "inbox",
|
|
@@ -348,10 +508,10 @@ var inboxCommand = defineCommand5({
|
|
|
348
508
|
return;
|
|
349
509
|
}
|
|
350
510
|
if (grants.length === 0) {
|
|
351
|
-
|
|
511
|
+
consola6.info("No pending grants to approve.");
|
|
352
512
|
return;
|
|
353
513
|
}
|
|
354
|
-
|
|
514
|
+
consola6.info(`${grants.length} grant(s) awaiting approval:
|
|
355
515
|
`);
|
|
356
516
|
for (const grant of grants) {
|
|
357
517
|
const cmd = grant.request?.command?.join(" ") || "(no command)";
|
|
@@ -366,7 +526,7 @@ var inboxCommand = defineCommand5({
|
|
|
366
526
|
}
|
|
367
527
|
console.log();
|
|
368
528
|
}
|
|
369
|
-
|
|
529
|
+
consola6.info("Use `apes grants approve <id>` or `apes grants deny <id>` to respond.");
|
|
370
530
|
}
|
|
371
531
|
});
|
|
372
532
|
|
|
@@ -422,7 +582,7 @@ var statusCommand = defineCommand6({
|
|
|
422
582
|
// src/commands/grants/request.ts
|
|
423
583
|
import { hostname } from "os";
|
|
424
584
|
import { defineCommand as defineCommand7 } from "citty";
|
|
425
|
-
import
|
|
585
|
+
import consola7 from "consola";
|
|
426
586
|
var requestCommand = defineCommand7({
|
|
427
587
|
meta: {
|
|
428
588
|
name: "request",
|
|
@@ -489,9 +649,9 @@ var requestCommand = defineCommand7({
|
|
|
489
649
|
...args["run-as"] ? { run_as: args["run-as"] } : {}
|
|
490
650
|
}
|
|
491
651
|
});
|
|
492
|
-
|
|
652
|
+
consola7.success(`Grant requested: ${grant.id} (status: ${grant.status})`);
|
|
493
653
|
if (args.wait) {
|
|
494
|
-
|
|
654
|
+
consola7.info("Waiting for approval...");
|
|
495
655
|
await waitForApproval(grantsUrl, grant.id);
|
|
496
656
|
}
|
|
497
657
|
}
|
|
@@ -503,7 +663,7 @@ async function waitForApproval(grantsUrl, grantId) {
|
|
|
503
663
|
while (Date.now() - start < maxWait) {
|
|
504
664
|
const grant = await apiFetch(`${grantsUrl}/${grantId}`);
|
|
505
665
|
if (grant.status === "approved") {
|
|
506
|
-
|
|
666
|
+
consola7.success("Grant approved!");
|
|
507
667
|
return;
|
|
508
668
|
}
|
|
509
669
|
if (grant.status === "denied") {
|
|
@@ -519,9 +679,8 @@ async function waitForApproval(grantsUrl, grantId) {
|
|
|
519
679
|
|
|
520
680
|
// src/commands/grants/request-capability.ts
|
|
521
681
|
import { hostname as hostname2 } from "os";
|
|
522
|
-
import { buildStructuredCliGrantRequest, loadAdapter, resolveCapabilityRequest } from "@openape/shapes";
|
|
523
682
|
import { defineCommand as defineCommand8 } from "citty";
|
|
524
|
-
import
|
|
683
|
+
import consola8 from "consola";
|
|
525
684
|
function parseCapabilityArgs(rawArgs) {
|
|
526
685
|
const tokens = [...rawArgs];
|
|
527
686
|
if (tokens[0] === "request-capability") {
|
|
@@ -628,7 +787,7 @@ async function waitForApproval2(grantsUrl, grantId) {
|
|
|
628
787
|
while (Date.now() - start < maxWait) {
|
|
629
788
|
const grant = await apiFetch(`${grantsUrl}/${grantId}`);
|
|
630
789
|
if (grant.status === "approved") {
|
|
631
|
-
|
|
790
|
+
consola8.success("Grant approved!");
|
|
632
791
|
return;
|
|
633
792
|
}
|
|
634
793
|
if (grant.status === "denied") {
|
|
@@ -637,7 +796,7 @@ async function waitForApproval2(grantsUrl, grantId) {
|
|
|
637
796
|
if (grant.status === "revoked") {
|
|
638
797
|
throw new CliError("Grant revoked.");
|
|
639
798
|
}
|
|
640
|
-
await new Promise((
|
|
799
|
+
await new Promise((resolve3) => setTimeout(resolve3, interval));
|
|
641
800
|
}
|
|
642
801
|
throw new CliError("Timed out waiting for approval.");
|
|
643
802
|
}
|
|
@@ -729,9 +888,9 @@ var requestCapabilityCommand = defineCommand8({
|
|
|
729
888
|
idp,
|
|
730
889
|
body: request
|
|
731
890
|
});
|
|
732
|
-
|
|
891
|
+
consola8.success(`Grant requested: ${grant.id} (status: ${grant.status})`);
|
|
733
892
|
if (parsed.wait) {
|
|
734
|
-
|
|
893
|
+
consola8.info("Waiting for approval...");
|
|
735
894
|
await waitForApproval2(grantsUrl, grant.id);
|
|
736
895
|
}
|
|
737
896
|
}
|
|
@@ -739,7 +898,7 @@ var requestCapabilityCommand = defineCommand8({
|
|
|
739
898
|
|
|
740
899
|
// src/commands/grants/approve.ts
|
|
741
900
|
import { defineCommand as defineCommand9 } from "citty";
|
|
742
|
-
import
|
|
901
|
+
import consola9 from "consola";
|
|
743
902
|
var approveCommand = defineCommand9({
|
|
744
903
|
meta: {
|
|
745
904
|
name: "approve",
|
|
@@ -758,13 +917,13 @@ var approveCommand = defineCommand9({
|
|
|
758
917
|
await apiFetch(`${grantsUrl}/${args.id}/approve`, {
|
|
759
918
|
method: "POST"
|
|
760
919
|
});
|
|
761
|
-
|
|
920
|
+
consola9.success(`Grant ${args.id} approved.`);
|
|
762
921
|
}
|
|
763
922
|
});
|
|
764
923
|
|
|
765
924
|
// src/commands/grants/deny.ts
|
|
766
925
|
import { defineCommand as defineCommand10 } from "citty";
|
|
767
|
-
import
|
|
926
|
+
import consola10 from "consola";
|
|
768
927
|
var denyCommand = defineCommand10({
|
|
769
928
|
meta: {
|
|
770
929
|
name: "deny",
|
|
@@ -783,13 +942,13 @@ var denyCommand = defineCommand10({
|
|
|
783
942
|
await apiFetch(`${grantsUrl}/${args.id}/deny`, {
|
|
784
943
|
method: "POST"
|
|
785
944
|
});
|
|
786
|
-
|
|
945
|
+
consola10.success(`Grant ${args.id} denied.`);
|
|
787
946
|
}
|
|
788
947
|
});
|
|
789
948
|
|
|
790
949
|
// src/commands/grants/revoke.ts
|
|
791
950
|
import { defineCommand as defineCommand11 } from "citty";
|
|
792
|
-
import
|
|
951
|
+
import consola11 from "consola";
|
|
793
952
|
var revokeCommand = defineCommand11({
|
|
794
953
|
meta: {
|
|
795
954
|
name: "revoke",
|
|
@@ -818,11 +977,11 @@ var revokeCommand = defineCommand11({
|
|
|
818
977
|
const idp = getIdpUrl();
|
|
819
978
|
const grantsUrl = await getGrantsEndpoint(idp);
|
|
820
979
|
if (args.debug) {
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
980
|
+
consola11.debug(`idp: ${idp}`);
|
|
981
|
+
consola11.debug(`grantsUrl: ${grantsUrl}`);
|
|
982
|
+
consola11.debug(`auth.email: ${auth?.email}`);
|
|
983
|
+
consola11.debug(`auth.expires_at: ${auth?.expires_at} (now: ${Math.floor(Date.now() / 1e3)})`);
|
|
984
|
+
consola11.debug(`getAuthToken(): ${token ? `${token.substring(0, 20)}...` : "NULL"}`);
|
|
826
985
|
}
|
|
827
986
|
if (!auth || !token) {
|
|
828
987
|
throw new CliError("Authentication required. Run `apes login` and try again.");
|
|
@@ -840,11 +999,11 @@ var revokeCommand = defineCommand11({
|
|
|
840
999
|
);
|
|
841
1000
|
const ownPending = auth2?.email ? response.data.filter((g) => g.request?.requester === auth2.email) : response.data;
|
|
842
1001
|
if (ownPending.length === 0) {
|
|
843
|
-
|
|
1002
|
+
consola11.info("No pending grants to revoke.");
|
|
844
1003
|
return;
|
|
845
1004
|
}
|
|
846
1005
|
ids = ownPending.map((g) => g.id);
|
|
847
|
-
|
|
1006
|
+
consola11.info(`Found ${ids.length} pending grant(s) to revoke.`);
|
|
848
1007
|
} else if (explicitIds.length > 0) {
|
|
849
1008
|
ids = explicitIds;
|
|
850
1009
|
} else {
|
|
@@ -852,7 +1011,7 @@ var revokeCommand = defineCommand11({
|
|
|
852
1011
|
}
|
|
853
1012
|
if (ids.length === 1) {
|
|
854
1013
|
await apiFetch(`${grantsUrl}/${ids[0]}/revoke`, { method: "POST", token });
|
|
855
|
-
|
|
1014
|
+
consola11.success(`Grant ${ids[0]} revoked.`);
|
|
856
1015
|
return;
|
|
857
1016
|
}
|
|
858
1017
|
const operations = ids.map((id) => ({ id, action: "revoke" }));
|
|
@@ -863,16 +1022,16 @@ var revokeCommand = defineCommand11({
|
|
|
863
1022
|
let succeeded = 0;
|
|
864
1023
|
for (const r of results) {
|
|
865
1024
|
if (r.success) {
|
|
866
|
-
|
|
1025
|
+
consola11.success(`Grant ${r.id} revoked.`);
|
|
867
1026
|
succeeded++;
|
|
868
1027
|
} else {
|
|
869
|
-
|
|
1028
|
+
consola11.error(`Grant ${r.id}: ${r.error?.title || "Failed"}`);
|
|
870
1029
|
}
|
|
871
1030
|
}
|
|
872
1031
|
if (succeeded < results.length) {
|
|
873
1032
|
throw new CliError(`Revoked ${succeeded} of ${results.length} grants.`);
|
|
874
1033
|
} else {
|
|
875
|
-
|
|
1034
|
+
consola11.success(`All ${succeeded} grants revoked.`);
|
|
876
1035
|
}
|
|
877
1036
|
}
|
|
878
1037
|
});
|
|
@@ -906,7 +1065,7 @@ var tokenCommand = defineCommand12({
|
|
|
906
1065
|
|
|
907
1066
|
// src/commands/grants/delegate.ts
|
|
908
1067
|
import { defineCommand as defineCommand13 } from "citty";
|
|
909
|
-
import
|
|
1068
|
+
import consola12 from "consola";
|
|
910
1069
|
var delegateCommand = defineCommand13({
|
|
911
1070
|
meta: {
|
|
912
1071
|
name: "delegate",
|
|
@@ -959,7 +1118,7 @@ var delegateCommand = defineCommand13({
|
|
|
959
1118
|
method: "POST",
|
|
960
1119
|
body
|
|
961
1120
|
});
|
|
962
|
-
|
|
1121
|
+
consola12.success(`Delegation created: ${result.id}`);
|
|
963
1122
|
console.log(` Delegate: ${args.to}`);
|
|
964
1123
|
console.log(` Audience: ${args.at}`);
|
|
965
1124
|
if (args.scopes)
|
|
@@ -972,7 +1131,7 @@ var delegateCommand = defineCommand13({
|
|
|
972
1131
|
|
|
973
1132
|
// src/commands/grants/delegations.ts
|
|
974
1133
|
import { defineCommand as defineCommand14 } from "citty";
|
|
975
|
-
import
|
|
1134
|
+
import consola13 from "consola";
|
|
976
1135
|
var delegationsCommand = defineCommand14({
|
|
977
1136
|
meta: {
|
|
978
1137
|
name: "delegations",
|
|
@@ -988,13 +1147,14 @@ var delegationsCommand = defineCommand14({
|
|
|
988
1147
|
async run({ args }) {
|
|
989
1148
|
const idp = getIdpUrl();
|
|
990
1149
|
const delegationsUrl = await getDelegationsEndpoint(idp);
|
|
991
|
-
const
|
|
1150
|
+
const response = await apiFetch(delegationsUrl);
|
|
1151
|
+
const delegations = Array.isArray(response) ? response : response.data;
|
|
992
1152
|
if (args.json) {
|
|
993
1153
|
console.log(JSON.stringify(delegations, null, 2));
|
|
994
1154
|
return;
|
|
995
1155
|
}
|
|
996
1156
|
if (delegations.length === 0) {
|
|
997
|
-
|
|
1157
|
+
consola13.info("No delegations found.");
|
|
998
1158
|
return;
|
|
999
1159
|
}
|
|
1000
1160
|
for (const d of delegations) {
|
|
@@ -1005,27 +1165,345 @@ var delegationsCommand = defineCommand14({
|
|
|
1005
1165
|
}
|
|
1006
1166
|
});
|
|
1007
1167
|
|
|
1008
|
-
// src/commands/
|
|
1168
|
+
// src/commands/grants/delegation-revoke.ts
|
|
1009
1169
|
import { defineCommand as defineCommand15 } from "citty";
|
|
1010
|
-
import
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
}
|
|
1022
|
-
|
|
1170
|
+
import consola14 from "consola";
|
|
1171
|
+
var delegationRevokeCommand = defineCommand15({
|
|
1172
|
+
meta: {
|
|
1173
|
+
name: "delegation-revoke",
|
|
1174
|
+
description: "Revoke a delegation"
|
|
1175
|
+
},
|
|
1176
|
+
args: {
|
|
1177
|
+
id: {
|
|
1178
|
+
type: "positional",
|
|
1179
|
+
description: "Delegation ID to revoke",
|
|
1180
|
+
required: true
|
|
1181
|
+
}
|
|
1182
|
+
},
|
|
1183
|
+
async run({ args }) {
|
|
1184
|
+
const idp = getIdpUrl();
|
|
1185
|
+
if (!idp) {
|
|
1186
|
+
throw new CliError("No IdP URL configured. Run `apes login` first or pass --idp.");
|
|
1187
|
+
}
|
|
1188
|
+
const delegationsUrl = await getDelegationsEndpoint(idp);
|
|
1189
|
+
const id = String(args.id);
|
|
1190
|
+
const result = await apiFetch(
|
|
1191
|
+
`${delegationsUrl}/${id}`,
|
|
1192
|
+
{ method: "DELETE" }
|
|
1193
|
+
);
|
|
1194
|
+
consola14.success(`Delegation ${result.id} revoked.`);
|
|
1195
|
+
}
|
|
1196
|
+
});
|
|
1197
|
+
|
|
1198
|
+
// src/commands/admin/index.ts
|
|
1199
|
+
import { defineCommand as defineCommand18 } from "citty";
|
|
1200
|
+
|
|
1201
|
+
// src/commands/admin/users.ts
|
|
1202
|
+
import { defineCommand as defineCommand16 } from "citty";
|
|
1203
|
+
import consola15 from "consola";
|
|
1204
|
+
function getManagementToken() {
|
|
1205
|
+
const token = process.env.APES_MANAGEMENT_TOKEN;
|
|
1206
|
+
if (!token) {
|
|
1207
|
+
throw new CliError("Management token required. Set APES_MANAGEMENT_TOKEN environment variable.");
|
|
1208
|
+
}
|
|
1209
|
+
return token;
|
|
1210
|
+
}
|
|
1211
|
+
var usersListCommand = defineCommand16({
|
|
1212
|
+
meta: {
|
|
1213
|
+
name: "list",
|
|
1214
|
+
description: "List all users"
|
|
1215
|
+
},
|
|
1216
|
+
args: {
|
|
1217
|
+
json: {
|
|
1218
|
+
type: "boolean",
|
|
1219
|
+
description: "Output as JSON",
|
|
1220
|
+
default: false
|
|
1221
|
+
},
|
|
1222
|
+
limit: {
|
|
1223
|
+
type: "string",
|
|
1224
|
+
description: "Max number of users to return (1-100, default 50)"
|
|
1225
|
+
},
|
|
1226
|
+
cursor: {
|
|
1227
|
+
type: "string",
|
|
1228
|
+
description: "Pagination cursor (email of last item from previous page)"
|
|
1229
|
+
},
|
|
1230
|
+
search: {
|
|
1231
|
+
type: "string",
|
|
1232
|
+
description: "Filter by email or name (case-insensitive)"
|
|
1233
|
+
}
|
|
1234
|
+
},
|
|
1235
|
+
async run({ args }) {
|
|
1236
|
+
const idp = getIdpUrl();
|
|
1237
|
+
if (!idp) {
|
|
1238
|
+
throw new CliError("No IdP URL configured. Run `apes login` first or pass --idp.");
|
|
1239
|
+
}
|
|
1240
|
+
const token = getManagementToken();
|
|
1241
|
+
const params = new URLSearchParams();
|
|
1242
|
+
if (args.limit) params.set("limit", args.limit);
|
|
1243
|
+
if (args.cursor) params.set("cursor", args.cursor);
|
|
1244
|
+
if (args.search) params.set("search", args.search);
|
|
1245
|
+
const qs = params.toString();
|
|
1246
|
+
const url = qs ? `${idp}/api/admin/users?${qs}` : `${idp}/api/admin/users`;
|
|
1247
|
+
const result = await apiFetch(url, { token });
|
|
1248
|
+
if (args.json) {
|
|
1249
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1250
|
+
return;
|
|
1251
|
+
}
|
|
1252
|
+
if (result.data.length === 0) {
|
|
1253
|
+
consola15.info("No users found.");
|
|
1254
|
+
return;
|
|
1255
|
+
}
|
|
1256
|
+
for (const u of result.data) {
|
|
1257
|
+
const owner = u.owner ? ` (agent of ${u.owner})` : "";
|
|
1258
|
+
const active = u.isActive ? "" : " [inactive]";
|
|
1259
|
+
console.log(`${u.email} ${u.name}${owner}${active}`);
|
|
1260
|
+
}
|
|
1261
|
+
if (result.pagination.has_more) {
|
|
1262
|
+
consola15.info(`More results available. Use --cursor="${result.pagination.cursor}" to see next page.`);
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
});
|
|
1266
|
+
var usersCreateCommand = defineCommand16({
|
|
1267
|
+
meta: {
|
|
1268
|
+
name: "create",
|
|
1269
|
+
description: "Create a user"
|
|
1270
|
+
},
|
|
1271
|
+
args: {
|
|
1272
|
+
email: {
|
|
1273
|
+
type: "string",
|
|
1274
|
+
description: "User email",
|
|
1275
|
+
required: true
|
|
1276
|
+
},
|
|
1277
|
+
name: {
|
|
1278
|
+
type: "string",
|
|
1279
|
+
description: "User name",
|
|
1280
|
+
required: true
|
|
1281
|
+
}
|
|
1282
|
+
},
|
|
1283
|
+
async run({ args }) {
|
|
1284
|
+
const idp = getIdpUrl();
|
|
1285
|
+
if (!idp) {
|
|
1286
|
+
throw new CliError("No IdP URL configured. Run `apes login` first or pass --idp.");
|
|
1287
|
+
}
|
|
1288
|
+
const token = getManagementToken();
|
|
1289
|
+
const result = await apiFetch(
|
|
1290
|
+
`${idp}/api/admin/users`,
|
|
1291
|
+
{
|
|
1292
|
+
method: "POST",
|
|
1293
|
+
body: { email: args.email, name: args.name },
|
|
1294
|
+
token
|
|
1295
|
+
}
|
|
1296
|
+
);
|
|
1297
|
+
consola15.success(`User created: ${result.email} (${result.name})`);
|
|
1298
|
+
}
|
|
1299
|
+
});
|
|
1300
|
+
var usersDeleteCommand = defineCommand16({
|
|
1301
|
+
meta: {
|
|
1302
|
+
name: "delete",
|
|
1303
|
+
description: "Delete a user"
|
|
1304
|
+
},
|
|
1305
|
+
args: {
|
|
1306
|
+
email: {
|
|
1307
|
+
type: "positional",
|
|
1308
|
+
description: "User email",
|
|
1309
|
+
required: true
|
|
1310
|
+
}
|
|
1311
|
+
},
|
|
1312
|
+
async run({ args }) {
|
|
1313
|
+
const idp = getIdpUrl();
|
|
1314
|
+
if (!idp) {
|
|
1315
|
+
throw new CliError("No IdP URL configured. Run `apes login` first or pass --idp.");
|
|
1316
|
+
}
|
|
1317
|
+
const token = getManagementToken();
|
|
1318
|
+
const email = String(args.email);
|
|
1319
|
+
await apiFetch(`${idp}/api/admin/users/${encodeURIComponent(email)}`, {
|
|
1320
|
+
method: "DELETE",
|
|
1321
|
+
token
|
|
1322
|
+
});
|
|
1323
|
+
consola15.success(`User deleted: ${email}`);
|
|
1324
|
+
}
|
|
1325
|
+
});
|
|
1326
|
+
|
|
1327
|
+
// src/commands/admin/ssh-keys.ts
|
|
1328
|
+
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
1329
|
+
import { resolve } from "path";
|
|
1330
|
+
import { homedir as homedir3 } from "os";
|
|
1331
|
+
import { defineCommand as defineCommand17 } from "citty";
|
|
1332
|
+
import consola16 from "consola";
|
|
1333
|
+
function getManagementToken2() {
|
|
1334
|
+
const token = process.env.APES_MANAGEMENT_TOKEN;
|
|
1335
|
+
if (!token) {
|
|
1336
|
+
throw new CliError("Management token required. Set APES_MANAGEMENT_TOKEN environment variable.");
|
|
1337
|
+
}
|
|
1338
|
+
return token;
|
|
1339
|
+
}
|
|
1340
|
+
var sshKeysListCommand = defineCommand17({
|
|
1341
|
+
meta: {
|
|
1342
|
+
name: "list",
|
|
1343
|
+
description: "List SSH keys for a user"
|
|
1344
|
+
},
|
|
1345
|
+
args: {
|
|
1346
|
+
email: {
|
|
1347
|
+
type: "positional",
|
|
1348
|
+
description: "User email",
|
|
1349
|
+
required: true
|
|
1350
|
+
},
|
|
1351
|
+
json: {
|
|
1352
|
+
type: "boolean",
|
|
1353
|
+
description: "Output as JSON",
|
|
1354
|
+
default: false
|
|
1355
|
+
}
|
|
1356
|
+
},
|
|
1357
|
+
async run({ args }) {
|
|
1358
|
+
const idp = getIdpUrl();
|
|
1359
|
+
if (!idp) {
|
|
1360
|
+
throw new CliError("No IdP URL configured. Run `apes login` first or pass --idp.");
|
|
1361
|
+
}
|
|
1362
|
+
const token = getManagementToken2();
|
|
1363
|
+
const email = String(args.email);
|
|
1364
|
+
const keys = await apiFetch(
|
|
1365
|
+
`${idp}/api/admin/users/${encodeURIComponent(email)}/ssh-keys`,
|
|
1366
|
+
{ token }
|
|
1367
|
+
);
|
|
1368
|
+
if (args.json) {
|
|
1369
|
+
console.log(JSON.stringify(keys, null, 2));
|
|
1370
|
+
return;
|
|
1371
|
+
}
|
|
1372
|
+
if (keys.length === 0) {
|
|
1373
|
+
consola16.info(`No SSH keys found for ${email}.`);
|
|
1374
|
+
return;
|
|
1375
|
+
}
|
|
1376
|
+
for (const k of keys) {
|
|
1377
|
+
console.log(`${k.keyId} ${k.name} ${k.publicKey.substring(0, 40)}...`);
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
});
|
|
1381
|
+
var sshKeysAddCommand = defineCommand17({
|
|
1382
|
+
meta: {
|
|
1383
|
+
name: "add",
|
|
1384
|
+
description: "Add an SSH key for a user"
|
|
1385
|
+
},
|
|
1386
|
+
args: {
|
|
1387
|
+
email: {
|
|
1388
|
+
type: "string",
|
|
1389
|
+
description: "User email",
|
|
1390
|
+
required: true
|
|
1391
|
+
},
|
|
1392
|
+
key: {
|
|
1393
|
+
type: "string",
|
|
1394
|
+
description: "Path to public key file or key string",
|
|
1395
|
+
required: true
|
|
1396
|
+
},
|
|
1397
|
+
name: {
|
|
1398
|
+
type: "string",
|
|
1399
|
+
description: "Key name/label"
|
|
1400
|
+
}
|
|
1401
|
+
},
|
|
1402
|
+
async run({ args }) {
|
|
1403
|
+
const idp = getIdpUrl();
|
|
1404
|
+
if (!idp) {
|
|
1405
|
+
throw new CliError("No IdP URL configured. Run `apes login` first or pass --idp.");
|
|
1406
|
+
}
|
|
1407
|
+
const token = getManagementToken2();
|
|
1408
|
+
let publicKey = args.key;
|
|
1409
|
+
const resolved = resolve(args.key.replace(/^~/, homedir3()));
|
|
1410
|
+
if (existsSync2(resolved)) {
|
|
1411
|
+
publicKey = readFileSync(resolved, "utf-8").trim();
|
|
1412
|
+
}
|
|
1413
|
+
const body = { publicKey };
|
|
1414
|
+
if (args.name) {
|
|
1415
|
+
body.name = args.name;
|
|
1416
|
+
}
|
|
1417
|
+
const result = await apiFetch(
|
|
1418
|
+
`${idp}/api/admin/users/${encodeURIComponent(args.email)}/ssh-keys`,
|
|
1419
|
+
{
|
|
1420
|
+
method: "POST",
|
|
1421
|
+
body,
|
|
1422
|
+
token
|
|
1423
|
+
}
|
|
1424
|
+
);
|
|
1425
|
+
consola16.success(`SSH key added: ${result.keyId} (${result.name})`);
|
|
1426
|
+
}
|
|
1427
|
+
});
|
|
1428
|
+
var sshKeysDeleteCommand = defineCommand17({
|
|
1429
|
+
meta: {
|
|
1430
|
+
name: "delete",
|
|
1431
|
+
description: "Delete an SSH key"
|
|
1432
|
+
},
|
|
1433
|
+
args: {
|
|
1434
|
+
email: {
|
|
1435
|
+
type: "string",
|
|
1436
|
+
description: "User email",
|
|
1437
|
+
required: true
|
|
1438
|
+
},
|
|
1439
|
+
keyId: {
|
|
1440
|
+
type: "positional",
|
|
1441
|
+
description: "Key ID",
|
|
1442
|
+
required: true
|
|
1443
|
+
}
|
|
1444
|
+
},
|
|
1445
|
+
async run({ args }) {
|
|
1446
|
+
const idp = getIdpUrl();
|
|
1447
|
+
if (!idp) {
|
|
1448
|
+
throw new CliError("No IdP URL configured. Run `apes login` first or pass --idp.");
|
|
1449
|
+
}
|
|
1450
|
+
const token = getManagementToken2();
|
|
1451
|
+
const keyId = String(args.keyId);
|
|
1452
|
+
await apiFetch(
|
|
1453
|
+
`${idp}/api/admin/users/${encodeURIComponent(args.email)}/ssh-keys/${keyId}`,
|
|
1454
|
+
{
|
|
1455
|
+
method: "DELETE",
|
|
1456
|
+
token
|
|
1457
|
+
}
|
|
1458
|
+
);
|
|
1459
|
+
consola16.success(`SSH key deleted: ${keyId}`);
|
|
1460
|
+
}
|
|
1461
|
+
});
|
|
1462
|
+
|
|
1463
|
+
// src/commands/admin/index.ts
|
|
1464
|
+
var usersCommand = defineCommand18({
|
|
1465
|
+
meta: {
|
|
1466
|
+
name: "users",
|
|
1467
|
+
description: "Manage users"
|
|
1468
|
+
},
|
|
1469
|
+
subCommands: {
|
|
1470
|
+
list: usersListCommand,
|
|
1471
|
+
create: usersCreateCommand,
|
|
1472
|
+
delete: usersDeleteCommand
|
|
1473
|
+
}
|
|
1474
|
+
});
|
|
1475
|
+
var sshKeysCommand = defineCommand18({
|
|
1476
|
+
meta: {
|
|
1477
|
+
name: "ssh-keys",
|
|
1478
|
+
description: "Manage SSH keys"
|
|
1479
|
+
},
|
|
1480
|
+
subCommands: {
|
|
1481
|
+
list: sshKeysListCommand,
|
|
1482
|
+
add: sshKeysAddCommand,
|
|
1483
|
+
delete: sshKeysDeleteCommand
|
|
1484
|
+
}
|
|
1485
|
+
});
|
|
1486
|
+
var adminCommand = defineCommand18({
|
|
1487
|
+
meta: {
|
|
1488
|
+
name: "admin",
|
|
1489
|
+
description: "Admin commands (requires APES_MANAGEMENT_TOKEN)"
|
|
1490
|
+
},
|
|
1491
|
+
subCommands: {
|
|
1492
|
+
users: usersCommand,
|
|
1493
|
+
"ssh-keys": sshKeysCommand
|
|
1494
|
+
}
|
|
1495
|
+
});
|
|
1496
|
+
|
|
1497
|
+
// src/commands/adapter/index.ts
|
|
1498
|
+
import { defineCommand as defineCommand19 } from "citty";
|
|
1499
|
+
import consola17 from "consola";
|
|
1500
|
+
var adapterCommand = defineCommand19({
|
|
1023
1501
|
meta: {
|
|
1024
1502
|
name: "adapter",
|
|
1025
1503
|
description: "Manage CLI adapters"
|
|
1026
1504
|
},
|
|
1027
1505
|
subCommands: {
|
|
1028
|
-
list:
|
|
1506
|
+
list: defineCommand19({
|
|
1029
1507
|
meta: {
|
|
1030
1508
|
name: "list",
|
|
1031
1509
|
description: "List available adapters"
|
|
@@ -1056,7 +1534,7 @@ var adapterCommand = defineCommand15({
|
|
|
1056
1534
|
`);
|
|
1057
1535
|
return;
|
|
1058
1536
|
}
|
|
1059
|
-
|
|
1537
|
+
consola17.info(`Registry: ${index2.adapters.length} adapters (${index2.generated_at})`);
|
|
1060
1538
|
for (const a of index2.adapters) {
|
|
1061
1539
|
const installed = isInstalled(a.id, false) ? " [installed]" : "";
|
|
1062
1540
|
console.log(` ${a.id.padEnd(12)} ${a.name.padEnd(24)} ${a.category}${installed}`);
|
|
@@ -1067,7 +1545,7 @@ var adapterCommand = defineCommand15({
|
|
|
1067
1545
|
const local = [];
|
|
1068
1546
|
for (const a of index.adapters) {
|
|
1069
1547
|
try {
|
|
1070
|
-
const loaded =
|
|
1548
|
+
const loaded = loadAdapter(a.id);
|
|
1071
1549
|
local.push({ id: a.id, source: loaded.source, digest: loaded.digest });
|
|
1072
1550
|
} catch {
|
|
1073
1551
|
}
|
|
@@ -1078,7 +1556,7 @@ var adapterCommand = defineCommand15({
|
|
|
1078
1556
|
return;
|
|
1079
1557
|
}
|
|
1080
1558
|
if (local.length === 0) {
|
|
1081
|
-
|
|
1559
|
+
consola17.info("No adapters installed. Use `apes adapter list --remote` to see available adapters.");
|
|
1082
1560
|
return;
|
|
1083
1561
|
}
|
|
1084
1562
|
for (const a of local) {
|
|
@@ -1086,7 +1564,7 @@ var adapterCommand = defineCommand15({
|
|
|
1086
1564
|
}
|
|
1087
1565
|
}
|
|
1088
1566
|
}),
|
|
1089
|
-
install:
|
|
1567
|
+
install: defineCommand19({
|
|
1090
1568
|
meta: {
|
|
1091
1569
|
name: "install",
|
|
1092
1570
|
description: "Install an adapter from the registry"
|
|
@@ -1115,24 +1593,24 @@ var adapterCommand = defineCommand15({
|
|
|
1115
1593
|
for (const id of ids) {
|
|
1116
1594
|
const entry = findAdapter(index, id);
|
|
1117
1595
|
if (!entry) {
|
|
1118
|
-
|
|
1596
|
+
consola17.error(`Adapter "${id}" not found in registry. Use \`apes adapter search ${id}\` to search.`);
|
|
1119
1597
|
continue;
|
|
1120
1598
|
}
|
|
1121
1599
|
const conflicts = findConflictingAdapters(entry.executable, id);
|
|
1122
1600
|
if (conflicts.length > 0) {
|
|
1123
1601
|
for (const c of conflicts) {
|
|
1124
|
-
|
|
1125
|
-
|
|
1602
|
+
consola17.warn(`Conflicting adapter found: ${c.path} (id: ${c.adapterId}, executable: ${c.executable})`);
|
|
1603
|
+
consola17.warn(` Remove it with: apes adapter remove ${c.adapterId}`);
|
|
1126
1604
|
}
|
|
1127
1605
|
}
|
|
1128
1606
|
const result = await installAdapter(entry, { local });
|
|
1129
1607
|
const verb = result.updated ? "Updated" : "Installed";
|
|
1130
|
-
|
|
1131
|
-
|
|
1608
|
+
consola17.success(`${verb} ${result.id} \u2192 ${result.path}`);
|
|
1609
|
+
consola17.info(`Digest: ${result.digest}`);
|
|
1132
1610
|
}
|
|
1133
1611
|
}
|
|
1134
1612
|
}),
|
|
1135
|
-
remove:
|
|
1613
|
+
remove: defineCommand19({
|
|
1136
1614
|
meta: {
|
|
1137
1615
|
name: "remove",
|
|
1138
1616
|
description: "Remove an installed adapter"
|
|
@@ -1155,9 +1633,9 @@ var adapterCommand = defineCommand15({
|
|
|
1155
1633
|
let failed = false;
|
|
1156
1634
|
for (const id of ids) {
|
|
1157
1635
|
if (removeAdapter(id, local)) {
|
|
1158
|
-
|
|
1636
|
+
consola17.success(`Removed adapter: ${id}`);
|
|
1159
1637
|
} else {
|
|
1160
|
-
|
|
1638
|
+
consola17.error(`Adapter "${id}" is not installed${local ? " locally" : ""}`);
|
|
1161
1639
|
failed = true;
|
|
1162
1640
|
}
|
|
1163
1641
|
}
|
|
@@ -1165,7 +1643,7 @@ var adapterCommand = defineCommand15({
|
|
|
1165
1643
|
throw new CliError("Some adapters could not be removed");
|
|
1166
1644
|
}
|
|
1167
1645
|
}),
|
|
1168
|
-
info:
|
|
1646
|
+
info: defineCommand19({
|
|
1169
1647
|
meta: {
|
|
1170
1648
|
name: "info",
|
|
1171
1649
|
description: "Show detailed adapter information"
|
|
@@ -1207,7 +1685,7 @@ var adapterCommand = defineCommand15({
|
|
|
1207
1685
|
}
|
|
1208
1686
|
}
|
|
1209
1687
|
}),
|
|
1210
|
-
search:
|
|
1688
|
+
search: defineCommand19({
|
|
1211
1689
|
meta: {
|
|
1212
1690
|
name: "search",
|
|
1213
1691
|
description: "Search adapters in the registry"
|
|
@@ -1239,7 +1717,7 @@ var adapterCommand = defineCommand15({
|
|
|
1239
1717
|
return;
|
|
1240
1718
|
}
|
|
1241
1719
|
if (results.length === 0) {
|
|
1242
|
-
|
|
1720
|
+
consola17.info(`No adapters matching "${query}"`);
|
|
1243
1721
|
return;
|
|
1244
1722
|
}
|
|
1245
1723
|
for (const a of results) {
|
|
@@ -1248,7 +1726,7 @@ var adapterCommand = defineCommand15({
|
|
|
1248
1726
|
}
|
|
1249
1727
|
}
|
|
1250
1728
|
}),
|
|
1251
|
-
update:
|
|
1729
|
+
update: defineCommand19({
|
|
1252
1730
|
meta: {
|
|
1253
1731
|
name: "update",
|
|
1254
1732
|
description: "Update installed adapters"
|
|
@@ -1274,33 +1752,33 @@ var adapterCommand = defineCommand15({
|
|
|
1274
1752
|
const targetId = args.id ? String(args.id) : void 0;
|
|
1275
1753
|
const targets = targetId ? [targetId] : index.adapters.map((a) => a.id).filter((id) => isInstalled(id, false));
|
|
1276
1754
|
if (targets.length === 0) {
|
|
1277
|
-
|
|
1755
|
+
consola17.info("No adapters installed to update.");
|
|
1278
1756
|
return;
|
|
1279
1757
|
}
|
|
1280
1758
|
for (const id of targets) {
|
|
1281
1759
|
const entry = findAdapter(index, id);
|
|
1282
1760
|
if (!entry) {
|
|
1283
|
-
|
|
1761
|
+
consola17.warn(`${id}: not found in registry, skipping`);
|
|
1284
1762
|
continue;
|
|
1285
1763
|
}
|
|
1286
1764
|
const localDigest = getInstalledDigest(id, false);
|
|
1287
1765
|
if (localDigest === entry.digest) {
|
|
1288
|
-
|
|
1766
|
+
consola17.info(`${id}: already up to date`);
|
|
1289
1767
|
continue;
|
|
1290
1768
|
}
|
|
1291
1769
|
if (localDigest && !args.yes) {
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1770
|
+
consola17.warn(`${id}: digest will change \u2014 existing grants for this adapter will be invalidated`);
|
|
1771
|
+
consola17.info(` Old: ${localDigest}`);
|
|
1772
|
+
consola17.info(` New: ${entry.digest}`);
|
|
1773
|
+
consola17.info(" Use --yes to confirm");
|
|
1296
1774
|
continue;
|
|
1297
1775
|
}
|
|
1298
1776
|
const result = await installAdapter(entry);
|
|
1299
|
-
|
|
1777
|
+
consola17.success(`Updated ${result.id} \u2192 ${result.path}`);
|
|
1300
1778
|
}
|
|
1301
1779
|
}
|
|
1302
1780
|
}),
|
|
1303
|
-
verify:
|
|
1781
|
+
verify: defineCommand19({
|
|
1304
1782
|
meta: {
|
|
1305
1783
|
name: "verify",
|
|
1306
1784
|
description: "Verify installed adapter against registry digest"
|
|
@@ -1333,7 +1811,7 @@ var adapterCommand = defineCommand15({
|
|
|
1333
1811
|
if (!localDigest)
|
|
1334
1812
|
throw new Error(`Adapter "${id}" is not installed${local ? " locally" : ""}`);
|
|
1335
1813
|
if (localDigest === entry.digest) {
|
|
1336
|
-
|
|
1814
|
+
consola17.success(`${id}: digest matches registry`);
|
|
1337
1815
|
} else {
|
|
1338
1816
|
console.log(` Local: ${localDigest}`);
|
|
1339
1817
|
console.log(` Registry: ${entry.digest}`);
|
|
@@ -1347,20 +1825,10 @@ var adapterCommand = defineCommand15({
|
|
|
1347
1825
|
// src/commands/run.ts
|
|
1348
1826
|
import { execFileSync } from "child_process";
|
|
1349
1827
|
import { hostname as hostname3 } from "os";
|
|
1350
|
-
import {
|
|
1351
|
-
import {
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
extractWrappedCommand,
|
|
1355
|
-
fetchGrantToken,
|
|
1356
|
-
findExistingGrant,
|
|
1357
|
-
loadAdapter as loadAdapter3,
|
|
1358
|
-
resolveCommand,
|
|
1359
|
-
verifyAndExecute,
|
|
1360
|
-
waitForGrantStatus
|
|
1361
|
-
} from "@openape/shapes";
|
|
1362
|
-
import consola14 from "consola";
|
|
1363
|
-
var runCommand = defineCommand16({
|
|
1828
|
+
import { basename } from "path";
|
|
1829
|
+
import { defineCommand as defineCommand20 } from "citty";
|
|
1830
|
+
import consola18 from "consola";
|
|
1831
|
+
var runCommand = defineCommand20({
|
|
1364
1832
|
meta: {
|
|
1365
1833
|
name: "run",
|
|
1366
1834
|
description: "Execute a grant-secured command"
|
|
@@ -1396,6 +1864,11 @@ var runCommand = defineCommand16({
|
|
|
1396
1864
|
type: "string",
|
|
1397
1865
|
description: "IdP URL"
|
|
1398
1866
|
},
|
|
1867
|
+
"shell": {
|
|
1868
|
+
type: "boolean",
|
|
1869
|
+
description: "Shell mode: use session grant with audience ape-shell",
|
|
1870
|
+
default: false
|
|
1871
|
+
},
|
|
1399
1872
|
"_": {
|
|
1400
1873
|
type: "positional",
|
|
1401
1874
|
description: "Command to execute (after --)",
|
|
@@ -1404,6 +1877,10 @@ var runCommand = defineCommand16({
|
|
|
1404
1877
|
},
|
|
1405
1878
|
async run({ rawArgs, args }) {
|
|
1406
1879
|
const wrappedCommand = extractWrappedCommand(rawArgs ?? []);
|
|
1880
|
+
if (args.shell && wrappedCommand.length > 0) {
|
|
1881
|
+
await runShellMode(wrappedCommand, args);
|
|
1882
|
+
return;
|
|
1883
|
+
}
|
|
1407
1884
|
if (wrappedCommand.length > 0) {
|
|
1408
1885
|
await runAdapterMode(wrappedCommand, rawArgs ?? [], args);
|
|
1409
1886
|
} else {
|
|
@@ -1414,6 +1891,114 @@ var runCommand = defineCommand16({
|
|
|
1414
1891
|
}
|
|
1415
1892
|
}
|
|
1416
1893
|
});
|
|
1894
|
+
async function runShellMode(command, args) {
|
|
1895
|
+
const auth = loadAuth();
|
|
1896
|
+
if (!auth)
|
|
1897
|
+
throw new CliError("Not logged in. Run `apes login` first.");
|
|
1898
|
+
const idp = getIdpUrl(args.idp);
|
|
1899
|
+
if (!idp)
|
|
1900
|
+
throw new CliError("No IdP URL configured. Run `apes login` first or pass --idp.");
|
|
1901
|
+
const adapterHandled = await tryAdapterModeFromShell(command, idp, args);
|
|
1902
|
+
if (adapterHandled) return;
|
|
1903
|
+
const grantsUrl = await getGrantsEndpoint(idp);
|
|
1904
|
+
const targetHost = args.host || hostname3();
|
|
1905
|
+
try {
|
|
1906
|
+
const grants = await apiFetch(
|
|
1907
|
+
`${grantsUrl}?requester=${encodeURIComponent(auth.email)}&status=approved&limit=20`
|
|
1908
|
+
);
|
|
1909
|
+
const sessionGrant = grants.data.find(
|
|
1910
|
+
(g) => g.request.audience === "ape-shell" && g.request.target_host === targetHost && g.request.grant_type !== "once"
|
|
1911
|
+
);
|
|
1912
|
+
if (sessionGrant) {
|
|
1913
|
+
execShellCommand(command);
|
|
1914
|
+
return;
|
|
1915
|
+
}
|
|
1916
|
+
} catch {
|
|
1917
|
+
}
|
|
1918
|
+
consola18.info(`Requesting ape-shell session grant on ${targetHost}`);
|
|
1919
|
+
const grant = await apiFetch(grantsUrl, {
|
|
1920
|
+
method: "POST",
|
|
1921
|
+
body: {
|
|
1922
|
+
requester: auth.email,
|
|
1923
|
+
target_host: targetHost,
|
|
1924
|
+
audience: "ape-shell",
|
|
1925
|
+
grant_type: "once",
|
|
1926
|
+
command: command.slice(0, 3),
|
|
1927
|
+
reason: `Shell session: ${command.join(" ").slice(0, 100)}`
|
|
1928
|
+
}
|
|
1929
|
+
});
|
|
1930
|
+
consola18.info(`Grant requested: ${grant.id}`);
|
|
1931
|
+
consola18.info("Waiting for approval...");
|
|
1932
|
+
const maxWait = 3e5;
|
|
1933
|
+
const interval = 3e3;
|
|
1934
|
+
const start = Date.now();
|
|
1935
|
+
while (Date.now() - start < maxWait) {
|
|
1936
|
+
const status = await apiFetch(`${grantsUrl}/${grant.id}`);
|
|
1937
|
+
if (status.status === "approved")
|
|
1938
|
+
break;
|
|
1939
|
+
if (status.status === "denied" || status.status === "revoked")
|
|
1940
|
+
throw new CliError(`Grant ${status.status}.`);
|
|
1941
|
+
await new Promise((r) => setTimeout(r, interval));
|
|
1942
|
+
}
|
|
1943
|
+
execShellCommand(command);
|
|
1944
|
+
}
|
|
1945
|
+
async function tryAdapterModeFromShell(command, idp, args) {
|
|
1946
|
+
const cmdString = extractShellCommandString(command);
|
|
1947
|
+
if (!cmdString) return false;
|
|
1948
|
+
const parsed = parseShellCommand(cmdString);
|
|
1949
|
+
if (!parsed) return false;
|
|
1950
|
+
if (parsed.isCompound) return false;
|
|
1951
|
+
const loaded = await loadOrInstallAdapter(parsed.executable);
|
|
1952
|
+
if (!loaded) return false;
|
|
1953
|
+
const normalizedExecutable = basename(parsed.executable);
|
|
1954
|
+
let resolved;
|
|
1955
|
+
try {
|
|
1956
|
+
resolved = await resolveCommand(loaded, [normalizedExecutable, ...parsed.argv]);
|
|
1957
|
+
} catch (err) {
|
|
1958
|
+
consola18.debug(`ape-shell: adapter resolve failed for "${parsed.raw}":`, err);
|
|
1959
|
+
return false;
|
|
1960
|
+
}
|
|
1961
|
+
try {
|
|
1962
|
+
const existingGrantId = await findExistingGrant(resolved, idp);
|
|
1963
|
+
if (existingGrantId) {
|
|
1964
|
+
consola18.info(`Reusing grant ${existingGrantId} for: ${resolved.detail.display}`);
|
|
1965
|
+
const token2 = await fetchGrantToken(idp, existingGrantId);
|
|
1966
|
+
await verifyAndExecute(token2, resolved);
|
|
1967
|
+
return true;
|
|
1968
|
+
}
|
|
1969
|
+
} catch {
|
|
1970
|
+
}
|
|
1971
|
+
const approval = args.approval ?? "once";
|
|
1972
|
+
consola18.info(`Requesting grant for: ${resolved.detail.display}`);
|
|
1973
|
+
const grant = await createShapesGrant(resolved, {
|
|
1974
|
+
idp,
|
|
1975
|
+
approval,
|
|
1976
|
+
reason: args.reason || `ape-shell: ${resolved.detail.display}`
|
|
1977
|
+
});
|
|
1978
|
+
consola18.info(`Grant requested: ${grant.id}`);
|
|
1979
|
+
consola18.info(`Approve at: ${idp}/grant-approval?grant_id=${grant.id}`);
|
|
1980
|
+
if (grant.similar_grants?.similar_grants?.length) {
|
|
1981
|
+
const n = grant.similar_grants.similar_grants.length;
|
|
1982
|
+
consola18.info("");
|
|
1983
|
+
consola18.info(` Similar grant(s) found (${n}). Your approver can extend an existing grant to cover this request.`);
|
|
1984
|
+
}
|
|
1985
|
+
const status = await waitForGrantStatus(idp, grant.id);
|
|
1986
|
+
if (status !== "approved")
|
|
1987
|
+
throw new CliError(`Grant ${status}`);
|
|
1988
|
+
const token = await fetchGrantToken(idp, grant.id);
|
|
1989
|
+
await verifyAndExecute(token, resolved);
|
|
1990
|
+
return true;
|
|
1991
|
+
}
|
|
1992
|
+
function execShellCommand(command) {
|
|
1993
|
+
if (command.length === 0)
|
|
1994
|
+
throw new CliError("No command to execute");
|
|
1995
|
+
try {
|
|
1996
|
+
execFileSync(command[0], command.slice(1), { stdio: "inherit" });
|
|
1997
|
+
} catch (err) {
|
|
1998
|
+
const exitCode = err.status || 1;
|
|
1999
|
+
throw new CliExit(exitCode);
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
1417
2002
|
function extractPositionals(rawArgs) {
|
|
1418
2003
|
const positionals = [];
|
|
1419
2004
|
const delimiter = rawArgs.indexOf("--");
|
|
@@ -1439,13 +2024,13 @@ async function runAdapterMode(command, rawArgs, args) {
|
|
|
1439
2024
|
return;
|
|
1440
2025
|
}
|
|
1441
2026
|
const adapterOpt = extractOption(rawArgs, "adapter");
|
|
1442
|
-
const loaded =
|
|
2027
|
+
const loaded = loadAdapter(command[0], adapterOpt);
|
|
1443
2028
|
const resolved = await resolveCommand(loaded, command);
|
|
1444
2029
|
const approval = args.approval ?? "once";
|
|
1445
2030
|
try {
|
|
1446
2031
|
const existingGrantId = await findExistingGrant(resolved, idp);
|
|
1447
2032
|
if (existingGrantId) {
|
|
1448
|
-
|
|
2033
|
+
consola18.info(`Reusing existing grant: ${existingGrantId}`);
|
|
1449
2034
|
const token2 = await fetchGrantToken(idp, existingGrantId);
|
|
1450
2035
|
await verifyAndExecute(token2, resolved);
|
|
1451
2036
|
return;
|
|
@@ -1457,17 +2042,17 @@ async function runAdapterMode(command, rawArgs, args) {
|
|
|
1457
2042
|
approval,
|
|
1458
2043
|
...args.reason ? { reason: args.reason } : {}
|
|
1459
2044
|
});
|
|
1460
|
-
|
|
1461
|
-
|
|
2045
|
+
consola18.info(`Grant requested: ${grant.id}`);
|
|
2046
|
+
consola18.info(`Approve at: ${idp}/grant-approval?grant_id=${grant.id}`);
|
|
1462
2047
|
if (grant.similar_grants?.similar_grants?.length) {
|
|
1463
2048
|
const n = grant.similar_grants.similar_grants.length;
|
|
1464
|
-
|
|
1465
|
-
|
|
2049
|
+
consola18.info("");
|
|
2050
|
+
consola18.info(` Similar grant(s) found (${n}). Your approver can extend an existing grant to cover this request.`);
|
|
1466
2051
|
if (grant.similar_grants.widened_details?.length) {
|
|
1467
2052
|
const wider = grant.similar_grants.widened_details.map((d) => d.permission).join(", ");
|
|
1468
|
-
|
|
2053
|
+
consola18.info(` Broader scope: ${wider}`);
|
|
1469
2054
|
}
|
|
1470
|
-
|
|
2055
|
+
consola18.info("");
|
|
1471
2056
|
}
|
|
1472
2057
|
const status = await waitForGrantStatus(idp, grant.id);
|
|
1473
2058
|
if (status !== "approved")
|
|
@@ -1484,7 +2069,7 @@ async function runAudienceMode(audience, action, args) {
|
|
|
1484
2069
|
const grantsUrl = await getGrantsEndpoint(idp);
|
|
1485
2070
|
const command = action.split(" ");
|
|
1486
2071
|
const targetHost = args.host || hostname3();
|
|
1487
|
-
|
|
2072
|
+
consola18.info(`Requesting ${audience} grant on ${targetHost}: ${command.join(" ")}`);
|
|
1488
2073
|
const grant = await apiFetch(grantsUrl, {
|
|
1489
2074
|
method: "POST",
|
|
1490
2075
|
body: {
|
|
@@ -1497,15 +2082,15 @@ async function runAudienceMode(audience, action, args) {
|
|
|
1497
2082
|
...args.as ? { run_as: args.as } : {}
|
|
1498
2083
|
}
|
|
1499
2084
|
});
|
|
1500
|
-
|
|
1501
|
-
|
|
2085
|
+
consola18.success(`Grant requested: ${grant.id}`);
|
|
2086
|
+
consola18.info("Waiting for approval...");
|
|
1502
2087
|
const maxWait = 3e5;
|
|
1503
2088
|
const interval = 3e3;
|
|
1504
2089
|
const start = Date.now();
|
|
1505
2090
|
while (Date.now() - start < maxWait) {
|
|
1506
2091
|
const status = await apiFetch(`${grantsUrl}/${grant.id}`);
|
|
1507
2092
|
if (status.status === "approved") {
|
|
1508
|
-
|
|
2093
|
+
consola18.success("Grant approved!");
|
|
1509
2094
|
break;
|
|
1510
2095
|
}
|
|
1511
2096
|
if (status.status === "denied" || status.status === "revoked") {
|
|
@@ -1513,12 +2098,12 @@ async function runAudienceMode(audience, action, args) {
|
|
|
1513
2098
|
}
|
|
1514
2099
|
await new Promise((r) => setTimeout(r, interval));
|
|
1515
2100
|
}
|
|
1516
|
-
|
|
2101
|
+
consola18.info("Fetching grant token...");
|
|
1517
2102
|
const { authz_jwt } = await apiFetch(`${grantsUrl}/${grant.id}/token`, {
|
|
1518
2103
|
method: "POST"
|
|
1519
2104
|
});
|
|
1520
2105
|
if (audience === "escapes") {
|
|
1521
|
-
|
|
2106
|
+
consola18.info(`Executing: ${command.join(" ")}`);
|
|
1522
2107
|
try {
|
|
1523
2108
|
execFileSync(args["escapes-path"] || "escapes", ["--grant", authz_jwt, "--", ...command], {
|
|
1524
2109
|
stdio: "inherit"
|
|
@@ -1533,9 +2118,8 @@ async function runAudienceMode(audience, action, args) {
|
|
|
1533
2118
|
}
|
|
1534
2119
|
|
|
1535
2120
|
// src/commands/explain.ts
|
|
1536
|
-
import { defineCommand as
|
|
1537
|
-
|
|
1538
|
-
var explainCommand = defineCommand17({
|
|
2121
|
+
import { defineCommand as defineCommand21 } from "citty";
|
|
2122
|
+
var explainCommand = defineCommand21({
|
|
1539
2123
|
meta: {
|
|
1540
2124
|
name: "explain",
|
|
1541
2125
|
description: "Show what permission a command would need"
|
|
@@ -1552,12 +2136,12 @@ var explainCommand = defineCommand17({
|
|
|
1552
2136
|
}
|
|
1553
2137
|
},
|
|
1554
2138
|
async run({ rawArgs }) {
|
|
1555
|
-
const command =
|
|
2139
|
+
const command = extractWrappedCommand(rawArgs ?? []);
|
|
1556
2140
|
if (command.length === 0)
|
|
1557
2141
|
throw new Error("Missing wrapped command. Usage: apes explain [--adapter <file>] -- <cli> ...");
|
|
1558
|
-
const adapterOpt =
|
|
1559
|
-
const loaded =
|
|
1560
|
-
const resolved = await
|
|
2142
|
+
const adapterOpt = extractOption(rawArgs ?? [], "adapter");
|
|
2143
|
+
const loaded = loadAdapter(command[0], adapterOpt);
|
|
2144
|
+
const resolved = await resolveCommand(loaded, command);
|
|
1561
2145
|
process.stdout.write(`${JSON.stringify({
|
|
1562
2146
|
adapter: resolved.adapter.cli.id,
|
|
1563
2147
|
source: resolved.source,
|
|
@@ -1573,9 +2157,9 @@ var explainCommand = defineCommand17({
|
|
|
1573
2157
|
});
|
|
1574
2158
|
|
|
1575
2159
|
// src/commands/config/get.ts
|
|
1576
|
-
import { defineCommand as
|
|
1577
|
-
import
|
|
1578
|
-
var configGetCommand =
|
|
2160
|
+
import { defineCommand as defineCommand22 } from "citty";
|
|
2161
|
+
import consola19 from "consola";
|
|
2162
|
+
var configGetCommand = defineCommand22({
|
|
1579
2163
|
meta: {
|
|
1580
2164
|
name: "get",
|
|
1581
2165
|
description: "Get a configuration value"
|
|
@@ -1595,7 +2179,7 @@ var configGetCommand = defineCommand18({
|
|
|
1595
2179
|
if (idp)
|
|
1596
2180
|
console.log(idp);
|
|
1597
2181
|
else
|
|
1598
|
-
|
|
2182
|
+
consola19.info("No IdP configured.");
|
|
1599
2183
|
break;
|
|
1600
2184
|
}
|
|
1601
2185
|
case "email": {
|
|
@@ -1603,7 +2187,7 @@ var configGetCommand = defineCommand18({
|
|
|
1603
2187
|
if (auth?.email)
|
|
1604
2188
|
console.log(auth.email);
|
|
1605
2189
|
else
|
|
1606
|
-
|
|
2190
|
+
consola19.info("Not logged in.");
|
|
1607
2191
|
break;
|
|
1608
2192
|
}
|
|
1609
2193
|
default: {
|
|
@@ -1616,7 +2200,7 @@ var configGetCommand = defineCommand18({
|
|
|
1616
2200
|
if (sectionObj && field in sectionObj) {
|
|
1617
2201
|
console.log(sectionObj[field]);
|
|
1618
2202
|
} else {
|
|
1619
|
-
|
|
2203
|
+
consola19.info(`Key "${key}" not set.`);
|
|
1620
2204
|
}
|
|
1621
2205
|
} else {
|
|
1622
2206
|
throw new CliError(`Unknown key: "${key}". Use: idp, email, defaults.idp, defaults.approval, agent.key, agent.email`);
|
|
@@ -1627,9 +2211,9 @@ var configGetCommand = defineCommand18({
|
|
|
1627
2211
|
});
|
|
1628
2212
|
|
|
1629
2213
|
// src/commands/config/set.ts
|
|
1630
|
-
import { defineCommand as
|
|
1631
|
-
import
|
|
1632
|
-
var configSetCommand =
|
|
2214
|
+
import { defineCommand as defineCommand23 } from "citty";
|
|
2215
|
+
import consola20 from "consola";
|
|
2216
|
+
var configSetCommand = defineCommand23({
|
|
1633
2217
|
meta: {
|
|
1634
2218
|
name: "set",
|
|
1635
2219
|
description: "Set a configuration value"
|
|
@@ -1665,12 +2249,12 @@ var configSetCommand = defineCommand19({
|
|
|
1665
2249
|
throw new CliError(`Unknown section: "${section}". Use: defaults, agent`);
|
|
1666
2250
|
}
|
|
1667
2251
|
saveConfig(config);
|
|
1668
|
-
|
|
2252
|
+
consola20.success(`Set ${key} = ${value}`);
|
|
1669
2253
|
}
|
|
1670
2254
|
});
|
|
1671
2255
|
|
|
1672
2256
|
// src/commands/fetch/index.ts
|
|
1673
|
-
import { defineCommand as
|
|
2257
|
+
import { defineCommand as defineCommand24 } from "citty";
|
|
1674
2258
|
async function doRequest(method, url, body, contentType, raw, showHeaders) {
|
|
1675
2259
|
const token = getAuthToken();
|
|
1676
2260
|
if (!token) {
|
|
@@ -1706,13 +2290,13 @@ async function doRequest(method, url, body, contentType, raw, showHeaders) {
|
|
|
1706
2290
|
throw new CliError(`HTTP ${response.status} ${response.statusText}`);
|
|
1707
2291
|
}
|
|
1708
2292
|
}
|
|
1709
|
-
var fetchCommand =
|
|
2293
|
+
var fetchCommand = defineCommand24({
|
|
1710
2294
|
meta: {
|
|
1711
2295
|
name: "fetch",
|
|
1712
2296
|
description: "Make authenticated HTTP requests"
|
|
1713
2297
|
},
|
|
1714
2298
|
subCommands: {
|
|
1715
|
-
get:
|
|
2299
|
+
get: defineCommand24({
|
|
1716
2300
|
meta: {
|
|
1717
2301
|
name: "get",
|
|
1718
2302
|
description: "GET request with auth token"
|
|
@@ -1738,7 +2322,7 @@ var fetchCommand = defineCommand20({
|
|
|
1738
2322
|
await doRequest("GET", String(args.url), void 0, "application/json", Boolean(args.raw), Boolean(args.headers));
|
|
1739
2323
|
}
|
|
1740
2324
|
}),
|
|
1741
|
-
post:
|
|
2325
|
+
post: defineCommand24({
|
|
1742
2326
|
meta: {
|
|
1743
2327
|
name: "post",
|
|
1744
2328
|
description: "POST request with auth token"
|
|
@@ -1777,8 +2361,8 @@ var fetchCommand = defineCommand20({
|
|
|
1777
2361
|
});
|
|
1778
2362
|
|
|
1779
2363
|
// src/commands/mcp/index.ts
|
|
1780
|
-
import { defineCommand as
|
|
1781
|
-
var mcpCommand =
|
|
2364
|
+
import { defineCommand as defineCommand25 } from "citty";
|
|
2365
|
+
var mcpCommand = defineCommand25({
|
|
1782
2366
|
meta: {
|
|
1783
2367
|
name: "mcp",
|
|
1784
2368
|
description: "Start MCP server for AI agents"
|
|
@@ -1801,25 +2385,25 @@ var mcpCommand = defineCommand21({
|
|
|
1801
2385
|
if (transport !== "stdio" && transport !== "sse") {
|
|
1802
2386
|
throw new Error('Transport must be "stdio" or "sse"');
|
|
1803
2387
|
}
|
|
1804
|
-
const { startMcpServer } = await import("./server-
|
|
2388
|
+
const { startMcpServer } = await import("./server-UTCZSPCU.js");
|
|
1805
2389
|
await startMcpServer(transport, port);
|
|
1806
2390
|
}
|
|
1807
2391
|
});
|
|
1808
2392
|
|
|
1809
2393
|
// src/commands/init/index.ts
|
|
1810
|
-
import { existsSync, copyFileSync, writeFileSync } from "fs";
|
|
2394
|
+
import { existsSync as existsSync3, copyFileSync, writeFileSync } from "fs";
|
|
1811
2395
|
import { randomBytes } from "crypto";
|
|
1812
2396
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
1813
|
-
import { join } from "path";
|
|
1814
|
-
import { defineCommand as
|
|
1815
|
-
import
|
|
2397
|
+
import { join as join2 } from "path";
|
|
2398
|
+
import { defineCommand as defineCommand26 } from "citty";
|
|
2399
|
+
import consola21 from "consola";
|
|
1816
2400
|
var DEFAULT_IDP_URL = "https://id.openape.at";
|
|
1817
2401
|
async function downloadTemplate(repo, targetDir) {
|
|
1818
2402
|
const { downloadTemplate: gigetDownload } = await import("giget");
|
|
1819
2403
|
await gigetDownload(`gh:${repo}`, { dir: targetDir, force: false });
|
|
1820
2404
|
}
|
|
1821
2405
|
function installDeps(dir) {
|
|
1822
|
-
const hasLockFile = (name) =>
|
|
2406
|
+
const hasLockFile = (name) => existsSync3(join2(dir, name));
|
|
1823
2407
|
if (hasLockFile("pnpm-lock.yaml")) {
|
|
1824
2408
|
execFileSync2("pnpm", ["install"], { cwd: dir, stdio: "inherit" });
|
|
1825
2409
|
} else if (hasLockFile("bun.lockb")) {
|
|
@@ -1829,20 +2413,20 @@ function installDeps(dir) {
|
|
|
1829
2413
|
}
|
|
1830
2414
|
}
|
|
1831
2415
|
async function promptChoice(message, choices) {
|
|
1832
|
-
const result = await
|
|
2416
|
+
const result = await consola21.prompt(message, { type: "select", options: choices });
|
|
1833
2417
|
if (typeof result === "symbol") {
|
|
1834
2418
|
throw new CliExit(0);
|
|
1835
2419
|
}
|
|
1836
2420
|
return result;
|
|
1837
2421
|
}
|
|
1838
2422
|
async function promptText(message, defaultValue) {
|
|
1839
|
-
const result = await
|
|
2423
|
+
const result = await consola21.prompt(message, { type: "text", default: defaultValue, placeholder: defaultValue });
|
|
1840
2424
|
if (typeof result === "symbol") {
|
|
1841
2425
|
throw new CliExit(0);
|
|
1842
2426
|
}
|
|
1843
2427
|
return result || defaultValue || "";
|
|
1844
2428
|
}
|
|
1845
|
-
var initCommand =
|
|
2429
|
+
var initCommand = defineCommand26({
|
|
1846
2430
|
meta: {
|
|
1847
2431
|
name: "init",
|
|
1848
2432
|
description: "Scaffold a new OpenApe project"
|
|
@@ -1884,23 +2468,23 @@ var initCommand = defineCommand22({
|
|
|
1884
2468
|
});
|
|
1885
2469
|
async function initSP(targetDir) {
|
|
1886
2470
|
const dir = targetDir || "my-app";
|
|
1887
|
-
if (
|
|
2471
|
+
if (existsSync3(join2(dir, "package.json"))) {
|
|
1888
2472
|
throw new CliError(`Directory "${dir}" already contains a project.`);
|
|
1889
2473
|
}
|
|
1890
|
-
|
|
2474
|
+
consola21.start("Scaffolding SP starter...");
|
|
1891
2475
|
await downloadTemplate("openape-ai/openape-sp-starter", dir);
|
|
1892
|
-
|
|
1893
|
-
|
|
2476
|
+
consola21.success("Scaffolded from openape-sp-starter");
|
|
2477
|
+
consola21.start("Installing dependencies...");
|
|
1894
2478
|
installDeps(dir);
|
|
1895
|
-
|
|
1896
|
-
const envExample =
|
|
1897
|
-
const envFile =
|
|
1898
|
-
if (
|
|
2479
|
+
consola21.success("Dependencies installed");
|
|
2480
|
+
const envExample = join2(dir, ".env.example");
|
|
2481
|
+
const envFile = join2(dir, ".env");
|
|
2482
|
+
if (existsSync3(envExample) && !existsSync3(envFile)) {
|
|
1899
2483
|
copyFileSync(envExample, envFile);
|
|
1900
|
-
|
|
2484
|
+
consola21.success(`\`.env\` created (using Free IdP at ${DEFAULT_IDP_URL})`);
|
|
1901
2485
|
}
|
|
1902
2486
|
console.log("");
|
|
1903
|
-
|
|
2487
|
+
consola21.box([
|
|
1904
2488
|
`cd ${dir}`,
|
|
1905
2489
|
"npm run dev",
|
|
1906
2490
|
"",
|
|
@@ -1909,7 +2493,7 @@ async function initSP(targetDir) {
|
|
|
1909
2493
|
}
|
|
1910
2494
|
async function initIdP(targetDir) {
|
|
1911
2495
|
const dir = targetDir || "my-idp";
|
|
1912
|
-
if (
|
|
2496
|
+
if (existsSync3(join2(dir, "package.json"))) {
|
|
1913
2497
|
throw new CliError(`Directory "${dir}" already contains a project.`);
|
|
1914
2498
|
}
|
|
1915
2499
|
const domain = await promptText("Domain for the IdP", "localhost");
|
|
@@ -1919,15 +2503,15 @@ async function initIdP(targetDir) {
|
|
|
1919
2503
|
"s3 (S3-compatible)"
|
|
1920
2504
|
]);
|
|
1921
2505
|
const adminEmail = await promptText("Admin email");
|
|
1922
|
-
|
|
2506
|
+
consola21.start("Scaffolding IdP starter...");
|
|
1923
2507
|
await downloadTemplate("openape-ai/openape-idp-starter", dir);
|
|
1924
|
-
|
|
1925
|
-
|
|
2508
|
+
consola21.success("Scaffolded from openape-idp-starter");
|
|
2509
|
+
consola21.start("Installing dependencies...");
|
|
1926
2510
|
installDeps(dir);
|
|
1927
|
-
|
|
2511
|
+
consola21.success("Dependencies installed");
|
|
1928
2512
|
const sessionSecret = randomBytes(32).toString("hex");
|
|
1929
2513
|
const managementToken = randomBytes(32).toString("hex");
|
|
1930
|
-
|
|
2514
|
+
consola21.success("Secrets generated");
|
|
1931
2515
|
const isLocalhost = domain === "localhost";
|
|
1932
2516
|
const origin = isLocalhost ? "http://localhost:3000" : `https://${domain}`;
|
|
1933
2517
|
const envContent = [
|
|
@@ -1941,11 +2525,11 @@ async function initIdP(targetDir) {
|
|
|
1941
2525
|
`NUXT_OPENAPE_RP_ID=${domain}`,
|
|
1942
2526
|
`NUXT_OPENAPE_RP_ORIGIN=${origin}`
|
|
1943
2527
|
].join("\n");
|
|
1944
|
-
writeFileSync(
|
|
2528
|
+
writeFileSync(join2(dir, ".env"), `${envContent}
|
|
1945
2529
|
`, { mode: 384 });
|
|
1946
|
-
|
|
2530
|
+
consola21.success(".env created");
|
|
1947
2531
|
console.log("");
|
|
1948
|
-
|
|
2532
|
+
consola21.box([
|
|
1949
2533
|
`cd ${dir}`,
|
|
1950
2534
|
"npm run dev",
|
|
1951
2535
|
"",
|
|
@@ -1962,19 +2546,19 @@ async function initIdP(targetDir) {
|
|
|
1962
2546
|
|
|
1963
2547
|
// src/commands/enroll.ts
|
|
1964
2548
|
import { Buffer as Buffer2 } from "buffer";
|
|
1965
|
-
import { existsSync as
|
|
2549
|
+
import { existsSync as existsSync4, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync } from "fs";
|
|
1966
2550
|
import { execFile as execFile2 } from "child_process";
|
|
1967
2551
|
import { generateKeyPairSync, sign } from "crypto";
|
|
1968
|
-
import { dirname, resolve } from "path";
|
|
1969
|
-
import { homedir } from "os";
|
|
1970
|
-
import { defineCommand as
|
|
1971
|
-
import
|
|
2552
|
+
import { dirname, resolve as resolve2 } from "path";
|
|
2553
|
+
import { homedir as homedir4 } from "os";
|
|
2554
|
+
import { defineCommand as defineCommand27 } from "citty";
|
|
2555
|
+
import consola22 from "consola";
|
|
1972
2556
|
var DEFAULT_IDP_URL2 = "https://id.openape.at";
|
|
1973
2557
|
var DEFAULT_KEY_PATH = "~/.ssh/id_ed25519";
|
|
1974
2558
|
var POLL_INTERVAL = 3e3;
|
|
1975
2559
|
var POLL_TIMEOUT = 3e5;
|
|
1976
|
-
function
|
|
1977
|
-
return
|
|
2560
|
+
function resolvePath2(p) {
|
|
2561
|
+
return resolve2(p.replace(/^~/, homedir4()));
|
|
1978
2562
|
}
|
|
1979
2563
|
function openBrowser2(url) {
|
|
1980
2564
|
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
@@ -1983,10 +2567,10 @@ function openBrowser2(url) {
|
|
|
1983
2567
|
}
|
|
1984
2568
|
function readPublicKey(keyPath) {
|
|
1985
2569
|
const pubPath = `${keyPath}.pub`;
|
|
1986
|
-
if (
|
|
1987
|
-
return
|
|
2570
|
+
if (existsSync4(pubPath)) {
|
|
2571
|
+
return readFileSync2(pubPath, "utf-8").trim();
|
|
1988
2572
|
}
|
|
1989
|
-
const keyContent =
|
|
2573
|
+
const keyContent = readFileSync2(keyPath, "utf-8");
|
|
1990
2574
|
const privateKey = loadEd25519PrivateKey(keyContent);
|
|
1991
2575
|
const jwk = privateKey.export({ format: "jwk" });
|
|
1992
2576
|
const pubBytes = Buffer2.from(jwk.x, "base64url");
|
|
@@ -1999,9 +2583,9 @@ function readPublicKey(keyPath) {
|
|
|
1999
2583
|
return `ssh-ed25519 ${blob.toString("base64")}`;
|
|
2000
2584
|
}
|
|
2001
2585
|
function generateAndSaveKey(keyPath) {
|
|
2002
|
-
const resolved =
|
|
2586
|
+
const resolved = resolvePath2(keyPath);
|
|
2003
2587
|
const dir = dirname(resolved);
|
|
2004
|
-
if (!
|
|
2588
|
+
if (!existsSync4(dir)) {
|
|
2005
2589
|
mkdirSync(dir, { recursive: true });
|
|
2006
2590
|
}
|
|
2007
2591
|
const { publicKey, privateKey } = generateKeyPairSync("ed25519");
|
|
@@ -2021,8 +2605,8 @@ function generateAndSaveKey(keyPath) {
|
|
|
2021
2605
|
return pubKeyStr;
|
|
2022
2606
|
}
|
|
2023
2607
|
async function pollForEnrollment(idp, agentEmail, keyPath) {
|
|
2024
|
-
const resolvedKey =
|
|
2025
|
-
const keyContent =
|
|
2608
|
+
const resolvedKey = resolvePath2(keyPath);
|
|
2609
|
+
const keyContent = readFileSync2(resolvedKey, "utf-8");
|
|
2026
2610
|
const privateKey = loadEd25519PrivateKey(keyContent);
|
|
2027
2611
|
const challengeUrl = await getAgentChallengeEndpoint(idp);
|
|
2028
2612
|
const authenticateUrl = await getAgentAuthenticateEndpoint(idp);
|
|
@@ -2049,11 +2633,11 @@ async function pollForEnrollment(idp, agentEmail, keyPath) {
|
|
|
2049
2633
|
}
|
|
2050
2634
|
} catch {
|
|
2051
2635
|
}
|
|
2052
|
-
await new Promise((
|
|
2636
|
+
await new Promise((resolve3) => setTimeout(resolve3, POLL_INTERVAL));
|
|
2053
2637
|
}
|
|
2054
2638
|
throw new Error("Enrollment timed out. Please check the browser and try again.");
|
|
2055
2639
|
}
|
|
2056
|
-
var enrollCommand =
|
|
2640
|
+
var enrollCommand = defineCommand27({
|
|
2057
2641
|
meta: {
|
|
2058
2642
|
name: "enroll",
|
|
2059
2643
|
description: "Enroll an agent with an Identity Provider"
|
|
@@ -2073,38 +2657,38 @@ var enrollCommand = defineCommand23({
|
|
|
2073
2657
|
}
|
|
2074
2658
|
},
|
|
2075
2659
|
async run({ args }) {
|
|
2076
|
-
const idp = args.idp || await
|
|
2660
|
+
const idp = args.idp || await consola22.prompt("IdP URL", { type: "text", default: DEFAULT_IDP_URL2, placeholder: DEFAULT_IDP_URL2 }).then((r) => {
|
|
2077
2661
|
if (typeof r === "symbol") throw new CliExit(0);
|
|
2078
2662
|
return r;
|
|
2079
2663
|
}) || DEFAULT_IDP_URL2;
|
|
2080
|
-
const agentName = args.name || await
|
|
2664
|
+
const agentName = args.name || await consola22.prompt("Agent name", { type: "text", placeholder: "deploy-bot" }).then((r) => {
|
|
2081
2665
|
if (typeof r === "symbol") throw new CliExit(0);
|
|
2082
2666
|
return r;
|
|
2083
2667
|
});
|
|
2084
2668
|
if (!agentName) {
|
|
2085
2669
|
throw new CliError("Agent name is required.");
|
|
2086
2670
|
}
|
|
2087
|
-
const keyPath = args.key || await
|
|
2671
|
+
const keyPath = args.key || await consola22.prompt("Ed25519 key", { type: "text", default: DEFAULT_KEY_PATH, placeholder: DEFAULT_KEY_PATH }).then((r) => {
|
|
2088
2672
|
if (typeof r === "symbol") throw new CliExit(0);
|
|
2089
2673
|
return r;
|
|
2090
2674
|
}) || DEFAULT_KEY_PATH;
|
|
2091
|
-
const resolvedKey =
|
|
2675
|
+
const resolvedKey = resolvePath2(keyPath);
|
|
2092
2676
|
let publicKey;
|
|
2093
|
-
if (
|
|
2677
|
+
if (existsSync4(resolvedKey)) {
|
|
2094
2678
|
publicKey = readPublicKey(resolvedKey);
|
|
2095
|
-
|
|
2679
|
+
consola22.success(`Using existing key ${keyPath}`);
|
|
2096
2680
|
} else {
|
|
2097
|
-
|
|
2681
|
+
consola22.start(`Generating Ed25519 key pair at ${keyPath}...`);
|
|
2098
2682
|
publicKey = generateAndSaveKey(keyPath);
|
|
2099
|
-
|
|
2683
|
+
consola22.success(`Key pair generated at ${keyPath}`);
|
|
2100
2684
|
}
|
|
2101
2685
|
const encodedKey = encodeURIComponent(publicKey);
|
|
2102
2686
|
const enrollUrl = `${idp}/enroll?name=${encodeURIComponent(agentName)}&key=${encodedKey}`;
|
|
2103
|
-
|
|
2104
|
-
|
|
2687
|
+
consola22.info("Opening browser for enrollment...");
|
|
2688
|
+
consola22.info(`\u2192 ${idp}/enroll`);
|
|
2105
2689
|
openBrowser2(enrollUrl);
|
|
2106
2690
|
console.log("");
|
|
2107
|
-
const agentEmail = await
|
|
2691
|
+
const agentEmail = await consola22.prompt(
|
|
2108
2692
|
"Agent email (shown in browser after enrollment)",
|
|
2109
2693
|
{ type: "text", placeholder: `agent+${agentName}@...` }
|
|
2110
2694
|
).then((r) => {
|
|
@@ -2114,7 +2698,7 @@ var enrollCommand = defineCommand23({
|
|
|
2114
2698
|
if (!agentEmail) {
|
|
2115
2699
|
throw new CliError("Agent email is required to verify enrollment.");
|
|
2116
2700
|
}
|
|
2117
|
-
|
|
2701
|
+
consola22.start("Verifying enrollment...");
|
|
2118
2702
|
const { token, expiresIn } = await pollForEnrollment(idp, agentEmail, keyPath);
|
|
2119
2703
|
saveAuth({
|
|
2120
2704
|
idp,
|
|
@@ -2126,18 +2710,81 @@ var enrollCommand = defineCommand23({
|
|
|
2126
2710
|
config.defaults = { ...config.defaults, idp };
|
|
2127
2711
|
config.agent = { key: keyPath, email: agentEmail };
|
|
2128
2712
|
saveConfig(config);
|
|
2129
|
-
|
|
2130
|
-
|
|
2713
|
+
consola22.success(`Agent enrolled as ${agentEmail}`);
|
|
2714
|
+
consola22.success("Config saved to ~/.config/apes/");
|
|
2131
2715
|
console.log("");
|
|
2132
|
-
|
|
2716
|
+
consola22.info("Verify with: apes whoami");
|
|
2717
|
+
}
|
|
2718
|
+
});
|
|
2719
|
+
|
|
2720
|
+
// src/commands/register-user.ts
|
|
2721
|
+
import { existsSync as existsSync5, readFileSync as readFileSync3 } from "fs";
|
|
2722
|
+
import { defineCommand as defineCommand28 } from "citty";
|
|
2723
|
+
import consola23 from "consola";
|
|
2724
|
+
var registerUserCommand = defineCommand28({
|
|
2725
|
+
meta: {
|
|
2726
|
+
name: "register-user",
|
|
2727
|
+
description: "Register a sub-user with SSH key"
|
|
2728
|
+
},
|
|
2729
|
+
args: {
|
|
2730
|
+
email: {
|
|
2731
|
+
type: "string",
|
|
2732
|
+
description: "Email for the new user",
|
|
2733
|
+
required: true
|
|
2734
|
+
},
|
|
2735
|
+
name: {
|
|
2736
|
+
type: "string",
|
|
2737
|
+
description: "Name for the new user",
|
|
2738
|
+
required: true
|
|
2739
|
+
},
|
|
2740
|
+
key: {
|
|
2741
|
+
type: "string",
|
|
2742
|
+
description: "Path to SSH public key file or key string",
|
|
2743
|
+
required: true
|
|
2744
|
+
},
|
|
2745
|
+
type: {
|
|
2746
|
+
type: "string",
|
|
2747
|
+
description: "User type: human or agent (default: agent)"
|
|
2748
|
+
}
|
|
2749
|
+
},
|
|
2750
|
+
async run({ args }) {
|
|
2751
|
+
const auth = loadAuth();
|
|
2752
|
+
if (!auth) {
|
|
2753
|
+
throw new CliError("Not authenticated. Run `apes login` first.");
|
|
2754
|
+
}
|
|
2755
|
+
const idp = getIdpUrl();
|
|
2756
|
+
if (!idp) {
|
|
2757
|
+
throw new CliError("No IdP URL configured. Run `apes login` first.");
|
|
2758
|
+
}
|
|
2759
|
+
let publicKey = args.key;
|
|
2760
|
+
if (existsSync5(args.key)) {
|
|
2761
|
+
publicKey = readFileSync3(args.key, "utf-8").trim();
|
|
2762
|
+
}
|
|
2763
|
+
if (!publicKey.startsWith("ssh-ed25519 ")) {
|
|
2764
|
+
throw new CliError("Public key must be in ssh-ed25519 format.");
|
|
2765
|
+
}
|
|
2766
|
+
const userType = args.type;
|
|
2767
|
+
if (userType && userType !== "human" && userType !== "agent") {
|
|
2768
|
+
throw new CliError('Type must be "human" or "agent".');
|
|
2769
|
+
}
|
|
2770
|
+
const result = await apiFetch(`${idp}/api/auth/enroll`, {
|
|
2771
|
+
method: "POST",
|
|
2772
|
+
body: {
|
|
2773
|
+
email: args.email,
|
|
2774
|
+
name: args.name,
|
|
2775
|
+
publicKey,
|
|
2776
|
+
...userType ? { type: userType } : {}
|
|
2777
|
+
}
|
|
2778
|
+
});
|
|
2779
|
+
consola23.success(`User registered: ${result.email} (type: ${result.type}, owner: ${result.owner})`);
|
|
2133
2780
|
}
|
|
2134
2781
|
});
|
|
2135
2782
|
|
|
2136
2783
|
// src/commands/dns-check.ts
|
|
2137
|
-
import { defineCommand as
|
|
2138
|
-
import
|
|
2139
|
-
import { resolveDDISA } from "@openape/core";
|
|
2140
|
-
var dnsCheckCommand =
|
|
2784
|
+
import { defineCommand as defineCommand29 } from "citty";
|
|
2785
|
+
import consola24 from "consola";
|
|
2786
|
+
import { resolveDDISA as resolveDDISA2 } from "@openape/core";
|
|
2787
|
+
var dnsCheckCommand = defineCommand29({
|
|
2141
2788
|
meta: {
|
|
2142
2789
|
name: "dns-check",
|
|
2143
2790
|
description: "Validate DDISA DNS TXT records for a domain"
|
|
@@ -2151,16 +2798,16 @@ var dnsCheckCommand = defineCommand24({
|
|
|
2151
2798
|
},
|
|
2152
2799
|
async run({ args }) {
|
|
2153
2800
|
const domain = args.domain;
|
|
2154
|
-
|
|
2801
|
+
consola24.start(`Checking _ddisa.${domain}...`);
|
|
2155
2802
|
try {
|
|
2156
|
-
const result = await
|
|
2803
|
+
const result = await resolveDDISA2(domain);
|
|
2157
2804
|
if (!result) {
|
|
2158
2805
|
console.log("");
|
|
2159
2806
|
console.log("To set up DDISA, add a DNS TXT record:");
|
|
2160
2807
|
console.log(` _ddisa.${domain} TXT "v=ddisa1 idp=https://id.${domain}"`);
|
|
2161
2808
|
throw new CliError(`No DDISA record found for ${domain}`);
|
|
2162
2809
|
}
|
|
2163
|
-
|
|
2810
|
+
consola24.success(`_ddisa.${domain} \u2192 ${result.idp}`);
|
|
2164
2811
|
console.log("");
|
|
2165
2812
|
console.log(` Version: ${result.version || "ddisa1"}`);
|
|
2166
2813
|
console.log(` IdP URL: ${result.idp}`);
|
|
@@ -2169,14 +2816,14 @@ var dnsCheckCommand = defineCommand24({
|
|
|
2169
2816
|
if (result.priority !== void 0)
|
|
2170
2817
|
console.log(` Priority: ${result.priority}`);
|
|
2171
2818
|
console.log("");
|
|
2172
|
-
|
|
2819
|
+
consola24.start(`Verifying IdP at ${result.idp}...`);
|
|
2173
2820
|
const discoResp = await fetch(`${result.idp}/.well-known/openid-configuration`);
|
|
2174
2821
|
if (!discoResp.ok) {
|
|
2175
|
-
|
|
2822
|
+
consola24.warn(`IdP discovery failed (${discoResp.status}). Is the IdP running at ${result.idp}?`);
|
|
2176
2823
|
return;
|
|
2177
2824
|
}
|
|
2178
2825
|
const disco = await discoResp.json();
|
|
2179
|
-
|
|
2826
|
+
consola24.success(`IdP is reachable`);
|
|
2180
2827
|
console.log(` Issuer: ${disco.issuer}`);
|
|
2181
2828
|
console.log(` DDISA: v${disco.ddisa_version || "?"}`);
|
|
2182
2829
|
if (disco.ddisa_auth_methods_supported) {
|
|
@@ -2192,8 +2839,8 @@ var dnsCheckCommand = defineCommand24({
|
|
|
2192
2839
|
});
|
|
2193
2840
|
|
|
2194
2841
|
// src/commands/workflows.ts
|
|
2195
|
-
import { defineCommand as
|
|
2196
|
-
import
|
|
2842
|
+
import { defineCommand as defineCommand30 } from "citty";
|
|
2843
|
+
import consola25 from "consola";
|
|
2197
2844
|
|
|
2198
2845
|
// src/guides/index.ts
|
|
2199
2846
|
var guides = [
|
|
@@ -2243,7 +2890,7 @@ var guides = [
|
|
|
2243
2890
|
];
|
|
2244
2891
|
|
|
2245
2892
|
// src/commands/workflows.ts
|
|
2246
|
-
var workflowsCommand =
|
|
2893
|
+
var workflowsCommand = defineCommand30({
|
|
2247
2894
|
meta: {
|
|
2248
2895
|
name: "workflows",
|
|
2249
2896
|
description: "Discover workflow guides"
|
|
@@ -2264,7 +2911,7 @@ var workflowsCommand = defineCommand25({
|
|
|
2264
2911
|
if (args.id) {
|
|
2265
2912
|
const guide = guides.find((g) => g.id === String(args.id));
|
|
2266
2913
|
if (!guide) {
|
|
2267
|
-
|
|
2914
|
+
consola25.info(`Available: ${guides.map((g) => g.id).join(", ")}`);
|
|
2268
2915
|
throw new CliError(`Guide not found: ${args.id}`);
|
|
2269
2916
|
}
|
|
2270
2917
|
if (args.json) {
|
|
@@ -2308,8 +2955,39 @@ process.stdout.on("error", (err) => {
|
|
|
2308
2955
|
if (err.code === "EPIPE") process.exit(0);
|
|
2309
2956
|
throw err;
|
|
2310
2957
|
});
|
|
2958
|
+
var shellRewrite = rewriteApeShellArgs(process.argv, process.argv0);
|
|
2959
|
+
if (shellRewrite) {
|
|
2960
|
+
if (shellRewrite.action === "rewrite") {
|
|
2961
|
+
process.argv = shellRewrite.argv;
|
|
2962
|
+
} else if (shellRewrite.action === "version") {
|
|
2963
|
+
console.log(`ape-shell ${"0.7.1"} (OpenApe DDISA shell wrapper)`);
|
|
2964
|
+
process.exit(0);
|
|
2965
|
+
} else if (shellRewrite.action === "help") {
|
|
2966
|
+
console.log(`ape-shell ${"0.7.1"} \u2014 OpenApe DDISA shell wrapper`);
|
|
2967
|
+
console.log("");
|
|
2968
|
+
console.log("Usage:");
|
|
2969
|
+
console.log(" ape-shell Start interactive grant-mediated REPL");
|
|
2970
|
+
console.log(" ape-shell -c <command> Run a single command through the grant flow");
|
|
2971
|
+
console.log(" ape-shell -i | -l Force interactive mode");
|
|
2972
|
+
console.log("");
|
|
2973
|
+
console.log("Options:");
|
|
2974
|
+
console.log(" -c <command> Execute <command> via the apes grant flow and exit");
|
|
2975
|
+
console.log(" -i Interactive REPL (default when no args are given)");
|
|
2976
|
+
console.log(" -l, --login Login shell semantics \u2014 currently same as -i");
|
|
2977
|
+
console.log(" --version, -v Show ape-shell version");
|
|
2978
|
+
console.log(" --help, -h Show this help message");
|
|
2979
|
+
process.exit(0);
|
|
2980
|
+
} else if (shellRewrite.action === "interactive") {
|
|
2981
|
+
const { runInteractiveShell } = await import("./orchestrator-JAMWD6DD.js");
|
|
2982
|
+
await runInteractiveShell();
|
|
2983
|
+
process.exit(0);
|
|
2984
|
+
} else {
|
|
2985
|
+
console.error("ape-shell: unsupported invocation. Try `ape-shell --help`.");
|
|
2986
|
+
process.exit(1);
|
|
2987
|
+
}
|
|
2988
|
+
}
|
|
2311
2989
|
var debug = process.argv.includes("--debug");
|
|
2312
|
-
var grantsCommand =
|
|
2990
|
+
var grantsCommand = defineCommand31({
|
|
2313
2991
|
meta: {
|
|
2314
2992
|
name: "grants",
|
|
2315
2993
|
description: "Grant management"
|
|
@@ -2325,10 +3003,11 @@ var grantsCommand = defineCommand26({
|
|
|
2325
3003
|
revoke: revokeCommand,
|
|
2326
3004
|
token: tokenCommand,
|
|
2327
3005
|
delegate: delegateCommand,
|
|
2328
|
-
delegations: delegationsCommand
|
|
3006
|
+
delegations: delegationsCommand,
|
|
3007
|
+
"delegation-revoke": delegationRevokeCommand
|
|
2329
3008
|
}
|
|
2330
3009
|
});
|
|
2331
|
-
var configCommand =
|
|
3010
|
+
var configCommand = defineCommand31({
|
|
2332
3011
|
meta: {
|
|
2333
3012
|
name: "config",
|
|
2334
3013
|
description: "Configuration management"
|
|
@@ -2338,20 +3017,22 @@ var configCommand = defineCommand26({
|
|
|
2338
3017
|
set: configSetCommand
|
|
2339
3018
|
}
|
|
2340
3019
|
});
|
|
2341
|
-
var main =
|
|
3020
|
+
var main = defineCommand31({
|
|
2342
3021
|
meta: {
|
|
2343
3022
|
name: "apes",
|
|
2344
|
-
version: "0.
|
|
3023
|
+
version: "0.7.1",
|
|
2345
3024
|
description: "Unified CLI for OpenApe"
|
|
2346
3025
|
},
|
|
2347
3026
|
subCommands: {
|
|
2348
3027
|
init: initCommand,
|
|
2349
3028
|
enroll: enrollCommand,
|
|
3029
|
+
"register-user": registerUserCommand,
|
|
2350
3030
|
"dns-check": dnsCheckCommand,
|
|
2351
3031
|
login: loginCommand,
|
|
2352
3032
|
logout: logoutCommand,
|
|
2353
3033
|
whoami: whoamiCommand,
|
|
2354
3034
|
grants: grantsCommand,
|
|
3035
|
+
admin: adminCommand,
|
|
2355
3036
|
run: runCommand,
|
|
2356
3037
|
explain: explainCommand,
|
|
2357
3038
|
adapter: adapterCommand,
|
|
@@ -2366,13 +3047,13 @@ runMain(main).catch((err) => {
|
|
|
2366
3047
|
process.exit(err.exitCode);
|
|
2367
3048
|
}
|
|
2368
3049
|
if (err instanceof CliError) {
|
|
2369
|
-
|
|
3050
|
+
consola26.error(err.message);
|
|
2370
3051
|
process.exit(err.exitCode);
|
|
2371
3052
|
}
|
|
2372
3053
|
if (debug) {
|
|
2373
|
-
|
|
3054
|
+
consola26.error(err);
|
|
2374
3055
|
} else {
|
|
2375
|
-
|
|
3056
|
+
consola26.error(err instanceof ApiError ? err.message : err instanceof Error ? err.message : String(err));
|
|
2376
3057
|
}
|
|
2377
3058
|
process.exit(1);
|
|
2378
3059
|
});
|