@pmate/cli 0.29.11 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -234,3 +234,15 @@ Notes:
234
234
  - `pmate vercel` is a thin wrapper around the installed `vercel` CLI.
235
235
  - v1 only supports `vercel.scope` in PMate config.
236
236
  - `pmate vercel deploys` requires `--project <name>`.
237
+ # App-scoped access logs
238
+
239
+ Developers access logs use the same authenticated API as `dev.pmate.chat`, so CLI users cannot bypass App-Service associations or `pmate:dev` authorization by reading the Indexer directly.
240
+
241
+ ```sh
242
+ pmate service log pmate:developers-api --app pmate:developers --page 0 --json
243
+ pmate service log pmate:developers-api --app pmate:developers --search http.access --since-ms 1790000000000 --json
244
+ ```
245
+
246
+ Pages are zero-based. `--search` searches the last 24 hours; the ordinary list follows the Indexer's retention window (currently seven days). `--since-ms` is an epoch-millisecond lower bound. This mode does not accept `--env`, `--follow`, `--history`, or `--base-url`: environment filtering and follow are not implemented by the shared API and are never silently ignored. Without `--app`, the existing operational service-log command is unchanged.
247
+
248
+ A current CLI session is exchanged in memory for `pmate:developers`; the active App and session file are not changed. If login is required, run `pmate login --app pmate:developers`. Access denial never falls back to anonymous log reads. Neither source nor exchanged tokens are printed.
package/dist/index.d.ts CHANGED
@@ -957,6 +957,8 @@ type ServiceCommandArgs = {
957
957
  } | {
958
958
  type: Command.Service;
959
959
  action: "log";
960
+ app?: string;
961
+ sinceMs?: number;
960
962
  namespace: string;
961
963
  name: string;
962
964
  baseUrl?: string;
package/dist/index.js CHANGED
@@ -90,8 +90,8 @@ var init_namespacedRef = __esm({
90
90
  function buildSessionStorePath() {
91
91
  return import_node_path4.default.join(import_node_os3.default.homedir(), ".pmate", "session.json");
92
92
  }
93
- function assertValidAppRef(appRef) {
94
- return parseNamespacedRef(appRef, { entityLabel: "app ref" }).fullId;
93
+ function assertValidAppRef(appRef2) {
94
+ return parseNamespacedRef(appRef2, { entityLabel: "app ref" }).fullId;
95
95
  }
96
96
  function saveLoginSession(session, requestedApp = DEFAULT_APP_ID) {
97
97
  const store = loadExistingSessionStoreOrEmpty();
@@ -222,9 +222,9 @@ function isSessionStore(value) {
222
222
  if (!(normalizedActiveApp in candidate.sessions)) {
223
223
  return false;
224
224
  }
225
- for (const [appRef, session] of Object.entries(candidate.sessions)) {
225
+ for (const [appRef2, session] of Object.entries(candidate.sessions)) {
226
226
  try {
227
- if (assertValidAppRef(appRef) !== appRef) {
227
+ if (assertValidAppRef(appRef2) !== appRef2) {
228
228
  return false;
229
229
  }
230
230
  } catch {
@@ -17751,11 +17751,11 @@ function isSupportedImageDataUrl(value) {
17751
17751
  const mimeType = match[1]?.toLowerCase();
17752
17752
  return mimeType === "image/png" || mimeType === "image/webp" || mimeType === "image/svg+xml" || mimeType === "image/x-icon";
17753
17753
  }
17754
- async function withDefaultSvgIconVariant(icon, icons, cwd, appRef) {
17754
+ async function withDefaultSvgIconVariant(icon, icons, cwd, appRef2) {
17755
17755
  const variant = await renderAppIconVariant({
17756
17756
  icon,
17757
17757
  name: DEFAULT_APP_ICON_VARIANT_NAME,
17758
- appRef,
17758
+ appRef: appRef2,
17759
17759
  width: 512,
17760
17760
  height: 512,
17761
17761
  format: "webp",
@@ -17796,7 +17796,7 @@ function buildAppIconVariantObjectKey(input) {
17796
17796
  `${resolveAppIconAssetStem(input.icon, input.appRef)}-${input.name}.${extensionForIconFormat(input.format)}`
17797
17797
  );
17798
17798
  }
17799
- function resolveAppIconAssetStem(icon, appRef) {
17799
+ function resolveAppIconAssetStem(icon, appRef2) {
17800
17800
  if (icon) {
17801
17801
  try {
17802
17802
  const url = new URL(icon);
@@ -17809,8 +17809,8 @@ function resolveAppIconAssetStem(icon, appRef) {
17809
17809
  } catch {
17810
17810
  }
17811
17811
  }
17812
- if (appRef) {
17813
- const { objectId } = parseNamespacedRef(appRef, {
17812
+ if (appRef2) {
17813
+ const { objectId } = parseNamespacedRef(appRef2, {
17814
17814
  entityLabel: "app ref"
17815
17815
  });
17816
17816
  const normalized = sanitizeAppIconAssetStem(objectId);
@@ -18052,9 +18052,9 @@ var AppCommandHandler = class {
18052
18052
  throw new Error(`Invalid JSON in ${absolutePath}: ${message}`);
18053
18053
  }
18054
18054
  }
18055
- async preparePayload(filePath, appRef) {
18055
+ async preparePayload(filePath, appRef2) {
18056
18056
  const absolutePath = this.resolveFilePath(filePath);
18057
- return prepareAppPayload(this.readJsonFile(filePath), import_node_path23.default.dirname(absolutePath), { appRef });
18057
+ return prepareAppPayload(this.readJsonFile(filePath), import_node_path23.default.dirname(absolutePath), { appRef: appRef2 });
18058
18058
  }
18059
18059
  resolveFilePath(filePath) {
18060
18060
  return import_node_path23.default.resolve(process.cwd(), filePath);
@@ -20318,6 +20318,51 @@ function parseServiceRef(serviceId) {
20318
20318
 
20319
20319
  // src/commands/ServiceCommandHandler.ts
20320
20320
  init_namespacedRef();
20321
+
20322
+ // src/services/DeveloperLogsClient.ts
20323
+ init_sessionStore();
20324
+ init_namespacedRef();
20325
+ var appRef = "pmate:developers";
20326
+ async function queryDeveloperLogs(input, fetcher = fetch) {
20327
+ parseNamespacedRef(input.app, { entityLabel: "app ref" });
20328
+ parseNamespacedRef(input.service, { entityLabel: "service ref" });
20329
+ if (!Number.isSafeInteger(input.page) || input.page < 0) throw new Error("Invalid log page");
20330
+ if (input.sinceMs !== void 0 && (!Number.isSafeInteger(input.sinceMs) || input.sinceMs < 0)) throw new Error("Invalid log time");
20331
+ if (input.keyword && input.keyword.length > 256) throw new Error("Log search must be at most 256 characters");
20332
+ const session = readRequiredSession({ preferredApp: appRef, fallbackToActiveApp: true });
20333
+ if (Date.parse(session.expiresAt) <= Date.now() || !Number.isFinite(Date.parse(session.expiresAt)))
20334
+ throw new Error("Session expired. Run pmate login --app pmate:developers");
20335
+ async function request2(url, init) {
20336
+ let response;
20337
+ try {
20338
+ response = await fetcher(url, { ...init, redirect: "error", signal: AbortSignal.timeout(3e4) });
20339
+ } catch {
20340
+ throw new Error("Developers log request failed; no anonymous fallback was attempted");
20341
+ }
20342
+ if (!response.ok) throw new Error(`Developers log request failed (HTTP ${response.status}); check login, pmate:dev role and App-Service access`);
20343
+ let body;
20344
+ try {
20345
+ body = await response.json();
20346
+ } catch {
20347
+ throw new Error("Invalid Developers API response");
20348
+ }
20349
+ if (!body?.success) throw new Error("Developers API rejected the request");
20350
+ return body.data;
20351
+ }
20352
+ const exchanged = await request2("https://auth-api-v2.pmate.chat/session/exchange", {
20353
+ method: "POST",
20354
+ headers: { Authorization: `Bearer ${session.token}`, "Content-Type": "application/json" },
20355
+ body: JSON.stringify({ app: appRef })
20356
+ });
20357
+ if (exchanged?.session?.identity?.app !== appRef || exchanged.session.identity.accountId !== session.accountId || typeof exchanged.token !== "string" || !exchanged.token)
20358
+ throw new Error("Developers session identity mismatch");
20359
+ const params = new URLSearchParams({ app: input.app, service: input.service, page: String(input.page) });
20360
+ if (input.keyword) params.set("keyword", input.keyword);
20361
+ if (input.sinceMs !== void 0) params.set("sinceMs", String(input.sinceMs));
20362
+ return request2(`https://dev-api.pmate.chat/api/logs?${params}`, { headers: { Authorization: `Bearer ${exchanged.token}` } });
20363
+ }
20364
+
20365
+ // src/commands/ServiceCommandHandler.ts
20321
20366
  var ServiceCommandHandler = class {
20322
20367
  async handle(args) {
20323
20368
  const baseUrl = this.resolveBaseUrl(args.baseUrl);
@@ -20514,6 +20559,11 @@ var ServiceCommandHandler = class {
20514
20559
  return;
20515
20560
  }
20516
20561
  case "log": {
20562
+ if (args.app) {
20563
+ const result = await queryDeveloperLogs({ app: args.app, service: `${args.namespace}:${args.name}`, page: args.page, keyword: args.search, sinceMs: args.sinceMs });
20564
+ console.log(JSON.stringify(result, null, args.json ? void 0 : 2));
20565
+ return;
20566
+ }
20517
20567
  const record = await registry.getService(args.namespace, args.name);
20518
20568
  const service = args.env ? await this.materializeServiceEnv(registry, record, args.env) : record;
20519
20569
  await runtime.log(service, {
@@ -23770,6 +23820,8 @@ function parseServiceArgs(tokens) {
23770
23820
  }
23771
23821
  let cwd;
23772
23822
  let page = 0;
23823
+ let app;
23824
+ let sinceMs;
23773
23825
  let history = false;
23774
23826
  let search;
23775
23827
  let follow = false;
@@ -23777,6 +23829,15 @@ function parseServiceArgs(tokens) {
23777
23829
  while (rest.length > 0) {
23778
23830
  const token = rest.shift();
23779
23831
  switch (token) {
23832
+ case "--app":
23833
+ if (action !== "log") throw new Error(`Unknown service ${action} option: ${token}`);
23834
+ app = parseNamespacedRef(requireValue4(rest, token), { entityLabel: "app ref" }).fullId;
23835
+ break;
23836
+ case "--since-ms":
23837
+ if (action !== "log") throw new Error(`Unknown service ${action} option: ${token}`);
23838
+ sinceMs = Number(requireValue4(rest, token));
23839
+ if (!Number.isSafeInteger(sinceMs) || sinceMs < 0) throw new Error("Invalid --since-ms");
23840
+ break;
23780
23841
  case "--base-url":
23781
23842
  baseUrl = requireValue4(rest, token);
23782
23843
  break;
@@ -23793,7 +23854,8 @@ function parseServiceArgs(tokens) {
23793
23854
  env = requireValue4(rest, token);
23794
23855
  break;
23795
23856
  case "--page":
23796
- page = Number.parseInt(requireValue4(rest, token), 10);
23857
+ page = Number(requireValue4(rest, token));
23858
+ if (!Number.isSafeInteger(page) || page < 0) throw new Error("Invalid --page");
23797
23859
  break;
23798
23860
  case "--history":
23799
23861
  history = true;
@@ -23816,7 +23878,9 @@ function parseServiceArgs(tokens) {
23816
23878
  const namespace = parsedRef.namespace;
23817
23879
  const name = parsedRef.objectId;
23818
23880
  if (action === "log") {
23819
- return { type: "service" /* Service */, action, namespace, name, baseUrl, json, page, history, search, follow, env };
23881
+ if (app && (history || follow || env || baseUrl)) throw new Error("App logs do not support --history, --follow, --env or --base-url; use --page, --search and --since-ms");
23882
+ if (sinceMs !== void 0 && !app) throw new Error("--since-ms requires --app");
23883
+ return { type: "service" /* Service */, action, namespace, name, baseUrl, json, page, history, search, follow, env, ...app ? { app } : {}, ...sinceMs !== void 0 ? { sinceMs } : {} };
23820
23884
  }
23821
23885
  if (action === "deploy-logs" || action === "deploy-history") {
23822
23886
  return { type: "service" /* Service */, action, namespace, name, env, page, baseUrl, json };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pmate/cli",
3
- "version": "0.29.11",
3
+ "version": "0.30.0",
4
4
  "description": "CLI utilities for the pMate project",
5
5
  "license": "MIT",
6
6
  "bin": {