@aexol/opencode-wizard 0.1.15 → 0.1.16

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 CHANGED
@@ -21,6 +21,7 @@ Useful commands:
21
21
 
22
22
  ```bash
23
23
  npm run typecheck
24
+ npm run test
24
25
  npm run build
25
26
  npm run release:check
26
27
  ```
@@ -45,7 +46,7 @@ Use `skills.urls` only for public registries that are intentionally cacheable by
45
46
 
46
47
  ## Catalog discovery and auth bootstrap
47
48
 
48
- On chat/system-context startup, the plugin attempts to load the catalog automatically with the stored plugin auth at `~/.config/opencode/opencode-wizard.json` (`auth` field). If no valid plugin session exists, startup stays passive and reports that interactive fetch will bootstrap browser login when needed; it does not open the browser from system context.
49
+ Catalog discovery uses the backend-issued plugin session token stored at `~/.config/opencode/opencode-wizard.json` (`auth` field); the plugin does not persist or send Microsoft/Entra tokens to GraphQL. If no valid plugin session exists, no-arg `opencode_wizard_published_skills_fetch`, explicit `opencode_wizard_status`, TUI status, and chat/system-context startup may start the browser Entra PKCE flow and exchange the callback for a fresh backend-issued plugin session.
49
50
 
50
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.
51
52
 
@@ -0,0 +1,6 @@
1
+ export declare const PUBLISHED_BACKEND_ORIGIN = "https://opencode-wizard.aexol.work";
2
+ export declare const normalizeBackendOrigin: (value: string | undefined) => string | null;
3
+ export declare const resolveBackendOriginFromValues: ({ environmentBackendOrigin, localBackendOrigin, }: {
4
+ environmentBackendOrigin?: string;
5
+ localBackendOrigin?: string;
6
+ }) => string;
package/dist/config.js ADDED
@@ -0,0 +1,19 @@
1
+ export const PUBLISHED_BACKEND_ORIGIN = 'https://opencode-wizard.aexol.work';
2
+ export const normalizeBackendOrigin = value => {
3
+ if (!value) return null;
4
+ try {
5
+ const normalizedUrl = new URL(value);
6
+ return normalizedUrl.origin;
7
+ } catch {
8
+ return null;
9
+ }
10
+ };
11
+ export const resolveBackendOriginFromValues = ({
12
+ environmentBackendOrigin,
13
+ localBackendOrigin
14
+ }) => {
15
+ const configuredBackendOrigin = normalizeBackendOrigin(environmentBackendOrigin) ?? normalizeBackendOrigin(localBackendOrigin);
16
+ if (configuredBackendOrigin) return configuredBackendOrigin;
17
+ return PUBLISHED_BACKEND_ORIGIN;
18
+ };
19
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["PUBLISHED_BACKEND_ORIGIN","normalizeBackendOrigin","value","normalizedUrl","URL","origin","resolveBackendOriginFromValues","environmentBackendOrigin","localBackendOrigin","configuredBackendOrigin"],"sources":["../src/config.ts"],"sourcesContent":["export const PUBLISHED_BACKEND_ORIGIN = 'https://opencode-wizard.aexol.work';\n\nexport const normalizeBackendOrigin = (value: string | undefined): string | null => {\n if (!value) return null;\n\n try {\n const normalizedUrl = new URL(value);\n return normalizedUrl.origin;\n } catch {\n return null;\n }\n};\n\nexport const resolveBackendOriginFromValues = ({\n environmentBackendOrigin,\n localBackendOrigin,\n}: {\n environmentBackendOrigin?: string;\n localBackendOrigin?: string;\n}): string => {\n const configuredBackendOrigin =\n normalizeBackendOrigin(environmentBackendOrigin) ?? normalizeBackendOrigin(localBackendOrigin);\n\n if (configuredBackendOrigin) return configuredBackendOrigin;\n\n return PUBLISHED_BACKEND_ORIGIN;\n};\n"],"mappings":"AAAA,OAAO,MAAMA,wBAAwB,GAAG,oCAAoC;AAE5E,OAAO,MAAMC,sBAAsB,GAAIC,KAAyB,IAAoB;EAClF,IAAI,CAACA,KAAK,EAAE,OAAO,IAAI;EAEvB,IAAI;IACF,MAAMC,aAAa,GAAG,IAAIC,GAAG,CAACF,KAAK,CAAC;IACpC,OAAOC,aAAa,CAACE,MAAM;EAC7B,CAAC,CAAC,MAAM;IACN,OAAO,IAAI;EACb;AACF,CAAC;AAED,OAAO,MAAMC,8BAA8B,GAAGA,CAAC;EAC7CC,wBAAwB;EACxBC;AAIF,CAAC,KAAa;EACZ,MAAMC,uBAAuB,GAC3BR,sBAAsB,CAACM,wBAAwB,CAAC,IAAIN,sBAAsB,CAACO,kBAAkB,CAAC;EAEhG,IAAIC,uBAAuB,EAAE,OAAOA,uBAAuB;EAE3D,OAAOT,wBAAwB;AACjC,CAAC","ignoreList":[]}
package/dist/server.d.ts CHANGED
@@ -185,6 +185,11 @@ export type NativeSkillsUrlCompatibility = {
185
185
  guidance: string;
186
186
  };
187
187
  export declare const NATIVE_SKILLS_URL_COMPATIBILITY: NativeSkillsUrlCompatibility;
188
+ type PublishedSkillsResult = {
189
+ directoryPath: string;
190
+ workspaceResolution: WorkspaceResolution;
191
+ fetchResult: FetchResult;
192
+ };
188
193
  type PublishedSkillsIgnoreState = {
189
194
  scopeKey: string;
190
195
  userKey: string;
@@ -238,6 +243,7 @@ export declare const selectPublishedSkills: <TItem extends PublishedSkillCatalog
238
243
  selectedItems: TItem[];
239
244
  missingIdentifiers: string[];
240
245
  };
246
+ export declare const buildSystemNote: (result: PublishedSkillsResult, config: ResolvedConfig, details: PublishedSkillDetail[]) => string | null;
241
247
  declare const toWorkspaceResolutionOutput: (resolution: WorkspaceResolution) => {
242
248
  requestedDirectory: string;
243
249
  repositoryRoot: string;
package/dist/server.js CHANGED
@@ -6,6 +6,8 @@ import crypto from 'node:crypto';
6
6
  import { execFile } from 'node:child_process';
7
7
  import { promisify } from 'node:util';
8
8
  import { URL, fileURLToPath } from 'node:url';
9
+ import { resolveBackendOriginFromValues } from './config.js';
10
+ import { deleteFileIfExists, readJsonFile, writePrivateJsonFile } from './storage.js';
9
11
  const execFileAsync = promisify(execFile);
10
12
  const MODULE_FILE_PATH = fileURLToPath(import.meta.url);
11
13
  const PACKAGE_ROOT_PATH = path.resolve(path.dirname(MODULE_FILE_PATH), '..');
@@ -14,7 +16,6 @@ const CACHE_TTL_MS = 30_000;
14
16
  const ROOT_SKILL_SEED_PATH = '.opencode/skills';
15
17
  const GLOBAL_CONFIG_PATH = path.join(os.homedir(), '.config', 'opencode', 'opencode-wizard.json');
16
18
  const LEGACY_AUTH_STATE_PATH = 'plugin/opencode-wizard/.generated/auth-state.json';
17
- const PUBLISHED_BACKEND_ORIGIN = 'https://opencode-wizard.aexol.work';
18
19
  const OIDC_ISSUER = 'https://login.microsoftonline.com/86f4caf4-0d6f-4682-9a06-ea57f3e4e76c/v2.0';
19
20
  const OIDC_CLIENT_ID = 'da963901-2375-442b-9e99-14e59f43eda2';
20
21
  const OIDC_CALLBACK_ORIGIN = 'http://localhost:24953';
@@ -307,20 +308,12 @@ const readLocalEnvValues = async startDirectory => {
307
308
  return new Map();
308
309
  }
309
310
  };
310
- const normalizeBackendOrigin = value => {
311
- if (!value) return null;
312
- try {
313
- const normalizedUrl = new URL(value);
314
- return normalizedUrl.origin;
315
- } catch {
316
- return null;
317
- }
318
- };
319
311
  const resolveBackendOrigin = async worktree => {
320
312
  const envValues = await readLocalEnvValues(worktree);
321
- const configuredBackendOrigin = normalizeBackendOrigin(process.env.OPENCODE_WIZARD_BACKEND_ORIGIN) ?? normalizeBackendOrigin(envValues.get('OPENCODE_WIZARD_BACKEND_ORIGIN'));
322
- if (configuredBackendOrigin) return configuredBackendOrigin;
323
- return PUBLISHED_BACKEND_ORIGIN;
313
+ return resolveBackendOriginFromValues({
314
+ environmentBackendOrigin: process.env.OPENCODE_WIZARD_BACKEND_ORIGIN,
315
+ localBackendOrigin: envValues.get('OPENCODE_WIZARD_BACKEND_ORIGIN')
316
+ });
324
317
  };
325
318
  const toWorkspaceSlug = value => {
326
319
  const normalized = value.trim().toLowerCase().replace(/[^a-z0-9-]+/gu, '-').replace(/^-+|-+$/gu, '');
@@ -436,26 +429,6 @@ const formatSkillLabel = item => {
436
429
  return item.skill.name;
437
430
  };
438
431
  const toFrontmatterString = value => JSON.stringify(value);
439
- const readJsonFile = async filePath => {
440
- try {
441
- const raw = await fs.readFile(filePath, 'utf8');
442
- const parsed = JSON.parse(raw);
443
- return parsed;
444
- } catch {
445
- return null;
446
- }
447
- };
448
- const writeJsonFile = async (filePath, value) => {
449
- await fs.mkdir(path.dirname(filePath), {
450
- recursive: true
451
- });
452
- await fs.writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
453
- };
454
- const deleteFileIfExists = async filePath => {
455
- await fs.rm(filePath, {
456
- force: true
457
- });
458
- };
459
432
  const isRecord = value => {
460
433
  return typeof value === 'object' && value !== null && !Array.isArray(value);
461
434
  };
@@ -472,7 +445,7 @@ const readGlobalConfig = async configFile => {
472
445
  return {};
473
446
  };
474
447
  const writeGlobalConfig = async (configFile, config) => {
475
- await writeJsonFile(configFile, config);
448
+ await writePrivateJsonFile(configFile, config);
476
449
  };
477
450
  const withoutLegacyPublishedSkillPreferences = config => {
478
451
  const {
@@ -833,7 +806,7 @@ const buildSkillDetailSnippetLine = detail => {
833
806
  const body = detail.markdownBody || detail.renderedContent || detail.markdownDocument;
834
807
  return `- ${detail.artifactName || detail.skillName}: ${truncateText(body, 700)}`;
835
808
  };
836
- const buildSystemNote = (result, config, details) => {
809
+ export const buildSystemNote = (result, config, details) => {
837
810
  if (!result.fetchResult.ok) return null;
838
811
  const catalog = toPublishedSkillCatalog(result.fetchResult.payload);
839
812
  const skillNames = catalog.skills.map(skill => skill.artifactName || skill.skillName || skill.skillSlug);
@@ -844,7 +817,7 @@ const buildSystemNote = (result, config, details) => {
844
817
  const projectSkills = catalog.skills.filter(skill => skill.contextKind === 'project').slice(0, 5).map(buildSkillCatalogLine);
845
818
  const detailLines = details.slice(0, SYSTEM_NOTE_DETAIL_LIMIT).map(buildSkillDetailSnippetLine);
846
819
  const detailBlock = detailLines.length > 0 ? ` Loaded body snippets (capped):\n${truncateText(detailLines.join('\n'), SYSTEM_NOTE_DETAIL_CHAR_LIMIT)}` : '';
847
- return [result.fetchResult.payload.workspace ? `opencode-wizard published skills are available from backend runtime delivery for workspace ${result.fetchResult.payload.workspace.slug}.` : 'opencode-wizard published global skills are available from backend runtime delivery; workspace-specific skills are unavailable because the workspace was not found.', `Current directory: ${result.directoryPath}.`, `Published skills for this scope: ${renderedSkillNames}${renderedCountSuffix}; counts: ${catalog.assignmentCounts.global} global, ${catalog.assignmentCounts.project} project, ${catalog.assignmentCounts.other} other.`, 'Catalog lines use short whenToUse guidance when available; fetch the full skill only when that guidance matches the task.', 'GLOBAL_CONTEXT skills are active context skills and are not project-installable; PROJECT_INSTALLABLE skills can be assigned globally or to project/workspace scopes; assignment rows decide which skills are active here.', globalSkills.length > 0 ? `Global context skills:\n${globalSkills.join('\n')}` : 'Global context skills: none.', projectSkills.length > 0 ? `Project-scoped active skills:\n${projectSkills.join('\n')}` : 'Project-scoped active skills: none.', detailBlock, 'Use opencode_wizard_published_skills_fetch for one or multiple skills.', `Root source seed path remains non-runtime input only: ${config.rootSkillSeedPath}/**.`].filter(line => line.length > 0).join(' ');
820
+ return [result.fetchResult.payload.workspace ? `Prefer opencode-wizard backend-published fetched skill bodies for scoped/private wizard skills in workspace ${result.fetchResult.payload.workspace.slug}.` : 'Prefer opencode-wizard backend-published global fetched skill bodies; workspace-specific skills are unavailable because the workspace was not found.', `Current directory: ${result.directoryPath}.`, `Published skills for this scope: ${renderedSkillNames}${renderedCountSuffix}; counts: ${catalog.assignmentCounts.global} global, ${catalog.assignmentCounts.project} project, ${catalog.assignmentCounts.other} other.`, 'Use catalog whenToUse guidance to decide applicability; when it matches the task, fetch full bodies with opencode_wizard_published_skills_fetch and prefer those fetched bodies for current scoped/private wizard guidance.', 'GLOBAL_CONTEXT skills are active context skills and are not project-installable; PROJECT_INSTALLABLE skills can be assigned globally or to project/workspace scopes; assignment rows decide which skills are active here.', globalSkills.length > 0 ? `Global context skills:\n${globalSkills.join('\n')}` : 'Global context skills: none.', projectSkills.length > 0 ? `Project-scoped active skills:\n${projectSkills.join('\n')}` : 'Project-scoped active skills: none.', detailBlock, 'Local/native sources can still complement wizard skills: .opencode/skills is source seed content, skills.urls is a public/static complement, and backend-published fetched bodies are preferred for private/scoped wizard guidance.', `Root source seed path remains seed/source content: ${config.rootSkillSeedPath}/**.`].filter(line => line.length > 0).join(' ');
848
821
  };
849
822
  const toWorkspaceResolutionOutput = resolution => ({
850
823
  requestedDirectory: resolution.requestedDirectory,