@newlandnpt/pep-cli 0.9.2 → 0.9.4

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 +125 -14
  2. package/package.json +1 -1
package/dist/pep.cjs CHANGED
@@ -23,6 +23,17 @@ var DEFAULT_SCOPES = [
23
23
  var DEFAULT_RESOURCES = ["urn:newland:pep:docs"];
24
24
  var LEGACY_PACKAGE = "semi-integration";
25
25
  var DEFAULT_DOCS_URL = "https://docs.newlandnpt.us";
26
+ var DOCS_BY_ISSUER = {
27
+ "https://pep.newlandnpt.us": "https://docs.newlandnpt.us",
28
+ "https://pep-webapp-view.onrender.com": "https://pep-developer-docs.onrender.com"
29
+ };
30
+ function docsUrlForIssuer(issuer) {
31
+ try {
32
+ return DOCS_BY_ISSUER[normalizeIssuer(issuer)];
33
+ } catch {
34
+ return void 0;
35
+ }
36
+ }
26
37
  var DEFAULT_SKILLS_GROUP = "distribution/skills";
27
38
  var EXAMPLE_SKILLS_REPO = `${DEFAULT_SKILLS_GROUP}/semi-integration-skill`;
28
39
  function issuerForEnvironment(environment) {
@@ -684,6 +695,7 @@ public static class PepCredential {
684
695
  function encodedPowerShell(script) {
685
696
  return Buffer.from(script, "utf16le").toString("base64");
686
697
  }
698
+ var CREDENTIAL_TOOL_TIMEOUT_MS = 6e4;
687
699
  async function runPowerShell(script, input) {
688
700
  return new Promise((resolve5, reject) => {
689
701
  const child = (0, import_node_child_process2.spawn)(
@@ -699,7 +711,11 @@ async function runPowerShell(script, input) {
699
711
  );
700
712
  let stdout = "";
701
713
  let stderr = "";
702
- const timeout = setTimeout(() => child.kill(), 1e4);
714
+ let killed = false;
715
+ const timeout = setTimeout(() => {
716
+ killed = true;
717
+ child.kill();
718
+ }, CREDENTIAL_TOOL_TIMEOUT_MS);
703
719
  child.stdout.setEncoding("utf8").on("data", (chunk) => stdout += chunk);
704
720
  child.stderr.setEncoding("utf8").on("data", (chunk) => stderr += chunk);
705
721
  child.once(
@@ -708,6 +724,14 @@ async function runPowerShell(script, input) {
708
724
  );
709
725
  child.once("close", (code) => {
710
726
  clearTimeout(timeout);
727
+ if (killed) {
728
+ reject(
729
+ new Error(
730
+ `Windows Credential Manager did not answer within ${CREDENTIAL_TOOL_TIMEOUT_MS / 1e3}s. The machine may be heavily loaded \u2014 try again.`
731
+ )
732
+ );
733
+ return;
734
+ }
711
735
  if (code === 0) resolve5(stdout.trim());
712
736
  else
713
737
  reject(
@@ -768,7 +792,11 @@ async function runSecurity(args) {
768
792
  });
769
793
  let stdout = "";
770
794
  let stderr = "";
771
- const timeout = setTimeout(() => child.kill(), 1e4);
795
+ let killed = false;
796
+ const timeout = setTimeout(() => {
797
+ killed = true;
798
+ child.kill();
799
+ }, CREDENTIAL_TOOL_TIMEOUT_MS);
772
800
  child.stdout.setEncoding("utf8").on("data", (chunk) => stdout += chunk);
773
801
  child.stderr.setEncoding("utf8").on("data", (chunk) => stderr += chunk);
774
802
  child.once(
@@ -777,6 +805,14 @@ async function runSecurity(args) {
777
805
  );
778
806
  child.once("close", (code) => {
779
807
  clearTimeout(timeout);
808
+ if (killed) {
809
+ reject(
810
+ new Error(
811
+ `macOS Keychain did not answer within ${CREDENTIAL_TOOL_TIMEOUT_MS / 1e3}s. The machine may be heavily loaded \u2014 try again.`
812
+ )
813
+ );
814
+ return;
815
+ }
780
816
  resolve5({ code: code ?? -1, stdout, stderr });
781
817
  });
782
818
  });
@@ -1117,6 +1153,30 @@ async function blobShaOfFile(path) {
1117
1153
  });
1118
1154
  return digest.digest("hex");
1119
1155
  }
1156
+ function humanBytes(n) {
1157
+ if (n < 1024) return `${n} B`;
1158
+ const units = ["KB", "MB", "GB"];
1159
+ let value = n / 1024;
1160
+ let unit = 0;
1161
+ while (value >= 1024 && unit < units.length - 1) {
1162
+ value /= 1024;
1163
+ unit += 1;
1164
+ }
1165
+ return `${value.toFixed(value < 10 ? 1 : 0)} ${units[unit]}`;
1166
+ }
1167
+ function formatProgress(progress, width = 24) {
1168
+ if (progress.phase === "verifying") return " verifying\u2026";
1169
+ const { bytes, total, elapsedMs } = progress;
1170
+ const speed = elapsedMs > 0 && bytes > 0 ? ` \xB7 ${humanBytes(bytes / elapsedMs * 1e3)}/s` : "";
1171
+ if (total === void 0 || total <= 0) {
1172
+ return ` ${humanBytes(bytes)}${speed}`;
1173
+ }
1174
+ const ratio = Math.min(1, bytes / total);
1175
+ const filled = Math.round(ratio * width);
1176
+ const bar = `${"\u2588".repeat(filled)}${"\u2591".repeat(width - filled)}`;
1177
+ const pct = `${Math.floor(ratio * 100)}`.padStart(3);
1178
+ return ` ${bar} ${pct}% ${humanBytes(bytes)} / ${humanBytes(total)}${speed}`;
1179
+ }
1120
1180
  async function downloadResource(dependencies, resource, options) {
1121
1181
  const file = (0, import_node_path3.basename)(resource.file);
1122
1182
  if (file !== resource.file || file === "." || file === "..") {
@@ -1149,8 +1209,26 @@ async function downloadResource(dependencies, resource, options) {
1149
1209
  await (0, import_promises3.mkdir)(directory, { recursive: true });
1150
1210
  const temporary = `${target}.${process.pid}.part`;
1151
1211
  try {
1212
+ const report = options.onProgress;
1213
+ const startedAt = Date.now();
1214
+ let seen = 0;
1152
1215
  await (0, import_promises4.pipeline)(
1153
1216
  import_node_stream.Readable.fromWeb(response.body),
1217
+ // ⚠ 这一层**无条件**插着,没有回调时它只是加一个数。本来想「没人听就不插」,
1218
+ // 但那要往 `pipeline(...)` 里展开一个数组,TS 的重载当场推不出来;为省一层
1219
+ // generator 去跟类型较劲不划算。
1220
+ async function* (source) {
1221
+ for await (const chunk of source) {
1222
+ seen += chunk.length;
1223
+ report?.({
1224
+ phase: "downloading",
1225
+ bytes: seen,
1226
+ ...Number.isFinite(declared) && declared > 0 ? { total: declared } : {},
1227
+ elapsedMs: Date.now() - startedAt
1228
+ });
1229
+ yield chunk;
1230
+ }
1231
+ },
1154
1232
  (0, import_node_fs.createWriteStream)(temporary)
1155
1233
  );
1156
1234
  const bytes = (await (0, import_promises3.stat)(temporary)).size;
@@ -1161,6 +1239,7 @@ async function downloadResource(dependencies, resource, options) {
1161
1239
  }
1162
1240
  let verified = "none";
1163
1241
  if (expected !== void 0) {
1242
+ options.onProgress?.({ phase: "verifying" });
1164
1243
  const actual = (await blobShaOfFile(temporary)).slice(0, 12);
1165
1244
  if (actual !== expected) {
1166
1245
  throw new Error(
@@ -1308,6 +1387,7 @@ async function persistMavenEnv(credential, target = resolveTarget(), now = () =>
1308
1387
  var EXAMPLE_PATH = EXAMPLE_SKILLS_REPO;
1309
1388
  var EXAMPLE_REPO = `https://git.newlandpayment.com/${EXAMPLE_PATH}.git`;
1310
1389
  var PROJECT_FLAG = "-p, --project Install into ./.agents/skills of the current directory instead";
1390
+ var DOCS_URL_FLAG = `--docs-url <url> This run only; login picks the one paired with your PEP (default: ${DEFAULT_DOCS_URL})`;
1311
1391
  var REGION_FLAG = "--region <id> Documentation region for this run only (see pep docs regions)";
1312
1392
  var groups = {
1313
1393
  auth: {
@@ -1435,7 +1515,7 @@ pep skills update will not bring the skills back. Run pep skills add to do that.
1435
1515
  usage: "pep docs list [flags]",
1436
1516
  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
1517
  flags: [
1438
- `--docs-url <url> Documentation server; saved for later use (default: ${DEFAULT_DOCS_URL})`,
1518
+ DOCS_URL_FLAG,
1439
1519
  REGION_FLAG
1440
1520
  ],
1441
1521
  examples: [
@@ -1449,7 +1529,7 @@ pep skills update will not bring the skills back. Run pep skills add to do that.
1449
1529
  usage: "pep docs get <path> [flags]",
1450
1530
  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
1531
  flags: [
1452
- `--docs-url <url> Documentation server; saved for later use (default: ${DEFAULT_DOCS_URL})`,
1532
+ DOCS_URL_FLAG,
1453
1533
  REGION_FLAG
1454
1534
  ],
1455
1535
  examples: [
@@ -1462,7 +1542,7 @@ pep skills update will not bring the skills back. Run pep skills add to do that.
1462
1542
  summary: "List the documentation regions this deployment serves.",
1463
1543
  usage: "pep docs regions [flags]",
1464
1544
  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.',
1465
- flags: [`--docs-url <url> Documentation server; saved for later use (default: ${DEFAULT_DOCS_URL})`],
1545
+ flags: [DOCS_URL_FLAG],
1466
1546
  examples: ["pep docs regions"]
1467
1547
  }
1468
1548
  }
@@ -1477,7 +1557,7 @@ pep skills update will not bring the skills back. Run pep skills add to do that.
1477
1557
  flags: [
1478
1558
  `--platform <name> Only this platform (${PLATFORMS.join(", ")})`,
1479
1559
  REGION_FLAG,
1480
- `--docs-url <url> Documentation server; saved for later use (default: ${DEFAULT_DOCS_URL})`
1560
+ DOCS_URL_FLAG
1481
1561
  ],
1482
1562
  examples: [
1483
1563
  "pep resources list",
@@ -1493,7 +1573,7 @@ pep skills update will not bring the skills back. Run pep skills add to do that.
1493
1573
  "--dir <path> Directory to save into; created if missing (default: the current directory)",
1494
1574
  "--force Replace an existing file of the same name",
1495
1575
  REGION_FLAG,
1496
- `--docs-url <url> Documentation server; saved for later use (default: ${DEFAULT_DOCS_URL})`
1576
+ DOCS_URL_FLAG
1497
1577
  ],
1498
1578
  examples: [
1499
1579
  "pep resources download SaleSystemDemo-4.8.0.2-win-x64.zip",
@@ -2484,8 +2564,29 @@ async function planSetup(dependencies) {
2484
2564
  }
2485
2565
 
2486
2566
  // src/cli.ts
2487
- var VERSION = false ? "0.0.0-dev" : "0.9.2";
2567
+ var VERSION = false ? "0.0.0-dev" : "0.9.4";
2488
2568
  var SKILLS_ADD_DEPRECATION = "\u26A0 `pep skills add` will be removed in a future release.";
2569
+ function createProgressReporter(stream = process.stderr) {
2570
+ if (!stream.isTTY) return void 0;
2571
+ const INTERVAL_MS = 100;
2572
+ let lastAt = 0;
2573
+ let painted = false;
2574
+ const paint = (line) => {
2575
+ stream.write(`\r${" ".repeat(78)}\r${line}`);
2576
+ painted = true;
2577
+ };
2578
+ return {
2579
+ report(progress) {
2580
+ const now = Date.now();
2581
+ if (progress.phase === "downloading" && now - lastAt < INTERVAL_MS) return;
2582
+ lastAt = now;
2583
+ paint(formatProgress(progress));
2584
+ },
2585
+ done() {
2586
+ if (painted) stream.write(`\r${" ".repeat(78)}\r`);
2587
+ }
2588
+ };
2589
+ }
2489
2590
  function reportUpdate(result) {
2490
2591
  const conflicts = () => {
2491
2592
  for (const { skill, owner } of result.conflicts) {
@@ -2565,14 +2666,14 @@ async function loginConfig(args, store = fileConfigStore()) {
2565
2666
  const explicit = repeatedOption(args, "--resource");
2566
2667
  const resources = explicit.length > 0 ? explicit : [...DEFAULT_RESOURCES];
2567
2668
  if (args.length > 0) throw new Error(`Unknown option: ${args[0]}`);
2568
- const saved = await store.read();
2669
+ const paired = docsUrlForIssuer(issuer);
2569
2670
  return {
2570
2671
  version: 1,
2571
2672
  issuer,
2572
2673
  clientId,
2573
2674
  redirectUri: DEFAULT_REDIRECT_URI,
2574
2675
  resources,
2575
- ...saved?.docsUrl !== void 0 ? { docsUrl: saved.docsUrl } : {}
2676
+ ...paired !== void 0 ? { docsUrl: paired } : {}
2576
2677
  };
2577
2678
  }
2578
2679
  function takeLoginOptions(args) {
@@ -2880,9 +2981,6 @@ ${usage(VERSION, [group])}`);
2880
2981
  }
2881
2982
  const saved = await configStore.read();
2882
2983
  const docsUrl = explicitDocsUrl ? normalizeDocsUrl(explicitDocsUrl) : saved?.docsUrl ?? DEFAULT_DOCS_URL;
2883
- if (explicitDocsUrl && saved && saved.docsUrl !== docsUrl) {
2884
- await configStore.write({ ...saved, docsUrl });
2885
- }
2886
2984
  if (action === "docs:regions") {
2887
2985
  const known = await fetchRegions({ docsUrl });
2888
2986
  for (const one of known.regions) {
@@ -2922,11 +3020,14 @@ ${usage(VERSION, [group])}`);
2922
3020
  return;
2923
3021
  }
2924
3022
  const target = pickResource(all, wantedFile);
3023
+ const progress = createProgressReporter();
2925
3024
  const result = await downloadResource(dependencies, target, {
2926
3025
  // 默认落在**执行命令时的当前目录**,不是 CLI 装在哪儿。
2927
3026
  directory: intoDirectory ?? process.cwd(),
2928
- force
3027
+ force,
3028
+ ...progress ? { onProgress: progress.report } : {}
2929
3029
  });
3030
+ progress?.done();
2930
3031
  console.error(
2931
3032
  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
3033
  );
@@ -3070,6 +3171,16 @@ ${usage(VERSION, ["skills"])}`);
3070
3171
  ${url}`)
3071
3172
  );
3072
3173
  console.log(`Logged in. Granted scopes: ${authorization.scopes.join(" ")}`);
3174
+ if (config.docsUrl !== void 0) {
3175
+ console.error(`Documentation site: ${config.docsUrl}`);
3176
+ } else {
3177
+ console.error(
3178
+ `\u26A0 No documentation site is paired with this PEP.
3179
+ docs and resources commands will use ${DEFAULT_DOCS_URL}, which checks
3180
+ tokens against a different PEP and will answer 401. Pass --docs-url to
3181
+ point them at the right one for this run.`
3182
+ );
3183
+ }
3073
3184
  return;
3074
3185
  }
3075
3186
  if (args.length > 0) throw new Error(`Unexpected argument: ${args[0]}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newlandnpt/pep-cli",
3
- "version": "0.9.2",
3
+ "version": "0.9.4",
4
4
  "description": "PEP CLI —— 凭 PEP 身份读取 Newland 开发者文档与 agent skills",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",