@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 +12 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +77 -13
- package/package.json +1 -1
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
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(
|
|
94
|
-
return parseNamespacedRef(
|
|
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 [
|
|
225
|
+
for (const [appRef2, session] of Object.entries(candidate.sessions)) {
|
|
226
226
|
try {
|
|
227
|
-
if (assertValidAppRef(
|
|
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,
|
|
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,
|
|
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 (
|
|
17813
|
-
const { objectId } = parseNamespacedRef(
|
|
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,
|
|
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
|
|
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
|
-
|
|
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 };
|