@aexol/opencode-wizard 0.3.1 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -1
- package/dist/server.d.ts +5 -0
- package/dist/server.js +256 -10
- package/dist/server.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -50,7 +50,13 @@ Catalog discovery uses the backend-issued plugin session token stored at `~/.con
|
|
|
50
50
|
|
|
51
51
|
Call `opencode_wizard_published_skills_fetch` without `skill` or `skills` to manually bootstrap plugin login if needed and return catalog-only discovery output for the current directory scope.
|
|
52
52
|
|
|
53
|
-
No-arg discovery returns published skill summaries, assignment counts split into `global` and `
|
|
53
|
+
No-arg discovery returns published skill summaries, assignment counts split into `global`, `project`, `user`, and `other`, policy metadata, and no markdown bodies. Existing `skill` and `skills` calls still fetch one or more full skill body/detail payloads by slug, artifact name, or skill name.
|
|
54
|
+
|
|
55
|
+
Workspace delivery still follows the backend contract: the plugin sends `workspaceSlug` when it has one, otherwise falls back to `repositoryUrl`. The plugin now prefers configured or learned workspace slug mappings over a repo-basename fallback, and learns durable slug-to-repo mappings from successful backend catalog responses so worktrees and nontrivial repo roots stay aligned.
|
|
56
|
+
|
|
57
|
+
Published skill fetches still support `refresh: true`, but normal cache entries now self-expire after 30 seconds and fetch/status payloads surface `source`, `workspaceSlug`, and `workspaceSlugSource` so stale-vs-refreshed behavior is visible without relying on manual cache deletion.
|
|
58
|
+
|
|
59
|
+
Use `opencode_wizard_published_skill_preference_set` for non-TUI preference actions (`install`, `uninstall`, `ignore`, `unignore`) against the same server-backed preference API used by the TUI overlay.
|
|
54
60
|
|
|
55
61
|
`GLOBAL_CONTEXT` skills are active context skills and are not meant to be installed per project. `PROJECT_INSTALLABLE` skills are gallery/installable skills that may be attached globally or to a workspace/path through assignment records; those assignments remain the source of truth for what is active in a catalog response.
|
|
56
62
|
|
package/dist/server.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ type ResolvedConfig = {
|
|
|
5
5
|
authSessionUrl: string;
|
|
6
6
|
presenceUrl: string;
|
|
7
7
|
actionsUrl: string;
|
|
8
|
+
configuredWorkspaceSlug: string | null;
|
|
8
9
|
fallbackWorkspaceSlug: string;
|
|
9
10
|
rootSkillSeedPath: string;
|
|
10
11
|
authStatePath: string;
|
|
@@ -13,6 +14,8 @@ type WorkspaceResolution = {
|
|
|
13
14
|
requestedDirectory: string;
|
|
14
15
|
repositoryRoot: string;
|
|
15
16
|
repositoryUrl: string | null;
|
|
17
|
+
workspaceSlug?: string | null;
|
|
18
|
+
workspaceSlugSource?: 'configured' | 'learned' | 'backend' | 'fallback' | 'repositoryUrl' | 'placeholder';
|
|
16
19
|
fallbackWorkspaceSlug: string | null;
|
|
17
20
|
directoryPath: string;
|
|
18
21
|
cacheKey: string;
|
|
@@ -271,6 +274,8 @@ declare const toWorkspaceResolutionOutput: (resolution: WorkspaceResolution) =>
|
|
|
271
274
|
requestedDirectory: string;
|
|
272
275
|
repositoryRoot: string;
|
|
273
276
|
repositoryUrl: string | null;
|
|
277
|
+
workspaceSlug: string | null | undefined;
|
|
278
|
+
workspaceSlugSource: "configured" | "learned" | "backend" | "fallback" | "repositoryUrl" | "placeholder" | undefined;
|
|
274
279
|
fallbackWorkspaceSlug: string | null;
|
|
275
280
|
directoryPath: string;
|
|
276
281
|
};
|
package/dist/server.js
CHANGED
|
@@ -14,6 +14,7 @@ const MODULE_FILE_PATH = fileURLToPath(import.meta.url);
|
|
|
14
14
|
const PACKAGE_ROOT_PATH = path.resolve(path.dirname(MODULE_FILE_PATH), '..');
|
|
15
15
|
export const PLUGIN_ID = 'opencode-wizard';
|
|
16
16
|
const CACHE_TTL_MS = 30_000;
|
|
17
|
+
const WORKSPACE_MAPPING_LIMIT = 100;
|
|
17
18
|
const ROOT_SKILL_SEED_PATH = '.opencode/skills';
|
|
18
19
|
const GLOBAL_CONFIG_PATH = path.join(os.homedir(), '.config', 'opencode', 'opencode-wizard.json');
|
|
19
20
|
const LEGACY_AUTH_STATE_PATH = 'plugin/opencode-wizard/.generated/auth-state.json';
|
|
@@ -55,7 +56,7 @@ const statusPathLoginBootstrap = {
|
|
|
55
56
|
failedAt: null
|
|
56
57
|
};
|
|
57
58
|
const importOpencodePluginModule = new Function('specifier', 'return import(specifier)');
|
|
58
|
-
export const AVAILABLE_PUBLISHED_SKILL_TOOLS = ['opencode_wizard_published_skills_fetch', 'opencode_wizard_status'];
|
|
59
|
+
export const AVAILABLE_PUBLISHED_SKILL_TOOLS = ['opencode_wizard_published_skills_fetch', 'opencode_wizard_published_skill_preference_set', 'opencode_wizard_status'];
|
|
59
60
|
let publishedSkillPreferenceCacheVersion = 0;
|
|
60
61
|
export const NATIVE_SKILLS_URL_COMPATIBILITY = {
|
|
61
62
|
configKey: 'skills.urls',
|
|
@@ -330,14 +331,19 @@ const resolveBackendOrigin = async worktree => {
|
|
|
330
331
|
localBackendOrigin: envValues.get('OPENCODE_WIZARD_BACKEND_ORIGIN')
|
|
331
332
|
});
|
|
332
333
|
};
|
|
334
|
+
const readConfiguredWorkspaceSlug = () => {
|
|
335
|
+
const configuredWorkspaceSlug = process.env.OPENCODE_WIZARD_SKILLS_WORKSPACE_SLUG?.trim();
|
|
336
|
+
if (!configuredWorkspaceSlug) return null;
|
|
337
|
+
return toWorkspaceSlug(configuredWorkspaceSlug);
|
|
338
|
+
};
|
|
333
339
|
const toWorkspaceSlug = value => {
|
|
334
340
|
const normalized = value.trim().toLowerCase().replace(/[^a-z0-9-]+/gu, '-').replace(/^-+|-+$/gu, '');
|
|
335
341
|
if (normalized) return normalized;
|
|
336
342
|
return 'workspace';
|
|
337
343
|
};
|
|
338
344
|
const resolveFallbackWorkspaceSlug = worktree => {
|
|
339
|
-
const configuredWorkspaceSlug =
|
|
340
|
-
if (configuredWorkspaceSlug) return
|
|
345
|
+
const configuredWorkspaceSlug = readConfiguredWorkspaceSlug();
|
|
346
|
+
if (configuredWorkspaceSlug) return configuredWorkspaceSlug;
|
|
341
347
|
return toWorkspaceSlug(path.basename(path.resolve(worktree)));
|
|
342
348
|
};
|
|
343
349
|
export const resolveConfig = async worktree => {
|
|
@@ -348,6 +354,7 @@ export const resolveConfig = async worktree => {
|
|
|
348
354
|
authSessionUrl: `${backendOrigin}/api/opencode-plugin/oauth/session`,
|
|
349
355
|
presenceUrl: `${backendOrigin}/api/opencode-plugin/presence`,
|
|
350
356
|
actionsUrl: `${backendOrigin}/api/opencode-plugin/actions`,
|
|
357
|
+
configuredWorkspaceSlug: readConfiguredWorkspaceSlug(),
|
|
351
358
|
fallbackWorkspaceSlug: resolveFallbackWorkspaceSlug(worktree),
|
|
352
359
|
rootSkillSeedPath: ROOT_SKILL_SEED_PATH,
|
|
353
360
|
authStatePath: GLOBAL_CONFIG_PATH
|
|
@@ -408,22 +415,31 @@ const resolveWorkspace = async ({
|
|
|
408
415
|
const gitRoot = await resolveGitRoot(requestedDirectory);
|
|
409
416
|
const repositoryRoot = gitRoot ?? requestedDirectory;
|
|
410
417
|
const repositoryUrl = gitRoot ? await resolveGitRemoteOriginUrl(gitRoot) : null;
|
|
418
|
+
const learnedWorkspaceMapping = await findWorkspaceSlugMapping({
|
|
419
|
+
configFile: config.authStatePath,
|
|
420
|
+
repositoryUrl,
|
|
421
|
+
repositoryRoot
|
|
422
|
+
});
|
|
411
423
|
const fallbackWorkspaceSlug = config.fallbackWorkspaceSlug;
|
|
412
424
|
const directoryPath = normalizeRepositoryPath(repositoryRoot, requestedDirectory);
|
|
413
|
-
const
|
|
425
|
+
const workspaceSlug = config.configuredWorkspaceSlug ?? learnedWorkspaceMapping?.workspaceSlug ?? fallbackWorkspaceSlug ?? null;
|
|
426
|
+
const workspaceSlugSource = config.configuredWorkspaceSlug ? 'configured' : learnedWorkspaceMapping?.workspaceSlug ? 'learned' : fallbackWorkspaceSlug ? 'fallback' : repositoryUrl ? 'repositoryUrl' : 'placeholder';
|
|
427
|
+
const workspaceIdentity = workspaceSlug ? `workspaceSlug:${workspaceSlug}` : repositoryUrl ? `repository:${repositoryUrl}` : 'workspace:placeholder';
|
|
414
428
|
return {
|
|
415
429
|
requestedDirectory,
|
|
416
430
|
repositoryRoot,
|
|
417
431
|
repositoryUrl,
|
|
432
|
+
workspaceSlug,
|
|
433
|
+
workspaceSlugSource,
|
|
418
434
|
fallbackWorkspaceSlug,
|
|
419
435
|
directoryPath,
|
|
420
436
|
cacheKey: JSON.stringify([workspaceIdentity, directoryPath])
|
|
421
437
|
};
|
|
422
438
|
};
|
|
423
439
|
const toDeliveryInput = resolution => {
|
|
424
|
-
if (resolution.
|
|
440
|
+
if (resolution.workspaceSlug) {
|
|
425
441
|
return {
|
|
426
|
-
workspaceSlug: resolution.
|
|
442
|
+
workspaceSlug: resolution.workspaceSlug,
|
|
427
443
|
directoryPath: resolution.directoryPath
|
|
428
444
|
};
|
|
429
445
|
}
|
|
@@ -475,6 +491,76 @@ const withoutLegacyPublishedSkillPreferences = config => {
|
|
|
475
491
|
const hasLegacyPublishedSkillPreferences = config => {
|
|
476
492
|
return Object.prototype.hasOwnProperty.call(config, 'publishedSkillPreferences') || Object.prototype.hasOwnProperty.call(config, 'ignoredPublishedSkills');
|
|
477
493
|
};
|
|
494
|
+
const isStoredWorkspaceSlugMapping = value => {
|
|
495
|
+
if (!isRecord(value)) return false;
|
|
496
|
+
const {
|
|
497
|
+
repositoryUrl,
|
|
498
|
+
repositoryRoot,
|
|
499
|
+
workspaceSlug,
|
|
500
|
+
updatedAt
|
|
501
|
+
} = value;
|
|
502
|
+
const hasValidRepositoryUrl = repositoryUrl === null || typeof repositoryUrl === 'string';
|
|
503
|
+
const hasValidRepositoryRoot = repositoryRoot === null || typeof repositoryRoot === 'string';
|
|
504
|
+
return hasValidRepositoryUrl && hasValidRepositoryRoot && typeof workspaceSlug === 'string' && workspaceSlug.trim().length > 0 && isValidIsoDateString(updatedAt);
|
|
505
|
+
};
|
|
506
|
+
const readWorkspaceSlugMappings = async configFile => {
|
|
507
|
+
const storedConfig = await readGlobalConfig(configFile);
|
|
508
|
+
const mappings = storedConfig.workspaceSlugMappings;
|
|
509
|
+
if (!Array.isArray(mappings)) return [];
|
|
510
|
+
return mappings.filter(isStoredWorkspaceSlugMapping).slice(0, WORKSPACE_MAPPING_LIMIT);
|
|
511
|
+
};
|
|
512
|
+
const writeWorkspaceSlugMappings = async (configFile, nextMappings) => {
|
|
513
|
+
const storedConfig = await readGlobalConfig(configFile);
|
|
514
|
+
await writeGlobalConfig(configFile, {
|
|
515
|
+
...withoutLegacyPublishedSkillPreferences(storedConfig),
|
|
516
|
+
workspaceSlugMappings: nextMappings.slice(0, WORKSPACE_MAPPING_LIMIT)
|
|
517
|
+
});
|
|
518
|
+
};
|
|
519
|
+
const normalizeStoredRepositoryRoot = value => {
|
|
520
|
+
if (!value) return null;
|
|
521
|
+
return normalizeAbsolutePath(value);
|
|
522
|
+
};
|
|
523
|
+
const upsertWorkspaceSlugMapping = async ({
|
|
524
|
+
configFile,
|
|
525
|
+
repositoryUrl,
|
|
526
|
+
repositoryRoot,
|
|
527
|
+
workspaceSlug
|
|
528
|
+
}) => {
|
|
529
|
+
const normalizedWorkspaceSlug = toWorkspaceSlug(workspaceSlug);
|
|
530
|
+
if (!normalizedWorkspaceSlug) return;
|
|
531
|
+
const normalizedRepositoryRoot = normalizeStoredRepositoryRoot(repositoryRoot);
|
|
532
|
+
const existingMappings = await readWorkspaceSlugMappings(configFile);
|
|
533
|
+
const filteredMappings = existingMappings.filter(mapping => {
|
|
534
|
+
if (repositoryUrl && mapping.repositoryUrl === repositoryUrl) return false;
|
|
535
|
+
if (normalizedRepositoryRoot && normalizeStoredRepositoryRoot(mapping.repositoryRoot) === normalizedRepositoryRoot) {
|
|
536
|
+
return false;
|
|
537
|
+
}
|
|
538
|
+
return true;
|
|
539
|
+
});
|
|
540
|
+
await writeWorkspaceSlugMappings(configFile, [{
|
|
541
|
+
repositoryUrl,
|
|
542
|
+
repositoryRoot: normalizedRepositoryRoot,
|
|
543
|
+
workspaceSlug: normalizedWorkspaceSlug,
|
|
544
|
+
updatedAt: new Date().toISOString()
|
|
545
|
+
}, ...filteredMappings]);
|
|
546
|
+
};
|
|
547
|
+
const findWorkspaceSlugMapping = async ({
|
|
548
|
+
configFile,
|
|
549
|
+
repositoryUrl,
|
|
550
|
+
repositoryRoot
|
|
551
|
+
}) => {
|
|
552
|
+
const normalizedRepositoryRoot = normalizeStoredRepositoryRoot(repositoryRoot);
|
|
553
|
+
const mappings = await readWorkspaceSlugMappings(configFile);
|
|
554
|
+
if (repositoryUrl) {
|
|
555
|
+
const repositoryMatch = mappings.find(mapping => mapping.repositoryUrl === repositoryUrl);
|
|
556
|
+
if (repositoryMatch) return repositoryMatch;
|
|
557
|
+
}
|
|
558
|
+
if (normalizedRepositoryRoot) {
|
|
559
|
+
const rootMatch = mappings.find(mapping => normalizeStoredRepositoryRoot(mapping.repositoryRoot) === normalizedRepositoryRoot);
|
|
560
|
+
if (rootMatch) return rootMatch;
|
|
561
|
+
}
|
|
562
|
+
return null;
|
|
563
|
+
};
|
|
478
564
|
const readGlobalAuthState = async configFile => {
|
|
479
565
|
const storedConfig = await readGlobalConfig(configFile);
|
|
480
566
|
const storedAuthState = storedConfig.auth;
|
|
@@ -517,8 +603,21 @@ const toIgnoredSkillSlug = value => {
|
|
|
517
603
|
if (!normalized) return null;
|
|
518
604
|
return normalized;
|
|
519
605
|
};
|
|
606
|
+
const toPublishedSkillPreferenceAction = value => {
|
|
607
|
+
const normalized = value.trim().toLowerCase();
|
|
608
|
+
if (normalized === 'install' || normalized === 'uninstall' || normalized === 'ignore' || normalized === 'unignore') {
|
|
609
|
+
return normalized;
|
|
610
|
+
}
|
|
611
|
+
throw new Error('Published skill preference action must be one of: install, uninstall, ignore, unignore.');
|
|
612
|
+
};
|
|
613
|
+
const toPublishedSkillPreferenceScope = (value, defaultScope) => {
|
|
614
|
+
if (!value) return defaultScope;
|
|
615
|
+
const normalized = value.trim().toLowerCase();
|
|
616
|
+
if (normalized === 'global' || normalized === 'project') return normalized;
|
|
617
|
+
throw new Error('Published skill preferenceScope must be either global or project.');
|
|
618
|
+
};
|
|
520
619
|
const getPublishedSkillIgnoreScopeKey = (resolution, payload) => {
|
|
521
|
-
const workspaceSlug = payload?.workspace?.slug ?? resolution.fallbackWorkspaceSlug;
|
|
620
|
+
const workspaceSlug = payload?.workspace?.slug ?? resolution.workspaceSlug ?? resolution.fallbackWorkspaceSlug;
|
|
522
621
|
if (workspaceSlug) return `workspace:${toWorkspaceSlug(workspaceSlug)}`;
|
|
523
622
|
if (resolution.repositoryUrl) return `repository:${resolution.repositoryUrl}`;
|
|
524
623
|
return `path:${toWorkspaceSlug(path.basename(resolution.repositoryRoot))}`;
|
|
@@ -866,6 +965,8 @@ const toWorkspaceResolutionOutput = resolution => ({
|
|
|
866
965
|
requestedDirectory: resolution.requestedDirectory,
|
|
867
966
|
repositoryRoot: resolution.repositoryRoot,
|
|
868
967
|
repositoryUrl: resolution.repositoryUrl,
|
|
968
|
+
workspaceSlug: resolution.workspaceSlug,
|
|
969
|
+
workspaceSlugSource: resolution.workspaceSlugSource,
|
|
869
970
|
fallbackWorkspaceSlug: resolution.fallbackWorkspaceSlug,
|
|
870
971
|
directoryPath: resolution.directoryPath
|
|
871
972
|
});
|
|
@@ -873,6 +974,8 @@ const toWorkspaceResolutionMetadata = resolution => ({
|
|
|
873
974
|
directoryPath: resolution.directoryPath,
|
|
874
975
|
repositoryRoot: resolution.repositoryRoot,
|
|
875
976
|
repositoryUrl: resolution.repositoryUrl ?? '',
|
|
977
|
+
workspaceSlug: resolution.workspaceSlug ?? '',
|
|
978
|
+
workspaceSlugSource: resolution.workspaceSlugSource ?? 'placeholder',
|
|
876
979
|
fallbackWorkspaceSlug: resolution.fallbackWorkspaceSlug ?? ''
|
|
877
980
|
});
|
|
878
981
|
const formatStatusOutput = async (worktree, config, publishedSkillsResult, loginBootstrapSnapshot) => {
|
|
@@ -1170,7 +1273,12 @@ const toPluginStatusMetadata = snapshot => ({
|
|
|
1170
1273
|
pluginStatus: snapshot.status,
|
|
1171
1274
|
authStatus: snapshot.authState.status,
|
|
1172
1275
|
authEmail: snapshot.authState.email ?? '',
|
|
1173
|
-
authUserId: snapshot.authState.userId ?? ''
|
|
1276
|
+
authUserId: snapshot.authState.userId ?? '',
|
|
1277
|
+
directoryPath: snapshot.workspaceResolution.directoryPath,
|
|
1278
|
+
repositoryUrl: snapshot.workspaceResolution.repositoryUrl ?? '',
|
|
1279
|
+
source: snapshot.source,
|
|
1280
|
+
workspaceSlug: snapshot.workspaceResolution.workspaceSlug ?? '',
|
|
1281
|
+
workspaceSlugSource: snapshot.workspaceResolution.workspaceSlugSource ?? 'placeholder'
|
|
1174
1282
|
});
|
|
1175
1283
|
const isUnauthorizedGraphQlMessage = message => {
|
|
1176
1284
|
const normalizedMessage = message.toLowerCase();
|
|
@@ -1491,6 +1599,24 @@ const fetchPublishedSkillsCatalog = async (worktree, config, resolution, signal,
|
|
|
1491
1599
|
source: 'network'
|
|
1492
1600
|
};
|
|
1493
1601
|
};
|
|
1602
|
+
const maybePersistWorkspaceSlugFromCatalog = async ({
|
|
1603
|
+
config,
|
|
1604
|
+
resolution,
|
|
1605
|
+
fetchResult
|
|
1606
|
+
}) => {
|
|
1607
|
+
if (!fetchResult.ok) return;
|
|
1608
|
+
const backendWorkspaceSlug = fetchResult.payload.workspace?.slug?.trim();
|
|
1609
|
+
if (!backendWorkspaceSlug) return;
|
|
1610
|
+
const normalizedWorkspaceSlug = toWorkspaceSlug(backendWorkspaceSlug);
|
|
1611
|
+
if (!normalizedWorkspaceSlug) return;
|
|
1612
|
+
if (resolution.workspaceSlug === normalizedWorkspaceSlug && resolution.workspaceSlugSource !== 'fallback') return;
|
|
1613
|
+
await upsertWorkspaceSlugMapping({
|
|
1614
|
+
configFile: config.authStatePath,
|
|
1615
|
+
repositoryUrl: resolution.repositoryUrl,
|
|
1616
|
+
repositoryRoot: resolution.repositoryRoot,
|
|
1617
|
+
workspaceSlug: normalizedWorkspaceSlug
|
|
1618
|
+
});
|
|
1619
|
+
};
|
|
1494
1620
|
const fetchPublishedSkillDetail = async ({
|
|
1495
1621
|
worktree,
|
|
1496
1622
|
config,
|
|
@@ -1928,6 +2054,11 @@ const OpencodeWizardSkillsPlugin = async input => {
|
|
|
1928
2054
|
}
|
|
1929
2055
|
const requestPromise = (async () => {
|
|
1930
2056
|
const fetchResult = await fetchPublishedSkillsCatalog(input.worktree, config, workspaceResolution, signal, clearPublishedSkillState);
|
|
2057
|
+
await maybePersistWorkspaceSlugFromCatalog({
|
|
2058
|
+
config,
|
|
2059
|
+
resolution: workspaceResolution,
|
|
2060
|
+
fetchResult
|
|
2061
|
+
});
|
|
1931
2062
|
cache.set(cacheKey, {
|
|
1932
2063
|
result: fetchResult,
|
|
1933
2064
|
expiresAt: Date.now() + CACHE_TTL_MS
|
|
@@ -1958,7 +2089,7 @@ const OpencodeWizardSkillsPlugin = async input => {
|
|
|
1958
2089
|
const cacheKey = getDetailCacheKey(catalogCacheKey, item.skillVersion.id);
|
|
1959
2090
|
const inflightKey = getDetailInflightKey(catalogCacheKey, item.skillVersion.id, purpose);
|
|
1960
2091
|
const cached = detailCache.get(cacheKey);
|
|
1961
|
-
if (
|
|
2092
|
+
if (useCache && cached && cached.expiresAt > Date.now()) {
|
|
1962
2093
|
return {
|
|
1963
2094
|
ok: true,
|
|
1964
2095
|
detail: toPublishedSkillDetail({
|
|
@@ -2135,11 +2266,13 @@ const OpencodeWizardSkillsPlugin = async input => {
|
|
|
2135
2266
|
},
|
|
2136
2267
|
fetchedAt: filteredPublishedSkillsResult.fetchResult.fetchedAt,
|
|
2137
2268
|
source: filteredPublishedSkillsResult.fetchResult.source,
|
|
2138
|
-
|
|
2269
|
+
cacheTtlMs: CACHE_TTL_MS,
|
|
2270
|
+
message: args.refresh ? 'Catalog discovery refreshed from the backend. Provide `skill` for one identifier or prefer `skills` for comma/newline-separated multiple identifiers to fetch markdown bodies/details.' : 'Catalog discovery only. Cached results expire automatically after 30 seconds, or pass `refresh: true` to force a backend refresh immediately. Provide `skill` for one identifier or prefer `skills` for comma/newline-separated multiple identifiers to fetch markdown bodies/details.'
|
|
2139
2271
|
}, null, 2),
|
|
2140
2272
|
metadata: {
|
|
2141
2273
|
status: 'ready',
|
|
2142
2274
|
...toWorkspaceResolutionMetadata(filteredPublishedSkillsResult.workspaceResolution),
|
|
2275
|
+
source: filteredPublishedSkillsResult.fetchResult.source,
|
|
2143
2276
|
publishedSkillCount: catalog.publishedSkillCount.toString(),
|
|
2144
2277
|
globalAssignmentCount: catalog.assignmentCounts.global.toString(),
|
|
2145
2278
|
projectAssignmentCount: catalog.assignmentCounts.project.toString(),
|
|
@@ -2229,6 +2362,7 @@ const OpencodeWizardSkillsPlugin = async input => {
|
|
|
2229
2362
|
metadata: {
|
|
2230
2363
|
status: 'ready',
|
|
2231
2364
|
...toWorkspaceResolutionMetadata(filteredPublishedSkillsResult.workspaceResolution),
|
|
2365
|
+
source: filteredPublishedSkillsResult.fetchResult.source,
|
|
2232
2366
|
skillSlug: detail.skillSlug
|
|
2233
2367
|
}
|
|
2234
2368
|
};
|
|
@@ -2258,6 +2392,7 @@ const OpencodeWizardSkillsPlugin = async input => {
|
|
|
2258
2392
|
metadata: {
|
|
2259
2393
|
status: selection.missingIdentifiers.length > 0 ? 'partial' : 'ready',
|
|
2260
2394
|
...toWorkspaceResolutionMetadata(filteredPublishedSkillsResult.workspaceResolution),
|
|
2395
|
+
source: filteredPublishedSkillsResult.fetchResult.source,
|
|
2261
2396
|
matchedCount: skillDetails.length.toString()
|
|
2262
2397
|
}
|
|
2263
2398
|
};
|
|
@@ -2286,6 +2421,9 @@ const OpencodeWizardSkillsPlugin = async input => {
|
|
|
2286
2421
|
// Keep returning the safe missing-auth snapshot when interactive login is cancelled or fails.
|
|
2287
2422
|
}
|
|
2288
2423
|
}
|
|
2424
|
+
if (snapshot.status === 'ready') {
|
|
2425
|
+
await scheduleInteractivePresenceStart();
|
|
2426
|
+
}
|
|
2289
2427
|
const metadata = toPluginStatusMetadata(snapshot);
|
|
2290
2428
|
context.metadata({
|
|
2291
2429
|
title: `opencode-wizard status: ${snapshot.status} / auth ${snapshot.authState.status}`,
|
|
@@ -2296,6 +2434,96 @@ const OpencodeWizardSkillsPlugin = async input => {
|
|
|
2296
2434
|
metadata
|
|
2297
2435
|
};
|
|
2298
2436
|
};
|
|
2437
|
+
const executePublishedSkillPreferenceTool = async ({
|
|
2438
|
+
args,
|
|
2439
|
+
context
|
|
2440
|
+
}) => {
|
|
2441
|
+
const requestedDirectory = normalizeDirectoryArg(context.directory, args.directory);
|
|
2442
|
+
const directoryPath = normalizeRepositoryPath(workspacePath, requestedDirectory);
|
|
2443
|
+
lastInteractiveDirectoryPath = directoryPath;
|
|
2444
|
+
const requestedSkill = typeof args.skill === 'string' ? args.skill.trim() : '';
|
|
2445
|
+
const emitPreferenceOutcome = async event => {
|
|
2446
|
+
await emitActionEventForCurrentSession({
|
|
2447
|
+
event,
|
|
2448
|
+
directoryPath
|
|
2449
|
+
});
|
|
2450
|
+
};
|
|
2451
|
+
try {
|
|
2452
|
+
if (!requestedSkill) {
|
|
2453
|
+
throw new Error('Published skill preference tool requires a non-empty skill slug, artifact name, or skill name.');
|
|
2454
|
+
}
|
|
2455
|
+
if (typeof args.action !== 'string') {
|
|
2456
|
+
throw new Error('Published skill preference tool requires an action: install, uninstall, ignore, or unignore.');
|
|
2457
|
+
}
|
|
2458
|
+
const action = toPublishedSkillPreferenceAction(args.action);
|
|
2459
|
+
const catalogResult = await loadPublishedSkillCatalog({
|
|
2460
|
+
directory: requestedDirectory,
|
|
2461
|
+
useCache: true,
|
|
2462
|
+
signal: context.abort
|
|
2463
|
+
});
|
|
2464
|
+
if (!catalogResult.fetchResult.ok) {
|
|
2465
|
+
throw new Error(`Cannot resolve published skill preference target: ${catalogResult.fetchResult.message}`);
|
|
2466
|
+
}
|
|
2467
|
+
const selectableCatalogSkills = catalogResult.fetchResult.payload.catalogSkills.map(item => ({
|
|
2468
|
+
...item,
|
|
2469
|
+
assignmentSource: 'CATALOG',
|
|
2470
|
+
assignmentType: 'PATH',
|
|
2471
|
+
scopePath: '',
|
|
2472
|
+
includeChildren: true
|
|
2473
|
+
}));
|
|
2474
|
+
const preferenceSelection = selectPublishedSkills({
|
|
2475
|
+
...catalogResult.fetchResult.payload,
|
|
2476
|
+
skills: [...catalogResult.fetchResult.payload.skills, ...selectableCatalogSkills, ...catalogResult.fetchResult.payload.userPreferences.ignoredSkills]
|
|
2477
|
+
}, [requestedSkill]);
|
|
2478
|
+
const matchedSkill = preferenceSelection.selectedItems[0];
|
|
2479
|
+
if (!matchedSkill) {
|
|
2480
|
+
throw new Error(`Published skill preference target was not found for identifier: ${requestedSkill}.`);
|
|
2481
|
+
}
|
|
2482
|
+
const skillSlug = matchedSkill.skill.slug;
|
|
2483
|
+
const preferenceState = action === 'ignore' || action === 'unignore' ? await setPublishedSkillIgnored({
|
|
2484
|
+
worktree: input.worktree,
|
|
2485
|
+
directory: requestedDirectory,
|
|
2486
|
+
skillSlug,
|
|
2487
|
+
ignored: action === 'ignore',
|
|
2488
|
+
preferenceScope: toPublishedSkillPreferenceScope(args.preferenceScope, 'project')
|
|
2489
|
+
}) : await setPublishedSkillInstalled({
|
|
2490
|
+
worktree: input.worktree,
|
|
2491
|
+
directory: requestedDirectory,
|
|
2492
|
+
skillSlug,
|
|
2493
|
+
installed: action === 'install',
|
|
2494
|
+
preferenceScope: toPublishedSkillPreferenceScope(args.preferenceScope, 'project')
|
|
2495
|
+
});
|
|
2496
|
+
await scheduleInteractivePresenceStart();
|
|
2497
|
+
await emitPreferenceOutcome('PREFERENCE_SUCCESS');
|
|
2498
|
+
const metadata = {
|
|
2499
|
+
status: 'updated',
|
|
2500
|
+
skillSlug,
|
|
2501
|
+
action,
|
|
2502
|
+
directoryPath,
|
|
2503
|
+
ignoredSkillCount: preferenceState.ignoredSkillSlugs.length.toString()
|
|
2504
|
+
};
|
|
2505
|
+
context.metadata({
|
|
2506
|
+
title: `opencode-wizard published skill preference: ${action} ${skillSlug}`,
|
|
2507
|
+
metadata
|
|
2508
|
+
});
|
|
2509
|
+
return {
|
|
2510
|
+
output: JSON.stringify({
|
|
2511
|
+
pluginId: PLUGIN_ID,
|
|
2512
|
+
status: 'updated',
|
|
2513
|
+
requestedIdentifier: requestedSkill,
|
|
2514
|
+
skillSlug,
|
|
2515
|
+
action,
|
|
2516
|
+
requestedDirectoryPath: directoryPath,
|
|
2517
|
+
preferenceState,
|
|
2518
|
+
message: 'Published skill preference updated through the shared server-backed API; TUI views will reflect this after refresh.'
|
|
2519
|
+
}, null, 2),
|
|
2520
|
+
metadata
|
|
2521
|
+
};
|
|
2522
|
+
} catch (error) {
|
|
2523
|
+
await emitPreferenceOutcome('PREFERENCE_FAILED');
|
|
2524
|
+
throw error;
|
|
2525
|
+
}
|
|
2526
|
+
};
|
|
2299
2527
|
return {
|
|
2300
2528
|
tool: {
|
|
2301
2529
|
opencode_wizard_published_skills_fetch: tool({
|
|
@@ -2313,6 +2541,21 @@ const OpencodeWizardSkillsPlugin = async input => {
|
|
|
2313
2541
|
});
|
|
2314
2542
|
}
|
|
2315
2543
|
}),
|
|
2544
|
+
opencode_wizard_published_skill_preference_set: tool({
|
|
2545
|
+
description: 'Install, uninstall, ignore, or unignore a backend-published wizard skill for non-TUI workflows using the same shared server-backed preference API as the TUI overlay',
|
|
2546
|
+
args: {
|
|
2547
|
+
skill: tool.schema.string().describe('Published skill slug, artifact name, or skill name to update'),
|
|
2548
|
+
action: tool.schema.string().describe('Preference action: install, uninstall, ignore, or unignore'),
|
|
2549
|
+
preferenceScope: tool.schema.string().optional().describe('Preference scope for the action: project or global; defaults to project'),
|
|
2550
|
+
directory: tool.schema.string().optional().describe('Optional absolute or relative directory override')
|
|
2551
|
+
},
|
|
2552
|
+
async execute(args, context) {
|
|
2553
|
+
return executePublishedSkillPreferenceTool({
|
|
2554
|
+
args,
|
|
2555
|
+
context
|
|
2556
|
+
});
|
|
2557
|
+
}
|
|
2558
|
+
}),
|
|
2316
2559
|
opencode_wizard_status: tool({
|
|
2317
2560
|
description: 'Report opencode-wizard plugin status, bootstrap auth when missing, and return a safe auth summary without exposing tokens',
|
|
2318
2561
|
args: {
|
|
@@ -2352,6 +2595,9 @@ const OpencodeWizardSkillsPlugin = async input => {
|
|
|
2352
2595
|
return;
|
|
2353
2596
|
}
|
|
2354
2597
|
}
|
|
2598
|
+
if (publishedSkillsResult.fetchResult.ok) {
|
|
2599
|
+
await scheduleInteractivePresenceStart();
|
|
2600
|
+
}
|
|
2355
2601
|
const filteredPublishedSkillsResult = await filterIgnoredPublishedSkills(config, publishedSkillsResult);
|
|
2356
2602
|
const details = await loadSystemNoteDetails({
|
|
2357
2603
|
publishedSkillsResult: filteredPublishedSkillsResult,
|