@openape/apes 0.15.2 → 0.17.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/dist/cli.js CHANGED
@@ -61,7 +61,7 @@ import {
61
61
  } from "./chunk-6GPSKAMU.js";
62
62
 
63
63
  // src/cli.ts
64
- import consola32 from "consola";
64
+ import consola33 from "consola";
65
65
 
66
66
  // src/ape-shell.ts
67
67
  import path from "path";
@@ -91,7 +91,7 @@ function rewriteApeShellArgs(argv, argv0) {
91
91
  }
92
92
 
93
93
  // src/cli.ts
94
- import { defineCommand as defineCommand39, runMain } from "citty";
94
+ import { defineCommand as defineCommand41, runMain } from "citty";
95
95
 
96
96
  // src/commands/auth/login.ts
97
97
  import { Buffer } from "buffer";
@@ -147,29 +147,44 @@ async function resolveLoginInputs(flags) {
147
147
  );
148
148
  }
149
149
  let idp;
150
+ let idpSource;
150
151
  if (flags.idp) {
151
152
  idp = flags.idp;
153
+ idpSource = "flag";
152
154
  } else if (process.env.APES_IDP) {
153
155
  idp = process.env.APES_IDP;
156
+ idpSource = "env";
154
157
  } else if (process.env.GRAPES_IDP) {
155
158
  idp = process.env.GRAPES_IDP;
159
+ idpSource = "env";
156
160
  consola.warn(
157
161
  "GRAPES_IDP is deprecated, use APES_IDP instead. GRAPES_IDP support will be removed in a future release."
158
162
  );
159
163
  } else if (config.defaults?.idp) {
160
164
  idp = config.defaults.idp;
161
- } else if (email && email.includes("@")) {
165
+ idpSource = "config";
166
+ }
167
+ let ddisaIdp;
168
+ let ddisaDomain;
169
+ if (email && email.includes("@")) {
162
170
  const domain = email.split("@")[1];
171
+ ddisaDomain = domain;
163
172
  try {
164
173
  const record = await resolveDDISA(domain);
165
- if (record?.idp) {
166
- idp = record.idp;
167
- consola.info(`Discovered IdP via DDISA (_ddisa.${domain}): ${idp}`);
168
- }
174
+ if (record?.idp) ddisaIdp = record.idp;
169
175
  } catch {
170
176
  }
171
177
  }
172
- return { keyPath, email, idp };
178
+ if (!idp && ddisaIdp && ddisaDomain) {
179
+ idp = ddisaIdp;
180
+ idpSource = "ddisa";
181
+ consola.info(`Discovered IdP via DDISA (_ddisa.${ddisaDomain}): ${idp}`);
182
+ }
183
+ let ddisaMismatch;
184
+ if (idp && ddisaIdp && ddisaDomain && idp !== ddisaIdp && idpSource !== "ddisa") {
185
+ ddisaMismatch = { dnsIdp: ddisaIdp, chosenIdp: idp, domain: ddisaDomain };
186
+ }
187
+ return { keyPath, email, idp, ddisaMismatch };
173
188
  }
174
189
 
175
190
  // src/commands/auth/login.ts
@@ -181,6 +196,11 @@ var loginCommand = defineCommand({
181
196
  description: "Authenticate with an OpenApe IdP"
182
197
  },
183
198
  args: {
199
+ user: {
200
+ type: "positional",
201
+ required: false,
202
+ description: "Agent email (e.g. patrick@hofmann.eco). Extracted from <key>.pub comment if omitted."
203
+ },
184
204
  idp: {
185
205
  type: "string",
186
206
  description: "IdP URL (e.g. https://id.openape.at). Auto-discovered via DDISA DNS if omitted."
@@ -191,24 +211,52 @@ var loginCommand = defineCommand({
191
211
  },
192
212
  email: {
193
213
  type: "string",
194
- description: "Agent email. Extracted from <key>.pub comment if omitted."
214
+ description: "Same as the positional email \u2014 flag form for backwards compatibility."
195
215
  },
196
216
  browser: {
197
217
  type: "boolean",
198
218
  description: "Force browser (PKCE) login even if an SSH key exists"
219
+ },
220
+ force: {
221
+ type: "boolean",
222
+ description: "Override DDISA mismatch warnings (use with care)"
199
223
  }
200
224
  },
201
225
  async run({ args }) {
226
+ const emailArg = typeof args.user === "string" && args.user.includes("@") ? args.user : typeof args.email === "string" ? args.email : void 0;
202
227
  const resolved = await resolveLoginInputs({
203
228
  key: args.key,
204
229
  idp: args.idp,
205
- email: args.email,
230
+ email: emailArg,
206
231
  browser: args.browser
207
232
  });
233
+ if (resolved.ddisaMismatch && !args.force) {
234
+ const { dnsIdp, chosenIdp, domain } = resolved.ddisaMismatch;
235
+ throw new CliError(
236
+ `IdP mismatch for ${domain}.
237
+
238
+ Authoritative DDISA: ${dnsIdp}
239
+ You selected: ${chosenIdp}
240
+
241
+ Logging in against a different IdP than DDISA points to means SPs that
242
+ trust the DDISA-resolved IdP (e.g. preview.openape.ai) will reject the
243
+ resulting token with "IdP mismatch".
244
+
245
+ Fix one of:
246
+ \u2022 Drop --idp/APES_IDP/config.defaults.idp \u2014 DDISA will auto-pick ${dnsIdp}
247
+ \u2022 Update _ddisa.${domain} if ${chosenIdp} is genuinely the correct IdP
248
+ \u2022 Re-run with --force to authenticate anyway (NOT recommended)`
249
+ );
250
+ }
251
+ if (resolved.ddisaMismatch && args.force) {
252
+ consola2.warn(
253
+ `Bypassing DDISA mismatch \u2014 ${resolved.ddisaMismatch.domain} resolves to ${resolved.ddisaMismatch.dnsIdp}, but you forced ${resolved.ddisaMismatch.chosenIdp}.`
254
+ );
255
+ }
208
256
  if (resolved.keyPath) {
209
257
  if (!resolved.email) {
210
258
  throw new CliError(
211
- `Agent email required for key-based login. Add an email comment to ${resolved.keyPath}.pub (ssh-keygen -C <email>) or pass --email <agent-email>.`
259
+ `Agent email required for key-based login. Pass it as a positional (\`apes login <email>\`), set --email, or add an email comment to ${resolved.keyPath}.pub via ssh-keygen -C <email>.`
212
260
  );
213
261
  }
214
262
  if (!resolved.idp) {
@@ -3651,7 +3699,7 @@ var mcpCommand = defineCommand32({
3651
3699
  if (transport !== "stdio" && transport !== "sse") {
3652
3700
  throw new Error('Transport must be "stdio" or "sse"');
3653
3701
  }
3654
- const { startMcpServer } = await import("./server-25QMTVUR.js");
3702
+ const { startMcpServer } = await import("./server-YGRMW3OW.js");
3655
3703
  await startMcpServer(transport, port);
3656
3704
  }
3657
3705
  });
@@ -4002,11 +4050,131 @@ var registerUserCommand = defineCommand35({
4002
4050
  }
4003
4051
  });
4004
4052
 
4005
- // src/commands/dns-check.ts
4053
+ // src/commands/utils/index.ts
4054
+ import { defineCommand as defineCommand37 } from "citty";
4055
+
4056
+ // src/commands/utils/dig.ts
4006
4057
  import { defineCommand as defineCommand36 } from "citty";
4007
4058
  import consola30 from "consola";
4008
4059
  import { resolveDDISA as resolveDDISA2 } from "@openape/core";
4009
- var dnsCheckCommand = defineCommand36({
4060
+ var digCommand = defineCommand36({
4061
+ meta: {
4062
+ name: "dig",
4063
+ description: "Resolve DDISA IdP for a domain or email (admin/diag tool)"
4064
+ },
4065
+ args: {
4066
+ target: {
4067
+ type: "positional",
4068
+ description: "Domain (example.com) or email (alice@example.com)",
4069
+ required: true
4070
+ },
4071
+ json: {
4072
+ type: "boolean",
4073
+ description: "Machine-readable JSON output"
4074
+ }
4075
+ },
4076
+ async run({ args }) {
4077
+ const raw = String(args.target).trim();
4078
+ const at = raw.indexOf("@");
4079
+ const domain = at >= 0 ? raw.slice(at + 1) : raw;
4080
+ const localPart = at >= 0 ? raw.slice(0, at) : null;
4081
+ if (!domain || !/^[a-z0-9.-]+\.[a-z]{2,}$/i.test(domain)) {
4082
+ throw new CliError(`Invalid domain: ${domain}`);
4083
+ }
4084
+ const result = {
4085
+ input: raw,
4086
+ domain,
4087
+ localPart,
4088
+ ddisa: { found: false }
4089
+ };
4090
+ const ddisa = await resolveDDISA2(domain);
4091
+ if (ddisa) {
4092
+ result.ddisa = {
4093
+ found: true,
4094
+ idp: ddisa.idp,
4095
+ version: ddisa.version,
4096
+ mode: ddisa.mode,
4097
+ priority: ddisa.priority
4098
+ };
4099
+ try {
4100
+ const resp = await fetch(`${ddisa.idp}/.well-known/openid-configuration`);
4101
+ if (resp.ok) {
4102
+ const disco = await resp.json();
4103
+ result.idpDiscovery = {
4104
+ ok: true,
4105
+ status: resp.status,
4106
+ issuer: typeof disco.issuer === "string" ? disco.issuer : void 0,
4107
+ ddisaVersion: typeof disco.ddisa_version === "string" ? disco.ddisa_version : void 0,
4108
+ authMethods: Array.isArray(disco.ddisa_auth_methods_supported) ? disco.ddisa_auth_methods_supported : void 0,
4109
+ grantTypes: Array.isArray(disco.openape_grant_types_supported) ? disco.openape_grant_types_supported : void 0
4110
+ };
4111
+ } else {
4112
+ result.idpDiscovery = { ok: false, status: resp.status };
4113
+ }
4114
+ } catch (err) {
4115
+ result.idpDiscovery = { ok: false };
4116
+ result.hint = `IdP at ${ddisa.idp} unreachable: ${err instanceof Error ? err.message : String(err)}`;
4117
+ }
4118
+ } else {
4119
+ result.hint = `No DDISA record. Add a TXT record:
4120
+ _ddisa.${domain} TXT "v=ddisa1 idp=https://id.${domain}; mode=open"`;
4121
+ }
4122
+ if (args.json) {
4123
+ process.stdout.write(`${JSON.stringify(result, null, 2)}
4124
+ `);
4125
+ if (!result.ddisa.found || result.idpDiscovery?.ok === false) process.exit(1);
4126
+ return;
4127
+ }
4128
+ console.log(`Target: ${raw}`);
4129
+ if (localPart) console.log(` user: ${localPart}`);
4130
+ console.log(` domain: ${domain}`);
4131
+ console.log("");
4132
+ if (!result.ddisa.found) {
4133
+ consola30.warn(`No DDISA record at _ddisa.${domain}`);
4134
+ if (result.hint) console.log(`
4135
+ ${result.hint}`);
4136
+ throw new CliError(`No DDISA record found for ${domain}`);
4137
+ }
4138
+ consola30.success(`_ddisa.${domain} \u2192 ${result.ddisa.idp}`);
4139
+ console.log(` Version: ${result.ddisa.version || "ddisa1"}`);
4140
+ console.log(` IdP URL: ${result.ddisa.idp}`);
4141
+ if (result.ddisa.mode) console.log(` Mode: ${result.ddisa.mode}`);
4142
+ if (result.ddisa.priority !== void 0) console.log(` Priority: ${result.ddisa.priority}`);
4143
+ console.log("");
4144
+ if (!result.idpDiscovery) {
4145
+ return;
4146
+ }
4147
+ if (result.idpDiscovery.ok) {
4148
+ consola30.success(`IdP reachable (${result.idpDiscovery.status ?? 200})`);
4149
+ if (result.idpDiscovery.issuer) console.log(` Issuer: ${result.idpDiscovery.issuer}`);
4150
+ if (result.idpDiscovery.ddisaVersion) console.log(` DDISA: v${result.idpDiscovery.ddisaVersion}`);
4151
+ if (result.idpDiscovery.authMethods?.length) console.log(` Auth: ${result.idpDiscovery.authMethods.join(", ")}`);
4152
+ if (result.idpDiscovery.grantTypes?.length) console.log(` Grants: ${result.idpDiscovery.grantTypes.join(", ")}`);
4153
+ } else {
4154
+ consola30.warn(`IdP discovery failed${result.idpDiscovery.status ? ` (HTTP ${result.idpDiscovery.status})` : ""}`);
4155
+ if (result.hint) console.log(`
4156
+ ${result.hint}`);
4157
+ throw new CliError(`IdP at ${result.ddisa.idp} not reachable`);
4158
+ }
4159
+ }
4160
+ });
4161
+
4162
+ // src/commands/utils/index.ts
4163
+ var utilsCommand = defineCommand37({
4164
+ meta: {
4165
+ name: "utils",
4166
+ description: "Admin/diagnostic utilities (dig, \u2026)"
4167
+ },
4168
+ subCommands: {
4169
+ dig: digCommand
4170
+ }
4171
+ });
4172
+
4173
+ // src/commands/dns-check.ts
4174
+ import { defineCommand as defineCommand38 } from "citty";
4175
+ import consola31 from "consola";
4176
+ import { resolveDDISA as resolveDDISA3 } from "@openape/core";
4177
+ var dnsCheckCommand = defineCommand38({
4010
4178
  meta: {
4011
4179
  name: "dns-check",
4012
4180
  description: "Validate DDISA DNS TXT records for a domain"
@@ -4020,16 +4188,16 @@ var dnsCheckCommand = defineCommand36({
4020
4188
  },
4021
4189
  async run({ args }) {
4022
4190
  const domain = args.domain;
4023
- consola30.start(`Checking _ddisa.${domain}...`);
4191
+ consola31.start(`Checking _ddisa.${domain}...`);
4024
4192
  try {
4025
- const result = await resolveDDISA2(domain);
4193
+ const result = await resolveDDISA3(domain);
4026
4194
  if (!result) {
4027
4195
  console.log("");
4028
4196
  console.log("To set up DDISA, add a DNS TXT record:");
4029
4197
  console.log(` _ddisa.${domain} TXT "v=ddisa1 idp=https://id.${domain}"`);
4030
4198
  throw new CliError(`No DDISA record found for ${domain}`);
4031
4199
  }
4032
- consola30.success(`_ddisa.${domain} \u2192 ${result.idp}`);
4200
+ consola31.success(`_ddisa.${domain} \u2192 ${result.idp}`);
4033
4201
  console.log("");
4034
4202
  console.log(` Version: ${result.version || "ddisa1"}`);
4035
4203
  console.log(` IdP URL: ${result.idp}`);
@@ -4038,14 +4206,14 @@ var dnsCheckCommand = defineCommand36({
4038
4206
  if (result.priority !== void 0)
4039
4207
  console.log(` Priority: ${result.priority}`);
4040
4208
  console.log("");
4041
- consola30.start(`Verifying IdP at ${result.idp}...`);
4209
+ consola31.start(`Verifying IdP at ${result.idp}...`);
4042
4210
  const discoResp = await fetch(`${result.idp}/.well-known/openid-configuration`);
4043
4211
  if (!discoResp.ok) {
4044
- consola30.warn(`IdP discovery failed (${discoResp.status}). Is the IdP running at ${result.idp}?`);
4212
+ consola31.warn(`IdP discovery failed (${discoResp.status}). Is the IdP running at ${result.idp}?`);
4045
4213
  return;
4046
4214
  }
4047
4215
  const disco = await discoResp.json();
4048
- consola30.success(`IdP is reachable`);
4216
+ consola31.success(`IdP is reachable`);
4049
4217
  console.log(` Issuer: ${disco.issuer}`);
4050
4218
  console.log(` DDISA: v${disco.ddisa_version || "?"}`);
4051
4219
  if (disco.ddisa_auth_methods_supported) {
@@ -4063,7 +4231,7 @@ var dnsCheckCommand = defineCommand36({
4063
4231
  // src/commands/health.ts
4064
4232
  import { exec } from "child_process";
4065
4233
  import { promisify } from "util";
4066
- import { defineCommand as defineCommand37 } from "citty";
4234
+ import { defineCommand as defineCommand39 } from "citty";
4067
4235
  var execAsync = promisify(exec);
4068
4236
  async function resolveApeShellPath() {
4069
4237
  try {
@@ -4099,7 +4267,7 @@ async function bestEffortGrantCount(idp) {
4099
4267
  }
4100
4268
  }
4101
4269
  async function runHealth(args) {
4102
- const version = true ? "0.15.2" : "0.0.0";
4270
+ const version = true ? "0.17.0" : "0.0.0";
4103
4271
  const auth = loadAuth();
4104
4272
  if (!auth) {
4105
4273
  throw new CliError("Not logged in. Run `apes login` first.", 1);
@@ -4162,7 +4330,7 @@ async function runHealth(args) {
4162
4330
  throw new CliError(`IdP ${auth.idp} unreachable: ${idpProbe.error}`, 1);
4163
4331
  }
4164
4332
  }
4165
- var healthCommand = defineCommand37({
4333
+ var healthCommand = defineCommand39({
4166
4334
  meta: {
4167
4335
  name: "health",
4168
4336
  description: "Report CLI diagnostic state (auth, IdP, grants, binaries)"
@@ -4180,8 +4348,8 @@ var healthCommand = defineCommand37({
4180
4348
  });
4181
4349
 
4182
4350
  // src/commands/workflows.ts
4183
- import { defineCommand as defineCommand38 } from "citty";
4184
- import consola31 from "consola";
4351
+ import { defineCommand as defineCommand40 } from "citty";
4352
+ import consola32 from "consola";
4185
4353
 
4186
4354
  // src/guides/index.ts
4187
4355
  var guides = [
@@ -4231,7 +4399,7 @@ var guides = [
4231
4399
  ];
4232
4400
 
4233
4401
  // src/commands/workflows.ts
4234
- var workflowsCommand = defineCommand38({
4402
+ var workflowsCommand = defineCommand40({
4235
4403
  meta: {
4236
4404
  name: "workflows",
4237
4405
  description: "Discover workflow guides"
@@ -4252,7 +4420,7 @@ var workflowsCommand = defineCommand38({
4252
4420
  if (args.id) {
4253
4421
  const guide = guides.find((g) => g.id === String(args.id));
4254
4422
  if (!guide) {
4255
- consola31.info(`Available: ${guides.map((g) => g.id).join(", ")}`);
4423
+ consola32.info(`Available: ${guides.map((g) => g.id).join(", ")}`);
4256
4424
  throw new CliError(`Guide not found: ${args.id}`);
4257
4425
  }
4258
4426
  if (args.json) {
@@ -4301,10 +4469,10 @@ if (shellRewrite) {
4301
4469
  if (shellRewrite.action === "rewrite") {
4302
4470
  process.argv = shellRewrite.argv;
4303
4471
  } else if (shellRewrite.action === "version") {
4304
- console.log(`ape-shell ${"0.15.2"} (OpenApe DDISA shell wrapper)`);
4472
+ console.log(`ape-shell ${"0.17.0"} (OpenApe DDISA shell wrapper)`);
4305
4473
  process.exit(0);
4306
4474
  } else if (shellRewrite.action === "help") {
4307
- console.log(`ape-shell ${"0.15.2"} \u2014 OpenApe DDISA shell wrapper`);
4475
+ console.log(`ape-shell ${"0.17.0"} \u2014 OpenApe DDISA shell wrapper`);
4308
4476
  console.log("");
4309
4477
  console.log("Usage:");
4310
4478
  console.log(" ape-shell Start interactive grant-mediated REPL");
@@ -4328,7 +4496,7 @@ if (shellRewrite) {
4328
4496
  }
4329
4497
  }
4330
4498
  var debug = process.argv.includes("--debug");
4331
- var grantsCommand = defineCommand39({
4499
+ var grantsCommand = defineCommand41({
4332
4500
  meta: {
4333
4501
  name: "grants",
4334
4502
  description: "Grant management"
@@ -4349,7 +4517,7 @@ var grantsCommand = defineCommand39({
4349
4517
  "delegation-revoke": delegationRevokeCommand
4350
4518
  }
4351
4519
  });
4352
- var configCommand = defineCommand39({
4520
+ var configCommand = defineCommand41({
4353
4521
  meta: {
4354
4522
  name: "config",
4355
4523
  description: "Configuration management"
@@ -4359,10 +4527,10 @@ var configCommand = defineCommand39({
4359
4527
  set: configSetCommand
4360
4528
  }
4361
4529
  });
4362
- var main = defineCommand39({
4530
+ var main = defineCommand41({
4363
4531
  meta: {
4364
4532
  name: "apes",
4365
- version: "0.15.2",
4533
+ version: "0.17.0",
4366
4534
  description: "Unified CLI for OpenApe"
4367
4535
  },
4368
4536
  subCommands: {
@@ -4370,6 +4538,7 @@ var main = defineCommand39({
4370
4538
  enroll: enrollCommand,
4371
4539
  "register-user": registerUserCommand,
4372
4540
  "dns-check": dnsCheckCommand,
4541
+ utils: utilsCommand,
4373
4542
  login: loginCommand,
4374
4543
  logout: logoutCommand,
4375
4544
  whoami: whoamiCommand,
@@ -4392,13 +4561,13 @@ runMain(main).catch((err) => {
4392
4561
  process.exit(err.exitCode);
4393
4562
  }
4394
4563
  if (err instanceof CliError) {
4395
- consola32.error(err.message);
4564
+ consola33.error(err.message);
4396
4565
  process.exit(err.exitCode);
4397
4566
  }
4398
4567
  if (debug) {
4399
- consola32.error(err);
4568
+ consola33.error(err);
4400
4569
  } else {
4401
- consola32.error(err instanceof ApiError ? err.message : err instanceof Error ? err.message : String(err));
4570
+ consola33.error(err instanceof ApiError ? err.message : err instanceof Error ? err.message : String(err));
4402
4571
  }
4403
4572
  process.exit(1);
4404
4573
  });