@pmate/cli 0.25.8 → 0.25.9

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/index.d.ts CHANGED
@@ -452,6 +452,47 @@ type AccountCommandArgs = {
452
452
  role?: "practitioner" | "mate";
453
453
  baseUrl?: string;
454
454
  json: boolean;
455
+ } | {
456
+ type: Command.Account;
457
+ action: "access-key-create";
458
+ app: string;
459
+ name: string;
460
+ namespace?: string;
461
+ scopes: string[];
462
+ expiresAt: string;
463
+ out?: string;
464
+ baseUrl?: string;
465
+ json: boolean;
466
+ } | {
467
+ type: Command.Account;
468
+ action: "access-key-list";
469
+ app?: string;
470
+ namespace?: string;
471
+ baseUrl?: string;
472
+ json: boolean;
473
+ } | {
474
+ type: Command.Account;
475
+ action: "access-key-disable";
476
+ id: string;
477
+ baseUrl?: string;
478
+ json: boolean;
479
+ } | {
480
+ type: Command.Account;
481
+ action: "access-key-rotate";
482
+ id: string;
483
+ expiresAt: string;
484
+ gracePeriodSeconds?: number;
485
+ out?: string;
486
+ baseUrl?: string;
487
+ json: boolean;
488
+ } | {
489
+ type: Command.Account;
490
+ action: "access-key-login";
491
+ app: string;
492
+ key?: string;
493
+ keyFile?: string;
494
+ baseUrl?: string;
495
+ json: boolean;
455
496
  };
456
497
  type OrgCommandArgs = {
457
498
  type: Command.Org;
package/dist/index.js CHANGED
@@ -22303,6 +22303,88 @@ var AccountCommandHandler = class {
22303
22303
  this.renderJson(profile, args.json);
22304
22304
  return;
22305
22305
  }
22306
+ case "access-key-create": {
22307
+ const token = this.readToken();
22308
+ const baseUrl = this.resolveAuthBaseUrl(args.baseUrl);
22309
+ const result = await this.request(
22310
+ baseUrl,
22311
+ token,
22312
+ "POST",
22313
+ "/access-keys",
22314
+ {
22315
+ app: args.app,
22316
+ name: args.name,
22317
+ namespace: args.namespace,
22318
+ scopes: args.scopes,
22319
+ expiresAt: args.expiresAt
22320
+ }
22321
+ );
22322
+ this.persistAccessKeySecret(result, args.out);
22323
+ this.renderAccessKeyCreateResult(result, args.json, args.out);
22324
+ return;
22325
+ }
22326
+ case "access-key-list": {
22327
+ const token = this.readToken();
22328
+ const baseUrl = this.resolveAuthBaseUrl(args.baseUrl);
22329
+ const result = await this.request(
22330
+ baseUrl,
22331
+ token,
22332
+ "GET",
22333
+ `/access-keys?${this.buildAccessKeyListQuery(args)}`
22334
+ );
22335
+ this.renderAccessKeys(result.items, args.json);
22336
+ return;
22337
+ }
22338
+ case "access-key-disable": {
22339
+ const token = this.readToken();
22340
+ const baseUrl = this.resolveAuthBaseUrl(args.baseUrl);
22341
+ const item = await this.request(
22342
+ baseUrl,
22343
+ token,
22344
+ "DELETE",
22345
+ `/access-keys/${encodeURIComponent(args.id)}`
22346
+ );
22347
+ if (!args.json) {
22348
+ ui.success(`Disabled access key ${item.id}`);
22349
+ }
22350
+ this.renderJson(item, args.json);
22351
+ return;
22352
+ }
22353
+ case "access-key-rotate": {
22354
+ const token = this.readToken();
22355
+ const baseUrl = this.resolveAuthBaseUrl(args.baseUrl);
22356
+ const result = await this.request(
22357
+ baseUrl,
22358
+ token,
22359
+ "POST",
22360
+ `/access-keys/${encodeURIComponent(args.id)}/rotate`,
22361
+ {
22362
+ expiresAt: args.expiresAt,
22363
+ gracePeriodSeconds: args.gracePeriodSeconds
22364
+ }
22365
+ );
22366
+ this.persistAccessKeySecret(result, args.out);
22367
+ this.renderAccessKeyCreateResult(result, args.json, args.out);
22368
+ return;
22369
+ }
22370
+ case "access-key-login": {
22371
+ const baseUrl = this.resolveAuthBaseUrl(args.baseUrl);
22372
+ const secret = args.key ?? this.readAccessKeyFile(args.keyFile, args.app).secret;
22373
+ const login = await this.request(
22374
+ baseUrl,
22375
+ secret,
22376
+ "POST",
22377
+ "/access-keys/session",
22378
+ { app: args.app }
22379
+ );
22380
+ const profileId = await this.resolveLoginProfileId(login, args.app, baseUrl);
22381
+ const saved = saveLoginSession({ ...login, profileId }, args.app);
22382
+ if (!args.json) {
22383
+ ui.success(`Logged in access key ${login.session.identity.accessKeyId ?? "unknown"} to ${saved.app}`);
22384
+ }
22385
+ this.renderJson(login, args.json);
22386
+ return;
22387
+ }
22306
22388
  }
22307
22389
  }
22308
22390
  resolveAuthBaseUrl(baseUrl) {
@@ -22344,6 +22426,12 @@ var AccountCommandHandler = class {
22344
22426
  if (args.namespace) params.set("namespace", args.namespace);
22345
22427
  return params.toString();
22346
22428
  }
22429
+ buildAccessKeyListQuery(args) {
22430
+ const params = new URLSearchParams();
22431
+ if (args.app) params.set("app", args.app);
22432
+ if (args.namespace) params.set("namespace", args.namespace);
22433
+ return params.toString();
22434
+ }
22347
22435
  async request(baseUrl, token, method, pathname, body) {
22348
22436
  const response = await fetch(`${baseUrl}${pathname}`, {
22349
22437
  method,
@@ -22501,6 +22589,9 @@ var AccountCommandHandler = class {
22501
22589
  resolveDefaultBotKeyPath(app, name) {
22502
22590
  return import_node_path26.default.join(this.homeDir(), ".pmate", "bots", app, `${name}.bot-key.json`);
22503
22591
  }
22592
+ resolveDefaultAccessKeyPath(app, name) {
22593
+ return import_node_path26.default.join(this.homeDir(), ".pmate", "access-keys", app, `${name}.access-key.json`);
22594
+ }
22504
22595
  homeDir() {
22505
22596
  return process.env.HOME || process.env.USERPROFILE || process.cwd();
22506
22597
  }
@@ -22537,6 +22628,89 @@ var AccountCommandHandler = class {
22537
22628
  }
22538
22629
  return parsed;
22539
22630
  }
22631
+ readAccessKeyFile(filePath, expectedApp) {
22632
+ if (!filePath) {
22633
+ throw new Error("Missing access key file");
22634
+ }
22635
+ const absolutePath = this.resolveFilePath(filePath);
22636
+ if (!(0, import_node_fs22.existsSync)(absolutePath)) {
22637
+ throw new Error(`Access key file not found: ${absolutePath}`);
22638
+ }
22639
+ const parsed = JSON.parse((0, import_node_fs22.readFileSync)(absolutePath, "utf8"));
22640
+ if (parsed.type !== "pmate-access-key" || parsed.version !== 1 || !parsed.id || !parsed.app || !parsed.secret || !parsed.expiresAt) {
22641
+ throw new Error(`Invalid PMate access key file: ${absolutePath}`);
22642
+ }
22643
+ if (expectedApp && parsed.app !== expectedApp) {
22644
+ throw new Error(`Access key app mismatch. expected=${expectedApp}, received=${parsed.app}`);
22645
+ }
22646
+ return parsed;
22647
+ }
22648
+ persistAccessKeySecret(result, out) {
22649
+ const keyFile = {
22650
+ type: "pmate-access-key",
22651
+ version: 1,
22652
+ id: result.key.id,
22653
+ app: result.key.app,
22654
+ name: result.key.name,
22655
+ namespace: result.key.namespace,
22656
+ secret: result.secret,
22657
+ expiresAt: result.key.expiresAt,
22658
+ createdAt: result.key.createdAt
22659
+ };
22660
+ const outPath = out ? this.resolveFilePath(out) : this.resolveDefaultAccessKeyPath(result.key.app, result.key.name);
22661
+ (0, import_node_fs22.mkdirSync)(import_node_path26.default.dirname(outPath), { recursive: true });
22662
+ (0, import_node_fs22.writeFileSync)(outPath, JSON.stringify(keyFile, null, 2), { mode: 384 });
22663
+ (0, import_node_fs22.chmodSync)(outPath, 384);
22664
+ }
22665
+ renderAccessKeyCreateResult(result, asJson, out) {
22666
+ if (asJson) {
22667
+ this.renderJson(result, true);
22668
+ return;
22669
+ }
22670
+ const outPath = out ? this.resolveFilePath(out) : this.resolveDefaultAccessKeyPath(result.key.app, result.key.name);
22671
+ ui.success(`Access key saved to ${outPath}`);
22672
+ ui.info(result.key.id, {
22673
+ app: result.key.app,
22674
+ name: result.key.name,
22675
+ namespace: result.key.namespace,
22676
+ scopes: result.key.scopes,
22677
+ expiresAt: result.key.expiresAt
22678
+ });
22679
+ }
22680
+ renderAccessKeys(items, asJson) {
22681
+ if (asJson) {
22682
+ this.renderJson({ items }, true);
22683
+ return;
22684
+ }
22685
+ if (items.length === 0) {
22686
+ ui.warn("No access keys found.");
22687
+ return;
22688
+ }
22689
+ for (const item of items) {
22690
+ ui.info(item.id, {
22691
+ app: item.app,
22692
+ name: item.name,
22693
+ namespace: item.namespace,
22694
+ scopes: item.scopes,
22695
+ status: item.status,
22696
+ expiresAt: item.expiresAt,
22697
+ lastUsedAt: item.lastUsedAt,
22698
+ rotationExpiresAt: item.rotationExpiresAt
22699
+ });
22700
+ }
22701
+ }
22702
+ async resolveLoginProfileId(login, app, authApiBaseUrl) {
22703
+ try {
22704
+ return await fetchDefaultProfileId({
22705
+ token: login.token,
22706
+ app,
22707
+ accountId: login.session.identity.accountId,
22708
+ authApiBaseUrl
22709
+ });
22710
+ } catch {
22711
+ return login.session.identity.accessKeyId ?? login.session.identity.accountId;
22712
+ }
22713
+ }
22540
22714
  signBotChallenge(challenge, privateKey) {
22541
22715
  const key = (0, import_node_crypto5.createPrivateKey)({
22542
22716
  key: Buffer.from(privateKey, "base64url"),
@@ -25339,6 +25513,104 @@ function parseAccountArgs(tokens) {
25339
25513
  }
25340
25514
  throw new Error(`Unknown account bot action: ${botAction}`);
25341
25515
  }
25516
+ if (action === "access-key") {
25517
+ const keyAction = rest.shift();
25518
+ if (!keyAction) {
25519
+ throw new Error("Missing account access-key action. Use: account access-key create|list|rotate|disable|login");
25520
+ }
25521
+ let app;
25522
+ let id;
25523
+ let name;
25524
+ let namespace2;
25525
+ const scopes = [];
25526
+ let expiresAt;
25527
+ let out;
25528
+ let key;
25529
+ let keyFile;
25530
+ let gracePeriodSeconds;
25531
+ let baseUrl2;
25532
+ let json2 = false;
25533
+ while (rest.length > 0) {
25534
+ const token = rest.shift();
25535
+ switch (token) {
25536
+ case "--app":
25537
+ app = parseNamespacedRef(requireValue8(rest, token), {
25538
+ entityLabel: "app ref"
25539
+ }).fullId;
25540
+ break;
25541
+ case "--name":
25542
+ name = requireValue8(rest, token);
25543
+ break;
25544
+ case "--namespace":
25545
+ namespace2 = requireValue8(rest, token);
25546
+ break;
25547
+ case "--scope":
25548
+ scopes.push(requireValue8(rest, token));
25549
+ break;
25550
+ case "--expires-at":
25551
+ expiresAt = requireValue8(rest, token);
25552
+ break;
25553
+ case "--expires-in":
25554
+ expiresAt = parseExpiresIn(requireValue8(rest, token));
25555
+ break;
25556
+ case "--grace-period":
25557
+ gracePeriodSeconds = parseDurationSeconds(requireValue8(rest, token), token);
25558
+ break;
25559
+ case "--out":
25560
+ out = requireValue8(rest, token);
25561
+ break;
25562
+ case "--key":
25563
+ key = requireValue8(rest, token);
25564
+ break;
25565
+ case "--key-file":
25566
+ keyFile = requireValue8(rest, token);
25567
+ break;
25568
+ case "--base-url":
25569
+ baseUrl2 = requireValue8(rest, token);
25570
+ break;
25571
+ case "--json":
25572
+ json2 = true;
25573
+ break;
25574
+ default:
25575
+ if ((keyAction === "rotate" || keyAction === "disable") && !token.startsWith("-")) {
25576
+ if (id) {
25577
+ throw new Error(`Unexpected account access-key id: ${token}`);
25578
+ }
25579
+ id = token;
25580
+ continue;
25581
+ }
25582
+ throw new Error(`Unknown account access-key option: ${token}`);
25583
+ }
25584
+ }
25585
+ if (keyAction === "create") {
25586
+ if (!app || !name || scopes.length === 0 || !expiresAt) {
25587
+ throw new Error("Usage: account access-key create --app <ns:app> --name <name> --scope <scope>... (--expires-at <iso> | --expires-in <duration>) [--namespace <ns>] [--out <path>] [--base-url <url>] [--json]");
25588
+ }
25589
+ return { type: "account" /* Account */, action: "access-key-create", app, name, namespace: namespace2, scopes, expiresAt, out, baseUrl: baseUrl2, json: json2 };
25590
+ }
25591
+ if (keyAction === "list") {
25592
+ return { type: "account" /* Account */, action: "access-key-list", app, namespace: namespace2, baseUrl: baseUrl2, json: json2 };
25593
+ }
25594
+ if (keyAction === "disable") {
25595
+ if (!id) {
25596
+ throw new Error("Usage: account access-key disable <id> [--base-url <url>] [--json]");
25597
+ }
25598
+ return { type: "account" /* Account */, action: "access-key-disable", id, baseUrl: baseUrl2, json: json2 };
25599
+ }
25600
+ if (keyAction === "rotate") {
25601
+ if (!id || !expiresAt) {
25602
+ throw new Error("Usage: account access-key rotate <id> (--expires-at <iso> | --expires-in <duration>) [--grace-period <duration>] [--out <path>] [--base-url <url>] [--json]");
25603
+ }
25604
+ return { type: "account" /* Account */, action: "access-key-rotate", id, expiresAt, gracePeriodSeconds, out, baseUrl: baseUrl2, json: json2 };
25605
+ }
25606
+ if (keyAction === "login") {
25607
+ if (!app || !key && !keyFile || key && keyFile) {
25608
+ throw new Error("Usage: account access-key login --app <ns:app> (--key <secret> | --key-file <path>) [--base-url <url>] [--json]");
25609
+ }
25610
+ return { type: "account" /* Account */, action: "access-key-login", app, key, keyFile, baseUrl: baseUrl2, json: json2 };
25611
+ }
25612
+ throw new Error(`Unknown account access-key action: ${keyAction}`);
25613
+ }
25342
25614
  if (action !== "role") {
25343
25615
  throw new Error(`Unknown account action: ${action}`);
25344
25616
  }
@@ -25420,6 +25692,19 @@ function parsePositiveInt(value, optionName) {
25420
25692
  }
25421
25693
  return parsed;
25422
25694
  }
25695
+ function parseExpiresIn(value) {
25696
+ return new Date(Date.now() + parseDurationSeconds(value, "--expires-in") * 1e3).toISOString();
25697
+ }
25698
+ function parseDurationSeconds(value, optionName) {
25699
+ const match = /^(\d+)(s|m|h|d)?$/.exec(value.trim());
25700
+ if (!match) {
25701
+ throw new Error(`${optionName} must be a duration like 3600s, 60m, 24h, or 30d`);
25702
+ }
25703
+ const amount = Number(match[1]);
25704
+ const unit = match[2] ?? "s";
25705
+ const multiplier = unit === "d" ? 86400 : unit === "h" ? 3600 : unit === "m" ? 60 : 1;
25706
+ return amount * multiplier;
25707
+ }
25423
25708
 
25424
25709
  // src/orgCommandParser.ts
25425
25710
  init_namespacedRef();
@@ -26173,6 +26458,11 @@ var HELP_TEXT = [
26173
26458
  ` ${import_chalk4.default.gray(" bot list --app <ns:app> [--base-url <url>] [--json]")}`,
26174
26459
  ` ${import_chalk4.default.gray(" bot login --app <ns:app> --name <name> (--key-file <path> | --private-key <key>) [--base-url <url>] [--json]")}`,
26175
26460
  ` ${import_chalk4.default.gray(" bot profile create --app <ns:app> --name <name> --nickname <nickname> [--role <role>] [--base-url <url>] [--json]")}`,
26461
+ ` ${import_chalk4.default.gray(" access-key create --app <ns:app> --name <name> --scope <scope>... (--expires-at <iso> | --expires-in <duration>) [--namespace <ns>] [--out <path>] [--base-url <url>] [--json]")}`,
26462
+ ` ${import_chalk4.default.gray(" access-key list [--app <ns:app>] [--namespace <ns>] [--base-url <url>] [--json]")}`,
26463
+ ` ${import_chalk4.default.gray(" access-key rotate <id> (--expires-at <iso> | --expires-in <duration>) [--grace-period <duration>] [--out <path>] [--base-url <url>] [--json]")}`,
26464
+ ` ${import_chalk4.default.gray(" access-key disable <id> [--base-url <url>] [--json]")}`,
26465
+ ` ${import_chalk4.default.gray(" access-key login --app <ns:app> (--key <secret> | --key-file <path>) [--base-url <url>] [--json]")}`,
26176
26466
  ` ${import_chalk4.default.gray(" default base URL: https://auth-api-v2.pmate.chat")}`,
26177
26467
  ` ${import_chalk4.default.cyan("org")} Manage orgs, org members, and org visible apps via apps-api`,
26178
26468
  ` ${import_chalk4.default.gray(" list [--base-url <url>] [--json]")}`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pmate/cli",
3
- "version": "0.25.8",
3
+ "version": "0.25.9",
4
4
  "description": "CLI utilities for the pMate project",
5
5
  "license": "MIT",
6
6
  "bin": {