reskill 1.16.0 → 1.17.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.
package/dist/cli/index.js CHANGED
@@ -3473,6 +3473,7 @@ class RegistryClient {
3473
3473
  formData.append('metadata', JSON.stringify(metadata));
3474
3474
  if (payload.skillMd?.allowedTools) formData.append('allowed_tools', payload.skillMd.allowedTools.join(' '));
3475
3475
  if (options.tag) formData.append('tag', options.tag);
3476
+ if (options.groupPath) formData.append('group_path', options.groupPath);
3476
3477
  // Append tarball as Blob
3477
3478
  const tarballBlob = new Blob([
3478
3479
  tarball
@@ -3490,6 +3491,186 @@ class RegistryClient {
3490
3491
  if (!response.ok) throw new RegistryError(data.error || `Publish failed: ${response.status}`, response.status, data);
3491
3492
  return data;
3492
3493
  }
3494
+ // ============================================================================
3495
+ // Group Methods
3496
+ // ============================================================================
3497
+ /**
3498
+ * Resolve a human-readable group path to its details.
3499
+ *
3500
+ * @param groupPath - Human-readable path (e.g., "kanyun/frontend")
3501
+ * @returns Group detail with current_user_role if authenticated
3502
+ * @throws RegistryError if not found or request failed
3503
+ */ async resolveGroup(groupPath) {
3504
+ const params = new URLSearchParams({
3505
+ path: groupPath
3506
+ });
3507
+ const url = `${this.getApiBase()}/skill-groups/resolve?${params.toString()}`;
3508
+ const response = await fetch(url, {
3509
+ method: 'GET',
3510
+ headers: this.getAuthHeaders()
3511
+ });
3512
+ if (!response.ok) {
3513
+ const data = await response.json();
3514
+ throw new RegistryError(data.error || `Group not found: ${groupPath}`, response.status, data);
3515
+ }
3516
+ const body = await response.json();
3517
+ return body.data;
3518
+ }
3519
+ /**
3520
+ * List groups visible to the current user.
3521
+ *
3522
+ * @param options - Filter options (parent_id, visibility)
3523
+ * @returns Array of groups
3524
+ */ async listGroups(options = {}) {
3525
+ const params = new URLSearchParams();
3526
+ if (options.parentId) params.set('parent_id', options.parentId);
3527
+ if (options.visibility) params.set('visibility', options.visibility);
3528
+ if (options.flat) params.set('flat', 'true');
3529
+ const qs = params.toString();
3530
+ const url = `${this.getApiBase()}/skill-groups${qs ? `?${qs}` : ''}`;
3531
+ const response = await fetch(url, {
3532
+ method: 'GET',
3533
+ headers: this.getAuthHeaders()
3534
+ });
3535
+ if (!response.ok) {
3536
+ const data = await response.json();
3537
+ throw new RegistryError(data.error || `Failed to list groups: ${response.status}`, response.status, data);
3538
+ }
3539
+ const body = await response.json();
3540
+ return body.data;
3541
+ }
3542
+ /**
3543
+ * Create a new skill group.
3544
+ *
3545
+ * @param input - Group creation parameters
3546
+ * @returns Created group
3547
+ */ async createGroup(input) {
3548
+ const url = `${this.getApiBase()}/skill-groups`;
3549
+ const response = await fetch(url, {
3550
+ method: 'POST',
3551
+ headers: {
3552
+ ...this.getAuthHeaders(),
3553
+ 'Content-Type': 'application/json'
3554
+ },
3555
+ body: JSON.stringify(input)
3556
+ });
3557
+ if (!response.ok) {
3558
+ const data = await response.json();
3559
+ throw new RegistryError(data.error || `Failed to create group: ${response.status}`, response.status, data);
3560
+ }
3561
+ const body = await response.json();
3562
+ return body.data;
3563
+ }
3564
+ /**
3565
+ * Delete a skill group.
3566
+ *
3567
+ * @param groupId - Group UUID
3568
+ * @param dryRun - If true, only preview what would be deleted
3569
+ * @returns Deletion result (affected skills count in dry-run mode)
3570
+ */ async deleteGroup(groupId, dryRun = false) {
3571
+ const params = dryRun ? '?dry_run=true' : '';
3572
+ const encodedGroupId = encodeURIComponent(groupId);
3573
+ const url = `${this.getApiBase()}/skill-groups/${encodedGroupId}${params}`;
3574
+ const response = await fetch(url, {
3575
+ method: 'DELETE',
3576
+ headers: this.getAuthHeaders()
3577
+ });
3578
+ if (!response.ok) {
3579
+ const data = await response.json();
3580
+ throw new RegistryError(data.error || `Failed to delete group: ${response.status}`, response.status, data);
3581
+ }
3582
+ const body = await response.json();
3583
+ return body.data;
3584
+ }
3585
+ /**
3586
+ * List members of a group.
3587
+ *
3588
+ * @param groupId - Group UUID
3589
+ * @returns Array of members
3590
+ */ async listGroupMembers(groupId) {
3591
+ const encodedGroupId = encodeURIComponent(groupId);
3592
+ const url = `${this.getApiBase()}/skill-groups/${encodedGroupId}/members`;
3593
+ const response = await fetch(url, {
3594
+ method: 'GET',
3595
+ headers: this.getAuthHeaders()
3596
+ });
3597
+ if (!response.ok) {
3598
+ const data = await response.json();
3599
+ throw new RegistryError(data.error || `Failed to list members: ${response.status}`, response.status, data);
3600
+ }
3601
+ const body = await response.json();
3602
+ return body.data;
3603
+ }
3604
+ /**
3605
+ * Add members to a group.
3606
+ *
3607
+ * @param groupId - Group UUID
3608
+ * @param userIds - Array of user IDs to add
3609
+ * @param role - Role to assign (defaults to 'developer')
3610
+ */ async addGroupMembers(groupId, userIds, role = 'developer') {
3611
+ const encodedGroupId = encodeURIComponent(groupId);
3612
+ const url = `${this.getApiBase()}/skill-groups/${encodedGroupId}/members`;
3613
+ const response = await fetch(url, {
3614
+ method: 'POST',
3615
+ headers: {
3616
+ ...this.getAuthHeaders(),
3617
+ 'Content-Type': 'application/json'
3618
+ },
3619
+ body: JSON.stringify({
3620
+ user_ids: userIds,
3621
+ role
3622
+ })
3623
+ });
3624
+ if (!response.ok) {
3625
+ const data = await response.json();
3626
+ throw new RegistryError(data.error || `Failed to add members: ${response.status}`, response.status, data);
3627
+ }
3628
+ }
3629
+ /**
3630
+ * Remove a member from a group.
3631
+ *
3632
+ * @param groupId - Group UUID
3633
+ * @param userId - User ID to remove
3634
+ */ async removeGroupMember(groupId, userId) {
3635
+ const params = new URLSearchParams({
3636
+ user_id: userId
3637
+ });
3638
+ const encodedGroupId = encodeURIComponent(groupId);
3639
+ const url = `${this.getApiBase()}/skill-groups/${encodedGroupId}/members?${params.toString()}`;
3640
+ const response = await fetch(url, {
3641
+ method: 'DELETE',
3642
+ headers: this.getAuthHeaders()
3643
+ });
3644
+ if (!response.ok) {
3645
+ const data = await response.json();
3646
+ throw new RegistryError(data.error || `Failed to remove member: ${response.status}`, response.status, data);
3647
+ }
3648
+ }
3649
+ /**
3650
+ * Update a member's role in a group.
3651
+ *
3652
+ * @param groupId - Group UUID
3653
+ * @param userId - User ID to update
3654
+ * @param role - New role to assign
3655
+ */ async updateGroupMemberRole(groupId, userId, role) {
3656
+ const encodedGroupId = encodeURIComponent(groupId);
3657
+ const url = `${this.getApiBase()}/skill-groups/${encodedGroupId}/members`;
3658
+ const response = await fetch(url, {
3659
+ method: 'PATCH',
3660
+ headers: {
3661
+ ...this.getAuthHeaders(),
3662
+ 'Content-Type': 'application/json'
3663
+ },
3664
+ body: JSON.stringify({
3665
+ user_id: userId,
3666
+ role
3667
+ })
3668
+ });
3669
+ if (!response.ok) {
3670
+ const data = await response.json();
3671
+ throw new RegistryError(data.error || `Failed to update member role: ${response.status}`, response.status, data);
3672
+ }
3673
+ }
3493
3674
  }
3494
3675
  /**
3495
3676
  * Tarball Extractor (Step 3.6)
@@ -4784,15 +4965,15 @@ class RegistryResolver {
4784
4965
  /**
4785
4966
  * Install a web-published skill.
4786
4967
  *
4787
- * Web-published skills do not support versioning. Branches to different
4968
+ * Web-published skills (except local) do not support versioning. Branches to different
4788
4969
  * installation logic based on source_type:
4789
4970
  * - github/gitlab: reuses installToAgentsFromGit
4790
4971
  * - oss_url/custom_url: reuses installToAgentsFromHttp
4791
4972
  * - local: downloads tarball via Registry API
4792
4973
  */ async installFromWebPublished(skillInfo, parsed, targetAgents, options = {}) {
4793
4974
  const { source_type, source_url } = skillInfo;
4794
- // Web-published skills do not support version specifiers
4795
- if (parsed.version && 'latest' !== parsed.version) throw new Error(`Version specifier not supported for web-published skills.\n'${parsed.fullName}' was published via web and does not support versioning.\nUse: reskill install ${parsed.fullName}`);
4975
+ // Web-published skills (except local) do not support version specifiers
4976
+ if ('local' !== source_type && parsed.version && 'latest' !== parsed.version) throw new Error(`Version specifier not supported for web-published skills.\n'${parsed.fullName}' was published via web and does not support versioning.\nUse: reskill install ${parsed.fullName}`);
4796
4977
  if (!source_url) throw new Error(`Missing source_url for web-published skill: ${parsed.fullName}`);
4797
4978
  logger_logger["package"](`Installing ${parsed.fullName} from ${source_type} source...`);
4798
4979
  // Build registry context so downstream methods use the registry name
@@ -4883,12 +5064,13 @@ class RegistryResolver {
4883
5064
  const { save = true, mode = 'symlink' } = options;
4884
5065
  const registryUrl = await this.resolveRegistryUrl(parsed.fullName, options.registry);
4885
5066
  const shortName = getShortName(parsed.fullName);
4886
- const version = 'latest';
4887
- // Download tarball via RegistryClient (handles auth + 302 redirect to signed URL)
5067
+ // Resolve version via dist-tags (supports @latest, @1.0.0, etc.)
4888
5068
  const client = new RegistryClient({
4889
5069
  registry: registryUrl,
4890
5070
  token: options.token
4891
5071
  });
5072
+ const version = await client.resolveVersion(parsed.fullName, parsed.version);
5073
+ // Download tarball via RegistryClient (handles auth + 302 redirect to signed URL)
4892
5074
  const { tarball } = await client.downloadSkill(parsed.fullName, version);
4893
5075
  logger_logger["package"](`Installing ${shortName} from ${registryUrl} to ${targetAgents.length} agent(s)...`);
4894
5076
  // Extract tarball to temp directory (clean stale files first)
@@ -6419,6 +6601,406 @@ class AuthManager {
6419
6601
  // Command Definition
6420
6602
  // ============================================================================
6421
6603
  const findCommand = new __WEBPACK_EXTERNAL_MODULE_commander__.Command('find').alias('search').description('Search for skills in the registry').argument('<query>', 'Search query').option('-r, --registry <url>', 'Registry URL (or set RESKILL_REGISTRY env var, or defaults.publishRegistry in skills.json)').option('-l, --limit <n>', 'Maximum number of results', '10').option('-j, --json', 'Output as JSON').action(findAction);
6604
+ /**
6605
+ * Group path utilities — normalization, slug generation, and validation.
6606
+ *
6607
+ * Shared by the `group` and `publish` CLI commands.
6608
+ */ const SLUG_REGEX = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
6609
+ const MAX_GROUP_DEPTH = 3;
6610
+ const MAX_SEGMENT_LENGTH = 64;
6611
+ /**
6612
+ * Normalize a group path for API usage.
6613
+ *
6614
+ * Rules from spec §13.2:
6615
+ * - Strip leading/trailing slashes and whitespace
6616
+ * - Collapse consecutive slashes
6617
+ * - Lowercase
6618
+ */ function normalizeGroupPath(raw) {
6619
+ return raw.trim().replace(/\/+/g, '/').replace(/^\/|\/$/g, '').toLowerCase();
6620
+ }
6621
+ /**
6622
+ * Generate a URL-safe slug from a human-readable name.
6623
+ *
6624
+ * Spec §13.4:
6625
+ * - Lowercase, trim, replace spaces/underscores with hyphens
6626
+ * - Strip non-alphanumeric characters (except hyphens)
6627
+ * - Collapse consecutive hyphens, strip leading/trailing hyphens
6628
+ * - Truncate to MAX_SEGMENT_LENGTH characters
6629
+ */ function generateSlug(name) {
6630
+ return name.toLowerCase().trim().replace(/[\s_]+/g, '-').replace(/[^a-z0-9-]/g, '').replace(/-+/g, '-').replace(/^-|-$/g, '').slice(0, MAX_SEGMENT_LENGTH).replace(/-$/g, '');
6631
+ }
6632
+ /**
6633
+ * Validate a normalized group path.
6634
+ *
6635
+ * Spec §13.2:
6636
+ * - Segment slug must match SLUG_REGEX
6637
+ * - Segment length <= 64
6638
+ * - Max depth <= 3
6639
+ */ function validateGroupPath(path) {
6640
+ if (!path) return {
6641
+ valid: false,
6642
+ error: 'Group path cannot be empty'
6643
+ };
6644
+ const segments = path.split('/');
6645
+ if (segments.length > MAX_GROUP_DEPTH) return {
6646
+ valid: false,
6647
+ error: `Group path depth cannot exceed ${MAX_GROUP_DEPTH} segments`
6648
+ };
6649
+ for (const segment of segments){
6650
+ if (segment.length > MAX_SEGMENT_LENGTH) return {
6651
+ valid: false,
6652
+ error: `Group path segment "${segment}" exceeds ${MAX_SEGMENT_LENGTH} characters`
6653
+ };
6654
+ if (!SLUG_REGEX.test(segment)) return {
6655
+ valid: false,
6656
+ error: `Invalid group path segment "${segment}". Segments must match ${SLUG_REGEX}`
6657
+ };
6658
+ }
6659
+ return {
6660
+ valid: true
6661
+ };
6662
+ }
6663
+ /**
6664
+ * group command - Manage skill groups
6665
+ *
6666
+ * Provides subcommands for listing, creating, inspecting, and deleting
6667
+ * skill groups, as well as managing group membership.
6668
+ *
6669
+ * Usage:
6670
+ * reskill group list # List visible groups
6671
+ * reskill group create <name> # Create a group
6672
+ * reskill group info <path> # Show group details
6673
+ * reskill group delete <path> # Delete a group
6674
+ * reskill group member list <path> # List members
6675
+ * reskill group member add <path> <users...> # Add members
6676
+ * reskill group member remove <path> <user> # Remove a member
6677
+ * reskill group member role <path> <user> <role> # Change member role
6678
+ */ // ============================================================================
6679
+ // Constants
6680
+ // ============================================================================
6681
+ const VALID_ROLES = [
6682
+ 'owner',
6683
+ 'maintainer',
6684
+ 'developer'
6685
+ ];
6686
+ /**
6687
+ * Validate that a role string is one of the allowed values.
6688
+ */ function validateRole(role) {
6689
+ return VALID_ROLES.includes(role);
6690
+ }
6691
+ function assertValidGroupPath(path) {
6692
+ const validation = validateGroupPath(path);
6693
+ if (!validation.valid) {
6694
+ logger_logger.error(validation.error);
6695
+ process.exit(1);
6696
+ }
6697
+ }
6698
+ // ============================================================================
6699
+ // Client Factory
6700
+ // ============================================================================
6701
+ function createClient(registry) {
6702
+ const authManager = new AuthManager();
6703
+ const token = authManager.getToken(registry);
6704
+ if (!token) {
6705
+ logger_logger.error('Authentication required');
6706
+ logger_logger.newline();
6707
+ logger_logger.log("Run 'reskill login' to authenticate.");
6708
+ process.exit(1);
6709
+ }
6710
+ return new RegistryClient({
6711
+ registry,
6712
+ token
6713
+ });
6714
+ }
6715
+ // ============================================================================
6716
+ // Display Helpers
6717
+ // ============================================================================
6718
+ function getGroupIndentLevel(group) {
6719
+ if ('number' == typeof group.level && Number.isFinite(group.level)) return Math.max(0, group.level - 1);
6720
+ return Math.max(0, group.path.split('/').length - 1);
6721
+ }
6722
+ function displayGroupList(groups, json, tree = false) {
6723
+ if (json) {
6724
+ console.log(JSON.stringify(groups, null, 2));
6725
+ return;
6726
+ }
6727
+ if (0 === groups.length) {
6728
+ logger_logger.warn('No groups found');
6729
+ return;
6730
+ }
6731
+ logger_logger.newline();
6732
+ logger_logger.log(`Found ${__WEBPACK_EXTERNAL_MODULE_chalk__["default"].bold(String(groups.length))} group${1 === groups.length ? '' : 's'}:`);
6733
+ logger_logger.newline();
6734
+ if (tree) {
6735
+ const sortedGroups = [
6736
+ ...groups
6737
+ ].sort((a, b)=>a.path.localeCompare(b.path));
6738
+ for (const group of sortedGroups){
6739
+ const vis = 'private' === group.visibility ? __WEBPACK_EXTERNAL_MODULE_chalk__["default"].yellow(' (private)') : '';
6740
+ const desc = group.description ? __WEBPACK_EXTERNAL_MODULE_chalk__["default"].gray(` - ${group.description}`) : '';
6741
+ const indent = ' '.repeat(getGroupIndentLevel(group));
6742
+ const nodeLabel = group.path.split('/').pop() || group.path;
6743
+ logger_logger.log(` ${indent}└─ ${__WEBPACK_EXTERNAL_MODULE_chalk__["default"].bold.cyan(nodeLabel)}${vis}${desc} ${__WEBPACK_EXTERNAL_MODULE_chalk__["default"].gray(`(${group.path})`)}`);
6744
+ }
6745
+ logger_logger.newline();
6746
+ return;
6747
+ }
6748
+ for (const group of groups){
6749
+ const vis = 'private' === group.visibility ? __WEBPACK_EXTERNAL_MODULE_chalk__["default"].yellow(' (private)') : '';
6750
+ const desc = group.description ? __WEBPACK_EXTERNAL_MODULE_chalk__["default"].gray(` - ${group.description}`) : '';
6751
+ logger_logger.log(` ${__WEBPACK_EXTERNAL_MODULE_chalk__["default"].bold.cyan(group.path)}${vis}${desc}`);
6752
+ const meta = [];
6753
+ if (void 0 !== group.skill_count) meta.push(`${group.skill_count} skill(s)`);
6754
+ if (void 0 !== group.member_count) meta.push(`${group.member_count} member(s)`);
6755
+ if (meta.length > 0) logger_logger.log(` ${__WEBPACK_EXTERNAL_MODULE_chalk__["default"].gray(meta.join(' · '))}`);
6756
+ }
6757
+ logger_logger.newline();
6758
+ }
6759
+ function displayGroupDetail(detail, json) {
6760
+ if (json) {
6761
+ console.log(JSON.stringify(detail, null, 2));
6762
+ return;
6763
+ }
6764
+ logger_logger.newline();
6765
+ logger_logger.log(`${__WEBPACK_EXTERNAL_MODULE_chalk__["default"].bold(detail.name)} ${__WEBPACK_EXTERNAL_MODULE_chalk__["default"].gray(`(${detail.path})`)}`);
6766
+ if (detail.description) logger_logger.log(` ${detail.description}`);
6767
+ logger_logger.newline();
6768
+ const vis = 'private' === detail.visibility ? __WEBPACK_EXTERNAL_MODULE_chalk__["default"].yellow('private') : __WEBPACK_EXTERNAL_MODULE_chalk__["default"].green('public');
6769
+ logger_logger.log(` Visibility: ${vis}`);
6770
+ logger_logger.log(` Level: ${detail.level}`);
6771
+ if (void 0 !== detail.skill_count) logger_logger.log(` Skills: ${detail.skill_count}`);
6772
+ if (void 0 !== detail.member_count) logger_logger.log(` Members: ${detail.member_count}`);
6773
+ if (detail.current_user_role) logger_logger.log(` Your role: ${__WEBPACK_EXTERNAL_MODULE_chalk__["default"].bold(detail.current_user_role)}`);
6774
+ if (detail.children && detail.children.length > 0) {
6775
+ logger_logger.newline();
6776
+ logger_logger.log(' Sub groups:');
6777
+ for (const child of detail.children)logger_logger.log(` • ${child.path}`);
6778
+ }
6779
+ logger_logger.newline();
6780
+ }
6781
+ function displayMemberList(members, groupPath, json) {
6782
+ if (json) {
6783
+ console.log(JSON.stringify(members, null, 2));
6784
+ return;
6785
+ }
6786
+ if (0 === members.length) {
6787
+ logger_logger.warn(`No members in group "${groupPath}"`);
6788
+ return;
6789
+ }
6790
+ logger_logger.newline();
6791
+ logger_logger.log(`${__WEBPACK_EXTERNAL_MODULE_chalk__["default"].bold(String(members.length))} member${1 === members.length ? '' : 's'} in ${__WEBPACK_EXTERNAL_MODULE_chalk__["default"].bold(groupPath)}:`);
6792
+ logger_logger.newline();
6793
+ for (const member of members){
6794
+ const role = __WEBPACK_EXTERNAL_MODULE_chalk__["default"].bold(member.role);
6795
+ const handle = member.handle || member.user_id;
6796
+ logger_logger.log(` ${handle} ${role}`);
6797
+ }
6798
+ logger_logger.newline();
6799
+ }
6800
+ // ============================================================================
6801
+ // Subcommand Actions
6802
+ // ============================================================================
6803
+ async function listAction(options) {
6804
+ const registry = resolveRegistry(options.registry);
6805
+ const client = createClient(registry);
6806
+ try {
6807
+ const groups = await client.listGroups({
6808
+ flat: Boolean(options.tree)
6809
+ });
6810
+ displayGroupList(groups, options.json || false, Boolean(options.tree));
6811
+ } catch (error) {
6812
+ logger_logger.error(`Failed to list groups: ${error.message}`);
6813
+ process.exit(1);
6814
+ }
6815
+ }
6816
+ async function createAction(name, options) {
6817
+ const registry = resolveRegistry(options.registry);
6818
+ const slug = generateSlug(name);
6819
+ if (!slug) {
6820
+ logger_logger.error('Name must contain at least one ASCII alphanumeric character to generate a valid slug');
6821
+ process.exit(1);
6822
+ }
6823
+ if (!SLUG_REGEX.test(slug)) {
6824
+ logger_logger.error(`Generated slug "${slug}" is invalid. Name must produce a slug matching ${SLUG_REGEX.source}`);
6825
+ process.exit(1);
6826
+ }
6827
+ let parentId;
6828
+ let client;
6829
+ if (options.parent) {
6830
+ const normalizedParent = normalizeGroupPath(options.parent);
6831
+ assertValidGroupPath(normalizedParent);
6832
+ client = createClient(registry);
6833
+ try {
6834
+ const parentGroup = await client.resolveGroup(normalizedParent);
6835
+ parentId = parentGroup.id;
6836
+ } catch (error) {
6837
+ if (error instanceof RegistryError) logger_logger.error(`Parent group "${options.parent}" not found`);
6838
+ else logger_logger.error(`Failed to resolve parent: ${error.message}`);
6839
+ process.exit(1);
6840
+ }
6841
+ }
6842
+ try {
6843
+ const ensuredClient = client ?? createClient(registry);
6844
+ const group = await ensuredClient.createGroup({
6845
+ name,
6846
+ slug,
6847
+ description: options.description,
6848
+ visibility: options.visibility,
6849
+ parent_id: parentId
6850
+ });
6851
+ if (options.json) console.log(JSON.stringify(group, null, 2));
6852
+ else {
6853
+ logger_logger.newline();
6854
+ logger_logger.success(`Group created: ${__WEBPACK_EXTERNAL_MODULE_chalk__["default"].bold(group.path)}`);
6855
+ logger_logger.log(` ID: ${group.id}`);
6856
+ logger_logger.log(` Visibility: ${group.visibility}`);
6857
+ logger_logger.newline();
6858
+ }
6859
+ } catch (error) {
6860
+ logger_logger.error(`Failed to create group: ${error.message}`);
6861
+ process.exit(1);
6862
+ }
6863
+ }
6864
+ async function infoAction(groupPath, options) {
6865
+ const normalized = normalizeGroupPath(groupPath);
6866
+ assertValidGroupPath(normalized);
6867
+ const registry = resolveRegistry(options.registry);
6868
+ const client = createClient(registry);
6869
+ try {
6870
+ const detail = await client.resolveGroup(normalized);
6871
+ displayGroupDetail(detail, options.json || false);
6872
+ } catch (error) {
6873
+ if (error instanceof RegistryError) {
6874
+ if (404 === error.statusCode) logger_logger.error(`Group "${groupPath}" not found`);
6875
+ else logger_logger.error(`Failed to get group info: ${error.message}`);
6876
+ } else logger_logger.error(`Failed to get group info: ${error.message}`);
6877
+ process.exit(1);
6878
+ }
6879
+ }
6880
+ async function deleteAction(groupPath, options) {
6881
+ const normalized = normalizeGroupPath(groupPath);
6882
+ assertValidGroupPath(normalized);
6883
+ const registry = resolveRegistry(options.registry);
6884
+ const client = createClient(registry);
6885
+ try {
6886
+ const detail = await client.resolveGroup(normalized);
6887
+ if (options.dryRun) {
6888
+ const result = await client.deleteGroup(detail.id, true);
6889
+ logger_logger.newline();
6890
+ logger_logger.log(`Dry run: deleting group "${__WEBPACK_EXTERNAL_MODULE_chalk__["default"].bold(groupPath)}"`);
6891
+ if (void 0 !== result.affected_skills) logger_logger.log(` Affected skills: ${result.affected_skills}`);
6892
+ logger_logger.log('No changes made (--dry-run)');
6893
+ logger_logger.newline();
6894
+ return;
6895
+ }
6896
+ if (!options.yes) {
6897
+ const { createInterface } = await import("node:readline");
6898
+ const rl = createInterface({
6899
+ input: process.stdin,
6900
+ output: process.stdout
6901
+ });
6902
+ const answer = await new Promise((resolve)=>{
6903
+ rl.question(`\n? Delete group "${groupPath}" and all its contents? (y/N) `, resolve);
6904
+ rl.once('close', ()=>resolve(''));
6905
+ });
6906
+ rl.close();
6907
+ if ('y' !== answer.trim().toLowerCase() && 'yes' !== answer.trim().toLowerCase()) {
6908
+ logger_logger.log('Cancelled.');
6909
+ return;
6910
+ }
6911
+ }
6912
+ await client.deleteGroup(detail.id, false);
6913
+ logger_logger.success(`Group "${groupPath}" deleted`);
6914
+ } catch (error) {
6915
+ if (error instanceof RegistryError) {
6916
+ if (404 === error.statusCode) logger_logger.error(`Group "${groupPath}" not found`);
6917
+ else logger_logger.error(`Failed to delete group: ${error.message}`);
6918
+ } else logger_logger.error(`Failed to delete group: ${error.message}`);
6919
+ process.exit(1);
6920
+ }
6921
+ }
6922
+ // ============================================================================
6923
+ // Member Subcommand Actions
6924
+ // ============================================================================
6925
+ async function memberListAction(groupPath, options) {
6926
+ const normalized = normalizeGroupPath(groupPath);
6927
+ assertValidGroupPath(normalized);
6928
+ const registry = resolveRegistry(options.registry);
6929
+ const client = createClient(registry);
6930
+ try {
6931
+ const detail = await client.resolveGroup(normalized);
6932
+ const members = await client.listGroupMembers(detail.id);
6933
+ displayMemberList(members, groupPath, options.json || false);
6934
+ } catch (error) {
6935
+ logger_logger.error(`Failed to list members: ${error.message}`);
6936
+ process.exit(1);
6937
+ }
6938
+ }
6939
+ async function memberAddAction(groupPath, userIds, options) {
6940
+ const normalized = normalizeGroupPath(groupPath);
6941
+ assertValidGroupPath(normalized);
6942
+ const registry = resolveRegistry(options.registry);
6943
+ const client = createClient(registry);
6944
+ const role = options.role || 'developer';
6945
+ if (!validateRole(role)) {
6946
+ logger_logger.error(`Invalid role "${role}". Must be one of: ${VALID_ROLES.join(', ')}`);
6947
+ process.exit(1);
6948
+ }
6949
+ try {
6950
+ const detail = await client.resolveGroup(normalized);
6951
+ await client.addGroupMembers(detail.id, userIds, role);
6952
+ logger_logger.success(`Added ${userIds.length} member(s) to "${groupPath}" as ${__WEBPACK_EXTERNAL_MODULE_chalk__["default"].bold(role)}`);
6953
+ } catch (error) {
6954
+ logger_logger.error(`Failed to add members: ${error.message}`);
6955
+ process.exit(1);
6956
+ }
6957
+ }
6958
+ async function memberRemoveAction(groupPath, userId, options) {
6959
+ const normalized = normalizeGroupPath(groupPath);
6960
+ assertValidGroupPath(normalized);
6961
+ const registry = resolveRegistry(options.registry);
6962
+ const client = createClient(registry);
6963
+ try {
6964
+ const detail = await client.resolveGroup(normalized);
6965
+ await client.removeGroupMember(detail.id, userId);
6966
+ logger_logger.success(`Removed "${userId}" from "${groupPath}"`);
6967
+ } catch (error) {
6968
+ logger_logger.error(`Failed to remove member: ${error.message}`);
6969
+ process.exit(1);
6970
+ }
6971
+ }
6972
+ async function memberRoleAction(groupPath, userId, role, options) {
6973
+ const normalized = normalizeGroupPath(groupPath);
6974
+ assertValidGroupPath(normalized);
6975
+ const registry = resolveRegistry(options.registry);
6976
+ const client = createClient(registry);
6977
+ if (!validateRole(role)) {
6978
+ logger_logger.error(`Invalid role "${role}". Must be one of: ${VALID_ROLES.join(', ')}`);
6979
+ process.exit(1);
6980
+ }
6981
+ try {
6982
+ const detail = await client.resolveGroup(normalized);
6983
+ await client.updateGroupMemberRole(detail.id, userId, role);
6984
+ logger_logger.success(`Updated role of "${userId}" in "${groupPath}" to ${__WEBPACK_EXTERNAL_MODULE_chalk__["default"].bold(role)}`);
6985
+ } catch (error) {
6986
+ logger_logger.error(`Failed to update role: ${error.message}`);
6987
+ process.exit(1);
6988
+ }
6989
+ }
6990
+ // ============================================================================
6991
+ // Command Definitions
6992
+ // ============================================================================
6993
+ const memberCommand = new __WEBPACK_EXTERNAL_MODULE_commander__.Command('member').description('Manage group members');
6994
+ memberCommand.command('list <path>').description('List members of a group').option('-r, --registry <url>', 'Registry URL').option('-j, --json', 'Output as JSON').action(memberListAction);
6995
+ memberCommand.command('add <path> <users...>').description('Add members to a group').option('-r, --registry <url>', 'Registry URL').option('--role <role>', 'Role to assign (owner|maintainer|developer)', 'developer').action(memberAddAction);
6996
+ memberCommand.command('remove <path> <user>').description('Remove a member from a group').option('-r, --registry <url>', 'Registry URL').action(memberRemoveAction);
6997
+ memberCommand.command('role <path> <user> <role>').description("Change a member's role").option('-r, --registry <url>', 'Registry URL').action(memberRoleAction);
6998
+ const groupCommand = new __WEBPACK_EXTERNAL_MODULE_commander__.Command('group').description('Manage skill groups');
6999
+ groupCommand.command('list').description('List visible groups').option('-r, --registry <url>', 'Registry URL').option('--tree', 'Render groups as a tree (requests flat group list)').option('-j, --json', 'Output as JSON').action(listAction);
7000
+ groupCommand.command('create <name>').description('Create a new group').option('-r, --registry <url>', 'Registry URL').option('-d, --description <text>', 'Group description').option('--visibility <level>', 'Visibility: public or private', 'public').option('--parent <path>', 'Parent group path (for sub groups)').option('-j, --json', 'Output as JSON').action(createAction);
7001
+ groupCommand.command('info <path>').description('Show group details').option('-r, --registry <url>', 'Registry URL').option('-j, --json', 'Output as JSON').action(infoAction);
7002
+ groupCommand.command('delete <path>').description('Delete a group').option('-r, --registry <url>', 'Registry URL').option('-n, --dry-run', 'Preview deletion without executing').option('-y, --yes', 'Skip confirmation').action(deleteAction);
7003
+ groupCommand.addCommand(memberCommand);
6422
7004
  /**
6423
7005
  * info command - Show skill details
6424
7006
  */ const infoCommand = new __WEBPACK_EXTERNAL_MODULE_commander__.Command('info').description('Show skill details').argument('<skill>', 'Skill name').option('-j, --json', 'Output as JSON').action((skillName, options)=>{
@@ -8760,6 +9342,7 @@ async function publishAction(skillPath, options) {
8760
9342
  const absolutePath = __WEBPACK_EXTERNAL_MODULE_node_path__.resolve(skillPath);
8761
9343
  // Use cwd() as project root to find skills.json, not the skill path
8762
9344
  const registry = resolveRegistry(options.registry, process.cwd());
9345
+ let normalizedGroupPath;
8763
9346
  // Validate registry is not a blocked public registry
8764
9347
  validateRegistry(registry);
8765
9348
  // Check directory exists
@@ -8767,6 +9350,14 @@ async function publishAction(skillPath, options) {
8767
9350
  logger_logger.error(`Directory not found: ${skillPath}`);
8768
9351
  process.exit(1);
8769
9352
  }
9353
+ if (options.group) {
9354
+ normalizedGroupPath = normalizeGroupPath(options.group);
9355
+ const validation = validateGroupPath(normalizedGroupPath);
9356
+ if (!validation.valid) {
9357
+ logger_logger.error(validation.error);
9358
+ process.exit(1);
9359
+ }
9360
+ }
8770
9361
  const validator = new SkillValidator();
8771
9362
  const publisher = new Publisher();
8772
9363
  try {
@@ -8852,6 +9443,11 @@ async function publishAction(skillPath, options) {
8852
9443
  displayFiles(absolutePath, skill.files, publisher);
8853
9444
  // Display metadata
8854
9445
  displayMetadata(skill);
9446
+ // Display group info
9447
+ if (normalizedGroupPath) {
9448
+ logger_logger.newline();
9449
+ logger_logger.log(`Group: ${normalizedGroupPath}`);
9450
+ }
8855
9451
  // 8. Dry run mode ends here
8856
9452
  if (options.dryRun && payload) {
8857
9453
  displayDryRunSummary(payload);
@@ -8897,7 +9493,8 @@ async function publishAction(skillPath, options) {
8897
9493
  process.exit(1);
8898
9494
  }
8899
9495
  const result = await client.publish(skillName, payload, absolutePath, {
8900
- tag: options.tag
9496
+ tag: options.tag,
9497
+ groupPath: normalizedGroupPath
8901
9498
  });
8902
9499
  if (!result.success || !result.data) {
8903
9500
  logger_logger.error(result.error || 'Publish failed');
@@ -8931,7 +9528,7 @@ async function publishAction(skillPath, options) {
8931
9528
  // ============================================================================
8932
9529
  // Command Definition
8933
9530
  // ============================================================================
8934
- const publishCommand = new __WEBPACK_EXTERNAL_MODULE_commander__.Command('publish').alias('pub').description('Publish a skill to the registry').argument('[path]', 'Path to skill directory', '.').option('-r, --registry <url>', 'Registry URL (or set RESKILL_REGISTRY env var, or defaults.publishRegistry in skills.json)').option('-t, --tag <tag>', 'Git tag to publish').option('--access <level>', 'Access level: public or restricted', 'public').option('-n, --dry-run', 'Validate without publishing').option('-y, --yes', 'Skip confirmation prompts').action(publishAction);
9531
+ const publishCommand = new __WEBPACK_EXTERNAL_MODULE_commander__.Command('publish').alias('pub').description('Publish a skill to the registry').argument('[path]', 'Path to skill directory', '.').option('-r, --registry <url>', 'Registry URL (or set RESKILL_REGISTRY env var, or defaults.publishRegistry in skills.json)').option('-t, --tag <tag>', 'Git tag to publish').option('--access <level>', 'Access level: public or restricted', 'public').option('-n, --dry-run', 'Validate without publishing').option('-y, --yes', 'Skip confirmation prompts').option('-g, --group <path>', 'Publish skill into a group (e.g., "kanyun/frontend")').action(publishAction);
8935
9532
  /**
8936
9533
  * uninstall command - Uninstall one or more skills
8937
9534
  */ const uninstallCommand = new __WEBPACK_EXTERNAL_MODULE_commander__.Command('uninstall').alias('un').alias('remove').alias('rm').description('Uninstall one or more skills').argument('<skills...>', 'Skill names to uninstall').option('-g, --global', 'Uninstall from global installation (~/.claude/skills)').option('-y, --yes', 'Skip confirmation prompts').action(async (skillNames, options)=>{
@@ -9107,6 +9704,7 @@ program.addCommand(publishCommand);
9107
9704
  program.addCommand(loginCommand);
9108
9705
  program.addCommand(logoutCommand);
9109
9706
  program.addCommand(whoamiCommand);
9707
+ program.addCommand(groupCommand);
9110
9708
  program.addCommand(completionCommand);
9111
9709
  program.addCommand(doctorCommand);
9112
9710
  // Start update check in background (non-blocking)