@aexol/opencode-wizard 0.1.14 → 0.1.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/server.js CHANGED
@@ -54,6 +54,7 @@ const statusPathLoginBootstrap = {
54
54
  };
55
55
  const importOpencodePluginModule = new Function('specifier', 'return import(specifier)');
56
56
  export const AVAILABLE_PUBLISHED_SKILL_TOOLS = ['opencode_wizard_published_skills_fetch', 'opencode_wizard_status'];
57
+ let publishedSkillPreferenceCacheVersion = 0;
57
58
  export const NATIVE_SKILLS_URL_COMPATIBILITY = {
58
59
  configKey: 'skills.urls',
59
60
  deliveryMode: 'public_static_registry',
@@ -114,6 +115,137 @@ const PUBLISHED_SKILLS_CATALOG_QUERY = `
114
115
  publishedAt
115
116
  }
116
117
  }
118
+ catalogSkills {
119
+ skill {
120
+ id
121
+ slug
122
+ name
123
+ summary
124
+ whenToUse
125
+ status
126
+ installPolicy
127
+ tags {
128
+ id
129
+ slug
130
+ label
131
+ description
132
+ facet {
133
+ id
134
+ slug
135
+ label
136
+ description
137
+ }
138
+ }
139
+ }
140
+ skillVersion {
141
+ id
142
+ version
143
+ title
144
+ summary
145
+ status
146
+ }
147
+ publishedArtifact {
148
+ id
149
+ frontmatterName
150
+ frontmatterDescription
151
+ checksum
152
+ publishedAt
153
+ }
154
+ }
155
+ userPreferences {
156
+ scopeKey
157
+ userKey
158
+ ignoredSkills {
159
+ assignmentSource
160
+ assignmentType
161
+ scopePath
162
+ includeChildren
163
+ skill {
164
+ id
165
+ slug
166
+ name
167
+ summary
168
+ whenToUse
169
+ status
170
+ installPolicy
171
+ tags {
172
+ id
173
+ slug
174
+ label
175
+ description
176
+ facet {
177
+ id
178
+ slug
179
+ label
180
+ description
181
+ }
182
+ }
183
+ }
184
+ skillVersion {
185
+ id
186
+ version
187
+ title
188
+ summary
189
+ status
190
+ }
191
+ publishedArtifact {
192
+ id
193
+ frontmatterName
194
+ frontmatterDescription
195
+ checksum
196
+ publishedAt
197
+ }
198
+ }
199
+ }
200
+ }
201
+ }
202
+ `;
203
+ const SET_PUBLISHED_SKILL_PREFERENCE_MUTATION = `
204
+ mutation SetPublishedSkillPreference($input: SetPublishedSkillPreferenceInput!) {
205
+ setPublishedSkillPreference(input: $input) {
206
+ scopeKey
207
+ userKey
208
+ ignoredSkills {
209
+ assignmentSource
210
+ assignmentType
211
+ scopePath
212
+ includeChildren
213
+ skill {
214
+ id
215
+ slug
216
+ name
217
+ summary
218
+ whenToUse
219
+ status
220
+ installPolicy
221
+ tags {
222
+ id
223
+ slug
224
+ label
225
+ description
226
+ facet {
227
+ id
228
+ slug
229
+ label
230
+ description
231
+ }
232
+ }
233
+ }
234
+ skillVersion {
235
+ id
236
+ version
237
+ title
238
+ summary
239
+ status
240
+ }
241
+ publishedArtifact {
242
+ id
243
+ frontmatterName
244
+ frontmatterDescription
245
+ checksum
246
+ publishedAt
247
+ }
248
+ }
117
249
  }
118
250
  }
119
251
  `;
@@ -342,13 +474,31 @@ const readGlobalConfig = async configFile => {
342
474
  const writeGlobalConfig = async (configFile, config) => {
343
475
  await writeJsonFile(configFile, config);
344
476
  };
477
+ const withoutLegacyPublishedSkillPreferences = config => {
478
+ const {
479
+ publishedSkillPreferences,
480
+ ignoredPublishedSkills,
481
+ ...safeConfig
482
+ } = config;
483
+ void publishedSkillPreferences;
484
+ void ignoredPublishedSkills;
485
+ return safeConfig;
486
+ };
487
+ const hasLegacyPublishedSkillPreferences = config => {
488
+ return Object.prototype.hasOwnProperty.call(config, 'publishedSkillPreferences') || Object.prototype.hasOwnProperty.call(config, 'ignoredPublishedSkills');
489
+ };
345
490
  const readGlobalAuthState = async configFile => {
346
491
  const storedConfig = await readGlobalConfig(configFile);
347
492
  const storedAuthState = storedConfig.auth;
348
493
  if (storedAuthState === undefined || storedAuthState === null) return null;
349
- if (isAuthState(storedAuthState)) return storedAuthState;
494
+ if (isAuthState(storedAuthState)) {
495
+ if (hasLegacyPublishedSkillPreferences(storedConfig)) {
496
+ await writeGlobalConfig(configFile, withoutLegacyPublishedSkillPreferences(storedConfig));
497
+ }
498
+ return storedAuthState;
499
+ }
350
500
  await writeGlobalConfig(configFile, {
351
- ...storedConfig,
501
+ ...withoutLegacyPublishedSkillPreferences(storedConfig),
352
502
  auth: null
353
503
  });
354
504
  return null;
@@ -363,17 +513,43 @@ const readLegacyAuthState = async authStateFile => {
363
513
  const writeAuthState = async (configFile, authState) => {
364
514
  const storedConfig = await readGlobalConfig(configFile);
365
515
  await writeGlobalConfig(configFile, {
366
- ...storedConfig,
516
+ ...withoutLegacyPublishedSkillPreferences(storedConfig),
367
517
  auth: authState
368
518
  });
369
519
  };
370
520
  const clearAuthState = async configFile => {
371
521
  const storedConfig = await readGlobalConfig(configFile);
372
522
  await writeGlobalConfig(configFile, {
373
- ...storedConfig,
523
+ ...withoutLegacyPublishedSkillPreferences(storedConfig),
374
524
  auth: null
375
525
  });
376
526
  };
527
+ const toIgnoredSkillSlug = value => {
528
+ const normalized = value.trim().toLowerCase();
529
+ if (!normalized) return null;
530
+ return normalized;
531
+ };
532
+ const getPublishedSkillIgnoreScopeKey = (resolution, payload) => {
533
+ const workspaceSlug = payload?.workspace?.slug ?? resolution.fallbackWorkspaceSlug;
534
+ if (workspaceSlug) return `workspace:${toWorkspaceSlug(workspaceSlug)}`;
535
+ if (resolution.repositoryUrl) return `repository:${resolution.repositoryUrl}`;
536
+ return `path:${toWorkspaceSlug(path.basename(resolution.repositoryRoot))}`;
537
+ };
538
+ const toStoredUserKey = authState => {
539
+ if (authState?.userId) return authState.userId;
540
+ if (authState?.email) return authState.email.toLowerCase();
541
+ return 'anonymous';
542
+ };
543
+ const resolvePublishedSkillPreferenceCacheContext = async config => {
544
+ const authState = await readGlobalAuthState(config.authStatePath);
545
+ return {
546
+ userKey: toStoredUserKey(authState),
547
+ preferenceVersion: publishedSkillPreferenceCacheVersion
548
+ };
549
+ };
550
+ const getCatalogCacheKey = (workspaceResolution, preferenceContext) => {
551
+ return JSON.stringify([workspaceResolution.cacheKey, preferenceContext.userKey, preferenceContext.preferenceVersion]);
552
+ };
377
553
  const toAuthState = session => ({
378
554
  pluginId: PLUGIN_ID,
379
555
  sessionToken: session.jwtToken,
@@ -475,11 +651,12 @@ const getPublishedSkillAssignmentCounts = items => items.reduce((counts, item) =
475
651
  other: 0
476
652
  });
477
653
  const getSkillContextKind = item => {
478
- if (item.assignmentSource === 'GLOBAL') return 'global';
654
+ if (item.assignmentSource === 'GLOBAL' || item.assignmentSource === 'USER_GLOBAL') return 'global';
479
655
  return 'project';
480
656
  };
481
657
  const getSkillPolicyLabel = (policy, contextKind) => {
482
658
  if (policy === 'GLOBAL_CONTEXT') return 'GLOBAL_CONTEXT · active context only, not project-installable';
659
+ if (contextKind === 'installable') return 'PROJECT_INSTALLABLE · available to install';
483
660
  if (contextKind === 'global') return 'PROJECT_INSTALLABLE · active global assignment';
484
661
  return 'PROJECT_INSTALLABLE · active project/workspace assignment';
485
662
  };
@@ -514,10 +691,35 @@ export const toPublishedSkillDetail = item => ({
514
691
  markdownBody: item.publishedArtifact.markdownBody,
515
692
  renderedContent: item.publishedArtifact.renderedContent
516
693
  });
694
+ const toInstallableSkillSummary = item => ({
695
+ skillSlug: item.skill.slug,
696
+ skillName: item.skill.name,
697
+ artifactName: item.publishedArtifact.frontmatterName,
698
+ artifactDescription: item.publishedArtifact.frontmatterDescription,
699
+ whenToUse: item.skill.whenToUse ?? null,
700
+ version: item.skillVersion.version,
701
+ assignmentSource: 'CATALOG',
702
+ assignmentType: 'PATH',
703
+ scopePath: '',
704
+ includeChildren: true,
705
+ checksum: item.publishedArtifact.checksum,
706
+ publishedAt: item.publishedArtifact.publishedAt,
707
+ identifiers: getSkillIdentifiers({
708
+ ...item,
709
+ assignmentSource: 'CATALOG',
710
+ assignmentType: 'PATH',
711
+ scopePath: '',
712
+ includeChildren: true
713
+ }),
714
+ tags: item.skill.tags.map(toPublishedSkillTagSummary),
715
+ contextKind: 'installable',
716
+ installPolicy: item.skill.installPolicy,
717
+ policyLabel: getSkillPolicyLabel(item.skill.installPolicy, 'installable')
718
+ });
517
719
  export const toPublishedSkillCatalog = payload => ({
518
720
  pluginId: PLUGIN_ID,
519
721
  runtimeMode: 'tool_fetch_only',
520
- deliveryModel: 'backend_published_global_project_assignments',
722
+ deliveryModel: 'backend_published_installed_effective_skills',
521
723
  workspace: payload.workspace,
522
724
  directoryPath: payload.directoryPath,
523
725
  rootSkillSeedPath: ROOT_SKILL_SEED_PATH,
@@ -527,6 +729,36 @@ export const toPublishedSkillCatalog = payload => ({
527
729
  facets: getPublishedSkillFacets(payload.skills),
528
730
  skills: payload.skills.map(toPublishedSkillSummary)
529
731
  });
732
+ const filterIgnoredPublishedSkills = async (config, result) => {
733
+ const authState = await readGlobalAuthState(config.authStatePath);
734
+ const userKey = toStoredUserKey(authState);
735
+ if (!result.fetchResult.ok) {
736
+ return {
737
+ ...result,
738
+ ignoreState: {
739
+ scopeKey: getPublishedSkillIgnoreScopeKey(result.workspaceResolution),
740
+ userKey,
741
+ ignoredSkillSlugs: [],
742
+ installedGlobalSkillSlugs: [],
743
+ installedWorkspaceSkillSlugs: []
744
+ },
745
+ ignoredSkills: []
746
+ };
747
+ }
748
+ const ignoredSkills = result.fetchResult.payload.userPreferences.ignoredSkills.map(toPublishedSkillSummary);
749
+ const ignoredSkillSlugs = ignoredSkills.map(skill => skill.skillSlug);
750
+ return {
751
+ ...result,
752
+ ignoreState: {
753
+ scopeKey: result.fetchResult.payload.userPreferences.scopeKey,
754
+ userKey: result.fetchResult.payload.userPreferences.userKey || userKey,
755
+ ignoredSkillSlugs,
756
+ installedGlobalSkillSlugs: [],
757
+ installedWorkspaceSkillSlugs: []
758
+ },
759
+ ignoredSkills
760
+ };
761
+ };
530
762
  const getWorkspaceUnavailableMessage = payload => {
531
763
  if (payload.workspace) return null;
532
764
  return 'Workspace-specific skills are unavailable because the workspace was not found; global skills are still loaded.';
@@ -629,6 +861,7 @@ const toWorkspaceResolutionMetadata = resolution => ({
629
861
  });
630
862
  const formatStatusOutput = async (worktree, config, publishedSkillsResult, loginBootstrapSnapshot) => {
631
863
  const authState = await resolveStoredAuthState(worktree, config);
864
+ const filteredResult = await filterIgnoredPublishedSkills(config, publishedSkillsResult);
632
865
  const base = {
633
866
  pluginId: PLUGIN_ID,
634
867
  runtimeMode: 'tool_fetch_only',
@@ -657,21 +890,26 @@ const formatStatusOutput = async (worktree, config, publishedSkillsResult, login
657
890
  email: loginBootstrapSnapshot.email,
658
891
  message: loginBootstrapSnapshot.message
659
892
  },
660
- status: publishedSkillsResult.fetchResult.status,
661
- fetchedAt: publishedSkillsResult.fetchResult.fetchedAt,
662
- source: publishedSkillsResult.fetchResult.source,
663
- availableTools: AVAILABLE_PUBLISHED_SKILL_TOOLS
893
+ status: filteredResult.fetchResult.status,
894
+ fetchedAt: filteredResult.fetchResult.fetchedAt,
895
+ source: filteredResult.fetchResult.source,
896
+ availableTools: AVAILABLE_PUBLISHED_SKILL_TOOLS,
897
+ ignoredPublishedSkills: {
898
+ scopeKey: filteredResult.ignoreState.scopeKey,
899
+ userKey: filteredResult.ignoreState.userKey,
900
+ count: filteredResult.ignoreState.ignoredSkillSlugs.length
901
+ }
664
902
  };
665
- if (!publishedSkillsResult.fetchResult.ok) {
903
+ if (!filteredResult.fetchResult.ok) {
666
904
  return JSON.stringify({
667
905
  ...base,
668
- message: publishedSkillsResult.fetchResult.message
906
+ message: filteredResult.fetchResult.message
669
907
  }, null, 2);
670
908
  }
671
909
  return JSON.stringify({
672
910
  ...base,
673
- ...toPublishedSkillCatalog(publishedSkillsResult.fetchResult.payload),
674
- message: getWorkspaceUnavailableMessage(publishedSkillsResult.fetchResult.payload)
911
+ ...toPublishedSkillCatalog(filteredResult.fetchResult.payload),
912
+ message: getWorkspaceUnavailableMessage(filteredResult.fetchResult.payload)
675
913
  }, null, 2);
676
914
  };
677
915
  export const toPluginAuthStateSummary = authState => {
@@ -703,6 +941,11 @@ export const resolvePluginStatusSnapshot = async ({
703
941
  directory
704
942
  });
705
943
  const fetchResult = await fetchPublishedSkillsCatalog(worktree, config, workspaceResolution, signal);
944
+ const filteredResult = await filterIgnoredPublishedSkills(config, {
945
+ directoryPath: workspaceResolution.directoryPath,
946
+ workspaceResolution,
947
+ fetchResult
948
+ });
706
949
  const authState = await resolveStoredAuthState(worktree, config);
707
950
  return {
708
951
  pluginId: PLUGIN_ID,
@@ -715,19 +958,43 @@ export const resolvePluginStatusSnapshot = async ({
715
958
  rootSkillSeedPath: config.rootSkillSeedPath,
716
959
  authStatePath: config.authStatePath,
717
960
  authState: toPluginAuthStateSummary(authState),
718
- status: fetchResult.status,
719
- authMode: fetchResult.authMode,
720
- fetchedAt: fetchResult.fetchedAt,
721
- source: fetchResult.source,
961
+ status: filteredResult.fetchResult.status,
962
+ authMode: filteredResult.fetchResult.authMode,
963
+ fetchedAt: filteredResult.fetchResult.fetchedAt,
964
+ source: filteredResult.fetchResult.source,
722
965
  availableTools: AVAILABLE_PUBLISHED_SKILL_TOOLS,
723
- message: fetchResult.ok ? getWorkspaceUnavailableMessage(fetchResult.payload) : fetchResult.message,
724
- catalog: fetchResult.ok ? toPublishedSkillCatalog(fetchResult.payload) : null
966
+ message: filteredResult.fetchResult.ok ? getWorkspaceUnavailableMessage(filteredResult.fetchResult.payload) : filteredResult.fetchResult.message,
967
+ catalog: filteredResult.fetchResult.ok ? toPublishedSkillCatalog(filteredResult.fetchResult.payload) : null,
968
+ installableCatalog: filteredResult.fetchResult.ok ? {
969
+ count: filteredResult.fetchResult.payload.catalogSkills.length,
970
+ skills: filteredResult.fetchResult.payload.catalogSkills.map(toInstallableSkillSummary)
971
+ } : null,
972
+ ignoredPublishedSkills: {
973
+ scopeKey: filteredResult.ignoreState.scopeKey,
974
+ userKey: filteredResult.ignoreState.userKey,
975
+ count: filteredResult.ignoreState.ignoredSkillSlugs.length,
976
+ skills: filteredResult.ignoredSkills
977
+ }
725
978
  };
726
979
  };
727
980
  const withStatusMessage = (snapshot, message) => ({
728
981
  ...snapshot,
729
982
  message
730
983
  });
984
+ const toAiFacingPluginStatusSnapshot = snapshot => {
985
+ const {
986
+ ignoredPublishedSkills,
987
+ installableCatalog: _installableCatalog,
988
+ ...safeSnapshot
989
+ } = snapshot;
990
+ return {
991
+ ...safeSnapshot,
992
+ ignoredPublishedSkills: {
993
+ scopeKey: ignoredPublishedSkills.scopeKey,
994
+ count: ignoredPublishedSkills.count
995
+ }
996
+ };
997
+ };
731
998
  const startStatusPathLoginBootstrap = (worktree, config) => {
732
999
  if (statusPathLoginBootstrap.promise) return;
733
1000
  if (statusPathLoginBootstrap.status === 'failed' && statusPathLoginBootstrap.failedAt && Date.now() - statusPathLoginBootstrap.failedAt < STATUS_PATH_LOGIN_RETRY_COOLDOWN_MS) {
@@ -794,6 +1061,93 @@ export const resolvePluginStatusSnapshotWithAuthBootstrap = async ({
794
1061
  }
795
1062
  return withStatusMessage(snapshot, 'Browser login is pending from the TUI/status path.');
796
1063
  };
1064
+ const toBackendPreferenceScope = preferenceScope => {
1065
+ if (preferenceScope === 'global') return 'GLOBAL';
1066
+ return 'WORKSPACE';
1067
+ };
1068
+ const setPublishedSkillPreference = async ({
1069
+ worktree,
1070
+ directory,
1071
+ config,
1072
+ skillSlug,
1073
+ preferenceScope,
1074
+ installed,
1075
+ ignored
1076
+ }) => {
1077
+ const workspaceResolution = await resolveWorkspace({
1078
+ config,
1079
+ directory
1080
+ });
1081
+ const response = await fetchPublishedSkillsGraphQl({
1082
+ worktree,
1083
+ config,
1084
+ query: SET_PUBLISHED_SKILL_PREFERENCE_MUTATION,
1085
+ variables: {
1086
+ input: {
1087
+ ...toDeliveryInput(workspaceResolution),
1088
+ skillSlug,
1089
+ preferenceScope: toBackendPreferenceScope(preferenceScope),
1090
+ installed,
1091
+ ignored
1092
+ }
1093
+ },
1094
+ signal: AbortSignal.timeout(PRESENCE_EVENT_TIMEOUT_MS)
1095
+ });
1096
+ if (!response.ok) {
1097
+ throw new Error(response.result.message);
1098
+ }
1099
+ const preferences = response.data.setPublishedSkillPreference;
1100
+ publishedSkillPreferenceCacheVersion += 1;
1101
+ return {
1102
+ scopeKey: preferences.scopeKey,
1103
+ userKey: preferences.userKey,
1104
+ ignoredSkillSlugs: preferences.ignoredSkills.map(item => item.skill.slug),
1105
+ installedGlobalSkillSlugs: [],
1106
+ installedWorkspaceSkillSlugs: []
1107
+ };
1108
+ };
1109
+ export const setPublishedSkillIgnored = async ({
1110
+ worktree,
1111
+ directory,
1112
+ skillSlug,
1113
+ ignored,
1114
+ preferenceScope
1115
+ }) => {
1116
+ const config = await resolveConfig(worktree);
1117
+ const normalizedSkillSlug = toIgnoredSkillSlug(skillSlug);
1118
+ if (!normalizedSkillSlug) {
1119
+ throw new Error('Cannot toggle an empty published skill slug.');
1120
+ }
1121
+ return setPublishedSkillPreference({
1122
+ worktree,
1123
+ directory,
1124
+ config,
1125
+ skillSlug: normalizedSkillSlug,
1126
+ preferenceScope: preferenceScope ?? 'project',
1127
+ ignored
1128
+ });
1129
+ };
1130
+ export const setPublishedSkillInstalled = async ({
1131
+ worktree,
1132
+ directory,
1133
+ skillSlug,
1134
+ installed,
1135
+ preferenceScope
1136
+ }) => {
1137
+ const config = await resolveConfig(worktree);
1138
+ const normalizedSkillSlug = toIgnoredSkillSlug(skillSlug);
1139
+ if (!normalizedSkillSlug) {
1140
+ throw new Error('Cannot toggle an empty published skill slug.');
1141
+ }
1142
+ return setPublishedSkillPreference({
1143
+ worktree,
1144
+ directory,
1145
+ config,
1146
+ skillSlug: normalizedSkillSlug,
1147
+ preferenceScope,
1148
+ installed
1149
+ });
1150
+ };
797
1151
  const toPluginStatusMetadata = snapshot => ({
798
1152
  backendOrigin: snapshot.backendOrigin,
799
1153
  graphqlUrl: snapshot.graphqlUrl,
@@ -1503,11 +1857,11 @@ const openBrowser = async url => {
1503
1857
  const normalizeDirectoryArg = (contextDirectory, directory) => {
1504
1858
  return normalizeAbsolutePath(directory ? path.resolve(contextDirectory, directory) : contextDirectory);
1505
1859
  };
1506
- const getDetailCacheKey = (workspaceResolution, skillVersionId) => {
1507
- return JSON.stringify([workspaceResolution.cacheKey, skillVersionId]);
1860
+ const getDetailCacheKey = (catalogCacheKey, skillVersionId) => {
1861
+ return JSON.stringify([catalogCacheKey, skillVersionId]);
1508
1862
  };
1509
- const getDetailInflightKey = (workspaceResolution, skillVersionId, purpose) => {
1510
- return JSON.stringify([workspaceResolution.cacheKey, skillVersionId, purpose]);
1863
+ const getDetailInflightKey = (catalogCacheKey, skillVersionId, purpose) => {
1864
+ return JSON.stringify([catalogCacheKey, skillVersionId, purpose]);
1511
1865
  };
1512
1866
  const OpencodeWizardSkillsPlugin = async input => {
1513
1867
  const {
@@ -1727,7 +2081,8 @@ const OpencodeWizardSkillsPlugin = async input => {
1727
2081
  directory
1728
2082
  });
1729
2083
  const directoryPath = workspaceResolution.directoryPath;
1730
- const cacheKey = workspaceResolution.cacheKey;
2084
+ const preferenceContext = await resolvePublishedSkillPreferenceCacheContext(config);
2085
+ const cacheKey = getCatalogCacheKey(workspaceResolution, preferenceContext);
1731
2086
  const cached = cache.get(cacheKey);
1732
2087
  if (useCache && cached && cached.expiresAt > Date.now()) {
1733
2088
  return {
@@ -1770,8 +2125,10 @@ const OpencodeWizardSkillsPlugin = async input => {
1770
2125
  purpose
1771
2126
  }) => {
1772
2127
  const directoryPath = workspaceResolution.directoryPath;
1773
- const cacheKey = getDetailCacheKey(workspaceResolution, item.skillVersion.id);
1774
- const inflightKey = getDetailInflightKey(workspaceResolution, item.skillVersion.id, purpose);
2128
+ const preferenceContext = await resolvePublishedSkillPreferenceCacheContext(config);
2129
+ const catalogCacheKey = getCatalogCacheKey(workspaceResolution, preferenceContext);
2130
+ const cacheKey = getDetailCacheKey(catalogCacheKey, item.skillVersion.id);
2131
+ const inflightKey = getDetailInflightKey(catalogCacheKey, item.skillVersion.id, purpose);
1775
2132
  const cached = detailCache.get(cacheKey);
1776
2133
  if (purpose === 'SYSTEM_CONTEXT' && useCache && cached && cached.expiresAt > Date.now()) {
1777
2134
  return {
@@ -1911,18 +2268,29 @@ const OpencodeWizardSkillsPlugin = async input => {
1911
2268
  loginBootstrapSnapshot: loginBootstrap.snapshot
1912
2269
  });
1913
2270
  }
1914
- const selection = selectPublishedSkills(publishedSkillsResult.fetchResult.payload, requestedSkills);
2271
+ const filteredPublishedSkillsResult = await filterIgnoredPublishedSkills(config, publishedSkillsResult);
2272
+ if (!filteredPublishedSkillsResult.fetchResult.ok) {
2273
+ await emitFetchOutcome('FETCH_FAILED');
2274
+ return toFetchFailureOutput({
2275
+ worktree: input.worktree,
2276
+ config,
2277
+ publishedSkillsResult: filteredPublishedSkillsResult,
2278
+ loginBootstrapSnapshot: loginBootstrap.snapshot
2279
+ });
2280
+ }
2281
+ const selection = selectPublishedSkills(filteredPublishedSkillsResult.fetchResult.payload, requestedSkills);
1915
2282
  const isSingleRequest = requestedSkills.length === 1;
1916
2283
  if (requestedSkills.length === 0) {
1917
- const catalog = toPublishedSkillCatalog(publishedSkillsResult.fetchResult.payload);
2284
+ const catalog = toPublishedSkillCatalog(filteredPublishedSkillsResult.fetchResult.payload);
1918
2285
  context.metadata({
1919
- title: `opencode-wizard published skills catalog: ${catalog.publishedSkillCount}`,
2286
+ title: `opencode-wizard published skills catalog: ${catalog.publishedSkillCount} active`,
1920
2287
  metadata: {
1921
- ...toWorkspaceResolutionMetadata(publishedSkillsResult.workspaceResolution),
2288
+ ...toWorkspaceResolutionMetadata(filteredPublishedSkillsResult.workspaceResolution),
1922
2289
  status: 'ready',
1923
2290
  publishedSkillCount: catalog.publishedSkillCount.toString(),
1924
2291
  globalAssignmentCount: catalog.assignmentCounts.global.toString(),
1925
- projectAssignmentCount: catalog.assignmentCounts.project.toString()
2292
+ projectAssignmentCount: catalog.assignmentCounts.project.toString(),
2293
+ ignoredSkillCount: filteredPublishedSkillsResult.ignoreState.ignoredSkillSlugs.length.toString()
1926
2294
  }
1927
2295
  });
1928
2296
  await emitFetchOutcome('FETCH_SUCCESS');
@@ -1930,16 +2298,21 @@ const OpencodeWizardSkillsPlugin = async input => {
1930
2298
  output: JSON.stringify({
1931
2299
  ...catalog,
1932
2300
  status: 'ready',
1933
- requestedDirectoryPath: publishedSkillsResult.directoryPath,
1934
- workspaceResolution: toWorkspaceResolutionOutput(publishedSkillsResult.workspaceResolution),
1935
- fetchedAt: publishedSkillsResult.fetchResult.fetchedAt,
1936
- source: publishedSkillsResult.fetchResult.source,
2301
+ requestedDirectoryPath: filteredPublishedSkillsResult.directoryPath,
2302
+ workspaceResolution: toWorkspaceResolutionOutput(filteredPublishedSkillsResult.workspaceResolution),
2303
+ ignoredPublishedSkills: {
2304
+ scopeKey: filteredPublishedSkillsResult.ignoreState.scopeKey,
2305
+ count: filteredPublishedSkillsResult.ignoreState.ignoredSkillSlugs.length
2306
+ },
2307
+ fetchedAt: filteredPublishedSkillsResult.fetchResult.fetchedAt,
2308
+ source: filteredPublishedSkillsResult.fetchResult.source,
1937
2309
  message: 'Catalog discovery only. Provide `skill` or `skills` to fetch markdown bodies/details for selected skills.'
1938
2310
  }, null, 2),
1939
2311
  metadata: {
1940
2312
  status: 'ready',
1941
- ...toWorkspaceResolutionMetadata(publishedSkillsResult.workspaceResolution),
1942
- publishedSkillCount: catalog.publishedSkillCount.toString()
2313
+ ...toWorkspaceResolutionMetadata(filteredPublishedSkillsResult.workspaceResolution),
2314
+ publishedSkillCount: catalog.publishedSkillCount.toString(),
2315
+ ignoredSkillCount: filteredPublishedSkillsResult.ignoreState.ignoredSkillSlugs.length.toString()
1943
2316
  }
1944
2317
  };
1945
2318
  }
@@ -1950,19 +2323,23 @@ const OpencodeWizardSkillsPlugin = async input => {
1950
2323
  pluginId: PLUGIN_ID,
1951
2324
  runtimeMode: 'tool_fetch_only',
1952
2325
  status: 'not_found',
1953
- requestedDirectoryPath: publishedSkillsResult.directoryPath,
1954
- workspaceResolution: toWorkspaceResolutionOutput(publishedSkillsResult.workspaceResolution),
2326
+ requestedDirectoryPath: filteredPublishedSkillsResult.directoryPath,
2327
+ workspaceResolution: toWorkspaceResolutionOutput(filteredPublishedSkillsResult.workspaceResolution),
1955
2328
  requestedSkill: requestedSkills[0],
1956
- availableSkills: publishedSkillsResult.fetchResult.payload.skills.map(toPublishedSkillSummary)
2329
+ availableSkills: filteredPublishedSkillsResult.fetchResult.payload.skills.map(toPublishedSkillSummary),
2330
+ ignoredPublishedSkills: {
2331
+ scopeKey: filteredPublishedSkillsResult.ignoreState.scopeKey,
2332
+ count: filteredPublishedSkillsResult.ignoreState.ignoredSkillSlugs.length
2333
+ }
1957
2334
  }, null, 2),
1958
2335
  metadata: {
1959
2336
  status: 'not_found',
1960
- ...toWorkspaceResolutionMetadata(publishedSkillsResult.workspaceResolution)
2337
+ ...toWorkspaceResolutionMetadata(filteredPublishedSkillsResult.workspaceResolution)
1961
2338
  }
1962
2339
  };
1963
2340
  }
1964
2341
  let skillDetailResults = await Promise.all(selection.selectedItems.map(item => loadPublishedSkillDetail({
1965
- workspaceResolution: publishedSkillsResult.workspaceResolution,
2342
+ workspaceResolution: filteredPublishedSkillsResult.workspaceResolution,
1966
2343
  item,
1967
2344
  signal: context.abort,
1968
2345
  useCache: !args.refresh,
@@ -1974,7 +2351,7 @@ const OpencodeWizardSkillsPlugin = async input => {
1974
2351
  await schedulePresenceStart(authState);
1975
2352
  });
1976
2353
  skillDetailResults = await Promise.all(selection.selectedItems.map(item => loadPublishedSkillDetail({
1977
- workspaceResolution: publishedSkillsResult.workspaceResolution,
2354
+ workspaceResolution: filteredPublishedSkillsResult.workspaceResolution,
1978
2355
  item,
1979
2356
  signal: context.abort,
1980
2357
  useCache: false,
@@ -2000,7 +2377,7 @@ const OpencodeWizardSkillsPlugin = async input => {
2000
2377
  context.metadata({
2001
2378
  title: `opencode-wizard published skill: ${detail.artifactName || detail.skillName}`,
2002
2379
  metadata: {
2003
- ...toWorkspaceResolutionMetadata(publishedSkillsResult.workspaceResolution),
2380
+ ...toWorkspaceResolutionMetadata(filteredPublishedSkillsResult.workspaceResolution),
2004
2381
  skillSlug: detail.skillSlug,
2005
2382
  version: detail.version
2006
2383
  }
@@ -2010,16 +2387,16 @@ const OpencodeWizardSkillsPlugin = async input => {
2010
2387
  output: JSON.stringify({
2011
2388
  pluginId: PLUGIN_ID,
2012
2389
  runtimeMode: 'tool_fetch_only',
2013
- requestedDirectoryPath: publishedSkillsResult.directoryPath,
2014
- workspaceResolution: toWorkspaceResolutionOutput(publishedSkillsResult.workspaceResolution),
2015
- workspace: publishedSkillsResult.fetchResult.payload.workspace,
2016
- fetchedAt: publishedSkillsResult.fetchResult.fetchedAt,
2017
- source: publishedSkillsResult.fetchResult.source,
2390
+ requestedDirectoryPath: filteredPublishedSkillsResult.directoryPath,
2391
+ workspaceResolution: toWorkspaceResolutionOutput(filteredPublishedSkillsResult.workspaceResolution),
2392
+ workspace: filteredPublishedSkillsResult.fetchResult.payload.workspace,
2393
+ fetchedAt: filteredPublishedSkillsResult.fetchResult.fetchedAt,
2394
+ source: filteredPublishedSkillsResult.fetchResult.source,
2018
2395
  skill: detail
2019
2396
  }, null, 2),
2020
2397
  metadata: {
2021
2398
  status: 'ready',
2022
- ...toWorkspaceResolutionMetadata(publishedSkillsResult.workspaceResolution),
2399
+ ...toWorkspaceResolutionMetadata(filteredPublishedSkillsResult.workspaceResolution),
2023
2400
  skillSlug: detail.skillSlug
2024
2401
  }
2025
2402
  };
@@ -2027,7 +2404,7 @@ const OpencodeWizardSkillsPlugin = async input => {
2027
2404
  context.metadata({
2028
2405
  title: `opencode-wizard published skills fetch: ${skillDetails.length}`,
2029
2406
  metadata: {
2030
- ...toWorkspaceResolutionMetadata(publishedSkillsResult.workspaceResolution),
2407
+ ...toWorkspaceResolutionMetadata(filteredPublishedSkillsResult.workspaceResolution),
2031
2408
  requestedCount: requestedSkills.length.toString(),
2032
2409
  matchedCount: skillDetails.length.toString()
2033
2410
  }
@@ -2037,18 +2414,18 @@ const OpencodeWizardSkillsPlugin = async input => {
2037
2414
  output: JSON.stringify({
2038
2415
  pluginId: PLUGIN_ID,
2039
2416
  runtimeMode: 'tool_fetch_only',
2040
- requestedDirectoryPath: publishedSkillsResult.directoryPath,
2041
- workspaceResolution: toWorkspaceResolutionOutput(publishedSkillsResult.workspaceResolution),
2042
- workspace: publishedSkillsResult.fetchResult.payload.workspace,
2043
- fetchedAt: publishedSkillsResult.fetchResult.fetchedAt,
2044
- source: publishedSkillsResult.fetchResult.source,
2417
+ requestedDirectoryPath: filteredPublishedSkillsResult.directoryPath,
2418
+ workspaceResolution: toWorkspaceResolutionOutput(filteredPublishedSkillsResult.workspaceResolution),
2419
+ workspace: filteredPublishedSkillsResult.fetchResult.payload.workspace,
2420
+ fetchedAt: filteredPublishedSkillsResult.fetchResult.fetchedAt,
2421
+ source: filteredPublishedSkillsResult.fetchResult.source,
2045
2422
  requestedSkills,
2046
2423
  missingSkills: selection.missingIdentifiers,
2047
2424
  skills: skillDetails
2048
2425
  }, null, 2),
2049
2426
  metadata: {
2050
2427
  status: selection.missingIdentifiers.length > 0 ? 'partial' : 'ready',
2051
- ...toWorkspaceResolutionMetadata(publishedSkillsResult.workspaceResolution),
2428
+ ...toWorkspaceResolutionMetadata(filteredPublishedSkillsResult.workspaceResolution),
2052
2429
  matchedCount: skillDetails.length.toString()
2053
2430
  }
2054
2431
  };
@@ -2083,7 +2460,7 @@ const OpencodeWizardSkillsPlugin = async input => {
2083
2460
  metadata
2084
2461
  });
2085
2462
  return {
2086
- output: JSON.stringify(snapshot, null, 2),
2463
+ output: JSON.stringify(toAiFacingPluginStatusSnapshot(snapshot), null, 2),
2087
2464
  metadata
2088
2465
  };
2089
2466
  };
@@ -2143,11 +2520,12 @@ const OpencodeWizardSkillsPlugin = async input => {
2143
2520
  return;
2144
2521
  }
2145
2522
  }
2523
+ const filteredPublishedSkillsResult = await filterIgnoredPublishedSkills(config, publishedSkillsResult);
2146
2524
  const details = await loadSystemNoteDetails({
2147
- publishedSkillsResult,
2525
+ publishedSkillsResult: filteredPublishedSkillsResult,
2148
2526
  signal: AbortSignal.timeout(5_000)
2149
2527
  });
2150
- const systemNote = buildSystemNote(publishedSkillsResult, config, details);
2528
+ const systemNote = buildSystemNote(filteredPublishedSkillsResult, config, details);
2151
2529
  if (!systemNote) return;
2152
2530
  output.system.push(systemNote);
2153
2531
  }