@pmate/cli 0.29.10 → 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
@@ -2,6 +2,12 @@
2
2
 
3
3
  A TypeScript-powered command-line tool scaffold for future pMate automation.
4
4
 
5
+ ## Deploy from an isolated checkout
6
+
7
+ Local-build services can be released without modifying the conventional `~/dev/pmate` checkout. Use `pmate service deploy pmate:<service> --env prod --cwd /absolute/path/to/repo` for Vite, static or FC services. The registered service directory is resolved relative to that repository root; `--cwd` does not change the service's registered repository or environment.
8
+
9
+ Remote Node/Python/Rust deployments still use the registered repository and reject `--cwd` instead of silently ignoring it. Merge and push the intended release to `main` before deployment. The normal service deploy-history and log flow is unchanged.
10
+
5
11
  ## Development
6
12
 
7
13
  ```bash
@@ -228,3 +234,15 @@ Notes:
228
234
  - `pmate vercel` is a thin wrapper around the installed `vercel` CLI.
229
235
  - v1 only supports `vercel.scope` in PMate config.
230
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
@@ -942,6 +942,7 @@ type ServiceCommandArgs = {
942
942
  namespace: string;
943
943
  name: string;
944
944
  env?: string;
945
+ cwd?: string;
945
946
  baseUrl?: string;
946
947
  json: boolean;
947
948
  } | {
@@ -956,6 +957,8 @@ type ServiceCommandArgs = {
956
957
  } | {
957
958
  type: Command.Service;
958
959
  action: "log";
960
+ app?: string;
961
+ sinceMs?: number;
959
962
  namespace: string;
960
963
  name: string;
961
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);
@@ -18866,6 +18866,9 @@ var ServiceRuntimeRunner = class {
18866
18866
  this.envProvider = envProvider;
18867
18867
  }
18868
18868
  async deploy(service, options = {}) {
18869
+ if (options.cwd && !["vite", "static", "fc"].includes(this.getType(service))) {
18870
+ throw new Error("--cwd is only supported for local-build vite, static and fc services; remote deployments use the registered repository");
18871
+ }
18869
18872
  switch (this.getType(service)) {
18870
18873
  case "node-api":
18871
18874
  await this.deployNode(service, options);
@@ -19165,8 +19168,8 @@ var ServiceRuntimeRunner = class {
19165
19168
  async deployFc(service, options = {}) {
19166
19169
  const fc = this.getFc(service);
19167
19170
  this.validateFcConfig(service, fc);
19168
- const repoRoot = this.resolveRepoRoot(service);
19169
- const appDir = this.resolveLocalDir(service);
19171
+ const repoRoot = this.resolveRepoRoot(service, options.cwd);
19172
+ const appDir = this.resolveLocalDir(service, options.cwd);
19170
19173
  const packageJsonPath = import_node_path25.default.join(appDir, "package.json");
19171
19174
  if (!(0, import_node_fs20.existsSync)(packageJsonPath)) {
19172
19175
  throw new Error(`No package.json found at ${packageJsonPath}`);
@@ -20315,6 +20318,51 @@ function parseServiceRef(serviceId) {
20315
20318
 
20316
20319
  // src/commands/ServiceCommandHandler.ts
20317
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
20318
20366
  var ServiceCommandHandler = class {
20319
20367
  async handle(args) {
20320
20368
  const baseUrl = this.resolveBaseUrl(args.baseUrl);
@@ -20480,7 +20528,7 @@ var ServiceCommandHandler = class {
20480
20528
  const record = await registry.getService(args.namespace, args.name);
20481
20529
  const service = await this.materializeServiceEnv(registry, record, args.env);
20482
20530
  try {
20483
- await runtime.deploy(service, { envName: args.env });
20531
+ await runtime.deploy(service, { envName: args.env, ...args.cwd ? { cwd: args.cwd } : {} });
20484
20532
  await this.appendDeployHistory(registry, record.id, args.env, "success");
20485
20533
  } catch (error) {
20486
20534
  await this.appendDeployHistory(
@@ -20511,6 +20559,11 @@ var ServiceCommandHandler = class {
20511
20559
  return;
20512
20560
  }
20513
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
+ }
20514
20567
  const record = await registry.getService(args.namespace, args.name);
20515
20568
  const service = args.env ? await this.materializeServiceEnv(registry, record, args.env) : record;
20516
20569
  await runtime.log(service, {
@@ -23767,6 +23820,8 @@ function parseServiceArgs(tokens) {
23767
23820
  }
23768
23821
  let cwd;
23769
23822
  let page = 0;
23823
+ let app;
23824
+ let sinceMs;
23770
23825
  let history = false;
23771
23826
  let search;
23772
23827
  let follow = false;
@@ -23774,11 +23829,20 @@ function parseServiceArgs(tokens) {
23774
23829
  while (rest.length > 0) {
23775
23830
  const token = rest.shift();
23776
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;
23777
23841
  case "--base-url":
23778
23842
  baseUrl = requireValue4(rest, token);
23779
23843
  break;
23780
23844
  case "--cwd":
23781
- if (action !== "init") {
23845
+ if (action !== "init" && action !== "deploy") {
23782
23846
  throw new Error(`Unknown service ${action} option: ${token}`);
23783
23847
  }
23784
23848
  cwd = requireValue4(rest, token);
@@ -23790,7 +23854,8 @@ function parseServiceArgs(tokens) {
23790
23854
  env = requireValue4(rest, token);
23791
23855
  break;
23792
23856
  case "--page":
23793
- 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");
23794
23859
  break;
23795
23860
  case "--history":
23796
23861
  history = true;
@@ -23813,7 +23878,9 @@ function parseServiceArgs(tokens) {
23813
23878
  const namespace = parsedRef.namespace;
23814
23879
  const name = parsedRef.objectId;
23815
23880
  if (action === "log") {
23816
- 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 } : {} };
23817
23884
  }
23818
23885
  if (action === "deploy-logs" || action === "deploy-history") {
23819
23886
  return { type: "service" /* Service */, action, namespace, name, env, page, baseUrl, json };
@@ -23822,7 +23889,7 @@ function parseServiceArgs(tokens) {
23822
23889
  if (action === "deploy" && !env) {
23823
23890
  throw new Error("Missing required option --env for service deploy");
23824
23891
  }
23825
- return { type: "service" /* Service */, action, namespace, name, env, baseUrl, json };
23892
+ return { type: "service" /* Service */, action, namespace, name, env, ...cwd ? { cwd } : {}, baseUrl, json };
23826
23893
  }
23827
23894
  if (action === "init") {
23828
23895
  if (!env) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pmate/cli",
3
- "version": "0.29.10",
3
+ "version": "0.30.0",
4
4
  "description": "CLI utilities for the pMate project",
5
5
  "license": "MIT",
6
6
  "bin": {
@@ -11,16 +11,6 @@
11
11
  "files": [
12
12
  "dist"
13
13
  ],
14
- "scripts": {
15
- "build": "npm run clean && tsup --config tsup.config.ts && node scripts/copy-templates.js",
16
- "clean": "rimraf dist",
17
- "dev": "ts-node src/index.ts",
18
- "release": "pnpm publish",
19
- "start": "node dist/index.js",
20
- "test": "vitest run src/tests",
21
- "typecheck": "tsc --noEmit",
22
- "prepare": "npm run build"
23
- },
24
14
  "keywords": [
25
15
  "cli",
26
16
  "typescript",
@@ -67,10 +57,13 @@
67
57
  "typescript": "^5.9.3",
68
58
  "vitest": "^4.0.17"
69
59
  },
70
- "pnpm": {
71
- "overrides": {
72
- "@pmate/meta": "^1.3.1",
73
- "@pmate/utils": "^1.1.4"
74
- }
60
+ "scripts": {
61
+ "build": "npm run clean && tsup --config tsup.config.ts && node scripts/copy-templates.js",
62
+ "clean": "rimraf dist",
63
+ "dev": "ts-node src/index.ts",
64
+ "release": "pnpm publish",
65
+ "start": "node dist/index.js",
66
+ "test": "vitest run src/tests",
67
+ "typecheck": "tsc --noEmit"
75
68
  }
76
- }
69
+ }