@serviceme/devtools-cli 2.0.0 → 2.0.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.
- package/dist/bridgeServer.js +117 -36
- package/dist/cli.js +463 -100
- package/package.json +4 -4
package/dist/bridgeServer.js
CHANGED
|
@@ -59,6 +59,8 @@ var BRIDGE_METHODS = [
|
|
|
59
59
|
"copilotContent.convertToSymlink",
|
|
60
60
|
"copilotContent.uninstall",
|
|
61
61
|
"copilotContent.setEntryEnabled",
|
|
62
|
+
"copilotContent.detectUnmanaged",
|
|
63
|
+
"copilotContent.adoptUnmanaged",
|
|
62
64
|
"copilotContent.listLinked",
|
|
63
65
|
"copilotContent.draft.create",
|
|
64
66
|
"copilotContent.draft.commit",
|
|
@@ -244,7 +246,7 @@ function normalizeServicemeError(error, fallbackCode = "internal_error") {
|
|
|
244
246
|
}
|
|
245
247
|
|
|
246
248
|
// src/version.ts
|
|
247
|
-
var SERVICEME_CLI_VERSION = "2.0.
|
|
249
|
+
var SERVICEME_CLI_VERSION = "2.0.1";
|
|
248
250
|
|
|
249
251
|
// src/bridge/CopilotContentBridgeHandler.ts
|
|
250
252
|
var fsp = __toESM(require("fs/promises"));
|
|
@@ -305,16 +307,12 @@ var CopilotContentBridgeHandler = class {
|
|
|
305
307
|
* removed and never resurrected) while the manifest stays intact.
|
|
306
308
|
*/
|
|
307
309
|
async makeReconciler(workspaceDir) {
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
return marks.some(
|
|
315
|
-
(mark) => mark.scope === "workspace" && mark.workspaceDir === workspaceDir && mark.repoId === repoId && mark.name === pluginId && kindName === mark.kind
|
|
316
|
-
);
|
|
317
|
-
};
|
|
310
|
+
let marks = [];
|
|
311
|
+
try {
|
|
312
|
+
marks = await new import_devtools_core.DisabledContentStore().list();
|
|
313
|
+
} catch {
|
|
314
|
+
}
|
|
315
|
+
const matches = (0, import_devtools_core.buildDisabledIdentityMatcher)(workspaceDir, marks);
|
|
318
316
|
return new import_devtools_core.WorkspaceCopilotContentReconciler({
|
|
319
317
|
workspaceDir,
|
|
320
318
|
ensureRepository: (manifest) => this.ensureRepositories(manifest),
|
|
@@ -483,7 +481,8 @@ var CopilotCustomizationsBridgeHandler = class {
|
|
|
483
481
|
installations: snapshot.installations.map(toPublicInstallation),
|
|
484
482
|
statesByArtifactId: toPublicStates(snapshot.statesByArtifactId),
|
|
485
483
|
legacyCount: snapshot.legacyCount,
|
|
486
|
-
generatedAt: snapshot.generatedAt
|
|
484
|
+
generatedAt: snapshot.generatedAt,
|
|
485
|
+
...snapshot.disabledArtifactIds ? { disabledArtifactIds: snapshot.disabledArtifactIds } : {}
|
|
487
486
|
}),
|
|
488
487
|
...availablePackages ? { availablePackages } : {}
|
|
489
488
|
};
|
|
@@ -607,13 +606,35 @@ function createAvailablePackagesEnumerator(store) {
|
|
|
607
606
|
repositories: [],
|
|
608
607
|
plugins: []
|
|
609
608
|
};
|
|
610
|
-
const
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
609
|
+
const coversPackage = (pkg, selectedIds, selectedKinds) => pkg.artifacts.every(
|
|
610
|
+
(artifact) => selectedIds !== void 0 ? selectedIds.includes(artifact.id) : selectedKinds?.[artifact.kind] === true
|
|
611
|
+
);
|
|
612
|
+
const manifestSelections = new Map(
|
|
613
|
+
manifest.plugins.map((plugin) => [
|
|
614
|
+
`${(0, import_devtools_core2.getWorkspaceManifestPluginSourceId)(manifest, plugin)}::${plugin.id}`,
|
|
615
|
+
plugin
|
|
616
|
+
])
|
|
614
617
|
);
|
|
615
|
-
const
|
|
616
|
-
|
|
618
|
+
const machineMarks = await new import_devtools_core2.DisabledContentStore().list().catch(() => []);
|
|
619
|
+
const disabledTargets = /* @__PURE__ */ new Map();
|
|
620
|
+
const addDisabledTarget = (packageKey, target) => {
|
|
621
|
+
const targets = disabledTargets.get(packageKey) ?? /* @__PURE__ */ new Set();
|
|
622
|
+
targets.add(target);
|
|
623
|
+
disabledTargets.set(packageKey, targets);
|
|
624
|
+
};
|
|
625
|
+
for (const plugin of manifest.plugins) {
|
|
626
|
+
const packageKey = `${(0, import_devtools_core2.getWorkspaceManifestPluginSourceId)(manifest, plugin)}::${plugin.id}`;
|
|
627
|
+
for (const target of plugin.disabledArtifacts ?? []) {
|
|
628
|
+
addDisabledTarget(packageKey, target);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
for (const mark of machineMarks) {
|
|
632
|
+
if (mark.scope === "user" || mark.workspaceDir === params.workspaceDir) {
|
|
633
|
+
addDisabledTarget(`${mark.repoId}::*`, `${mark.kind}:${mark.name}`);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
const personalSelections = new Map(
|
|
637
|
+
(personalIntent?.installations ?? []).filter((installation) => installation.scope === "personal").map((installation) => [installation.packageId, installation])
|
|
617
638
|
);
|
|
618
639
|
const registeredScopes = /* @__PURE__ */ new Map();
|
|
619
640
|
for (const reg of registrations) {
|
|
@@ -623,7 +644,20 @@ function createAvailablePackagesEnumerator(store) {
|
|
|
623
644
|
registeredScopes.set(packageId, scopes);
|
|
624
645
|
}
|
|
625
646
|
return catalogPackages.map((pkg) => {
|
|
626
|
-
const
|
|
647
|
+
const manifestPlugin = manifestSelections.get(pkg.packageId);
|
|
648
|
+
const personalInstallation = personalSelections.get(pkg.packageId);
|
|
649
|
+
const disabledTargetsForPkg = /* @__PURE__ */ new Set([
|
|
650
|
+
...disabledTargets.get(pkg.packageId) ?? [],
|
|
651
|
+
...disabledTargets.get(`${pkg.sourceId}::*`) ?? []
|
|
652
|
+
]);
|
|
653
|
+
const hasDisabledArtifact = pkg.artifacts.find(
|
|
654
|
+
(artifact) => disabledTargetsForPkg.has(`${artifact.kind}:${artifact.displayName}`)
|
|
655
|
+
) !== void 0;
|
|
656
|
+
const installed = registeredScopes.get(pkg.packageId) !== void 0 || hasDisabledArtifact || manifestPlugin !== void 0 && coversPackage(
|
|
657
|
+
pkg,
|
|
658
|
+
manifestPlugin.artifactIds,
|
|
659
|
+
manifestPlugin.artifactIds === void 0 ? manifestPlugin.artifacts : void 0
|
|
660
|
+
) || personalInstallation !== void 0 && coversPackage(pkg, personalInstallation.selectedArtifactIds, void 0);
|
|
627
661
|
return {
|
|
628
662
|
...pkg,
|
|
629
663
|
installedScopes: installed ? ["personal"] : []
|
|
@@ -804,7 +838,8 @@ async function createPackageInstallationService(store) {
|
|
|
804
838
|
packages: snapshot.packages,
|
|
805
839
|
installations: snapshot.installations,
|
|
806
840
|
statesByArtifactId: snapshot.statesByArtifactId,
|
|
807
|
-
generatedAt: snapshot.generatedAt
|
|
841
|
+
generatedAt: snapshot.generatedAt,
|
|
842
|
+
...snapshot.disabledArtifactIds ? { disabledArtifactIds: snapshot.disabledArtifactIds } : {}
|
|
808
843
|
});
|
|
809
844
|
},
|
|
810
845
|
readWorkspaceInstallations: import_devtools_core2.readDeclaredWorkspaceInstallations
|
|
@@ -972,9 +1007,16 @@ async function createWorkspaceSnapshot(repoDisplayName, readers, workspaceDir) {
|
|
|
972
1007
|
})
|
|
973
1008
|
);
|
|
974
1009
|
const state = await readers.readWorkspaceState(workspaceDir);
|
|
1010
|
+
let isDisabled = () => false;
|
|
1011
|
+
try {
|
|
1012
|
+
const marks = await new import_devtools_core2.DisabledContentStore().list();
|
|
1013
|
+
isDisabled = (0, import_devtools_core2.buildDisabledIdentityMatcher)(workspaceDir, marks);
|
|
1014
|
+
} catch {
|
|
1015
|
+
}
|
|
975
1016
|
const packages = [];
|
|
976
1017
|
const installations = [];
|
|
977
1018
|
const statesByArtifactId = {};
|
|
1019
|
+
const disabledArtifactIds = [];
|
|
978
1020
|
const resolvedPlugins = await Promise.all(
|
|
979
1021
|
manifest.plugins.map(async (plugin) => {
|
|
980
1022
|
const sourceId = (0, import_devtools_core2.getWorkspaceManifestPluginSourceId)(manifest, plugin);
|
|
@@ -1013,11 +1055,27 @@ async function createWorkspaceSnapshot(repoDisplayName, readers, workspaceDir) {
|
|
|
1013
1055
|
});
|
|
1014
1056
|
for (const entry of entries) {
|
|
1015
1057
|
const previous = state.entries.find((candidate) => candidate.identity === entry.artifactId);
|
|
1016
|
-
|
|
1058
|
+
if (isDisabled(entry.artifactId)) {
|
|
1059
|
+
disabledArtifactIds.push(entry.artifactId);
|
|
1060
|
+
statesByArtifactId[entry.artifactId] = {
|
|
1061
|
+
intent: "selected",
|
|
1062
|
+
health: "healthy",
|
|
1063
|
+
gate: "ready"
|
|
1064
|
+
};
|
|
1065
|
+
} else {
|
|
1066
|
+
statesByArtifactId[entry.artifactId] = fallbackHealth ? { intent: "selected", health: fallbackHealth, gate: "ready" } : toArtifactState(entry, previous);
|
|
1067
|
+
}
|
|
1017
1068
|
}
|
|
1018
1069
|
}
|
|
1019
1070
|
await mergeRegistrarPackages(sources, packages, installations, statesByArtifactId);
|
|
1020
|
-
return {
|
|
1071
|
+
return {
|
|
1072
|
+
sources,
|
|
1073
|
+
packages,
|
|
1074
|
+
installations,
|
|
1075
|
+
statesByArtifactId,
|
|
1076
|
+
disabledArtifactIds,
|
|
1077
|
+
workspaceManifest: manifest
|
|
1078
|
+
};
|
|
1021
1079
|
}
|
|
1022
1080
|
async function createFullPersonalSnapshot(options, legacyLinks) {
|
|
1023
1081
|
const capabilities = options.personal?.capabilities ?? await (0, import_devtools_core2.createDefaultCopilotHostCapabilities)();
|
|
@@ -1076,20 +1134,29 @@ async function createFullPersonalSnapshot(options, legacyLinks) {
|
|
|
1076
1134
|
}
|
|
1077
1135
|
function fallbackEntries(plugin, packageId) {
|
|
1078
1136
|
const kinds = Object.keys(plugin.artifacts);
|
|
1079
|
-
return kinds.filter((kind) => plugin.artifacts[kind] !== false).map((kind) =>
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1137
|
+
return kinds.filter((kind) => plugin.artifacts[kind] !== false).map((kind) => {
|
|
1138
|
+
const repoId = packageId.split("::")[0] ?? packageId;
|
|
1139
|
+
const identity = (0, import_devtools_core2.buildContentIdentity)({
|
|
1140
|
+
repoId,
|
|
1141
|
+
pluginId: plugin.id,
|
|
1142
|
+
kind,
|
|
1143
|
+
name: plugin.id
|
|
1144
|
+
});
|
|
1145
|
+
return {
|
|
1146
|
+
identity,
|
|
1147
|
+
artifactId: identity,
|
|
1148
|
+
packageId,
|
|
1149
|
+
packageDisplayName: plugin.id,
|
|
1150
|
+
repositoryId: repoId,
|
|
1151
|
+
pluginId: plugin.id,
|
|
1152
|
+
kind,
|
|
1153
|
+
sourcePath: "",
|
|
1154
|
+
sourceIsFile: false,
|
|
1155
|
+
name: plugin.id,
|
|
1156
|
+
digest: "",
|
|
1157
|
+
requiresApproval: kind === "hook" || kind === "mcp"
|
|
1158
|
+
};
|
|
1159
|
+
});
|
|
1093
1160
|
}
|
|
1094
1161
|
function toArtifactState(entry, previous) {
|
|
1095
1162
|
if (!previous) {
|
|
@@ -1607,6 +1674,20 @@ var BridgeServer = class {
|
|
|
1607
1674
|
this.writeSuccess(request.id, result);
|
|
1608
1675
|
return;
|
|
1609
1676
|
}
|
|
1677
|
+
case "copilotContent.detectUnmanaged": {
|
|
1678
|
+
const r = request;
|
|
1679
|
+
if (!this.skillRepoHandler) return this.unsupportedV2(request.id);
|
|
1680
|
+
const result = await this.skillRepoHandler.detectUnmanaged(r.params);
|
|
1681
|
+
this.writeSuccess(request.id, result);
|
|
1682
|
+
return;
|
|
1683
|
+
}
|
|
1684
|
+
case "copilotContent.adoptUnmanaged": {
|
|
1685
|
+
const r = request;
|
|
1686
|
+
if (!this.skillRepoHandler) return this.unsupportedV2(request.id);
|
|
1687
|
+
const result = await this.skillRepoHandler.adoptUnmanaged(r.params);
|
|
1688
|
+
this.writeSuccess(request.id, result);
|
|
1689
|
+
return;
|
|
1690
|
+
}
|
|
1610
1691
|
case "copilotContent.listLinked":
|
|
1611
1692
|
case "skillRepo.listLinked": {
|
|
1612
1693
|
const r = request;
|
package/dist/cli.js
CHANGED
|
@@ -123,16 +123,12 @@ var init_CopilotContentBridgeHandler = __esm({
|
|
|
123
123
|
* removed and never resurrected) while the manifest stays intact.
|
|
124
124
|
*/
|
|
125
125
|
async makeReconciler(workspaceDir) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
return marks.some(
|
|
133
|
-
(mark) => mark.scope === "workspace" && mark.workspaceDir === workspaceDir && mark.repoId === repoId && mark.name === pluginId && kindName === mark.kind
|
|
134
|
-
);
|
|
135
|
-
};
|
|
126
|
+
let marks = [];
|
|
127
|
+
try {
|
|
128
|
+
marks = await new import_devtools_core2.DisabledContentStore().list();
|
|
129
|
+
} catch {
|
|
130
|
+
}
|
|
131
|
+
const matches = (0, import_devtools_core2.buildDisabledIdentityMatcher)(workspaceDir, marks);
|
|
136
132
|
return new import_devtools_core2.WorkspaceCopilotContentReconciler({
|
|
137
133
|
workspaceDir,
|
|
138
134
|
ensureRepository: (manifest) => this.ensureRepositories(manifest),
|
|
@@ -301,6 +297,8 @@ var BRIDGE_METHODS = [
|
|
|
301
297
|
"copilotContent.convertToSymlink",
|
|
302
298
|
"copilotContent.uninstall",
|
|
303
299
|
"copilotContent.setEntryEnabled",
|
|
300
|
+
"copilotContent.detectUnmanaged",
|
|
301
|
+
"copilotContent.adoptUnmanaged",
|
|
304
302
|
"copilotContent.listLinked",
|
|
305
303
|
"copilotContent.draft.create",
|
|
306
304
|
"copilotContent.draft.commit",
|
|
@@ -718,22 +716,38 @@ async function handleUninstall(store, workspacePath, parsed) {
|
|
|
718
716
|
throw createServicemeError("invalid_params", "Expected --id <remoteAgentId>.");
|
|
719
717
|
}
|
|
720
718
|
const agentId = normalizeAgentIdOrThrow(store, remoteId);
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
719
|
+
const workspaceRoot = path.resolve(workspacePath);
|
|
720
|
+
const targets = [
|
|
721
|
+
path.join(workspaceRoot, ".github", "agents", `${agentId}.agent.md`),
|
|
722
|
+
path.join(workspaceRoot, ".github", "agents", agentId)
|
|
723
|
+
];
|
|
724
|
+
for (const target of targets) {
|
|
725
|
+
const resolvedTarget = path.resolve(target);
|
|
726
|
+
if (!resolvedTarget.startsWith(workspaceRoot + path.sep)) {
|
|
727
|
+
throw createServicemeError("invalid_params", `Agent link escapes the workspace: ${agentId}`);
|
|
728
|
+
}
|
|
729
|
+
await fs.rm(resolvedTarget, {
|
|
730
|
+
recursive: true,
|
|
731
|
+
force: true
|
|
732
|
+
});
|
|
733
|
+
}
|
|
734
|
+
const userAgentsRoot = path.resolve(store.getUserAgentsRootPath());
|
|
735
|
+
for (const userTarget of [
|
|
736
|
+
path.join(userAgentsRoot, `${agentId}.agent.md`),
|
|
737
|
+
path.join(userAgentsRoot, agentId)
|
|
738
|
+
]) {
|
|
739
|
+
const resolvedUserTarget = path.resolve(userTarget);
|
|
740
|
+
if (!resolvedUserTarget.startsWith(userAgentsRoot + path.sep)) {
|
|
741
|
+
throw createServicemeError(
|
|
742
|
+
"invalid_params",
|
|
743
|
+
`Agent link escapes the agents root: ${agentId}`
|
|
744
|
+
);
|
|
745
|
+
}
|
|
746
|
+
await fs.rm(resolvedUserTarget, {
|
|
747
|
+
recursive: true,
|
|
748
|
+
force: true
|
|
749
|
+
});
|
|
750
|
+
}
|
|
737
751
|
await store.removeInstalledAgent(remoteId);
|
|
738
752
|
return {
|
|
739
753
|
changed: true,
|
|
@@ -1008,7 +1022,7 @@ var readline = __toESM(require("readline"));
|
|
|
1008
1022
|
|
|
1009
1023
|
// src/version.ts
|
|
1010
1024
|
var SERVICEME_CLI_NAME = "serviceme";
|
|
1011
|
-
var SERVICEME_CLI_VERSION = "2.0.
|
|
1025
|
+
var SERVICEME_CLI_VERSION = "2.0.1";
|
|
1012
1026
|
|
|
1013
1027
|
// src/bridge/BridgeServer.ts
|
|
1014
1028
|
init_CopilotContentBridgeHandler();
|
|
@@ -1069,7 +1083,8 @@ var CopilotCustomizationsBridgeHandler = class {
|
|
|
1069
1083
|
installations: snapshot.installations.map(toPublicInstallation),
|
|
1070
1084
|
statesByArtifactId: toPublicStates(snapshot.statesByArtifactId),
|
|
1071
1085
|
legacyCount: snapshot.legacyCount,
|
|
1072
|
-
generatedAt: snapshot.generatedAt
|
|
1086
|
+
generatedAt: snapshot.generatedAt,
|
|
1087
|
+
...snapshot.disabledArtifactIds ? { disabledArtifactIds: snapshot.disabledArtifactIds } : {}
|
|
1073
1088
|
}),
|
|
1074
1089
|
...availablePackages ? { availablePackages } : {}
|
|
1075
1090
|
};
|
|
@@ -1193,13 +1208,35 @@ function createAvailablePackagesEnumerator(store) {
|
|
|
1193
1208
|
repositories: [],
|
|
1194
1209
|
plugins: []
|
|
1195
1210
|
};
|
|
1196
|
-
const
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1211
|
+
const coversPackage = (pkg, selectedIds, selectedKinds) => pkg.artifacts.every(
|
|
1212
|
+
(artifact) => selectedIds !== void 0 ? selectedIds.includes(artifact.id) : selectedKinds?.[artifact.kind] === true
|
|
1213
|
+
);
|
|
1214
|
+
const manifestSelections = new Map(
|
|
1215
|
+
manifest.plugins.map((plugin) => [
|
|
1216
|
+
`${(0, import_devtools_core3.getWorkspaceManifestPluginSourceId)(manifest, plugin)}::${plugin.id}`,
|
|
1217
|
+
plugin
|
|
1218
|
+
])
|
|
1200
1219
|
);
|
|
1201
|
-
const
|
|
1202
|
-
|
|
1220
|
+
const machineMarks = await new import_devtools_core3.DisabledContentStore().list().catch(() => []);
|
|
1221
|
+
const disabledTargets = /* @__PURE__ */ new Map();
|
|
1222
|
+
const addDisabledTarget = (packageKey, target) => {
|
|
1223
|
+
const targets = disabledTargets.get(packageKey) ?? /* @__PURE__ */ new Set();
|
|
1224
|
+
targets.add(target);
|
|
1225
|
+
disabledTargets.set(packageKey, targets);
|
|
1226
|
+
};
|
|
1227
|
+
for (const plugin of manifest.plugins) {
|
|
1228
|
+
const packageKey = `${(0, import_devtools_core3.getWorkspaceManifestPluginSourceId)(manifest, plugin)}::${plugin.id}`;
|
|
1229
|
+
for (const target of plugin.disabledArtifacts ?? []) {
|
|
1230
|
+
addDisabledTarget(packageKey, target);
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
for (const mark of machineMarks) {
|
|
1234
|
+
if (mark.scope === "user" || mark.workspaceDir === params.workspaceDir) {
|
|
1235
|
+
addDisabledTarget(`${mark.repoId}::*`, `${mark.kind}:${mark.name}`);
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
const personalSelections = new Map(
|
|
1239
|
+
(personalIntent?.installations ?? []).filter((installation) => installation.scope === "personal").map((installation) => [installation.packageId, installation])
|
|
1203
1240
|
);
|
|
1204
1241
|
const registeredScopes = /* @__PURE__ */ new Map();
|
|
1205
1242
|
for (const reg of registrations) {
|
|
@@ -1209,7 +1246,20 @@ function createAvailablePackagesEnumerator(store) {
|
|
|
1209
1246
|
registeredScopes.set(packageId, scopes);
|
|
1210
1247
|
}
|
|
1211
1248
|
return catalogPackages.map((pkg) => {
|
|
1212
|
-
const
|
|
1249
|
+
const manifestPlugin = manifestSelections.get(pkg.packageId);
|
|
1250
|
+
const personalInstallation = personalSelections.get(pkg.packageId);
|
|
1251
|
+
const disabledTargetsForPkg = /* @__PURE__ */ new Set([
|
|
1252
|
+
...disabledTargets.get(pkg.packageId) ?? [],
|
|
1253
|
+
...disabledTargets.get(`${pkg.sourceId}::*`) ?? []
|
|
1254
|
+
]);
|
|
1255
|
+
const hasDisabledArtifact = pkg.artifacts.find(
|
|
1256
|
+
(artifact) => disabledTargetsForPkg.has(`${artifact.kind}:${artifact.displayName}`)
|
|
1257
|
+
) !== void 0;
|
|
1258
|
+
const installed = registeredScopes.get(pkg.packageId) !== void 0 || hasDisabledArtifact || manifestPlugin !== void 0 && coversPackage(
|
|
1259
|
+
pkg,
|
|
1260
|
+
manifestPlugin.artifactIds,
|
|
1261
|
+
manifestPlugin.artifactIds === void 0 ? manifestPlugin.artifacts : void 0
|
|
1262
|
+
) || personalInstallation !== void 0 && coversPackage(pkg, personalInstallation.selectedArtifactIds, void 0);
|
|
1213
1263
|
return {
|
|
1214
1264
|
...pkg,
|
|
1215
1265
|
installedScopes: installed ? ["personal"] : []
|
|
@@ -1390,7 +1440,8 @@ async function createPackageInstallationService(store) {
|
|
|
1390
1440
|
packages: snapshot.packages,
|
|
1391
1441
|
installations: snapshot.installations,
|
|
1392
1442
|
statesByArtifactId: snapshot.statesByArtifactId,
|
|
1393
|
-
generatedAt: snapshot.generatedAt
|
|
1443
|
+
generatedAt: snapshot.generatedAt,
|
|
1444
|
+
...snapshot.disabledArtifactIds ? { disabledArtifactIds: snapshot.disabledArtifactIds } : {}
|
|
1394
1445
|
});
|
|
1395
1446
|
},
|
|
1396
1447
|
readWorkspaceInstallations: import_devtools_core3.readDeclaredWorkspaceInstallations
|
|
@@ -1558,9 +1609,16 @@ async function createWorkspaceSnapshot(repoDisplayName, readers, workspaceDir) {
|
|
|
1558
1609
|
})
|
|
1559
1610
|
);
|
|
1560
1611
|
const state = await readers.readWorkspaceState(workspaceDir);
|
|
1612
|
+
let isDisabled = () => false;
|
|
1613
|
+
try {
|
|
1614
|
+
const marks = await new import_devtools_core3.DisabledContentStore().list();
|
|
1615
|
+
isDisabled = (0, import_devtools_core3.buildDisabledIdentityMatcher)(workspaceDir, marks);
|
|
1616
|
+
} catch {
|
|
1617
|
+
}
|
|
1561
1618
|
const packages = [];
|
|
1562
1619
|
const installations = [];
|
|
1563
1620
|
const statesByArtifactId = {};
|
|
1621
|
+
const disabledArtifactIds = [];
|
|
1564
1622
|
const resolvedPlugins = await Promise.all(
|
|
1565
1623
|
manifest.plugins.map(async (plugin) => {
|
|
1566
1624
|
const sourceId = (0, import_devtools_core3.getWorkspaceManifestPluginSourceId)(manifest, plugin);
|
|
@@ -1599,11 +1657,27 @@ async function createWorkspaceSnapshot(repoDisplayName, readers, workspaceDir) {
|
|
|
1599
1657
|
});
|
|
1600
1658
|
for (const entry of entries) {
|
|
1601
1659
|
const previous = state.entries.find((candidate) => candidate.identity === entry.artifactId);
|
|
1602
|
-
|
|
1660
|
+
if (isDisabled(entry.artifactId)) {
|
|
1661
|
+
disabledArtifactIds.push(entry.artifactId);
|
|
1662
|
+
statesByArtifactId[entry.artifactId] = {
|
|
1663
|
+
intent: "selected",
|
|
1664
|
+
health: "healthy",
|
|
1665
|
+
gate: "ready"
|
|
1666
|
+
};
|
|
1667
|
+
} else {
|
|
1668
|
+
statesByArtifactId[entry.artifactId] = fallbackHealth ? { intent: "selected", health: fallbackHealth, gate: "ready" } : toArtifactState(entry, previous);
|
|
1669
|
+
}
|
|
1603
1670
|
}
|
|
1604
1671
|
}
|
|
1605
1672
|
await mergeRegistrarPackages(sources, packages, installations, statesByArtifactId);
|
|
1606
|
-
return {
|
|
1673
|
+
return {
|
|
1674
|
+
sources,
|
|
1675
|
+
packages,
|
|
1676
|
+
installations,
|
|
1677
|
+
statesByArtifactId,
|
|
1678
|
+
disabledArtifactIds,
|
|
1679
|
+
workspaceManifest: manifest
|
|
1680
|
+
};
|
|
1607
1681
|
}
|
|
1608
1682
|
async function createFullPersonalSnapshot(options, legacyLinks) {
|
|
1609
1683
|
const capabilities = options.personal?.capabilities ?? await (0, import_devtools_core3.createDefaultCopilotHostCapabilities)();
|
|
@@ -1662,20 +1736,29 @@ async function createFullPersonalSnapshot(options, legacyLinks) {
|
|
|
1662
1736
|
}
|
|
1663
1737
|
function fallbackEntries(plugin, packageId) {
|
|
1664
1738
|
const kinds = Object.keys(plugin.artifacts);
|
|
1665
|
-
return kinds.filter((kind) => plugin.artifacts[kind] !== false).map((kind) =>
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1739
|
+
return kinds.filter((kind) => plugin.artifacts[kind] !== false).map((kind) => {
|
|
1740
|
+
const repoId = packageId.split("::")[0] ?? packageId;
|
|
1741
|
+
const identity = (0, import_devtools_core3.buildContentIdentity)({
|
|
1742
|
+
repoId,
|
|
1743
|
+
pluginId: plugin.id,
|
|
1744
|
+
kind,
|
|
1745
|
+
name: plugin.id
|
|
1746
|
+
});
|
|
1747
|
+
return {
|
|
1748
|
+
identity,
|
|
1749
|
+
artifactId: identity,
|
|
1750
|
+
packageId,
|
|
1751
|
+
packageDisplayName: plugin.id,
|
|
1752
|
+
repositoryId: repoId,
|
|
1753
|
+
pluginId: plugin.id,
|
|
1754
|
+
kind,
|
|
1755
|
+
sourcePath: "",
|
|
1756
|
+
sourceIsFile: false,
|
|
1757
|
+
name: plugin.id,
|
|
1758
|
+
digest: "",
|
|
1759
|
+
requiresApproval: kind === "hook" || kind === "mcp"
|
|
1760
|
+
};
|
|
1761
|
+
});
|
|
1679
1762
|
}
|
|
1680
1763
|
function toArtifactState(entry, previous) {
|
|
1681
1764
|
if (!previous) {
|
|
@@ -2049,12 +2132,12 @@ var BridgeServer = class {
|
|
|
2049
2132
|
input: process.stdin,
|
|
2050
2133
|
crlfDelay: Number.POSITIVE_INFINITY
|
|
2051
2134
|
});
|
|
2052
|
-
await new Promise((
|
|
2135
|
+
await new Promise((resolve9) => {
|
|
2053
2136
|
reader.on("line", (line) => {
|
|
2054
2137
|
void this.handleLine(line);
|
|
2055
2138
|
});
|
|
2056
2139
|
reader.on("close", () => {
|
|
2057
|
-
|
|
2140
|
+
resolve9();
|
|
2058
2141
|
});
|
|
2059
2142
|
});
|
|
2060
2143
|
}
|
|
@@ -2193,6 +2276,20 @@ var BridgeServer = class {
|
|
|
2193
2276
|
this.writeSuccess(request.id, result);
|
|
2194
2277
|
return;
|
|
2195
2278
|
}
|
|
2279
|
+
case "copilotContent.detectUnmanaged": {
|
|
2280
|
+
const r = request;
|
|
2281
|
+
if (!this.skillRepoHandler) return this.unsupportedV2(request.id);
|
|
2282
|
+
const result = await this.skillRepoHandler.detectUnmanaged(r.params);
|
|
2283
|
+
this.writeSuccess(request.id, result);
|
|
2284
|
+
return;
|
|
2285
|
+
}
|
|
2286
|
+
case "copilotContent.adoptUnmanaged": {
|
|
2287
|
+
const r = request;
|
|
2288
|
+
if (!this.skillRepoHandler) return this.unsupportedV2(request.id);
|
|
2289
|
+
const result = await this.skillRepoHandler.adoptUnmanaged(r.params);
|
|
2290
|
+
this.writeSuccess(request.id, result);
|
|
2291
|
+
return;
|
|
2292
|
+
}
|
|
2196
2293
|
case "copilotContent.listLinked":
|
|
2197
2294
|
case "skillRepo.listLinked": {
|
|
2198
2295
|
const r = request;
|
|
@@ -2603,9 +2700,22 @@ var SkillRepoBridgeHandler = class {
|
|
|
2603
2700
|
marks = await new import_devtools_core6.DisabledContentStore().list();
|
|
2604
2701
|
} catch {
|
|
2605
2702
|
}
|
|
2606
|
-
const
|
|
2607
|
-
|
|
2608
|
-
|
|
2703
|
+
const declaredDisabled = /* @__PURE__ */ new Set();
|
|
2704
|
+
if (params.workspaceDir) {
|
|
2705
|
+
const manifest = await (0, import_devtools_core6.loadWorkspaceCopilotManifest)(params.workspaceDir).catch(
|
|
2706
|
+
() => void 0
|
|
2707
|
+
);
|
|
2708
|
+
if (manifest) {
|
|
2709
|
+
for (const plugin of manifest.plugins) {
|
|
2710
|
+
for (const target of plugin.disabledArtifacts ?? []) {
|
|
2711
|
+
declaredDisabled.add(
|
|
2712
|
+
`${(0, import_devtools_core6.getWorkspaceManifestPluginSourceId)(manifest, plugin)}|${target}`
|
|
2713
|
+
);
|
|
2714
|
+
}
|
|
2715
|
+
}
|
|
2716
|
+
}
|
|
2717
|
+
}
|
|
2718
|
+
const isDisabled = (entry) => marks.some((mark) => disabledMarkApplies(mark, entry, params.workspaceDir)) || declaredDisabled.has(`${entry.repoId}|${entry.kind}:${entry.name}`);
|
|
2609
2719
|
return {
|
|
2610
2720
|
entries: filtered.map((entry) => ({
|
|
2611
2721
|
...toBridgeEntry(entry),
|
|
@@ -2647,10 +2757,22 @@ var SkillRepoBridgeHandler = class {
|
|
|
2647
2757
|
async install(params) {
|
|
2648
2758
|
const kind = params.kind ?? "skill";
|
|
2649
2759
|
const source = await this.resolveEntrySource(params.repoId, params.name, kind);
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2760
|
+
const scope = (params.scope ?? "workspace") === "workspace" ? "workspace" : "user";
|
|
2761
|
+
const store = new import_devtools_core6.DisabledContentStore();
|
|
2762
|
+
await store.removeForArtifact({
|
|
2763
|
+
repoId: params.repoId,
|
|
2764
|
+
name: params.name,
|
|
2765
|
+
kind,
|
|
2766
|
+
scope: "user"
|
|
2767
|
+
});
|
|
2768
|
+
await store.removeForArtifact({
|
|
2769
|
+
repoId: params.repoId,
|
|
2770
|
+
name: params.name,
|
|
2771
|
+
kind,
|
|
2772
|
+
scope,
|
|
2773
|
+
...scope === "workspace" ? { workspaceDir: params.workspaceDir } : {}
|
|
2774
|
+
});
|
|
2775
|
+
return scope === "workspace" ? this.installThroughDeclaration(params, kind, source) : this.installUserScope(params, kind, source);
|
|
2654
2776
|
}
|
|
2655
2777
|
/**
|
|
2656
2778
|
* Workspace installs go through the shared declaration: pin the
|
|
@@ -2672,15 +2794,31 @@ var SkillRepoBridgeHandler = class {
|
|
|
2672
2794
|
`Cannot pin repository ${params.repoId}: git returned an invalid commit '${commit}'`
|
|
2673
2795
|
);
|
|
2674
2796
|
}
|
|
2797
|
+
const coveringPlugin = await (0, import_devtools_core6.findPluginCoveringArtifact)(repoRoot, kind, params.name);
|
|
2675
2798
|
await (0, import_devtools_core6.upsertWorkspaceContentSelection)({
|
|
2676
2799
|
workspaceDir: params.workspaceDir,
|
|
2677
2800
|
repository: { id: repo.id, url: repo.url, commit },
|
|
2678
|
-
pluginId: params.name,
|
|
2801
|
+
pluginId: coveringPlugin ?? params.name,
|
|
2679
2802
|
kind
|
|
2680
2803
|
});
|
|
2804
|
+
await this.setDeclaredArtifactDisabled(params, kind, false);
|
|
2805
|
+
if (coveringPlugin && coveringPlugin !== params.name) {
|
|
2806
|
+
await (0, import_devtools_core6.removeWorkspaceContentSelection)({
|
|
2807
|
+
workspaceDir: params.workspaceDir,
|
|
2808
|
+
repositoryId: params.repoId,
|
|
2809
|
+
pluginId: params.name,
|
|
2810
|
+
kind
|
|
2811
|
+
});
|
|
2812
|
+
}
|
|
2681
2813
|
const restored = await this.reconcileInstalledContent(params.workspaceDir);
|
|
2682
2814
|
const linkBasename = source.sourceIsFile ? path5.basename(source.sourcePath) : params.name;
|
|
2683
|
-
const
|
|
2815
|
+
const declaringPlugin = coveringPlugin ?? params.name;
|
|
2816
|
+
const expectedIdentity = (0, import_devtools_core6.buildContentIdentity)({
|
|
2817
|
+
repoId: params.repoId,
|
|
2818
|
+
pluginId: declaringPlugin,
|
|
2819
|
+
kind,
|
|
2820
|
+
name: stripAgentSuffix(linkBasename)
|
|
2821
|
+
});
|
|
2684
2822
|
const entry = restored.entries.find((candidate) => candidate.identity === expectedIdentity);
|
|
2685
2823
|
if (!entry || entry.status !== "restored" && entry.status !== "adopted") {
|
|
2686
2824
|
throw createServicemeError(
|
|
@@ -2784,12 +2922,26 @@ var SkillRepoBridgeHandler = class {
|
|
|
2784
2922
|
async uninstall(params) {
|
|
2785
2923
|
const kind = params.kind ?? "skill";
|
|
2786
2924
|
if ((params.scope ?? "workspace") === "workspace") {
|
|
2925
|
+
const coveringPlugin = await (0, import_devtools_core6.findPluginCoveringArtifact)(
|
|
2926
|
+
(0, import_devtools_core6.getRepoDir)(params.repoId),
|
|
2927
|
+
kind,
|
|
2928
|
+
params.name
|
|
2929
|
+
);
|
|
2787
2930
|
await (0, import_devtools_core6.removeWorkspaceContentSelection)({
|
|
2788
2931
|
workspaceDir: params.workspaceDir,
|
|
2789
2932
|
repositoryId: params.repoId,
|
|
2790
|
-
pluginId: params.name,
|
|
2933
|
+
pluginId: coveringPlugin ?? params.name,
|
|
2791
2934
|
kind
|
|
2792
2935
|
});
|
|
2936
|
+
if (coveringPlugin) {
|
|
2937
|
+
await (0, import_devtools_core6.removeWorkspaceContentSelection)({
|
|
2938
|
+
workspaceDir: params.workspaceDir,
|
|
2939
|
+
repositoryId: params.repoId,
|
|
2940
|
+
pluginId: params.name,
|
|
2941
|
+
kind
|
|
2942
|
+
});
|
|
2943
|
+
}
|
|
2944
|
+
await this.setDeclaredArtifactDisabled(params, kind, false);
|
|
2793
2945
|
await this.reconcileInstalledContent(params.workspaceDir);
|
|
2794
2946
|
}
|
|
2795
2947
|
try {
|
|
@@ -2809,14 +2961,22 @@ var SkillRepoBridgeHandler = class {
|
|
|
2809
2961
|
}
|
|
2810
2962
|
throw err;
|
|
2811
2963
|
}
|
|
2964
|
+
await new import_devtools_core6.DisabledContentStore().removeForArtifact({
|
|
2965
|
+
repoId: params.repoId,
|
|
2966
|
+
name: params.name,
|
|
2967
|
+
kind,
|
|
2968
|
+
scope: (params.scope ?? "workspace") === "workspace" ? "workspace" : "user",
|
|
2969
|
+
...params.workspaceDir ? { workspaceDir: params.workspaceDir } : {}
|
|
2970
|
+
});
|
|
2812
2971
|
return { removed: true };
|
|
2813
2972
|
}
|
|
2814
2973
|
/**
|
|
2815
2974
|
* Enable/disable a per-artifact skill/agent WITHOUT dropping its
|
|
2816
|
-
* installation. Disable records
|
|
2817
|
-
*
|
|
2818
|
-
*
|
|
2819
|
-
*
|
|
2975
|
+
* installation. Disable records the target in the shared declaration
|
|
2976
|
+
* (`disabledArtifacts` — survives reloads and machine state loss) plus
|
|
2977
|
+
* a machine-local mark, and removes the materialized link; the
|
|
2978
|
+
* reconciler treats both as undeclared, so a restore never
|
|
2979
|
+
* resurrects them. Enable clears both and re-links.
|
|
2820
2980
|
*/
|
|
2821
2981
|
async setEntryEnabled(params) {
|
|
2822
2982
|
const kind = params.kind ?? "skill";
|
|
@@ -2833,8 +2993,10 @@ var SkillRepoBridgeHandler = class {
|
|
|
2833
2993
|
...scope === "workspace" && params.workspaceDir ? { workspaceDir: params.workspaceDir } : {}
|
|
2834
2994
|
};
|
|
2835
2995
|
if (params.enabled) {
|
|
2996
|
+
await store.remove({ ...mark, scope: "user" });
|
|
2836
2997
|
await store.remove(mark);
|
|
2837
2998
|
if (scope === "workspace") {
|
|
2999
|
+
await this.setDeclaredArtifactDisabled(params, kind, false);
|
|
2838
3000
|
await this.reconcileInstalledContent(params.workspaceDir);
|
|
2839
3001
|
} else {
|
|
2840
3002
|
const source = await this.resolveEntrySource(params.repoId, params.name, kind);
|
|
@@ -2846,21 +3008,9 @@ var SkillRepoBridgeHandler = class {
|
|
|
2846
3008
|
}
|
|
2847
3009
|
return { enabled: true };
|
|
2848
3010
|
}
|
|
2849
|
-
await store.add(mark);
|
|
2850
3011
|
if (scope === "workspace") {
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
});
|
|
2854
|
-
const state = await stateStore.read();
|
|
2855
|
-
for (const previous of state.entries) {
|
|
2856
|
-
if (previous.repositoryId !== params.repoId || previous.pluginId !== params.name || previous.kind !== kind || !previous.linkPath.replace(/\\/g, "/").includes(".github")) {
|
|
2857
|
-
continue;
|
|
2858
|
-
}
|
|
2859
|
-
await fs3.rm(path5.resolve(params.workspaceDir, previous.linkPath), {
|
|
2860
|
-
force: true,
|
|
2861
|
-
recursive: true
|
|
2862
|
-
});
|
|
2863
|
-
}
|
|
3012
|
+
await store.add(mark);
|
|
3013
|
+
await this.setDeclaredArtifactDisabled(params, kind, true);
|
|
2864
3014
|
await this.reconcileInstalledContent(params.workspaceDir);
|
|
2865
3015
|
} else {
|
|
2866
3016
|
let base = params.name;
|
|
@@ -2869,8 +3019,18 @@ var SkillRepoBridgeHandler = class {
|
|
|
2869
3019
|
base = source.sourceIsFile ? path5.basename(source.sourcePath) : params.name;
|
|
2870
3020
|
} catch {
|
|
2871
3021
|
}
|
|
2872
|
-
const userRoot = path5.
|
|
2873
|
-
|
|
3022
|
+
const userRoot = path5.resolve(
|
|
3023
|
+
path5.join((0, import_devtools_core6.getHomeDir)(), ".copilot", kind === "agent" ? "agents" : "skills")
|
|
3024
|
+
);
|
|
3025
|
+
const resolvedUserTarget = path5.resolve(path5.join(userRoot, base));
|
|
3026
|
+
if (!resolvedUserTarget.startsWith(userRoot + path5.sep)) {
|
|
3027
|
+
throw createServicemeError(
|
|
3028
|
+
"invalid_params",
|
|
3029
|
+
`User ${kind} link escapes the user root: ${params.name}`
|
|
3030
|
+
);
|
|
3031
|
+
}
|
|
3032
|
+
await store.add(mark);
|
|
3033
|
+
await fs3.rm(resolvedUserTarget, { force: true, recursive: true });
|
|
2874
3034
|
}
|
|
2875
3035
|
return { enabled: false };
|
|
2876
3036
|
}
|
|
@@ -2896,6 +3056,136 @@ var SkillRepoBridgeHandler = class {
|
|
|
2896
3056
|
}))
|
|
2897
3057
|
};
|
|
2898
3058
|
}
|
|
3059
|
+
/**
|
|
3060
|
+
* `copilotContent.detectUnmanaged` — read-only scan of a workspace
|
|
3061
|
+
* that carries Copilot content without a declaration. Provenance
|
|
3062
|
+
* rules live in the core detector (symlink targets, fingerprints,
|
|
3063
|
+
* generated-config identity maps).
|
|
3064
|
+
*/
|
|
3065
|
+
async detectUnmanaged(params) {
|
|
3066
|
+
const catalog = await this.buildSkillStore().listAll();
|
|
3067
|
+
return (0, import_devtools_core6.detectUnmanagedWorkspaceContent)({
|
|
3068
|
+
workspaceDir: params.workspaceDir,
|
|
3069
|
+
reposDir: (0, import_devtools_core6.getReposDir)(),
|
|
3070
|
+
catalog: catalog.map((entry) => ({
|
|
3071
|
+
repoId: entry.repoId,
|
|
3072
|
+
name: entry.name,
|
|
3073
|
+
kind: entry.kind,
|
|
3074
|
+
dir: entry.dir,
|
|
3075
|
+
manifestPath: entry.manifestPath
|
|
3076
|
+
})),
|
|
3077
|
+
knownRepoIds: new Set(
|
|
3078
|
+
this.reposStore.list().filter((repo) => repo.enabled).map((repo) => repo.id)
|
|
3079
|
+
)
|
|
3080
|
+
});
|
|
3081
|
+
}
|
|
3082
|
+
/**
|
|
3083
|
+
* `copilotContent.adoptUnmanaged` — initialize the declaration from
|
|
3084
|
+
* detected content. Skill/agent entries reuse the install pipeline
|
|
3085
|
+
* (pin commit → upsert selection → reconcile; the materializer
|
|
3086
|
+
* adopts an already-matching link instead of rewriting it); real
|
|
3087
|
+
* copies are converted to symlinks first when asked. Hook/MCP
|
|
3088
|
+
* integrations are upserted by kind and surface through the normal
|
|
3089
|
+
* approval gate on reconcile. Per-entry failures are reported as
|
|
3090
|
+
* conflict statuses instead of aborting the batch.
|
|
3091
|
+
*/
|
|
3092
|
+
async adoptUnmanaged(params) {
|
|
3093
|
+
const failures = [];
|
|
3094
|
+
const pluginManifests = /* @__PURE__ */ new Map();
|
|
3095
|
+
for (const entry of params.entries) {
|
|
3096
|
+
const key = `${entry.repoId}::${entry.name}`;
|
|
3097
|
+
if (pluginManifests.has(key)) continue;
|
|
3098
|
+
try {
|
|
3099
|
+
const manifestPath = path5.join(
|
|
3100
|
+
(0, import_devtools_core6.getRepoDir)(entry.repoId),
|
|
3101
|
+
"plugins",
|
|
3102
|
+
entry.name,
|
|
3103
|
+
"plugin.json"
|
|
3104
|
+
);
|
|
3105
|
+
const raw = await fs3.readFile(manifestPath, "utf8");
|
|
3106
|
+
pluginManifests.set(key, JSON.parse(raw));
|
|
3107
|
+
} catch {
|
|
3108
|
+
}
|
|
3109
|
+
}
|
|
3110
|
+
const { kept, dropped } = (0, import_devtools_core6.dedupeAdoptionsByPluginManifests)(params.entries, pluginManifests);
|
|
3111
|
+
for (const { entry, coveredBy } of dropped) {
|
|
3112
|
+
failures.push({
|
|
3113
|
+
identity: (0, import_devtools_core6.buildContentIdentity)({
|
|
3114
|
+
repoId: entry.repoId,
|
|
3115
|
+
pluginId: entry.name,
|
|
3116
|
+
kind: entry.kind,
|
|
3117
|
+
name: entry.name
|
|
3118
|
+
}),
|
|
3119
|
+
status: "restored",
|
|
3120
|
+
message: `Covered by the plugin ${coveredBy} selection`
|
|
3121
|
+
});
|
|
3122
|
+
}
|
|
3123
|
+
for (const entry of kept) {
|
|
3124
|
+
try {
|
|
3125
|
+
if (entry.convertRealCopy === true) {
|
|
3126
|
+
await this.convertToSymlink({
|
|
3127
|
+
repoId: entry.repoId,
|
|
3128
|
+
name: entry.name,
|
|
3129
|
+
workspaceDir: params.workspaceDir,
|
|
3130
|
+
kind: entry.kind,
|
|
3131
|
+
scope: "workspace"
|
|
3132
|
+
});
|
|
3133
|
+
}
|
|
3134
|
+
await this.install({
|
|
3135
|
+
repoId: entry.repoId,
|
|
3136
|
+
name: entry.name,
|
|
3137
|
+
workspaceDir: params.workspaceDir,
|
|
3138
|
+
kind: entry.kind,
|
|
3139
|
+
scope: "workspace"
|
|
3140
|
+
});
|
|
3141
|
+
} catch (error) {
|
|
3142
|
+
failures.push({
|
|
3143
|
+
identity: (0, import_devtools_core6.buildContentIdentity)({
|
|
3144
|
+
repoId: entry.repoId,
|
|
3145
|
+
pluginId: entry.name,
|
|
3146
|
+
kind: entry.kind,
|
|
3147
|
+
name: entry.name
|
|
3148
|
+
}),
|
|
3149
|
+
status: "conflict",
|
|
3150
|
+
message: error instanceof Error ? error.message : String(error)
|
|
3151
|
+
});
|
|
3152
|
+
}
|
|
3153
|
+
}
|
|
3154
|
+
for (const integration of params.integrations ?? []) {
|
|
3155
|
+
try {
|
|
3156
|
+
const repo = this.reposStore.get(integration.repoId);
|
|
3157
|
+
if (!repo) {
|
|
3158
|
+
throw new Error(`Unknown repository ${integration.repoId}`);
|
|
3159
|
+
}
|
|
3160
|
+
const commit = await this.gitClient.revParseHead((0, import_devtools_core6.getRepoDir)(integration.repoId));
|
|
3161
|
+
if (!/^[0-9a-f]{40}$/.test(commit)) {
|
|
3162
|
+
throw new Error(`Cannot pin ${integration.repoId}: invalid commit '${commit}'`);
|
|
3163
|
+
}
|
|
3164
|
+
await (0, import_devtools_core6.upsertWorkspaceContentSelection)({
|
|
3165
|
+
workspaceDir: params.workspaceDir,
|
|
3166
|
+
repository: { id: repo.id, url: repo.url, commit },
|
|
3167
|
+
pluginId: integration.pluginId,
|
|
3168
|
+
kind: integration.kind
|
|
3169
|
+
});
|
|
3170
|
+
} catch (error) {
|
|
3171
|
+
failures.push({
|
|
3172
|
+
identity: (0, import_devtools_core6.buildContentIdentity)({
|
|
3173
|
+
repoId: integration.repoId,
|
|
3174
|
+
pluginId: integration.pluginId,
|
|
3175
|
+
kind: integration.kind,
|
|
3176
|
+
name: integration.pluginId
|
|
3177
|
+
}),
|
|
3178
|
+
status: "conflict",
|
|
3179
|
+
message: error instanceof Error ? error.message : String(error)
|
|
3180
|
+
});
|
|
3181
|
+
}
|
|
3182
|
+
}
|
|
3183
|
+
const restored = await this.reconcileInstalledContent(params.workspaceDir);
|
|
3184
|
+
return {
|
|
3185
|
+
changed: restored.changed,
|
|
3186
|
+
entries: [...restored.entries, ...failures]
|
|
3187
|
+
};
|
|
3188
|
+
}
|
|
2899
3189
|
// ─────────────────────────────────────────────────────────────────
|
|
2900
3190
|
// skillRepo.draft.*
|
|
2901
3191
|
// ─────────────────────────────────────────────────────────────────
|
|
@@ -3150,6 +3440,29 @@ var SkillRepoBridgeHandler = class {
|
|
|
3150
3440
|
invalidateSkillStoreCache() {
|
|
3151
3441
|
this.skillStoreCache = void 0;
|
|
3152
3442
|
}
|
|
3443
|
+
/**
|
|
3444
|
+
* Mirror an enable/disable toggle into the shared declaration's
|
|
3445
|
+
* `disabledArtifacts` field (same covering-plugin rule the install
|
|
3446
|
+
* pipeline uses). Best-effort for the REPO-side lookups: a missing
|
|
3447
|
+
* repo/checkout must not fail the toggle — the machine-local mark
|
|
3448
|
+
* still applies, and the declaration converges on the next install.
|
|
3449
|
+
*/
|
|
3450
|
+
async setDeclaredArtifactDisabled(params, kind, disabled) {
|
|
3451
|
+
const { workspaceDir } = params;
|
|
3452
|
+
if (!workspaceDir) return;
|
|
3453
|
+
let pluginId = params.name;
|
|
3454
|
+
try {
|
|
3455
|
+
pluginId = await (0, import_devtools_core6.findPluginCoveringArtifact)((0, import_devtools_core6.getRepoDir)(params.repoId), kind, params.name) ?? params.name;
|
|
3456
|
+
} catch {
|
|
3457
|
+
}
|
|
3458
|
+
await (0, import_devtools_core6.setWorkspacePluginArtifactsDisabled)({
|
|
3459
|
+
workspaceDir,
|
|
3460
|
+
repositoryId: params.repoId,
|
|
3461
|
+
pluginId,
|
|
3462
|
+
target: `${kind}:${params.name}`,
|
|
3463
|
+
disabled
|
|
3464
|
+
}).catch(() => void 0);
|
|
3465
|
+
}
|
|
3153
3466
|
/**
|
|
3154
3467
|
* Resolve an entry's actual on-disk location via `SkillStore` for
|
|
3155
3468
|
* `install`/`convertToSymlink`. Repos vary wildly in layout — flat
|
|
@@ -3189,6 +3502,9 @@ var SkillRepoBridgeHandler = class {
|
|
|
3189
3502
|
function stripAgentSuffix(basename4) {
|
|
3190
3503
|
return basename4.replace(/\.agent\.md$/, "");
|
|
3191
3504
|
}
|
|
3505
|
+
function disabledMarkApplies(mark, entry, workspaceDir) {
|
|
3506
|
+
return mark.repoId === entry.repoId && mark.name === entry.name && mark.kind === entry.kind && (mark.scope === "user" || workspaceDir !== void 0 && mark.workspaceDir === workspaceDir);
|
|
3507
|
+
}
|
|
3192
3508
|
function toBridgeEntry(e) {
|
|
3193
3509
|
return {
|
|
3194
3510
|
repoId: e.repoId,
|
|
@@ -3421,9 +3737,20 @@ var import_devtools_core11 = require("@serviceme/devtools-core");
|
|
|
3421
3737
|
|
|
3422
3738
|
// src/input.ts
|
|
3423
3739
|
var fs4 = __toESM(require("fs/promises"));
|
|
3740
|
+
var path6 = __toESM(require("path"));
|
|
3741
|
+
function sanitizeCliFilePath(value) {
|
|
3742
|
+
const hasControlCharacter = [...value].some((character) => {
|
|
3743
|
+
const code = character.charCodeAt(0);
|
|
3744
|
+
return code > 0 && code < 32;
|
|
3745
|
+
});
|
|
3746
|
+
if (hasControlCharacter) {
|
|
3747
|
+
throw new Error("Invalid --file path: control characters are not allowed.");
|
|
3748
|
+
}
|
|
3749
|
+
return path6.resolve(value);
|
|
3750
|
+
}
|
|
3424
3751
|
async function readCommandInput(options) {
|
|
3425
3752
|
if (options.filePath) {
|
|
3426
|
-
return fs4.readFile(options.filePath, "utf8");
|
|
3753
|
+
return fs4.readFile(sanitizeCliFilePath(options.filePath), "utf8");
|
|
3427
3754
|
}
|
|
3428
3755
|
if (options.stdin) {
|
|
3429
3756
|
return readStdin();
|
|
@@ -3701,7 +4028,7 @@ async function runReposCommand(parsed) {
|
|
|
3701
4028
|
|
|
3702
4029
|
// src/commands/schedule.ts
|
|
3703
4030
|
var fs5 = __toESM(require("fs"));
|
|
3704
|
-
var
|
|
4031
|
+
var path7 = __toESM(require("path"));
|
|
3705
4032
|
var import_devtools_core14 = require("@serviceme/devtools-core");
|
|
3706
4033
|
async function runScheduleCommand(parsed) {
|
|
3707
4034
|
const action = parsed.positionals[1];
|
|
@@ -3756,6 +4083,9 @@ function requireId(parsed) {
|
|
|
3756
4083
|
if (!id) {
|
|
3757
4084
|
throw createServicemeError("invalid_params", "Missing required flag: --id <task-id>");
|
|
3758
4085
|
}
|
|
4086
|
+
if (!/^[A-Za-z0-9._-]+$/.test(id)) {
|
|
4087
|
+
throw createServicemeError("invalid_params", "Invalid --id: must match [A-Za-z0-9._-]");
|
|
4088
|
+
}
|
|
3759
4089
|
return id;
|
|
3760
4090
|
}
|
|
3761
4091
|
function requireConfirmation(parsed) {
|
|
@@ -3786,6 +4116,16 @@ function parsePayload(parsed, taskType) {
|
|
|
3786
4116
|
"invalid_payload",
|
|
3787
4117
|
"Missing --script for shell task (or use --payload-json)"
|
|
3788
4118
|
);
|
|
4119
|
+
const hasControlCharacter = [...script].some((character) => {
|
|
4120
|
+
const code = character.charCodeAt(0);
|
|
4121
|
+
return code > 0 && code < 32 && character !== " ";
|
|
4122
|
+
});
|
|
4123
|
+
if (hasControlCharacter) {
|
|
4124
|
+
throw createServicemeError(
|
|
4125
|
+
"invalid_payload",
|
|
4126
|
+
"Invalid --script: control characters are not allowed"
|
|
4127
|
+
);
|
|
4128
|
+
}
|
|
3789
4129
|
return {
|
|
3790
4130
|
script,
|
|
3791
4131
|
cwd: getStringFlag(parsed, "cwd"),
|
|
@@ -3902,7 +4242,7 @@ async function handleCreate(parsed) {
|
|
|
3902
4242
|
payload,
|
|
3903
4243
|
workspace: {
|
|
3904
4244
|
path: wp,
|
|
3905
|
-
name:
|
|
4245
|
+
name: path7.basename(wp) || wp
|
|
3906
4246
|
},
|
|
3907
4247
|
enabled
|
|
3908
4248
|
};
|
|
@@ -4237,7 +4577,7 @@ function writeDescribe(action) {
|
|
|
4237
4577
|
// src/commands/scheduler.ts
|
|
4238
4578
|
var import_node_child_process = require("child_process");
|
|
4239
4579
|
var fs6 = __toESM(require("fs"));
|
|
4240
|
-
var
|
|
4580
|
+
var path8 = __toESM(require("path"));
|
|
4241
4581
|
var import_devtools_core15 = require("@serviceme/devtools-core");
|
|
4242
4582
|
var REPO_SYNC_INTERVAL_MS = 5 * 60 * 1e3;
|
|
4243
4583
|
async function runSchedulerCommand(parsed) {
|
|
@@ -4325,12 +4665,16 @@ function handleStart(_parsed) {
|
|
|
4325
4665
|
throw createServicemeError("internal_error", "Cannot determine CLI path for daemon spawn");
|
|
4326
4666
|
}
|
|
4327
4667
|
const logPath = (0, import_devtools_core15.getSchedulerLogPath)();
|
|
4328
|
-
const
|
|
4668
|
+
const homeRoot = path8.resolve((0, import_devtools_core15.getServicemeHome)());
|
|
4669
|
+
if (!path8.resolve(logPath).startsWith(homeRoot + path8.sep)) {
|
|
4670
|
+
throw createServicemeError("internal_error", "Scheduler log path escapes the SERVICEME home");
|
|
4671
|
+
}
|
|
4672
|
+
const logDir = path8.dirname(logPath);
|
|
4329
4673
|
if (!fs6.existsSync(logDir)) {
|
|
4330
4674
|
fs6.mkdirSync(logDir, { recursive: true });
|
|
4331
4675
|
}
|
|
4332
4676
|
const spawnCmd = process.execPath;
|
|
4333
|
-
const spawnArgs = [cliPath, "scheduler", "__daemon",
|
|
4677
|
+
const spawnArgs = [cliPath, "scheduler", "__daemon", `--logPath=${logPath}`];
|
|
4334
4678
|
if (process.platform === "win32") {
|
|
4335
4679
|
fs6.appendFileSync(
|
|
4336
4680
|
logPath,
|
|
@@ -4463,6 +4807,10 @@ function handleLogs2(parsed) {
|
|
|
4463
4807
|
async function runDaemon(parsed) {
|
|
4464
4808
|
const logPath = getStringFlag(parsed, "logPath");
|
|
4465
4809
|
if (logPath) {
|
|
4810
|
+
const homeRoot = path8.resolve((0, import_devtools_core15.getServicemeHome)());
|
|
4811
|
+
if (!path8.resolve(logPath).startsWith(homeRoot + path8.sep)) {
|
|
4812
|
+
throw createServicemeError("invalid_params", "--logPath must stay inside the SERVICEME home");
|
|
4813
|
+
}
|
|
4466
4814
|
process.env.SERVICEME_SCHEDULER_LOG_PATH = logPath;
|
|
4467
4815
|
}
|
|
4468
4816
|
const pidMgr = new import_devtools_core15.PidManager("", { pidPath: (0, import_devtools_core15.getSchedulerPidPath)() });
|
|
@@ -4566,7 +4914,7 @@ function writeDescribe2(action) {
|
|
|
4566
4914
|
// src/commands/skill.ts
|
|
4567
4915
|
var fs7 = __toESM(require("fs/promises"));
|
|
4568
4916
|
var os2 = __toESM(require("os"));
|
|
4569
|
-
var
|
|
4917
|
+
var path9 = __toESM(require("path"));
|
|
4570
4918
|
var import_devtools_core16 = require("@serviceme/devtools-core");
|
|
4571
4919
|
function normalizeSkillIdOrThrow(store, remoteId) {
|
|
4572
4920
|
try {
|
|
@@ -4583,7 +4931,7 @@ async function runSkillCommand(parsed) {
|
|
|
4583
4931
|
}
|
|
4584
4932
|
const store = new import_devtools_core16.SkillStore({
|
|
4585
4933
|
workspacePath,
|
|
4586
|
-
userSkillsRoot:
|
|
4934
|
+
userSkillsRoot: path9.join(os2.homedir(), ".copilot", "skills")
|
|
4587
4935
|
});
|
|
4588
4936
|
const catalogClient = new import_devtools_core16.SkillCatalogClient({
|
|
4589
4937
|
baseUrl: getStringFlag(parsed, "baseUrl")
|
|
@@ -4697,11 +5045,24 @@ async function handleUninstall2(store, workspacePath, parsed) {
|
|
|
4697
5045
|
throw createServicemeError("invalid_params", "Expected --id <remoteSkillId>.");
|
|
4698
5046
|
}
|
|
4699
5047
|
const skillId = normalizeSkillIdOrThrow(store, remoteId);
|
|
4700
|
-
|
|
5048
|
+
const workspaceRoot = path9.resolve(workspacePath);
|
|
5049
|
+
const workspaceLinkPath = path9.resolve(workspaceRoot, store.getWorkspaceSkillPath(skillId));
|
|
5050
|
+
if (!workspaceLinkPath.startsWith(workspaceRoot + path9.sep)) {
|
|
5051
|
+
throw createServicemeError("invalid_params", `Skill link escapes the workspace: ${skillId}`);
|
|
5052
|
+
}
|
|
5053
|
+
await fs7.rm(workspaceLinkPath, {
|
|
4701
5054
|
recursive: true,
|
|
4702
5055
|
force: true
|
|
4703
5056
|
});
|
|
4704
|
-
|
|
5057
|
+
const userSkillsRoot = path9.resolve(path9.join(os2.homedir(), ".copilot", "skills"));
|
|
5058
|
+
const userSkillPath = path9.resolve(store.getUserSkillPath(skillId));
|
|
5059
|
+
if (!userSkillPath.startsWith(userSkillsRoot + path9.sep)) {
|
|
5060
|
+
throw createServicemeError(
|
|
5061
|
+
"invalid_params",
|
|
5062
|
+
`User skill link escapes the skills root: ${skillId}`
|
|
5063
|
+
);
|
|
5064
|
+
}
|
|
5065
|
+
await fs7.rm(userSkillPath, {
|
|
4705
5066
|
recursive: true,
|
|
4706
5067
|
force: true
|
|
4707
5068
|
});
|
|
@@ -4723,7 +5084,7 @@ async function handleMove2(store, workspacePath, parsed) {
|
|
|
4723
5084
|
throw createServicemeError("invalid_params", "Expected --to value to be workspace or user.");
|
|
4724
5085
|
}
|
|
4725
5086
|
const skillId = normalizeSkillIdOrThrow(store, remoteId);
|
|
4726
|
-
const workspacePathForSkill =
|
|
5087
|
+
const workspacePathForSkill = path9.join(workspacePath, store.getWorkspaceSkillPath(skillId));
|
|
4727
5088
|
const userPathForSkill = store.getUserSkillPath(skillId);
|
|
4728
5089
|
if (to === "user") {
|
|
4729
5090
|
await moveDirectory(workspacePathForSkill, userPathForSkill);
|
|
@@ -4744,7 +5105,7 @@ async function handleMove2(store, workspacePath, parsed) {
|
|
|
4744
5105
|
};
|
|
4745
5106
|
}
|
|
4746
5107
|
async function moveDirectory(fromPath, toPath) {
|
|
4747
|
-
await fs7.mkdir(
|
|
5108
|
+
await fs7.mkdir(path9.dirname(toPath), { recursive: true });
|
|
4748
5109
|
try {
|
|
4749
5110
|
await fs7.rename(fromPath, toPath);
|
|
4750
5111
|
} catch {
|
|
@@ -4850,14 +5211,14 @@ async function handlePublishable(store, workspacePath) {
|
|
|
4850
5211
|
skills: workspaceSkillIds.map((skillId) => ({
|
|
4851
5212
|
id: skillId,
|
|
4852
5213
|
displayName: skillId,
|
|
4853
|
-
path:
|
|
5214
|
+
path: path9.join(workspacePath, store.getWorkspaceSkillPath(skillId))
|
|
4854
5215
|
}))
|
|
4855
5216
|
};
|
|
4856
5217
|
}
|
|
4857
5218
|
|
|
4858
5219
|
// src/commands/skills.ts
|
|
4859
5220
|
var fs8 = __toESM(require("fs/promises"));
|
|
4860
|
-
var
|
|
5221
|
+
var path10 = __toESM(require("path"));
|
|
4861
5222
|
var import_devtools_core17 = require("@serviceme/devtools-core");
|
|
4862
5223
|
function requireFlag(parsed, name) {
|
|
4863
5224
|
const value = getStringFlag(parsed, name);
|
|
@@ -4929,12 +5290,14 @@ async function readDraftFilesFromDir(dirPath) {
|
|
|
4929
5290
|
}
|
|
4930
5291
|
const files = [];
|
|
4931
5292
|
let manifestFound = false;
|
|
5293
|
+
const dirRoot = path10.resolve(dirPath);
|
|
4932
5294
|
async function walk(currentAbs, currentRel) {
|
|
4933
5295
|
const entries = await fs8.readdir(currentAbs, { withFileTypes: true });
|
|
4934
5296
|
for (const entry of entries) {
|
|
4935
5297
|
if (entry.name.startsWith(".")) continue;
|
|
4936
5298
|
if (entry.name === "node_modules") continue;
|
|
4937
|
-
const childAbs =
|
|
5299
|
+
const childAbs = path10.resolve(currentAbs, entry.name);
|
|
5300
|
+
if (!childAbs.startsWith(dirRoot + path10.sep)) continue;
|
|
4938
5301
|
const childRel = currentRel ? `${currentRel}/${entry.name}` : entry.name;
|
|
4939
5302
|
if (entry.isSymbolicLink()) continue;
|
|
4940
5303
|
if (entry.isDirectory()) {
|
|
@@ -4949,7 +5312,7 @@ async function readDraftFilesFromDir(dirPath) {
|
|
|
4949
5312
|
files.push({ path: childRel, content });
|
|
4950
5313
|
}
|
|
4951
5314
|
}
|
|
4952
|
-
await walk(
|
|
5315
|
+
await walk(dirRoot, "");
|
|
4953
5316
|
if (!manifestFound) {
|
|
4954
5317
|
throw createServicemeError(
|
|
4955
5318
|
"invalid_params",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@serviceme/devtools-cli",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "Unified SERVICEME CLI for automation, project scaffolding, and bridge-based tooling.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
6
6
|
"repository": {
|
|
@@ -39,9 +39,9 @@
|
|
|
39
39
|
"registry": "https://registry.npmjs.org/"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@serviceme/devtools-core": "2.0.
|
|
43
|
-
"@serviceme/devtools-protocol": "2.0.
|
|
44
|
-
"@serviceme/devtools-shared": "2.0.
|
|
42
|
+
"@serviceme/devtools-core": "2.0.1",
|
|
43
|
+
"@serviceme/devtools-protocol": "2.0.1",
|
|
44
|
+
"@serviceme/devtools-shared": "2.0.1"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@types/node": "^24",
|