@autonomee/gohub 0.8.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.
Files changed (179) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +57 -0
  3. package/bin/clawdhub.js +2 -0
  4. package/bin/gohub.js +2 -0
  5. package/dist/browserAuth.d.ts +20 -0
  6. package/dist/browserAuth.js +156 -0
  7. package/dist/browserAuth.js.map +1 -0
  8. package/dist/browserAuth.test.d.ts +1 -0
  9. package/dist/browserAuth.test.js +83 -0
  10. package/dist/browserAuth.test.js.map +1 -0
  11. package/dist/cli/authToken.d.ts +2 -0
  12. package/dist/cli/authToken.js +13 -0
  13. package/dist/cli/authToken.js.map +1 -0
  14. package/dist/cli/autonomeeConfig.d.ts +1 -0
  15. package/dist/cli/autonomeeConfig.js +2 -0
  16. package/dist/cli/autonomeeConfig.js.map +1 -0
  17. package/dist/cli/buildInfo.d.ts +3 -0
  18. package/dist/cli/buildInfo.js +104 -0
  19. package/dist/cli/buildInfo.js.map +1 -0
  20. package/dist/cli/clawdbotConfig.d.ts +6 -0
  21. package/dist/cli/clawdbotConfig.js +155 -0
  22. package/dist/cli/clawdbotConfig.js.map +1 -0
  23. package/dist/cli/clawdbotConfig.test.d.ts +1 -0
  24. package/dist/cli/clawdbotConfig.test.js +199 -0
  25. package/dist/cli/clawdbotConfig.test.js.map +1 -0
  26. package/dist/cli/commands/auth.d.ts +9 -0
  27. package/dist/cli/commands/auth.js +74 -0
  28. package/dist/cli/commands/auth.js.map +1 -0
  29. package/dist/cli/commands/auth.test.d.ts +1 -0
  30. package/dist/cli/commands/auth.test.js +50 -0
  31. package/dist/cli/commands/auth.test.js.map +1 -0
  32. package/dist/cli/commands/delete.d.ts +28 -0
  33. package/dist/cli/commands/delete.js +94 -0
  34. package/dist/cli/commands/delete.js.map +1 -0
  35. package/dist/cli/commands/delete.test.d.ts +1 -0
  36. package/dist/cli/commands/delete.test.js +61 -0
  37. package/dist/cli/commands/delete.test.js.map +1 -0
  38. package/dist/cli/commands/inspect.d.ts +12 -0
  39. package/dist/cli/commands/inspect.js +281 -0
  40. package/dist/cli/commands/inspect.js.map +1 -0
  41. package/dist/cli/commands/inspect.test.d.ts +1 -0
  42. package/dist/cli/commands/inspect.test.js +151 -0
  43. package/dist/cli/commands/inspect.test.js.map +1 -0
  44. package/dist/cli/commands/moderation.d.ts +19 -0
  45. package/dist/cli/commands/moderation.js +161 -0
  46. package/dist/cli/commands/moderation.js.map +1 -0
  47. package/dist/cli/commands/moderation.test.d.ts +1 -0
  48. package/dist/cli/commands/moderation.test.js +147 -0
  49. package/dist/cli/commands/moderation.test.js.map +1 -0
  50. package/dist/cli/commands/ownership.d.ts +15 -0
  51. package/dist/cli/commands/ownership.js +76 -0
  52. package/dist/cli/commands/ownership.js.map +1 -0
  53. package/dist/cli/commands/ownership.test.d.ts +1 -0
  54. package/dist/cli/commands/ownership.test.js +68 -0
  55. package/dist/cli/commands/ownership.test.js.map +1 -0
  56. package/dist/cli/commands/publish.d.ts +9 -0
  57. package/dist/cli/commands/publish.js +85 -0
  58. package/dist/cli/commands/publish.js.map +1 -0
  59. package/dist/cli/commands/publish.test.d.ts +1 -0
  60. package/dist/cli/commands/publish.test.js +105 -0
  61. package/dist/cli/commands/publish.test.js.map +1 -0
  62. package/dist/cli/commands/skills.d.ts +26 -0
  63. package/dist/cli/commands/skills.js +394 -0
  64. package/dist/cli/commands/skills.js.map +1 -0
  65. package/dist/cli/commands/skills.test.d.ts +1 -0
  66. package/dist/cli/commands/skills.test.js +402 -0
  67. package/dist/cli/commands/skills.test.js.map +1 -0
  68. package/dist/cli/commands/star.d.ts +8 -0
  69. package/dist/cli/commands/star.js +31 -0
  70. package/dist/cli/commands/star.js.map +1 -0
  71. package/dist/cli/commands/sync.d.ts +3 -0
  72. package/dist/cli/commands/sync.js +157 -0
  73. package/dist/cli/commands/sync.js.map +1 -0
  74. package/dist/cli/commands/sync.test.d.ts +1 -0
  75. package/dist/cli/commands/sync.test.js +277 -0
  76. package/dist/cli/commands/sync.test.js.map +1 -0
  77. package/dist/cli/commands/syncHelpers.d.ts +76 -0
  78. package/dist/cli/commands/syncHelpers.js +350 -0
  79. package/dist/cli/commands/syncHelpers.js.map +1 -0
  80. package/dist/cli/commands/syncHelpers.test.d.ts +1 -0
  81. package/dist/cli/commands/syncHelpers.test.js +22 -0
  82. package/dist/cli/commands/syncHelpers.test.js.map +1 -0
  83. package/dist/cli/commands/syncTypes.d.ts +24 -0
  84. package/dist/cli/commands/syncTypes.js +2 -0
  85. package/dist/cli/commands/syncTypes.js.map +1 -0
  86. package/dist/cli/commands/transfer.d.ts +50 -0
  87. package/dist/cli/commands/transfer.js +131 -0
  88. package/dist/cli/commands/transfer.js.map +1 -0
  89. package/dist/cli/commands/transfer.test.d.ts +1 -0
  90. package/dist/cli/commands/transfer.test.js +90 -0
  91. package/dist/cli/commands/transfer.test.js.map +1 -0
  92. package/dist/cli/commands/unstar.d.ts +8 -0
  93. package/dist/cli/commands/unstar.js +31 -0
  94. package/dist/cli/commands/unstar.js.map +1 -0
  95. package/dist/cli/helpStyle.d.ts +13 -0
  96. package/dist/cli/helpStyle.js +38 -0
  97. package/dist/cli/helpStyle.js.map +1 -0
  98. package/dist/cli/registry.d.ts +7 -0
  99. package/dist/cli/registry.js +42 -0
  100. package/dist/cli/registry.js.map +1 -0
  101. package/dist/cli/registry.test.d.ts +1 -0
  102. package/dist/cli/registry.test.js +48 -0
  103. package/dist/cli/registry.test.js.map +1 -0
  104. package/dist/cli/scanSkills.d.ts +7 -0
  105. package/dist/cli/scanSkills.js +91 -0
  106. package/dist/cli/scanSkills.js.map +1 -0
  107. package/dist/cli/scanSkills.test.d.ts +1 -0
  108. package/dist/cli/scanSkills.test.js +62 -0
  109. package/dist/cli/scanSkills.test.js.map +1 -0
  110. package/dist/cli/slug.d.ts +2 -0
  111. package/dist/cli/slug.js +16 -0
  112. package/dist/cli/slug.js.map +1 -0
  113. package/dist/cli/types.d.ts +15 -0
  114. package/dist/cli/types.js +2 -0
  115. package/dist/cli/types.js.map +1 -0
  116. package/dist/cli/ui.d.ts +7 -0
  117. package/dist/cli/ui.js +82 -0
  118. package/dist/cli/ui.js.map +1 -0
  119. package/dist/cli/ui.test.d.ts +1 -0
  120. package/dist/cli/ui.test.js +45 -0
  121. package/dist/cli/ui.test.js.map +1 -0
  122. package/dist/cli.d.ts +2 -0
  123. package/dist/cli.js +419 -0
  124. package/dist/cli.js.map +1 -0
  125. package/dist/config.d.ts +4 -0
  126. package/dist/config.js +77 -0
  127. package/dist/config.js.map +1 -0
  128. package/dist/config.test.d.ts +1 -0
  129. package/dist/config.test.js +56 -0
  130. package/dist/config.test.js.map +1 -0
  131. package/dist/discovery.d.ts +5 -0
  132. package/dist/discovery.js +25 -0
  133. package/dist/discovery.js.map +1 -0
  134. package/dist/discovery.test.d.ts +1 -0
  135. package/dist/discovery.test.js +46 -0
  136. package/dist/discovery.test.js.map +1 -0
  137. package/dist/homedir.d.ts +7 -0
  138. package/dist/homedir.js +29 -0
  139. package/dist/homedir.js.map +1 -0
  140. package/dist/http.bun.test.d.ts +1 -0
  141. package/dist/http.bun.test.js +231 -0
  142. package/dist/http.bun.test.js.map +1 -0
  143. package/dist/http.d.ts +43 -0
  144. package/dist/http.js +498 -0
  145. package/dist/http.js.map +1 -0
  146. package/dist/http.test.d.ts +1 -0
  147. package/dist/http.test.js +322 -0
  148. package/dist/http.test.js.map +1 -0
  149. package/dist/schema/ark.d.ts +4 -0
  150. package/dist/schema/ark.js +26 -0
  151. package/dist/schema/ark.js.map +1 -0
  152. package/dist/schema/index.d.ts +6 -0
  153. package/dist/schema/index.js +6 -0
  154. package/dist/schema/index.js.map +1 -0
  155. package/dist/schema/license.d.ts +4 -0
  156. package/dist/schema/license.js +5 -0
  157. package/dist/schema/license.js.map +1 -0
  158. package/dist/schema/routes.d.ts +22 -0
  159. package/dist/schema/routes.js +23 -0
  160. package/dist/schema/routes.js.map +1 -0
  161. package/dist/schema/schemas.d.ts +353 -0
  162. package/dist/schema/schemas.js +318 -0
  163. package/dist/schema/schemas.js.map +1 -0
  164. package/dist/schema/textFiles.d.ts +5 -0
  165. package/dist/schema/textFiles.js +66 -0
  166. package/dist/schema/textFiles.js.map +1 -0
  167. package/dist/schema/textFiles.test.d.ts +1 -0
  168. package/dist/schema/textFiles.test.js +20 -0
  169. package/dist/schema/textFiles.test.js.map +1 -0
  170. package/dist/skills.d.ts +43 -0
  171. package/dist/skills.js +178 -0
  172. package/dist/skills.js.map +1 -0
  173. package/dist/skills.test.d.ts +1 -0
  174. package/dist/skills.test.js +147 -0
  175. package/dist/skills.test.js.map +1 -0
  176. package/dist/types.d.ts +7 -0
  177. package/dist/types.js +2 -0
  178. package/dist/types.js.map +1 -0
  179. package/package.json +41 -0
@@ -0,0 +1,85 @@
1
+ import { stat } from 'node:fs/promises';
2
+ import { basename, resolve } from 'node:path';
3
+ import semver from 'semver';
4
+ import { apiRequestForm } from '../../http.js';
5
+ import { ApiRoutes, ApiV1PublishResponseSchema } from '../../schema/index.js';
6
+ import { listTextFiles } from '../../skills.js';
7
+ import { requireAuthToken } from '../authToken.js';
8
+ import { getRegistry } from '../registry.js';
9
+ import { sanitizeSlug, titleCase } from '../slug.js';
10
+ import { createSpinner, fail, formatError } from '../ui.js';
11
+ export async function cmdPublish(opts, folderArg, options) {
12
+ const folder = folderArg ? resolve(opts.workdir, folderArg) : null;
13
+ if (!folder)
14
+ fail('Path required');
15
+ const folderStat = await stat(folder).catch(() => null);
16
+ if (!folderStat || !folderStat.isDirectory())
17
+ fail('Path must be a folder');
18
+ const token = await requireAuthToken();
19
+ const registry = await getRegistry(opts, { cache: true });
20
+ const slug = options.slug ?? sanitizeSlug(basename(folder));
21
+ const displayName = options.name ?? titleCase(basename(folder));
22
+ const version = options.version;
23
+ const changelog = options.changelog ?? '';
24
+ const tagsValue = options.tags ?? 'latest';
25
+ const tags = tagsValue
26
+ .split(',')
27
+ .map((tag) => tag.trim())
28
+ .filter(Boolean);
29
+ const forkOfRaw = options.forkOf?.trim();
30
+ const forkOf = forkOfRaw ? parseForkOf(forkOfRaw) : undefined;
31
+ if (!slug)
32
+ fail('--slug required');
33
+ if (!displayName)
34
+ fail('--name required');
35
+ if (!version || !semver.valid(version))
36
+ fail('--version must be valid semver');
37
+ const spinner = createSpinner(`Preparing ${slug}@${version}`);
38
+ try {
39
+ const filesOnDisk = await listTextFiles(folder);
40
+ if (filesOnDisk.length === 0)
41
+ fail('No files found');
42
+ if (!filesOnDisk.some((file) => {
43
+ const lower = file.relPath.toLowerCase();
44
+ return lower === 'skill.md' || lower === 'skills.md';
45
+ })) {
46
+ fail('SKILL.md required');
47
+ }
48
+ const form = new FormData();
49
+ form.set('payload', JSON.stringify({
50
+ slug,
51
+ displayName,
52
+ version,
53
+ changelog,
54
+ acceptLicenseTerms: true,
55
+ tags,
56
+ ...(forkOf ? { forkOf } : {}),
57
+ }));
58
+ let index = 0;
59
+ for (const file of filesOnDisk) {
60
+ index += 1;
61
+ spinner.text = `Uploading ${file.relPath} (${index}/${filesOnDisk.length})`;
62
+ const blob = new Blob([Buffer.from(file.bytes)], { type: file.contentType ?? 'text/plain' });
63
+ form.append('files', blob, file.relPath);
64
+ }
65
+ spinner.text = `Publishing ${slug}@${version}`;
66
+ const result = await apiRequestForm(registry, { method: 'POST', path: ApiRoutes.skills, token, form }, ApiV1PublishResponseSchema);
67
+ spinner.succeed(`OK. Published ${slug}@${version} (${result.versionId})`);
68
+ }
69
+ catch (error) {
70
+ spinner.fail(formatError(error));
71
+ throw error;
72
+ }
73
+ }
74
+ function parseForkOf(value) {
75
+ const trimmed = value.trim();
76
+ const [slugRaw, versionRaw] = trimmed.split('@');
77
+ const slug = (slugRaw ?? '').trim().toLowerCase();
78
+ if (!slug)
79
+ fail('--fork-of must be <slug> or <slug@version>');
80
+ const version = (versionRaw ?? '').trim();
81
+ if (version && !semver.valid(version))
82
+ fail('--fork-of version must be valid semver');
83
+ return { slug, version: version || undefined };
84
+ }
85
+ //# sourceMappingURL=publish.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"publish.js","sourceRoot":"","sources":["../../../src/cli/commands/publish.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,105 @@
1
+ /* @vitest-environment node */
2
+ import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { afterEach, describe, expect, it, vi } from 'vitest';
6
+ vi.mock('../authToken.js', () => ({
7
+ requireAuthToken: vi.fn(async () => 'tkn'),
8
+ }));
9
+ const mockGetRegistry = vi.fn(async (_opts, _params) => 'https://gohub.ai');
10
+ vi.mock('../registry.js', () => ({
11
+ getRegistry: (opts, params) => mockGetRegistry(opts, params),
12
+ }));
13
+ const mockApiRequestForm = vi.fn();
14
+ vi.mock('../../http.js', () => ({
15
+ apiRequestForm: (registry, args, schema) => mockApiRequestForm(registry, args, schema),
16
+ }));
17
+ const mockFail = vi.fn((message) => {
18
+ throw new Error(message);
19
+ });
20
+ const mockSpinner = { text: '', succeed: vi.fn(), fail: vi.fn() };
21
+ vi.mock('../ui.js', () => ({
22
+ createSpinner: vi.fn(() => mockSpinner),
23
+ fail: (message) => mockFail(message),
24
+ formatError: (error) => (error instanceof Error ? error.message : String(error)),
25
+ }));
26
+ const { cmdPublish } = await import('./publish');
27
+ async function makeTmpWorkdir() {
28
+ const root = await mkdtemp(join(tmpdir(), 'gohub-publish-'));
29
+ return root;
30
+ }
31
+ function makeOpts(workdir) {
32
+ return {
33
+ workdir,
34
+ dir: join(workdir, 'skills'),
35
+ site: 'https://gohub.ai',
36
+ registry: 'https://gohub.ai',
37
+ registrySource: 'default',
38
+ };
39
+ }
40
+ afterEach(() => {
41
+ vi.unstubAllGlobals();
42
+ vi.clearAllMocks();
43
+ });
44
+ describe('cmdPublish', () => {
45
+ it('publishes SKILL.md from disk (mocked HTTP)', async () => {
46
+ const workdir = await makeTmpWorkdir();
47
+ try {
48
+ const folder = join(workdir, 'my-skill');
49
+ await mkdir(folder, { recursive: true });
50
+ const skillContent = '# Skill\n\nHello\n';
51
+ const notesContent = 'notes\n';
52
+ await writeFile(join(folder, 'SKILL.md'), skillContent, 'utf8');
53
+ await writeFile(join(folder, 'notes.md'), notesContent, 'utf8');
54
+ mockApiRequestForm.mockResolvedValueOnce({ ok: true, skillId: 'skill_1', versionId: 'ver_1' });
55
+ await cmdPublish(makeOpts(workdir), 'my-skill', {
56
+ slug: 'my-skill',
57
+ name: 'My Skill',
58
+ version: '1.0.0',
59
+ changelog: '',
60
+ tags: 'latest',
61
+ });
62
+ const publishCall = mockApiRequestForm.mock.calls.find((call) => {
63
+ const req = call[1];
64
+ return req?.path === '/api/v1/skills';
65
+ });
66
+ if (!publishCall)
67
+ throw new Error('Missing publish call');
68
+ const publishForm = publishCall[1].form;
69
+ const payloadEntry = publishForm.get('payload');
70
+ if (typeof payloadEntry !== 'string')
71
+ throw new Error('Missing publish payload');
72
+ const payload = JSON.parse(payloadEntry);
73
+ expect(payload.slug).toBe('my-skill');
74
+ expect(payload.displayName).toBe('My Skill');
75
+ expect(payload.version).toBe('1.0.0');
76
+ expect(payload.changelog).toBe('');
77
+ expect(payload.acceptLicenseTerms).toBe(true);
78
+ expect(payload.tags).toEqual(['latest']);
79
+ const files = publishForm.getAll('files');
80
+ expect(files.map((file) => String(file.name ?? '')).sort()).toEqual(['SKILL.md', 'notes.md']);
81
+ }
82
+ finally {
83
+ await rm(workdir, { recursive: true, force: true });
84
+ }
85
+ });
86
+ it('allows empty changelog when updating an existing skill', async () => {
87
+ const workdir = await makeTmpWorkdir();
88
+ try {
89
+ const folder = join(workdir, 'existing-skill');
90
+ await mkdir(folder, { recursive: true });
91
+ await writeFile(join(folder, 'SKILL.md'), '# Skill\n', 'utf8');
92
+ mockApiRequestForm.mockResolvedValueOnce({ ok: true, skillId: 'skill_1', versionId: 'ver_2' });
93
+ await cmdPublish(makeOpts(workdir), 'existing-skill', {
94
+ version: '1.0.1',
95
+ changelog: '',
96
+ tags: 'latest',
97
+ });
98
+ expect(mockApiRequestForm).toHaveBeenCalledWith(expect.anything(), expect.objectContaining({ path: '/api/v1/skills', method: 'POST' }), expect.anything());
99
+ }
100
+ finally {
101
+ await rm(workdir, { recursive: true, force: true });
102
+ }
103
+ });
104
+ });
105
+ //# sourceMappingURL=publish.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"publish.test.js","sourceRoot":"","sources":["../../../src/cli/commands/publish.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ import type { GlobalOpts } from '../types.js';
2
+ export declare function cmdSearch(opts: GlobalOpts, query: string, limit?: number): Promise<void>;
3
+ export declare function cmdInstall(opts: GlobalOpts, slug: string, versionFlag?: string, force?: boolean): Promise<void>;
4
+ export declare function cmdUpdate(opts: GlobalOpts, slugArg: string | undefined, options: {
5
+ all?: boolean;
6
+ version?: string;
7
+ force?: boolean;
8
+ }, inputAllowed: boolean): Promise<void>;
9
+ export declare function cmdList(opts: GlobalOpts): Promise<void>;
10
+ export declare function cmdUninstall(opts: GlobalOpts, slug: string, options: {
11
+ yes?: boolean;
12
+ } | undefined, inputAllowed: boolean): Promise<void>;
13
+ export declare function cmdExplore(opts: GlobalOpts, options?: {
14
+ limit?: number;
15
+ sort?: string;
16
+ json?: boolean;
17
+ }): Promise<void>;
18
+ export declare function formatExploreLine(item: {
19
+ slug: string;
20
+ summary?: string | null;
21
+ updatedAt: number;
22
+ latestVersion?: {
23
+ version: string;
24
+ } | null;
25
+ }): string;
26
+ export declare function clampLimit(limit: number, fallback?: number): number;
@@ -0,0 +1,394 @@
1
+ import { mkdir, rm, stat } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import semver from 'semver';
4
+ import { apiRequest, downloadZip, registryUrl } from '../../http.js';
5
+ import { ApiRoutes, ApiV1SearchResponseSchema, ApiV1SkillListResponseSchema, ApiV1SkillResolveResponseSchema, ApiV1SkillResponseSchema, ApiV1SkillVersionResponseSchema, } from '../../schema/index.js';
6
+ import { extractZipToDir, hashSkillFiles, listTextFiles, readLockfile, readSkillOrigin, writeLockfile, writeSkillOrigin, } from '../../skills.js';
7
+ import { getRegistry } from '../registry.js';
8
+ import { createSpinner, fail, formatError, isInteractive, promptConfirm } from '../ui.js';
9
+ import { getOptionalAuthToken } from '../authToken.js';
10
+ function normalizeSkillSlugOrFail(raw) {
11
+ const slug = raw.trim();
12
+ if (!slug)
13
+ fail('Slug required');
14
+ // Safety: never allow path traversal or nested paths to become filesystem operations.
15
+ if (slug.includes('/') || slug.includes('\\') || slug.includes('..')) {
16
+ fail(`Invalid slug: ${slug}`);
17
+ }
18
+ return slug;
19
+ }
20
+ function isSafeSkillSlug(slug) {
21
+ return Boolean(slug) && !slug.includes('/') && !slug.includes('\\') && !slug.includes('..');
22
+ }
23
+ export async function cmdSearch(opts, query, limit) {
24
+ if (!query)
25
+ fail('Query required');
26
+ const token = await getOptionalAuthToken();
27
+ const registry = await getRegistry(opts, { cache: true });
28
+ const spinner = createSpinner('Searching');
29
+ try {
30
+ const url = registryUrl(ApiRoutes.search, registry);
31
+ url.searchParams.set('q', query);
32
+ if (typeof limit === 'number' && Number.isFinite(limit)) {
33
+ url.searchParams.set('limit', String(limit));
34
+ }
35
+ const result = await apiRequest(registry, { method: 'GET', url: url.toString(), token }, ApiV1SearchResponseSchema);
36
+ spinner.stop();
37
+ for (const entry of result.results) {
38
+ const slug = entry.slug ?? 'unknown';
39
+ const name = entry.displayName ?? slug;
40
+ const version = entry.version ? ` v${entry.version}` : '';
41
+ console.log(`${slug}${version} ${name} (${entry.score.toFixed(3)})`);
42
+ }
43
+ }
44
+ catch (error) {
45
+ spinner.fail(formatError(error));
46
+ throw error;
47
+ }
48
+ }
49
+ export async function cmdInstall(opts, slug, versionFlag, force = false) {
50
+ const trimmed = normalizeSkillSlugOrFail(slug);
51
+ const token = await getOptionalAuthToken();
52
+ const registry = await getRegistry(opts, { cache: true });
53
+ await mkdir(opts.dir, { recursive: true });
54
+ const target = join(opts.dir, trimmed);
55
+ if (!force) {
56
+ const exists = await fileExists(target);
57
+ if (exists)
58
+ fail(`Already installed: ${target} (use --force)`);
59
+ }
60
+ const spinner = createSpinner(`Resolving ${trimmed}`);
61
+ try {
62
+ // Fetch skill metadata including moderation status
63
+ const skillMeta = await apiRequest(registry, { method: 'GET', path: `${ApiRoutes.skills}/${encodeURIComponent(trimmed)}`, token }, ApiV1SkillResponseSchema);
64
+ // Check moderation status before proceeding
65
+ if (skillMeta.moderation?.isMalwareBlocked) {
66
+ spinner.fail(`Blocked: ${trimmed} is flagged as malicious`);
67
+ fail('This skill has been flagged as malware and cannot be installed.');
68
+ }
69
+ if (skillMeta.moderation?.isSuspicious && !force) {
70
+ spinner.stop();
71
+ console.log(`\n⚠️ Warning: "${trimmed}" is flagged as suspicious by VirusTotal Code Insight.\n` +
72
+ ' This skill may contain risky patterns (crypto keys, external APIs, eval, etc.)\n' +
73
+ ' Review the skill code before use.\n');
74
+ if (isInteractive()) {
75
+ const confirm = await promptConfirm('Install anyway?');
76
+ if (!confirm)
77
+ fail('Installation cancelled');
78
+ spinner.start(`Resolving ${trimmed}`);
79
+ }
80
+ else {
81
+ fail('Use --force to install suspicious skills in non-interactive mode');
82
+ }
83
+ }
84
+ const resolvedVersion = versionFlag ?? skillMeta.latestVersion?.version ?? null;
85
+ if (!resolvedVersion)
86
+ fail('Could not resolve latest version');
87
+ if (versionFlag) {
88
+ await apiRequest(registry, {
89
+ method: 'GET',
90
+ path: `${ApiRoutes.skills}/${encodeURIComponent(trimmed)}/versions/${encodeURIComponent(resolvedVersion)}`,
91
+ token,
92
+ }, ApiV1SkillVersionResponseSchema);
93
+ }
94
+ if (force) {
95
+ await rm(target, { recursive: true, force: true });
96
+ }
97
+ spinner.text = `Downloading ${trimmed}@${resolvedVersion}`;
98
+ const zip = await downloadZip(registry, { slug: trimmed, version: resolvedVersion, token });
99
+ await extractZipToDir(zip, target);
100
+ await writeSkillOrigin(target, {
101
+ version: 1,
102
+ registry,
103
+ slug: trimmed,
104
+ installedVersion: resolvedVersion,
105
+ installedAt: Date.now(),
106
+ });
107
+ const lock = await readLockfile(opts.workdir);
108
+ lock.skills[trimmed] = {
109
+ version: resolvedVersion,
110
+ installedAt: Date.now(),
111
+ };
112
+ await writeLockfile(opts.workdir, lock);
113
+ spinner.succeed(`OK. Installed ${trimmed} -> ${target}`);
114
+ }
115
+ catch (error) {
116
+ spinner.fail(formatError(error));
117
+ throw error;
118
+ }
119
+ }
120
+ export async function cmdUpdate(opts, slugArg, options, inputAllowed) {
121
+ const slug = slugArg ? normalizeSkillSlugOrFail(slugArg) : undefined;
122
+ const all = Boolean(options.all);
123
+ if (!slug && !all)
124
+ fail('Provide <slug> or --all');
125
+ if (slug && all)
126
+ fail('Use either <slug> or --all');
127
+ if (options.version && !slug)
128
+ fail('--version requires a single <slug>');
129
+ if (options.version && !semver.valid(options.version))
130
+ fail('--version must be valid semver');
131
+ const allowPrompt = isInteractive() && inputAllowed;
132
+ const token = await getOptionalAuthToken();
133
+ const registry = await getRegistry(opts, { cache: true });
134
+ const lock = await readLockfile(opts.workdir);
135
+ const slugs = slug ? [slug] : Object.keys(lock.skills).filter(isSafeSkillSlug);
136
+ if (slugs.length === 0) {
137
+ console.log('No installed skills.');
138
+ return;
139
+ }
140
+ for (const entry of slugs) {
141
+ const spinner = createSpinner(`Checking ${entry}`);
142
+ try {
143
+ const target = join(opts.dir, entry);
144
+ const exists = await fileExists(target);
145
+ // Always fetch skill metadata to check moderation status
146
+ const skillMeta = await apiRequest(registry, { method: 'GET', path: `${ApiRoutes.skills}/${encodeURIComponent(entry)}`, token }, ApiV1SkillResponseSchema);
147
+ // Check moderation status before proceeding
148
+ if (skillMeta.moderation?.isMalwareBlocked) {
149
+ spinner.fail(`${entry}: blocked as malicious`);
150
+ console.log(' This skill has been flagged as malware and cannot be updated.');
151
+ continue;
152
+ }
153
+ if (skillMeta.moderation?.isSuspicious && !options.force) {
154
+ spinner.stop();
155
+ console.log(`\n⚠️ Warning: "${entry}" is flagged as suspicious by VirusTotal Code Insight.\n` +
156
+ ' This skill may contain risky patterns (crypto keys, external APIs, eval, etc.)\n');
157
+ if (allowPrompt) {
158
+ const confirm = await promptConfirm('Update anyway?');
159
+ if (!confirm) {
160
+ console.log(`${entry}: skipped`);
161
+ continue;
162
+ }
163
+ spinner.start(`Checking ${entry}`);
164
+ }
165
+ else {
166
+ console.log(`${entry}: skipped (use --force to update suspicious skills)`);
167
+ continue;
168
+ }
169
+ }
170
+ let localFingerprint = null;
171
+ if (exists) {
172
+ const filesOnDisk = await listTextFiles(target);
173
+ if (filesOnDisk.length > 0) {
174
+ const hashed = hashSkillFiles(filesOnDisk);
175
+ localFingerprint = hashed.fingerprint;
176
+ }
177
+ }
178
+ let resolveResult;
179
+ if (localFingerprint) {
180
+ resolveResult = await resolveSkillVersion(registry, entry, localFingerprint, token);
181
+ }
182
+ else {
183
+ resolveResult = { match: null, latestVersion: skillMeta.latestVersion ?? null };
184
+ }
185
+ const latest = resolveResult.latestVersion?.version ?? null;
186
+ const matched = resolveResult.match?.version ?? null;
187
+ if (matched && lock.skills[entry]?.version !== matched) {
188
+ lock.skills[entry] = {
189
+ version: matched,
190
+ installedAt: lock.skills[entry]?.installedAt ?? Date.now(),
191
+ };
192
+ }
193
+ if (!latest) {
194
+ spinner.fail(`${entry}: not found`);
195
+ continue;
196
+ }
197
+ if (!matched && localFingerprint && !options.force) {
198
+ spinner.stop();
199
+ if (!allowPrompt) {
200
+ console.log(`${entry}: local changes (no match). Use --force to overwrite.`);
201
+ continue;
202
+ }
203
+ const confirm = await promptConfirm(`${entry}: local changes (no match). Overwrite with ${options.version ?? latest}?`);
204
+ if (!confirm) {
205
+ console.log(`${entry}: skipped`);
206
+ continue;
207
+ }
208
+ spinner.start(`Updating ${entry} -> ${options.version ?? latest}`);
209
+ }
210
+ const targetVersion = options.version ?? latest;
211
+ if (options.version) {
212
+ if (matched && matched === targetVersion) {
213
+ spinner.succeed(`${entry}: already at ${matched}`);
214
+ continue;
215
+ }
216
+ }
217
+ else if (matched && semver.valid(matched) && semver.gte(matched, targetVersion)) {
218
+ spinner.succeed(`${entry}: up to date (${matched})`);
219
+ continue;
220
+ }
221
+ if (spinner.isSpinning) {
222
+ spinner.text = `Updating ${entry} -> ${targetVersion}`;
223
+ }
224
+ else {
225
+ spinner.start(`Updating ${entry} -> ${targetVersion}`);
226
+ }
227
+ await rm(target, { recursive: true, force: true });
228
+ const zip = await downloadZip(registry, { slug: entry, version: targetVersion, token });
229
+ await extractZipToDir(zip, target);
230
+ const existingOrigin = await readSkillOrigin(target);
231
+ await writeSkillOrigin(target, {
232
+ version: 1,
233
+ registry: existingOrigin?.registry ?? registry,
234
+ slug: existingOrigin?.slug ?? entry,
235
+ installedVersion: targetVersion,
236
+ installedAt: existingOrigin?.installedAt ?? Date.now(),
237
+ });
238
+ lock.skills[entry] = { version: targetVersion, installedAt: Date.now() };
239
+ spinner.succeed(`${entry}: updated -> ${targetVersion}`);
240
+ }
241
+ catch (error) {
242
+ spinner.fail(formatError(error));
243
+ throw error;
244
+ }
245
+ }
246
+ await writeLockfile(opts.workdir, lock);
247
+ }
248
+ export async function cmdList(opts) {
249
+ const lock = await readLockfile(opts.workdir);
250
+ const entries = Object.entries(lock.skills);
251
+ if (entries.length === 0) {
252
+ console.log('No installed skills.');
253
+ return;
254
+ }
255
+ for (const [slug, entry] of entries) {
256
+ console.log(`${slug} ${entry.version ?? 'latest'}`);
257
+ }
258
+ }
259
+ export async function cmdUninstall(opts, slug, options = {}, inputAllowed) {
260
+ const trimmed = normalizeSkillSlugOrFail(slug);
261
+ const lock = await readLockfile(opts.workdir);
262
+ if (!lock.skills[trimmed]) {
263
+ fail(`Not installed: ${trimmed}`);
264
+ }
265
+ const allowPrompt = isInteractive() && inputAllowed;
266
+ if (!options.yes) {
267
+ if (!allowPrompt)
268
+ fail('Pass --yes (no input)');
269
+ const confirm = await promptConfirm(`Uninstall ${trimmed}?`);
270
+ if (!confirm) {
271
+ console.log('Cancelled.');
272
+ return;
273
+ }
274
+ }
275
+ const spinner = createSpinner(`Uninstalling ${trimmed}`);
276
+ try {
277
+ const target = join(opts.dir, trimmed);
278
+ await rm(target, { recursive: true, force: true });
279
+ delete lock.skills[trimmed];
280
+ await writeLockfile(opts.workdir, lock);
281
+ spinner.succeed(`Uninstalled ${trimmed}`);
282
+ }
283
+ catch (error) {
284
+ spinner.fail(formatError(error));
285
+ throw error;
286
+ }
287
+ }
288
+ export async function cmdExplore(opts, options = {}) {
289
+ const token = await getOptionalAuthToken();
290
+ const registry = await getRegistry(opts, { cache: true });
291
+ const spinner = createSpinner('Fetching latest skills');
292
+ try {
293
+ const url = registryUrl(ApiRoutes.skills, registry);
294
+ const boundedLimit = clampLimit(options.limit ?? 25);
295
+ const { apiSort } = resolveExploreSort(options.sort);
296
+ url.searchParams.set('limit', String(boundedLimit));
297
+ if (apiSort !== 'updated')
298
+ url.searchParams.set('sort', apiSort);
299
+ const result = await apiRequest(registry, { method: 'GET', url: url.toString(), token }, ApiV1SkillListResponseSchema);
300
+ spinner.stop();
301
+ if (options.json) {
302
+ console.log(JSON.stringify(result, null, 2));
303
+ return;
304
+ }
305
+ if (result.items.length === 0) {
306
+ console.log('No skills found.');
307
+ return;
308
+ }
309
+ for (const item of result.items) {
310
+ console.log(formatExploreLine(item));
311
+ }
312
+ }
313
+ catch (error) {
314
+ spinner.fail(formatError(error));
315
+ throw error;
316
+ }
317
+ }
318
+ export function formatExploreLine(item) {
319
+ const version = item.latestVersion?.version ?? '?';
320
+ const age = formatRelativeTime(item.updatedAt);
321
+ const summary = item.summary ? ` ${truncate(item.summary, 50)}` : '';
322
+ return `${item.slug} v${version} ${age}${summary}`;
323
+ }
324
+ export function clampLimit(limit, fallback = 25) {
325
+ if (!Number.isFinite(limit))
326
+ return fallback;
327
+ return Math.min(Math.max(1, limit), 200);
328
+ }
329
+ function formatRelativeTime(timestamp) {
330
+ const now = Date.now();
331
+ const diff = now - timestamp;
332
+ const seconds = Math.floor(diff / 1000);
333
+ const minutes = Math.floor(seconds / 60);
334
+ const hours = Math.floor(minutes / 60);
335
+ const days = Math.floor(hours / 24);
336
+ if (days > 30) {
337
+ const months = Math.floor(days / 30);
338
+ return `${months}mo ago`;
339
+ }
340
+ if (days > 0)
341
+ return `${days}d ago`;
342
+ if (hours > 0)
343
+ return `${hours}h ago`;
344
+ if (minutes > 0)
345
+ return `${minutes}m ago`;
346
+ return 'just now';
347
+ }
348
+ function truncate(str, maxLen) {
349
+ if (str.length <= maxLen)
350
+ return str;
351
+ return `${str.slice(0, maxLen - 1)}…`;
352
+ }
353
+ function resolveExploreSort(raw) {
354
+ const normalized = raw?.trim().toLowerCase();
355
+ if (!normalized || normalized === 'newest' || normalized === 'updated') {
356
+ return { sort: 'newest', apiSort: 'updated' };
357
+ }
358
+ if (normalized === 'downloads' || normalized === 'download') {
359
+ return { sort: 'downloads', apiSort: 'downloads' };
360
+ }
361
+ if (normalized === 'rating' || normalized === 'stars' || normalized === 'star') {
362
+ return { sort: 'rating', apiSort: 'stars' };
363
+ }
364
+ if (normalized === 'installs' ||
365
+ normalized === 'install' ||
366
+ normalized === 'installscurrent' ||
367
+ normalized === 'installs-current' ||
368
+ normalized === 'current') {
369
+ return { sort: 'installs', apiSort: 'installsCurrent' };
370
+ }
371
+ if (normalized === 'installsalltime' || normalized === 'installs-all-time') {
372
+ return { sort: 'installsAllTime', apiSort: 'installsAllTime' };
373
+ }
374
+ if (normalized === 'trending') {
375
+ return { sort: 'trending', apiSort: 'trending' };
376
+ }
377
+ fail(`Invalid sort "${raw}". Use newest, downloads, rating, installs, installsAllTime, or trending.`);
378
+ }
379
+ async function resolveSkillVersion(registry, slug, hash, token) {
380
+ const url = registryUrl(ApiRoutes.resolve, registry);
381
+ url.searchParams.set('slug', slug);
382
+ url.searchParams.set('hash', hash);
383
+ return apiRequest(registry, { method: 'GET', url: url.toString(), token }, ApiV1SkillResolveResponseSchema);
384
+ }
385
+ async function fileExists(path) {
386
+ try {
387
+ await stat(path);
388
+ return true;
389
+ }
390
+ catch {
391
+ return false;
392
+ }
393
+ }
394
+ //# sourceMappingURL=skills.js.map