@newlandnpt/pep-cli 0.8.1 → 0.9.0-rc.1

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 (3) hide show
  1. package/README.md +70 -7
  2. package/dist/pep.cjs +592 -109
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -52,8 +52,66 @@ node scripts/oauth-bootstrap.mjs --client --public \
52
52
  If one PEP deployment serves multiple customer tenants, register a distinct client ID for each tenant
53
53
  and pass it with `--client-id`.
54
54
 
55
+ ## First run
56
+
57
+ ```powershell
58
+ pep setup
59
+ ```
60
+
61
+ Runs the three first-run steps in order — sign in, install the integration guide skills, configure
62
+ Maven credentials — and **skips what is already done**.
63
+
64
+ Step 2 installs **every skills repository in a group**. It asks the platform which repositories that
65
+ group holds right now, then installs each one, so a new skills repository reaches everybody without
66
+ a CLI release. A repository that fails does not stop the others: the command reports how many went
67
+ in, which ones did not, and exits non-zero.
68
+
69
+ Steps 2 and 3 are independent: if one fails the other still runs, and the command names which ones
70
+ did not complete (and exits non-zero). Every step is idempotent, so running it again is safe — that
71
+ is the intended way to recover from a partial run.
72
+
73
+ ```powershell
74
+ pep setup # all three steps
75
+ pep setup --skip-nexus # skip Maven (only Android needs it)
76
+ pep setup --skip-skills # only sign in and configure Maven
77
+ pep setup group/sub -p # a different group, into THIS project
78
+ ```
79
+
80
+ The integration guide **group** is baked in at build time (currently `distribution/skills`). Passing
81
+ one overrides it **for that run only** — it is deliberately not remembered, unlike `--docs-url`.
82
+ Where each repository installs *is* remembered, in the local ledger, so `pep skills update` later
83
+ returns each one to the same place.
84
+
85
+ If a repository you installed earlier has since left the group, `pep setup` says so and **leaves it
86
+ alone** — nothing on your disk is deleted. It stays in the ledger, so `pep skills update` will report
87
+ 404 for it (which reads exactly like a typo, but is not). `pep skills remove <repo>` drops it for
88
+ good.
89
+
90
+ Step 3 needs a contract that includes Maven repository access (`ADMIN`, `US-ISO`, `US-ISV`); other
91
+ contracts get a 403 there, which is why it fails softly rather than aborting the run. It also never
92
+ prints the password — run `pep nexus setup --print-env` yourself if you need that.
93
+
94
+ Each step's own command still exists and does exactly the same thing; `pep setup` only chains them.
95
+
55
96
  ## Use
56
97
 
98
+ Use `--help` (or `-h`) at any level for the relevant commands, purpose, flags and examples:
99
+
100
+ ```powershell
101
+ pep --help
102
+ pep setup --help
103
+ pep auth --help
104
+ pep auth login --help
105
+ pep docs get --help
106
+ pep skills add --help
107
+ pep skills update --help
108
+ pep nexus setup --help
109
+ ```
110
+
111
+ `pep help auth login` is equivalent to `pep auth login --help`. Help is available without
112
+ logging in and does not access credentials or contact any service. The `pep-cli` alias supports
113
+ the same commands.
114
+
57
115
  ```powershell
58
116
  pep auth login
59
117
  pep auth status
@@ -86,6 +144,9 @@ pep auth login --issuer https://pep-webapp-dev.onrender.com --client-id <that de
86
144
 
87
145
  ## Skills
88
146
 
147
+ > **`pep skills add` will be removed in a future release.** It still works and behaves exactly as
148
+ > before; using it now prints that notice.
149
+
89
150
  ```powershell
90
151
  pep skills add https://git.newlandpayment.com/group/sub/project # or group/sub/project[@ref]
91
152
  pep skills list
@@ -163,8 +224,8 @@ updating.
163
224
 
164
225
  ```
165
226
  ⚠ 3 repositories all install "semi-integration" here:
166
- https://git.example.com/team-a/skill
167
- https://git.example.com/team-b/skill-testing
227
+ https://git.newlandpayment.com/ai-projects/research-projects/skills/semi-integration-skill
228
+ https://git.newlandpayment.com/distribution/skills/semi-integration-skill-testing
168
229
  They overwrite each other. `pep skills remove <repo>` keeps just one.
169
230
  ```
170
231
 
@@ -204,16 +265,18 @@ Asks PEP for your Maven repository credential and saves it as the two environmen
204
265
  Newland Android SDK reads (`NEWLAND_MAVEN_USERNAME` / `NEWLAND_MAVEN_PASSWORD`). Windows uses
205
266
  `setx`; macOS keeps a marked block in your shell profile, backed up first.
206
267
 
207
- **The password is never printed.** The credential is derived from who you are, so running this
208
- again — on this machine or any other — hands you the same one; nothing is lost by not showing it.
209
- Use `--print-env` when you want the two lines on stdout instead:
268
+ **The password is never printed.** The credential is derived from your organisation's identity, so
269
+ running this again — on this machine or any other — hands you the same one; nothing is lost by not
270
+ showing it. Use `--print-env` when you want the two lines on stdout instead:
210
271
 
211
272
  ```bash
212
273
  eval "$(pep nexus setup --print-env)" # the shell you are in gets them now
213
274
  ```
214
275
 
215
- The credential is per person, not per company — colleagues get their own, and so does the same
216
- account on a second machine.
276
+ The credential is **per company, not per person**: colleagues who run this get the same username and
277
+ password. It is a read-only credential for the SDK repositories, not a personal account — it says
278
+ which company is pulling, not who. Nothing about it is per-machine either, which is the point: a
279
+ second laptop just runs the command again.
217
280
 
218
281
  ⚠ Persistence only reaches **new** processes. A running IDE keeps the environment it started with,
219
282
  and the Gradle daemon outlives the IDE, so after the first setup: quit Android Studio completely,
package/dist/pep.cjs CHANGED
@@ -22,6 +22,8 @@ var DEFAULT_SCOPES = [
22
22
  var DEFAULT_RESOURCES = ["urn:newland:pep:docs"];
23
23
  var LEGACY_PACKAGE = "semi-integration";
24
24
  var DEFAULT_DOCS_URL = "https://docs.newlandnpt.us";
25
+ var DEFAULT_SKILLS_GROUP = "distribution/skills";
26
+ var EXAMPLE_SKILLS_REPO = `${DEFAULT_SKILLS_GROUP}/semi-integration-skill`;
25
27
  function issuerForEnvironment(environment) {
26
28
  if (environment === "production") return "https://pep.newlandnpt.us";
27
29
  if (environment === "view") return "https://pep-webapp-view.onrender.com";
@@ -932,8 +934,15 @@ function stamp(at) {
932
934
  }
933
935
  async function persistMavenEnv(credential, target = resolveTarget(), now = () => /* @__PURE__ */ new Date()) {
934
936
  if (target.kind === "windows") {
935
- await run("setx", [USERNAME_VAR, credential.username]);
936
- await run("setx", [PASSWORD_VAR, credential.password]);
937
+ try {
938
+ await run("setx", [USERNAME_VAR, credential.username]);
939
+ await run("setx", [PASSWORD_VAR, credential.password]);
940
+ } catch {
941
+ return {
942
+ status: "manual",
943
+ reason: "setx could not write the user environment. The original error is omitted on purpose: it contains the password."
944
+ };
945
+ }
937
946
  return { status: "windows" };
938
947
  }
939
948
  const existing = await (0, import_promises3.readFile)(target.path, "utf8").catch((error) => {
@@ -961,6 +970,294 @@ async function persistMavenEnv(credential, target = resolveTarget(), now = () =>
961
970
  return plan.action === "replace" ? { status: "profile-replaced", path: target.path, backupPath } : { status: "profile-appended", path: target.path, ...backupPath ? { backupPath } : {} };
962
971
  }
963
972
 
973
+ // src/help.ts
974
+ var EXAMPLE_PATH = EXAMPLE_SKILLS_REPO;
975
+ var EXAMPLE_REPO = `https://git.newlandpayment.com/${EXAMPLE_PATH}.git`;
976
+ var PROJECT_FLAG = "-p, --project Install into ./.agents/skills of the current directory instead";
977
+ var groups = {
978
+ auth: {
979
+ summary: "Sign in to PEP and manage your local authorization.",
980
+ commands: {
981
+ login: {
982
+ summary: "Sign in to PEP through your browser.",
983
+ usage: "pep auth login [flags]",
984
+ description: `Opens your browser to sign in, then saves tokens in the OS keychain.
985
+ Tokens are refreshed automatically when needed by other commands.
986
+ Issuer, client ID and resources use this build's defaults for each new login;
987
+ overrides apply to this login only and are not reused by the next login.`,
988
+ flags: [
989
+ `--issuer <url> PEP server for this login (default: ${DEFAULT_ISSUER})`,
990
+ `--client-id <id> Registered OAuth client (default: ${DEFAULT_CLIENT_ID})`,
991
+ `--resource <uri> Token audience; repeat for multiple resources (default: ${DEFAULT_RESOURCES.join(", ")})`
992
+ ],
993
+ examples: ["pep auth login", "pep auth login --issuer http://localhost:3000"]
994
+ },
995
+ status: {
996
+ summary: "Show the current account and authorization status.",
997
+ usage: "pep auth status",
998
+ description: "Shows the issuer, client, account, granted scopes and access-token expiry.\nRequires a saved login and contacts PEP to retrieve account information.",
999
+ examples: ["pep auth status"]
1000
+ },
1001
+ token: {
1002
+ summary: "Print a current access token for scripts and API clients.",
1003
+ usage: "pep auth token",
1004
+ description: "Requires a saved login. Refreshes the token if needed and writes only the\naccess token to stdout, so another command can capture it.",
1005
+ examples: ["pep auth token", "$token = pep auth token # PowerShell"]
1006
+ },
1007
+ logout: {
1008
+ summary: "Sign out and remove local authorization.",
1009
+ usage: "pep auth logout",
1010
+ description: "Attempts to revoke the saved authorization on PEP, then removes local tokens\nand login configuration. Local data is removed even if remote revocation fails.",
1011
+ examples: ["pep auth logout"]
1012
+ }
1013
+ }
1014
+ },
1015
+ skills: {
1016
+ summary: "Download and update PEP agent skills.",
1017
+ commands: {
1018
+ list: {
1019
+ summary: "List the skill repositories you have added.",
1020
+ usage: "pep skills list",
1021
+ description: "Requires a saved PEP login. Lists locally registered repositories.\nUse pep skills add to add a repository, or pep skills update to refresh it.",
1022
+ examples: ["pep skills list"]
1023
+ },
1024
+ add: {
1025
+ summary: "Add skills from a repository on PEP's GitLab.",
1026
+ usage: "pep skills add <repo> [flags]",
1027
+ description: `Accepts a full HTTPS repository URL or group/project[@ref]. URLs with
1028
+ /-/tree/<ref> are also supported. PEP fetches the repository with its own service
1029
+ account; no GitLab credential is needed locally. Other hosts are refused.
1030
+ Each directory containing SKILL.md is installed as a skill.
1031
+ Requires a saved PEP login.
1032
+
1033
+ Installs for your user account by default. Pass --project to install into the
1034
+ current directory instead, so the skills travel with the repository and everyone
1035
+ working on it gets the same set. The choice is remembered per repository, so
1036
+ pep skills update does not need the flag again.
1037
+
1038
+ Also re-installs skills you removed with pep skills remove. This is the only
1039
+ command that does; pep skills update leaves them removed.`,
1040
+ flags: [
1041
+ "--dir <path> Install only to this directory; skip links into Claude Code",
1042
+ `${PROJECT_FLAG}`
1043
+ ],
1044
+ examples: [
1045
+ `pep skills add ${EXAMPLE_REPO}`,
1046
+ `pep skills add ${EXAMPLE_PATH}@main`,
1047
+ `pep skills add ${EXAMPLE_REPO} --project`,
1048
+ `pep skills add ${EXAMPLE_PATH} --dir "./my-skills"`
1049
+ ]
1050
+ },
1051
+ update: {
1052
+ summary: "Update installed skill repositories and report changed skills.",
1053
+ usage: "pep skills update [<repo>...] [flags]",
1054
+ description: `Requires a saved PEP login. Downloads skills to:
1055
+ ${defaultSkillsDirectory()}
1056
+ Also links them into ${claudeSkillsDirectory()} for Claude Code,
1057
+ or copies them if links are unavailable. Only skills managed by pep are updated
1058
+ or removed; files you added yourself are left alone.
1059
+ With no repository arguments, updates all added repositories. Named repositories
1060
+ must already have been added. Reports which individual skills changed.
1061
+
1062
+ Each repository is updated where it was installed, so the flags you used with
1063
+ pep skills add do not need repeating.
1064
+
1065
+ Skills you removed with pep skills remove stay removed: update refreshes what is
1066
+ installed and does not bring anything back. Run pep skills add to reinstate them.`,
1067
+ flags: [
1068
+ "--dir <path> Install only to this directory; skip links into Claude Code",
1069
+ `${PROJECT_FLAG}`
1070
+ ],
1071
+ examples: [
1072
+ "pep skills update",
1073
+ `pep skills update ${EXAMPLE_PATH}`,
1074
+ 'pep skills update --dir "./my-skills"'
1075
+ ]
1076
+ },
1077
+ remove: {
1078
+ summary: "Remove the skills a repository installed.",
1079
+ usage: "pep skills remove <repo>",
1080
+ description: `Deletes the skills this repository installed, wherever they were installed,
1081
+ and stops tracking it. Takes no --dir or --project: the location comes from what
1082
+ pep recorded when it installed them.
1083
+
1084
+ Only files pep installed from this repository are deleted. A skill another added
1085
+ repository also provides is left in place, and so is anything you wrote yourself.
1086
+
1087
+ pep skills update will not bring the skills back. Run pep skills add to do that.`,
1088
+ examples: [
1089
+ `pep skills remove ${EXAMPLE_PATH}`,
1090
+ `pep skills remove ${EXAMPLE_REPO}`
1091
+ ]
1092
+ }
1093
+ }
1094
+ },
1095
+ docs: {
1096
+ summary: "Find and read PEP developer documentation.",
1097
+ commands: {
1098
+ list: {
1099
+ summary: "List the documents your account can read.",
1100
+ usage: "pep docs list [flags]",
1101
+ 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.",
1102
+ flags: [`--docs-url <url> Documentation server; saved for later use (default: ${DEFAULT_DOCS_URL})`],
1103
+ examples: ["pep docs list", "pep docs list --docs-url http://localhost:3001"]
1104
+ },
1105
+ get: {
1106
+ summary: "Read a document as Markdown.",
1107
+ usage: "pep docs get <path> [flags]",
1108
+ 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.",
1109
+ flags: [`--docs-url <url> Documentation server; saved for later use (default: ${DEFAULT_DOCS_URL})`],
1110
+ examples: ['pep docs get "<path-from-pep-docs-list>"', 'pep docs get "<path-from-pep-docs-list>" > document.md']
1111
+ }
1112
+ }
1113
+ },
1114
+ nexus: {
1115
+ summary: "Configure Maven repository access for the Newland Android SDK.",
1116
+ commands: {
1117
+ setup: {
1118
+ summary: "Save your organisation's Maven credentials for the Newland SDK.",
1119
+ usage: "pep nexus setup [flags]",
1120
+ description: `Requires a saved PEP login and Maven access in your organisation's contract.
1121
+ Saves ${USERNAME_VAR} and ${PASSWORD_VAR} to the Windows user
1122
+ environment or your macOS shell profile.
1123
+
1124
+ Safe to run again, on as many machines as you like: the credential is derived
1125
+ from your organisation's identity, so every run produces the same one. Losing it
1126
+ is not a problem \u2014 run this again.
1127
+
1128
+ One credential per organisation: colleagues who run this get the same username
1129
+ and password. It is a read-only credential for the Newland SDK repositories, not
1130
+ a personal account, so it identifies your company rather than you.
1131
+
1132
+ Neither value is printed. Pass --print-env to write the two export lines to
1133
+ stdout instead, for the current shell:
1134
+ eval "$(pep nexus setup --print-env)"
1135
+
1136
+ Saved values only reach new processes. After setup:
1137
+ - open a new terminal;
1138
+ - fully quit and reopen Android Studio / IntelliJ, whose built-in terminal
1139
+ inherits the environment it was started with;
1140
+ - stop the Gradle daemon, which outlives the IDE: gradlew --stop.
1141
+ A daemon started before setup keeps the old environment and fails to resolve the
1142
+ SDK even once everything else is correct.`,
1143
+ flags: ["--print-env Also write the export lines to stdout for the current shell"],
1144
+ examples: ["pep nexus setup", 'eval "$(pep nexus setup --print-env)"']
1145
+ }
1146
+ }
1147
+ }
1148
+ };
1149
+ function flagBlock(flags) {
1150
+ const split = flags.map((one) => {
1151
+ const gap = one.search(/ {2,}/);
1152
+ return gap === -1 ? [one, ""] : [one.slice(0, gap), one.slice(gap).trimStart()];
1153
+ });
1154
+ const width = Math.max(...split.map(([name]) => name.length)) + 3;
1155
+ return split.map(([name, text]) => ` ${text === "" ? name : name.padEnd(width) + text}`).join("\n");
1156
+ }
1157
+ var topLevel = {
1158
+ setup: {
1159
+ summary: "Sign in, install the integration guide and configure Maven in one go.",
1160
+ usage: "pep setup [<group>] [flags]",
1161
+ description: `Runs the three first-run steps in order, skipping what is already done:
1162
+ 1. Sign in \u2014 skipped when you are already signed in (tokens refresh themselves).
1163
+ 2. Install every skills repository in a group (default: ${DEFAULT_SKILLS_GROUP}).
1164
+ 3. Configure Maven credentials \u2014 only Android development needs this.
1165
+
1166
+ Step 2 asks the platform which repositories that group holds right now, then
1167
+ installs each one. A repository that fails does not stop the others: the command
1168
+ reports how many went in, which ones did not, and exits non-zero.
1169
+
1170
+ Steps 2 and 3 are independent: if one fails the other still runs. Safe to run
1171
+ again \u2014 every step is idempotent.
1172
+
1173
+ Repositories you installed earlier that have since left the group are reported,
1174
+ never deleted. \`pep skills remove <repo>\` drops one for good.
1175
+
1176
+ The group is baked into this build and is not remembered between runs: pass one
1177
+ to install from a different group, just for this run.`,
1178
+ flags: [
1179
+ "--skip-skills Skip the integration guide step",
1180
+ "--skip-nexus Skip the Maven credential step (not needed outside Android)",
1181
+ PROJECT_FLAG,
1182
+ "--dir <path> Install skills into this directory instead"
1183
+ ],
1184
+ examples: [
1185
+ "pep setup",
1186
+ "pep setup --skip-nexus",
1187
+ `pep setup ${DEFAULT_SKILLS_GROUP} -p`
1188
+ ]
1189
+ }
1190
+ };
1191
+ function renderCommand(command) {
1192
+ return `${command.summary}
1193
+
1194
+ ${command.description}
1195
+
1196
+ USAGE
1197
+ ${command.usage}
1198
+
1199
+ FLAGS
1200
+ ${flagBlock([...command.flags ?? [], "-h, --help Show help for this command"])}
1201
+
1202
+ EXAMPLES
1203
+ ${command.examples.map((example) => ` ${example}`).join("\n")}`;
1204
+ }
1205
+ function usage(version, path = []) {
1206
+ const [groupName, commandName] = path;
1207
+ if (!groupName) {
1208
+ return `PEP CLI ${version}
1209
+ Sign in to PEP, read developer documentation and configure development tools.
1210
+
1211
+ USAGE
1212
+ pep <command> <subcommand> [flags]
1213
+
1214
+ COMMANDS
1215
+ ${Object.entries(topLevel).map(([name, command]) => ` ${name.padEnd(10)}${command.summary}`).join("\n")}
1216
+ ${Object.entries(groups).map(([name, group2]) => ` ${name.padEnd(10)}${group2.summary}`).join("\n")}
1217
+
1218
+ FLAGS
1219
+ -h, --help Show help for a command
1220
+ -v, --version Show the CLI version
1221
+
1222
+ EXAMPLES
1223
+ pep setup
1224
+ pep docs list
1225
+ pep nexus setup --help
1226
+
1227
+ LEARN MORE
1228
+ Use pep <command> --help or pep <command> <subcommand> --help.
1229
+ You can also use pep help <command> <subcommand>.
1230
+ pep-cli is an alias for pep.`;
1231
+ }
1232
+ if (Object.hasOwn(topLevel, groupName)) {
1233
+ if (path.length > 2) {
1234
+ throw new Error(`Unknown ${groupName} argument: ${path.slice(1).join(" ")}. Run pep ${groupName} --help.`);
1235
+ }
1236
+ return renderCommand(topLevel[groupName]);
1237
+ }
1238
+ if (!Object.hasOwn(groups, groupName)) throw new Error(`Unknown command: ${groupName}. Run pep --help.`);
1239
+ const group = groups[groupName];
1240
+ if (!commandName) {
1241
+ return `${group.summary}
1242
+
1243
+ USAGE
1244
+ pep ${groupName} <command> [flags]
1245
+
1246
+ COMMANDS
1247
+ ${Object.entries(group.commands).map(([name, command]) => ` ${name.padEnd(10)}${command.summary}`).join("\n")}
1248
+
1249
+ FLAGS
1250
+ -h, --help Show help for this command
1251
+
1252
+ LEARN MORE
1253
+ Use pep ${groupName} <command> --help for details and examples.`;
1254
+ }
1255
+ if (path.length > 2 || !Object.hasOwn(group.commands, commandName)) {
1256
+ throw new Error(`Unknown ${groupName} command: ${path.slice(1).join(" ")}. Run pep ${groupName} --help.`);
1257
+ }
1258
+ return renderCommand(group.commands[commandName]);
1259
+ }
1260
+
964
1261
  // src/nexus-service.ts
965
1262
  var CREDENTIAL_PATH = "/api/nexus/credential";
966
1263
  function describeFailure2(status, code) {
@@ -974,13 +1271,16 @@ function describeFailure2(status, code) {
974
1271
  return "This access token carries no `nexus-credentials:write` scope. Ask an operator to add it to this client's allowed_scopes in PEP, then run `pep auth login` again \u2014 existing tokens do not gain new scopes.";
975
1272
  }
976
1273
  if (status === 409) {
977
- return "This PEP deployment still issues one Maven credential per company, so it cannot give you yours again. Newer PEP issues one per person and can always re-derive it \u2014 ask an operator to upgrade this deployment; until then, copy the credential from the machine that has it.";
1274
+ return "This PEP deployment issues a random Maven password once and does not store it, so it cannot give you yours again. Newer PEP derives it and can always re-issue the same one \u2014 ask an operator to upgrade this deployment; until then, copy the credential from the machine that has it.";
978
1275
  }
979
1276
  if (status === 503) {
980
1277
  return "PEP could not reach Nexus (not configured, or upstream is down). This is a platform-side problem \u2014 retrying will not help. Report it to a PEP operator.";
981
1278
  }
982
1279
  return `PEP answered ${status} when asking for a Maven credential.`;
983
1280
  }
1281
+ function scrub(text, password) {
1282
+ return password === "" ? text : text.split(password).join("<redacted>");
1283
+ }
984
1284
  async function errorCodeOf(response) {
985
1285
  try {
986
1286
  const body = await response.json();
@@ -1016,7 +1316,11 @@ async function setupNexusCredential(dependencies) {
1016
1316
  const persisted = await persistMavenEnv(credential, target, dependencies.now).catch(
1017
1317
  (error) => ({
1018
1318
  status: "manual",
1019
- reason: error.message,
1319
+ // ⚠ **兜底要洗一遍密码。** `reason` 会被 `nexusReport` 原样打到 stderr,而异常消息里
1320
+ // 带密码是有过先例的:`execFile` 被拒时的 message 含**完整命令行**,`setx` 的密码就是
1321
+ // 它的一个参数。那一处已经在 `maven-env.ts` 里就地接住了,这里是第二道 —— 往后谁在
1322
+ // 那条路上新加一个 throw,不必先想起这条注释也不会漏出去。
1323
+ reason: scrub(error.message, credential.password),
1020
1324
  ...target.kind === "profile" ? { path: target.path } : {}
1021
1325
  })
1022
1326
  );
@@ -1105,6 +1409,7 @@ function readTar(bytes) {
1105
1409
 
1106
1410
  // src/skills-service.ts
1107
1411
  var ARCHIVE_PATH = "/api/skills/archive";
1412
+ var GROUP_PATH = "/api/skills/group";
1108
1413
  var MAX_ARCHIVE_BYTES = 64 * 1024 * 1024;
1109
1414
  var MAX_UNPACKED_BYTES = 256 * 1024 * 1024;
1110
1415
  var MAX_ENTRIES = 1e4;
@@ -1200,6 +1505,69 @@ function describeFailure3(status) {
1200
1505
  }
1201
1506
  return `PEP answered ${status}.`;
1202
1507
  }
1508
+ function describeGroupFailure(status) {
1509
+ if (status === 400)
1510
+ return "PEP did not accept that group path. It must be a <group> or <group>/<subgroup> path on the platform's GitLab host, or an https URL to that group.";
1511
+ if (status === 404) {
1512
+ return "No such group on the platform's GitLab (or this PEP deployment has no /api/skills/group endpoint at all). Check the path, then ask an operator whether this deployment serves skills.";
1513
+ }
1514
+ if (status === 503)
1515
+ return "PEP could not reach GitLab to list that group. Retry shortly, or ask an operator whether this deployment serves skills.";
1516
+ return describeFailure3(status);
1517
+ }
1518
+ var MAX_GROUP_LIST_BYTES = 4 * 1024 * 1024;
1519
+ function parseGroupList(body) {
1520
+ let payload;
1521
+ try {
1522
+ payload = JSON.parse(body);
1523
+ } catch {
1524
+ throw new Error("PEP returned a group listing this CLI cannot read (not JSON).");
1525
+ }
1526
+ const items = payload?.data?.items;
1527
+ if (!Array.isArray(items)) {
1528
+ throw new Error("PEP returned a group listing this CLI cannot read (no data.items array).");
1529
+ }
1530
+ const paths = [];
1531
+ for (const item of items) {
1532
+ const path = item?.path;
1533
+ if (typeof path !== "string" || path.trim() === "") {
1534
+ throw new Error("PEP returned a group listing this CLI cannot read (an entry has no path).");
1535
+ }
1536
+ paths.push(path.trim());
1537
+ }
1538
+ return [...new Set(paths)].sort();
1539
+ }
1540
+ async function listGroupRepos(dependencies) {
1541
+ const doFetch = dependencies.fetch ?? globalThis.fetch;
1542
+ const url = new URL(`${dependencies.issuer.replace(/\/+$/, "")}${GROUP_PATH}`);
1543
+ url.searchParams.set("path", dependencies.group);
1544
+ const response = await doFetch(url, {
1545
+ headers: { Authorization: `Bearer ${dependencies.accessToken}` }
1546
+ });
1547
+ if (!response.ok) {
1548
+ await response.body?.cancel();
1549
+ throw new Error(describeGroupFailure(response.status));
1550
+ }
1551
+ const raw = await readCapped(response, dependencies.maxListBytes ?? MAX_GROUP_LIST_BYTES);
1552
+ return parseGroupList(new TextDecoder().decode(raw));
1553
+ }
1554
+ function groupMemberPath(ledgerKey, group) {
1555
+ let path = ledgerKey.trim();
1556
+ if (/^https?:\/\//i.test(path)) {
1557
+ try {
1558
+ path = new URL(path).pathname;
1559
+ } catch {
1560
+ return void 0;
1561
+ }
1562
+ }
1563
+ path = (path.split("@")[0] ?? "").replace(/\.git$/i, "");
1564
+ const segments = path.split("/").filter((one) => one !== "");
1565
+ const groupSegments = group.split("/").filter((one) => one !== "");
1566
+ if (groupSegments.length === 0 || segments.length <= groupSegments.length) return void 0;
1567
+ const lower = segments.map((one) => one.toLowerCase());
1568
+ if (!groupSegments.every((one, index) => one.toLowerCase() === lower[index])) return void 0;
1569
+ return lower.join("/");
1570
+ }
1203
1571
  async function physicalDir(path) {
1204
1572
  const absolute = (0, import_node_path4.resolve)(path);
1205
1573
  try {
@@ -1425,98 +1793,110 @@ async function updateSkills(dependencies) {
1425
1793
  };
1426
1794
  }
1427
1795
 
1428
- // src/cli.ts
1429
- var VERSION = false ? "0.0.0-dev" : "0.8.1";
1430
- function usage() {
1431
- return `PEP CLI ${VERSION}
1432
-
1433
- Usage:
1434
- pep auth login [--issuer <url>] [--client-id <id>] [--resource <uri> ...]
1435
- pep auth status
1436
- pep auth token
1437
- pep auth logout
1438
- pep skills list
1439
- pep skills add <repo-url | group/project[@ref]> [-p | --dir <path>]
1440
- pep skills update [<repo>...] [-p | --dir <path>]
1441
- pep skills remove <repo>
1442
- pep docs list [--docs-url <url>]
1443
- pep docs get <path> [--docs-url <url>]
1444
- pep nexus setup [--print-env]
1445
-
1446
- The issuer is built into this executable. --issuer overrides it for that one command and is
1447
- NOT remembered \u2014 pass it every time you log in against a non-default deployment.
1448
- --resource names which resource server the token is for (RFC 8707); repeat it for more than
1449
- one. It defaults to the docs platform \u2014 a token minted without it is rejected by every
1450
- resource server, and their answer looks exactly like "this token does not exist".
1451
- Use \`pep auth token\` when another agent needs a fresh bearer token.
1452
-
1453
- \`pep skills add\` takes a repository on the platform's own GitLab, either as a full https URL or
1454
- as the path with the host left off:
1455
-
1456
- pep skills add https://git.example.com/group/sub/project
1457
- pep skills add https://git.example.com/group/sub/project/-/tree/some-branch
1458
- pep skills add group/sub/project
1459
- pep skills add group/sub/project@some-branch
1460
-
1461
- Anything on another host is refused. PEP fetches it with its own read-only service account, so no
1462
- GitLab credential ever reaches this machine. \`pep skills list\` shows what you have added and
1463
- \`pep skills update\` refreshes it \u2014 all of it, or just the repositories you name. One repository
1464
- may hold more than one skill: every directory containing a SKILL.md becomes one, wherever it sits,
1465
- and update reports which of them actually changed rather than just that the repository moved.
1466
-
1467
- \`pep skills update\` fetches the latest skills from PEP and writes them to the shared agent
1468
- directory (${defaultSkillsDirectory()}), which Codex, Cursor, Amp and ~20 other agents read
1469
- directly. Claude Code keeps its own directory, so each skill is also linked into
1470
- ${claudeSkillsDirectory()} \u2014 one copy on disk, updated in one place. Where links are not
1471
- available the skill is copied instead and the run says so.
1472
-
1473
- -p, --project installs into the CURRENT PROJECT instead: ./.agents/skills plus the same link into
1474
- ./.claude/skills. Use it when the skills belong to one repository and should be committed with
1475
- it; note both directories then show up in git status, which is why the personal location is the
1476
- default.
1477
-
1478
- --dir <path> writes to that path ONLY and skips the linking, for an agent that reads neither
1479
- directory.
1480
-
1481
- Where a repository was installed is remembered, so \`pep skills update\` refreshes each one where
1482
- it already lives and never moves anything. On update, -p and --dir instead NARROW the run to the
1483
- repositories installed there: \`pep skills update -p\` refreshes only this project's, and reports
1484
- nothing to do when the project has none. To move a repository, add it again with the new flag \u2014
1485
- add is where the location is decided, and the copy in the old location is then removed.
1486
- \`pep skills remove <repo>\` deletes the skills that repository installed and drops it from the
1487
- list. It only removes what it installed itself, and it needs no login. A skill that another
1488
- repository also installs is left on disk (which copy it is can no longer be told) and reported.
1489
-
1490
- Two repositories cannot both provide a skill with the SAME NAME in one directory \u2014 the skill name
1491
- is the folder name inside the repository, so they would overwrite each other. add refuses, naming
1492
- the repository already there. update never fails over it: it leaves that one skill untouched,
1493
- refreshes everything else, and reports it every run until you remove one. Installing them into
1494
- different directories (-p, or --dir) is fine.
1495
-
1496
- Delete a skill folder by hand and update LEAVES IT DELETED \u2014 it refreshes what is still there and
1497
- reports the ones it left alone. Run \`pep skills add <repo>\` to put them back; add is the command
1498
- that installs. Either way update only touches skills it wrote itself; anything you put there by
1499
- hand is left alone.
1500
-
1501
- \`pep docs list\` prints the documents this account can read (path + description); feed a path
1502
- straight to \`pep docs get\` to print that document as markdown on stdout. The documentation
1503
- site is built in (${DEFAULT_DOCS_URL}); --docs-url overrides it and is then remembered.
1504
-
1505
- \`pep nexus setup\` asks PEP for your Maven repository credential and saves it as the two
1506
- environment variables the Newland Android SDK reads \u2014 \`${USERNAME_VAR}\` and \`${PASSWORD_VAR}\`
1507
- \u2014 so you do not copy anything by hand. On Windows it writes them to your user environment
1508
- (\`setx\`); on macOS it keeps a marked block in your shell profile, backing the file up first and
1509
- touching nothing else. Either way persistence only affects NEW shells.
1510
-
1511
- **The password is never printed.** The credential is derived from who you are, so running this
1512
- again \u2014 on this machine or any other \u2014 hands you the same one. Nothing is lost by not showing it.
1513
- \`--print-env\` writes the two lines to stdout instead, for
1514
- \`eval "$(pep nexus setup --print-env)"\` when you want the shell you are in to have them now.
1515
-
1516
- The credential is per person, not per company: your colleagues get their own, and so does the same
1517
- account on a second machine. Only Android needs this \u2014 the Windows and iOS SDKs are cloned from
1518
- Git, not pulled from Maven.`;
1796
+ // src/setup-flow.ts
1797
+ function accountOf(user) {
1798
+ return user.email ?? user.name ?? user.sub;
1799
+ }
1800
+ function messageOf(error) {
1801
+ return error instanceof Error ? error.message : String(error);
1802
+ }
1803
+ function driftedFromGroup(installedSources, groupRepos, group) {
1804
+ const present = new Set(groupRepos.map((one) => one.trim().toLowerCase()));
1805
+ return installedSources.filter((key) => {
1806
+ const member = groupMemberPath(key, group);
1807
+ return member !== void 0 && !present.has(member);
1808
+ }).sort();
1809
+ }
1810
+ async function installGroup(dependencies, remote) {
1811
+ const group = dependencies.group;
1812
+ let repos;
1813
+ try {
1814
+ repos = await dependencies.listGroupRepos(remote);
1815
+ } catch (error) {
1816
+ const message = messageOf(error);
1817
+ dependencies.onProgress(` \u2717 ${message}`);
1818
+ return { status: "failed", group, message };
1819
+ }
1820
+ if (repos.length === 0) {
1821
+ dependencies.onProgress(` ${group} lists no repositories \u2014 nothing to install.`);
1822
+ }
1823
+ const installed = [];
1824
+ const failed = [];
1825
+ for (const [index, source] of repos.entries()) {
1826
+ dependencies.onProgress(` [${index + 1}/${repos.length}] ${source}`);
1827
+ try {
1828
+ installed.push({ source, ...await dependencies.installSkills(remote, source) });
1829
+ } catch (error) {
1830
+ const message = messageOf(error);
1831
+ failed.push({ source, message });
1832
+ dependencies.onProgress(` \u2717 ${source}: ${message}`);
1833
+ }
1834
+ }
1835
+ return {
1836
+ status: "installed",
1837
+ group,
1838
+ installed,
1839
+ failed,
1840
+ drifted: driftedFromGroup(dependencies.installedSources, repos, group)
1841
+ };
1519
1842
  }
1843
+ async function runSetup(dependencies) {
1844
+ dependencies.onProgress("\u2460 Signing in to PEP\u2026");
1845
+ let login;
1846
+ let signedIn;
1847
+ try {
1848
+ signedIn = await dependencies.auth.status();
1849
+ login = { alreadySignedIn: true, account: accountOf(signedIn.user) };
1850
+ dependencies.onProgress(` Already signed in as ${login.account} \u2014 skipped.`);
1851
+ } catch {
1852
+ const config = await dependencies.loginConfig();
1853
+ dependencies.onProgress(" Opening your browser to sign in\u2026");
1854
+ await dependencies.auth.login(config, dependencies.onAuthorizationUrl);
1855
+ const account = await dependencies.auth.status().then((it) => accountOf(it.user)).catch(() => "(account name unavailable)");
1856
+ login = { alreadySignedIn: false, account };
1857
+ dependencies.onProgress(` Signed in as ${account}.`);
1858
+ }
1859
+ const authorization = signedIn?.authorization ?? await dependencies.auth.currentAuthorization();
1860
+ const remote = { issuer: authorization.issuer, accessToken: authorization.accessToken };
1861
+ let skills;
1862
+ if (dependencies.skipSkills) {
1863
+ skills = { status: "skipped", reason: "--skip-skills" };
1864
+ dependencies.onProgress("\u2461 Installing skills\u2026 skipped (--skip-skills).");
1865
+ } else {
1866
+ dependencies.onProgress(`\u2461 Listing skills repositories in ${dependencies.group}\u2026`);
1867
+ skills = await installGroup(dependencies, remote);
1868
+ }
1869
+ let nexus;
1870
+ if (dependencies.skipNexus) {
1871
+ nexus = { status: "skipped", reason: "--skip-nexus" };
1872
+ dependencies.onProgress("\u2462 Maven credentials\u2026 skipped (--skip-nexus).");
1873
+ } else {
1874
+ dependencies.onProgress("\u2462 Setting up Maven credentials\u2026");
1875
+ try {
1876
+ nexus = { status: "done", result: await dependencies.setupNexus(remote) };
1877
+ } catch (error) {
1878
+ nexus = { status: "failed", message: messageOf(error) };
1879
+ dependencies.onProgress(` \u2717 ${nexus.message}`);
1880
+ }
1881
+ }
1882
+ return { login, skills, nexus };
1883
+ }
1884
+ function setupFailureSummary(report) {
1885
+ const failed = [];
1886
+ if (report.skills.status === "failed") {
1887
+ failed.push("skills (could not list the group)");
1888
+ } else if (report.skills.status === "installed" && report.skills.failed.length > 0) {
1889
+ const total = report.skills.installed.length + report.skills.failed.length;
1890
+ failed.push(`skills (${report.skills.failed.length} of ${total} repositories)`);
1891
+ }
1892
+ if (report.nexus.status === "failed") failed.push("Maven credentials");
1893
+ if (failed.length === 0) return void 0;
1894
+ return `Setup finished with problems: ${failed.join(", ")} did not complete.`;
1895
+ }
1896
+
1897
+ // src/cli.ts
1898
+ var VERSION = false ? "0.0.0-dev" : "0.9.0-rc.1";
1899
+ var SKILLS_ADD_DEPRECATION = "\u26A0 `pep skills add` will be removed in a future release.";
1520
1900
  function reportUpdate(result) {
1521
1901
  const conflicts = () => {
1522
1902
  for (const { skill, owner } of result.conflicts) {
@@ -1604,7 +1984,7 @@ function parseSkillsArgs(command, args, cwd) {
1604
1984
  }
1605
1985
  const unknownOption = args.find((one) => one.startsWith("-"));
1606
1986
  if (unknownOption) throw new Error(`Unknown option: ${unknownOption}`);
1607
- const takesOne = command === "add" || command === "remove";
1987
+ const takesOne = command === "add" || command === "remove" || command === "setup";
1608
1988
  const requested = takesOne ? args.shift() : void 0;
1609
1989
  if (command === "add" && !requested) {
1610
1990
  throw new Error("pep skills add needs a repository: a full https URL, or <group>/<project>.");
@@ -1642,6 +2022,8 @@ function nexusReport(result, printEnv) {
1642
2022
  `Nothing was written${persisted.path ? ` to ${persisted.path}` : ""}: ${persisted.reason}`
1643
2023
  );
1644
2024
  stderr.push("Run this again to get the same credential \u2014 it is derived, not stored.");
2025
+ stderr.push(`Nexus user: ${result.username}`);
2026
+ return { stdout, stderr, failure: "Maven credentials could not be fully saved." };
1645
2027
  } else {
1646
2028
  stderr.push(
1647
2029
  persisted.status === "profile-replaced" ? `Replaced the pep-cli block in ${persisted.path}` : `Appended a pep-cli block to ${persisted.path}`
@@ -1654,10 +2036,58 @@ function nexusReport(result, printEnv) {
1654
2036
  stderr.push(`Nexus user: ${result.username}`);
1655
2037
  return { stdout, stderr };
1656
2038
  }
2039
+ function createCliServices() {
2040
+ const configStore = fileConfigStore(configPath());
2041
+ return {
2042
+ configStore,
2043
+ auth: createAuthService({
2044
+ configStore,
2045
+ credentialStore: systemCredentialStore(),
2046
+ oauth: createOAuthClient()
2047
+ })
2048
+ };
2049
+ }
2050
+ function reportSetup(report) {
2051
+ console.log("");
2052
+ const skills = report.skills;
2053
+ if (skills.status === "skipped") {
2054
+ console.error(`Skills: ${skills.reason}`);
2055
+ } else if (skills.status === "failed") {
2056
+ console.error(`Skills: could not list ${skills.group} \u2014 ${skills.message}`);
2057
+ } else {
2058
+ const total = skills.installed.length + skills.failed.length;
2059
+ for (const directory of [...new Set(skills.installed.map((one) => one.directory))].sort()) {
2060
+ console.log(`Next: read ${directory}/<name>/SKILL.md`);
2061
+ }
2062
+ console.error(
2063
+ total === 0 ? `Skills: ${skills.group} lists no repositories \u2014 nothing was installed.` : `Skills: installed ${skills.installed.length} of ${total} repositories in ${skills.group}.`
2064
+ );
2065
+ for (const { source, message } of skills.failed) {
2066
+ console.error(` \u2717 ${source}: ${message}`);
2067
+ }
2068
+ if (skills.drifted.length > 0) {
2069
+ console.error(`
2070
+ \u26A0 Still on your list, but no longer in ${skills.group}:`);
2071
+ for (const source of skills.drifted) console.error(` ${source}`);
2072
+ console.error(" They were left in place \u2014 nothing was deleted. `pep skills update` will");
2073
+ console.error(" report 404 for each of them (which reads exactly like a typo, but is not).");
2074
+ console.error(" `pep skills remove <repo>` drops one for good.");
2075
+ }
2076
+ }
2077
+ if (report.nexus.status === "skipped") {
2078
+ console.error(`Maven credentials: skipped (${report.nexus.reason}).`);
2079
+ }
2080
+ }
1657
2081
  async function main() {
1658
2082
  const args = process.argv.slice(2);
2083
+ if (args[0] === "help") {
2084
+ console.log(usage(VERSION, args.slice(1).filter((arg) => arg !== "--help" && arg !== "-h")));
2085
+ return;
2086
+ }
1659
2087
  if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
1660
- console.log(usage());
2088
+ const path = args.filter((arg) => arg !== "--help" && arg !== "-h").slice(0, 2);
2089
+ const flagIndex = path.findIndex((arg) => arg.startsWith("-"));
2090
+ console.log(usage(VERSION, flagIndex === -1 ? path : path.slice(0, flagIndex)));
1661
2091
  return;
1662
2092
  }
1663
2093
  if (args[0] === "--version" || args[0] === "-v") {
@@ -1665,22 +2095,71 @@ async function main() {
1665
2095
  return;
1666
2096
  }
1667
2097
  const group = args.shift();
2098
+ if (group === "setup") {
2099
+ const skipNexus = flag(args, "--skip-nexus");
2100
+ const skipSkills = flag(args, "--skip-skills");
2101
+ const { requested, chosenTarget } = parseSkillsArgs("setup", args);
2102
+ const skillsGroup = requested ?? DEFAULT_SKILLS_GROUP;
2103
+ const { configStore: configStore2, auth: auth2 } = createCliServices();
2104
+ const stateStore = fileSkillsStateStore();
2105
+ const state = await stateStore.read();
2106
+ const report = await runSetup({
2107
+ auth: auth2,
2108
+ loginConfig: () => loginConfig(args, configStore2),
2109
+ group: skillsGroup,
2110
+ skipSkills,
2111
+ listGroupRepos: (remote) => listGroupRepos({ ...remote, group: skillsGroup }),
2112
+ installSkills: async (remote, source) => {
2113
+ const target = targetForSource(chosenTarget, state?.packages[source]);
2114
+ const result = await updateSkills({
2115
+ ...remote,
2116
+ source,
2117
+ directory: target.directory,
2118
+ ...target.linkInto !== void 0 ? { linkInto: target.linkInto } : {},
2119
+ // `add` 那一档的语义:盘上被删掉的那些由它补回来(`update` 不管)。
2120
+ restoreMissing: true,
2121
+ stateStore
2122
+ });
2123
+ return { result, directory: target.directory };
2124
+ },
2125
+ // 漂移比对的另一半。取**这一趟开始前**的账 —— 装完再读的话,本轮刚装上的那些也在
2126
+ // 里头,而它们当然都还在群里,比出来一样,只是白读一遍。
2127
+ installedSources: Object.keys(state?.packages ?? {}),
2128
+ skipNexus,
2129
+ setupNexus: (remote) => setupNexusCredential(remote),
2130
+ onProgress: (line) => console.error(line),
2131
+ onAuthorizationUrl: (url) => console.error(`If the browser does not open, visit:
2132
+ ${url}`)
2133
+ });
2134
+ if (report.skills.status === "installed") {
2135
+ for (const one of report.skills.installed) reportUpdate(one.result);
2136
+ }
2137
+ if (report.nexus.status === "done") {
2138
+ const nexus = nexusReport(report.nexus.result, false);
2139
+ for (const line of nexus.stdout) console.log(line);
2140
+ for (const line of nexus.stderr) console.error(line);
2141
+ if (nexus.failure) report.nexus = { status: "failed", message: nexus.failure };
2142
+ }
2143
+ reportSetup(report);
2144
+ const failure = setupFailureSummary(report);
2145
+ if (failure) throw new Error(failure);
2146
+ return;
2147
+ }
1668
2148
  if (group !== "auth" && group !== "skills" && group !== "docs" && group !== "nexus")
1669
2149
  throw new Error(`Unknown command.
1670
2150
 
1671
- ${usage()}`);
2151
+ ${usage(VERSION)}`);
1672
2152
  const command = args.shift();
1673
- const configStore = fileConfigStore(configPath());
1674
- const auth = createAuthService({
1675
- configStore,
1676
- credentialStore: systemCredentialStore(),
1677
- oauth: createOAuthClient()
1678
- });
2153
+ if (!command) {
2154
+ console.log(usage(VERSION, [group]));
2155
+ return;
2156
+ }
2157
+ const { configStore, auth } = createCliServices();
1679
2158
  if (group === "docs") {
1680
2159
  if (command !== "list" && command !== "get") {
1681
2160
  throw new Error(`Unknown docs command.
1682
2161
 
1683
- ${usage()}`);
2162
+ ${usage(VERSION, ["docs"])}`);
1684
2163
  }
1685
2164
  const explicitDocsUrl = option(args, "--docs-url");
1686
2165
  const path = command === "get" ? args.shift() : void 0;
@@ -1708,9 +2187,11 @@ ${usage()}`);
1708
2187
  return;
1709
2188
  }
1710
2189
  if (group === "nexus") {
1711
- if (command !== "setup") throw new Error(`Unknown nexus command.
2190
+ if (command !== "setup") {
2191
+ throw new Error(`Unknown nexus command.
1712
2192
 
1713
- ${usage()}`);
2193
+ ${usage(VERSION, ["nexus"])}`);
2194
+ }
1714
2195
  const printEnv = flag(args, "--print-env");
1715
2196
  if (args.length > 0) throw new Error(`Unknown option: ${args[0]}`);
1716
2197
  const authorization = await auth.currentAuthorization();
@@ -1721,13 +2202,14 @@ ${usage()}`);
1721
2202
  const report = nexusReport(result, printEnv);
1722
2203
  for (const line of report.stdout) console.log(line);
1723
2204
  for (const line of report.stderr) console.error(line);
2205
+ if (report.failure) throw new Error(report.failure);
1724
2206
  return;
1725
2207
  }
1726
2208
  if (group === "skills") {
1727
2209
  if (command !== "update" && command !== "add" && command !== "list" && command !== "remove") {
1728
2210
  throw new Error(`Unknown skills command.
1729
2211
 
1730
- ${usage()}`);
2212
+ ${usage(VERSION, ["skills"])}`);
1731
2213
  }
1732
2214
  const { requested, named, chosenTarget } = parseSkillsArgs(command, args);
1733
2215
  const stateStore = fileSkillsStateStore();
@@ -1784,6 +2266,7 @@ ${usage()}`);
1784
2266
  })
1785
2267
  );
1786
2268
  if (command === "add") {
2269
+ console.error(SKILLS_ADD_DEPRECATION);
1787
2270
  await run2(
1788
2271
  requested,
1789
2272
  targetForSource(chosenTarget, state?.packages[requested]),
@@ -1862,7 +2345,7 @@ ${url}`)
1862
2345
  }
1863
2346
  throw new Error(`Unknown auth command.
1864
2347
 
1865
- ${usage()}`);
2348
+ ${usage(VERSION, ["auth"])}`);
1866
2349
  }
1867
2350
 
1868
2351
  // src/main.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newlandnpt/pep-cli",
3
- "version": "0.8.1",
3
+ "version": "0.9.0-rc.1",
4
4
  "description": "PEP CLI —— 凭 PEP 身份读取 Newland 开发者文档与 agent skills",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",