@newlandnpt/pep-cli 0.9.1 → 0.9.3
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/dist/pep.cjs +549 -72
- 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((
|
|
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
|
|
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((
|
|
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
|
-
|
|
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((
|
|
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
|
|
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");
|
|
@@ -684,8 +684,9 @@ public static class PepCredential {
|
|
|
684
684
|
function encodedPowerShell(script) {
|
|
685
685
|
return Buffer.from(script, "utf16le").toString("base64");
|
|
686
686
|
}
|
|
687
|
+
var CREDENTIAL_TOOL_TIMEOUT_MS = 6e4;
|
|
687
688
|
async function runPowerShell(script, input) {
|
|
688
|
-
return new Promise((
|
|
689
|
+
return new Promise((resolve5, reject) => {
|
|
689
690
|
const child = (0, import_node_child_process2.spawn)(
|
|
690
691
|
"powershell.exe",
|
|
691
692
|
[
|
|
@@ -699,7 +700,11 @@ async function runPowerShell(script, input) {
|
|
|
699
700
|
);
|
|
700
701
|
let stdout = "";
|
|
701
702
|
let stderr = "";
|
|
702
|
-
|
|
703
|
+
let killed = false;
|
|
704
|
+
const timeout = setTimeout(() => {
|
|
705
|
+
killed = true;
|
|
706
|
+
child.kill();
|
|
707
|
+
}, CREDENTIAL_TOOL_TIMEOUT_MS);
|
|
703
708
|
child.stdout.setEncoding("utf8").on("data", (chunk) => stdout += chunk);
|
|
704
709
|
child.stderr.setEncoding("utf8").on("data", (chunk) => stderr += chunk);
|
|
705
710
|
child.once(
|
|
@@ -708,7 +713,15 @@ async function runPowerShell(script, input) {
|
|
|
708
713
|
);
|
|
709
714
|
child.once("close", (code) => {
|
|
710
715
|
clearTimeout(timeout);
|
|
711
|
-
if (
|
|
716
|
+
if (killed) {
|
|
717
|
+
reject(
|
|
718
|
+
new Error(
|
|
719
|
+
`Windows Credential Manager did not answer within ${CREDENTIAL_TOOL_TIMEOUT_MS / 1e3}s. The machine may be heavily loaded \u2014 try again.`
|
|
720
|
+
)
|
|
721
|
+
);
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
if (code === 0) resolve5(stdout.trim());
|
|
712
725
|
else
|
|
713
726
|
reject(
|
|
714
727
|
new Error(
|
|
@@ -762,13 +775,17 @@ $raw = [Console]::In.ReadToEnd()
|
|
|
762
775
|
}
|
|
763
776
|
var SECURITY_NOT_FOUND = 44;
|
|
764
777
|
async function runSecurity(args) {
|
|
765
|
-
return new Promise((
|
|
778
|
+
return new Promise((resolve5, reject) => {
|
|
766
779
|
const child = (0, import_node_child_process2.spawn)("/usr/bin/security", [...args], {
|
|
767
780
|
stdio: ["ignore", "pipe", "pipe"]
|
|
768
781
|
});
|
|
769
782
|
let stdout = "";
|
|
770
783
|
let stderr = "";
|
|
771
|
-
|
|
784
|
+
let killed = false;
|
|
785
|
+
const timeout = setTimeout(() => {
|
|
786
|
+
killed = true;
|
|
787
|
+
child.kill();
|
|
788
|
+
}, CREDENTIAL_TOOL_TIMEOUT_MS);
|
|
772
789
|
child.stdout.setEncoding("utf8").on("data", (chunk) => stdout += chunk);
|
|
773
790
|
child.stderr.setEncoding("utf8").on("data", (chunk) => stderr += chunk);
|
|
774
791
|
child.once(
|
|
@@ -777,7 +794,15 @@ async function runSecurity(args) {
|
|
|
777
794
|
);
|
|
778
795
|
child.once("close", (code) => {
|
|
779
796
|
clearTimeout(timeout);
|
|
780
|
-
|
|
797
|
+
if (killed) {
|
|
798
|
+
reject(
|
|
799
|
+
new Error(
|
|
800
|
+
`macOS Keychain did not answer within ${CREDENTIAL_TOOL_TIMEOUT_MS / 1e3}s. The machine may be heavily loaded \u2014 try again.`
|
|
801
|
+
)
|
|
802
|
+
);
|
|
803
|
+
return;
|
|
804
|
+
}
|
|
805
|
+
resolve5({ code: code ?? -1, stdout, stderr });
|
|
781
806
|
});
|
|
782
807
|
});
|
|
783
808
|
}
|
|
@@ -874,6 +899,8 @@ async function fetchOrExplain(doFetch, url, init, what, timeoutMs = HEADERS_TIME
|
|
|
874
899
|
|
|
875
900
|
// src/docs-service.ts
|
|
876
901
|
var ENTRY = /^-\s*\[([^\]]*)\]\(([^)]+)\)(?::\s*(.*))?$/;
|
|
902
|
+
var INDEX_PATH = "/llms.txt";
|
|
903
|
+
var REGIONS_PATH = "/api/docs/regions";
|
|
877
904
|
function parseDocsIndex(text) {
|
|
878
905
|
const entries = [];
|
|
879
906
|
for (const line of text.split("\n")) {
|
|
@@ -900,6 +927,50 @@ function contentPathFor(path) {
|
|
|
900
927
|
const after = segments.slice(docsAt);
|
|
901
928
|
return `/${[...before, "llms.mdx", ...after, "content.md"].join("/")}`;
|
|
902
929
|
}
|
|
930
|
+
async function fetchRegions(dependencies) {
|
|
931
|
+
const doFetch = dependencies.fetch ?? globalThis.fetch;
|
|
932
|
+
const response = await fetchOrExplain(
|
|
933
|
+
doFetch,
|
|
934
|
+
`${dependencies.docsUrl.replace(/\/+$/, "")}${REGIONS_PATH}`,
|
|
935
|
+
{ headers: { Accept: "application/json" } },
|
|
936
|
+
"Could not reach the documentation platform"
|
|
937
|
+
);
|
|
938
|
+
if (!response.ok) {
|
|
939
|
+
await response.body?.cancel();
|
|
940
|
+
if (response.status === 404) {
|
|
941
|
+
throw new Error(
|
|
942
|
+
"This documentation deployment has no /api/docs/regions endpoint \u2014 it predates region support. Ask an operator to deploy a newer build."
|
|
943
|
+
);
|
|
944
|
+
}
|
|
945
|
+
throw new Error(describeFailure(response.status, REGIONS_PATH));
|
|
946
|
+
}
|
|
947
|
+
const body = await response.json();
|
|
948
|
+
const regions = body?.regions;
|
|
949
|
+
if (!Array.isArray(regions) || regions.length === 0) {
|
|
950
|
+
throw new Error("The documentation platform returned no regions.");
|
|
951
|
+
}
|
|
952
|
+
return body;
|
|
953
|
+
}
|
|
954
|
+
function resolveRegion(known, ref) {
|
|
955
|
+
const wanted = ref.trim().toLowerCase();
|
|
956
|
+
if (!wanted) return void 0;
|
|
957
|
+
return known.regions.find(
|
|
958
|
+
(region) => region.id.toLowerCase() === wanted || (region.aliases ?? []).some((alias) => alias.toLowerCase() === wanted)
|
|
959
|
+
);
|
|
960
|
+
}
|
|
961
|
+
function unknownRegion(known, ref) {
|
|
962
|
+
const names = known.regions.map((one) => one.aliases?.length ? `${one.id} (${one.aliases.join(", ")})` : one.id).join(", ");
|
|
963
|
+
return new Error(`No such documentation region: ${ref}. Available: ${names}.`);
|
|
964
|
+
}
|
|
965
|
+
function withRegion(path, region) {
|
|
966
|
+
const clean = `/${path.replace(/^\/+/, "").replace(/\/+$/, "")}`;
|
|
967
|
+
const segments = clean.split("/").filter(Boolean);
|
|
968
|
+
const marker = segments.findIndex((one) => one === "llms.mdx" || one === "docs");
|
|
969
|
+
if (marker === -1) {
|
|
970
|
+
throw new Error(`Not a documentation path (expected \u2026/docs/\u2026): ${path}`);
|
|
971
|
+
}
|
|
972
|
+
return `/${[region.id, region.defaultLanguage, ...segments.slice(marker)].join("/")}`;
|
|
973
|
+
}
|
|
903
974
|
function describeFailure(status, what) {
|
|
904
975
|
if (status === 401) {
|
|
905
976
|
return `The documentation platform rejected the access token. Run \`pep auth login\` again.`;
|
|
@@ -910,12 +981,15 @@ function describeFailure(status, what) {
|
|
|
910
981
|
if (status === 404) {
|
|
911
982
|
return `No such document, or your account cannot read it: ${what}`;
|
|
912
983
|
}
|
|
984
|
+
if (status === 400) {
|
|
985
|
+
return `The documentation platform rejected that request: ${what}`;
|
|
986
|
+
}
|
|
913
987
|
if (status === 502) {
|
|
914
988
|
return "The documentation platform could not reach PEP to verify the token. Retry shortly.";
|
|
915
989
|
}
|
|
916
990
|
return `The documentation platform answered ${status} for ${what}.`;
|
|
917
991
|
}
|
|
918
|
-
async function get(dependencies, path, accept) {
|
|
992
|
+
async function get(dependencies, path, accept, explain) {
|
|
919
993
|
const doFetch = dependencies.fetch ?? globalThis.fetch;
|
|
920
994
|
const response = await fetchOrExplain(
|
|
921
995
|
doFetch,
|
|
@@ -924,13 +998,22 @@ async function get(dependencies, path, accept) {
|
|
|
924
998
|
"Could not reach the documentation platform"
|
|
925
999
|
);
|
|
926
1000
|
if (!response.ok) {
|
|
927
|
-
await response
|
|
928
|
-
|
|
1001
|
+
const custom = explain ? await explain(response) : void 0;
|
|
1002
|
+
if (!response.bodyUsed) await response.body?.cancel().catch(() => void 0);
|
|
1003
|
+
throw new Error(custom ?? describeFailure(response.status, path));
|
|
929
1004
|
}
|
|
930
1005
|
return response;
|
|
931
1006
|
}
|
|
932
|
-
async function fetchDocsIndex(dependencies) {
|
|
933
|
-
const
|
|
1007
|
+
async function fetchDocsIndex(dependencies, region) {
|
|
1008
|
+
const path = region === void 0 ? INDEX_PATH : `${INDEX_PATH}?site=${encodeURIComponent(region)}`;
|
|
1009
|
+
const response = await get(dependencies, path, "text/plain", async (bad) => {
|
|
1010
|
+
if (bad.status !== 400) return void 0;
|
|
1011
|
+
const available = await bad.json().then((body) => body?.available).catch(() => void 0);
|
|
1012
|
+
if (!Array.isArray(available) || available.length === 0) {
|
|
1013
|
+
return `No such documentation region: ${region}.`;
|
|
1014
|
+
}
|
|
1015
|
+
return `No such documentation region: ${region}. Available: ${available.join(", ")}.`;
|
|
1016
|
+
});
|
|
934
1017
|
return parseDocsIndex(await response.text());
|
|
935
1018
|
}
|
|
936
1019
|
async function fetchDocContent(dependencies, path) {
|
|
@@ -938,10 +1021,251 @@ async function fetchDocContent(dependencies, path) {
|
|
|
938
1021
|
return response.text();
|
|
939
1022
|
}
|
|
940
1023
|
|
|
941
|
-
// src/
|
|
1024
|
+
// src/docs-resources.ts
|
|
1025
|
+
var import_node_crypto3 = require("node:crypto");
|
|
1026
|
+
var import_node_fs = require("node:fs");
|
|
942
1027
|
var import_promises3 = require("node:fs/promises");
|
|
943
|
-
var import_node_os2 = require("node:os");
|
|
944
1028
|
var import_node_path3 = require("node:path");
|
|
1029
|
+
var import_node_stream = require("node:stream");
|
|
1030
|
+
var import_promises4 = require("node:stream/promises");
|
|
1031
|
+
var RESOURCES_SLUG = "docs/Terminal/References/Developer-Resources";
|
|
1032
|
+
var PLATFORMS = ["Android", "Windows", "iOS"];
|
|
1033
|
+
function resolvePlatform(ref) {
|
|
1034
|
+
const wanted = ref.trim().toLowerCase();
|
|
1035
|
+
return PLATFORMS.find((one) => one.toLowerCase() === wanted);
|
|
1036
|
+
}
|
|
1037
|
+
function extractResourceLinks(markdown, platform3) {
|
|
1038
|
+
const text = markdown.replace(/!\[[^\]]*\]\([^)]*\)/g, "").replace(/<img\b[^>]*>/gi, "");
|
|
1039
|
+
const found = [];
|
|
1040
|
+
for (const match of text.matchAll(/\[[^\]]*\]\(([^)\s]+)/g)) {
|
|
1041
|
+
const url = match[1];
|
|
1042
|
+
if (url.startsWith("#")) continue;
|
|
1043
|
+
const external = /^https?:\/\//i.test(url);
|
|
1044
|
+
const pathPart = external ? safePath(url) : url;
|
|
1045
|
+
if (pathPart === void 0) continue;
|
|
1046
|
+
if (!external && !pathPart.startsWith("/assets/")) continue;
|
|
1047
|
+
const inSitePool = pathPart.startsWith("/assets/");
|
|
1048
|
+
const file = decodeURIComponent((0, import_node_path3.basename)(pathPart));
|
|
1049
|
+
if (!file) continue;
|
|
1050
|
+
found.push({ file, url, platforms: [platform3], external: external && !inSitePool });
|
|
1051
|
+
}
|
|
1052
|
+
return found;
|
|
1053
|
+
}
|
|
1054
|
+
function safePath(url) {
|
|
1055
|
+
try {
|
|
1056
|
+
return new URL(url).pathname;
|
|
1057
|
+
} catch {
|
|
1058
|
+
return void 0;
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
function mergeResources(all) {
|
|
1062
|
+
const byUrl = /* @__PURE__ */ new Map();
|
|
1063
|
+
for (const one of all) {
|
|
1064
|
+
const seen = byUrl.get(one.url);
|
|
1065
|
+
if (!seen) {
|
|
1066
|
+
byUrl.set(one.url, { ...one, platforms: [...one.platforms] });
|
|
1067
|
+
continue;
|
|
1068
|
+
}
|
|
1069
|
+
for (const p of one.platforms) if (!seen.platforms.includes(p)) seen.platforms.push(p);
|
|
1070
|
+
}
|
|
1071
|
+
return [...byUrl.values()].sort((a, b) => a.file.localeCompare(b.file));
|
|
1072
|
+
}
|
|
1073
|
+
async function fetchResources(dependencies, platform3) {
|
|
1074
|
+
const page = withRegion(`/${RESOURCES_SLUG}`, dependencies.region);
|
|
1075
|
+
const wanted = platform3 ? [platform3] : [...PLATFORMS];
|
|
1076
|
+
const all = [];
|
|
1077
|
+
for (const one of wanted) {
|
|
1078
|
+
const markdown = await fetchTab(dependencies, page, one);
|
|
1079
|
+
all.push(...extractResourceLinks(markdown, one));
|
|
1080
|
+
}
|
|
1081
|
+
return mergeResources(all);
|
|
1082
|
+
}
|
|
1083
|
+
async function fetchTab(dependencies, page, platform3) {
|
|
1084
|
+
const doFetch = dependencies.fetch ?? globalThis.fetch;
|
|
1085
|
+
const url = new URL(
|
|
1086
|
+
`${dependencies.docsUrl.replace(/\/+$/, "")}${contentPathFor(page)}`
|
|
1087
|
+
);
|
|
1088
|
+
url.searchParams.set("lang", platform3);
|
|
1089
|
+
const response = await fetchOrExplain(
|
|
1090
|
+
doFetch,
|
|
1091
|
+
url,
|
|
1092
|
+
{
|
|
1093
|
+
headers: {
|
|
1094
|
+
Authorization: `Bearer ${dependencies.accessToken}`,
|
|
1095
|
+
Accept: "text/markdown"
|
|
1096
|
+
}
|
|
1097
|
+
},
|
|
1098
|
+
"Could not read the developer resources page"
|
|
1099
|
+
);
|
|
1100
|
+
if (!response.ok) {
|
|
1101
|
+
await response.body?.cancel().catch(() => void 0);
|
|
1102
|
+
if (response.status === 404) {
|
|
1103
|
+
throw new Error(
|
|
1104
|
+
`The developer resources page is not available to this account (or this region has no such page): ${page}`
|
|
1105
|
+
);
|
|
1106
|
+
}
|
|
1107
|
+
throw new Error(`The documentation platform answered ${response.status} for ${page}.`);
|
|
1108
|
+
}
|
|
1109
|
+
return response.text();
|
|
1110
|
+
}
|
|
1111
|
+
function pickResource(all, file) {
|
|
1112
|
+
const hits = all.filter((one) => one.file === file);
|
|
1113
|
+
if (hits.length === 0) {
|
|
1114
|
+
throw new Error(
|
|
1115
|
+
`No such file on the developer resources page: ${file}. Run \`pep docs resources\` to see what is there.`
|
|
1116
|
+
);
|
|
1117
|
+
}
|
|
1118
|
+
if (hits.length > 1) {
|
|
1119
|
+
const urls = hits.map((one) => ` ${one.url}`).join("\n");
|
|
1120
|
+
throw new Error(
|
|
1121
|
+
`${file} appears more than once with different addresses:
|
|
1122
|
+
${urls}
|
|
1123
|
+
The page is ambiguous \u2014 ask an operator which one is current.`
|
|
1124
|
+
);
|
|
1125
|
+
}
|
|
1126
|
+
const only = hits[0];
|
|
1127
|
+
if (only.external) {
|
|
1128
|
+
throw new Error(
|
|
1129
|
+
`${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.`
|
|
1130
|
+
);
|
|
1131
|
+
}
|
|
1132
|
+
return only;
|
|
1133
|
+
}
|
|
1134
|
+
function expectedBlobSha(url) {
|
|
1135
|
+
return /\/assets\/_shared\/([0-9a-f]{12})\//.exec(url)?.[1];
|
|
1136
|
+
}
|
|
1137
|
+
async function blobShaOfFile(path) {
|
|
1138
|
+
const size = (await (0, import_promises3.stat)(path)).size;
|
|
1139
|
+
const digest = (0, import_node_crypto3.createHash)("sha1").update(`blob ${size}\0`);
|
|
1140
|
+
await (0, import_promises4.pipeline)((0, import_node_fs.createReadStream)(path), async function* (source) {
|
|
1141
|
+
for await (const chunk of source) digest.update(chunk);
|
|
1142
|
+
});
|
|
1143
|
+
return digest.digest("hex");
|
|
1144
|
+
}
|
|
1145
|
+
function humanBytes(n) {
|
|
1146
|
+
if (n < 1024) return `${n} B`;
|
|
1147
|
+
const units = ["KB", "MB", "GB"];
|
|
1148
|
+
let value = n / 1024;
|
|
1149
|
+
let unit = 0;
|
|
1150
|
+
while (value >= 1024 && unit < units.length - 1) {
|
|
1151
|
+
value /= 1024;
|
|
1152
|
+
unit += 1;
|
|
1153
|
+
}
|
|
1154
|
+
return `${value.toFixed(value < 10 ? 1 : 0)} ${units[unit]}`;
|
|
1155
|
+
}
|
|
1156
|
+
function formatProgress(progress, width = 24) {
|
|
1157
|
+
if (progress.phase === "verifying") return " verifying\u2026";
|
|
1158
|
+
const { bytes, total, elapsedMs } = progress;
|
|
1159
|
+
const speed = elapsedMs > 0 && bytes > 0 ? ` \xB7 ${humanBytes(bytes / elapsedMs * 1e3)}/s` : "";
|
|
1160
|
+
if (total === void 0 || total <= 0) {
|
|
1161
|
+
return ` ${humanBytes(bytes)}${speed}`;
|
|
1162
|
+
}
|
|
1163
|
+
const ratio = Math.min(1, bytes / total);
|
|
1164
|
+
const filled = Math.round(ratio * width);
|
|
1165
|
+
const bar = `${"\u2588".repeat(filled)}${"\u2591".repeat(width - filled)}`;
|
|
1166
|
+
const pct = `${Math.floor(ratio * 100)}`.padStart(3);
|
|
1167
|
+
return ` ${bar} ${pct}% ${humanBytes(bytes)} / ${humanBytes(total)}${speed}`;
|
|
1168
|
+
}
|
|
1169
|
+
async function downloadResource(dependencies, resource, options) {
|
|
1170
|
+
const file = (0, import_node_path3.basename)(resource.file);
|
|
1171
|
+
if (file !== resource.file || file === "." || file === "..") {
|
|
1172
|
+
throw new Error(`Refusing to write a file named ${resource.file}.`);
|
|
1173
|
+
}
|
|
1174
|
+
const directory = (0, import_node_path3.resolve)(options.directory);
|
|
1175
|
+
const target = (0, import_node_path3.resolve)(directory, file);
|
|
1176
|
+
if (!target.startsWith(directory + "/") && !target.startsWith(directory + "\\")) {
|
|
1177
|
+
throw new Error(`Refusing to write outside ${directory}.`);
|
|
1178
|
+
}
|
|
1179
|
+
if (!options.force && await (0, import_promises3.stat)(target).catch(() => null)) {
|
|
1180
|
+
throw new Error(
|
|
1181
|
+
`${target} already exists. Pass --force to replace it (the existing file is kept until the new one has downloaded completely).`
|
|
1182
|
+
);
|
|
1183
|
+
}
|
|
1184
|
+
const doFetch = dependencies.fetch ?? globalThis.fetch;
|
|
1185
|
+
const absolute = resource.url.startsWith("/") ? `${dependencies.docsUrl.replace(/\/+$/, "")}${resource.url}` : resource.url;
|
|
1186
|
+
const response = await fetchOrExplain(
|
|
1187
|
+
doFetch,
|
|
1188
|
+
absolute,
|
|
1189
|
+
{ headers: { Authorization: `Bearer ${dependencies.accessToken}` } },
|
|
1190
|
+
`Could not download ${file}`
|
|
1191
|
+
);
|
|
1192
|
+
if (!response.ok || !response.body) {
|
|
1193
|
+
await response.body?.cancel().catch(() => void 0);
|
|
1194
|
+
throw new Error(describeDownloadFailure(response.status, file));
|
|
1195
|
+
}
|
|
1196
|
+
const declared = Number(response.headers.get("content-length") ?? "");
|
|
1197
|
+
const expected = expectedBlobSha(resource.url);
|
|
1198
|
+
await (0, import_promises3.mkdir)(directory, { recursive: true });
|
|
1199
|
+
const temporary = `${target}.${process.pid}.part`;
|
|
1200
|
+
try {
|
|
1201
|
+
const report = options.onProgress;
|
|
1202
|
+
const startedAt = Date.now();
|
|
1203
|
+
let seen = 0;
|
|
1204
|
+
await (0, import_promises4.pipeline)(
|
|
1205
|
+
import_node_stream.Readable.fromWeb(response.body),
|
|
1206
|
+
// ⚠ 这一层**无条件**插着,没有回调时它只是加一个数。本来想「没人听就不插」,
|
|
1207
|
+
// 但那要往 `pipeline(...)` 里展开一个数组,TS 的重载当场推不出来;为省一层
|
|
1208
|
+
// generator 去跟类型较劲不划算。
|
|
1209
|
+
async function* (source) {
|
|
1210
|
+
for await (const chunk of source) {
|
|
1211
|
+
seen += chunk.length;
|
|
1212
|
+
report?.({
|
|
1213
|
+
phase: "downloading",
|
|
1214
|
+
bytes: seen,
|
|
1215
|
+
...Number.isFinite(declared) && declared > 0 ? { total: declared } : {},
|
|
1216
|
+
elapsedMs: Date.now() - startedAt
|
|
1217
|
+
});
|
|
1218
|
+
yield chunk;
|
|
1219
|
+
}
|
|
1220
|
+
},
|
|
1221
|
+
(0, import_node_fs.createWriteStream)(temporary)
|
|
1222
|
+
);
|
|
1223
|
+
const bytes = (await (0, import_promises3.stat)(temporary)).size;
|
|
1224
|
+
if (Number.isFinite(declared) && declared > 0 && bytes !== declared) {
|
|
1225
|
+
throw new Error(
|
|
1226
|
+
`${file} stopped after ${bytes} of ${declared} bytes. Nothing was written \u2014 run this again.`
|
|
1227
|
+
);
|
|
1228
|
+
}
|
|
1229
|
+
let verified = "none";
|
|
1230
|
+
if (expected !== void 0) {
|
|
1231
|
+
options.onProgress?.({ phase: "verifying" });
|
|
1232
|
+
const actual = (await blobShaOfFile(temporary)).slice(0, 12);
|
|
1233
|
+
if (actual !== expected) {
|
|
1234
|
+
throw new Error(
|
|
1235
|
+
`${file} does not match the content hash in its address (expected ${expected}, got ${actual}). Nothing was written.`
|
|
1236
|
+
);
|
|
1237
|
+
}
|
|
1238
|
+
verified = "blob-sha";
|
|
1239
|
+
} else if (Number.isFinite(declared) && declared > 0) {
|
|
1240
|
+
verified = "content-length";
|
|
1241
|
+
}
|
|
1242
|
+
await (0, import_promises3.rename)(temporary, target);
|
|
1243
|
+
return { path: target, bytes, verified };
|
|
1244
|
+
} catch (error) {
|
|
1245
|
+
await (0, import_promises3.unlink)(temporary).catch(() => void 0);
|
|
1246
|
+
throw error;
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
function describeDownloadFailure(status, file) {
|
|
1250
|
+
if (status === 401) {
|
|
1251
|
+
return "The documentation platform rejected the access token. Run `pep auth login` again.";
|
|
1252
|
+
}
|
|
1253
|
+
if (status === 403) {
|
|
1254
|
+
return "This client is not allowed to read documentation \u2014 it needs the `docs:read` scope.";
|
|
1255
|
+
}
|
|
1256
|
+
if (status === 404) {
|
|
1257
|
+
return `${file} is no longer on the documentation platform. Run \`pep docs resources\` for the current list.`;
|
|
1258
|
+
}
|
|
1259
|
+
if (status === 502) {
|
|
1260
|
+
return "The documentation platform could not reach PEP to verify the token. Retry shortly.";
|
|
1261
|
+
}
|
|
1262
|
+
return `The documentation platform answered ${status} for ${file}.`;
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
// src/maven-env.ts
|
|
1266
|
+
var import_promises5 = require("node:fs/promises");
|
|
1267
|
+
var import_node_os2 = require("node:os");
|
|
1268
|
+
var import_node_path4 = require("node:path");
|
|
945
1269
|
var import_node_child_process3 = require("node:child_process");
|
|
946
1270
|
var import_node_util = require("node:util");
|
|
947
1271
|
var run = (0, import_node_util.promisify)(import_node_child_process3.execFile);
|
|
@@ -952,7 +1276,7 @@ var END = "# <<< pep-cli: Newland Maven credentials <<<";
|
|
|
952
1276
|
function resolveTarget(os = (0, import_node_os2.platform)(), shell = process.env.SHELL) {
|
|
953
1277
|
if (os === "win32") return { kind: "windows" };
|
|
954
1278
|
const file = shell?.endsWith("/bash") ? ".bash_profile" : ".zshrc";
|
|
955
|
-
return { kind: "profile", path: (0,
|
|
1279
|
+
return { kind: "profile", path: (0, import_node_path4.join)((0, import_node_os2.homedir)(), file) };
|
|
956
1280
|
}
|
|
957
1281
|
function shellQuote(value) {
|
|
958
1282
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
@@ -1020,7 +1344,7 @@ async function persistMavenEnv(credential, target = resolveTarget(), now = () =>
|
|
|
1020
1344
|
}
|
|
1021
1345
|
return { status: "windows" };
|
|
1022
1346
|
}
|
|
1023
|
-
const existing = await (0,
|
|
1347
|
+
const existing = await (0, import_promises5.readFile)(target.path, "utf8").catch((error) => {
|
|
1024
1348
|
if (error.code === "ENOENT") return null;
|
|
1025
1349
|
throw error;
|
|
1026
1350
|
});
|
|
@@ -1032,7 +1356,7 @@ async function persistMavenEnv(credential, target = resolveTarget(), now = () =>
|
|
|
1032
1356
|
if (existing !== null) {
|
|
1033
1357
|
backupPath = `${target.path}.bak-${stamp(now())}`;
|
|
1034
1358
|
try {
|
|
1035
|
-
await (0,
|
|
1359
|
+
await (0, import_promises5.writeFile)(backupPath, existing, { encoding: "utf8", flag: "wx" });
|
|
1036
1360
|
} catch (error) {
|
|
1037
1361
|
return {
|
|
1038
1362
|
status: "manual",
|
|
@@ -1041,10 +1365,10 @@ async function persistMavenEnv(credential, target = resolveTarget(), now = () =>
|
|
|
1041
1365
|
};
|
|
1042
1366
|
}
|
|
1043
1367
|
}
|
|
1044
|
-
const originalMode = await (0,
|
|
1368
|
+
const originalMode = await (0, import_promises5.stat)(target.path).then((info) => info.mode & 511).catch(() => 384);
|
|
1045
1369
|
const temporary = `${target.path}.${process.pid}.tmp`;
|
|
1046
|
-
await (0,
|
|
1047
|
-
await (0,
|
|
1370
|
+
await (0, import_promises5.writeFile)(temporary, plan.content, { encoding: "utf8", mode: originalMode });
|
|
1371
|
+
await (0, import_promises5.rename)(temporary, target.path);
|
|
1048
1372
|
return plan.action === "replace" ? { status: "profile-replaced", path: target.path, backupPath } : { status: "profile-appended", path: target.path, ...backupPath ? { backupPath } : {} };
|
|
1049
1373
|
}
|
|
1050
1374
|
|
|
@@ -1052,6 +1376,7 @@ async function persistMavenEnv(credential, target = resolveTarget(), now = () =>
|
|
|
1052
1376
|
var EXAMPLE_PATH = EXAMPLE_SKILLS_REPO;
|
|
1053
1377
|
var EXAMPLE_REPO = `https://git.newlandpayment.com/${EXAMPLE_PATH}.git`;
|
|
1054
1378
|
var PROJECT_FLAG = "-p, --project Install into ./.agents/skills of the current directory instead";
|
|
1379
|
+
var REGION_FLAG = "--region <id> Documentation region for this run only (see pep docs regions)";
|
|
1055
1380
|
var groups = {
|
|
1056
1381
|
auth: {
|
|
1057
1382
|
summary: "Sign in to PEP and manage your local authorization.",
|
|
@@ -1176,16 +1501,72 @@ pep skills update will not bring the skills back. Run pep skills add to do that.
|
|
|
1176
1501
|
list: {
|
|
1177
1502
|
summary: "List the documents your account can read.",
|
|
1178
1503
|
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: [
|
|
1181
|
-
|
|
1504
|
+
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.",
|
|
1505
|
+
flags: [
|
|
1506
|
+
`--docs-url <url> Documentation server; saved for later use (default: ${DEFAULT_DOCS_URL})`,
|
|
1507
|
+
REGION_FLAG
|
|
1508
|
+
],
|
|
1509
|
+
examples: [
|
|
1510
|
+
"pep docs list",
|
|
1511
|
+
"pep docs list --region na",
|
|
1512
|
+
"pep docs list --docs-url http://localhost:3001"
|
|
1513
|
+
]
|
|
1182
1514
|
},
|
|
1183
1515
|
get: {
|
|
1184
1516
|
summary: "Read a document as Markdown.",
|
|
1185
1517
|
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.",
|
|
1518
|
+
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.",
|
|
1519
|
+
flags: [
|
|
1520
|
+
`--docs-url <url> Documentation server; saved for later use (default: ${DEFAULT_DOCS_URL})`,
|
|
1521
|
+
REGION_FLAG
|
|
1522
|
+
],
|
|
1523
|
+
examples: [
|
|
1524
|
+
'pep docs get "<path-from-pep-docs-list>"',
|
|
1525
|
+
'pep docs get "<path-from-pep-docs-list>" --region na',
|
|
1526
|
+
'pep docs get "<path-from-pep-docs-list>" > document.md'
|
|
1527
|
+
]
|
|
1528
|
+
},
|
|
1529
|
+
regions: {
|
|
1530
|
+
summary: "List the documentation regions this deployment serves.",
|
|
1531
|
+
usage: "pep docs regions [flags]",
|
|
1532
|
+
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
1533
|
flags: [`--docs-url <url> Documentation server; saved for later use (default: ${DEFAULT_DOCS_URL})`],
|
|
1188
|
-
examples: [
|
|
1534
|
+
examples: ["pep docs regions"]
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
},
|
|
1538
|
+
resources: {
|
|
1539
|
+
summary: "List and download SDKs, demos and tools.",
|
|
1540
|
+
commands: {
|
|
1541
|
+
list: {
|
|
1542
|
+
summary: "List the SDKs, demos and tools you can download.",
|
|
1543
|
+
usage: "pep resources list [flags]",
|
|
1544
|
+
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.',
|
|
1545
|
+
flags: [
|
|
1546
|
+
`--platform <name> Only this platform (${PLATFORMS.join(", ")})`,
|
|
1547
|
+
REGION_FLAG,
|
|
1548
|
+
`--docs-url <url> Documentation server; saved for later use (default: ${DEFAULT_DOCS_URL})`
|
|
1549
|
+
],
|
|
1550
|
+
examples: [
|
|
1551
|
+
"pep resources list",
|
|
1552
|
+
"pep resources list --platform windows",
|
|
1553
|
+
"pep resources list --region na --platform android"
|
|
1554
|
+
]
|
|
1555
|
+
},
|
|
1556
|
+
download: {
|
|
1557
|
+
summary: "Download a file from the developer resources page.",
|
|
1558
|
+
usage: "pep resources download <file> [flags]",
|
|
1559
|
+
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.",
|
|
1560
|
+
flags: [
|
|
1561
|
+
"--dir <path> Directory to save into; created if missing (default: the current directory)",
|
|
1562
|
+
"--force Replace an existing file of the same name",
|
|
1563
|
+
REGION_FLAG,
|
|
1564
|
+
`--docs-url <url> Documentation server; saved for later use (default: ${DEFAULT_DOCS_URL})`
|
|
1565
|
+
],
|
|
1566
|
+
examples: [
|
|
1567
|
+
"pep resources download SaleSystemDemo-4.8.0.2-win-x64.zip",
|
|
1568
|
+
"pep resources download PayExplorerECRDemo.apk --region na --dir ./downloads"
|
|
1569
|
+
]
|
|
1189
1570
|
}
|
|
1190
1571
|
}
|
|
1191
1572
|
},
|
|
@@ -1299,6 +1680,7 @@ ${flagBlock([...command.flags ?? [], "-h, --help Show help for this command"])}
|
|
|
1299
1680
|
EXAMPLES
|
|
1300
1681
|
${command.examples.map((example) => ` ${example}`).join("\n")}`;
|
|
1301
1682
|
}
|
|
1683
|
+
var GROUP_NAMES = Object.keys(groups);
|
|
1302
1684
|
function usage(version, path = []) {
|
|
1303
1685
|
const [groupName, commandName] = path;
|
|
1304
1686
|
if (!groupName) {
|
|
@@ -1309,8 +1691,8 @@ USAGE
|
|
|
1309
1691
|
pep <command> <subcommand> [flags]
|
|
1310
1692
|
|
|
1311
1693
|
COMMANDS
|
|
1312
|
-
${Object.entries(topLevel).map(([name, command]) => ` ${name.padEnd(
|
|
1313
|
-
${Object.entries(groups).map(([name, group2]) => ` ${name.padEnd(
|
|
1694
|
+
${Object.entries(topLevel).map(([name, command]) => ` ${name.padEnd(11)}${command.summary}`).join("\n")}
|
|
1695
|
+
${Object.entries(groups).map(([name, group2]) => ` ${name.padEnd(11)}${group2.summary}`).join("\n")}
|
|
1314
1696
|
|
|
1315
1697
|
FLAGS
|
|
1316
1698
|
-h, --help Show help for a command
|
|
@@ -1319,6 +1701,7 @@ FLAGS
|
|
|
1319
1701
|
EXAMPLES
|
|
1320
1702
|
pep setup
|
|
1321
1703
|
pep docs list
|
|
1704
|
+
pep resources list
|
|
1322
1705
|
pep nexus setup --help
|
|
1323
1706
|
|
|
1324
1707
|
LEARN MORE
|
|
@@ -1341,7 +1724,7 @@ USAGE
|
|
|
1341
1724
|
pep ${groupName} <command> [flags]
|
|
1342
1725
|
|
|
1343
1726
|
COMMANDS
|
|
1344
|
-
${Object.entries(group.commands).map(([name, command]) => ` ${name.padEnd(
|
|
1727
|
+
${Object.entries(group.commands).map(([name, command]) => ` ${name.padEnd(11)}${command.summary}`).join("\n")}
|
|
1345
1728
|
|
|
1346
1729
|
FLAGS
|
|
1347
1730
|
-h, --help Show help for this command
|
|
@@ -1430,10 +1813,10 @@ async function setupNexusCredential(dependencies) {
|
|
|
1430
1813
|
}
|
|
1431
1814
|
|
|
1432
1815
|
// src/skills-service.ts
|
|
1433
|
-
var
|
|
1816
|
+
var import_promises6 = require("node:fs/promises");
|
|
1434
1817
|
var import_node_os3 = require("node:os");
|
|
1435
|
-
var
|
|
1436
|
-
var
|
|
1818
|
+
var import_node_path5 = require("node:path");
|
|
1819
|
+
var import_node_crypto4 = require("node:crypto");
|
|
1437
1820
|
var import_node_zlib = require("node:zlib");
|
|
1438
1821
|
|
|
1439
1822
|
// src/tar.ts
|
|
@@ -1592,8 +1975,8 @@ async function readCapped(response, limit) {
|
|
|
1592
1975
|
return out;
|
|
1593
1976
|
}
|
|
1594
1977
|
function assertInside(base, target) {
|
|
1595
|
-
const rel = (0,
|
|
1596
|
-
if (rel === "" || rel.startsWith("..") || (0,
|
|
1978
|
+
const rel = (0, import_node_path5.relative)((0, import_node_path5.resolve)(base), (0, import_node_path5.resolve)(target));
|
|
1979
|
+
if (rel === "" || rel.startsWith("..") || (0, import_node_path5.isAbsolute)(rel)) {
|
|
1597
1980
|
throw new Error(`Refusing an archive entry that escapes its directory: ${target}`);
|
|
1598
1981
|
}
|
|
1599
1982
|
}
|
|
@@ -1696,32 +2079,32 @@ function groupMemberPath(ledgerKey, group) {
|
|
|
1696
2079
|
return path;
|
|
1697
2080
|
}
|
|
1698
2081
|
async function physicalDir(path) {
|
|
1699
|
-
const absolute = (0,
|
|
2082
|
+
const absolute = (0, import_node_path5.resolve)(path);
|
|
1700
2083
|
try {
|
|
1701
|
-
return (0,
|
|
2084
|
+
return (0, import_node_path5.join)(await (0, import_promises6.realpath)((0, import_node_path5.dirname)(absolute)), (0, import_node_path5.basename)(absolute));
|
|
1702
2085
|
} catch {
|
|
1703
2086
|
return absolute;
|
|
1704
2087
|
}
|
|
1705
2088
|
}
|
|
1706
2089
|
async function linkSkill(canonicalSkillDir, linkPath) {
|
|
1707
|
-
await (0,
|
|
1708
|
-
const linkDir = (0,
|
|
1709
|
-
await (0,
|
|
2090
|
+
await (0, import_promises6.rm)(linkPath, { recursive: true, force: true });
|
|
2091
|
+
const linkDir = (0, import_node_path5.dirname)(linkPath);
|
|
2092
|
+
await (0, import_promises6.mkdir)(linkDir, { recursive: true });
|
|
1710
2093
|
try {
|
|
1711
2094
|
const junction = (0, import_node_os3.platform)() === "win32";
|
|
1712
|
-
await (0,
|
|
1713
|
-
junction ? canonicalSkillDir : (0,
|
|
2095
|
+
await (0, import_promises6.symlink)(
|
|
2096
|
+
junction ? canonicalSkillDir : (0, import_node_path5.relative)(await physicalDir(linkDir), canonicalSkillDir),
|
|
1714
2097
|
linkPath,
|
|
1715
2098
|
junction ? "junction" : void 0
|
|
1716
2099
|
);
|
|
1717
2100
|
return "linked";
|
|
1718
2101
|
} catch {
|
|
1719
|
-
await (0,
|
|
2102
|
+
await (0, import_promises6.cp)(canonicalSkillDir, linkPath, { recursive: true });
|
|
1720
2103
|
return "copied";
|
|
1721
2104
|
}
|
|
1722
2105
|
}
|
|
1723
2106
|
function hashSkill(files) {
|
|
1724
|
-
const hash = (0,
|
|
2107
|
+
const hash = (0, import_node_crypto4.createHash)("sha256");
|
|
1725
2108
|
for (const file of [...files].sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0)) {
|
|
1726
2109
|
hash.update(file.path);
|
|
1727
2110
|
hash.update("\0");
|
|
@@ -1758,9 +2141,9 @@ async function removeSkills(name, store) {
|
|
|
1758
2141
|
keptForOthers.push({ skill, owner });
|
|
1759
2142
|
continue;
|
|
1760
2143
|
}
|
|
1761
|
-
await (0,
|
|
2144
|
+
await (0, import_promises6.rm)((0, import_node_path5.join)(pkg.directory, skill), { recursive: true, force: true });
|
|
1762
2145
|
if (pkg.linkedInto !== void 0) {
|
|
1763
|
-
await (0,
|
|
2146
|
+
await (0, import_promises6.rm)((0, import_node_path5.join)(pkg.linkedInto, skill), { recursive: true, force: true });
|
|
1764
2147
|
}
|
|
1765
2148
|
deleted.push(skill);
|
|
1766
2149
|
}
|
|
@@ -1777,15 +2160,15 @@ async function outsidersAt(directory, skills, ours) {
|
|
|
1777
2160
|
const blocked = [];
|
|
1778
2161
|
for (const skill of skills) {
|
|
1779
2162
|
if (ours.has(skill)) continue;
|
|
1780
|
-
const path = (0,
|
|
1781
|
-
if (await (0,
|
|
2163
|
+
const path = (0, import_node_path5.join)(directory, skill);
|
|
2164
|
+
if (await (0, import_promises6.stat)(path).catch(() => null)) blocked.push({ skill, path });
|
|
1782
2165
|
}
|
|
1783
2166
|
return blocked;
|
|
1784
2167
|
}
|
|
1785
2168
|
async function missingSkills(directory, skills) {
|
|
1786
2169
|
const gone = /* @__PURE__ */ new Set();
|
|
1787
2170
|
for (const skill of skills) {
|
|
1788
|
-
if (!await (0,
|
|
2171
|
+
if (!await (0, import_promises6.stat)((0, import_node_path5.join)(directory, skill)).catch(() => null)) gone.add(skill);
|
|
1789
2172
|
}
|
|
1790
2173
|
return gone;
|
|
1791
2174
|
}
|
|
@@ -1899,14 +2282,14 @@ async function updateSkills(dependencies) {
|
|
|
1899
2282
|
if (otherClaimant(state?.packages ?? {}, name, previous.directory, skill)) continue;
|
|
1900
2283
|
const stalePlace = deletableLedgerDirectory(previous.directory);
|
|
1901
2284
|
if (stalePlace === void 0) continue;
|
|
1902
|
-
await (0,
|
|
2285
|
+
await (0, import_promises6.rm)((0, import_node_path5.join)(stalePlace, skill), { recursive: true, force: true });
|
|
1903
2286
|
if (previous.linkedInto !== void 0) {
|
|
1904
|
-
await (0,
|
|
2287
|
+
await (0, import_promises6.rm)((0, import_node_path5.join)(previous.linkedInto, skill), { recursive: true, force: true });
|
|
1905
2288
|
}
|
|
1906
2289
|
}
|
|
1907
2290
|
}
|
|
1908
2291
|
for (const skill of skills) {
|
|
1909
|
-
await (0,
|
|
2292
|
+
await (0, import_promises6.rm)((0, import_node_path5.join)(dependencies.directory, skill), { recursive: true, force: true });
|
|
1910
2293
|
}
|
|
1911
2294
|
const untouched = /* @__PURE__ */ new Set([
|
|
1912
2295
|
...skipped,
|
|
@@ -1915,17 +2298,17 @@ async function updateSkills(dependencies) {
|
|
|
1915
2298
|
]);
|
|
1916
2299
|
for (const file of files) {
|
|
1917
2300
|
if (untouched.has(file.skill)) continue;
|
|
1918
|
-
const target = (0,
|
|
2301
|
+
const target = (0, import_node_path5.join)(dependencies.directory, file.skill, ...file.path.split("/"));
|
|
1919
2302
|
assertInside(dependencies.directory, target);
|
|
1920
|
-
await (0,
|
|
1921
|
-
await (0,
|
|
2303
|
+
await (0, import_promises6.mkdir)((0, import_node_path5.dirname)(target), { recursive: true });
|
|
2304
|
+
await (0, import_promises6.writeFile)(target, file.data);
|
|
1922
2305
|
}
|
|
1923
2306
|
let copiedCount = 0;
|
|
1924
2307
|
if (dependencies.linkInto !== void 0) {
|
|
1925
2308
|
for (const skill of skills) {
|
|
1926
2309
|
const how = await linkSkill(
|
|
1927
|
-
(0,
|
|
1928
|
-
(0,
|
|
2310
|
+
(0, import_node_path5.join)(dependencies.directory, skill),
|
|
2311
|
+
(0, import_node_path5.join)(dependencies.linkInto, skill)
|
|
1929
2312
|
);
|
|
1930
2313
|
if (how === "copied") copiedCount += 1;
|
|
1931
2314
|
}
|
|
@@ -2169,8 +2552,29 @@ async function planSetup(dependencies) {
|
|
|
2169
2552
|
}
|
|
2170
2553
|
|
|
2171
2554
|
// src/cli.ts
|
|
2172
|
-
var VERSION = false ? "0.0.0-dev" : "0.9.
|
|
2555
|
+
var VERSION = false ? "0.0.0-dev" : "0.9.3";
|
|
2173
2556
|
var SKILLS_ADD_DEPRECATION = "\u26A0 `pep skills add` will be removed in a future release.";
|
|
2557
|
+
function createProgressReporter(stream = process.stderr) {
|
|
2558
|
+
if (!stream.isTTY) return void 0;
|
|
2559
|
+
const INTERVAL_MS = 100;
|
|
2560
|
+
let lastAt = 0;
|
|
2561
|
+
let painted = false;
|
|
2562
|
+
const paint = (line) => {
|
|
2563
|
+
stream.write(`\r${" ".repeat(78)}\r${line}`);
|
|
2564
|
+
painted = true;
|
|
2565
|
+
};
|
|
2566
|
+
return {
|
|
2567
|
+
report(progress) {
|
|
2568
|
+
const now = Date.now();
|
|
2569
|
+
if (progress.phase === "downloading" && now - lastAt < INTERVAL_MS) return;
|
|
2570
|
+
lastAt = now;
|
|
2571
|
+
paint(formatProgress(progress));
|
|
2572
|
+
},
|
|
2573
|
+
done() {
|
|
2574
|
+
if (painted) stream.write(`\r${" ".repeat(78)}\r`);
|
|
2575
|
+
}
|
|
2576
|
+
};
|
|
2577
|
+
}
|
|
2174
2578
|
function reportUpdate(result) {
|
|
2175
2579
|
const conflicts = () => {
|
|
2176
2580
|
for (const { skill, owner } of result.conflicts) {
|
|
@@ -2302,7 +2706,7 @@ function parseSkillsArgs(command, args, cwd) {
|
|
|
2302
2706
|
// `rm -rf` 到 B 头上。`--project` 没有这个问题,因为它走 `join(cwd, …)`。
|
|
2303
2707
|
// 基准用 `cwd` 而不是 `process.cwd()`:与同一处的 `projectSkillsTarget(cwd)` 同源,
|
|
2304
2708
|
// 也让这条分支可测。
|
|
2305
|
-
{ directory: (0,
|
|
2709
|
+
{ directory: (0, import_node_path6.resolve)(cwd ?? process.cwd(), explicitDirectory) }
|
|
2306
2710
|
) : project ? projectSkillsTarget(cwd) : void 0;
|
|
2307
2711
|
return { ...requested !== void 0 ? { requested } : {}, named, ...chosenTarget ? { chosenTarget } : {} };
|
|
2308
2712
|
}
|
|
@@ -2517,7 +2921,8 @@ ${url}`)
|
|
|
2517
2921
|
if (failure) throw new Error(failure);
|
|
2518
2922
|
return;
|
|
2519
2923
|
}
|
|
2520
|
-
|
|
2924
|
+
const GROUPS = ["auth", "skills", "docs", "resources", "nexus"];
|
|
2925
|
+
if (group === void 0 || !GROUPS.includes(group))
|
|
2521
2926
|
throw new Error(`Unknown command.
|
|
2522
2927
|
|
|
2523
2928
|
${usage(VERSION)}`);
|
|
@@ -2527,25 +2932,59 @@ ${usage(VERSION)}`);
|
|
|
2527
2932
|
return;
|
|
2528
2933
|
}
|
|
2529
2934
|
const { configStore, auth } = createCliServices();
|
|
2530
|
-
if (group === "docs") {
|
|
2531
|
-
|
|
2532
|
-
|
|
2935
|
+
if (group === "docs" || group === "resources") {
|
|
2936
|
+
const allowed = group === "docs" ? ["list", "get", "regions"] : ["list", "download"];
|
|
2937
|
+
if (!allowed.includes(command)) {
|
|
2938
|
+
throw new Error(`Unknown ${group} command.
|
|
2533
2939
|
|
|
2534
|
-
${usage(VERSION, [
|
|
2940
|
+
${usage(VERSION, [group])}`);
|
|
2535
2941
|
}
|
|
2942
|
+
const action = `${group}:${command}`;
|
|
2536
2943
|
const explicitDocsUrl = option(args, "--docs-url");
|
|
2537
|
-
const
|
|
2944
|
+
const region = option(args, "--region");
|
|
2945
|
+
const platform3 = option(args, "--platform");
|
|
2946
|
+
const intoDirectory = option(args, "--dir");
|
|
2947
|
+
const force = flag(args, "--force");
|
|
2948
|
+
const path = action === "docs:get" ? args.shift() : void 0;
|
|
2949
|
+
const wantedFile = action === "resources:download" ? args.shift() : void 0;
|
|
2538
2950
|
if (args.length > 0) throw new Error(`Unknown option: ${args[0]}`);
|
|
2539
|
-
if (
|
|
2951
|
+
if (action === "docs:get" && !path) throw new Error("pep docs get needs a document path.");
|
|
2952
|
+
if (action === "docs:regions" && region !== void 0) {
|
|
2953
|
+
throw new Error("pep docs regions lists every region \u2014 it takes no --region.");
|
|
2954
|
+
}
|
|
2955
|
+
if (action === "resources:download" && !wantedFile) {
|
|
2956
|
+
throw new Error(
|
|
2957
|
+
"pep resources download needs a file name. Run `pep resources list` to see what is there."
|
|
2958
|
+
);
|
|
2959
|
+
}
|
|
2960
|
+
if (platform3 !== void 0 && group !== "resources") {
|
|
2961
|
+
throw new Error(`pep ${group} ${command} takes no --platform.`);
|
|
2962
|
+
}
|
|
2963
|
+
if ((intoDirectory !== void 0 || force) && action !== "resources:download") {
|
|
2964
|
+
throw new Error(`pep ${group} ${command} takes no --dir or --force.`);
|
|
2965
|
+
}
|
|
2966
|
+
const wantedPlatform = platform3 === void 0 ? void 0 : resolvePlatform(platform3);
|
|
2967
|
+
if (platform3 !== void 0 && wantedPlatform === void 0) {
|
|
2968
|
+
throw new Error(`No such platform: ${platform3}. Available: ${PLATFORMS.join(", ")}.`);
|
|
2969
|
+
}
|
|
2540
2970
|
const saved = await configStore.read();
|
|
2541
2971
|
const docsUrl = explicitDocsUrl ? normalizeDocsUrl(explicitDocsUrl) : saved?.docsUrl ?? DEFAULT_DOCS_URL;
|
|
2542
2972
|
if (explicitDocsUrl && saved && saved.docsUrl !== docsUrl) {
|
|
2543
2973
|
await configStore.write({ ...saved, docsUrl });
|
|
2544
2974
|
}
|
|
2975
|
+
if (action === "docs:regions") {
|
|
2976
|
+
const known = await fetchRegions({ docsUrl });
|
|
2977
|
+
for (const one of known.regions) {
|
|
2978
|
+
const aliases = one.aliases?.length ? ` ${one.aliases.join(",")}` : " ";
|
|
2979
|
+
const mark = one.id === known.defaultRegion ? " (default)" : "";
|
|
2980
|
+
console.log(`${one.id}${aliases} ${one.defaultLanguage} ${one.name}${mark}`);
|
|
2981
|
+
}
|
|
2982
|
+
return;
|
|
2983
|
+
}
|
|
2545
2984
|
const authorization = await auth.currentAuthorization();
|
|
2546
2985
|
const dependencies = { docsUrl, accessToken: authorization.accessToken };
|
|
2547
|
-
if (
|
|
2548
|
-
const entries = await fetchDocsIndex(dependencies);
|
|
2986
|
+
if (action === "docs:list") {
|
|
2987
|
+
const entries = await fetchDocsIndex(dependencies, region);
|
|
2549
2988
|
if (entries.length === 0) {
|
|
2550
2989
|
console.error("No documents are readable with this account.");
|
|
2551
2990
|
return;
|
|
@@ -2555,7 +2994,45 @@ ${usage(VERSION, ["docs"])}`);
|
|
|
2555
2994
|
}
|
|
2556
2995
|
return;
|
|
2557
2996
|
}
|
|
2558
|
-
|
|
2997
|
+
if (group === "resources") {
|
|
2998
|
+
const known = await fetchRegions({ docsUrl });
|
|
2999
|
+
const picked = region === void 0 ? resolveRegion(known, known.defaultRegion) : resolveRegion(known, region);
|
|
3000
|
+
if (!picked) throw unknownRegion(known, region ?? known.defaultRegion);
|
|
3001
|
+
const all = await fetchResources({ ...dependencies, region: picked }, wantedPlatform);
|
|
3002
|
+
if (action === "resources:list") {
|
|
3003
|
+
if (all.length === 0) {
|
|
3004
|
+
console.error("The developer resources page lists no files.");
|
|
3005
|
+
return;
|
|
3006
|
+
}
|
|
3007
|
+
for (const one of all) {
|
|
3008
|
+
const where = one.external ? "external" : one.platforms.join(",");
|
|
3009
|
+
console.log(`${one.file} ${where} ${one.url}`);
|
|
3010
|
+
}
|
|
3011
|
+
return;
|
|
3012
|
+
}
|
|
3013
|
+
const target = pickResource(all, wantedFile);
|
|
3014
|
+
const progress = createProgressReporter();
|
|
3015
|
+
const result = await downloadResource(dependencies, target, {
|
|
3016
|
+
// 默认落在**执行命令时的当前目录**,不是 CLI 装在哪儿。
|
|
3017
|
+
directory: intoDirectory ?? process.cwd(),
|
|
3018
|
+
force,
|
|
3019
|
+
...progress ? { onProgress: progress.report } : {}
|
|
3020
|
+
});
|
|
3021
|
+
progress?.done();
|
|
3022
|
+
console.error(
|
|
3023
|
+
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`
|
|
3024
|
+
);
|
|
3025
|
+
console.log(result.path);
|
|
3026
|
+
return;
|
|
3027
|
+
}
|
|
3028
|
+
let wanted = path;
|
|
3029
|
+
if (region !== void 0) {
|
|
3030
|
+
const known = await fetchRegions({ docsUrl });
|
|
3031
|
+
const picked = resolveRegion(known, region);
|
|
3032
|
+
if (!picked) throw unknownRegion(known, region);
|
|
3033
|
+
wanted = withRegion(wanted, picked);
|
|
3034
|
+
}
|
|
3035
|
+
process.stdout.write(await fetchDocContent(dependencies, wanted));
|
|
2559
3036
|
return;
|
|
2560
3037
|
}
|
|
2561
3038
|
if (group === "nexus") {
|