@serviceme/devtools-cli 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/bridgeServer.js +1237 -10
  2. package/dist/cli.js +1638 -161
  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,23 @@ 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.listLinked",
63
+ "copilotContent.draft.create",
64
+ "copilotContent.draft.commit",
65
+ "copilotContent.draft.list",
66
+ "copilotContent.draft.delete",
56
67
  "skillRepo.list",
57
68
  "skillRepo.get",
58
69
  "skillRepo.install",
59
70
  "skillRepo.convertToSymlink",
60
71
  "skillRepo.uninstall",
72
+ "skillRepo.setEntryEnabled",
61
73
  "skillRepo.listLinked",
62
74
  "skillRepo.draft.create",
63
75
  "skillRepo.draft.commit",
@@ -71,6 +83,25 @@ var BRIDGE_METHODS = [
71
83
  "repo.update",
72
84
  "repo.sync",
73
85
  "repo.syncAll",
86
+ "repo.resetParseCache",
87
+ "copilotContent.status",
88
+ "copilotContent.restore",
89
+ "copilotContent.approve",
90
+ "copilotContent.migrateLegacy",
91
+ "copilotContent.integrationStatus",
92
+ "copilotContent.customizations.list",
93
+ "copilotContent.package.install",
94
+ "copilotContent.package.update",
95
+ "copilotContent.package.uninstall",
96
+ "copilotContent.package.move",
97
+ "copilotPlugin.list",
98
+ "copilotPlugin.register",
99
+ "copilotPlugin.unregister",
100
+ "copilotContent.legacy.preview",
101
+ "copilotContent.legacy.migrate",
102
+ "copilotContent.sources.list",
103
+ "copilotContent.sources.remove",
104
+ "copilotContent.package.previewUpdate",
74
105
  "auth.status",
75
106
  "auth.login",
76
107
  "auth.logout",
@@ -88,7 +119,7 @@ function isBridgeMethod(value) {
88
119
  return typeof value === "string" && BRIDGE_METHODS.includes(value);
89
120
  }
90
121
  function isBridgeRequest(value) {
91
- if (!isRecord(value)) {
122
+ if (!isRecord2(value)) {
92
123
  return false;
93
124
  }
94
125
  return isValidProtocolVersion(value.protocolVersion) && value.kind === "request" && typeof value.id === "string" && isBridgeMethod(value.method) && "params" in value;
@@ -140,14 +171,14 @@ var RETRYABLE_ERROR_CODES = /* @__PURE__ */ new Set([
140
171
  "executor_timeout",
141
172
  "internal_error"
142
173
  ]);
143
- function isRecord2(value) {
174
+ function isRecord3(value) {
144
175
  return typeof value === "object" && value !== null;
145
176
  }
146
177
  function isServicemeErrorCode(value) {
147
178
  return typeof value === "string" && SERVICEME_ERROR_CODES.includes(value);
148
179
  }
149
180
  function isServicemeErrorDetails(value) {
150
- if (!isRecord2(value)) {
181
+ if (!isRecord3(value)) {
151
182
  return false;
152
183
  }
153
184
  return isServicemeErrorCode(value.code) && typeof value.message === "string" && typeof value.retryable === "boolean";
@@ -187,7 +218,7 @@ function normalizeServicemeError(error, fallbackCode = "internal_error") {
187
218
  if (isServicemeErrorDetails(error)) {
188
219
  return error;
189
220
  }
190
- if (isRecord2(error)) {
221
+ if (isRecord3(error)) {
191
222
  const code = isServicemeErrorCode(error.code) ? error.code : fallbackCode;
192
223
  const message = typeof error.message === "string" ? error.message : "Unexpected serviceme error.";
193
224
  const retryable = typeof error.retryable === "boolean" ? error.retryable : isRetryableErrorCode(code);
@@ -213,7 +244,1038 @@ function normalizeServicemeError(error, fallbackCode = "internal_error") {
213
244
  }
214
245
 
215
246
  // src/version.ts
216
- var SERVICEME_CLI_VERSION = "1.0.0";
247
+ var SERVICEME_CLI_VERSION = "2.0.0";
248
+
249
+ // src/bridge/CopilotContentBridgeHandler.ts
250
+ var fsp = __toESM(require("fs/promises"));
251
+ var path = __toESM(require("path"));
252
+ var import_devtools_core = require("@serviceme/devtools-core");
253
+ var CopilotContentBridgeHandler = class {
254
+ constructor(opts) {
255
+ this.repoManager = new import_devtools_core.RepoManager({
256
+ store: opts.store,
257
+ gitClient: opts.gitClient,
258
+ skipClone: opts.skipClone ?? false
259
+ });
260
+ }
261
+ /** Ensure every declared repository is checked out at its pinned commit. */
262
+ async ensureRepositories(manifest) {
263
+ const sources = /* @__PURE__ */ new Map();
264
+ for (const source of (0, import_devtools_core.getWorkspaceManifestSources)(
265
+ manifest ?? { version: 2, sources: [], plugins: [] }
266
+ )) {
267
+ if (source.type === "catalog") {
268
+ const localPath = path.resolve((0, import_devtools_core.getReposDir)(), source.id);
269
+ const stat3 = await fsp.stat(localPath).catch(() => null);
270
+ sources.set(
271
+ source.id,
272
+ stat3?.isDirectory() ? { ready: true } : {
273
+ ready: false,
274
+ error: "Source not materialized under ~/.serviceme/repos"
275
+ }
276
+ );
277
+ continue;
278
+ }
279
+ try {
280
+ await this.repoManager.ensureAtCommit({
281
+ repository: {
282
+ id: source.id,
283
+ url: source.url,
284
+ useProxy: true
285
+ },
286
+ commit: source.commit
287
+ });
288
+ sources.set(source.id, { ready: true });
289
+ } catch (error) {
290
+ sources.set(source.id, {
291
+ ready: false,
292
+ error: error instanceof Error ? error.message : String(error)
293
+ });
294
+ }
295
+ }
296
+ return sources;
297
+ }
298
+ async reconcile(workspaceDir) {
299
+ const reconciler = await this.makeReconciler(workspaceDir);
300
+ return reconciler.reconcile();
301
+ }
302
+ /**
303
+ * Reconciler with machine-local disable marks applied: identities
304
+ * disabled for THIS workspace behave as if undeclared (links are
305
+ * removed and never resurrected) while the manifest stays intact.
306
+ */
307
+ async makeReconciler(workspaceDir) {
308
+ const disabledStore = new import_devtools_core.DisabledContentStore();
309
+ const marks = await disabledStore.list();
310
+ const matches = (identity) => {
311
+ const segments = identity.split("::");
312
+ if (segments.length < 3) return false;
313
+ const [repoId, pluginId, kindName] = segments;
314
+ return marks.some(
315
+ (mark) => mark.scope === "workspace" && mark.workspaceDir === workspaceDir && mark.repoId === repoId && mark.name === pluginId && kindName === mark.kind
316
+ );
317
+ };
318
+ return new import_devtools_core.WorkspaceCopilotContentReconciler({
319
+ workspaceDir,
320
+ ensureRepository: (manifest) => this.ensureRepositories(manifest),
321
+ isDisabled: async (identity) => matches(identity)
322
+ });
323
+ }
324
+ /** `copilotContent.restore` — reconcile declared content now. */
325
+ async restore(params) {
326
+ return this.reconcile(params.workspaceDir);
327
+ }
328
+ /** `copilotContent.status` — declaration + current status without mutations. */
329
+ async status(params) {
330
+ const manifest = await (0, import_devtools_core.loadWorkspaceCopilotManifest)(params.workspaceDir);
331
+ if (!manifest) {
332
+ return { changed: false, entries: [] };
333
+ }
334
+ return this.reconcile(params.workspaceDir);
335
+ }
336
+ /** `copilotContent.approve` — record local approval, then re-reconcile. */
337
+ async approve(params) {
338
+ const reconciler = await this.makeReconciler(params.workspaceDir);
339
+ return reconciler.approve({ identities: params.identities });
340
+ }
341
+ /** `copilotContent.migrateLegacy` — report legacy ~/.agents links for migration. */
342
+ async migrateLegacy(params) {
343
+ const manifest = await (0, import_devtools_core.loadWorkspaceCopilotManifest)(params.workspaceDir);
344
+ if (!manifest) {
345
+ return { entries: [] };
346
+ }
347
+ const plan = await (0, import_devtools_core.resolveWorkspaceContentPlan)({
348
+ manifest,
349
+ reposDir: (0, import_devtools_core.getReposDir)()
350
+ });
351
+ const materializer = new import_devtools_core.CopilotLinkMaterializer();
352
+ const entries = [];
353
+ for (const entry of plan.entries) {
354
+ const legacy = await materializer.inspectLegacyUserLink({
355
+ homeDir: (0, import_devtools_core.getServicemeHome)(),
356
+ entry
357
+ });
358
+ entries.push({
359
+ identity: legacy.identity,
360
+ status: legacy.status,
361
+ message: legacy.message
362
+ });
363
+ }
364
+ return { entries };
365
+ }
366
+ /**
367
+ * `copilotContent.integrationStatus` — read-only diagnostics for the
368
+ * machine-local MCP / hook integrations of a declared workspace.
369
+ *
370
+ * The state store provides the declared identities and approval flags;
371
+ * the two generated config files and their `_serviceme` ownership maps
372
+ * prove which names are actually present on this machine. This method
373
+ * never mutates state or configuration.
374
+ */
375
+ async integrationStatus(params) {
376
+ const state = await new import_devtools_core.WorkspaceContentStateStore({
377
+ workspaceDir: params.workspaceDir
378
+ }).read();
379
+ const mcp = await readOwnershipConfig(
380
+ path.join(params.workspaceDir, ".vscode", "mcp.serviceme.json")
381
+ );
382
+ const hooks = await readOwnershipConfig(
383
+ path.join(params.workspaceDir, ".vscode", "hooks.serviceme.json")
384
+ );
385
+ const integrations = [];
386
+ for (const entry of state.entries) {
387
+ if (entry.kind !== "mcp" && entry.kind !== "hook") continue;
388
+ const config = entry.kind === "mcp" ? mcp : hooks;
389
+ const owned = config.ownership[entry.identity] ?? [];
390
+ const active = config.exists && owned.length > 0;
391
+ const status = active ? "active" : entry.approved ? "missing_local_configuration" : "pending_approval";
392
+ integrations.push({
393
+ identity: entry.identity,
394
+ kind: entry.kind,
395
+ configPath: config.configPath,
396
+ names: owned,
397
+ status
398
+ });
399
+ }
400
+ return { integrations };
401
+ }
402
+ };
403
+ async function readOwnershipConfig(configPath) {
404
+ try {
405
+ const raw = await fsp.readFile(configPath, "utf8");
406
+ const parsed = JSON.parse(raw);
407
+ const ownershipNode = parsed._serviceme;
408
+ const ownership = ownershipNode && typeof ownershipNode === "object" ? normalizeOwnership(ownershipNode) ?? {} : {};
409
+ return { exists: true, configPath, ownership };
410
+ } catch (error) {
411
+ if (error.code === "ENOENT") {
412
+ return { exists: false, configPath, ownership: {} };
413
+ }
414
+ throw error;
415
+ }
416
+ }
417
+ function normalizeOwnership(node) {
418
+ if (node === null || typeof node !== "object" || Array.isArray(node)) {
419
+ return void 0;
420
+ }
421
+ const result = {};
422
+ for (const [identity, names] of Object.entries(node)) {
423
+ if (Array.isArray(names) && names.every((name) => typeof name === "string")) {
424
+ result[identity] = names;
425
+ }
426
+ }
427
+ return result;
428
+ }
429
+
430
+ // src/bridge/CopilotCustomizationsBridgeHandler.ts
431
+ var fsp2 = __toESM(require("fs/promises"));
432
+ var path2 = __toESM(require("path"));
433
+ var import_devtools_core2 = require("@serviceme/devtools-core");
434
+ var import_skill_linker = require("@serviceme/devtools-core/skill-linker");
435
+ function createCopilotCustomizationsProductionDeps(options) {
436
+ return {
437
+ snapshot: async (params) => {
438
+ if (params.scope === "personal") {
439
+ return createFullPersonalSnapshot(options, await options.readers.listPersonalLinks());
440
+ }
441
+ return createWorkspaceSnapshot(
442
+ options.repoDisplayName,
443
+ options.readers,
444
+ params.workspaceDir ?? process.cwd()
445
+ );
446
+ }
447
+ };
448
+ }
449
+ var CopilotCustomizationsBridgeHandler = class {
450
+ constructor(deps) {
451
+ if ("snapshot" in deps && typeof deps.snapshot === "function") {
452
+ this.snapshot = deps.snapshot;
453
+ this.listAvailablePackages = deps.listAvailablePackages;
454
+ this.mutations = createUnimplementedMutations(
455
+ "Injected snapshot deps do not support package mutations"
456
+ );
457
+ } else if ("store" in deps && deps.store !== void 0) {
458
+ this.snapshot = createCopilotCustomizationsProductionDeps({
459
+ repoDisplayName: (repoId) => deps.store.get(repoId)?.name,
460
+ readers: createDefaultProductionReaders(),
461
+ personal: createDefaultPersonalSnapshotInput(deps.store)
462
+ }).snapshot;
463
+ this.mutations = createProductionMutations(deps.store);
464
+ this.listAvailablePackages = createAvailablePackagesEnumerator(deps.store);
465
+ } else {
466
+ this.snapshot = async () => emptySnapshot();
467
+ this.mutations = createUnimplementedMutations(
468
+ "Package mutations require production dependencies"
469
+ );
470
+ }
471
+ }
472
+ async list(params) {
473
+ const snapshot = await this.snapshot(params);
474
+ const shared = snapshot.workspaceManifest !== void 0 ? { workspaceManifest: snapshot.workspaceManifest } : {};
475
+ const availablePackages = await this.listAvailablePackages?.(params, shared).catch(
476
+ () => void 0
477
+ );
478
+ return {
479
+ view: (0, import_devtools_core2.buildCopilotCustomizationView)({
480
+ scope: params.scope,
481
+ sources: snapshot.sources.map(toPublicSource),
482
+ packages: snapshot.packages.map(toPublicPackage),
483
+ installations: snapshot.installations.map(toPublicInstallation),
484
+ statesByArtifactId: toPublicStates(snapshot.statesByArtifactId),
485
+ legacyCount: snapshot.legacyCount,
486
+ generatedAt: snapshot.generatedAt
487
+ }),
488
+ ...availablePackages ? { availablePackages } : {}
489
+ };
490
+ }
491
+ async install(params) {
492
+ return this.mutations.install(params);
493
+ }
494
+ async update(params) {
495
+ return this.mutations.update(params);
496
+ }
497
+ async uninstall(params) {
498
+ const result = await this.mutations.uninstall(params);
499
+ try {
500
+ const registrar = new import_devtools_core2.CopilotPluginRegistrar({
501
+ copilotDir: path2.join((0, import_devtools_core2.getHomeDir)(), ".copilot")
502
+ });
503
+ await registrar.unregister({
504
+ registrationId: params.packageId.replace("::", ":"),
505
+ scope: params.scope
506
+ });
507
+ const otherScope = params.scope === "personal" ? "workspace" : "personal";
508
+ await registrar.unregister({
509
+ registrationId: params.packageId.replace("::", ":"),
510
+ scope: otherScope
511
+ });
512
+ } catch {
513
+ }
514
+ return result;
515
+ }
516
+ async move(params) {
517
+ return this.mutations.move(params);
518
+ }
519
+ async legacyPreview(params) {
520
+ return this.mutations.legacyPreview(params);
521
+ }
522
+ async legacyMigrate(params) {
523
+ return this.mutations.legacyMigrate(params);
524
+ }
525
+ async sources(params) {
526
+ return this.mutations.sources(params);
527
+ }
528
+ async removeSource(params) {
529
+ return this.mutations.removeSource(params);
530
+ }
531
+ async previewUpdate(params) {
532
+ return this.mutations.previewUpdate(params);
533
+ }
534
+ };
535
+ function createDefaultPersonalSnapshotInput(store) {
536
+ return {
537
+ // Capabilities are intentionally omitted: createFullPersonalSnapshot
538
+ // falls back to the real default host-capability provider, which
539
+ // claims the verified ~/.copilot agent/skill targets. Pinning an
540
+ // empty personalTargets map here regressed every link artifact to
541
+ // unsupported. The explicit package resolver is the part Task 8
542
+ // flagged as silently missing.
543
+ resolvePersonalPackage: (packageId) => resolvePersonalPackageFromRepos(packageId, store)
544
+ };
545
+ }
546
+ async function resolvePersonalPackageFromRepos(packageId, store) {
547
+ const [sourceId, pluginId] = packageId.split("::");
548
+ if (!sourceId || !pluginId) return [];
549
+ const repoRoot = path2.resolve((0, import_devtools_core2.getReposDir)(), sourceId);
550
+ const stat3 = await fsp2.stat(repoRoot).catch(() => null);
551
+ if (!stat3?.isDirectory()) return [];
552
+ const repo = store.get(sourceId);
553
+ const manifest = {
554
+ version: 1,
555
+ repositories: [
556
+ {
557
+ id: sourceId,
558
+ url: repo?.url ?? `https://serviceme.local/catalog/${encodeURIComponent(sourceId)}`,
559
+ commit: repo?.lastSyncCommitSha ?? "".padEnd(40, "0")
560
+ }
561
+ ],
562
+ plugins: [
563
+ {
564
+ repository: sourceId,
565
+ id: pluginId,
566
+ artifacts: {
567
+ agent: true,
568
+ skill: true,
569
+ instruction: true,
570
+ prompt: true,
571
+ hook: true,
572
+ mcp: true
573
+ }
574
+ }
575
+ ]
576
+ };
577
+ const resolved = await (0, import_devtools_core2.resolveWorkspaceContentPlan)({
578
+ manifest,
579
+ reposDir: (0, import_devtools_core2.getReposDir)()
580
+ }).catch(() => ({ entries: [] }));
581
+ return resolved.entries;
582
+ }
583
+ function createAvailablePackagesEnumerator(store) {
584
+ const catalog = (0, import_devtools_core2.createPluginCatalogService)();
585
+ const personalStore = new import_devtools_core2.PersonalInstallationStore({});
586
+ const pluginRegistrar = new import_devtools_core2.CopilotPluginRegistrar({
587
+ copilotDir: path2.join((0, import_devtools_core2.getHomeDir)(), ".copilot")
588
+ });
589
+ return async (params, shared) => {
590
+ const repos = store.list().filter((repo) => repo.enabled).map((repo) => ({ id: repo.id }));
591
+ if (repos.length === 0) return [];
592
+ const catalogPackages = await catalog.list({
593
+ reposDir: (0, import_devtools_core2.getReposDir)(),
594
+ repos
595
+ });
596
+ const [workspaceManifest, personalIntent, registrations] = await Promise.all([
597
+ // Shared read from the same list() call when provided; only a
598
+ // standalone enumerator invocation (personal scope, tests,
599
+ // direct calls) hits the disk here.
600
+ shared?.workspaceManifest !== void 0 ? Promise.resolve(shared.workspaceManifest) : params.workspaceDir ? (0, import_devtools_core2.loadWorkspaceCopilotManifest)(params.workspaceDir).catch(() => void 0) : Promise.resolve(void 0),
601
+ personalStore.read().catch(() => void 0),
602
+ // Whole-package registry — prune-on-list keeps it clean.
603
+ pluginRegistrar.list({}).catch(() => [])
604
+ ]);
605
+ const manifest = workspaceManifest ?? {
606
+ version: 1,
607
+ repositories: [],
608
+ plugins: []
609
+ };
610
+ const workspaceInstalled = new Set(
611
+ manifest.plugins.map(
612
+ (plugin) => `${(0, import_devtools_core2.getWorkspaceManifestPluginSourceId)(manifest, plugin)}::${plugin.id}`
613
+ )
614
+ );
615
+ const personalInstalled = new Set(
616
+ (personalIntent?.installations ?? []).filter((installation) => installation.scope === "personal").map((installation) => installation.packageId)
617
+ );
618
+ const registeredScopes = /* @__PURE__ */ new Map();
619
+ for (const reg of registrations) {
620
+ const packageId = `${reg.repoId}::${reg.pluginId}`;
621
+ const scopes = registeredScopes.get(packageId) ?? /* @__PURE__ */ new Set();
622
+ for (const scope of reg.scopes) scopes.add(scope);
623
+ registeredScopes.set(packageId, scopes);
624
+ }
625
+ return catalogPackages.map((pkg) => {
626
+ const installed = registeredScopes.get(pkg.packageId) !== void 0 || workspaceInstalled.has(pkg.packageId) || personalInstalled.has(pkg.packageId);
627
+ return {
628
+ ...pkg,
629
+ installedScopes: installed ? ["personal"] : []
630
+ };
631
+ });
632
+ };
633
+ }
634
+ function createProductionMutations(store) {
635
+ let servicePromise;
636
+ let sourceCatalogPromise;
637
+ const service = () => servicePromise ??= createPackageInstallationService(store);
638
+ const sourceCatalog = () => sourceCatalogPromise ??= createSourceCatalogService(store);
639
+ return {
640
+ install: async (params) => toResult(await (await service()).install(params)),
641
+ update: async (params) => toResult(await (await service()).update(params)),
642
+ uninstall: async (params) => toResult(await (await service()).uninstall(params)),
643
+ move: async (params) => toResult(await (await service()).move(params)),
644
+ legacyPreview: async () => {
645
+ const preview = await (await service()).previewLegacy();
646
+ return {
647
+ sourceLabel: preview.sourceLabel,
648
+ count: preview.entries.length,
649
+ artifacts: preview.entries.map((entry) => ({
650
+ id: entry.artifactId,
651
+ packageId: "legacy",
652
+ kind: entry.kind,
653
+ displayName: entry.name,
654
+ installStrategy: "link",
655
+ risk: "none"
656
+ }))
657
+ };
658
+ },
659
+ legacyMigrate: async (params) => toResult(await (await service()).migrateLegacy(params)),
660
+ sources: async (params) => {
661
+ const records = await (await sourceCatalog()).listSources({
662
+ scope: params.scope,
663
+ ...params.workspaceDir ? { workspaceDir: params.workspaceDir } : {}
664
+ });
665
+ return {
666
+ sources: records.map((record) => ({
667
+ id: record.id,
668
+ type: record.type,
669
+ displayName: record.displayName,
670
+ updateCapability: record.updateCapability,
671
+ available: record.available,
672
+ declaredIn: [...record.declaredIn]
673
+ }))
674
+ };
675
+ },
676
+ removeSource: async (params) => {
677
+ await (await sourceCatalog()).removeSource(params.sourceId, params.workspaceDir);
678
+ return { removed: true };
679
+ },
680
+ previewUpdate: async (params) => ({
681
+ preview: await (await sourceCatalog()).previewUpdate(params)
682
+ })
683
+ };
684
+ }
685
+ async function createSourceCatalogService(store) {
686
+ const personalStore = new import_devtools_core2.PersonalInstallationStore({});
687
+ const loadManifest = async (workspaceDir) => {
688
+ return (0, import_devtools_core2.loadWorkspaceCopilotManifest)(workspaceDir).catch(() => void 0);
689
+ };
690
+ return new import_devtools_core2.CopilotSourceCatalogService({
691
+ personalStore,
692
+ // The source manager is the single repository management surface;
693
+ // surface every ReposStore entry (built-in defaults + user git
694
+ // repos) so they can be synced / toggled / edited from there even
695
+ // when no installation or workspace declaration references them.
696
+ // Read per call: repos.json can change under the long-lived bridge.
697
+ listStoreSources: async () => store.list().map((repo) => ({
698
+ id: repo.id,
699
+ name: repo.name,
700
+ enabled: repo.enabled
701
+ })),
702
+ resolvePackage: (packageId) => resolvePersonalPackageFromRepos(packageId, store),
703
+ resolveActualRevision: async (packageId) => {
704
+ const [sourceId] = packageId.split("::");
705
+ if (!sourceId) return void 0;
706
+ const repo = store.get(sourceId);
707
+ if (repo?.lastSyncCommitSha) return repo.lastSyncCommitSha;
708
+ const repoDir = path2.resolve((0, import_devtools_core2.getReposDir)(), sourceId);
709
+ const head = await new import_devtools_core2.GitClient({ serverProxyBase: "" }).revParseHead(repoDir).catch(() => void 0);
710
+ return head;
711
+ },
712
+ listWorkspaceSources: async (workspaceDir) => {
713
+ const manifest = await loadManifest(workspaceDir);
714
+ if (!manifest) return [];
715
+ return (0, import_devtools_core2.getWorkspaceManifestSources)(manifest).map((source) => source.id);
716
+ },
717
+ readWorkspaceInstallations: async (workspaceDir) => {
718
+ const manifest = await loadManifest(workspaceDir);
719
+ if (!manifest) return [];
720
+ return manifest.plugins.map((plugin) => {
721
+ const sourceId = (0, import_devtools_core2.getWorkspaceManifestPluginSourceId)(manifest, plugin);
722
+ const source = (0, import_devtools_core2.getWorkspaceManifestSources)(manifest).find((s) => s.id === sourceId);
723
+ return {
724
+ packageId: `${sourceId}::${plugin.id}`,
725
+ scope: "workspace",
726
+ selectedArtifactIds: Object.entries(plugin.artifacts).filter(([, enabled]) => enabled !== false).map(([kind]) => `${sourceId}::${plugin.id}::${kind}:`),
727
+ pinnedVersion: source?.type === "git" ? source.commit : void 0
728
+ };
729
+ });
730
+ },
731
+ artifactSelected: (marker, artifactId) => artifactId.startsWith(marker),
732
+ ensureCatalogSource: async (sourceId) => {
733
+ const mgr = new import_devtools_core2.RepoManager({
734
+ store,
735
+ gitClient: new import_devtools_core2.GitClient({ serverProxyBase: "" })
736
+ });
737
+ return mgr.ensureCatalogSource({
738
+ sourceId,
739
+ provider: {
740
+ // Placeholder staging: the marketplace catalog client is
741
+ // not yet wired to per-source payloads, so the first
742
+ // materialization establishes the directory contract.
743
+ // Real payload extraction lands with the catalog client.
744
+ materialize: async (targetDir) => {
745
+ await fsp2.mkdir(targetDir, { recursive: true });
746
+ }
747
+ }
748
+ });
749
+ },
750
+ isCatalogSource: async (sourceId, workspaceDir) => {
751
+ if (!workspaceDir) return false;
752
+ const declared = await loadManifest(workspaceDir);
753
+ const source = declared ? (0, import_devtools_core2.getWorkspaceManifestSources)(declared).find((s) => s.id === sourceId) : void 0;
754
+ return source?.type === "catalog";
755
+ },
756
+ hasCatalogPayload: async (localPath) => {
757
+ const pluginsDir = path2.join(localPath, "plugins");
758
+ const entries = await fsp2.readdir(pluginsDir).catch(() => []);
759
+ if (entries.length === 0) return false;
760
+ for (const entry of entries) {
761
+ const stat3 = await fsp2.stat(path2.join(pluginsDir, entry)).catch(() => null);
762
+ if (stat3?.isDirectory()) {
763
+ const manifest = await fsp2.stat(path2.join(pluginsDir, entry, "plugin.json")).catch(() => null);
764
+ if (manifest?.isFile()) return true;
765
+ }
766
+ }
767
+ return false;
768
+ }
769
+ });
770
+ }
771
+ async function createPackageInstallationService(store) {
772
+ const personalStore = new import_devtools_core2.PersonalInstallationStore({});
773
+ const personalReconciler = new import_devtools_core2.PersonalCopilotContentReconciler({
774
+ capabilities: await (0, import_devtools_core2.createDefaultCopilotHostCapabilities)(),
775
+ resolvePackage: (packageId) => resolvePersonalPackageFromRepos(packageId, store)
776
+ });
777
+ return new import_devtools_core2.PackageInstallationService({
778
+ personalStore,
779
+ personalReconciler,
780
+ userHomeDir: (0, import_devtools_core2.getHomeDir)(),
781
+ resolvePackage: (packageId) => resolvePersonalPackageFromRepos(packageId, store),
782
+ resolveWorkspaceSource: async (sourceId) => {
783
+ const repo = store.get(sourceId);
784
+ if (!repo) return void 0;
785
+ const repoDir = path2.resolve((0, import_devtools_core2.getReposDir)(), sourceId);
786
+ const stat3 = await fsp2.stat(repoDir).catch(() => null);
787
+ if (!stat3?.isDirectory()) return void 0;
788
+ return {
789
+ id: sourceId,
790
+ url: repo.url,
791
+ commit: repo.lastSyncCommitSha ?? "0".repeat(40)
792
+ };
793
+ },
794
+ createWorkspaceReconciler: (workspaceDir) => new import_devtools_core2.WorkspaceCopilotContentReconciler({ workspaceDir }),
795
+ readWorkspaceView: async (workspaceDir) => {
796
+ const snapshot = await createWorkspaceSnapshot(
797
+ (repoId) => store.get(repoId)?.name,
798
+ createDefaultProductionReaders(),
799
+ workspaceDir
800
+ );
801
+ return (0, import_devtools_core2.buildCopilotCustomizationView)({
802
+ scope: "workspace",
803
+ sources: snapshot.sources,
804
+ packages: snapshot.packages,
805
+ installations: snapshot.installations,
806
+ statesByArtifactId: snapshot.statesByArtifactId,
807
+ generatedAt: snapshot.generatedAt
808
+ });
809
+ },
810
+ readWorkspaceInstallations: import_devtools_core2.readDeclaredWorkspaceInstallations
811
+ });
812
+ }
813
+ function toResult(view) {
814
+ return { view: toPublicView(view) };
815
+ }
816
+ function toPublicView(view) {
817
+ return {
818
+ scope: view.scope,
819
+ generatedAt: view.generatedAt,
820
+ sources: view.sources.map((source) => ({
821
+ ...toPublicSource(source),
822
+ packages: source.packages.map((pkg) => toPublicPackageView(pkg))
823
+ })),
824
+ packages: view.packages.map((pkg) => ({
825
+ ...toPublicPackageView(pkg)
826
+ })),
827
+ attention: view.attention.map(({ artifact, state }) => ({
828
+ artifact,
829
+ state
830
+ })),
831
+ summary: view.summary,
832
+ ...view.legacyMigration ? { legacyMigration: view.legacyMigration } : {}
833
+ };
834
+ }
835
+ function toPublicPackageView(pkg) {
836
+ return {
837
+ definition: toPublicPackage(pkg.definition),
838
+ installation: toPublicInstallation(pkg.installation),
839
+ artifacts: pkg.artifacts.map(({ artifact, state }) => ({
840
+ artifact,
841
+ state
842
+ })),
843
+ selectedArtifactCount: pkg.selectedArtifactCount,
844
+ status: pkg.status
845
+ };
846
+ }
847
+ function createUnimplementedMutations(reason) {
848
+ const reject = async () => {
849
+ throw new Error(reason);
850
+ };
851
+ return {
852
+ install: reject,
853
+ update: reject,
854
+ uninstall: reject,
855
+ move: reject,
856
+ legacyPreview: reject,
857
+ legacyMigrate: reject,
858
+ sources: reject,
859
+ removeSource: reject,
860
+ previewUpdate: reject
861
+ };
862
+ }
863
+ async function mergeRegistrarPackages(sources, packages, installations, statesByArtifactId) {
864
+ const registrar = new import_devtools_core2.CopilotPluginRegistrar({
865
+ copilotDir: path2.join((0, import_devtools_core2.getHomeDir)(), ".copilot")
866
+ });
867
+ const registrations = await registrar.list({}).catch(() => []);
868
+ if (registrations.length === 0) return;
869
+ const asMap = packages instanceof Map ? packages : new Map(packages.map((p) => [p.id, p]));
870
+ const knownIds = new Set(asMap.keys());
871
+ const pushed = packages instanceof Map ? void 0 : packages;
872
+ for (const reg of registrations) {
873
+ const packageId = `${reg.repoId}::${reg.pluginId}`;
874
+ if (knownIds.has(packageId)) continue;
875
+ const pluginJsonPath = path2.join(reg.pluginDir, "plugin.json");
876
+ let definition;
877
+ try {
878
+ const raw = JSON.parse(await fsp2.readFile(pluginJsonPath, "utf8"));
879
+ const repoRoot = path2.basename(path2.dirname(reg.pluginDir)) === "plugins" ? path2.dirname(path2.dirname(reg.pluginDir)) : reg.pluginDir;
880
+ const entries = await (0, import_devtools_core2.resolvePluginEntriesLenient)({
881
+ repoRoot,
882
+ repositoryId: reg.repoId,
883
+ pluginId: reg.pluginId,
884
+ manifest: {
885
+ name: typeof raw.name === "string" && raw.name.trim() !== "" ? raw.name : reg.pluginId,
886
+ ...typeof raw.description === "string" ? { description: raw.description } : {},
887
+ ...typeof raw.version === "string" ? { version: raw.version } : {},
888
+ extensions: raw.extensions ?? {}
889
+ }
890
+ }).catch(() => []);
891
+ const artifacts = entries.map(import_devtools_core2.toArtifactSummary);
892
+ if (artifacts.length > 0) {
893
+ definition = {
894
+ id: packageId,
895
+ sourceId: reg.repoId,
896
+ displayName: typeof raw.name === "string" && raw.name || reg.displayName || reg.pluginId,
897
+ ...typeof raw.description === "string" ? { description: raw.description } : {},
898
+ ...typeof raw.version === "string" ? { version: raw.version } : {},
899
+ artifacts,
900
+ wholePackage: true
901
+ };
902
+ for (const artifact of artifacts) {
903
+ statesByArtifactId[artifact.id] = {
904
+ intent: "selected",
905
+ health: "healthy",
906
+ gate: "ready"
907
+ };
908
+ }
909
+ } else {
910
+ const artifact = {
911
+ id: `${packageId}::package:${reg.pluginId}`,
912
+ packageId,
913
+ kind: "skill",
914
+ displayName: reg.pluginId,
915
+ installStrategy: "link",
916
+ risk: "none"
917
+ };
918
+ definition = {
919
+ id: packageId,
920
+ sourceId: reg.repoId,
921
+ displayName: typeof raw.name === "string" && raw.name || reg.displayName || reg.pluginId,
922
+ ...typeof raw.description === "string" ? { description: raw.description } : {},
923
+ ...typeof raw.version === "string" ? { version: raw.version } : {},
924
+ artifacts: [artifact],
925
+ wholePackage: true
926
+ };
927
+ statesByArtifactId[artifact.id] = {
928
+ intent: "selected",
929
+ health: "healthy",
930
+ gate: "ready"
931
+ };
932
+ }
933
+ } catch {
934
+ definition = {
935
+ id: packageId,
936
+ sourceId: reg.repoId,
937
+ displayName: reg.displayName || reg.pluginId,
938
+ ...reg.version !== void 0 ? { version: reg.version } : {},
939
+ artifacts: [],
940
+ wholePackage: true
941
+ };
942
+ }
943
+ if (pushed) {
944
+ pushed.push(definition);
945
+ } else {
946
+ asMap.set(packageId, definition);
947
+ }
948
+ if (!sources.some((source) => source.id === reg.repoId)) {
949
+ sources.push({
950
+ id: reg.repoId,
951
+ type: "git",
952
+ displayName: reg.repoId,
953
+ updateCapability: "pinned"
954
+ });
955
+ }
956
+ installations.push({
957
+ packageId,
958
+ scope: "personal",
959
+ selectedArtifactIds: definition.artifacts.map((artifact) => artifact.id)
960
+ });
961
+ }
962
+ }
963
+ async function createWorkspaceSnapshot(repoDisplayName, readers, workspaceDir) {
964
+ const manifest = await readers.loadWorkspaceManifest(workspaceDir);
965
+ if (!manifest) return emptySnapshot();
966
+ const sources = (0, import_devtools_core2.getWorkspaceManifestSources)(manifest).map(
967
+ (source) => ({
968
+ id: source.id,
969
+ type: source.type === "catalog" ? "marketplace" : "git",
970
+ displayName: source.type === "catalog" ? source.catalogId : repoDisplayName(source.id) ?? source.id,
971
+ updateCapability: "pinned"
972
+ })
973
+ );
974
+ const state = await readers.readWorkspaceState(workspaceDir);
975
+ const packages = [];
976
+ const installations = [];
977
+ const statesByArtifactId = {};
978
+ const resolvedPlugins = await Promise.all(
979
+ manifest.plugins.map(async (plugin) => {
980
+ const sourceId = (0, import_devtools_core2.getWorkspaceManifestPluginSourceId)(manifest, plugin);
981
+ const packageId = `${sourceId}::${plugin.id}`;
982
+ let fallbackHealth;
983
+ let entries;
984
+ if (!await readers.isSourceAvailable(sourceId)) {
985
+ fallbackHealth = "source-unavailable";
986
+ entries = fallbackEntries(plugin, packageId);
987
+ } else {
988
+ try {
989
+ entries = await readers.resolveWorkspacePlugin({ manifest, plugin });
990
+ } catch {
991
+ fallbackHealth = "conflict";
992
+ entries = fallbackEntries(plugin, packageId);
993
+ }
994
+ }
995
+ return { plugin, sourceId, packageId, entries, fallbackHealth };
996
+ })
997
+ );
998
+ for (const resolved of resolvedPlugins) {
999
+ const { plugin, sourceId, packageId, entries, fallbackHealth } = resolved;
1000
+ const artifacts = entries.map(import_devtools_core2.toArtifactSummary);
1001
+ packages.push({
1002
+ id: packageId,
1003
+ sourceId,
1004
+ displayName: entries[0]?.packageDisplayName ?? plugin.id,
1005
+ ...entries[0]?.packageDescription ? { description: entries[0].packageDescription } : {},
1006
+ ...entries[0]?.packageVersion ? { version: entries[0].packageVersion } : {},
1007
+ artifacts
1008
+ });
1009
+ installations.push({
1010
+ packageId,
1011
+ scope: "workspace",
1012
+ selectedArtifactIds: artifacts.map((artifact) => artifact.id)
1013
+ });
1014
+ for (const entry of entries) {
1015
+ const previous = state.entries.find((candidate) => candidate.identity === entry.artifactId);
1016
+ statesByArtifactId[entry.artifactId] = fallbackHealth ? { intent: "selected", health: fallbackHealth, gate: "ready" } : toArtifactState(entry, previous);
1017
+ }
1018
+ }
1019
+ await mergeRegistrarPackages(sources, packages, installations, statesByArtifactId);
1020
+ return { sources, packages, installations, statesByArtifactId, workspaceManifest: manifest };
1021
+ }
1022
+ async function createFullPersonalSnapshot(options, legacyLinks) {
1023
+ const capabilities = options.personal?.capabilities ?? await (0, import_devtools_core2.createDefaultCopilotHostCapabilities)();
1024
+ const homeDir = options.personal?.homeDir;
1025
+ const store = new import_devtools_core2.PersonalInstallationStore(homeDir !== void 0 ? { homeDir } : {});
1026
+ const reconciler = new import_devtools_core2.PersonalCopilotContentReconciler({
1027
+ capabilities,
1028
+ ...homeDir ? { homeDir } : {},
1029
+ ...options.personal?.resolvePersonalPackage ? { resolvePackage: options.personal.resolvePersonalPackage } : {}
1030
+ });
1031
+ const intent = await store.read();
1032
+ const inspection = await reconciler.inspect();
1033
+ const sources = [];
1034
+ const sourceIds = /* @__PURE__ */ new Set();
1035
+ const packages = /* @__PURE__ */ new Map();
1036
+ const statesByArtifactId = {
1037
+ ...inspection.statesByArtifactId
1038
+ };
1039
+ let legacyCount = 0;
1040
+ for (const link of legacyLinks) {
1041
+ if (link.legacy) legacyCount += 1;
1042
+ }
1043
+ for (const installation of intent.installations) {
1044
+ if (installation.scope !== "personal") continue;
1045
+ const resolved = options.personal ? await options.personal.resolvePersonalPackage(installation.packageId) : [];
1046
+ const artifacts = resolved.filter((entry) => installation.selectedArtifactIds.includes(entry.artifactId)).map(import_devtools_core2.toArtifactSummary);
1047
+ const [sourceId] = installation.packageId.split("::");
1048
+ if (sourceId && !sourceIds.has(sourceId)) {
1049
+ sourceIds.add(sourceId);
1050
+ sources.push({
1051
+ id: sourceId,
1052
+ type: "git",
1053
+ displayName: options.repoDisplayName(sourceId) ?? sourceId,
1054
+ updateCapability: "pinned"
1055
+ });
1056
+ }
1057
+ if (!packages.has(installation.packageId) && artifacts.length > 0) {
1058
+ packages.set(installation.packageId, {
1059
+ id: installation.packageId,
1060
+ sourceId: sourceId ?? "personal-local",
1061
+ displayName: resolved[0]?.packageDisplayName ?? installation.packageId,
1062
+ ...resolved[0]?.packageDescription ? { description: resolved[0].packageDescription } : {},
1063
+ ...resolved[0]?.packageVersion ? { version: resolved[0].packageVersion } : {},
1064
+ artifacts
1065
+ });
1066
+ }
1067
+ }
1068
+ await mergeRegistrarPackages(sources, packages, intent.installations, statesByArtifactId);
1069
+ return {
1070
+ sources,
1071
+ packages: [...packages.values()],
1072
+ installations: intent.installations.filter((installation) => installation.scope === "personal"),
1073
+ statesByArtifactId,
1074
+ ...legacyCount > 0 ? { legacyCount } : {}
1075
+ };
1076
+ }
1077
+ function fallbackEntries(plugin, packageId) {
1078
+ const kinds = Object.keys(plugin.artifacts);
1079
+ return kinds.filter((kind) => plugin.artifacts[kind] !== false).map((kind) => ({
1080
+ identity: `${packageId}::${kind}:${plugin.id}`,
1081
+ artifactId: `${packageId}::${kind}:${plugin.id}`,
1082
+ packageId,
1083
+ packageDisplayName: plugin.id,
1084
+ repositoryId: packageId.split("::")[0] ?? packageId,
1085
+ pluginId: plugin.id,
1086
+ kind,
1087
+ sourcePath: "",
1088
+ sourceIsFile: false,
1089
+ name: plugin.id,
1090
+ digest: "",
1091
+ requiresApproval: kind === "hook" || kind === "mcp"
1092
+ }));
1093
+ }
1094
+ function toArtifactState(entry, previous) {
1095
+ if (!previous) {
1096
+ return {
1097
+ intent: "selected",
1098
+ health: "missing",
1099
+ gate: entry.requiresApproval ? "approval-required" : "ready"
1100
+ };
1101
+ }
1102
+ return {
1103
+ intent: "selected",
1104
+ health: previous.digest === entry.digest ? "healthy" : "drifted",
1105
+ gate: entry.requiresApproval && !previous.approved ? "approval-required" : "ready"
1106
+ };
1107
+ }
1108
+ function createDefaultProductionReaders() {
1109
+ return {
1110
+ loadWorkspaceManifest: import_devtools_core2.loadWorkspaceCopilotManifest,
1111
+ readWorkspaceState: async (workspaceDir) => new import_devtools_core2.WorkspaceContentStateStore({ workspaceDir }).read(),
1112
+ isSourceAvailable: async (sourceId) => {
1113
+ const stat3 = await fsp2.stat(path2.join((0, import_devtools_core2.getReposDir)(), sourceId)).catch(() => void 0);
1114
+ return stat3?.isDirectory() === true;
1115
+ },
1116
+ resolveWorkspacePlugin: async ({ manifest, plugin }) => {
1117
+ const scopedManifest = manifest.version === 1 ? { ...manifest, plugins: [plugin] } : { ...manifest, plugins: [plugin] };
1118
+ return (await (0, import_devtools_core2.resolveWorkspaceContentPlan)({
1119
+ manifest: scopedManifest,
1120
+ reposDir: (0, import_devtools_core2.getReposDir)()
1121
+ })).entries;
1122
+ },
1123
+ listPersonalLinks: async () => [
1124
+ ...(await (0, import_skill_linker.listLinkedSkills)("", "skill", "user")).map((link) => ({
1125
+ repoId: link.repoId,
1126
+ name: link.skillName,
1127
+ kind: "skill",
1128
+ legacy: link.linkPath.includes(`${path2.sep}.agents${path2.sep}`)
1129
+ })),
1130
+ ...(await (0, import_skill_linker.listLinkedSkills)("", "agent", "user")).map((link) => ({
1131
+ repoId: link.repoId,
1132
+ name: link.skillName,
1133
+ kind: "agent",
1134
+ legacy: link.linkPath.includes(`${path2.sep}.agents${path2.sep}`)
1135
+ }))
1136
+ ]
1137
+ };
1138
+ }
1139
+ function emptySnapshot() {
1140
+ return {
1141
+ sources: [],
1142
+ packages: [],
1143
+ installations: [],
1144
+ statesByArtifactId: {}
1145
+ };
1146
+ }
1147
+ function toPublicSource(source) {
1148
+ return {
1149
+ id: source.id,
1150
+ type: source.type,
1151
+ displayName: source.displayName,
1152
+ updateCapability: source.updateCapability
1153
+ };
1154
+ }
1155
+ function toPublicPackage(pkg) {
1156
+ return {
1157
+ id: pkg.id,
1158
+ sourceId: pkg.sourceId,
1159
+ displayName: pkg.displayName,
1160
+ ...pkg.description !== void 0 ? { description: pkg.description } : {},
1161
+ ...pkg.version !== void 0 ? { version: pkg.version } : {},
1162
+ artifacts: pkg.artifacts.map((artifact) => ({
1163
+ id: artifact.id,
1164
+ packageId: artifact.packageId,
1165
+ kind: artifact.kind,
1166
+ displayName: artifact.displayName,
1167
+ ...artifact.description !== void 0 ? { description: artifact.description } : {},
1168
+ installStrategy: artifact.installStrategy,
1169
+ risk: artifact.risk
1170
+ })),
1171
+ ...pkg.wholePackage === true ? { wholePackage: true } : {}
1172
+ };
1173
+ }
1174
+ function toPublicInstallation(installation) {
1175
+ return {
1176
+ packageId: installation.packageId,
1177
+ scope: installation.scope,
1178
+ selectedArtifactIds: [...installation.selectedArtifactIds],
1179
+ ...installation.pinnedVersion !== void 0 ? { pinnedVersion: installation.pinnedVersion } : {}
1180
+ };
1181
+ }
1182
+ function toPublicStates(states) {
1183
+ return Object.fromEntries(
1184
+ Object.entries(states).map(([artifactId, state]) => [artifactId, { ...state }])
1185
+ );
1186
+ }
1187
+
1188
+ // src/bridge/CopilotPluginBridgeHandler.ts
1189
+ var fs = __toESM(require("fs/promises"));
1190
+ var path3 = __toESM(require("path"));
1191
+ var import_devtools_core3 = require("@serviceme/devtools-core");
1192
+ async function readManifestInfo(pluginDir) {
1193
+ try {
1194
+ const raw = JSON.parse(await fs.readFile(path3.join(pluginDir, "plugin.json"), "utf8"));
1195
+ let mcpServers = [];
1196
+ const mcpPath = path3.join(pluginDir, "mcp.json");
1197
+ try {
1198
+ const mcp = JSON.parse(await fs.readFile(mcpPath, "utf8"));
1199
+ mcpServers = Object.keys(mcp.mcpServers ?? {});
1200
+ } catch {
1201
+ }
1202
+ return {
1203
+ ...typeof raw.name === "string" ? { displayName: raw.name } : {},
1204
+ ...typeof raw.version === "string" ? { version: raw.version } : {},
1205
+ mcpServers,
1206
+ hasExtensions: Boolean(raw.extensions?.["com.github.copilot"])
1207
+ };
1208
+ } catch {
1209
+ return { mcpServers: [], hasExtensions: false };
1210
+ }
1211
+ }
1212
+ function toPayload(reg, info) {
1213
+ return {
1214
+ registrationId: reg.registrationId,
1215
+ repoId: reg.repoId,
1216
+ pluginId: reg.pluginId,
1217
+ ...reg.displayName ?? info.displayName ? { displayName: reg.displayName ?? info.displayName } : {},
1218
+ ...reg.version ?? info.version ? { version: reg.version ?? info.version } : {},
1219
+ scopes: reg.scopes,
1220
+ mcpServers: info.mcpServers,
1221
+ hasExtensions: info.hasExtensions
1222
+ };
1223
+ }
1224
+ var CopilotPluginBridgeHandler = class {
1225
+ constructor(options = {}) {
1226
+ const homeDir = options.homeDir ?? (0, import_devtools_core3.getHomeDir)();
1227
+ this.registrar = new import_devtools_core3.CopilotPluginRegistrar({
1228
+ // copilotDir is the migration source only — projections live
1229
+ // under the SERVICEME home, out of VS Code's ~/.copilot
1230
+ // reconciliation reach.
1231
+ copilotDir: path3.join(homeDir, ".copilot"),
1232
+ pluginsDir: path3.join(homeDir, ".serviceme", "copilot-plugins")
1233
+ });
1234
+ this.reposDir = path3.join(homeDir, ".serviceme", "repos");
1235
+ }
1236
+ pluginDir(repoId, pluginId) {
1237
+ return path3.join(this.reposDir, (0, import_devtools_core3.assertSafeRepoId)(repoId), "plugins", pluginId);
1238
+ }
1239
+ async list(_params) {
1240
+ const registrations = await this.registrar.list({});
1241
+ const plugins = await Promise.all(
1242
+ registrations.map(async (reg) => toPayload(reg, await readManifestInfo(reg.pluginDir)))
1243
+ );
1244
+ return { plugins };
1245
+ }
1246
+ async register(params) {
1247
+ const pluginDir = this.pluginDir(params.repoId, params.pluginId);
1248
+ const info = await readManifestInfo(pluginDir);
1249
+ await this.registrar.register({
1250
+ repoId: params.repoId,
1251
+ pluginId: params.pluginId,
1252
+ pluginDir,
1253
+ scope: params.scope,
1254
+ ...info.displayName !== void 0 ? { displayName: info.displayName } : {},
1255
+ ...info.version !== void 0 ? { version: info.version } : {}
1256
+ });
1257
+ return this.list({});
1258
+ }
1259
+ async unregister(params) {
1260
+ const normalized = params.registrationId.replace("::", ":");
1261
+ await this.registrar.unregister({
1262
+ registrationId: normalized,
1263
+ scope: params.scope
1264
+ });
1265
+ if (normalized !== params.registrationId) {
1266
+ await this.registrar.unregister({
1267
+ registrationId: params.registrationId,
1268
+ scope: params.scope
1269
+ });
1270
+ }
1271
+ const otherScope = params.scope === "personal" ? "workspace" : "personal";
1272
+ await this.registrar.unregister({
1273
+ registrationId: normalized,
1274
+ scope: otherScope
1275
+ });
1276
+ return this.list({});
1277
+ }
1278
+ };
217
1279
 
218
1280
  // src/bridge/handlers/AuthBridgeHandlers.ts
219
1281
  var import_auth2 = require("@serviceme/devtools-core/auth");
@@ -328,12 +1390,12 @@ function writeBridgeMessage(message) {
328
1390
  }
329
1391
 
330
1392
  // src/bridge/TaskBridgeHandler.ts
331
- var import_devtools_core = require("@serviceme/devtools-core");
1393
+ var import_devtools_core4 = require("@serviceme/devtools-core");
332
1394
  var TaskBridgeHandler = class {
333
1395
  constructor(logger, emitEvent) {
334
1396
  this.logger = logger;
335
1397
  this.emitEvent = emitEvent;
336
- this.engine = new import_devtools_core.TaskExecutionEngine((taskType) => (0, import_devtools_core.getExecutor)(taskType));
1398
+ this.engine = new import_devtools_core4.TaskExecutionEngine((taskType) => (0, import_devtools_core4.getExecutor)(taskType));
337
1399
  this.engine.setListener({
338
1400
  onStarted: (params) => this.emitEvent("task.started", params),
339
1401
  onOutput: (params) => this.emitEvent("task.output", params),
@@ -376,6 +1438,7 @@ var CAPABILITIES = {
376
1438
  tasks: 1,
377
1439
  skillRepo: 1,
378
1440
  repoMgmt: 1,
1441
+ copilotContent: 1,
379
1442
  auth: 1,
380
1443
  device: 1,
381
1444
  toolbox: 1
@@ -388,6 +1451,9 @@ var BridgeServer = class {
388
1451
  this.writeEvent(event, params);
389
1452
  });
390
1453
  this.skillRepoHandler = opts.skillRepoHandler;
1454
+ this.copilotContentHandler = opts.skillRepoHandler ? new CopilotContentBridgeHandler(opts.skillRepoHandler.copilotContentDependencies) : void 0;
1455
+ this.copilotCustomizationsHandler = opts.skillRepoHandler ? new CopilotCustomizationsBridgeHandler(opts.skillRepoHandler.copilotContentDependencies) : void 0;
1456
+ this.copilotPluginHandler = opts.skillRepoHandler ? new CopilotPluginBridgeHandler() : void 0;
391
1457
  this.authHandler = new AuthBridgeHandlers();
392
1458
  this.deviceHandler = new DeviceBridgeHandlers();
393
1459
  this.toolboxHandler = new ToolboxBridgeHandlers();
@@ -397,12 +1463,12 @@ var BridgeServer = class {
397
1463
  input: process.stdin,
398
1464
  crlfDelay: Number.POSITIVE_INFINITY
399
1465
  });
400
- await new Promise((resolve) => {
1466
+ await new Promise((resolve3) => {
401
1467
  reader.on("line", (line) => {
402
1468
  void this.handleLine(line);
403
1469
  });
404
1470
  reader.on("close", () => {
405
- resolve();
1471
+ resolve3();
406
1472
  });
407
1473
  });
408
1474
  }
@@ -493,6 +1559,7 @@ var BridgeServer = class {
493
1559
  this.writeSuccess(request.id, result);
494
1560
  return;
495
1561
  }
1562
+ case "copilotContent.list":
496
1563
  case "skillRepo.list": {
497
1564
  const r = request;
498
1565
  if (!this.skillRepoHandler) return this.unsupportedV2(request.id);
@@ -500,6 +1567,7 @@ var BridgeServer = class {
500
1567
  this.writeSuccess(request.id, result);
501
1568
  return;
502
1569
  }
1570
+ case "copilotContent.get":
503
1571
  case "skillRepo.get": {
504
1572
  const r = request;
505
1573
  if (!this.skillRepoHandler) return this.unsupportedV2(request.id);
@@ -507,6 +1575,7 @@ var BridgeServer = class {
507
1575
  this.writeSuccess(request.id, result);
508
1576
  return;
509
1577
  }
1578
+ case "copilotContent.install":
510
1579
  case "skillRepo.install": {
511
1580
  const r = request;
512
1581
  if (!this.skillRepoHandler) return this.unsupportedV2(request.id);
@@ -514,6 +1583,7 @@ var BridgeServer = class {
514
1583
  this.writeSuccess(request.id, result);
515
1584
  return;
516
1585
  }
1586
+ case "copilotContent.convertToSymlink":
517
1587
  case "skillRepo.convertToSymlink": {
518
1588
  const r = request;
519
1589
  if (!this.skillRepoHandler) return this.unsupportedV2(request.id);
@@ -521,6 +1591,7 @@ var BridgeServer = class {
521
1591
  this.writeSuccess(request.id, result);
522
1592
  return;
523
1593
  }
1594
+ case "copilotContent.uninstall":
524
1595
  case "skillRepo.uninstall": {
525
1596
  const r = request;
526
1597
  if (!this.skillRepoHandler) return this.unsupportedV2(request.id);
@@ -528,6 +1599,15 @@ var BridgeServer = class {
528
1599
  this.writeSuccess(request.id, result);
529
1600
  return;
530
1601
  }
1602
+ case "copilotContent.setEntryEnabled":
1603
+ case "skillRepo.setEntryEnabled": {
1604
+ const r = request;
1605
+ if (!this.skillRepoHandler) return this.unsupportedV2(request.id);
1606
+ const result = await this.skillRepoHandler.setEntryEnabled(r.params);
1607
+ this.writeSuccess(request.id, result);
1608
+ return;
1609
+ }
1610
+ case "copilotContent.listLinked":
531
1611
  case "skillRepo.listLinked": {
532
1612
  const r = request;
533
1613
  if (!this.skillRepoHandler) return this.unsupportedV2(request.id);
@@ -535,6 +1615,7 @@ var BridgeServer = class {
535
1615
  this.writeSuccess(request.id, result);
536
1616
  return;
537
1617
  }
1618
+ case "copilotContent.draft.create":
538
1619
  case "skillRepo.draft.create": {
539
1620
  const r = request;
540
1621
  if (!this.skillRepoHandler) return this.unsupportedV2(request.id);
@@ -542,6 +1623,7 @@ var BridgeServer = class {
542
1623
  this.writeSuccess(request.id, result);
543
1624
  return;
544
1625
  }
1626
+ case "copilotContent.draft.commit":
545
1627
  case "skillRepo.draft.commit": {
546
1628
  const r = request;
547
1629
  if (!this.skillRepoHandler) return this.unsupportedV2(request.id);
@@ -549,6 +1631,7 @@ var BridgeServer = class {
549
1631
  this.writeSuccess(request.id, result);
550
1632
  return;
551
1633
  }
1634
+ case "copilotContent.draft.list":
552
1635
  case "skillRepo.draft.list": {
553
1636
  const r = request;
554
1637
  if (!this.skillRepoHandler) return this.unsupportedV2(request.id);
@@ -556,6 +1639,7 @@ var BridgeServer = class {
556
1639
  this.writeSuccess(request.id, result);
557
1640
  return;
558
1641
  }
1642
+ case "copilotContent.draft.delete":
559
1643
  case "skillRepo.draft.delete": {
560
1644
  const r = request;
561
1645
  if (!this.skillRepoHandler) return this.unsupportedV2(request.id);
@@ -619,6 +1703,140 @@ var BridgeServer = class {
619
1703
  this.writeSuccess(request.id, result);
620
1704
  return;
621
1705
  }
1706
+ case "repo.resetParseCache": {
1707
+ const r = request;
1708
+ if (!this.skillRepoHandler) return this.unsupportedV2(request.id);
1709
+ const result = await this.skillRepoHandler.repoResetParseCache(r.params);
1710
+ this.writeSuccess(request.id, result);
1711
+ return;
1712
+ }
1713
+ case "copilotPlugin.list": {
1714
+ const r = request;
1715
+ if (!this.copilotPluginHandler) return this.unsupportedV2(request.id);
1716
+ const listResult = await this.copilotPluginHandler.list(r.params);
1717
+ this.writeSuccess(request.id, listResult);
1718
+ return;
1719
+ }
1720
+ case "copilotPlugin.register": {
1721
+ const r = request;
1722
+ if (!this.copilotPluginHandler) return this.unsupportedV2(request.id);
1723
+ const regResult = await this.copilotPluginHandler.register(r.params);
1724
+ this.writeSuccess(request.id, regResult);
1725
+ return;
1726
+ }
1727
+ case "copilotPlugin.unregister": {
1728
+ const r = request;
1729
+ if (!this.copilotPluginHandler) return this.unsupportedV2(request.id);
1730
+ const unregResult = await this.copilotPluginHandler.unregister(r.params);
1731
+ this.writeSuccess(request.id, unregResult);
1732
+ return;
1733
+ }
1734
+ // ── copilotContent.* (declarative reconciliation) ──────────────
1735
+ case "copilotContent.status": {
1736
+ const r = request;
1737
+ if (!this.copilotContentHandler) return this.unsupportedCopilotContent(request.id);
1738
+ const result = await this.copilotContentHandler.status(r.params);
1739
+ this.writeSuccess(request.id, result);
1740
+ return;
1741
+ }
1742
+ case "copilotContent.restore": {
1743
+ const r = request;
1744
+ if (!this.copilotContentHandler) return this.unsupportedCopilotContent(request.id);
1745
+ const result = await this.copilotContentHandler.restore(r.params);
1746
+ this.writeSuccess(request.id, result);
1747
+ return;
1748
+ }
1749
+ case "copilotContent.approve": {
1750
+ const r = request;
1751
+ if (!this.copilotContentHandler) return this.unsupportedCopilotContent(request.id);
1752
+ const result = await this.copilotContentHandler.approve(r.params);
1753
+ this.writeSuccess(request.id, result);
1754
+ return;
1755
+ }
1756
+ case "copilotContent.migrateLegacy": {
1757
+ const r = request;
1758
+ if (!this.copilotContentHandler) return this.unsupportedCopilotContent(request.id);
1759
+ const result = await this.copilotContentHandler.migrateLegacy(r.params);
1760
+ this.writeSuccess(request.id, result);
1761
+ return;
1762
+ }
1763
+ case "copilotContent.integrationStatus": {
1764
+ const r = request;
1765
+ if (!this.copilotContentHandler) return this.unsupportedCopilotContent(request.id);
1766
+ const result = await this.copilotContentHandler.integrationStatus(r.params);
1767
+ this.writeSuccess(request.id, result);
1768
+ return;
1769
+ }
1770
+ case "copilotContent.customizations.list": {
1771
+ const r = request;
1772
+ if (!this.copilotCustomizationsHandler) return this.unsupportedCopilotContent(request.id);
1773
+ const result = await this.copilotCustomizationsHandler.list(r.params);
1774
+ this.writeSuccess(request.id, result);
1775
+ return;
1776
+ }
1777
+ case "copilotContent.package.install": {
1778
+ const r = request;
1779
+ if (!this.copilotCustomizationsHandler) return this.unsupportedCopilotContent(request.id);
1780
+ const result = await this.copilotCustomizationsHandler.install(r.params);
1781
+ this.writeSuccess(request.id, result);
1782
+ return;
1783
+ }
1784
+ case "copilotContent.package.update": {
1785
+ const r = request;
1786
+ if (!this.copilotCustomizationsHandler) return this.unsupportedCopilotContent(request.id);
1787
+ const result = await this.copilotCustomizationsHandler.update(r.params);
1788
+ this.writeSuccess(request.id, result);
1789
+ return;
1790
+ }
1791
+ case "copilotContent.package.uninstall": {
1792
+ const r = request;
1793
+ if (!this.copilotCustomizationsHandler) return this.unsupportedCopilotContent(request.id);
1794
+ const result = await this.copilotCustomizationsHandler.uninstall(r.params);
1795
+ this.writeSuccess(request.id, result);
1796
+ return;
1797
+ }
1798
+ case "copilotContent.package.move": {
1799
+ const r = request;
1800
+ if (!this.copilotCustomizationsHandler) return this.unsupportedCopilotContent(request.id);
1801
+ const result = await this.copilotCustomizationsHandler.move(r.params);
1802
+ this.writeSuccess(request.id, result);
1803
+ return;
1804
+ }
1805
+ case "copilotContent.legacy.preview": {
1806
+ const r = request;
1807
+ if (!this.copilotCustomizationsHandler) return this.unsupportedCopilotContent(request.id);
1808
+ const result = await this.copilotCustomizationsHandler.legacyPreview(r.params);
1809
+ this.writeSuccess(request.id, result);
1810
+ return;
1811
+ }
1812
+ case "copilotContent.legacy.migrate": {
1813
+ const r = request;
1814
+ if (!this.copilotCustomizationsHandler) return this.unsupportedCopilotContent(request.id);
1815
+ const result = await this.copilotCustomizationsHandler.legacyMigrate(r.params);
1816
+ this.writeSuccess(request.id, result);
1817
+ return;
1818
+ }
1819
+ case "copilotContent.sources.list": {
1820
+ const r = request;
1821
+ if (!this.copilotCustomizationsHandler) return this.unsupportedCopilotContent(request.id);
1822
+ const result = await this.copilotCustomizationsHandler.sources(r.params);
1823
+ this.writeSuccess(request.id, result);
1824
+ return;
1825
+ }
1826
+ case "copilotContent.sources.remove": {
1827
+ const r = request;
1828
+ if (!this.copilotCustomizationsHandler) return this.unsupportedCopilotContent(request.id);
1829
+ const result = await this.copilotCustomizationsHandler.removeSource(r.params);
1830
+ this.writeSuccess(request.id, result);
1831
+ return;
1832
+ }
1833
+ case "copilotContent.package.previewUpdate": {
1834
+ const r = request;
1835
+ if (!this.copilotCustomizationsHandler) return this.unsupportedCopilotContent(request.id);
1836
+ const result = await this.copilotCustomizationsHandler.previewUpdate(r.params);
1837
+ this.writeSuccess(request.id, result);
1838
+ return;
1839
+ }
622
1840
  // ── auth.* (Phase 5.4) ─────────────────────────────────────────
623
1841
  case "auth.status": {
624
1842
  const r = request;
@@ -722,6 +1940,15 @@ var BridgeServer = class {
722
1940
  )
723
1941
  );
724
1942
  }
1943
+ unsupportedCopilotContent(id) {
1944
+ this.writeError(
1945
+ id,
1946
+ createServicemeError(
1947
+ "invalid_params",
1948
+ "copilotContent.* methods require v2 CLI deps; the bridge was started without a CopilotContentBridgeHandler."
1949
+ )
1950
+ );
1951
+ }
725
1952
  writeError(id, error) {
726
1953
  writeBridgeMessage({
727
1954
  protocolVersion: SERVICEME_PROTOCOL_VERSION,