@pmate/cli 0.19.0 → 0.20.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.
Files changed (2) hide show
  1. package/dist/index.js +164 -88
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -34,24 +34,94 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
34
34
  ));
35
35
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
36
36
 
37
+ // src/namespacedRef.ts
38
+ function parseNamespacedRef(value, options = {}) {
39
+ const raw = value.trim();
40
+ const entityLabel = options.entityLabel || "ref";
41
+ if (!raw) {
42
+ throw new Error(`Invalid ${entityLabel}.`);
43
+ }
44
+ const colonIndex = raw.indexOf(":");
45
+ if (colonIndex > 0) {
46
+ const namespace2 = raw.slice(0, colonIndex).trim();
47
+ const objectId = raw.slice(colonIndex + 1).trim();
48
+ if (!namespace2 || !objectId) {
49
+ throw new Error(`Invalid ${entityLabel} "${value}". Use <ns:objectId>.`);
50
+ }
51
+ return {
52
+ namespace: namespace2,
53
+ objectId,
54
+ fullId: `${namespace2}:${objectId}`
55
+ };
56
+ }
57
+ const namespace = options.defaultNamespace?.trim();
58
+ if (namespace) {
59
+ return {
60
+ namespace,
61
+ objectId: raw,
62
+ fullId: `${namespace}:${raw}`
63
+ };
64
+ }
65
+ throw new Error(`Invalid ${entityLabel} "${value}". Use <ns:objectId>.`);
66
+ }
67
+ function composeNamespacedRef(namespace, objectId) {
68
+ const normalizedNamespace = namespace.trim();
69
+ const normalizedObjectId = objectId.trim();
70
+ return `${normalizedNamespace}:${normalizedObjectId}`;
71
+ }
72
+ function formatNamespacedDisplayId(namespace, objectIdOrFullId) {
73
+ const raw = objectIdOrFullId.trim();
74
+ if (raw.includes(":")) {
75
+ return raw;
76
+ }
77
+ const ns = namespace?.trim();
78
+ if (!ns) {
79
+ return raw;
80
+ }
81
+ return composeNamespacedRef(ns, raw);
82
+ }
83
+ var init_namespacedRef = __esm({
84
+ "src/namespacedRef.ts"() {
85
+ "use strict";
86
+ }
87
+ });
88
+
37
89
  // src/sessionStore.ts
38
90
  function buildSessionStorePath() {
39
91
  return import_node_path3.default.join(import_node_os2.default.homedir(), ".pmate", "session.json");
40
92
  }
93
+ function assertValidAppRef(appRef) {
94
+ return parseNamespacedRef(appRef, { entityLabel: "app ref" }).fullId;
95
+ }
41
96
  function saveLoginSession(session, requestedApp = DEFAULT_APP_ID) {
42
97
  const store = loadExistingSessionStoreOrEmpty();
43
- const app = resolveStoredAppId(session, requestedApp);
98
+ const app = assertValidAppRef(requestedApp);
99
+ const sessionApp = session.session?.app?.trim();
100
+ if (!sessionApp) {
101
+ throw new Error("Missing app in login session payload.");
102
+ }
103
+ const normalizedSessionApp = assertValidAppRef(sessionApp);
104
+ if (normalizedSessionApp !== app) {
105
+ throw new Error(
106
+ `Session app mismatch. requested=${app}, received=${normalizedSessionApp}`
107
+ );
108
+ }
109
+ const accountId = session.session.identity?.accountId?.trim();
110
+ const token = session.token?.trim();
111
+ const issuedAt = session.session.issuedAt?.trim();
112
+ const expiresAt = session.session.expiresAt?.trim();
113
+ if (!accountId || !token || !issuedAt || !expiresAt) {
114
+ throw new Error("Invalid login session payload.");
115
+ }
44
116
  const nextSession = {
45
- ...session,
46
- app,
47
- session: {
48
- ...session.session,
49
- ...session.session.app ? {} : { app }
50
- },
117
+ token,
118
+ accountId,
119
+ issuedAt,
120
+ expiresAt,
51
121
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
52
122
  };
53
123
  store.sessions[app] = nextSession;
54
- store.activeApp = app;
124
+ store.activeAppRef = app;
55
125
  writeSessionStore(store);
56
126
  return {
57
127
  app,
@@ -67,7 +137,9 @@ function readRequiredSession(options) {
67
137
  }
68
138
  const token = session.token?.trim();
69
139
  if (!token) {
70
- throw new Error(`Missing token in ${sessionPath}. Run "pmate login --app ${resolvedApp}" again.`);
140
+ throw new Error(
141
+ `Missing token in ${sessionPath}. Run "pmate login --app ${resolvedApp}" again.`
142
+ );
71
143
  }
72
144
  return {
73
145
  ...session,
@@ -82,7 +154,7 @@ function loadExistingSessionStoreOrEmpty() {
82
154
  if (!(0, import_node_fs2.existsSync)(sessionPath)) {
83
155
  return {
84
156
  version: SESSION_STORE_VERSION,
85
- activeApp: DEFAULT_APP_ID,
157
+ activeAppRef: DEFAULT_APP_ID,
86
158
  sessions: {}
87
159
  };
88
160
  }
@@ -104,31 +176,74 @@ function normalizeStore(value) {
104
176
  `Invalid session store format in ${buildSessionStorePath()}. Run "pmate login" again.`
105
177
  );
106
178
  }
107
- return {
108
- version: SESSION_STORE_VERSION,
109
- activeApp: value.activeApp || DEFAULT_APP_ID,
110
- sessions: value.sessions || {}
111
- };
179
+ return value;
112
180
  }
113
181
  function isSessionStore(value) {
114
- return !!value && typeof value === "object" && "version" in value && "sessions" in value && typeof value.sessions === "object";
182
+ if (!value || typeof value !== "object") {
183
+ return false;
184
+ }
185
+ const candidate = value;
186
+ if (candidate.version !== SESSION_STORE_VERSION) {
187
+ return false;
188
+ }
189
+ if (typeof candidate.activeAppRef !== "string") {
190
+ return false;
191
+ }
192
+ let normalizedActiveApp;
193
+ try {
194
+ normalizedActiveApp = assertValidAppRef(candidate.activeAppRef);
195
+ } catch {
196
+ return false;
197
+ }
198
+ if (normalizedActiveApp !== candidate.activeAppRef) {
199
+ return false;
200
+ }
201
+ if (!candidate.sessions || typeof candidate.sessions !== "object") {
202
+ return false;
203
+ }
204
+ if (!(normalizedActiveApp in candidate.sessions)) {
205
+ return false;
206
+ }
207
+ for (const [appRef, session] of Object.entries(candidate.sessions)) {
208
+ try {
209
+ if (assertValidAppRef(appRef) !== appRef) {
210
+ return false;
211
+ }
212
+ } catch {
213
+ return false;
214
+ }
215
+ if (!isStoredSession(session)) {
216
+ return false;
217
+ }
218
+ }
219
+ return true;
115
220
  }
116
- function resolveStoredAppId(session, fallbackApp) {
117
- return session.session?.app?.trim() || session.app?.trim() || fallbackApp.trim() || DEFAULT_APP_ID;
221
+ function isStoredSession(value) {
222
+ if (!value || typeof value !== "object") {
223
+ return false;
224
+ }
225
+ const session = value;
226
+ return typeof session.token === "string" && !!session.token.trim() && typeof session.accountId === "string" && !!session.accountId.trim() && typeof session.issuedAt === "string" && !!session.issuedAt.trim() && typeof session.expiresAt === "string" && !!session.expiresAt.trim() && typeof session.updatedAt === "string" && !!session.updatedAt.trim();
118
227
  }
119
228
  function resolveReadApp(store, options) {
120
229
  const requestedApp = options?.app?.trim();
121
230
  if (requestedApp) {
122
- return requestedApp;
231
+ return assertValidAppRef(requestedApp);
123
232
  }
124
233
  const preferredApp = options?.preferredApp?.trim();
125
- if (preferredApp && store.sessions[preferredApp]) {
126
- return preferredApp;
234
+ if (preferredApp) {
235
+ const normalizedPreferred = assertValidAppRef(preferredApp);
236
+ if (store.sessions[normalizedPreferred]) {
237
+ return normalizedPreferred;
238
+ }
127
239
  }
128
- if (options?.fallbackToActiveApp && store.activeApp && store.sessions[store.activeApp]) {
129
- return store.activeApp;
240
+ if (options?.fallbackToActiveApp && store.sessions[store.activeAppRef]) {
241
+ return store.activeAppRef;
130
242
  }
131
- return preferredApp || store.activeApp || DEFAULT_APP_ID;
243
+ if (preferredApp) {
244
+ return assertValidAppRef(preferredApp);
245
+ }
246
+ return store.activeAppRef;
132
247
  }
133
248
  function writeSessionStore(store) {
134
249
  const sessionPath = buildSessionStorePath();
@@ -154,8 +269,9 @@ var init_sessionStore = __esm({
154
269
  import_node_fs2 = require("fs");
155
270
  import_node_os2 = __toESM(require("os"));
156
271
  import_node_path3 = __toESM(require("path"));
157
- DEFAULT_APP_ID = "@pmate/cli";
158
- SESSION_STORE_VERSION = 2;
272
+ init_namespacedRef();
273
+ DEFAULT_APP_ID = "pmate:cli";
274
+ SESSION_STORE_VERSION = 3;
159
275
  }
160
276
  });
161
277
 
@@ -13025,23 +13141,24 @@ var ListCommandHandler = class {
13025
13141
  };
13026
13142
 
13027
13143
  // src/commands/LoginCommandHandler.ts
13028
- var import_node_http = require("http");
13029
13144
  var import_node_child_process = require("child_process");
13145
+ var import_node_http = require("http");
13030
13146
  init_sessionStore();
13031
13147
  var AUTH_UI_BASE_URL = "https://auth.pmate.chat";
13032
13148
  var AUTH_API_BASE_URL = "https://auth-api-v2.pmate.chat";
13033
13149
  var CALLBACK_PATH = "/pmate-login-callback";
13034
13150
  var LOGIN_TIMEOUT_MS = 5 * 60 * 1e3;
13035
13151
  function buildHostedLoginUrl(redirectUrl, appId = DEFAULT_APP_ID) {
13152
+ const resolvedApp = assertValidAppRef(appId);
13036
13153
  const url = new URL(AUTH_UI_BASE_URL);
13037
13154
  url.searchParams.set("redirect", redirectUrl);
13038
- url.searchParams.set("app", appId);
13155
+ url.searchParams.set("app", resolvedApp);
13039
13156
  return url.toString();
13040
13157
  }
13041
13158
  var LoginCommandHandler = class {
13042
13159
  async handle(args) {
13043
13160
  const callback = await this.startCallbackServer();
13044
- const appId = args.app?.trim() || DEFAULT_APP_ID;
13161
+ const appId = assertValidAppRef(args.app?.trim() || DEFAULT_APP_ID);
13045
13162
  const loginUrl = buildHostedLoginUrl(callback.redirectUrl, appId);
13046
13163
  let savedSession = null;
13047
13164
  ui.info("Opening browser for login.", {
@@ -13058,7 +13175,7 @@ var LoginCommandHandler = class {
13058
13175
  });
13059
13176
  try {
13060
13177
  const token = await callback.waitForToken;
13061
- const loginResponse = await this.fetchSession(token, appId);
13178
+ const loginResponse = await this.exchangeSession(token, appId);
13062
13179
  savedSession = saveLoginSession(loginResponse, appId);
13063
13180
  } finally {
13064
13181
  await callback.close();
@@ -13069,26 +13186,26 @@ var LoginCommandHandler = class {
13069
13186
  );
13070
13187
  }
13071
13188
  }
13072
- async fetchSession(token, appId) {
13073
- const url = new URL(`${AUTH_API_BASE_URL}/session`);
13074
- url.searchParams.set("app", appId);
13075
- const response = await fetch(url.toString(), {
13076
- method: "GET",
13189
+ async exchangeSession(token, appId) {
13190
+ const response = await fetch(`${AUTH_API_BASE_URL}/session/exchange`, {
13191
+ method: "POST",
13077
13192
  headers: {
13193
+ "Content-Type": "application/json",
13078
13194
  Authorization: `Bearer ${token}`
13079
- }
13195
+ },
13196
+ body: JSON.stringify({ app: appId })
13080
13197
  });
13081
13198
  if (!response.ok) {
13082
13199
  const detail = await response.text();
13083
13200
  throw new Error(
13084
- `Failed to validate session (${response.status}): ${detail || response.statusText}`
13201
+ `Failed to exchange app session (${response.status}): ${detail || response.statusText}`
13085
13202
  );
13086
13203
  }
13087
13204
  const resp = await response.json();
13088
- return {
13089
- ...resp.data,
13090
- token
13091
- };
13205
+ if (!resp?.data?.session?.app || resp.data.session.app !== appId) {
13206
+ throw new Error("Invalid exchange response: app-scoped session mismatch.");
13207
+ }
13208
+ return resp.data;
13092
13209
  }
13093
13210
  async startCallbackServer() {
13094
13211
  let resolveToken;
@@ -18990,54 +19107,8 @@ var NamespaceRegistry = class {
18990
19107
  }
18991
19108
  };
18992
19109
 
18993
- // src/namespacedRef.ts
18994
- function parseNamespacedRef(value, options = {}) {
18995
- const raw = value.trim();
18996
- const entityLabel = options.entityLabel || "ref";
18997
- if (!raw) {
18998
- throw new Error(`Invalid ${entityLabel}.`);
18999
- }
19000
- const colonIndex = raw.indexOf(":");
19001
- if (colonIndex > 0) {
19002
- const namespace2 = raw.slice(0, colonIndex).trim();
19003
- const objectId = raw.slice(colonIndex + 1).trim();
19004
- if (!namespace2 || !objectId) {
19005
- throw new Error(`Invalid ${entityLabel} "${value}". Use <ns:objectId>.`);
19006
- }
19007
- return {
19008
- namespace: namespace2,
19009
- objectId,
19010
- fullId: `${namespace2}:${objectId}`
19011
- };
19012
- }
19013
- const namespace = options.defaultNamespace?.trim();
19014
- if (namespace) {
19015
- return {
19016
- namespace,
19017
- objectId: raw,
19018
- fullId: `${namespace}:${raw}`
19019
- };
19020
- }
19021
- throw new Error(`Invalid ${entityLabel} "${value}". Use <ns:objectId>.`);
19022
- }
19023
- function composeNamespacedRef(namespace, objectId) {
19024
- const normalizedNamespace = namespace.trim();
19025
- const normalizedObjectId = objectId.trim();
19026
- return `${normalizedNamespace}:${normalizedObjectId}`;
19027
- }
19028
- function formatNamespacedDisplayId(namespace, objectIdOrFullId) {
19029
- const raw = objectIdOrFullId.trim();
19030
- if (raw.includes(":")) {
19031
- return raw;
19032
- }
19033
- const ns = namespace?.trim();
19034
- if (!ns) {
19035
- return raw;
19036
- }
19037
- return composeNamespacedRef(ns, raw);
19038
- }
19039
-
19040
19110
  // src/commands/AgentCommandHandler.ts
19111
+ init_namespacedRef();
19041
19112
  var AgentCommandHandler = class {
19042
19113
  async handle(args) {
19043
19114
  const token = this.readToken();
@@ -19501,6 +19572,7 @@ var StoreCommandHandler = class {
19501
19572
  var import_node_fs13 = require("fs");
19502
19573
  var import_node_path18 = __toESM(require("path"));
19503
19574
  init_sessionStore();
19575
+ init_namespacedRef();
19504
19576
  var AppCommandHandler = class {
19505
19577
  async handle(args) {
19506
19578
  const token = this.readToken();
@@ -20208,6 +20280,7 @@ function normalizeRepository(repository) {
20208
20280
  }
20209
20281
 
20210
20282
  // src/commands/ServiceCommandHandler.ts
20283
+ init_namespacedRef();
20211
20284
  var ServiceCommandHandler = class {
20212
20285
  async handle(args) {
20213
20286
  const baseUrl = this.resolveBaseUrl(args.baseUrl);
@@ -20723,6 +20796,7 @@ var AccountCommandHandler = class {
20723
20796
  };
20724
20797
 
20725
20798
  // src/agentCommandParser.ts
20799
+ init_namespacedRef();
20726
20800
  var DEFAULT_AGENT_NAMESPACE = "pmate";
20727
20801
  function parseAgentArgs(tokens) {
20728
20802
  const rest = [...tokens];
@@ -21231,6 +21305,7 @@ function parsePage(value) {
21231
21305
  }
21232
21306
 
21233
21307
  // src/appCommandParser.ts
21308
+ init_namespacedRef();
21234
21309
  function parseAppArgs(tokens) {
21235
21310
  const rest = [...tokens];
21236
21311
  const rawAction = rest.shift();
@@ -21420,6 +21495,7 @@ function parseBareRenameId2(value, entityName) {
21420
21495
  }
21421
21496
 
21422
21497
  // src/serviceCommandParser.ts
21498
+ init_namespacedRef();
21423
21499
  function parseServiceArgs(tokens) {
21424
21500
  const rest = [...tokens];
21425
21501
  const rawAction = rest.shift();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pmate/cli",
3
- "version": "0.19.0",
3
+ "version": "0.20.0",
4
4
  "description": "CLI utilities for the pMate project",
5
5
  "license": "MIT",
6
6
  "bin": {