@pmate/cli 0.34.0 → 0.35.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
@@ -293,3 +293,7 @@ Config (paths relative to the config file, `web.dist` relative to `web.cwd`):
293
293
  Use `--dry-run` to inspect the plan, `--dir` for an unpacked `.app`, or `--signed` to require a configured macOS signing identity. Default builds are ad-hoc signed for local use; they are not notarized distribution releases. Signed builds use electron-builder's standard signing/notarization environment variables; verify notarization separately before distributing.
294
294
 
295
295
  The shell serves the bundled renderer under its existing exact HTTPS origin, preserving authentication callbacks and API CORS. Other origins remain normal network requests. Static resources are hashed and checked at launch; missing assets return 404. The renderer is sandboxed with Node disabled and no privileged preload bridge. Sign-in, chat and inference still require internet. Updates require a new package. Each build prints its artifact directory and a resource/signing report; no build uploads or auto-updates occur.
296
+
297
+ ### 日志统计默认口径
298
+
299
+ 请求次数与日均可使用 `pmate logs aggregate pmate:issues-api --metric count --topic application --field event=http.access --interval day --timezone Asia/Shanghai --from <ISO> --to <ISO> --json`。计数由数据库完成,返回 `count`、`durationDays`、`averagePerDay`,时间桶包含零值;不返回 UV 或延迟百分位。JSON 模式失败以非零状态退出并返回固定 `error.code`,区分权限、保留期、查询预算和上游故障,避免泄露上游原始错误。
package/dist/index.js CHANGED
@@ -17612,7 +17612,7 @@ async function buildDesktop(args) {
17612
17612
  files: ["**/*"],
17613
17613
  asar: true,
17614
17614
  artifactName: "${productName}-${version}-${arch}.${ext}",
17615
- mac: { icon, target: args.dir ? ["dir"] : ["dmg", "zip"], category: "public.app-category.social-networking", hardenedRuntime: args.signed, extendInfo: { NSMicrophoneUsageDescription: `${c.productName} uses your microphone only when you record a voice message.` }, ...args.signed ? {} : { identity: "-", notarize: false } },
17615
+ mac: { icon, target: args.dir ? ["dir"] : ["dmg", "zip"], category: "public.app-category.social-networking", hardenedRuntime: args.signed, extendInfo: { NSMicrophoneUsageDescription: `${c.productName} uses your microphone for voice calls and voice messages.` }, ...args.signed ? {} : { identity: "-", notarize: false } },
17616
17616
  forceCodeSigning: args.signed
17617
17617
  };
17618
17618
  const configPath = import_node_path23.default.join(staging, "builder.json");
@@ -18743,6 +18743,30 @@ function capitalize(value) {
18743
18743
  // src/services/DeveloperLogsClient.ts
18744
18744
  init_sessionStore();
18745
18745
  init_namespacedRef();
18746
+ var DeveloperLogError = class extends Error {
18747
+ constructor(code, status, message) {
18748
+ super(message);
18749
+ this.code = code;
18750
+ this.status = status;
18751
+ }
18752
+ };
18753
+ function logResponseError(status, message) {
18754
+ const errors = {
18755
+ 400: ["logs_invalid_query", "Invalid log query; correct parameters before retrying"],
18756
+ 401: ["logs_unauthenticated", "Log session expired or invalid"],
18757
+ 403: ["logs_forbidden", "Log access denied for this sender"],
18758
+ 404: ["logs_not_found", "Requested log service or resource was not found"],
18759
+ 409: ["logs_index_changed", "Log index changed; retry the same query once"],
18760
+ 422: ["logs_query_limit", "Log query exceeded a retention or analysis limit"],
18761
+ 429: ["logs_rate_limited", "Log service rate limit reached"]
18762
+ };
18763
+ if (status === 422 && message === "Requested window extends beyond the safe retained log window")
18764
+ return new DeveloperLogError("logs_retention", status, "Requested time window is outside retained logs");
18765
+ if (status === 422 && ["Analysis exceeds 100 pages; reduce the time window", "Analysis exceeds 10000 records; reduce the time window", "Analysis budget exceeded; reduce the time window", "More than 100 groups; narrow query or choose a coarser dimension"].includes(String(message)))
18766
+ return new DeveloperLogError("logs_budget_exceeded", status, "Analysis limit exceeded; use metric=count for counts, or narrow the window/dimensions for detailed metrics");
18767
+ const [code, detail] = errors[status] ?? ["logs_unavailable", "Log service temporarily unavailable"];
18768
+ return new DeveloperLogError(code, status, `${detail} (HTTP ${status})`);
18769
+ }
18746
18770
  var appRef = "pmate:developers";
18747
18771
  async function queryDeveloperLogs(input, fetcher = fetch) {
18748
18772
  if (input.app) parseNamespacedRef(input.app, { entityLabel: "app ref" });
@@ -18766,7 +18790,14 @@ async function requestDeveloperLogTool(tool, params, fetcher = fetch, delegate)
18766
18790
  } catch {
18767
18791
  throw new Error("Developers log request failed; no anonymous fallback was attempted");
18768
18792
  }
18769
- if (!response.ok) throw new Error(`Developers log request failed (HTTP ${response.status}); check login, pmate:dev role`);
18793
+ if (!response.ok) {
18794
+ let detail;
18795
+ try {
18796
+ detail = (await response.json())?.message;
18797
+ } catch {
18798
+ }
18799
+ throw logResponseError(response.status, detail);
18800
+ }
18770
18801
  let body;
18771
18802
  try {
18772
18803
  body = await response.json();
@@ -24762,7 +24793,7 @@ function parseLogsArgs(input) {
24762
24793
  const params = {}, fields = {};
24763
24794
  let json = false, actorProfileId;
24764
24795
  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" };
24796
+ const keys = { "--metric": "metric", "--topic": "topic", "--env": "environment", "--search": "keyword", "--group-by": "groupBy", "--interval": "interval", "--timezone": "timezone", "--request-id": "requestId", "--page": "page" };
24766
24797
  const times = { "--from": "from", "--to": "to", "--compare-from": "compareFrom", "--compare-to": "compareTo" };
24767
24798
  while (rest.length) {
24768
24799
  const flag = rest.shift();
@@ -24797,10 +24828,11 @@ function parseLogsArgs(input) {
24797
24828
  if (tool !== "schema" && !params.service) throw Error("Service reference required");
24798
24829
  if (tool === "schema" && (Object.keys(params).length || Object.keys(fields).length)) throw Error("logs schema only accepts --json");
24799
24830
  if (tool === "aggregate" || tool === "compare") {
24831
+ if (params.metric && params.metric !== "count") throw Error("Supported metric: count");
24800
24832
  if (!params.from || !params.to || Number(params.from) >= Number(params.to)) throw Error("An increasing --from/--to window is required");
24801
24833
  if (params.page || params.requestId) throw Error("Use --field requestId=value for analysis; --page only applies to query/trace");
24802
24834
  } else {
24803
- if (params.groupBy || params.interval || params.timezone || params.compareFrom || params.compareTo) throw Error("Grouping and comparison options require aggregate/compare");
24835
+ if (params.metric || params.groupBy || params.interval || params.timezone || params.compareFrom || params.compareTo) throw Error("Grouping and comparison options require aggregate/compare");
24804
24836
  if (params.from) {
24805
24837
  params.sinceMs = params.from;
24806
24838
  delete params.from;
@@ -28280,8 +28312,15 @@ async function main() {
28280
28312
  const delegate = commandArgs.actorProfileId ? { profileId: commandArgs.actorProfileId, token: process.env.PMATE_LOG_DELEGATE_TOKEN ?? "" } : void 0;
28281
28313
  if (delegate && !delegate.token) throw new Error("PMATE_LOG_DELEGATE_TOKEN is required for delegated log tools");
28282
28314
  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));
28315
+ try {
28316
+ const result = await requestDeveloperLogTool(commandArgs.tool, new URLSearchParams(commandArgs.params), fetch, delegate);
28317
+ console.log(JSON.stringify(result, null, commandArgs.json ? void 0 : 2));
28318
+ } catch (error) {
28319
+ if (!commandArgs.json) throw error;
28320
+ const failure = error instanceof DeveloperLogError ? { code: error.code, status: error.status, message: error.message } : { code: "logs_unavailable", message: "Log tool failed before receiving a valid response" };
28321
+ console.log(JSON.stringify({ ok: false, error: failure }));
28322
+ process.exitCode = 1;
28323
+ }
28285
28324
  return;
28286
28325
  }
28287
28326
  case "service" /* Service */: {
@@ -1,6 +1,7 @@
1
- const { app, BrowserWindow, session, shell, Menu } = require('electron')
1
+ const { app, BrowserWindow, session, shell, Menu, desktopCapturer } = require('electron')
2
2
  const path = require('node:path')
3
3
  const { createResourceHandler } = require('./resources.cjs')
4
+ const { installMediaPermissions } = require('./media.cjs')
4
5
  const config = require('./desktop-config.json')
5
6
  const manifest = require('./resource-manifest.json')
6
7
  app.setName(config.productName)
@@ -44,12 +45,7 @@ else {
44
45
  }
45
46
  return resources(request) ?? desktopSession.fetch(request, { bypassCustomProtocolHandlers: true })
46
47
  })
47
- desktopSession.setPermissionRequestHandler((contents, permission, callback, details) => {
48
- let trusted = false
49
- try { trusted = !!contents && new URL(contents.getURL()).origin === config.origin } catch {}
50
- callback(Boolean(trusted && permission === 'media' && details.mediaTypes?.length && details.mediaTypes.every(type => type === 'audio')))
51
- })
52
- desktopSession.setPermissionCheckHandler((_contents, permission, origin, details) => origin === config.origin && permission === 'media' && details.mediaType === 'audio')
48
+ installMediaPermissions(desktopSession, { origin: config.origin, getWindow: () => mainWindow, desktopCapturer, Menu })
53
49
  Menu.setApplicationMenu(Menu.buildFromTemplate([
54
50
  { role: 'appMenu' }, { role: 'editMenu' }, { role: 'viewMenu' }, { role: 'windowMenu' },
55
51
  { label: config.productName, submenu: [{ label: 'Back to app', click: () => { if (!mainWindow) createWindow(); else void mainWindow.loadURL(config.origin) } }] },
@@ -0,0 +1,38 @@
1
+ function installMediaPermissions(desktopSession, { origin, getWindow, desktopCapturer, Menu }) {
2
+ const sameOrigin = raw => { try { return new URL(raw).origin === origin } catch { return false } }
3
+ const trusted = contents => {
4
+ const window = getWindow()
5
+ return !!window && !window.isDestroyed() && contents === window.webContents && sameOrigin(contents.getURL())
6
+ }
7
+ desktopSession.setPermissionRequestHandler((contents, permission, callback, details) => {
8
+ const allowed = trusted(contents) && details.isMainFrame === true && sameOrigin(details.requestingUrl)
9
+ callback(Boolean(allowed && (permission === 'display-capture' || (permission === 'media' && details.mediaTypes?.length && details.mediaTypes.every(type => type === 'audio')))))
10
+ })
11
+ desktopSession.setPermissionCheckHandler((contents, permission, requestingOrigin, details) => {
12
+ return Boolean(trusted(contents) && sameOrigin(requestingOrigin) && details.isMainFrame === true && (permission === 'display-capture' || (permission === 'media' && details.mediaType === 'audio')))
13
+ })
14
+ desktopSession.setDisplayMediaRequestHandler(async (request, callback) => {
15
+ const window = getWindow()
16
+ const valid = () => window && !window.isDestroyed() && trusted(window.webContents) && request.frame && request.frame === window.webContents.mainFrame && sameOrigin(request.securityOrigin)
17
+ if (!valid() || !request.userGesture || !request.videoRequested || request.audioRequested) return callback(null)
18
+ let completed = false
19
+ const finish = source => {
20
+ if (completed) return
21
+ completed = true
22
+ try { callback(source && valid() ? { video: source } : null) } catch { /* Request frame was closed or navigated. */ }
23
+ }
24
+ try {
25
+ const sources = await desktopCapturer.getSources({ types: ['screen', 'window'], thumbnailSize: { width: 0, height: 0 } })
26
+ if (!valid() || !sources.length) return finish(null)
27
+ // Older macOS versions have no system picker. Native menu selection remains explicit.
28
+ const menu = Menu.buildFromTemplate([
29
+ { label: 'Choose a screen or window to share', enabled: false },
30
+ { type: 'separator' },
31
+ ...sources.map(source => ({ label: source.name.replace(/&/g, '&&'), click: () => finish(source) })),
32
+ { type: 'separator' }, { label: 'Cancel', click: () => finish(null) },
33
+ ])
34
+ menu.popup({ window, callback: () => finish(null) })
35
+ } catch { finish(null) }
36
+ }, { useSystemPicker: true })
37
+ }
38
+ module.exports = { installMediaPermissions }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pmate/cli",
3
- "version": "0.34.0",
3
+ "version": "0.35.0",
4
4
  "description": "CLI utilities for the pMate project",
5
5
  "license": "MIT",
6
6
  "bin": {