@newlandnpt/pep-cli 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -0
- package/dist/pep.cjs +103 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -137,6 +137,38 @@ pep skills add group/sub/project -p # move it into this project
|
|
|
137
137
|
`pep skills list` prints each repository with the directory it lives in, which is what `-p` matches
|
|
138
138
|
against.
|
|
139
139
|
|
|
140
|
+
### Removing a repository
|
|
141
|
+
|
|
142
|
+
```powershell
|
|
143
|
+
pep skills remove group/sub/project
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Deletes the skills that repository installed (in both the canonical directory and the linked one)
|
|
147
|
+
and drops it from the list. It only removes what it installed itself — anything you put there by
|
|
148
|
+
hand stays — and it needs no login, since nothing leaves the machine.
|
|
149
|
+
|
|
150
|
+
### Two repositories, one skill name
|
|
151
|
+
|
|
152
|
+
A skill's name is the folder name **inside** the repository, not the repository's own name. So two
|
|
153
|
+
different repositories that each ship a `semi-integration/SKILL.md` both want
|
|
154
|
+
`~/.agents/skills/semi-integration/`, and the second would silently overwrite the first.
|
|
155
|
+
|
|
156
|
+
Adding a second such repository is refused, and the error names the one already there. If you
|
|
157
|
+
already have a colliding pair from an older version, nothing breaks — every command reports it
|
|
158
|
+
until you `remove` one:
|
|
159
|
+
|
|
160
|
+
```
|
|
161
|
+
⚠ 3 repositories all install "semi-integration" here:
|
|
162
|
+
https://git.example.com/team-a/skill
|
|
163
|
+
https://git.example.com/team-b/skill-testing
|
|
164
|
+
They overwrite each other. `pep skills remove <repo>` keeps just one.
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Installing them into **different** directories is fine — that is what `-p` is for.
|
|
168
|
+
|
|
169
|
+
`remove` leaves a shared skill's files on disk (which copy it is can no longer be told) and says
|
|
170
|
+
so, rather than deleting what might be the other repository's content.
|
|
171
|
+
|
|
140
172
|
### Deleting a skill
|
|
141
173
|
|
|
142
174
|
Delete a skill folder by hand and it **stays deleted**. `update` refreshes what is still on disk
|
package/dist/pep.cjs
CHANGED
|
@@ -1195,6 +1195,32 @@ function hashSkill(files) {
|
|
|
1195
1195
|
}
|
|
1196
1196
|
return hash.digest("hex").slice(0, 32);
|
|
1197
1197
|
}
|
|
1198
|
+
async function removeSkills(name, store) {
|
|
1199
|
+
const state = await store.read();
|
|
1200
|
+
const pkg = state?.packages[name];
|
|
1201
|
+
if (!state || !pkg) {
|
|
1202
|
+
throw new Error(`Not added: ${name}. Run \`pep skills list\` to see what is.`);
|
|
1203
|
+
}
|
|
1204
|
+
const deleted = [];
|
|
1205
|
+
const keptForOthers = [];
|
|
1206
|
+
for (const skill of Object.keys(pkg.skills).sort()) {
|
|
1207
|
+
const owner = Object.entries(state.packages).find(
|
|
1208
|
+
([other, one]) => other !== name && one.directory === pkg.directory && skill in one.skills
|
|
1209
|
+
)?.[0];
|
|
1210
|
+
if (owner !== void 0) {
|
|
1211
|
+
keptForOthers.push({ skill, owner });
|
|
1212
|
+
continue;
|
|
1213
|
+
}
|
|
1214
|
+
await (0, import_promises4.rm)((0, import_node_path4.join)(pkg.directory, skill), { recursive: true, force: true });
|
|
1215
|
+
if (pkg.linkedInto !== void 0) {
|
|
1216
|
+
await (0, import_promises4.rm)((0, import_node_path4.join)(pkg.linkedInto, skill), { recursive: true, force: true });
|
|
1217
|
+
}
|
|
1218
|
+
deleted.push(skill);
|
|
1219
|
+
}
|
|
1220
|
+
const { [name]: _dropped, ...rest } = state.packages;
|
|
1221
|
+
await store.write({ version: 4, packages: rest });
|
|
1222
|
+
return { name, deleted, keptForOthers, directory: pkg.directory };
|
|
1223
|
+
}
|
|
1198
1224
|
function pickSkipped(previous, skipped) {
|
|
1199
1225
|
return Object.fromEntries(
|
|
1200
1226
|
skipped.map((skill) => [skill, previous?.skills[skill] ?? ""])
|
|
@@ -1223,11 +1249,18 @@ async function updateSkills(dependencies) {
|
|
|
1223
1249
|
const state = await dependencies.stateStore.read();
|
|
1224
1250
|
const previous = state?.packages[name];
|
|
1225
1251
|
const sameTarget = previous?.directory === dependencies.directory && previous?.linkedInto === dependencies.linkInto;
|
|
1252
|
+
const claimedElsewhere = (skill) => Object.entries(state?.packages ?? {}).find(
|
|
1253
|
+
([other, pkg]) => other !== name && pkg.directory === dependencies.directory && skill in pkg.skills
|
|
1254
|
+
)?.[0];
|
|
1226
1255
|
const gone = previous !== void 0 && sameTarget ? await missingSkills(previous.directory, Object.keys(previous.skills)) : /* @__PURE__ */ new Set();
|
|
1227
1256
|
const nothingToRestore = !dependencies.restoreMissing || gone.size === 0;
|
|
1257
|
+
const standing = Object.keys(previous?.skills ?? {}).flatMap((skill) => {
|
|
1258
|
+
const owner = claimedElsewhere(skill);
|
|
1259
|
+
return owner ? [{ skill, owner }] : [];
|
|
1260
|
+
});
|
|
1228
1261
|
if (commit !== void 0 && commit === previous?.commit && sameTarget && nothingToRestore) {
|
|
1229
1262
|
await response.body?.cancel();
|
|
1230
|
-
return { status: "unchanged", name, commit };
|
|
1263
|
+
return { status: "unchanged", name, commit, conflicts: standing };
|
|
1231
1264
|
}
|
|
1232
1265
|
const gzipped = new Uint8Array(await response.arrayBuffer());
|
|
1233
1266
|
const files = planSkillFiles(readTar((0, import_node_zlib.gunzipSync)(gzipped)));
|
|
@@ -1251,6 +1284,18 @@ async function updateSkills(dependencies) {
|
|
|
1251
1284
|
else if (before !== void 0 && before !== "" && before === hash) unchanged.push(skill);
|
|
1252
1285
|
else updated.push(skill);
|
|
1253
1286
|
}
|
|
1287
|
+
const conflicts = [];
|
|
1288
|
+
for (const skill of skills) {
|
|
1289
|
+
const owner = claimedElsewhere(skill);
|
|
1290
|
+
if (owner === void 0) continue;
|
|
1291
|
+
if (previous !== void 0 && skill in previous.skills) {
|
|
1292
|
+
conflicts.push({ skill, owner });
|
|
1293
|
+
continue;
|
|
1294
|
+
}
|
|
1295
|
+
throw new Error(
|
|
1296
|
+
`${owner} already installs a skill named "${skill}" in ${dependencies.directory}. Two repositories cannot both provide "${skill}" there \u2014 the second would silently overwrite the first. Install this one elsewhere (-p, or --dir <path>), or run \`pep skills remove ${owner}\` first.`
|
|
1297
|
+
);
|
|
1298
|
+
}
|
|
1254
1299
|
const removed = Object.keys(previous?.skills ?? {}).filter((skill) => !inArchive.includes(skill));
|
|
1255
1300
|
if (previous !== void 0) {
|
|
1256
1301
|
const stale = sameTarget ? removed : Object.keys(previous.skills);
|
|
@@ -1305,6 +1350,7 @@ async function updateSkills(dependencies) {
|
|
|
1305
1350
|
fileCount: files.filter((one) => !skipped.includes(one.skill)).length,
|
|
1306
1351
|
removed,
|
|
1307
1352
|
skipped,
|
|
1353
|
+
conflicts,
|
|
1308
1354
|
directory: dependencies.directory,
|
|
1309
1355
|
...dependencies.linkInto !== void 0 ? { linkedInto: dependencies.linkInto } : {},
|
|
1310
1356
|
...copiedCount > 0 ? { copiedCount } : {},
|
|
@@ -1313,7 +1359,7 @@ async function updateSkills(dependencies) {
|
|
|
1313
1359
|
}
|
|
1314
1360
|
|
|
1315
1361
|
// src/cli.ts
|
|
1316
|
-
var VERSION = false ? "0.0.0-dev" : "0.
|
|
1362
|
+
var VERSION = false ? "0.0.0-dev" : "0.6.0";
|
|
1317
1363
|
function usage() {
|
|
1318
1364
|
return `PEP CLI ${VERSION}
|
|
1319
1365
|
|
|
@@ -1325,6 +1371,7 @@ Usage:
|
|
|
1325
1371
|
pep skills list
|
|
1326
1372
|
pep skills add <repo-url | group/project[@ref]> [-p | --dir <path>]
|
|
1327
1373
|
pep skills update [<repo>...] [-p | --dir <path>]
|
|
1374
|
+
pep skills remove <repo>
|
|
1328
1375
|
pep docs list [--docs-url <url>]
|
|
1329
1376
|
pep docs get <path> [--docs-url <url>]
|
|
1330
1377
|
pep nexus setup
|
|
@@ -1369,6 +1416,14 @@ it already lives and never moves anything. On update, -p and --dir instead NARRO
|
|
|
1369
1416
|
repositories installed there: \`pep skills update -p\` refreshes only this project's, and reports
|
|
1370
1417
|
nothing to do when the project has none. To move a repository, add it again with the new flag \u2014
|
|
1371
1418
|
add is where the location is decided, and the copy in the old location is then removed.
|
|
1419
|
+
\`pep skills remove <repo>\` deletes the skills that repository installed and drops it from the
|
|
1420
|
+
list. It only removes what it installed itself, and it needs no login. A skill that another
|
|
1421
|
+
repository also installs is left on disk (which copy it is can no longer be told) and reported.
|
|
1422
|
+
|
|
1423
|
+
Two repositories cannot both provide a skill with the SAME NAME in one directory \u2014 the skill name
|
|
1424
|
+
is the folder name inside the repository, so they would overwrite each other. Adding a second one
|
|
1425
|
+
is refused; if you already have such a pair, every command says so until you remove one.
|
|
1426
|
+
|
|
1372
1427
|
Delete a skill folder by hand and update LEAVES IT DELETED \u2014 it refreshes what is still there and
|
|
1373
1428
|
reports the ones it left alone. Run \`pep skills add <repo>\` to put them back; add is the command
|
|
1374
1429
|
that installs. Either way update only touches skills it wrote itself; anything you put there by
|
|
@@ -1389,8 +1444,15 @@ The password is shown by PEP once and never stored, so a second run reports 409
|
|
|
1389
1444
|
handing it out again.`;
|
|
1390
1445
|
}
|
|
1391
1446
|
function reportUpdate(result) {
|
|
1447
|
+
const conflicts = () => {
|
|
1448
|
+
for (const { skill, owner } of result.conflicts) {
|
|
1449
|
+
console.error(` \u26A0 "${skill}" is also installed here by ${owner} \u2014 they overwrite`);
|
|
1450
|
+
console.error(` each other. \`pep skills remove\` one of them to settle it.`);
|
|
1451
|
+
}
|
|
1452
|
+
};
|
|
1392
1453
|
if (result.status === "unchanged") {
|
|
1393
1454
|
console.log(`${result.name}: already at ${result.commit}. Nothing to do.`);
|
|
1455
|
+
conflicts();
|
|
1394
1456
|
return;
|
|
1395
1457
|
}
|
|
1396
1458
|
console.log(
|
|
@@ -1403,6 +1465,7 @@ function reportUpdate(result) {
|
|
|
1403
1465
|
console.log(` deleted locally, left alone: ${result.skipped.join(" ")}`);
|
|
1404
1466
|
console.log(` (\`pep skills add ${result.name}\` puts them back)`);
|
|
1405
1467
|
}
|
|
1468
|
+
conflicts();
|
|
1406
1469
|
console.log(` ${result.fileCount} file(s) in ${result.directory}`);
|
|
1407
1470
|
if (result.linkedInto !== void 0) {
|
|
1408
1471
|
console.log(
|
|
@@ -1459,17 +1522,22 @@ async function loginConfig(args, store = fileConfigStore()) {
|
|
|
1459
1522
|
};
|
|
1460
1523
|
}
|
|
1461
1524
|
function parseSkillsArgs(command, args, cwd) {
|
|
1462
|
-
const
|
|
1463
|
-
const
|
|
1525
|
+
const local = command === "list" || command === "remove";
|
|
1526
|
+
const explicitDirectory = local ? void 0 : option(args, "--dir");
|
|
1527
|
+
const project = local ? false : flag(args, "--project", "-p");
|
|
1464
1528
|
if (explicitDirectory !== void 0 && project) {
|
|
1465
1529
|
throw new Error("--dir and --project/-p cannot be used together: they name different places.");
|
|
1466
1530
|
}
|
|
1467
1531
|
const unknownOption = args.find((one) => one.startsWith("-"));
|
|
1468
1532
|
if (unknownOption) throw new Error(`Unknown option: ${unknownOption}`);
|
|
1469
|
-
const
|
|
1533
|
+
const takesOne = command === "add" || command === "remove";
|
|
1534
|
+
const requested = takesOne ? args.shift() : void 0;
|
|
1470
1535
|
if (command === "add" && !requested) {
|
|
1471
1536
|
throw new Error("pep skills add needs a repository: a full https URL, or <group>/<project>.");
|
|
1472
1537
|
}
|
|
1538
|
+
if (command === "remove" && !requested) {
|
|
1539
|
+
throw new Error("pep skills remove needs a repository. Run `pep skills list` to see them.");
|
|
1540
|
+
}
|
|
1473
1541
|
const named = command === "update" ? [...args] : [];
|
|
1474
1542
|
if (command !== "update" && args.length > 0) {
|
|
1475
1543
|
throw new Error(`Unexpected argument: ${args[0]}`);
|
|
@@ -1569,16 +1637,15 @@ ${usage()}`);
|
|
|
1569
1637
|
return;
|
|
1570
1638
|
}
|
|
1571
1639
|
if (group === "skills") {
|
|
1572
|
-
if (command !== "update" && command !== "add" && command !== "list") {
|
|
1640
|
+
if (command !== "update" && command !== "add" && command !== "list" && command !== "remove") {
|
|
1573
1641
|
throw new Error(`Unknown skills command.
|
|
1574
1642
|
|
|
1575
1643
|
${usage()}`);
|
|
1576
1644
|
}
|
|
1577
1645
|
const { requested, named, chosenTarget } = parseSkillsArgs(command, args);
|
|
1578
|
-
const
|
|
1579
|
-
const remote = { issuer: authorization.issuer, accessToken: authorization.accessToken };
|
|
1646
|
+
const stateStore = fileSkillsStateStore();
|
|
1580
1647
|
if (command === "list") {
|
|
1581
|
-
const listed = await
|
|
1648
|
+
const listed = await stateStore.read();
|
|
1582
1649
|
const packages = Object.entries(listed?.packages ?? {}).sort(
|
|
1583
1650
|
([a], [b]) => a.localeCompare(b)
|
|
1584
1651
|
);
|
|
@@ -1587,10 +1654,36 @@ ${usage()}`);
|
|
|
1587
1654
|
return;
|
|
1588
1655
|
}
|
|
1589
1656
|
for (const [source, pkg] of packages) console.log(`${source} ${pkg.directory}`);
|
|
1657
|
+
const byPlace = /* @__PURE__ */ new Map();
|
|
1658
|
+
for (const [source, pkg] of packages) {
|
|
1659
|
+
for (const skill of Object.keys(pkg.skills)) {
|
|
1660
|
+
const key = `${pkg.directory}\0${skill}`;
|
|
1661
|
+
byPlace.set(key, [...byPlace.get(key) ?? [], source]);
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
for (const [key, owners] of byPlace) {
|
|
1665
|
+
if (owners.length < 2) continue;
|
|
1666
|
+
const skill = key.split("\0")[1];
|
|
1667
|
+
console.error(`
|
|
1668
|
+
\u26A0 ${owners.length} repositories all install "${skill}" here:`);
|
|
1669
|
+
for (const one of owners) console.error(` ${one}`);
|
|
1670
|
+
console.error(" They overwrite each other. `pep skills remove <repo>` keeps just one.");
|
|
1671
|
+
}
|
|
1590
1672
|
console.error("\n`pep skills update` refreshes all of them, each where it lives.");
|
|
1591
1673
|
return;
|
|
1592
1674
|
}
|
|
1593
|
-
|
|
1675
|
+
if (command === "remove") {
|
|
1676
|
+
const result = await removeSkills(requested, stateStore);
|
|
1677
|
+
console.log(
|
|
1678
|
+
result.deleted.length > 0 ? `${result.name}: removed ${result.deleted.join(" ")} from ${result.directory}` : `${result.name}: nothing on disk to delete; dropped it from the list`
|
|
1679
|
+
);
|
|
1680
|
+
for (const { skill, owner } of result.keptForOthers) {
|
|
1681
|
+
console.error(` kept "${skill}" \u2014 ${owner} also installs it here`);
|
|
1682
|
+
}
|
|
1683
|
+
return;
|
|
1684
|
+
}
|
|
1685
|
+
const authorization = await auth.currentAuthorization();
|
|
1686
|
+
const remote = { issuer: authorization.issuer, accessToken: authorization.accessToken };
|
|
1594
1687
|
const state = await stateStore.read();
|
|
1595
1688
|
const installed = Object.keys(state?.packages ?? {}).sort();
|
|
1596
1689
|
const run2 = async (source, target, restoreMissing = false) => reportUpdate(
|