@aexol/opencode-wizard 0.4.0 → 0.4.2

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.
@@ -1,7 +1,7 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
- import { CREATE_OR_UPDATE_SKILL_FROM_MARKDOWN_MUTATION, IMPORT_WIZARD_ARTIFACT_SNAPSHOT_MUTATION, SET_WIZARD_ARTIFACT_PREFERENCE_MUTATION } from '../graphql-operations.js';
4
- import { createPublishedSkillToolDefinitions, resolveAvailableTools } from '../plugin-tools.js';
3
+ import { BULK_CREATE_SKILL_ASSIGNMENTS_MUTATION, CREATE_OR_UPDATE_SKILL_FROM_MARKDOWN_MUTATION, EDITOR_SKILL_WORKSPACE_ASSIGNMENT_LOOKUP_QUERY, IMPORT_WIZARD_ARTIFACT_SNAPSHOT_MUTATION, SET_WIZARD_ARTIFACT_PREFERENCE_MUTATION } from '../graphql-operations.js';
4
+ import { createPublishedSkillToolDefinitions, hasPrivilegedWizardToolRole, resolveAvailableTools } from '../plugin-tools.js';
5
5
  import { resolveStoredAuthState, toAuthState, writeAuthState } from './auth-store.js';
6
6
  import { resolveConfig } from './config.js';
7
7
  export { resolveConfig } from './config.js';
@@ -537,7 +537,9 @@ export const OpencodeWizardSkillsPlugin = async input => {
537
537
  requestedDirectoryPath: directoryPath,
538
538
  workspaceResolution: toWorkspaceResolutionOutput(workspaceResolution),
539
539
  requestedSkillVersionId: item.skillVersion.id,
540
+ requestedArtifactVersionId: item.publishedArtifact.id,
540
541
  message: detailResult.result.message,
542
+ graphQlErrors: 'graphQlErrors' in detailResult.result ? detailResult.result.graphQlErrors : undefined,
541
543
  fetchedAt: detailResult.result.fetchedAt,
542
544
  source: detailResult.result.source
543
545
  }, null, 2),
@@ -1387,6 +1389,127 @@ export const OpencodeWizardSkillsPlugin = async input => {
1387
1389
  });
1388
1390
  return withWizardArtifactEnvelope(result, artifactKind);
1389
1391
  };
1392
+ const createEditorWorkspaceSkillAssignment = async ({
1393
+ skillSlug,
1394
+ skillVersionId,
1395
+ workspaceResolution,
1396
+ signal
1397
+ }) => {
1398
+ if (!skillSlug || !skillVersionId) {
1399
+ return {
1400
+ status: 'skipped',
1401
+ directoryPath: workspaceResolution.directoryPath,
1402
+ workspaceSlug: workspaceResolution.workspaceSlug,
1403
+ message: 'Skill creation did not return a skill slug or version id, so no workspace assignment was created.'
1404
+ };
1405
+ }
1406
+ if (!workspaceResolution.workspaceSlug) {
1407
+ return {
1408
+ status: 'skipped',
1409
+ directoryPath: workspaceResolution.directoryPath,
1410
+ workspaceSlug: null,
1411
+ message: 'No resolved workspace slug was available for this directory context.'
1412
+ };
1413
+ }
1414
+ const lookupResponse = await fetchPublishedSkillsGraphQl({
1415
+ worktree: input.worktree,
1416
+ config,
1417
+ query: EDITOR_SKILL_WORKSPACE_ASSIGNMENT_LOOKUP_QUERY,
1418
+ variables: {
1419
+ skillSlug,
1420
+ workspaceSlug: workspaceResolution.workspaceSlug
1421
+ },
1422
+ signal,
1423
+ operationName: 'editorSkillWorkspaceAssignmentLookup'
1424
+ });
1425
+ if (!lookupResponse.ok) {
1426
+ return {
1427
+ status: 'failed',
1428
+ directoryPath: workspaceResolution.directoryPath,
1429
+ workspaceSlug: workspaceResolution.workspaceSlug,
1430
+ message: lookupResponse.result.message
1431
+ };
1432
+ }
1433
+ const {
1434
+ skill,
1435
+ workspace
1436
+ } = lookupResponse.data.admin;
1437
+ if (!skill) {
1438
+ return {
1439
+ status: 'failed',
1440
+ directoryPath: workspaceResolution.directoryPath,
1441
+ workspaceSlug: workspaceResolution.workspaceSlug,
1442
+ message: `Created skill ${skillSlug} could not be resolved for workspace assignment.`
1443
+ };
1444
+ }
1445
+ if (!workspace) {
1446
+ return {
1447
+ status: 'skipped',
1448
+ directoryPath: workspaceResolution.directoryPath,
1449
+ workspaceSlug: workspaceResolution.workspaceSlug,
1450
+ message: `Resolved workspace ${workspaceResolution.workspaceSlug} was not found in the backend.`
1451
+ };
1452
+ }
1453
+ const assignmentResponse = await fetchPublishedSkillsGraphQl({
1454
+ worktree: input.worktree,
1455
+ config,
1456
+ query: BULK_CREATE_SKILL_ASSIGNMENTS_MUTATION,
1457
+ variables: {
1458
+ input: {
1459
+ scopePathAssignments: [{
1460
+ workspaceId: workspace.id,
1461
+ skillId: skill.id,
1462
+ skillVersionId,
1463
+ assignmentType: 'PATH',
1464
+ scopePath: workspaceResolution.directoryPath,
1465
+ includeChildren: true,
1466
+ status: 'ACTIVE',
1467
+ notes: 'Created by opencode-wizard editor skill publishing.'
1468
+ }]
1469
+ }
1470
+ },
1471
+ signal,
1472
+ operationName: 'bulkCreateSkillAssignments'
1473
+ });
1474
+ if (!assignmentResponse.ok) {
1475
+ return {
1476
+ status: 'failed',
1477
+ directoryPath: workspaceResolution.directoryPath,
1478
+ workspaceSlug: workspace.slug,
1479
+ workspaceId: workspace.id,
1480
+ message: assignmentResponse.result.message
1481
+ };
1482
+ }
1483
+ const assignmentResult = assignmentResponse.data.admin.bulkCreateSkillAssignments;
1484
+ const [createdAssignment] = assignmentResult.scopePathAssignments;
1485
+ if (createdAssignment) {
1486
+ return {
1487
+ status: 'created',
1488
+ directoryPath: workspaceResolution.directoryPath,
1489
+ workspaceSlug: workspace.slug,
1490
+ workspaceId: workspace.id,
1491
+ assignmentId: createdAssignment.id
1492
+ };
1493
+ }
1494
+ const [skippedAssignment] = assignmentResult.skippedScopePathAssignments;
1495
+ if (skippedAssignment) {
1496
+ return {
1497
+ status: 'already_exists',
1498
+ directoryPath: workspaceResolution.directoryPath,
1499
+ workspaceSlug: workspace.slug,
1500
+ workspaceId: workspace.id,
1501
+ assignmentId: skippedAssignment.id
1502
+ };
1503
+ }
1504
+ const conflictMessage = assignmentResult.conflicts.map(conflict => conflict.message).join('; ');
1505
+ return {
1506
+ status: 'failed',
1507
+ directoryPath: workspaceResolution.directoryPath,
1508
+ workspaceSlug: workspace.slug,
1509
+ workspaceId: workspace.id,
1510
+ message: conflictMessage || 'Workspace assignment was not created and no existing matching assignment was returned.'
1511
+ };
1512
+ };
1390
1513
  const executeEditorCreateOrUpdateSkillMarkdown = async ({
1391
1514
  markdownContent,
1392
1515
  context,
@@ -1394,14 +1517,14 @@ export const OpencodeWizardSkillsPlugin = async input => {
1394
1517
  requestedSkillSlug
1395
1518
  }) => {
1396
1519
  const authState = await resolveStoredAuthState(input.worktree, config);
1397
- if (!authState || authState.role !== 'EDITOR') {
1520
+ if (!authState || !hasPrivilegedWizardToolRole(authState.role)) {
1398
1521
  return {
1399
1522
  output: JSON.stringify({
1400
1523
  pluginId: PLUGIN_ID,
1401
1524
  status: 'forbidden',
1402
1525
  skillSlug: requestedSkillSlug ?? null,
1403
1526
  source,
1404
- message: 'This tool requires EDITOR role. Your current session does not have the required editor permission.'
1527
+ message: 'This tool requires ADMIN or EDITOR role. Your current session does not have the required privileged permission.'
1405
1528
  }, null, 2),
1406
1529
  metadata: {
1407
1530
  status: 'forbidden',
@@ -1413,7 +1536,11 @@ export const OpencodeWizardSkillsPlugin = async input => {
1413
1536
  throw new Error('Editor skill create/update requires non-empty markdownContent.');
1414
1537
  }
1415
1538
  const requestedDirectory = normalizeDirectoryArg(context.directory, undefined);
1416
- const directoryPath = normalizeRepositoryPath(workspacePath, requestedDirectory);
1539
+ const workspaceResolution = await resolveWorkspace({
1540
+ config,
1541
+ directory: requestedDirectory
1542
+ });
1543
+ const directoryPath = workspaceResolution.directoryPath;
1417
1544
  lastInteractiveDirectoryPath = directoryPath;
1418
1545
  const response = await fetchPublishedSkillsGraphQl({
1419
1546
  worktree: input.worktree,
@@ -1441,6 +1568,15 @@ export const OpencodeWizardSkillsPlugin = async input => {
1441
1568
  };
1442
1569
  }
1443
1570
  const payload = response.data.admin.createOrUpdateSkillFromMarkdown;
1571
+ const workspaceAssignment = payload.success ? await createEditorWorkspaceSkillAssignment({
1572
+ skillSlug: payload.skillSlug,
1573
+ skillVersionId: payload.skillVersionId,
1574
+ workspaceResolution,
1575
+ signal: context.abort
1576
+ }) : null;
1577
+ if (payload.success) {
1578
+ clearPublishedSkillState();
1579
+ }
1444
1580
  await scheduleInteractivePresenceStart();
1445
1581
  return {
1446
1582
  output: JSON.stringify({
@@ -1451,16 +1587,20 @@ export const OpencodeWizardSkillsPlugin = async input => {
1451
1587
  skillVersionId: payload.skillVersionId,
1452
1588
  artifactSlug: payload.artifactSlug,
1453
1589
  artifactVersionId: payload.artifactVersionId,
1590
+ workspaceAssignment,
1454
1591
  requestedDirectoryPath: directoryPath,
1592
+ workspaceResolution: toWorkspaceResolutionOutput(workspaceResolution),
1455
1593
  errors: payload.errors,
1456
- message: source === 'direct_markdown' ? 'Skill markdown was sent directly to the backend; no local seed file was required.' : 'Local seed SKILL.md was published through the backend skill markdown mutation.'
1594
+ message: source === 'direct_markdown' ? 'Skill markdown was sent directly to the backend; no local seed file was required. Workspace assignments are created when a backend workspace resolves for the directory.' : 'Local seed SKILL.md was published through the backend skill markdown mutation. Workspace assignments are created when a backend workspace resolves for the directory.'
1457
1595
  }, null, 2),
1458
1596
  metadata: {
1459
1597
  status: payload.success ? 'created_or_updated' : 'create_or_update_failed',
1460
1598
  skillSlug: payload.skillSlug,
1461
1599
  skillVersionId: payload.skillVersionId ?? '',
1462
1600
  source,
1463
- directoryPath
1601
+ directoryPath,
1602
+ workspaceAssignmentStatus: workspaceAssignment?.status ?? '',
1603
+ workspaceAssignmentId: workspaceAssignment?.assignmentId ?? ''
1464
1604
  }
1465
1605
  };
1466
1606
  };
@@ -1489,14 +1629,14 @@ export const OpencodeWizardSkillsPlugin = async input => {
1489
1629
  throw new Error('Editor publish requires a non-empty skill slug.');
1490
1630
  }
1491
1631
  const authState = await resolveStoredAuthState(input.worktree, config);
1492
- if (!authState || authState.role !== 'EDITOR') {
1632
+ if (!authState || !hasPrivilegedWizardToolRole(authState.role)) {
1493
1633
  return {
1494
1634
  output: JSON.stringify({
1495
1635
  pluginId: PLUGIN_ID,
1496
1636
  status: 'forbidden',
1497
1637
  skillSlug: requestedSkillSlug,
1498
1638
  source: 'seed_file',
1499
- message: 'This tool requires EDITOR role. Your current session does not have the required editor permission.'
1639
+ message: 'This tool requires ADMIN or EDITOR role. Your current session does not have the required privileged permission.'
1500
1640
  }, null, 2),
1501
1641
  metadata: {
1502
1642
  status: 'forbidden',
@@ -1554,14 +1694,14 @@ export const OpencodeWizardSkillsPlugin = async input => {
1554
1694
  slug: args.slug,
1555
1695
  skill: args.skill
1556
1696
  });
1557
- if (!authState || authState.role !== 'EDITOR') {
1697
+ if (!authState || !hasPrivilegedWizardToolRole(authState.role)) {
1558
1698
  return {
1559
1699
  output: JSON.stringify({
1560
1700
  pluginId: PLUGIN_ID,
1561
1701
  status: 'forbidden',
1562
1702
  artifactKind: importPlan.artifactKind,
1563
1703
  source: importPlan.source,
1564
- message: 'This tool requires EDITOR role. Your current session does not have the required editor permission.'
1704
+ message: 'This tool requires ADMIN or EDITOR role. Your current session does not have the required privileged permission.'
1565
1705
  }, null, 2),
1566
1706
  metadata: {
1567
1707
  status: 'forbidden',
@@ -1632,7 +1772,7 @@ export const OpencodeWizardSkillsPlugin = async input => {
1632
1772
  }
1633
1773
  };
1634
1774
  }
1635
- const payload = response.data.importWizardArtifactSnapshot;
1775
+ const payload = response.data.admin.importWizardArtifactSnapshot;
1636
1776
  await scheduleInteractivePresenceStart();
1637
1777
  return {
1638
1778
  output: JSON.stringify({