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/README.md +56 -17
- package/README.zh-CN.md +60 -18
- package/dist/cli/commands/group.d.ts +20 -0
- package/dist/cli/commands/group.d.ts.map +1 -0
- package/dist/cli/commands/index.d.ts +1 -0
- package/dist/cli/commands/index.d.ts.map +1 -1
- package/dist/cli/commands/publish.d.ts.map +1 -1
- package/dist/cli/index.js +605 -7
- package/dist/core/registry-client.d.ts +75 -1
- package/dist/core/registry-client.d.ts.map +1 -1
- package/dist/core/skill-manager.d.ts +1 -1
- package/dist/core/skill-manager.d.ts.map +1 -1
- package/dist/index.js +187 -5
- package/dist/types/index.d.ts +27 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/group-path.d.ts +40 -0
- package/dist/utils/group-path.d.ts.map +1 -0
- package/package.json +1 -1
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
|
-
|
|
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)
|