@pmate/cli 0.33.1 → 0.34.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 +21 -3
- package/dist/index.d.ts +9 -1
- package/dist/index.js +99 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -236,18 +236,36 @@ Notes:
|
|
|
236
236
|
- `pmate vercel deploys` requires `--project <name>`.
|
|
237
237
|
# Service logs
|
|
238
238
|
|
|
239
|
-
Service logs use the same authenticated API as `dev.pmate.chat`.
|
|
239
|
+
Service logs use the same authenticated API as `dev.pmate.chat`. Sources are registered Service/topic pairs on the dedicated `log` chain.
|
|
240
240
|
|
|
241
241
|
```sh
|
|
242
242
|
pmate service log pmate:issues-api --topic application --page 0 --json
|
|
243
|
-
pmate service log pmate:issues-api --topic nginx --field status=500
|
|
244
|
-
pmate service log pmate:issues-api --field requestId=<id>
|
|
243
|
+
pmate service log pmate:issues-api --topic nginx --field status=500
|
|
244
|
+
pmate service log pmate:issues-api --field requestId=<id>
|
|
245
245
|
```
|
|
246
246
|
|
|
247
247
|
Pages are zero-based. Lists and `--search` cover the chain's configured retention window (30 days in production). `--since-ms` adds an epoch-millisecond lower bound. `--field name=value` (repeatable, scalar JSON values) and `--env` filter on the server. Topics are independent sources such as `application`, `nginx`, `pm2`, `deployment`, `pv`, and `vitals`; custom fields live in `data`. Old `--type`, `--level`, `--source`, and `--request-id` flags are removed. The legacy `--app` argument is optional and does not select a different source.
|
|
248
248
|
|
|
249
249
|
A current CLI session is exchanged in memory for `pmate:developers`; the active App and session file are unchanged. If needed, run `pmate login --app pmate:developers`. Permission denial never falls back to anonymous Indexer reads. Credentials are never printed.
|
|
250
250
|
|
|
251
|
+
### Log analysis tools
|
|
252
|
+
|
|
253
|
+
`pmate logs` exposes the same read-only tools used by Meili. The server calculates results over a bounded retained window, with query IDs and coverage metadata; the model does not count sampled pages.
|
|
254
|
+
|
|
255
|
+
```sh
|
|
256
|
+
pmate logs schema --json
|
|
257
|
+
pmate logs query pmate:apps-api --topic application --field level=error --json
|
|
258
|
+
pmate logs aggregate pmate:hello --topic pv --from 2026-10-08T00:00:00+08:00 --to 2026-10-08T10:00:00+08:00 --group-by path --interval hour --timezone Asia/Shanghai --json
|
|
259
|
+
pmate logs trace pmate:developers-api --request-id REQUEST_ID --json
|
|
260
|
+
pmate logs compare pmate:hello --topic pv --from 2026-10-08T00:00:00+08:00 --to 2026-10-08T10:00:00+08:00 --compare-from 2026-10-07T00:00:00+08:00 --compare-to 2026-10-07T10:00:00+08:00 --json
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
PV counts page-view events; sessions deduplicate appId/accessId; Profile UV deduplicates appId/profileId. Missing IDs and anonymous events remain separate. Query/trace default to application; aggregate/compare default to pv. Vitals percentiles require `--field name=LCP` (or another specific metric). Current user classification is visit frequency within the query window; first-ever/new users cannot be inferred from retention-limited history.
|
|
264
|
+
|
|
265
|
+
Time windows are `[from,to)` and at most 31 days, inside the configured retention. Times accept Unix milliseconds or ISO with explicit timezone. Comparisons require equal duration. More than 10,000 records/100 pages/100 groups is rejected instead of reporting partial totals. Indexing and browser telemetry remain best effort; `completeRead` is not proof of complete real-world activity. Query pages are zero-based.
|
|
266
|
+
|
|
267
|
+
Meili's Host may pass `--actor-profile-id` with `PMATE_LOG_DELEGATE_TOKEN` in the child environment. This mode never reads or exchanges the host's personal CLI session. Auth API verifies the allowlisted Bot and the actual sender's pmate:dev permission on every call. Models cannot supply the actor or token.
|
|
268
|
+
|
|
251
269
|
### Package a desktop app
|
|
252
270
|
|
|
253
271
|
`pmate app build <ns:app> --target electron --config <file>` builds the configured web app, embeds its static output in an Electron ASAR, and produces a macOS DMG and ZIP. It does not require registry login. The first build downloads pinned Electron and electron-builder versions. Current targets: macOS `arm64` and `x64`, built on macOS.
|
package/dist/index.d.ts
CHANGED
|
@@ -31,6 +31,7 @@ declare enum Command {
|
|
|
31
31
|
Store = "store",
|
|
32
32
|
App = "app",
|
|
33
33
|
Service = "service",
|
|
34
|
+
Logs = "logs",
|
|
34
35
|
Vercel = "vercel",
|
|
35
36
|
Host = "host",
|
|
36
37
|
Quiz = "quiz"
|
|
@@ -1143,7 +1144,14 @@ type VercelDeploysCommandArgs = {
|
|
|
1143
1144
|
lastWeek: boolean;
|
|
1144
1145
|
};
|
|
1145
1146
|
type VercelCommandArgs = VercelProjectsCommandArgs | VercelDeploysCommandArgs;
|
|
1146
|
-
type
|
|
1147
|
+
type LogsCommandArgs = {
|
|
1148
|
+
type: Command.Logs;
|
|
1149
|
+
tool: "schema" | "query" | "aggregate" | "trace" | "compare";
|
|
1150
|
+
params: Record<string, string>;
|
|
1151
|
+
json: boolean;
|
|
1152
|
+
actorProfileId?: string;
|
|
1153
|
+
};
|
|
1154
|
+
type CommandArgs = LogsCommandArgs | NoneCommandArgs | ListCommandArgs | LoginCommandArgs | AddDnsCommandArgs | StsCommandArgs | BucketCommandArgs | DeployCommandArgs | LogCommandArgs | InitCommandArgs | DeleteCommandArgs | DevCommandArgs | EnvCommandArgs | CommitCommandArgs | SummaryCommandArgs | QuizCommandArgs | ServiceCommandArgs | LinearCommandArgs | NotionCommandArgs | OssCommandArgs | QrcodeCommandArgs | RandomCommandArgs | CollectCommandArgs | ChatRoomCommandArgs | FunctionCommandArgs | WorkerCommandArgs | CallCommandArgs | NsCommandArgs | RoleCommandArgs | AccountCommandArgs | OrgCommandArgs | StoreCommandArgs | AppCommandArgs | VercelCommandArgs | HostCommandArgs;
|
|
1147
1155
|
|
|
1148
1156
|
interface ParsedArgs {
|
|
1149
1157
|
help: boolean;
|
package/dist/index.js
CHANGED
|
@@ -18750,13 +18750,19 @@ async function queryDeveloperLogs(input, fetcher = fetch) {
|
|
|
18750
18750
|
if (!Number.isSafeInteger(input.page) || input.page < 0) throw new Error("Invalid log page");
|
|
18751
18751
|
if (input.sinceMs !== void 0 && (!Number.isSafeInteger(input.sinceMs) || input.sinceMs < 0)) throw new Error("Invalid log time");
|
|
18752
18752
|
if (input.keyword && input.keyword.length > 256) throw new Error("Log search must be at most 256 characters");
|
|
18753
|
-
const
|
|
18754
|
-
if (
|
|
18755
|
-
|
|
18756
|
-
|
|
18753
|
+
const params = new URLSearchParams({ service: input.service, page: String(input.page), topic: input.topic ?? "application" });
|
|
18754
|
+
if (input.environment) params.set("environment", input.environment);
|
|
18755
|
+
if (input.fields) params.set("fields", JSON.stringify(input.fields));
|
|
18756
|
+
if (input.keyword) params.set("keyword", input.keyword);
|
|
18757
|
+
if (input.sinceMs !== void 0) params.set("sinceMs", String(input.sinceMs));
|
|
18758
|
+
return requestDeveloperLogTool("query", params, fetcher);
|
|
18759
|
+
}
|
|
18760
|
+
async function requestDeveloperLogTool(tool, params, fetcher = fetch, delegate) {
|
|
18761
|
+
if (!["schema", "query", "aggregate", "trace", "compare"].includes(tool)) throw new Error("Unknown log tool");
|
|
18762
|
+
async function request2(url2, init) {
|
|
18757
18763
|
let response;
|
|
18758
18764
|
try {
|
|
18759
|
-
response = await fetcher(
|
|
18765
|
+
response = await fetcher(url2, { ...init, redirect: "error", signal: AbortSignal.timeout(3e4) });
|
|
18760
18766
|
} catch {
|
|
18761
18767
|
throw new Error("Developers log request failed; no anonymous fallback was attempted");
|
|
18762
18768
|
}
|
|
@@ -18770,6 +18776,15 @@ async function queryDeveloperLogs(input, fetcher = fetch) {
|
|
|
18770
18776
|
if (!body?.success) throw new Error("Developers API rejected the request");
|
|
18771
18777
|
return body.data;
|
|
18772
18778
|
}
|
|
18779
|
+
const suffix = tool === "query" ? "" : `/${tool}`;
|
|
18780
|
+
const url = `https://dev-api.pmate.chat/api/logs${suffix}?${params}`;
|
|
18781
|
+
if (delegate) {
|
|
18782
|
+
if (!delegate.token || !/^[a-zA-Z0-9_.:@-]{1,128}$/.test(delegate.profileId)) throw new Error("Invalid log delegation");
|
|
18783
|
+
return request2(url, { headers: { Authorization: `Bearer ${delegate.token}`, "x-pmate-actor-profile-id": delegate.profileId } });
|
|
18784
|
+
}
|
|
18785
|
+
const session = readRequiredSession({ preferredApp: appRef, fallbackToActiveApp: true });
|
|
18786
|
+
if (Date.parse(session.expiresAt) <= Date.now() || !Number.isFinite(Date.parse(session.expiresAt)))
|
|
18787
|
+
throw new Error("Session expired. Run pmate login --app pmate:developers");
|
|
18773
18788
|
const exchanged = await request2("https://auth-api-v2.pmate.chat/session/exchange", {
|
|
18774
18789
|
method: "POST",
|
|
18775
18790
|
headers: { Authorization: `Bearer ${session.token}`, "Content-Type": "application/json" },
|
|
@@ -18777,12 +18792,7 @@ async function queryDeveloperLogs(input, fetcher = fetch) {
|
|
|
18777
18792
|
});
|
|
18778
18793
|
if (exchanged?.session?.identity?.app !== appRef || exchanged.session.identity.accountId !== session.accountId || typeof exchanged.token !== "string" || !exchanged.token)
|
|
18779
18794
|
throw new Error("Developers session identity mismatch");
|
|
18780
|
-
|
|
18781
|
-
if (input.environment) params.set("environment", input.environment);
|
|
18782
|
-
if (input.fields) params.set("fields", JSON.stringify(input.fields));
|
|
18783
|
-
if (input.keyword) params.set("keyword", input.keyword);
|
|
18784
|
-
if (input.sinceMs !== void 0) params.set("sinceMs", String(input.sinceMs));
|
|
18785
|
-
return request2(`https://dev-api.pmate.chat/api/logs?${params}`, { headers: { Authorization: `Bearer ${exchanged.token}` } });
|
|
18795
|
+
return request2(url, { headers: { Authorization: `Bearer ${exchanged.token}` } });
|
|
18786
18796
|
}
|
|
18787
18797
|
|
|
18788
18798
|
// src/services/ServiceRuntimeRunner.ts
|
|
@@ -24744,6 +24754,74 @@ function parseServiceEnvArgs(appId, tokens) {
|
|
|
24744
24754
|
throw new Error(`Unknown service env action: ${action}`);
|
|
24745
24755
|
}
|
|
24746
24756
|
|
|
24757
|
+
// src/logsCommandParser.ts
|
|
24758
|
+
init_namespacedRef();
|
|
24759
|
+
function parseLogsArgs(input) {
|
|
24760
|
+
const rest = [...input], tool = rest.shift();
|
|
24761
|
+
if (!["schema", "query", "aggregate", "trace", "compare"].includes(tool)) throw Error("Usage: pmate logs schema|query|aggregate|trace|compare [ns:service] [options]");
|
|
24762
|
+
const params = {}, fields = {};
|
|
24763
|
+
let json = false, actorProfileId;
|
|
24764
|
+
if (rest[0] && !rest[0].startsWith("--")) params.service = parseNamespacedRef(rest.shift(), { entityLabel: "service" }).fullId;
|
|
24765
|
+
const keys = { "--topic": "topic", "--env": "environment", "--search": "keyword", "--group-by": "groupBy", "--interval": "interval", "--timezone": "timezone", "--request-id": "requestId", "--page": "page" };
|
|
24766
|
+
const times = { "--from": "from", "--to": "to", "--compare-from": "compareFrom", "--compare-to": "compareTo" };
|
|
24767
|
+
while (rest.length) {
|
|
24768
|
+
const flag = rest.shift();
|
|
24769
|
+
if (flag === "--json") {
|
|
24770
|
+
json = true;
|
|
24771
|
+
continue;
|
|
24772
|
+
}
|
|
24773
|
+
const value = rest.shift();
|
|
24774
|
+
if (value === void 0 || value.startsWith("--")) throw Error(`Missing value for ${flag}`);
|
|
24775
|
+
if (flag === "--actor-profile-id") {
|
|
24776
|
+
if (!/^[a-zA-Z0-9_.:@-]{1,128}$/.test(value)) throw Error("Invalid actor profile");
|
|
24777
|
+
actorProfileId = value;
|
|
24778
|
+
} else if (flag === "--field") {
|
|
24779
|
+
const match = /^([a-zA-Z0-9_-]+(?:\.[a-zA-Z0-9_-]+)*)=(.*)$/.exec(value);
|
|
24780
|
+
if (!match || match[1].length > 128 || match[2].length > 256) throw Error("Use --field name=value");
|
|
24781
|
+
let parsed = match[2];
|
|
24782
|
+
try {
|
|
24783
|
+
parsed = JSON.parse(match[2]);
|
|
24784
|
+
} catch {
|
|
24785
|
+
}
|
|
24786
|
+
if (!["string", "number", "boolean"].includes(typeof parsed)) throw Error("Field must be a scalar");
|
|
24787
|
+
fields[match[1]] = parsed;
|
|
24788
|
+
if (Object.keys(fields).length > 20) throw Error("At most 20 field filters");
|
|
24789
|
+
} else if (times[flag]) {
|
|
24790
|
+
if (!/^\d+$/.test(value) && !/^\d{4}-\d{2}-\d{2}T.*(?:Z|[+-]\d{2}:\d{2})$/.test(value)) throw Error("Use Unix milliseconds or ISO time with an explicit timezone");
|
|
24791
|
+
const milliseconds = /^\d+$/.test(value) ? Number(value) : Date.parse(value);
|
|
24792
|
+
if (!Number.isSafeInteger(milliseconds) || milliseconds < 0) throw Error(`Invalid ${flag}`);
|
|
24793
|
+
params[times[flag]] = String(milliseconds);
|
|
24794
|
+
} else if (keys[flag]) params[keys[flag]] = value;
|
|
24795
|
+
else throw Error(`Unknown log option: ${flag}`);
|
|
24796
|
+
}
|
|
24797
|
+
if (tool !== "schema" && !params.service) throw Error("Service reference required");
|
|
24798
|
+
if (tool === "schema" && (Object.keys(params).length || Object.keys(fields).length)) throw Error("logs schema only accepts --json");
|
|
24799
|
+
if (tool === "aggregate" || tool === "compare") {
|
|
24800
|
+
if (!params.from || !params.to || Number(params.from) >= Number(params.to)) throw Error("An increasing --from/--to window is required");
|
|
24801
|
+
if (params.page || params.requestId) throw Error("Use --field requestId=value for analysis; --page only applies to query/trace");
|
|
24802
|
+
} else {
|
|
24803
|
+
if (params.groupBy || params.interval || params.timezone || params.compareFrom || params.compareTo) throw Error("Grouping and comparison options require aggregate/compare");
|
|
24804
|
+
if (params.from) {
|
|
24805
|
+
params.sinceMs = params.from;
|
|
24806
|
+
delete params.from;
|
|
24807
|
+
}
|
|
24808
|
+
if (params.to) {
|
|
24809
|
+
params.untilMs = params.to;
|
|
24810
|
+
delete params.to;
|
|
24811
|
+
}
|
|
24812
|
+
}
|
|
24813
|
+
if (tool !== "compare" && (params.compareFrom || params.compareTo)) throw Error("Comparison bounds require compare");
|
|
24814
|
+
if (tool === "compare" && (!params.compareFrom || !params.compareTo)) throw Error("compare requires --compare-from and --compare-to");
|
|
24815
|
+
if (tool === "trace" && !params.requestId) throw Error("trace requires --request-id");
|
|
24816
|
+
if (tool === "query" && params.requestId) {
|
|
24817
|
+
fields.requestId = params.requestId;
|
|
24818
|
+
delete params.requestId;
|
|
24819
|
+
}
|
|
24820
|
+
if (params.page !== void 0 && (!/^\d+$/.test(params.page) || !Number.isSafeInteger(Number(params.page)))) throw Error("Invalid --page");
|
|
24821
|
+
if (Object.keys(fields).length) params.fields = JSON.stringify(fields);
|
|
24822
|
+
return { type: "logs" /* Logs */, tool, params, json, ...actorProfileId ? { actorProfileId } : {} };
|
|
24823
|
+
}
|
|
24824
|
+
|
|
24747
24825
|
// src/roleCommandParser.ts
|
|
24748
24826
|
function parseRoleArgs(tokens) {
|
|
24749
24827
|
const rest = [...tokens];
|
|
@@ -26147,6 +26225,7 @@ var HELP_TEXT = [
|
|
|
26147
26225
|
import_chalk4.default.bold(`pmate v${pkg.version}`),
|
|
26148
26226
|
"",
|
|
26149
26227
|
import_chalk4.default.bold("Usage:"),
|
|
26228
|
+
" logs schema|query|aggregate|trace|compare [service] --from <ISO|ms> --to <ISO|ms> --topic <topic> --field name=value --json",
|
|
26150
26229
|
` ${import_chalk4.default.cyan("pmate")} ${import_chalk4.default.gray("[options]")}`,
|
|
26151
26230
|
"",
|
|
26152
26231
|
import_chalk4.default.bold("Options:"),
|
|
@@ -26501,6 +26580,7 @@ function parseCommandArgs(args) {
|
|
|
26501
26580
|
if (args.rest[0] === "app") {
|
|
26502
26581
|
return parseAppArgs(args.rest.slice(1));
|
|
26503
26582
|
}
|
|
26583
|
+
if (args.rest[0] === "logs") return parseLogsArgs(args.rest.slice(1));
|
|
26504
26584
|
if (args.rest[0] === "service") {
|
|
26505
26585
|
return parseServiceArgs(args.rest.slice(1));
|
|
26506
26586
|
}
|
|
@@ -28196,6 +28276,14 @@ async function main() {
|
|
|
28196
28276
|
await handler.handle(commandArgs);
|
|
28197
28277
|
return;
|
|
28198
28278
|
}
|
|
28279
|
+
case "logs" /* Logs */: {
|
|
28280
|
+
const delegate = commandArgs.actorProfileId ? { profileId: commandArgs.actorProfileId, token: process.env.PMATE_LOG_DELEGATE_TOKEN ?? "" } : void 0;
|
|
28281
|
+
if (delegate && !delegate.token) throw new Error("PMATE_LOG_DELEGATE_TOKEN is required for delegated log tools");
|
|
28282
|
+
if (!delegate && process.env.PMATE_LOG_DELEGATE_TOKEN) throw new Error("Delegated log tools require --actor-profile-id");
|
|
28283
|
+
const result = await requestDeveloperLogTool(commandArgs.tool, new URLSearchParams(commandArgs.params), fetch, delegate);
|
|
28284
|
+
console.log(JSON.stringify(result, null, commandArgs.json ? void 0 : 2));
|
|
28285
|
+
return;
|
|
28286
|
+
}
|
|
28199
28287
|
case "service" /* Service */: {
|
|
28200
28288
|
const handler = new ServiceCommandHandler();
|
|
28201
28289
|
await handler.handle(commandArgs);
|