@newlandnpt/pep-cli 0.9.1 → 0.9.2

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/pep.cjs +455 -70
  2. package/package.json +1 -1
package/dist/pep.cjs CHANGED
@@ -305,12 +305,12 @@ async function waitForAuthorizationCallback(args) {
305
305
  throw new Error("CLI redirect URI must be an http://localhost URL with an explicit port.");
306
306
  }
307
307
  let server;
308
- return new Promise((resolve4, reject) => {
308
+ return new Promise((resolve5, reject) => {
309
309
  const finish = (error, code) => {
310
310
  clearTimeout(timeout);
311
311
  server?.close();
312
312
  if (error) reject(error);
313
- else resolve4(code);
313
+ else resolve5(code);
314
314
  };
315
315
  const timeout = setTimeout(
316
316
  () => finish(new Error("Login timed out while waiting for the browser callback.")),
@@ -353,7 +353,7 @@ async function waitForAuthorizationCallback(args) {
353
353
  function openBrowser(url) {
354
354
  const command = process.platform === "win32" ? "rundll32.exe" : process.platform === "darwin" ? "open" : "xdg-open";
355
355
  const commandArgs = process.platform === "win32" ? ["url.dll,FileProtocolHandler", url] : [url];
356
- return new Promise((resolve4, reject) => {
356
+ return new Promise((resolve5, reject) => {
357
357
  const child = (0, import_node_child_process.spawn)(command, commandArgs, {
358
358
  detached: true,
359
359
  stdio: "ignore",
@@ -362,7 +362,7 @@ function openBrowser(url) {
362
362
  child.once("error", reject);
363
363
  child.once("spawn", () => {
364
364
  child.unref();
365
- resolve4();
365
+ resolve5();
366
366
  });
367
367
  });
368
368
  }
@@ -473,7 +473,7 @@ var REFRESH_EARLY_MS = 60 * 1e3;
473
473
  var LOCK_STALE_MS = 30 * 1e3;
474
474
  var LOCK_WAIT_MS = 10 * 1e3;
475
475
  async function delay(milliseconds) {
476
- await new Promise((resolve4) => setTimeout(resolve4, milliseconds));
476
+ await new Promise((resolve5) => setTimeout(resolve5, milliseconds));
477
477
  }
478
478
  async function releaseIfMine(path, stamp2) {
479
479
  try {
@@ -608,7 +608,7 @@ function createAuthService(dependencies) {
608
608
  }
609
609
 
610
610
  // src/cli.ts
611
- var import_node_path5 = require("node:path");
611
+ var import_node_path6 = require("node:path");
612
612
 
613
613
  // src/credential-store.ts
614
614
  var import_node_child_process2 = require("node:child_process");
@@ -685,7 +685,7 @@ function encodedPowerShell(script) {
685
685
  return Buffer.from(script, "utf16le").toString("base64");
686
686
  }
687
687
  async function runPowerShell(script, input) {
688
- return new Promise((resolve4, reject) => {
688
+ return new Promise((resolve5, reject) => {
689
689
  const child = (0, import_node_child_process2.spawn)(
690
690
  "powershell.exe",
691
691
  [
@@ -708,7 +708,7 @@ async function runPowerShell(script, input) {
708
708
  );
709
709
  child.once("close", (code) => {
710
710
  clearTimeout(timeout);
711
- if (code === 0) resolve4(stdout.trim());
711
+ if (code === 0) resolve5(stdout.trim());
712
712
  else
713
713
  reject(
714
714
  new Error(
@@ -762,7 +762,7 @@ $raw = [Console]::In.ReadToEnd()
762
762
  }
763
763
  var SECURITY_NOT_FOUND = 44;
764
764
  async function runSecurity(args) {
765
- return new Promise((resolve4, reject) => {
765
+ return new Promise((resolve5, reject) => {
766
766
  const child = (0, import_node_child_process2.spawn)("/usr/bin/security", [...args], {
767
767
  stdio: ["ignore", "pipe", "pipe"]
768
768
  });
@@ -777,7 +777,7 @@ async function runSecurity(args) {
777
777
  );
778
778
  child.once("close", (code) => {
779
779
  clearTimeout(timeout);
780
- resolve4({ code: code ?? -1, stdout, stderr });
780
+ resolve5({ code: code ?? -1, stdout, stderr });
781
781
  });
782
782
  });
783
783
  }
@@ -874,6 +874,8 @@ async function fetchOrExplain(doFetch, url, init, what, timeoutMs = HEADERS_TIME
874
874
 
875
875
  // src/docs-service.ts
876
876
  var ENTRY = /^-\s*\[([^\]]*)\]\(([^)]+)\)(?::\s*(.*))?$/;
877
+ var INDEX_PATH = "/llms.txt";
878
+ var REGIONS_PATH = "/api/docs/regions";
877
879
  function parseDocsIndex(text) {
878
880
  const entries = [];
879
881
  for (const line of text.split("\n")) {
@@ -900,6 +902,50 @@ function contentPathFor(path) {
900
902
  const after = segments.slice(docsAt);
901
903
  return `/${[...before, "llms.mdx", ...after, "content.md"].join("/")}`;
902
904
  }
905
+ async function fetchRegions(dependencies) {
906
+ const doFetch = dependencies.fetch ?? globalThis.fetch;
907
+ const response = await fetchOrExplain(
908
+ doFetch,
909
+ `${dependencies.docsUrl.replace(/\/+$/, "")}${REGIONS_PATH}`,
910
+ { headers: { Accept: "application/json" } },
911
+ "Could not reach the documentation platform"
912
+ );
913
+ if (!response.ok) {
914
+ await response.body?.cancel();
915
+ if (response.status === 404) {
916
+ throw new Error(
917
+ "This documentation deployment has no /api/docs/regions endpoint \u2014 it predates region support. Ask an operator to deploy a newer build."
918
+ );
919
+ }
920
+ throw new Error(describeFailure(response.status, REGIONS_PATH));
921
+ }
922
+ const body = await response.json();
923
+ const regions = body?.regions;
924
+ if (!Array.isArray(regions) || regions.length === 0) {
925
+ throw new Error("The documentation platform returned no regions.");
926
+ }
927
+ return body;
928
+ }
929
+ function resolveRegion(known, ref) {
930
+ const wanted = ref.trim().toLowerCase();
931
+ if (!wanted) return void 0;
932
+ return known.regions.find(
933
+ (region) => region.id.toLowerCase() === wanted || (region.aliases ?? []).some((alias) => alias.toLowerCase() === wanted)
934
+ );
935
+ }
936
+ function unknownRegion(known, ref) {
937
+ const names = known.regions.map((one) => one.aliases?.length ? `${one.id} (${one.aliases.join(", ")})` : one.id).join(", ");
938
+ return new Error(`No such documentation region: ${ref}. Available: ${names}.`);
939
+ }
940
+ function withRegion(path, region) {
941
+ const clean = `/${path.replace(/^\/+/, "").replace(/\/+$/, "")}`;
942
+ const segments = clean.split("/").filter(Boolean);
943
+ const marker = segments.findIndex((one) => one === "llms.mdx" || one === "docs");
944
+ if (marker === -1) {
945
+ throw new Error(`Not a documentation path (expected \u2026/docs/\u2026): ${path}`);
946
+ }
947
+ return `/${[region.id, region.defaultLanguage, ...segments.slice(marker)].join("/")}`;
948
+ }
903
949
  function describeFailure(status, what) {
904
950
  if (status === 401) {
905
951
  return `The documentation platform rejected the access token. Run \`pep auth login\` again.`;
@@ -910,12 +956,15 @@ function describeFailure(status, what) {
910
956
  if (status === 404) {
911
957
  return `No such document, or your account cannot read it: ${what}`;
912
958
  }
959
+ if (status === 400) {
960
+ return `The documentation platform rejected that request: ${what}`;
961
+ }
913
962
  if (status === 502) {
914
963
  return "The documentation platform could not reach PEP to verify the token. Retry shortly.";
915
964
  }
916
965
  return `The documentation platform answered ${status} for ${what}.`;
917
966
  }
918
- async function get(dependencies, path, accept) {
967
+ async function get(dependencies, path, accept, explain) {
919
968
  const doFetch = dependencies.fetch ?? globalThis.fetch;
920
969
  const response = await fetchOrExplain(
921
970
  doFetch,
@@ -924,13 +973,22 @@ async function get(dependencies, path, accept) {
924
973
  "Could not reach the documentation platform"
925
974
  );
926
975
  if (!response.ok) {
927
- await response.body?.cancel();
928
- throw new Error(describeFailure(response.status, path));
976
+ const custom = explain ? await explain(response) : void 0;
977
+ if (!response.bodyUsed) await response.body?.cancel().catch(() => void 0);
978
+ throw new Error(custom ?? describeFailure(response.status, path));
929
979
  }
930
980
  return response;
931
981
  }
932
- async function fetchDocsIndex(dependencies) {
933
- const response = await get(dependencies, "/llms.txt", "text/plain");
982
+ async function fetchDocsIndex(dependencies, region) {
983
+ const path = region === void 0 ? INDEX_PATH : `${INDEX_PATH}?site=${encodeURIComponent(region)}`;
984
+ const response = await get(dependencies, path, "text/plain", async (bad) => {
985
+ if (bad.status !== 400) return void 0;
986
+ const available = await bad.json().then((body) => body?.available).catch(() => void 0);
987
+ if (!Array.isArray(available) || available.length === 0) {
988
+ return `No such documentation region: ${region}.`;
989
+ }
990
+ return `No such documentation region: ${region}. Available: ${available.join(", ")}.`;
991
+ });
934
992
  return parseDocsIndex(await response.text());
935
993
  }
936
994
  async function fetchDocContent(dependencies, path) {
@@ -938,10 +996,208 @@ async function fetchDocContent(dependencies, path) {
938
996
  return response.text();
939
997
  }
940
998
 
941
- // src/maven-env.ts
999
+ // src/docs-resources.ts
1000
+ var import_node_crypto3 = require("node:crypto");
1001
+ var import_node_fs = require("node:fs");
942
1002
  var import_promises3 = require("node:fs/promises");
943
- var import_node_os2 = require("node:os");
944
1003
  var import_node_path3 = require("node:path");
1004
+ var import_node_stream = require("node:stream");
1005
+ var import_promises4 = require("node:stream/promises");
1006
+ var RESOURCES_SLUG = "docs/Terminal/References/Developer-Resources";
1007
+ var PLATFORMS = ["Android", "Windows", "iOS"];
1008
+ function resolvePlatform(ref) {
1009
+ const wanted = ref.trim().toLowerCase();
1010
+ return PLATFORMS.find((one) => one.toLowerCase() === wanted);
1011
+ }
1012
+ function extractResourceLinks(markdown, platform3) {
1013
+ const text = markdown.replace(/!\[[^\]]*\]\([^)]*\)/g, "").replace(/<img\b[^>]*>/gi, "");
1014
+ const found = [];
1015
+ for (const match of text.matchAll(/\[[^\]]*\]\(([^)\s]+)/g)) {
1016
+ const url = match[1];
1017
+ if (url.startsWith("#")) continue;
1018
+ const external = /^https?:\/\//i.test(url);
1019
+ const pathPart = external ? safePath(url) : url;
1020
+ if (pathPart === void 0) continue;
1021
+ if (!external && !pathPart.startsWith("/assets/")) continue;
1022
+ const inSitePool = pathPart.startsWith("/assets/");
1023
+ const file = decodeURIComponent((0, import_node_path3.basename)(pathPart));
1024
+ if (!file) continue;
1025
+ found.push({ file, url, platforms: [platform3], external: external && !inSitePool });
1026
+ }
1027
+ return found;
1028
+ }
1029
+ function safePath(url) {
1030
+ try {
1031
+ return new URL(url).pathname;
1032
+ } catch {
1033
+ return void 0;
1034
+ }
1035
+ }
1036
+ function mergeResources(all) {
1037
+ const byUrl = /* @__PURE__ */ new Map();
1038
+ for (const one of all) {
1039
+ const seen = byUrl.get(one.url);
1040
+ if (!seen) {
1041
+ byUrl.set(one.url, { ...one, platforms: [...one.platforms] });
1042
+ continue;
1043
+ }
1044
+ for (const p of one.platforms) if (!seen.platforms.includes(p)) seen.platforms.push(p);
1045
+ }
1046
+ return [...byUrl.values()].sort((a, b) => a.file.localeCompare(b.file));
1047
+ }
1048
+ async function fetchResources(dependencies, platform3) {
1049
+ const page = withRegion(`/${RESOURCES_SLUG}`, dependencies.region);
1050
+ const wanted = platform3 ? [platform3] : [...PLATFORMS];
1051
+ const all = [];
1052
+ for (const one of wanted) {
1053
+ const markdown = await fetchTab(dependencies, page, one);
1054
+ all.push(...extractResourceLinks(markdown, one));
1055
+ }
1056
+ return mergeResources(all);
1057
+ }
1058
+ async function fetchTab(dependencies, page, platform3) {
1059
+ const doFetch = dependencies.fetch ?? globalThis.fetch;
1060
+ const url = new URL(
1061
+ `${dependencies.docsUrl.replace(/\/+$/, "")}${contentPathFor(page)}`
1062
+ );
1063
+ url.searchParams.set("lang", platform3);
1064
+ const response = await fetchOrExplain(
1065
+ doFetch,
1066
+ url,
1067
+ {
1068
+ headers: {
1069
+ Authorization: `Bearer ${dependencies.accessToken}`,
1070
+ Accept: "text/markdown"
1071
+ }
1072
+ },
1073
+ "Could not read the developer resources page"
1074
+ );
1075
+ if (!response.ok) {
1076
+ await response.body?.cancel().catch(() => void 0);
1077
+ if (response.status === 404) {
1078
+ throw new Error(
1079
+ `The developer resources page is not available to this account (or this region has no such page): ${page}`
1080
+ );
1081
+ }
1082
+ throw new Error(`The documentation platform answered ${response.status} for ${page}.`);
1083
+ }
1084
+ return response.text();
1085
+ }
1086
+ function pickResource(all, file) {
1087
+ const hits = all.filter((one) => one.file === file);
1088
+ if (hits.length === 0) {
1089
+ throw new Error(
1090
+ `No such file on the developer resources page: ${file}. Run \`pep docs resources\` to see what is there.`
1091
+ );
1092
+ }
1093
+ if (hits.length > 1) {
1094
+ const urls = hits.map((one) => ` ${one.url}`).join("\n");
1095
+ throw new Error(
1096
+ `${file} appears more than once with different addresses:
1097
+ ${urls}
1098
+ The page is ambiguous \u2014 ask an operator which one is current.`
1099
+ );
1100
+ }
1101
+ const only = hits[0];
1102
+ if (only.external) {
1103
+ throw new Error(
1104
+ `${file} is hosted outside the documentation platform (${only.url}). Open that page in a browser \u2014 pep does not send your PEP token to third parties.`
1105
+ );
1106
+ }
1107
+ return only;
1108
+ }
1109
+ function expectedBlobSha(url) {
1110
+ return /\/assets\/_shared\/([0-9a-f]{12})\//.exec(url)?.[1];
1111
+ }
1112
+ async function blobShaOfFile(path) {
1113
+ const size = (await (0, import_promises3.stat)(path)).size;
1114
+ const digest = (0, import_node_crypto3.createHash)("sha1").update(`blob ${size}\0`);
1115
+ await (0, import_promises4.pipeline)((0, import_node_fs.createReadStream)(path), async function* (source) {
1116
+ for await (const chunk of source) digest.update(chunk);
1117
+ });
1118
+ return digest.digest("hex");
1119
+ }
1120
+ async function downloadResource(dependencies, resource, options) {
1121
+ const file = (0, import_node_path3.basename)(resource.file);
1122
+ if (file !== resource.file || file === "." || file === "..") {
1123
+ throw new Error(`Refusing to write a file named ${resource.file}.`);
1124
+ }
1125
+ const directory = (0, import_node_path3.resolve)(options.directory);
1126
+ const target = (0, import_node_path3.resolve)(directory, file);
1127
+ if (!target.startsWith(directory + "/") && !target.startsWith(directory + "\\")) {
1128
+ throw new Error(`Refusing to write outside ${directory}.`);
1129
+ }
1130
+ if (!options.force && await (0, import_promises3.stat)(target).catch(() => null)) {
1131
+ throw new Error(
1132
+ `${target} already exists. Pass --force to replace it (the existing file is kept until the new one has downloaded completely).`
1133
+ );
1134
+ }
1135
+ const doFetch = dependencies.fetch ?? globalThis.fetch;
1136
+ const absolute = resource.url.startsWith("/") ? `${dependencies.docsUrl.replace(/\/+$/, "")}${resource.url}` : resource.url;
1137
+ const response = await fetchOrExplain(
1138
+ doFetch,
1139
+ absolute,
1140
+ { headers: { Authorization: `Bearer ${dependencies.accessToken}` } },
1141
+ `Could not download ${file}`
1142
+ );
1143
+ if (!response.ok || !response.body) {
1144
+ await response.body?.cancel().catch(() => void 0);
1145
+ throw new Error(describeDownloadFailure(response.status, file));
1146
+ }
1147
+ const declared = Number(response.headers.get("content-length") ?? "");
1148
+ const expected = expectedBlobSha(resource.url);
1149
+ await (0, import_promises3.mkdir)(directory, { recursive: true });
1150
+ const temporary = `${target}.${process.pid}.part`;
1151
+ try {
1152
+ await (0, import_promises4.pipeline)(
1153
+ import_node_stream.Readable.fromWeb(response.body),
1154
+ (0, import_node_fs.createWriteStream)(temporary)
1155
+ );
1156
+ const bytes = (await (0, import_promises3.stat)(temporary)).size;
1157
+ if (Number.isFinite(declared) && declared > 0 && bytes !== declared) {
1158
+ throw new Error(
1159
+ `${file} stopped after ${bytes} of ${declared} bytes. Nothing was written \u2014 run this again.`
1160
+ );
1161
+ }
1162
+ let verified = "none";
1163
+ if (expected !== void 0) {
1164
+ const actual = (await blobShaOfFile(temporary)).slice(0, 12);
1165
+ if (actual !== expected) {
1166
+ throw new Error(
1167
+ `${file} does not match the content hash in its address (expected ${expected}, got ${actual}). Nothing was written.`
1168
+ );
1169
+ }
1170
+ verified = "blob-sha";
1171
+ } else if (Number.isFinite(declared) && declared > 0) {
1172
+ verified = "content-length";
1173
+ }
1174
+ await (0, import_promises3.rename)(temporary, target);
1175
+ return { path: target, bytes, verified };
1176
+ } catch (error) {
1177
+ await (0, import_promises3.unlink)(temporary).catch(() => void 0);
1178
+ throw error;
1179
+ }
1180
+ }
1181
+ function describeDownloadFailure(status, file) {
1182
+ if (status === 401) {
1183
+ return "The documentation platform rejected the access token. Run `pep auth login` again.";
1184
+ }
1185
+ if (status === 403) {
1186
+ return "This client is not allowed to read documentation \u2014 it needs the `docs:read` scope.";
1187
+ }
1188
+ if (status === 404) {
1189
+ return `${file} is no longer on the documentation platform. Run \`pep docs resources\` for the current list.`;
1190
+ }
1191
+ if (status === 502) {
1192
+ return "The documentation platform could not reach PEP to verify the token. Retry shortly.";
1193
+ }
1194
+ return `The documentation platform answered ${status} for ${file}.`;
1195
+ }
1196
+
1197
+ // src/maven-env.ts
1198
+ var import_promises5 = require("node:fs/promises");
1199
+ var import_node_os2 = require("node:os");
1200
+ var import_node_path4 = require("node:path");
945
1201
  var import_node_child_process3 = require("node:child_process");
946
1202
  var import_node_util = require("node:util");
947
1203
  var run = (0, import_node_util.promisify)(import_node_child_process3.execFile);
@@ -952,7 +1208,7 @@ var END = "# <<< pep-cli: Newland Maven credentials <<<";
952
1208
  function resolveTarget(os = (0, import_node_os2.platform)(), shell = process.env.SHELL) {
953
1209
  if (os === "win32") return { kind: "windows" };
954
1210
  const file = shell?.endsWith("/bash") ? ".bash_profile" : ".zshrc";
955
- return { kind: "profile", path: (0, import_node_path3.join)((0, import_node_os2.homedir)(), file) };
1211
+ return { kind: "profile", path: (0, import_node_path4.join)((0, import_node_os2.homedir)(), file) };
956
1212
  }
957
1213
  function shellQuote(value) {
958
1214
  return `'${value.replace(/'/g, `'\\''`)}'`;
@@ -1020,7 +1276,7 @@ async function persistMavenEnv(credential, target = resolveTarget(), now = () =>
1020
1276
  }
1021
1277
  return { status: "windows" };
1022
1278
  }
1023
- const existing = await (0, import_promises3.readFile)(target.path, "utf8").catch((error) => {
1279
+ const existing = await (0, import_promises5.readFile)(target.path, "utf8").catch((error) => {
1024
1280
  if (error.code === "ENOENT") return null;
1025
1281
  throw error;
1026
1282
  });
@@ -1032,7 +1288,7 @@ async function persistMavenEnv(credential, target = resolveTarget(), now = () =>
1032
1288
  if (existing !== null) {
1033
1289
  backupPath = `${target.path}.bak-${stamp(now())}`;
1034
1290
  try {
1035
- await (0, import_promises3.writeFile)(backupPath, existing, { encoding: "utf8", flag: "wx" });
1291
+ await (0, import_promises5.writeFile)(backupPath, existing, { encoding: "utf8", flag: "wx" });
1036
1292
  } catch (error) {
1037
1293
  return {
1038
1294
  status: "manual",
@@ -1041,10 +1297,10 @@ async function persistMavenEnv(credential, target = resolveTarget(), now = () =>
1041
1297
  };
1042
1298
  }
1043
1299
  }
1044
- const originalMode = await (0, import_promises3.stat)(target.path).then((info) => info.mode & 511).catch(() => 384);
1300
+ const originalMode = await (0, import_promises5.stat)(target.path).then((info) => info.mode & 511).catch(() => 384);
1045
1301
  const temporary = `${target.path}.${process.pid}.tmp`;
1046
- await (0, import_promises3.writeFile)(temporary, plan.content, { encoding: "utf8", mode: originalMode });
1047
- await (0, import_promises3.rename)(temporary, target.path);
1302
+ await (0, import_promises5.writeFile)(temporary, plan.content, { encoding: "utf8", mode: originalMode });
1303
+ await (0, import_promises5.rename)(temporary, target.path);
1048
1304
  return plan.action === "replace" ? { status: "profile-replaced", path: target.path, backupPath } : { status: "profile-appended", path: target.path, ...backupPath ? { backupPath } : {} };
1049
1305
  }
1050
1306
 
@@ -1052,6 +1308,7 @@ async function persistMavenEnv(credential, target = resolveTarget(), now = () =>
1052
1308
  var EXAMPLE_PATH = EXAMPLE_SKILLS_REPO;
1053
1309
  var EXAMPLE_REPO = `https://git.newlandpayment.com/${EXAMPLE_PATH}.git`;
1054
1310
  var PROJECT_FLAG = "-p, --project Install into ./.agents/skills of the current directory instead";
1311
+ var REGION_FLAG = "--region <id> Documentation region for this run only (see pep docs regions)";
1055
1312
  var groups = {
1056
1313
  auth: {
1057
1314
  summary: "Sign in to PEP and manage your local authorization.",
@@ -1176,16 +1433,72 @@ pep skills update will not bring the skills back. Run pep skills add to do that.
1176
1433
  list: {
1177
1434
  summary: "List the documents your account can read.",
1178
1435
  usage: "pep docs list [flags]",
1179
- description: "Requires a saved PEP login. Prints each document's path and description.\nPass a path from this list to pep docs get to read its Markdown content.",
1180
- flags: [`--docs-url <url> Documentation server; saved for later use (default: ${DEFAULT_DOCS_URL})`],
1181
- examples: ["pep docs list", "pep docs list --docs-url http://localhost:3001"]
1436
+ description: "Requires a saved PEP login. Prints each document's path and description.\nPass a path from this list to pep docs get to read its Markdown content.\n\nWithout --region you get the Global English documentation. The region applies to\nthis run only and is never remembered.",
1437
+ flags: [
1438
+ `--docs-url <url> Documentation server; saved for later use (default: ${DEFAULT_DOCS_URL})`,
1439
+ REGION_FLAG
1440
+ ],
1441
+ examples: [
1442
+ "pep docs list",
1443
+ "pep docs list --region na",
1444
+ "pep docs list --docs-url http://localhost:3001"
1445
+ ]
1182
1446
  },
1183
1447
  get: {
1184
1448
  summary: "Read a document as Markdown.",
1185
1449
  usage: "pep docs get <path> [flags]",
1186
- description: "Requires a saved PEP login. Use a document path returned by pep docs list.\nWrites the document's Markdown to stdout, ready to pipe or save to a file.",
1450
+ description: "Requires a saved PEP login. Use a document path returned by pep docs list.\nWrites the document's Markdown to stdout, ready to pipe or save to a file.\n\nThe path already names its region, so a path from pep docs list --region na reads\nthe North American version. Pass --region to read that same document from another\nregion instead.",
1451
+ flags: [
1452
+ `--docs-url <url> Documentation server; saved for later use (default: ${DEFAULT_DOCS_URL})`,
1453
+ REGION_FLAG
1454
+ ],
1455
+ examples: [
1456
+ 'pep docs get "<path-from-pep-docs-list>"',
1457
+ 'pep docs get "<path-from-pep-docs-list>" --region na',
1458
+ 'pep docs get "<path-from-pep-docs-list>" > document.md'
1459
+ ]
1460
+ },
1461
+ regions: {
1462
+ summary: "List the documentation regions this deployment serves.",
1463
+ usage: "pep docs regions [flags]",
1464
+ description: 'Prints one region per line: id, aliases, default language, name.\nThe list comes from the documentation server, so a region added there shows up\nwithout upgrading the CLI.\n\nThis is the one docs command that needs no login: "what do I pass to --region?"\nis a question you have before signing in.',
1187
1465
  flags: [`--docs-url <url> Documentation server; saved for later use (default: ${DEFAULT_DOCS_URL})`],
1188
- examples: ['pep docs get "<path-from-pep-docs-list>"', 'pep docs get "<path-from-pep-docs-list>" > document.md']
1466
+ examples: ["pep docs regions"]
1467
+ }
1468
+ }
1469
+ },
1470
+ resources: {
1471
+ summary: "List and download SDKs, demos and tools.",
1472
+ commands: {
1473
+ list: {
1474
+ summary: "List the SDKs, demos and tools you can download.",
1475
+ usage: "pep resources list [flags]",
1476
+ description: 'Requires a saved PEP login. Prints one file per line: name, platforms, address.\nThe list is read from the documentation page itself, so a new release shows up as\nsoon as that page is updated \u2014 the CLI keeps no version list of its own.\n\nWithout --platform it reads all three platform tabs and merges them; a file that\nappears in several is listed once with every platform named. Entries marked\n"external" are hosted elsewhere and cannot be fetched with pep resources download.',
1477
+ flags: [
1478
+ `--platform <name> Only this platform (${PLATFORMS.join(", ")})`,
1479
+ REGION_FLAG,
1480
+ `--docs-url <url> Documentation server; saved for later use (default: ${DEFAULT_DOCS_URL})`
1481
+ ],
1482
+ examples: [
1483
+ "pep resources list",
1484
+ "pep resources list --platform windows",
1485
+ "pep resources list --region na --platform android"
1486
+ ]
1487
+ },
1488
+ download: {
1489
+ summary: "Download a file from the developer resources page.",
1490
+ usage: "pep resources download <file> [flags]",
1491
+ description: "Requires a saved PEP login. Use a file name exactly as pep resources list prints\nit. Saves into the current directory unless --dir says otherwise, and prints the\nfull path of the finished file to stdout.\n\nThe download goes to a temporary file beside the target and is renamed only once\nit is complete and verified, so an interrupted run never leaves a half file \u2014 and\nwith --force your existing copy survives until the new one has arrived.\n\nWhere the address carries a content hash the downloaded bytes are checked against\nit; otherwise the declared length is checked. Which one happened is reported on\nstderr.",
1492
+ flags: [
1493
+ "--dir <path> Directory to save into; created if missing (default: the current directory)",
1494
+ "--force Replace an existing file of the same name",
1495
+ REGION_FLAG,
1496
+ `--docs-url <url> Documentation server; saved for later use (default: ${DEFAULT_DOCS_URL})`
1497
+ ],
1498
+ examples: [
1499
+ "pep resources download SaleSystemDemo-4.8.0.2-win-x64.zip",
1500
+ "pep resources download PayExplorerECRDemo.apk --region na --dir ./downloads"
1501
+ ]
1189
1502
  }
1190
1503
  }
1191
1504
  },
@@ -1299,6 +1612,7 @@ ${flagBlock([...command.flags ?? [], "-h, --help Show help for this command"])}
1299
1612
  EXAMPLES
1300
1613
  ${command.examples.map((example) => ` ${example}`).join("\n")}`;
1301
1614
  }
1615
+ var GROUP_NAMES = Object.keys(groups);
1302
1616
  function usage(version, path = []) {
1303
1617
  const [groupName, commandName] = path;
1304
1618
  if (!groupName) {
@@ -1309,8 +1623,8 @@ USAGE
1309
1623
  pep <command> <subcommand> [flags]
1310
1624
 
1311
1625
  COMMANDS
1312
- ${Object.entries(topLevel).map(([name, command]) => ` ${name.padEnd(10)}${command.summary}`).join("\n")}
1313
- ${Object.entries(groups).map(([name, group2]) => ` ${name.padEnd(10)}${group2.summary}`).join("\n")}
1626
+ ${Object.entries(topLevel).map(([name, command]) => ` ${name.padEnd(11)}${command.summary}`).join("\n")}
1627
+ ${Object.entries(groups).map(([name, group2]) => ` ${name.padEnd(11)}${group2.summary}`).join("\n")}
1314
1628
 
1315
1629
  FLAGS
1316
1630
  -h, --help Show help for a command
@@ -1319,6 +1633,7 @@ FLAGS
1319
1633
  EXAMPLES
1320
1634
  pep setup
1321
1635
  pep docs list
1636
+ pep resources list
1322
1637
  pep nexus setup --help
1323
1638
 
1324
1639
  LEARN MORE
@@ -1341,7 +1656,7 @@ USAGE
1341
1656
  pep ${groupName} <command> [flags]
1342
1657
 
1343
1658
  COMMANDS
1344
- ${Object.entries(group.commands).map(([name, command]) => ` ${name.padEnd(10)}${command.summary}`).join("\n")}
1659
+ ${Object.entries(group.commands).map(([name, command]) => ` ${name.padEnd(11)}${command.summary}`).join("\n")}
1345
1660
 
1346
1661
  FLAGS
1347
1662
  -h, --help Show help for this command
@@ -1430,10 +1745,10 @@ async function setupNexusCredential(dependencies) {
1430
1745
  }
1431
1746
 
1432
1747
  // src/skills-service.ts
1433
- var import_promises4 = require("node:fs/promises");
1748
+ var import_promises6 = require("node:fs/promises");
1434
1749
  var import_node_os3 = require("node:os");
1435
- var import_node_path4 = require("node:path");
1436
- var import_node_crypto3 = require("node:crypto");
1750
+ var import_node_path5 = require("node:path");
1751
+ var import_node_crypto4 = require("node:crypto");
1437
1752
  var import_node_zlib = require("node:zlib");
1438
1753
 
1439
1754
  // src/tar.ts
@@ -1592,8 +1907,8 @@ async function readCapped(response, limit) {
1592
1907
  return out;
1593
1908
  }
1594
1909
  function assertInside(base, target) {
1595
- const rel = (0, import_node_path4.relative)((0, import_node_path4.resolve)(base), (0, import_node_path4.resolve)(target));
1596
- if (rel === "" || rel.startsWith("..") || (0, import_node_path4.isAbsolute)(rel)) {
1910
+ const rel = (0, import_node_path5.relative)((0, import_node_path5.resolve)(base), (0, import_node_path5.resolve)(target));
1911
+ if (rel === "" || rel.startsWith("..") || (0, import_node_path5.isAbsolute)(rel)) {
1597
1912
  throw new Error(`Refusing an archive entry that escapes its directory: ${target}`);
1598
1913
  }
1599
1914
  }
@@ -1696,32 +2011,32 @@ function groupMemberPath(ledgerKey, group) {
1696
2011
  return path;
1697
2012
  }
1698
2013
  async function physicalDir(path) {
1699
- const absolute = (0, import_node_path4.resolve)(path);
2014
+ const absolute = (0, import_node_path5.resolve)(path);
1700
2015
  try {
1701
- return (0, import_node_path4.join)(await (0, import_promises4.realpath)((0, import_node_path4.dirname)(absolute)), (0, import_node_path4.basename)(absolute));
2016
+ return (0, import_node_path5.join)(await (0, import_promises6.realpath)((0, import_node_path5.dirname)(absolute)), (0, import_node_path5.basename)(absolute));
1702
2017
  } catch {
1703
2018
  return absolute;
1704
2019
  }
1705
2020
  }
1706
2021
  async function linkSkill(canonicalSkillDir, linkPath) {
1707
- await (0, import_promises4.rm)(linkPath, { recursive: true, force: true });
1708
- const linkDir = (0, import_node_path4.dirname)(linkPath);
1709
- await (0, import_promises4.mkdir)(linkDir, { recursive: true });
2022
+ await (0, import_promises6.rm)(linkPath, { recursive: true, force: true });
2023
+ const linkDir = (0, import_node_path5.dirname)(linkPath);
2024
+ await (0, import_promises6.mkdir)(linkDir, { recursive: true });
1710
2025
  try {
1711
2026
  const junction = (0, import_node_os3.platform)() === "win32";
1712
- await (0, import_promises4.symlink)(
1713
- junction ? canonicalSkillDir : (0, import_node_path4.relative)(await physicalDir(linkDir), canonicalSkillDir),
2027
+ await (0, import_promises6.symlink)(
2028
+ junction ? canonicalSkillDir : (0, import_node_path5.relative)(await physicalDir(linkDir), canonicalSkillDir),
1714
2029
  linkPath,
1715
2030
  junction ? "junction" : void 0
1716
2031
  );
1717
2032
  return "linked";
1718
2033
  } catch {
1719
- await (0, import_promises4.cp)(canonicalSkillDir, linkPath, { recursive: true });
2034
+ await (0, import_promises6.cp)(canonicalSkillDir, linkPath, { recursive: true });
1720
2035
  return "copied";
1721
2036
  }
1722
2037
  }
1723
2038
  function hashSkill(files) {
1724
- const hash = (0, import_node_crypto3.createHash)("sha256");
2039
+ const hash = (0, import_node_crypto4.createHash)("sha256");
1725
2040
  for (const file of [...files].sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0)) {
1726
2041
  hash.update(file.path);
1727
2042
  hash.update("\0");
@@ -1758,9 +2073,9 @@ async function removeSkills(name, store) {
1758
2073
  keptForOthers.push({ skill, owner });
1759
2074
  continue;
1760
2075
  }
1761
- await (0, import_promises4.rm)((0, import_node_path4.join)(pkg.directory, skill), { recursive: true, force: true });
2076
+ await (0, import_promises6.rm)((0, import_node_path5.join)(pkg.directory, skill), { recursive: true, force: true });
1762
2077
  if (pkg.linkedInto !== void 0) {
1763
- await (0, import_promises4.rm)((0, import_node_path4.join)(pkg.linkedInto, skill), { recursive: true, force: true });
2078
+ await (0, import_promises6.rm)((0, import_node_path5.join)(pkg.linkedInto, skill), { recursive: true, force: true });
1764
2079
  }
1765
2080
  deleted.push(skill);
1766
2081
  }
@@ -1777,15 +2092,15 @@ async function outsidersAt(directory, skills, ours) {
1777
2092
  const blocked = [];
1778
2093
  for (const skill of skills) {
1779
2094
  if (ours.has(skill)) continue;
1780
- const path = (0, import_node_path4.join)(directory, skill);
1781
- if (await (0, import_promises4.stat)(path).catch(() => null)) blocked.push({ skill, path });
2095
+ const path = (0, import_node_path5.join)(directory, skill);
2096
+ if (await (0, import_promises6.stat)(path).catch(() => null)) blocked.push({ skill, path });
1782
2097
  }
1783
2098
  return blocked;
1784
2099
  }
1785
2100
  async function missingSkills(directory, skills) {
1786
2101
  const gone = /* @__PURE__ */ new Set();
1787
2102
  for (const skill of skills) {
1788
- if (!await (0, import_promises4.stat)((0, import_node_path4.join)(directory, skill)).catch(() => null)) gone.add(skill);
2103
+ if (!await (0, import_promises6.stat)((0, import_node_path5.join)(directory, skill)).catch(() => null)) gone.add(skill);
1789
2104
  }
1790
2105
  return gone;
1791
2106
  }
@@ -1899,14 +2214,14 @@ async function updateSkills(dependencies) {
1899
2214
  if (otherClaimant(state?.packages ?? {}, name, previous.directory, skill)) continue;
1900
2215
  const stalePlace = deletableLedgerDirectory(previous.directory);
1901
2216
  if (stalePlace === void 0) continue;
1902
- await (0, import_promises4.rm)((0, import_node_path4.join)(stalePlace, skill), { recursive: true, force: true });
2217
+ await (0, import_promises6.rm)((0, import_node_path5.join)(stalePlace, skill), { recursive: true, force: true });
1903
2218
  if (previous.linkedInto !== void 0) {
1904
- await (0, import_promises4.rm)((0, import_node_path4.join)(previous.linkedInto, skill), { recursive: true, force: true });
2219
+ await (0, import_promises6.rm)((0, import_node_path5.join)(previous.linkedInto, skill), { recursive: true, force: true });
1905
2220
  }
1906
2221
  }
1907
2222
  }
1908
2223
  for (const skill of skills) {
1909
- await (0, import_promises4.rm)((0, import_node_path4.join)(dependencies.directory, skill), { recursive: true, force: true });
2224
+ await (0, import_promises6.rm)((0, import_node_path5.join)(dependencies.directory, skill), { recursive: true, force: true });
1910
2225
  }
1911
2226
  const untouched = /* @__PURE__ */ new Set([
1912
2227
  ...skipped,
@@ -1915,17 +2230,17 @@ async function updateSkills(dependencies) {
1915
2230
  ]);
1916
2231
  for (const file of files) {
1917
2232
  if (untouched.has(file.skill)) continue;
1918
- const target = (0, import_node_path4.join)(dependencies.directory, file.skill, ...file.path.split("/"));
2233
+ const target = (0, import_node_path5.join)(dependencies.directory, file.skill, ...file.path.split("/"));
1919
2234
  assertInside(dependencies.directory, target);
1920
- await (0, import_promises4.mkdir)((0, import_node_path4.dirname)(target), { recursive: true });
1921
- await (0, import_promises4.writeFile)(target, file.data);
2235
+ await (0, import_promises6.mkdir)((0, import_node_path5.dirname)(target), { recursive: true });
2236
+ await (0, import_promises6.writeFile)(target, file.data);
1922
2237
  }
1923
2238
  let copiedCount = 0;
1924
2239
  if (dependencies.linkInto !== void 0) {
1925
2240
  for (const skill of skills) {
1926
2241
  const how = await linkSkill(
1927
- (0, import_node_path4.join)(dependencies.directory, skill),
1928
- (0, import_node_path4.join)(dependencies.linkInto, skill)
2242
+ (0, import_node_path5.join)(dependencies.directory, skill),
2243
+ (0, import_node_path5.join)(dependencies.linkInto, skill)
1929
2244
  );
1930
2245
  if (how === "copied") copiedCount += 1;
1931
2246
  }
@@ -2169,7 +2484,7 @@ async function planSetup(dependencies) {
2169
2484
  }
2170
2485
 
2171
2486
  // src/cli.ts
2172
- var VERSION = false ? "0.0.0-dev" : "0.9.1";
2487
+ var VERSION = false ? "0.0.0-dev" : "0.9.2";
2173
2488
  var SKILLS_ADD_DEPRECATION = "\u26A0 `pep skills add` will be removed in a future release.";
2174
2489
  function reportUpdate(result) {
2175
2490
  const conflicts = () => {
@@ -2302,7 +2617,7 @@ function parseSkillsArgs(command, args, cwd) {
2302
2617
  // `rm -rf` 到 B 头上。`--project` 没有这个问题,因为它走 `join(cwd, …)`。
2303
2618
  // 基准用 `cwd` 而不是 `process.cwd()`:与同一处的 `projectSkillsTarget(cwd)` 同源,
2304
2619
  // 也让这条分支可测。
2305
- { directory: (0, import_node_path5.resolve)(cwd ?? process.cwd(), explicitDirectory) }
2620
+ { directory: (0, import_node_path6.resolve)(cwd ?? process.cwd(), explicitDirectory) }
2306
2621
  ) : project ? projectSkillsTarget(cwd) : void 0;
2307
2622
  return { ...requested !== void 0 ? { requested } : {}, named, ...chosenTarget ? { chosenTarget } : {} };
2308
2623
  }
@@ -2517,7 +2832,8 @@ ${url}`)
2517
2832
  if (failure) throw new Error(failure);
2518
2833
  return;
2519
2834
  }
2520
- if (group !== "auth" && group !== "skills" && group !== "docs" && group !== "nexus")
2835
+ const GROUPS = ["auth", "skills", "docs", "resources", "nexus"];
2836
+ if (group === void 0 || !GROUPS.includes(group))
2521
2837
  throw new Error(`Unknown command.
2522
2838
 
2523
2839
  ${usage(VERSION)}`);
@@ -2527,25 +2843,59 @@ ${usage(VERSION)}`);
2527
2843
  return;
2528
2844
  }
2529
2845
  const { configStore, auth } = createCliServices();
2530
- if (group === "docs") {
2531
- if (command !== "list" && command !== "get") {
2532
- throw new Error(`Unknown docs command.
2846
+ if (group === "docs" || group === "resources") {
2847
+ const allowed = group === "docs" ? ["list", "get", "regions"] : ["list", "download"];
2848
+ if (!allowed.includes(command)) {
2849
+ throw new Error(`Unknown ${group} command.
2533
2850
 
2534
- ${usage(VERSION, ["docs"])}`);
2851
+ ${usage(VERSION, [group])}`);
2535
2852
  }
2853
+ const action = `${group}:${command}`;
2536
2854
  const explicitDocsUrl = option(args, "--docs-url");
2537
- const path = command === "get" ? args.shift() : void 0;
2855
+ const region = option(args, "--region");
2856
+ const platform3 = option(args, "--platform");
2857
+ const intoDirectory = option(args, "--dir");
2858
+ const force = flag(args, "--force");
2859
+ const path = action === "docs:get" ? args.shift() : void 0;
2860
+ const wantedFile = action === "resources:download" ? args.shift() : void 0;
2538
2861
  if (args.length > 0) throw new Error(`Unknown option: ${args[0]}`);
2539
- if (command === "get" && !path) throw new Error("pep docs get needs a document path.");
2862
+ if (action === "docs:get" && !path) throw new Error("pep docs get needs a document path.");
2863
+ if (action === "docs:regions" && region !== void 0) {
2864
+ throw new Error("pep docs regions lists every region \u2014 it takes no --region.");
2865
+ }
2866
+ if (action === "resources:download" && !wantedFile) {
2867
+ throw new Error(
2868
+ "pep resources download needs a file name. Run `pep resources list` to see what is there."
2869
+ );
2870
+ }
2871
+ if (platform3 !== void 0 && group !== "resources") {
2872
+ throw new Error(`pep ${group} ${command} takes no --platform.`);
2873
+ }
2874
+ if ((intoDirectory !== void 0 || force) && action !== "resources:download") {
2875
+ throw new Error(`pep ${group} ${command} takes no --dir or --force.`);
2876
+ }
2877
+ const wantedPlatform = platform3 === void 0 ? void 0 : resolvePlatform(platform3);
2878
+ if (platform3 !== void 0 && wantedPlatform === void 0) {
2879
+ throw new Error(`No such platform: ${platform3}. Available: ${PLATFORMS.join(", ")}.`);
2880
+ }
2540
2881
  const saved = await configStore.read();
2541
2882
  const docsUrl = explicitDocsUrl ? normalizeDocsUrl(explicitDocsUrl) : saved?.docsUrl ?? DEFAULT_DOCS_URL;
2542
2883
  if (explicitDocsUrl && saved && saved.docsUrl !== docsUrl) {
2543
2884
  await configStore.write({ ...saved, docsUrl });
2544
2885
  }
2886
+ if (action === "docs:regions") {
2887
+ const known = await fetchRegions({ docsUrl });
2888
+ for (const one of known.regions) {
2889
+ const aliases = one.aliases?.length ? ` ${one.aliases.join(",")}` : " ";
2890
+ const mark = one.id === known.defaultRegion ? " (default)" : "";
2891
+ console.log(`${one.id}${aliases} ${one.defaultLanguage} ${one.name}${mark}`);
2892
+ }
2893
+ return;
2894
+ }
2545
2895
  const authorization = await auth.currentAuthorization();
2546
2896
  const dependencies = { docsUrl, accessToken: authorization.accessToken };
2547
- if (command === "list") {
2548
- const entries = await fetchDocsIndex(dependencies);
2897
+ if (action === "docs:list") {
2898
+ const entries = await fetchDocsIndex(dependencies, region);
2549
2899
  if (entries.length === 0) {
2550
2900
  console.error("No documents are readable with this account.");
2551
2901
  return;
@@ -2555,7 +2905,42 @@ ${usage(VERSION, ["docs"])}`);
2555
2905
  }
2556
2906
  return;
2557
2907
  }
2558
- process.stdout.write(await fetchDocContent(dependencies, path));
2908
+ if (group === "resources") {
2909
+ const known = await fetchRegions({ docsUrl });
2910
+ const picked = region === void 0 ? resolveRegion(known, known.defaultRegion) : resolveRegion(known, region);
2911
+ if (!picked) throw unknownRegion(known, region ?? known.defaultRegion);
2912
+ const all = await fetchResources({ ...dependencies, region: picked }, wantedPlatform);
2913
+ if (action === "resources:list") {
2914
+ if (all.length === 0) {
2915
+ console.error("The developer resources page lists no files.");
2916
+ return;
2917
+ }
2918
+ for (const one of all) {
2919
+ const where = one.external ? "external" : one.platforms.join(",");
2920
+ console.log(`${one.file} ${where} ${one.url}`);
2921
+ }
2922
+ return;
2923
+ }
2924
+ const target = pickResource(all, wantedFile);
2925
+ const result = await downloadResource(dependencies, target, {
2926
+ // 默认落在**执行命令时的当前目录**,不是 CLI 装在哪儿。
2927
+ directory: intoDirectory ?? process.cwd(),
2928
+ force
2929
+ });
2930
+ console.error(
2931
+ result.verified === "blob-sha" ? ` ${result.bytes} bytes, content hash verified` : result.verified === "content-length" ? ` ${result.bytes} bytes, length verified (no content hash in the address)` : ` ${result.bytes} bytes \u2014 the server declared neither length nor content hash`
2932
+ );
2933
+ console.log(result.path);
2934
+ return;
2935
+ }
2936
+ let wanted = path;
2937
+ if (region !== void 0) {
2938
+ const known = await fetchRegions({ docsUrl });
2939
+ const picked = resolveRegion(known, region);
2940
+ if (!picked) throw unknownRegion(known, region);
2941
+ wanted = withRegion(wanted, picked);
2942
+ }
2943
+ process.stdout.write(await fetchDocContent(dependencies, wanted));
2559
2944
  return;
2560
2945
  }
2561
2946
  if (group === "nexus") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newlandnpt/pep-cli",
3
- "version": "0.9.1",
3
+ "version": "0.9.2",
4
4
  "description": "PEP CLI —— 凭 PEP 身份读取 Newland 开发者文档与 agent skills",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",