@serviceme/devtools-cli 1.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.
Files changed (3) hide show
  1. package/dist/bridgeServer.js +1318 -10
  2. package/dist/cli.js +2070 -230
  3. package/package.json +4 -4
@@ -40,7 +40,7 @@ var readline = __toESM(require("readline"));
40
40
 
41
41
  // ../../packages/serviceme-protocol/src/bridge.ts
42
42
  var SERVICEME_PROTOCOL_VERSION = 2;
43
- function isRecord(value) {
43
+ function isRecord2(value) {
44
44
  return typeof value === "object" && value !== null;
45
45
  }
46
46
  function isValidProtocolVersion(value) {
@@ -53,11 +53,25 @@ var BRIDGE_METHODS = [
53
53
  "task.execute",
54
54
  "task.cancel",
55
55
  "task.list-running",
56
+ "copilotContent.list",
57
+ "copilotContent.get",
58
+ "copilotContent.install",
59
+ "copilotContent.convertToSymlink",
60
+ "copilotContent.uninstall",
61
+ "copilotContent.setEntryEnabled",
62
+ "copilotContent.detectUnmanaged",
63
+ "copilotContent.adoptUnmanaged",
64
+ "copilotContent.listLinked",
65
+ "copilotContent.draft.create",
66
+ "copilotContent.draft.commit",
67
+ "copilotContent.draft.list",
68
+ "copilotContent.draft.delete",
56
69
  "skillRepo.list",
57
70
  "skillRepo.get",
58
71
  "skillRepo.install",
59
72
  "skillRepo.convertToSymlink",
60
73
  "skillRepo.uninstall",
74
+ "skillRepo.setEntryEnabled",
61
75
  "skillRepo.listLinked",
62
76
  "skillRepo.draft.create",
63
77
  "skillRepo.draft.commit",
@@ -71,6 +85,25 @@ var BRIDGE_METHODS = [
71
85
  "repo.update",
72
86
  "repo.sync",
73
87
  "repo.syncAll",
88
+ "repo.resetParseCache",
89
+ "copilotContent.status",
90
+ "copilotContent.restore",
91
+ "copilotContent.approve",
92
+ "copilotContent.migrateLegacy",
93
+ "copilotContent.integrationStatus",
94
+ "copilotContent.customizations.list",
95
+ "copilotContent.package.install",
96
+ "copilotContent.package.update",
97
+ "copilotContent.package.uninstall",
98
+ "copilotContent.package.move",
99
+ "copilotPlugin.list",
100
+ "copilotPlugin.register",
101
+ "copilotPlugin.unregister",
102
+ "copilotContent.legacy.preview",
103
+ "copilotContent.legacy.migrate",
104
+ "copilotContent.sources.list",
105
+ "copilotContent.sources.remove",
106
+ "copilotContent.package.previewUpdate",
74
107
  "auth.status",
75
108
  "auth.login",
76
109
  "auth.logout",
@@ -88,7 +121,7 @@ function isBridgeMethod(value) {
88
121
  return typeof value === "string" && BRIDGE_METHODS.includes(value);
89
122
  }
90
123
  function isBridgeRequest(value) {
91
- if (!isRecord(value)) {
124
+ if (!isRecord2(value)) {
92
125
  return false;
93
126
  }
94
127
  return isValidProtocolVersion(value.protocolVersion) && value.kind === "request" && typeof value.id === "string" && isBridgeMethod(value.method) && "params" in value;
@@ -140,14 +173,14 @@ var RETRYABLE_ERROR_CODES = /* @__PURE__ */ new Set([
140
173
  "executor_timeout",
141
174
  "internal_error"
142
175
  ]);
143
- function isRecord2(value) {
176
+ function isRecord3(value) {
144
177
  return typeof value === "object" && value !== null;
145
178
  }
146
179
  function isServicemeErrorCode(value) {
147
180
  return typeof value === "string" && SERVICEME_ERROR_CODES.includes(value);
148
181
  }
149
182
  function isServicemeErrorDetails(value) {
150
- if (!isRecord2(value)) {
183
+ if (!isRecord3(value)) {
151
184
  return false;
152
185
  }
153
186
  return isServicemeErrorCode(value.code) && typeof value.message === "string" && typeof value.retryable === "boolean";
@@ -187,7 +220,7 @@ function normalizeServicemeError(error, fallbackCode = "internal_error") {
187
220
  if (isServicemeErrorDetails(error)) {
188
221
  return error;
189
222
  }
190
- if (isRecord2(error)) {
223
+ if (isRecord3(error)) {
191
224
  const code = isServicemeErrorCode(error.code) ? error.code : fallbackCode;
192
225
  const message = typeof error.message === "string" ? error.message : "Unexpected serviceme error.";
193
226
  const retryable = typeof error.retryable === "boolean" ? error.retryable : isRetryableErrorCode(code);
@@ -213,7 +246,1103 @@ function normalizeServicemeError(error, fallbackCode = "internal_error") {
213
246
  }
214
247
 
215
248
  // src/version.ts
216
- var SERVICEME_CLI_VERSION = "1.0.0";
249
+ var SERVICEME_CLI_VERSION = "2.0.1";
250
+
251
+ // src/bridge/CopilotContentBridgeHandler.ts
252
+ var fsp = __toESM(require("fs/promises"));
253
+ var path = __toESM(require("path"));
254
+ var import_devtools_core = require("@serviceme/devtools-core");
255
+ var CopilotContentBridgeHandler = class {
256
+ constructor(opts) {
257
+ this.repoManager = new import_devtools_core.RepoManager({
258
+ store: opts.store,
259
+ gitClient: opts.gitClient,
260
+ skipClone: opts.skipClone ?? false
261
+ });
262
+ }
263
+ /** Ensure every declared repository is checked out at its pinned commit. */
264
+ async ensureRepositories(manifest) {
265
+ const sources = /* @__PURE__ */ new Map();
266
+ for (const source of (0, import_devtools_core.getWorkspaceManifestSources)(
267
+ manifest ?? { version: 2, sources: [], plugins: [] }
268
+ )) {
269
+ if (source.type === "catalog") {
270
+ const localPath = path.resolve((0, import_devtools_core.getReposDir)(), source.id);
271
+ const stat3 = await fsp.stat(localPath).catch(() => null);
272
+ sources.set(
273
+ source.id,
274
+ stat3?.isDirectory() ? { ready: true } : {
275
+ ready: false,
276
+ error: "Source not materialized under ~/.serviceme/repos"
277
+ }
278
+ );
279
+ continue;
280
+ }
281
+ try {
282
+ await this.repoManager.ensureAtCommit({
283
+ repository: {
284
+ id: source.id,
285
+ url: source.url,
286
+ useProxy: true
287
+ },
288
+ commit: source.commit
289
+ });
290
+ sources.set(source.id, { ready: true });
291
+ } catch (error) {
292
+ sources.set(source.id, {
293
+ ready: false,
294
+ error: error instanceof Error ? error.message : String(error)
295
+ });
296
+ }
297
+ }
298
+ return sources;
299
+ }
300
+ async reconcile(workspaceDir) {
301
+ const reconciler = await this.makeReconciler(workspaceDir);
302
+ return reconciler.reconcile();
303
+ }
304
+ /**
305
+ * Reconciler with machine-local disable marks applied: identities
306
+ * disabled for THIS workspace behave as if undeclared (links are
307
+ * removed and never resurrected) while the manifest stays intact.
308
+ */
309
+ async makeReconciler(workspaceDir) {
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);
316
+ return new import_devtools_core.WorkspaceCopilotContentReconciler({
317
+ workspaceDir,
318
+ ensureRepository: (manifest) => this.ensureRepositories(manifest),
319
+ isDisabled: async (identity) => matches(identity)
320
+ });
321
+ }
322
+ /** `copilotContent.restore` — reconcile declared content now. */
323
+ async restore(params) {
324
+ return this.reconcile(params.workspaceDir);
325
+ }
326
+ /** `copilotContent.status` — declaration + current status without mutations. */
327
+ async status(params) {
328
+ const manifest = await (0, import_devtools_core.loadWorkspaceCopilotManifest)(params.workspaceDir);
329
+ if (!manifest) {
330
+ return { changed: false, entries: [] };
331
+ }
332
+ return this.reconcile(params.workspaceDir);
333
+ }
334
+ /** `copilotContent.approve` — record local approval, then re-reconcile. */
335
+ async approve(params) {
336
+ const reconciler = await this.makeReconciler(params.workspaceDir);
337
+ return reconciler.approve({ identities: params.identities });
338
+ }
339
+ /** `copilotContent.migrateLegacy` — report legacy ~/.agents links for migration. */
340
+ async migrateLegacy(params) {
341
+ const manifest = await (0, import_devtools_core.loadWorkspaceCopilotManifest)(params.workspaceDir);
342
+ if (!manifest) {
343
+ return { entries: [] };
344
+ }
345
+ const plan = await (0, import_devtools_core.resolveWorkspaceContentPlan)({
346
+ manifest,
347
+ reposDir: (0, import_devtools_core.getReposDir)()
348
+ });
349
+ const materializer = new import_devtools_core.CopilotLinkMaterializer();
350
+ const entries = [];
351
+ for (const entry of plan.entries) {
352
+ const legacy = await materializer.inspectLegacyUserLink({
353
+ homeDir: (0, import_devtools_core.getServicemeHome)(),
354
+ entry
355
+ });
356
+ entries.push({
357
+ identity: legacy.identity,
358
+ status: legacy.status,
359
+ message: legacy.message
360
+ });
361
+ }
362
+ return { entries };
363
+ }
364
+ /**
365
+ * `copilotContent.integrationStatus` — read-only diagnostics for the
366
+ * machine-local MCP / hook integrations of a declared workspace.
367
+ *
368
+ * The state store provides the declared identities and approval flags;
369
+ * the two generated config files and their `_serviceme` ownership maps
370
+ * prove which names are actually present on this machine. This method
371
+ * never mutates state or configuration.
372
+ */
373
+ async integrationStatus(params) {
374
+ const state = await new import_devtools_core.WorkspaceContentStateStore({
375
+ workspaceDir: params.workspaceDir
376
+ }).read();
377
+ const mcp = await readOwnershipConfig(
378
+ path.join(params.workspaceDir, ".vscode", "mcp.serviceme.json")
379
+ );
380
+ const hooks = await readOwnershipConfig(
381
+ path.join(params.workspaceDir, ".vscode", "hooks.serviceme.json")
382
+ );
383
+ const integrations = [];
384
+ for (const entry of state.entries) {
385
+ if (entry.kind !== "mcp" && entry.kind !== "hook") continue;
386
+ const config = entry.kind === "mcp" ? mcp : hooks;
387
+ const owned = config.ownership[entry.identity] ?? [];
388
+ const active = config.exists && owned.length > 0;
389
+ const status = active ? "active" : entry.approved ? "missing_local_configuration" : "pending_approval";
390
+ integrations.push({
391
+ identity: entry.identity,
392
+ kind: entry.kind,
393
+ configPath: config.configPath,
394
+ names: owned,
395
+ status
396
+ });
397
+ }
398
+ return { integrations };
399
+ }
400
+ };
401
+ async function readOwnershipConfig(configPath) {
402
+ try {
403
+ const raw = await fsp.readFile(configPath, "utf8");
404
+ const parsed = JSON.parse(raw);
405
+ const ownershipNode = parsed._serviceme;
406
+ const ownership = ownershipNode && typeof ownershipNode === "object" ? normalizeOwnership(ownershipNode) ?? {} : {};
407
+ return { exists: true, configPath, ownership };
408
+ } catch (error) {
409
+ if (error.code === "ENOENT") {
410
+ return { exists: false, configPath, ownership: {} };
411
+ }
412
+ throw error;
413
+ }
414
+ }
415
+ function normalizeOwnership(node) {
416
+ if (node === null || typeof node !== "object" || Array.isArray(node)) {
417
+ return void 0;
418
+ }
419
+ const result = {};
420
+ for (const [identity, names] of Object.entries(node)) {
421
+ if (Array.isArray(names) && names.every((name) => typeof name === "string")) {
422
+ result[identity] = names;
423
+ }
424
+ }
425
+ return result;
426
+ }
427
+
428
+ // src/bridge/CopilotCustomizationsBridgeHandler.ts
429
+ var fsp2 = __toESM(require("fs/promises"));
430
+ var path2 = __toESM(require("path"));
431
+ var import_devtools_core2 = require("@serviceme/devtools-core");
432
+ var import_skill_linker = require("@serviceme/devtools-core/skill-linker");
433
+ function createCopilotCustomizationsProductionDeps(options) {
434
+ return {
435
+ snapshot: async (params) => {
436
+ if (params.scope === "personal") {
437
+ return createFullPersonalSnapshot(options, await options.readers.listPersonalLinks());
438
+ }
439
+ return createWorkspaceSnapshot(
440
+ options.repoDisplayName,
441
+ options.readers,
442
+ params.workspaceDir ?? process.cwd()
443
+ );
444
+ }
445
+ };
446
+ }
447
+ var CopilotCustomizationsBridgeHandler = class {
448
+ constructor(deps) {
449
+ if ("snapshot" in deps && typeof deps.snapshot === "function") {
450
+ this.snapshot = deps.snapshot;
451
+ this.listAvailablePackages = deps.listAvailablePackages;
452
+ this.mutations = createUnimplementedMutations(
453
+ "Injected snapshot deps do not support package mutations"
454
+ );
455
+ } else if ("store" in deps && deps.store !== void 0) {
456
+ this.snapshot = createCopilotCustomizationsProductionDeps({
457
+ repoDisplayName: (repoId) => deps.store.get(repoId)?.name,
458
+ readers: createDefaultProductionReaders(),
459
+ personal: createDefaultPersonalSnapshotInput(deps.store)
460
+ }).snapshot;
461
+ this.mutations = createProductionMutations(deps.store);
462
+ this.listAvailablePackages = createAvailablePackagesEnumerator(deps.store);
463
+ } else {
464
+ this.snapshot = async () => emptySnapshot();
465
+ this.mutations = createUnimplementedMutations(
466
+ "Package mutations require production dependencies"
467
+ );
468
+ }
469
+ }
470
+ async list(params) {
471
+ const snapshot = await this.snapshot(params);
472
+ const shared = snapshot.workspaceManifest !== void 0 ? { workspaceManifest: snapshot.workspaceManifest } : {};
473
+ const availablePackages = await this.listAvailablePackages?.(params, shared).catch(
474
+ () => void 0
475
+ );
476
+ return {
477
+ view: (0, import_devtools_core2.buildCopilotCustomizationView)({
478
+ scope: params.scope,
479
+ sources: snapshot.sources.map(toPublicSource),
480
+ packages: snapshot.packages.map(toPublicPackage),
481
+ installations: snapshot.installations.map(toPublicInstallation),
482
+ statesByArtifactId: toPublicStates(snapshot.statesByArtifactId),
483
+ legacyCount: snapshot.legacyCount,
484
+ generatedAt: snapshot.generatedAt,
485
+ ...snapshot.disabledArtifactIds ? { disabledArtifactIds: snapshot.disabledArtifactIds } : {}
486
+ }),
487
+ ...availablePackages ? { availablePackages } : {}
488
+ };
489
+ }
490
+ async install(params) {
491
+ return this.mutations.install(params);
492
+ }
493
+ async update(params) {
494
+ return this.mutations.update(params);
495
+ }
496
+ async uninstall(params) {
497
+ const result = await this.mutations.uninstall(params);
498
+ try {
499
+ const registrar = new import_devtools_core2.CopilotPluginRegistrar({
500
+ copilotDir: path2.join((0, import_devtools_core2.getHomeDir)(), ".copilot")
501
+ });
502
+ await registrar.unregister({
503
+ registrationId: params.packageId.replace("::", ":"),
504
+ scope: params.scope
505
+ });
506
+ const otherScope = params.scope === "personal" ? "workspace" : "personal";
507
+ await registrar.unregister({
508
+ registrationId: params.packageId.replace("::", ":"),
509
+ scope: otherScope
510
+ });
511
+ } catch {
512
+ }
513
+ return result;
514
+ }
515
+ async move(params) {
516
+ return this.mutations.move(params);
517
+ }
518
+ async legacyPreview(params) {
519
+ return this.mutations.legacyPreview(params);
520
+ }
521
+ async legacyMigrate(params) {
522
+ return this.mutations.legacyMigrate(params);
523
+ }
524
+ async sources(params) {
525
+ return this.mutations.sources(params);
526
+ }
527
+ async removeSource(params) {
528
+ return this.mutations.removeSource(params);
529
+ }
530
+ async previewUpdate(params) {
531
+ return this.mutations.previewUpdate(params);
532
+ }
533
+ };
534
+ function createDefaultPersonalSnapshotInput(store) {
535
+ return {
536
+ // Capabilities are intentionally omitted: createFullPersonalSnapshot
537
+ // falls back to the real default host-capability provider, which
538
+ // claims the verified ~/.copilot agent/skill targets. Pinning an
539
+ // empty personalTargets map here regressed every link artifact to
540
+ // unsupported. The explicit package resolver is the part Task 8
541
+ // flagged as silently missing.
542
+ resolvePersonalPackage: (packageId) => resolvePersonalPackageFromRepos(packageId, store)
543
+ };
544
+ }
545
+ async function resolvePersonalPackageFromRepos(packageId, store) {
546
+ const [sourceId, pluginId] = packageId.split("::");
547
+ if (!sourceId || !pluginId) return [];
548
+ const repoRoot = path2.resolve((0, import_devtools_core2.getReposDir)(), sourceId);
549
+ const stat3 = await fsp2.stat(repoRoot).catch(() => null);
550
+ if (!stat3?.isDirectory()) return [];
551
+ const repo = store.get(sourceId);
552
+ const manifest = {
553
+ version: 1,
554
+ repositories: [
555
+ {
556
+ id: sourceId,
557
+ url: repo?.url ?? `https://serviceme.local/catalog/${encodeURIComponent(sourceId)}`,
558
+ commit: repo?.lastSyncCommitSha ?? "".padEnd(40, "0")
559
+ }
560
+ ],
561
+ plugins: [
562
+ {
563
+ repository: sourceId,
564
+ id: pluginId,
565
+ artifacts: {
566
+ agent: true,
567
+ skill: true,
568
+ instruction: true,
569
+ prompt: true,
570
+ hook: true,
571
+ mcp: true
572
+ }
573
+ }
574
+ ]
575
+ };
576
+ const resolved = await (0, import_devtools_core2.resolveWorkspaceContentPlan)({
577
+ manifest,
578
+ reposDir: (0, import_devtools_core2.getReposDir)()
579
+ }).catch(() => ({ entries: [] }));
580
+ return resolved.entries;
581
+ }
582
+ function createAvailablePackagesEnumerator(store) {
583
+ const catalog = (0, import_devtools_core2.createPluginCatalogService)();
584
+ const personalStore = new import_devtools_core2.PersonalInstallationStore({});
585
+ const pluginRegistrar = new import_devtools_core2.CopilotPluginRegistrar({
586
+ copilotDir: path2.join((0, import_devtools_core2.getHomeDir)(), ".copilot")
587
+ });
588
+ return async (params, shared) => {
589
+ const repos = store.list().filter((repo) => repo.enabled).map((repo) => ({ id: repo.id }));
590
+ if (repos.length === 0) return [];
591
+ const catalogPackages = await catalog.list({
592
+ reposDir: (0, import_devtools_core2.getReposDir)(),
593
+ repos
594
+ });
595
+ const [workspaceManifest, personalIntent, registrations] = await Promise.all([
596
+ // Shared read from the same list() call when provided; only a
597
+ // standalone enumerator invocation (personal scope, tests,
598
+ // direct calls) hits the disk here.
599
+ shared?.workspaceManifest !== void 0 ? Promise.resolve(shared.workspaceManifest) : params.workspaceDir ? (0, import_devtools_core2.loadWorkspaceCopilotManifest)(params.workspaceDir).catch(() => void 0) : Promise.resolve(void 0),
600
+ personalStore.read().catch(() => void 0),
601
+ // Whole-package registry — prune-on-list keeps it clean.
602
+ pluginRegistrar.list({}).catch(() => [])
603
+ ]);
604
+ const manifest = workspaceManifest ?? {
605
+ version: 1,
606
+ repositories: [],
607
+ plugins: []
608
+ };
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
+ ])
617
+ );
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])
638
+ );
639
+ const registeredScopes = /* @__PURE__ */ new Map();
640
+ for (const reg of registrations) {
641
+ const packageId = `${reg.repoId}::${reg.pluginId}`;
642
+ const scopes = registeredScopes.get(packageId) ?? /* @__PURE__ */ new Set();
643
+ for (const scope of reg.scopes) scopes.add(scope);
644
+ registeredScopes.set(packageId, scopes);
645
+ }
646
+ return catalogPackages.map((pkg) => {
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);
661
+ return {
662
+ ...pkg,
663
+ installedScopes: installed ? ["personal"] : []
664
+ };
665
+ });
666
+ };
667
+ }
668
+ function createProductionMutations(store) {
669
+ let servicePromise;
670
+ let sourceCatalogPromise;
671
+ const service = () => servicePromise ??= createPackageInstallationService(store);
672
+ const sourceCatalog = () => sourceCatalogPromise ??= createSourceCatalogService(store);
673
+ return {
674
+ install: async (params) => toResult(await (await service()).install(params)),
675
+ update: async (params) => toResult(await (await service()).update(params)),
676
+ uninstall: async (params) => toResult(await (await service()).uninstall(params)),
677
+ move: async (params) => toResult(await (await service()).move(params)),
678
+ legacyPreview: async () => {
679
+ const preview = await (await service()).previewLegacy();
680
+ return {
681
+ sourceLabel: preview.sourceLabel,
682
+ count: preview.entries.length,
683
+ artifacts: preview.entries.map((entry) => ({
684
+ id: entry.artifactId,
685
+ packageId: "legacy",
686
+ kind: entry.kind,
687
+ displayName: entry.name,
688
+ installStrategy: "link",
689
+ risk: "none"
690
+ }))
691
+ };
692
+ },
693
+ legacyMigrate: async (params) => toResult(await (await service()).migrateLegacy(params)),
694
+ sources: async (params) => {
695
+ const records = await (await sourceCatalog()).listSources({
696
+ scope: params.scope,
697
+ ...params.workspaceDir ? { workspaceDir: params.workspaceDir } : {}
698
+ });
699
+ return {
700
+ sources: records.map((record) => ({
701
+ id: record.id,
702
+ type: record.type,
703
+ displayName: record.displayName,
704
+ updateCapability: record.updateCapability,
705
+ available: record.available,
706
+ declaredIn: [...record.declaredIn]
707
+ }))
708
+ };
709
+ },
710
+ removeSource: async (params) => {
711
+ await (await sourceCatalog()).removeSource(params.sourceId, params.workspaceDir);
712
+ return { removed: true };
713
+ },
714
+ previewUpdate: async (params) => ({
715
+ preview: await (await sourceCatalog()).previewUpdate(params)
716
+ })
717
+ };
718
+ }
719
+ async function createSourceCatalogService(store) {
720
+ const personalStore = new import_devtools_core2.PersonalInstallationStore({});
721
+ const loadManifest = async (workspaceDir) => {
722
+ return (0, import_devtools_core2.loadWorkspaceCopilotManifest)(workspaceDir).catch(() => void 0);
723
+ };
724
+ return new import_devtools_core2.CopilotSourceCatalogService({
725
+ personalStore,
726
+ // The source manager is the single repository management surface;
727
+ // surface every ReposStore entry (built-in defaults + user git
728
+ // repos) so they can be synced / toggled / edited from there even
729
+ // when no installation or workspace declaration references them.
730
+ // Read per call: repos.json can change under the long-lived bridge.
731
+ listStoreSources: async () => store.list().map((repo) => ({
732
+ id: repo.id,
733
+ name: repo.name,
734
+ enabled: repo.enabled
735
+ })),
736
+ resolvePackage: (packageId) => resolvePersonalPackageFromRepos(packageId, store),
737
+ resolveActualRevision: async (packageId) => {
738
+ const [sourceId] = packageId.split("::");
739
+ if (!sourceId) return void 0;
740
+ const repo = store.get(sourceId);
741
+ if (repo?.lastSyncCommitSha) return repo.lastSyncCommitSha;
742
+ const repoDir = path2.resolve((0, import_devtools_core2.getReposDir)(), sourceId);
743
+ const head = await new import_devtools_core2.GitClient({ serverProxyBase: "" }).revParseHead(repoDir).catch(() => void 0);
744
+ return head;
745
+ },
746
+ listWorkspaceSources: async (workspaceDir) => {
747
+ const manifest = await loadManifest(workspaceDir);
748
+ if (!manifest) return [];
749
+ return (0, import_devtools_core2.getWorkspaceManifestSources)(manifest).map((source) => source.id);
750
+ },
751
+ readWorkspaceInstallations: async (workspaceDir) => {
752
+ const manifest = await loadManifest(workspaceDir);
753
+ if (!manifest) return [];
754
+ return manifest.plugins.map((plugin) => {
755
+ const sourceId = (0, import_devtools_core2.getWorkspaceManifestPluginSourceId)(manifest, plugin);
756
+ const source = (0, import_devtools_core2.getWorkspaceManifestSources)(manifest).find((s) => s.id === sourceId);
757
+ return {
758
+ packageId: `${sourceId}::${plugin.id}`,
759
+ scope: "workspace",
760
+ selectedArtifactIds: Object.entries(plugin.artifacts).filter(([, enabled]) => enabled !== false).map(([kind]) => `${sourceId}::${plugin.id}::${kind}:`),
761
+ pinnedVersion: source?.type === "git" ? source.commit : void 0
762
+ };
763
+ });
764
+ },
765
+ artifactSelected: (marker, artifactId) => artifactId.startsWith(marker),
766
+ ensureCatalogSource: async (sourceId) => {
767
+ const mgr = new import_devtools_core2.RepoManager({
768
+ store,
769
+ gitClient: new import_devtools_core2.GitClient({ serverProxyBase: "" })
770
+ });
771
+ return mgr.ensureCatalogSource({
772
+ sourceId,
773
+ provider: {
774
+ // Placeholder staging: the marketplace catalog client is
775
+ // not yet wired to per-source payloads, so the first
776
+ // materialization establishes the directory contract.
777
+ // Real payload extraction lands with the catalog client.
778
+ materialize: async (targetDir) => {
779
+ await fsp2.mkdir(targetDir, { recursive: true });
780
+ }
781
+ }
782
+ });
783
+ },
784
+ isCatalogSource: async (sourceId, workspaceDir) => {
785
+ if (!workspaceDir) return false;
786
+ const declared = await loadManifest(workspaceDir);
787
+ const source = declared ? (0, import_devtools_core2.getWorkspaceManifestSources)(declared).find((s) => s.id === sourceId) : void 0;
788
+ return source?.type === "catalog";
789
+ },
790
+ hasCatalogPayload: async (localPath) => {
791
+ const pluginsDir = path2.join(localPath, "plugins");
792
+ const entries = await fsp2.readdir(pluginsDir).catch(() => []);
793
+ if (entries.length === 0) return false;
794
+ for (const entry of entries) {
795
+ const stat3 = await fsp2.stat(path2.join(pluginsDir, entry)).catch(() => null);
796
+ if (stat3?.isDirectory()) {
797
+ const manifest = await fsp2.stat(path2.join(pluginsDir, entry, "plugin.json")).catch(() => null);
798
+ if (manifest?.isFile()) return true;
799
+ }
800
+ }
801
+ return false;
802
+ }
803
+ });
804
+ }
805
+ async function createPackageInstallationService(store) {
806
+ const personalStore = new import_devtools_core2.PersonalInstallationStore({});
807
+ const personalReconciler = new import_devtools_core2.PersonalCopilotContentReconciler({
808
+ capabilities: await (0, import_devtools_core2.createDefaultCopilotHostCapabilities)(),
809
+ resolvePackage: (packageId) => resolvePersonalPackageFromRepos(packageId, store)
810
+ });
811
+ return new import_devtools_core2.PackageInstallationService({
812
+ personalStore,
813
+ personalReconciler,
814
+ userHomeDir: (0, import_devtools_core2.getHomeDir)(),
815
+ resolvePackage: (packageId) => resolvePersonalPackageFromRepos(packageId, store),
816
+ resolveWorkspaceSource: async (sourceId) => {
817
+ const repo = store.get(sourceId);
818
+ if (!repo) return void 0;
819
+ const repoDir = path2.resolve((0, import_devtools_core2.getReposDir)(), sourceId);
820
+ const stat3 = await fsp2.stat(repoDir).catch(() => null);
821
+ if (!stat3?.isDirectory()) return void 0;
822
+ return {
823
+ id: sourceId,
824
+ url: repo.url,
825
+ commit: repo.lastSyncCommitSha ?? "0".repeat(40)
826
+ };
827
+ },
828
+ createWorkspaceReconciler: (workspaceDir) => new import_devtools_core2.WorkspaceCopilotContentReconciler({ workspaceDir }),
829
+ readWorkspaceView: async (workspaceDir) => {
830
+ const snapshot = await createWorkspaceSnapshot(
831
+ (repoId) => store.get(repoId)?.name,
832
+ createDefaultProductionReaders(),
833
+ workspaceDir
834
+ );
835
+ return (0, import_devtools_core2.buildCopilotCustomizationView)({
836
+ scope: "workspace",
837
+ sources: snapshot.sources,
838
+ packages: snapshot.packages,
839
+ installations: snapshot.installations,
840
+ statesByArtifactId: snapshot.statesByArtifactId,
841
+ generatedAt: snapshot.generatedAt,
842
+ ...snapshot.disabledArtifactIds ? { disabledArtifactIds: snapshot.disabledArtifactIds } : {}
843
+ });
844
+ },
845
+ readWorkspaceInstallations: import_devtools_core2.readDeclaredWorkspaceInstallations
846
+ });
847
+ }
848
+ function toResult(view) {
849
+ return { view: toPublicView(view) };
850
+ }
851
+ function toPublicView(view) {
852
+ return {
853
+ scope: view.scope,
854
+ generatedAt: view.generatedAt,
855
+ sources: view.sources.map((source) => ({
856
+ ...toPublicSource(source),
857
+ packages: source.packages.map((pkg) => toPublicPackageView(pkg))
858
+ })),
859
+ packages: view.packages.map((pkg) => ({
860
+ ...toPublicPackageView(pkg)
861
+ })),
862
+ attention: view.attention.map(({ artifact, state }) => ({
863
+ artifact,
864
+ state
865
+ })),
866
+ summary: view.summary,
867
+ ...view.legacyMigration ? { legacyMigration: view.legacyMigration } : {}
868
+ };
869
+ }
870
+ function toPublicPackageView(pkg) {
871
+ return {
872
+ definition: toPublicPackage(pkg.definition),
873
+ installation: toPublicInstallation(pkg.installation),
874
+ artifacts: pkg.artifacts.map(({ artifact, state }) => ({
875
+ artifact,
876
+ state
877
+ })),
878
+ selectedArtifactCount: pkg.selectedArtifactCount,
879
+ status: pkg.status
880
+ };
881
+ }
882
+ function createUnimplementedMutations(reason) {
883
+ const reject = async () => {
884
+ throw new Error(reason);
885
+ };
886
+ return {
887
+ install: reject,
888
+ update: reject,
889
+ uninstall: reject,
890
+ move: reject,
891
+ legacyPreview: reject,
892
+ legacyMigrate: reject,
893
+ sources: reject,
894
+ removeSource: reject,
895
+ previewUpdate: reject
896
+ };
897
+ }
898
+ async function mergeRegistrarPackages(sources, packages, installations, statesByArtifactId) {
899
+ const registrar = new import_devtools_core2.CopilotPluginRegistrar({
900
+ copilotDir: path2.join((0, import_devtools_core2.getHomeDir)(), ".copilot")
901
+ });
902
+ const registrations = await registrar.list({}).catch(() => []);
903
+ if (registrations.length === 0) return;
904
+ const asMap = packages instanceof Map ? packages : new Map(packages.map((p) => [p.id, p]));
905
+ const knownIds = new Set(asMap.keys());
906
+ const pushed = packages instanceof Map ? void 0 : packages;
907
+ for (const reg of registrations) {
908
+ const packageId = `${reg.repoId}::${reg.pluginId}`;
909
+ if (knownIds.has(packageId)) continue;
910
+ const pluginJsonPath = path2.join(reg.pluginDir, "plugin.json");
911
+ let definition;
912
+ try {
913
+ const raw = JSON.parse(await fsp2.readFile(pluginJsonPath, "utf8"));
914
+ const repoRoot = path2.basename(path2.dirname(reg.pluginDir)) === "plugins" ? path2.dirname(path2.dirname(reg.pluginDir)) : reg.pluginDir;
915
+ const entries = await (0, import_devtools_core2.resolvePluginEntriesLenient)({
916
+ repoRoot,
917
+ repositoryId: reg.repoId,
918
+ pluginId: reg.pluginId,
919
+ manifest: {
920
+ name: typeof raw.name === "string" && raw.name.trim() !== "" ? raw.name : reg.pluginId,
921
+ ...typeof raw.description === "string" ? { description: raw.description } : {},
922
+ ...typeof raw.version === "string" ? { version: raw.version } : {},
923
+ extensions: raw.extensions ?? {}
924
+ }
925
+ }).catch(() => []);
926
+ const artifacts = entries.map(import_devtools_core2.toArtifactSummary);
927
+ if (artifacts.length > 0) {
928
+ definition = {
929
+ id: packageId,
930
+ sourceId: reg.repoId,
931
+ displayName: typeof raw.name === "string" && raw.name || reg.displayName || reg.pluginId,
932
+ ...typeof raw.description === "string" ? { description: raw.description } : {},
933
+ ...typeof raw.version === "string" ? { version: raw.version } : {},
934
+ artifacts,
935
+ wholePackage: true
936
+ };
937
+ for (const artifact of artifacts) {
938
+ statesByArtifactId[artifact.id] = {
939
+ intent: "selected",
940
+ health: "healthy",
941
+ gate: "ready"
942
+ };
943
+ }
944
+ } else {
945
+ const artifact = {
946
+ id: `${packageId}::package:${reg.pluginId}`,
947
+ packageId,
948
+ kind: "skill",
949
+ displayName: reg.pluginId,
950
+ installStrategy: "link",
951
+ risk: "none"
952
+ };
953
+ definition = {
954
+ id: packageId,
955
+ sourceId: reg.repoId,
956
+ displayName: typeof raw.name === "string" && raw.name || reg.displayName || reg.pluginId,
957
+ ...typeof raw.description === "string" ? { description: raw.description } : {},
958
+ ...typeof raw.version === "string" ? { version: raw.version } : {},
959
+ artifacts: [artifact],
960
+ wholePackage: true
961
+ };
962
+ statesByArtifactId[artifact.id] = {
963
+ intent: "selected",
964
+ health: "healthy",
965
+ gate: "ready"
966
+ };
967
+ }
968
+ } catch {
969
+ definition = {
970
+ id: packageId,
971
+ sourceId: reg.repoId,
972
+ displayName: reg.displayName || reg.pluginId,
973
+ ...reg.version !== void 0 ? { version: reg.version } : {},
974
+ artifacts: [],
975
+ wholePackage: true
976
+ };
977
+ }
978
+ if (pushed) {
979
+ pushed.push(definition);
980
+ } else {
981
+ asMap.set(packageId, definition);
982
+ }
983
+ if (!sources.some((source) => source.id === reg.repoId)) {
984
+ sources.push({
985
+ id: reg.repoId,
986
+ type: "git",
987
+ displayName: reg.repoId,
988
+ updateCapability: "pinned"
989
+ });
990
+ }
991
+ installations.push({
992
+ packageId,
993
+ scope: "personal",
994
+ selectedArtifactIds: definition.artifacts.map((artifact) => artifact.id)
995
+ });
996
+ }
997
+ }
998
+ async function createWorkspaceSnapshot(repoDisplayName, readers, workspaceDir) {
999
+ const manifest = await readers.loadWorkspaceManifest(workspaceDir);
1000
+ if (!manifest) return emptySnapshot();
1001
+ const sources = (0, import_devtools_core2.getWorkspaceManifestSources)(manifest).map(
1002
+ (source) => ({
1003
+ id: source.id,
1004
+ type: source.type === "catalog" ? "marketplace" : "git",
1005
+ displayName: source.type === "catalog" ? source.catalogId : repoDisplayName(source.id) ?? source.id,
1006
+ updateCapability: "pinned"
1007
+ })
1008
+ );
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
+ }
1016
+ const packages = [];
1017
+ const installations = [];
1018
+ const statesByArtifactId = {};
1019
+ const disabledArtifactIds = [];
1020
+ const resolvedPlugins = await Promise.all(
1021
+ manifest.plugins.map(async (plugin) => {
1022
+ const sourceId = (0, import_devtools_core2.getWorkspaceManifestPluginSourceId)(manifest, plugin);
1023
+ const packageId = `${sourceId}::${plugin.id}`;
1024
+ let fallbackHealth;
1025
+ let entries;
1026
+ if (!await readers.isSourceAvailable(sourceId)) {
1027
+ fallbackHealth = "source-unavailable";
1028
+ entries = fallbackEntries(plugin, packageId);
1029
+ } else {
1030
+ try {
1031
+ entries = await readers.resolveWorkspacePlugin({ manifest, plugin });
1032
+ } catch {
1033
+ fallbackHealth = "conflict";
1034
+ entries = fallbackEntries(plugin, packageId);
1035
+ }
1036
+ }
1037
+ return { plugin, sourceId, packageId, entries, fallbackHealth };
1038
+ })
1039
+ );
1040
+ for (const resolved of resolvedPlugins) {
1041
+ const { plugin, sourceId, packageId, entries, fallbackHealth } = resolved;
1042
+ const artifacts = entries.map(import_devtools_core2.toArtifactSummary);
1043
+ packages.push({
1044
+ id: packageId,
1045
+ sourceId,
1046
+ displayName: entries[0]?.packageDisplayName ?? plugin.id,
1047
+ ...entries[0]?.packageDescription ? { description: entries[0].packageDescription } : {},
1048
+ ...entries[0]?.packageVersion ? { version: entries[0].packageVersion } : {},
1049
+ artifacts
1050
+ });
1051
+ installations.push({
1052
+ packageId,
1053
+ scope: "workspace",
1054
+ selectedArtifactIds: artifacts.map((artifact) => artifact.id)
1055
+ });
1056
+ for (const entry of entries) {
1057
+ const previous = state.entries.find((candidate) => candidate.identity === entry.artifactId);
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
+ }
1068
+ }
1069
+ }
1070
+ await mergeRegistrarPackages(sources, packages, installations, statesByArtifactId);
1071
+ return {
1072
+ sources,
1073
+ packages,
1074
+ installations,
1075
+ statesByArtifactId,
1076
+ disabledArtifactIds,
1077
+ workspaceManifest: manifest
1078
+ };
1079
+ }
1080
+ async function createFullPersonalSnapshot(options, legacyLinks) {
1081
+ const capabilities = options.personal?.capabilities ?? await (0, import_devtools_core2.createDefaultCopilotHostCapabilities)();
1082
+ const homeDir = options.personal?.homeDir;
1083
+ const store = new import_devtools_core2.PersonalInstallationStore(homeDir !== void 0 ? { homeDir } : {});
1084
+ const reconciler = new import_devtools_core2.PersonalCopilotContentReconciler({
1085
+ capabilities,
1086
+ ...homeDir ? { homeDir } : {},
1087
+ ...options.personal?.resolvePersonalPackage ? { resolvePackage: options.personal.resolvePersonalPackage } : {}
1088
+ });
1089
+ const intent = await store.read();
1090
+ const inspection = await reconciler.inspect();
1091
+ const sources = [];
1092
+ const sourceIds = /* @__PURE__ */ new Set();
1093
+ const packages = /* @__PURE__ */ new Map();
1094
+ const statesByArtifactId = {
1095
+ ...inspection.statesByArtifactId
1096
+ };
1097
+ let legacyCount = 0;
1098
+ for (const link of legacyLinks) {
1099
+ if (link.legacy) legacyCount += 1;
1100
+ }
1101
+ for (const installation of intent.installations) {
1102
+ if (installation.scope !== "personal") continue;
1103
+ const resolved = options.personal ? await options.personal.resolvePersonalPackage(installation.packageId) : [];
1104
+ const artifacts = resolved.filter((entry) => installation.selectedArtifactIds.includes(entry.artifactId)).map(import_devtools_core2.toArtifactSummary);
1105
+ const [sourceId] = installation.packageId.split("::");
1106
+ if (sourceId && !sourceIds.has(sourceId)) {
1107
+ sourceIds.add(sourceId);
1108
+ sources.push({
1109
+ id: sourceId,
1110
+ type: "git",
1111
+ displayName: options.repoDisplayName(sourceId) ?? sourceId,
1112
+ updateCapability: "pinned"
1113
+ });
1114
+ }
1115
+ if (!packages.has(installation.packageId) && artifacts.length > 0) {
1116
+ packages.set(installation.packageId, {
1117
+ id: installation.packageId,
1118
+ sourceId: sourceId ?? "personal-local",
1119
+ displayName: resolved[0]?.packageDisplayName ?? installation.packageId,
1120
+ ...resolved[0]?.packageDescription ? { description: resolved[0].packageDescription } : {},
1121
+ ...resolved[0]?.packageVersion ? { version: resolved[0].packageVersion } : {},
1122
+ artifacts
1123
+ });
1124
+ }
1125
+ }
1126
+ await mergeRegistrarPackages(sources, packages, intent.installations, statesByArtifactId);
1127
+ return {
1128
+ sources,
1129
+ packages: [...packages.values()],
1130
+ installations: intent.installations.filter((installation) => installation.scope === "personal"),
1131
+ statesByArtifactId,
1132
+ ...legacyCount > 0 ? { legacyCount } : {}
1133
+ };
1134
+ }
1135
+ function fallbackEntries(plugin, packageId) {
1136
+ const kinds = Object.keys(plugin.artifacts);
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
+ });
1160
+ }
1161
+ function toArtifactState(entry, previous) {
1162
+ if (!previous) {
1163
+ return {
1164
+ intent: "selected",
1165
+ health: "missing",
1166
+ gate: entry.requiresApproval ? "approval-required" : "ready"
1167
+ };
1168
+ }
1169
+ return {
1170
+ intent: "selected",
1171
+ health: previous.digest === entry.digest ? "healthy" : "drifted",
1172
+ gate: entry.requiresApproval && !previous.approved ? "approval-required" : "ready"
1173
+ };
1174
+ }
1175
+ function createDefaultProductionReaders() {
1176
+ return {
1177
+ loadWorkspaceManifest: import_devtools_core2.loadWorkspaceCopilotManifest,
1178
+ readWorkspaceState: async (workspaceDir) => new import_devtools_core2.WorkspaceContentStateStore({ workspaceDir }).read(),
1179
+ isSourceAvailable: async (sourceId) => {
1180
+ const stat3 = await fsp2.stat(path2.join((0, import_devtools_core2.getReposDir)(), sourceId)).catch(() => void 0);
1181
+ return stat3?.isDirectory() === true;
1182
+ },
1183
+ resolveWorkspacePlugin: async ({ manifest, plugin }) => {
1184
+ const scopedManifest = manifest.version === 1 ? { ...manifest, plugins: [plugin] } : { ...manifest, plugins: [plugin] };
1185
+ return (await (0, import_devtools_core2.resolveWorkspaceContentPlan)({
1186
+ manifest: scopedManifest,
1187
+ reposDir: (0, import_devtools_core2.getReposDir)()
1188
+ })).entries;
1189
+ },
1190
+ listPersonalLinks: async () => [
1191
+ ...(await (0, import_skill_linker.listLinkedSkills)("", "skill", "user")).map((link) => ({
1192
+ repoId: link.repoId,
1193
+ name: link.skillName,
1194
+ kind: "skill",
1195
+ legacy: link.linkPath.includes(`${path2.sep}.agents${path2.sep}`)
1196
+ })),
1197
+ ...(await (0, import_skill_linker.listLinkedSkills)("", "agent", "user")).map((link) => ({
1198
+ repoId: link.repoId,
1199
+ name: link.skillName,
1200
+ kind: "agent",
1201
+ legacy: link.linkPath.includes(`${path2.sep}.agents${path2.sep}`)
1202
+ }))
1203
+ ]
1204
+ };
1205
+ }
1206
+ function emptySnapshot() {
1207
+ return {
1208
+ sources: [],
1209
+ packages: [],
1210
+ installations: [],
1211
+ statesByArtifactId: {}
1212
+ };
1213
+ }
1214
+ function toPublicSource(source) {
1215
+ return {
1216
+ id: source.id,
1217
+ type: source.type,
1218
+ displayName: source.displayName,
1219
+ updateCapability: source.updateCapability
1220
+ };
1221
+ }
1222
+ function toPublicPackage(pkg) {
1223
+ return {
1224
+ id: pkg.id,
1225
+ sourceId: pkg.sourceId,
1226
+ displayName: pkg.displayName,
1227
+ ...pkg.description !== void 0 ? { description: pkg.description } : {},
1228
+ ...pkg.version !== void 0 ? { version: pkg.version } : {},
1229
+ artifacts: pkg.artifacts.map((artifact) => ({
1230
+ id: artifact.id,
1231
+ packageId: artifact.packageId,
1232
+ kind: artifact.kind,
1233
+ displayName: artifact.displayName,
1234
+ ...artifact.description !== void 0 ? { description: artifact.description } : {},
1235
+ installStrategy: artifact.installStrategy,
1236
+ risk: artifact.risk
1237
+ })),
1238
+ ...pkg.wholePackage === true ? { wholePackage: true } : {}
1239
+ };
1240
+ }
1241
+ function toPublicInstallation(installation) {
1242
+ return {
1243
+ packageId: installation.packageId,
1244
+ scope: installation.scope,
1245
+ selectedArtifactIds: [...installation.selectedArtifactIds],
1246
+ ...installation.pinnedVersion !== void 0 ? { pinnedVersion: installation.pinnedVersion } : {}
1247
+ };
1248
+ }
1249
+ function toPublicStates(states) {
1250
+ return Object.fromEntries(
1251
+ Object.entries(states).map(([artifactId, state]) => [artifactId, { ...state }])
1252
+ );
1253
+ }
1254
+
1255
+ // src/bridge/CopilotPluginBridgeHandler.ts
1256
+ var fs = __toESM(require("fs/promises"));
1257
+ var path3 = __toESM(require("path"));
1258
+ var import_devtools_core3 = require("@serviceme/devtools-core");
1259
+ async function readManifestInfo(pluginDir) {
1260
+ try {
1261
+ const raw = JSON.parse(await fs.readFile(path3.join(pluginDir, "plugin.json"), "utf8"));
1262
+ let mcpServers = [];
1263
+ const mcpPath = path3.join(pluginDir, "mcp.json");
1264
+ try {
1265
+ const mcp = JSON.parse(await fs.readFile(mcpPath, "utf8"));
1266
+ mcpServers = Object.keys(mcp.mcpServers ?? {});
1267
+ } catch {
1268
+ }
1269
+ return {
1270
+ ...typeof raw.name === "string" ? { displayName: raw.name } : {},
1271
+ ...typeof raw.version === "string" ? { version: raw.version } : {},
1272
+ mcpServers,
1273
+ hasExtensions: Boolean(raw.extensions?.["com.github.copilot"])
1274
+ };
1275
+ } catch {
1276
+ return { mcpServers: [], hasExtensions: false };
1277
+ }
1278
+ }
1279
+ function toPayload(reg, info) {
1280
+ return {
1281
+ registrationId: reg.registrationId,
1282
+ repoId: reg.repoId,
1283
+ pluginId: reg.pluginId,
1284
+ ...reg.displayName ?? info.displayName ? { displayName: reg.displayName ?? info.displayName } : {},
1285
+ ...reg.version ?? info.version ? { version: reg.version ?? info.version } : {},
1286
+ scopes: reg.scopes,
1287
+ mcpServers: info.mcpServers,
1288
+ hasExtensions: info.hasExtensions
1289
+ };
1290
+ }
1291
+ var CopilotPluginBridgeHandler = class {
1292
+ constructor(options = {}) {
1293
+ const homeDir = options.homeDir ?? (0, import_devtools_core3.getHomeDir)();
1294
+ this.registrar = new import_devtools_core3.CopilotPluginRegistrar({
1295
+ // copilotDir is the migration source only — projections live
1296
+ // under the SERVICEME home, out of VS Code's ~/.copilot
1297
+ // reconciliation reach.
1298
+ copilotDir: path3.join(homeDir, ".copilot"),
1299
+ pluginsDir: path3.join(homeDir, ".serviceme", "copilot-plugins")
1300
+ });
1301
+ this.reposDir = path3.join(homeDir, ".serviceme", "repos");
1302
+ }
1303
+ pluginDir(repoId, pluginId) {
1304
+ return path3.join(this.reposDir, (0, import_devtools_core3.assertSafeRepoId)(repoId), "plugins", pluginId);
1305
+ }
1306
+ async list(_params) {
1307
+ const registrations = await this.registrar.list({});
1308
+ const plugins = await Promise.all(
1309
+ registrations.map(async (reg) => toPayload(reg, await readManifestInfo(reg.pluginDir)))
1310
+ );
1311
+ return { plugins };
1312
+ }
1313
+ async register(params) {
1314
+ const pluginDir = this.pluginDir(params.repoId, params.pluginId);
1315
+ const info = await readManifestInfo(pluginDir);
1316
+ await this.registrar.register({
1317
+ repoId: params.repoId,
1318
+ pluginId: params.pluginId,
1319
+ pluginDir,
1320
+ scope: params.scope,
1321
+ ...info.displayName !== void 0 ? { displayName: info.displayName } : {},
1322
+ ...info.version !== void 0 ? { version: info.version } : {}
1323
+ });
1324
+ return this.list({});
1325
+ }
1326
+ async unregister(params) {
1327
+ const normalized = params.registrationId.replace("::", ":");
1328
+ await this.registrar.unregister({
1329
+ registrationId: normalized,
1330
+ scope: params.scope
1331
+ });
1332
+ if (normalized !== params.registrationId) {
1333
+ await this.registrar.unregister({
1334
+ registrationId: params.registrationId,
1335
+ scope: params.scope
1336
+ });
1337
+ }
1338
+ const otherScope = params.scope === "personal" ? "workspace" : "personal";
1339
+ await this.registrar.unregister({
1340
+ registrationId: normalized,
1341
+ scope: otherScope
1342
+ });
1343
+ return this.list({});
1344
+ }
1345
+ };
217
1346
 
218
1347
  // src/bridge/handlers/AuthBridgeHandlers.ts
219
1348
  var import_auth2 = require("@serviceme/devtools-core/auth");
@@ -328,12 +1457,12 @@ function writeBridgeMessage(message) {
328
1457
  }
329
1458
 
330
1459
  // src/bridge/TaskBridgeHandler.ts
331
- var import_devtools_core = require("@serviceme/devtools-core");
1460
+ var import_devtools_core4 = require("@serviceme/devtools-core");
332
1461
  var TaskBridgeHandler = class {
333
1462
  constructor(logger, emitEvent) {
334
1463
  this.logger = logger;
335
1464
  this.emitEvent = emitEvent;
336
- this.engine = new import_devtools_core.TaskExecutionEngine((taskType) => (0, import_devtools_core.getExecutor)(taskType));
1465
+ this.engine = new import_devtools_core4.TaskExecutionEngine((taskType) => (0, import_devtools_core4.getExecutor)(taskType));
337
1466
  this.engine.setListener({
338
1467
  onStarted: (params) => this.emitEvent("task.started", params),
339
1468
  onOutput: (params) => this.emitEvent("task.output", params),
@@ -376,6 +1505,7 @@ var CAPABILITIES = {
376
1505
  tasks: 1,
377
1506
  skillRepo: 1,
378
1507
  repoMgmt: 1,
1508
+ copilotContent: 1,
379
1509
  auth: 1,
380
1510
  device: 1,
381
1511
  toolbox: 1
@@ -388,6 +1518,9 @@ var BridgeServer = class {
388
1518
  this.writeEvent(event, params);
389
1519
  });
390
1520
  this.skillRepoHandler = opts.skillRepoHandler;
1521
+ this.copilotContentHandler = opts.skillRepoHandler ? new CopilotContentBridgeHandler(opts.skillRepoHandler.copilotContentDependencies) : void 0;
1522
+ this.copilotCustomizationsHandler = opts.skillRepoHandler ? new CopilotCustomizationsBridgeHandler(opts.skillRepoHandler.copilotContentDependencies) : void 0;
1523
+ this.copilotPluginHandler = opts.skillRepoHandler ? new CopilotPluginBridgeHandler() : void 0;
391
1524
  this.authHandler = new AuthBridgeHandlers();
392
1525
  this.deviceHandler = new DeviceBridgeHandlers();
393
1526
  this.toolboxHandler = new ToolboxBridgeHandlers();
@@ -397,12 +1530,12 @@ var BridgeServer = class {
397
1530
  input: process.stdin,
398
1531
  crlfDelay: Number.POSITIVE_INFINITY
399
1532
  });
400
- await new Promise((resolve) => {
1533
+ await new Promise((resolve3) => {
401
1534
  reader.on("line", (line) => {
402
1535
  void this.handleLine(line);
403
1536
  });
404
1537
  reader.on("close", () => {
405
- resolve();
1538
+ resolve3();
406
1539
  });
407
1540
  });
408
1541
  }
@@ -493,6 +1626,7 @@ var BridgeServer = class {
493
1626
  this.writeSuccess(request.id, result);
494
1627
  return;
495
1628
  }
1629
+ case "copilotContent.list":
496
1630
  case "skillRepo.list": {
497
1631
  const r = request;
498
1632
  if (!this.skillRepoHandler) return this.unsupportedV2(request.id);
@@ -500,6 +1634,7 @@ var BridgeServer = class {
500
1634
  this.writeSuccess(request.id, result);
501
1635
  return;
502
1636
  }
1637
+ case "copilotContent.get":
503
1638
  case "skillRepo.get": {
504
1639
  const r = request;
505
1640
  if (!this.skillRepoHandler) return this.unsupportedV2(request.id);
@@ -507,6 +1642,7 @@ var BridgeServer = class {
507
1642
  this.writeSuccess(request.id, result);
508
1643
  return;
509
1644
  }
1645
+ case "copilotContent.install":
510
1646
  case "skillRepo.install": {
511
1647
  const r = request;
512
1648
  if (!this.skillRepoHandler) return this.unsupportedV2(request.id);
@@ -514,6 +1650,7 @@ var BridgeServer = class {
514
1650
  this.writeSuccess(request.id, result);
515
1651
  return;
516
1652
  }
1653
+ case "copilotContent.convertToSymlink":
517
1654
  case "skillRepo.convertToSymlink": {
518
1655
  const r = request;
519
1656
  if (!this.skillRepoHandler) return this.unsupportedV2(request.id);
@@ -521,6 +1658,7 @@ var BridgeServer = class {
521
1658
  this.writeSuccess(request.id, result);
522
1659
  return;
523
1660
  }
1661
+ case "copilotContent.uninstall":
524
1662
  case "skillRepo.uninstall": {
525
1663
  const r = request;
526
1664
  if (!this.skillRepoHandler) return this.unsupportedV2(request.id);
@@ -528,6 +1666,29 @@ var BridgeServer = class {
528
1666
  this.writeSuccess(request.id, result);
529
1667
  return;
530
1668
  }
1669
+ case "copilotContent.setEntryEnabled":
1670
+ case "skillRepo.setEntryEnabled": {
1671
+ const r = request;
1672
+ if (!this.skillRepoHandler) return this.unsupportedV2(request.id);
1673
+ const result = await this.skillRepoHandler.setEntryEnabled(r.params);
1674
+ this.writeSuccess(request.id, result);
1675
+ return;
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
+ }
1691
+ case "copilotContent.listLinked":
531
1692
  case "skillRepo.listLinked": {
532
1693
  const r = request;
533
1694
  if (!this.skillRepoHandler) return this.unsupportedV2(request.id);
@@ -535,6 +1696,7 @@ var BridgeServer = class {
535
1696
  this.writeSuccess(request.id, result);
536
1697
  return;
537
1698
  }
1699
+ case "copilotContent.draft.create":
538
1700
  case "skillRepo.draft.create": {
539
1701
  const r = request;
540
1702
  if (!this.skillRepoHandler) return this.unsupportedV2(request.id);
@@ -542,6 +1704,7 @@ var BridgeServer = class {
542
1704
  this.writeSuccess(request.id, result);
543
1705
  return;
544
1706
  }
1707
+ case "copilotContent.draft.commit":
545
1708
  case "skillRepo.draft.commit": {
546
1709
  const r = request;
547
1710
  if (!this.skillRepoHandler) return this.unsupportedV2(request.id);
@@ -549,6 +1712,7 @@ var BridgeServer = class {
549
1712
  this.writeSuccess(request.id, result);
550
1713
  return;
551
1714
  }
1715
+ case "copilotContent.draft.list":
552
1716
  case "skillRepo.draft.list": {
553
1717
  const r = request;
554
1718
  if (!this.skillRepoHandler) return this.unsupportedV2(request.id);
@@ -556,6 +1720,7 @@ var BridgeServer = class {
556
1720
  this.writeSuccess(request.id, result);
557
1721
  return;
558
1722
  }
1723
+ case "copilotContent.draft.delete":
559
1724
  case "skillRepo.draft.delete": {
560
1725
  const r = request;
561
1726
  if (!this.skillRepoHandler) return this.unsupportedV2(request.id);
@@ -619,6 +1784,140 @@ var BridgeServer = class {
619
1784
  this.writeSuccess(request.id, result);
620
1785
  return;
621
1786
  }
1787
+ case "repo.resetParseCache": {
1788
+ const r = request;
1789
+ if (!this.skillRepoHandler) return this.unsupportedV2(request.id);
1790
+ const result = await this.skillRepoHandler.repoResetParseCache(r.params);
1791
+ this.writeSuccess(request.id, result);
1792
+ return;
1793
+ }
1794
+ case "copilotPlugin.list": {
1795
+ const r = request;
1796
+ if (!this.copilotPluginHandler) return this.unsupportedV2(request.id);
1797
+ const listResult = await this.copilotPluginHandler.list(r.params);
1798
+ this.writeSuccess(request.id, listResult);
1799
+ return;
1800
+ }
1801
+ case "copilotPlugin.register": {
1802
+ const r = request;
1803
+ if (!this.copilotPluginHandler) return this.unsupportedV2(request.id);
1804
+ const regResult = await this.copilotPluginHandler.register(r.params);
1805
+ this.writeSuccess(request.id, regResult);
1806
+ return;
1807
+ }
1808
+ case "copilotPlugin.unregister": {
1809
+ const r = request;
1810
+ if (!this.copilotPluginHandler) return this.unsupportedV2(request.id);
1811
+ const unregResult = await this.copilotPluginHandler.unregister(r.params);
1812
+ this.writeSuccess(request.id, unregResult);
1813
+ return;
1814
+ }
1815
+ // ── copilotContent.* (declarative reconciliation) ──────────────
1816
+ case "copilotContent.status": {
1817
+ const r = request;
1818
+ if (!this.copilotContentHandler) return this.unsupportedCopilotContent(request.id);
1819
+ const result = await this.copilotContentHandler.status(r.params);
1820
+ this.writeSuccess(request.id, result);
1821
+ return;
1822
+ }
1823
+ case "copilotContent.restore": {
1824
+ const r = request;
1825
+ if (!this.copilotContentHandler) return this.unsupportedCopilotContent(request.id);
1826
+ const result = await this.copilotContentHandler.restore(r.params);
1827
+ this.writeSuccess(request.id, result);
1828
+ return;
1829
+ }
1830
+ case "copilotContent.approve": {
1831
+ const r = request;
1832
+ if (!this.copilotContentHandler) return this.unsupportedCopilotContent(request.id);
1833
+ const result = await this.copilotContentHandler.approve(r.params);
1834
+ this.writeSuccess(request.id, result);
1835
+ return;
1836
+ }
1837
+ case "copilotContent.migrateLegacy": {
1838
+ const r = request;
1839
+ if (!this.copilotContentHandler) return this.unsupportedCopilotContent(request.id);
1840
+ const result = await this.copilotContentHandler.migrateLegacy(r.params);
1841
+ this.writeSuccess(request.id, result);
1842
+ return;
1843
+ }
1844
+ case "copilotContent.integrationStatus": {
1845
+ const r = request;
1846
+ if (!this.copilotContentHandler) return this.unsupportedCopilotContent(request.id);
1847
+ const result = await this.copilotContentHandler.integrationStatus(r.params);
1848
+ this.writeSuccess(request.id, result);
1849
+ return;
1850
+ }
1851
+ case "copilotContent.customizations.list": {
1852
+ const r = request;
1853
+ if (!this.copilotCustomizationsHandler) return this.unsupportedCopilotContent(request.id);
1854
+ const result = await this.copilotCustomizationsHandler.list(r.params);
1855
+ this.writeSuccess(request.id, result);
1856
+ return;
1857
+ }
1858
+ case "copilotContent.package.install": {
1859
+ const r = request;
1860
+ if (!this.copilotCustomizationsHandler) return this.unsupportedCopilotContent(request.id);
1861
+ const result = await this.copilotCustomizationsHandler.install(r.params);
1862
+ this.writeSuccess(request.id, result);
1863
+ return;
1864
+ }
1865
+ case "copilotContent.package.update": {
1866
+ const r = request;
1867
+ if (!this.copilotCustomizationsHandler) return this.unsupportedCopilotContent(request.id);
1868
+ const result = await this.copilotCustomizationsHandler.update(r.params);
1869
+ this.writeSuccess(request.id, result);
1870
+ return;
1871
+ }
1872
+ case "copilotContent.package.uninstall": {
1873
+ const r = request;
1874
+ if (!this.copilotCustomizationsHandler) return this.unsupportedCopilotContent(request.id);
1875
+ const result = await this.copilotCustomizationsHandler.uninstall(r.params);
1876
+ this.writeSuccess(request.id, result);
1877
+ return;
1878
+ }
1879
+ case "copilotContent.package.move": {
1880
+ const r = request;
1881
+ if (!this.copilotCustomizationsHandler) return this.unsupportedCopilotContent(request.id);
1882
+ const result = await this.copilotCustomizationsHandler.move(r.params);
1883
+ this.writeSuccess(request.id, result);
1884
+ return;
1885
+ }
1886
+ case "copilotContent.legacy.preview": {
1887
+ const r = request;
1888
+ if (!this.copilotCustomizationsHandler) return this.unsupportedCopilotContent(request.id);
1889
+ const result = await this.copilotCustomizationsHandler.legacyPreview(r.params);
1890
+ this.writeSuccess(request.id, result);
1891
+ return;
1892
+ }
1893
+ case "copilotContent.legacy.migrate": {
1894
+ const r = request;
1895
+ if (!this.copilotCustomizationsHandler) return this.unsupportedCopilotContent(request.id);
1896
+ const result = await this.copilotCustomizationsHandler.legacyMigrate(r.params);
1897
+ this.writeSuccess(request.id, result);
1898
+ return;
1899
+ }
1900
+ case "copilotContent.sources.list": {
1901
+ const r = request;
1902
+ if (!this.copilotCustomizationsHandler) return this.unsupportedCopilotContent(request.id);
1903
+ const result = await this.copilotCustomizationsHandler.sources(r.params);
1904
+ this.writeSuccess(request.id, result);
1905
+ return;
1906
+ }
1907
+ case "copilotContent.sources.remove": {
1908
+ const r = request;
1909
+ if (!this.copilotCustomizationsHandler) return this.unsupportedCopilotContent(request.id);
1910
+ const result = await this.copilotCustomizationsHandler.removeSource(r.params);
1911
+ this.writeSuccess(request.id, result);
1912
+ return;
1913
+ }
1914
+ case "copilotContent.package.previewUpdate": {
1915
+ const r = request;
1916
+ if (!this.copilotCustomizationsHandler) return this.unsupportedCopilotContent(request.id);
1917
+ const result = await this.copilotCustomizationsHandler.previewUpdate(r.params);
1918
+ this.writeSuccess(request.id, result);
1919
+ return;
1920
+ }
622
1921
  // ── auth.* (Phase 5.4) ─────────────────────────────────────────
623
1922
  case "auth.status": {
624
1923
  const r = request;
@@ -722,6 +2021,15 @@ var BridgeServer = class {
722
2021
  )
723
2022
  );
724
2023
  }
2024
+ unsupportedCopilotContent(id) {
2025
+ this.writeError(
2026
+ id,
2027
+ createServicemeError(
2028
+ "invalid_params",
2029
+ "copilotContent.* methods require v2 CLI deps; the bridge was started without a CopilotContentBridgeHandler."
2030
+ )
2031
+ );
2032
+ }
725
2033
  writeError(id, error) {
726
2034
  writeBridgeMessage({
727
2035
  protocolVersion: SERVICEME_PROTOCOL_VERSION,