@skillshub-labs/cli 0.1.17

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.
@@ -0,0 +1,66 @@
1
+ import type {
2
+ KitApplyResult,
3
+ KitLoadoutRecord,
4
+ KitPolicyRecord,
5
+ KitRecord,
6
+ KitSyncMode,
7
+ } from '../core/kit-types'
8
+
9
+ export function normalizeKitMode(value: unknown): KitSyncMode
10
+ export function normalizeLoadoutItems(items: unknown): Array<{
11
+ skillPath: string
12
+ mode: KitSyncMode
13
+ sortOrder: number
14
+ }>
15
+
16
+ export function listKitPolicies(): KitPolicyRecord[]
17
+ export function addKitPolicy(values: {
18
+ name: string
19
+ description?: string
20
+ content: string
21
+ }): KitPolicyRecord
22
+ export function updateKitPolicy(values: {
23
+ id: string
24
+ name?: string
25
+ description?: string
26
+ content?: string
27
+ }): KitPolicyRecord
28
+ export function deleteKitPolicy(id: string): boolean
29
+
30
+ export function listKitLoadouts(): KitLoadoutRecord[]
31
+ export function addKitLoadout(values: {
32
+ name: string
33
+ description?: string
34
+ items: Array<{ skillPath: string; mode?: KitSyncMode; sortOrder?: number }>
35
+ }): KitLoadoutRecord
36
+ export function updateKitLoadout(values: {
37
+ id: string
38
+ name?: string
39
+ description?: string
40
+ items?: Array<{ skillPath: string; mode?: KitSyncMode; sortOrder?: number }>
41
+ }): KitLoadoutRecord
42
+ export function deleteKitLoadout(id: string): boolean
43
+
44
+ export function listKits(): KitRecord[]
45
+ export function addKit(values: {
46
+ name: string
47
+ description?: string
48
+ policyId: string
49
+ loadoutId: string
50
+ }): KitRecord
51
+ export function updateKit(values: {
52
+ id: string
53
+ name?: string
54
+ description?: string
55
+ policyId?: string
56
+ loadoutId?: string
57
+ }): KitRecord
58
+ export function deleteKit(id: string): boolean
59
+
60
+ export function applyKit(values: {
61
+ kitId: string
62
+ projectPath: string
63
+ agentName: string
64
+ mode?: KitSyncMode
65
+ overwriteAgentsMd?: boolean
66
+ }): Promise<KitApplyResult>
@@ -0,0 +1,266 @@
1
+ import fs from 'fs-extra'
2
+ import os from 'os'
3
+ import path from 'path'
4
+ import {
5
+ addKit as addKitCore,
6
+ addKitLoadout as addKitLoadoutCore,
7
+ addKitPolicy as addKitPolicyCore,
8
+ deleteKit as deleteKitCore,
9
+ deleteKitLoadout as deleteKitLoadoutCore,
10
+ deleteKitPolicy as deleteKitPolicyCore,
11
+ getKitById,
12
+ getKitLoadoutById,
13
+ getKitPolicyById,
14
+ listKitLoadouts as listKitLoadoutsCore,
15
+ listKitPolicies as listKitPoliciesCore,
16
+ listKits as listKitsCore,
17
+ markKitApplied,
18
+ updateKit as updateKitCore,
19
+ updateKitLoadout as updateKitLoadoutCore,
20
+ updateKitPolicy as updateKitPolicyCore,
21
+ } from '../core/kit-core.mjs'
22
+
23
+ const CONFIG_PATH = path.join(os.homedir(), '.skills-hub', 'config.json')
24
+
25
+ function normalizeKitMode(value) {
26
+ return value === 'link' ? 'link' : 'copy'
27
+ }
28
+
29
+ function normalizeLoadoutItems(items) {
30
+ if (!Array.isArray(items)) {
31
+ throw new Error('Skills package items must be an array.')
32
+ }
33
+
34
+ const seen = new Set()
35
+ const normalized = []
36
+
37
+ for (const [index, item] of items.entries()) {
38
+ if (!item || typeof item !== 'object' || Array.isArray(item)) continue
39
+ const raw = item
40
+ const skillPath = String(raw.skillPath || '').trim()
41
+ if (!skillPath || seen.has(skillPath)) continue
42
+
43
+ normalized.push({
44
+ skillPath,
45
+ mode: normalizeKitMode(raw.mode),
46
+ sortOrder: Number.isInteger(raw.sortOrder) ? Number(raw.sortOrder) : index,
47
+ })
48
+ seen.add(skillPath)
49
+ }
50
+
51
+ return normalized
52
+ }
53
+
54
+ async function readRuntimeConfig() {
55
+ try {
56
+ const raw = await fs.readFile(CONFIG_PATH, 'utf-8')
57
+ if (!raw.trim()) {
58
+ return { projects: [], agents: [] }
59
+ }
60
+
61
+ const parsed = JSON.parse(raw)
62
+ const projects = Array.isArray(parsed.projects)
63
+ ? parsed.projects.map((entry) => String(entry || '').trim()).filter(Boolean)
64
+ : []
65
+ const agents = Array.isArray(parsed.agents)
66
+ ? parsed.agents.filter((entry) => entry && typeof entry === 'object' && !Array.isArray(entry))
67
+ : []
68
+
69
+ return { projects, agents }
70
+ } catch {
71
+ return { projects: [], agents: [] }
72
+ }
73
+ }
74
+
75
+ async function atomicWriteText(filePath, content) {
76
+ const dirPath = path.dirname(filePath)
77
+ await fs.ensureDir(dirPath)
78
+ const tempPath = path.join(
79
+ dirPath,
80
+ `.${path.basename(filePath)}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`
81
+ )
82
+ await fs.writeFile(tempPath, content, 'utf-8')
83
+ await fs.move(tempPath, filePath, { overwrite: true })
84
+ }
85
+
86
+ async function syncSkill(sourcePath, destParentPath, syncMode = 'copy') {
87
+ const skillDirName = path.basename(sourcePath)
88
+ const destPath = path.join(destParentPath, skillDirName)
89
+
90
+ if (sourcePath === destPath) {
91
+ return destPath
92
+ }
93
+
94
+ await fs.ensureDir(destParentPath)
95
+
96
+ if (syncMode === 'link') {
97
+ await fs.remove(destPath)
98
+ await fs.ensureSymlink(sourcePath, destPath)
99
+ return destPath
100
+ }
101
+
102
+ const isSymlink = await fs
103
+ .lstat(destPath)
104
+ .then((stat) => stat.isSymbolicLink())
105
+ .catch(() => false)
106
+
107
+ if (isSymlink) {
108
+ await fs.remove(destPath)
109
+ }
110
+
111
+ await fs.copy(sourcePath, destPath, { overwrite: true, errorOnExist: false })
112
+ return destPath
113
+ }
114
+
115
+ function listKitPolicies() {
116
+ return listKitPoliciesCore()
117
+ }
118
+
119
+ function addKitPolicy(values) {
120
+ return addKitPolicyCore(values)
121
+ }
122
+
123
+ function updateKitPolicy(values) {
124
+ return updateKitPolicyCore(values)
125
+ }
126
+
127
+ function deleteKitPolicy(id) {
128
+ return deleteKitPolicyCore(id)
129
+ }
130
+
131
+ function listKitLoadouts() {
132
+ return listKitLoadoutsCore()
133
+ }
134
+
135
+ function addKitLoadout(values) {
136
+ return addKitLoadoutCore({
137
+ ...values,
138
+ items: normalizeLoadoutItems(values.items || []),
139
+ })
140
+ }
141
+
142
+ function updateKitLoadout(values) {
143
+ return updateKitLoadoutCore({
144
+ ...values,
145
+ items: values.items === undefined ? undefined : normalizeLoadoutItems(values.items),
146
+ })
147
+ }
148
+
149
+ function deleteKitLoadout(id) {
150
+ return deleteKitLoadoutCore(id)
151
+ }
152
+
153
+ function listKits() {
154
+ return listKitsCore()
155
+ }
156
+
157
+ function addKit(values) {
158
+ return addKitCore(values)
159
+ }
160
+
161
+ function updateKit(values) {
162
+ return updateKitCore(values)
163
+ }
164
+
165
+ function deleteKit(id) {
166
+ return deleteKitCore(id)
167
+ }
168
+
169
+ async function applyKit(values) {
170
+ const kitId = String(values?.kitId || '').trim()
171
+ const projectPath = String(values?.projectPath || '').trim()
172
+ const agentName = String(values?.agentName || '').trim()
173
+ const applyMode = normalizeKitMode(values?.mode)
174
+ const overwriteAgentsMd = values?.overwriteAgentsMd === true
175
+
176
+ if (!kitId) throw new Error('kitId is required')
177
+ if (!projectPath) throw new Error('projectPath is required')
178
+ if (!agentName) throw new Error('agentName is required')
179
+
180
+ const kit = getKitById(kitId)
181
+ if (!kit) throw new Error(`Kit not found: ${kitId}`)
182
+
183
+ const policy = getKitPolicyById(kit.policyId)
184
+ if (!policy) throw new Error(`AGENTS.md not found: ${kit.policyId}`)
185
+
186
+ const loadout = getKitLoadoutById(kit.loadoutId)
187
+ if (!loadout) throw new Error(`Skills package not found: ${kit.loadoutId}`)
188
+
189
+ const config = await readRuntimeConfig()
190
+ const projectExists = config.projects.includes(projectPath)
191
+ if (!projectExists) {
192
+ throw new Error('Target project is not registered in Skills Hub.')
193
+ }
194
+
195
+ const targetAgent = config.agents.find((agent) => agent.enabled && agent.name === agentName)
196
+ if (!targetAgent) {
197
+ throw new Error('Target agent is not enabled or not found.')
198
+ }
199
+
200
+ const targetSkillParent = path.join(projectPath, targetAgent.projectPath)
201
+ const loadoutResults = []
202
+
203
+ for (const item of loadout.items) {
204
+ try {
205
+ const destination = await syncSkill(item.skillPath, targetSkillParent, applyMode)
206
+ loadoutResults.push({
207
+ skillPath: item.skillPath,
208
+ mode: applyMode,
209
+ destination,
210
+ status: 'success',
211
+ })
212
+ } catch (error) {
213
+ loadoutResults.push({
214
+ skillPath: item.skillPath,
215
+ mode: applyMode,
216
+ destination: path.join(targetSkillParent, path.basename(item.skillPath)),
217
+ status: 'failed',
218
+ error: error instanceof Error ? error.message : String(error),
219
+ })
220
+ throw new Error(`Failed to sync skill: ${item.skillPath}`)
221
+ }
222
+ }
223
+
224
+ const policyPath = path.join(projectPath, 'AGENTS.md')
225
+ const policyExists = await fs.pathExists(policyPath)
226
+ if (policyExists && !overwriteAgentsMd) {
227
+ throw new Error(`AGENTS_MD_EXISTS::${policyPath}`)
228
+ }
229
+
230
+ const policyContent = policy.content.endsWith('\n') ? policy.content : `${policy.content}\n`
231
+ await atomicWriteText(policyPath, policyContent)
232
+
233
+ const applied = markKitApplied({ id: kitId, projectPath, agentName })
234
+ if (!applied) {
235
+ throw new Error('Failed to record kit application metadata.')
236
+ }
237
+
238
+ return {
239
+ kitId,
240
+ kitName: applied.name,
241
+ policyPath,
242
+ projectPath,
243
+ agentName,
244
+ appliedAt: applied.lastAppliedAt || Date.now(),
245
+ overwroteAgentsMd: policyExists && overwriteAgentsMd,
246
+ loadoutResults,
247
+ }
248
+ }
249
+
250
+ export {
251
+ normalizeKitMode,
252
+ normalizeLoadoutItems,
253
+ listKitPolicies,
254
+ addKitPolicy,
255
+ updateKitPolicy,
256
+ deleteKitPolicy,
257
+ listKitLoadouts,
258
+ addKitLoadout,
259
+ updateKitLoadout,
260
+ deleteKitLoadout,
261
+ listKits,
262
+ addKit,
263
+ updateKit,
264
+ deleteKit,
265
+ applyKit,
266
+ }
@@ -0,0 +1,63 @@
1
+ import type {
2
+ AppType,
3
+ ProviderRecord,
4
+ ProviderSwitchResult,
5
+ UniversalProviderApps,
6
+ UniversalProviderModels,
7
+ UniversalProviderRecord,
8
+ } from '../core/provider-types'
9
+
10
+ export const APP_TYPES: AppType[]
11
+
12
+ export function assertAppType(appType: string): asserts appType is AppType
13
+ export function normalizeProviderConfig(config: unknown): Record<string, unknown>
14
+
15
+ export function listProvidersMasked(appType?: string): ProviderRecord[]
16
+ export function getCurrentProviderMasked(appType: string): ProviderRecord | null
17
+ export function getProviderRaw(id: string): ProviderRecord
18
+ export function addProviderRecord(values: {
19
+ appType: string
20
+ name: string
21
+ config: unknown
22
+ }): ProviderRecord
23
+ export function updateProviderRecord(values: {
24
+ id: string
25
+ name?: string
26
+ config?: unknown
27
+ }): ProviderRecord
28
+ export function deleteProviderRecord(id: string): boolean
29
+ export function switchProviderRecord(values: {
30
+ appType: string
31
+ providerId: string
32
+ }): Promise<ProviderSwitchResult>
33
+ export function getLatestProviderBackup(appType: string): unknown
34
+ export function restoreLatestProviderBackup(appType: string): Promise<unknown>
35
+ export function captureProviderFromLiveRecord(values: {
36
+ appType: string
37
+ name: string
38
+ profile?: Record<string, unknown>
39
+ }): Promise<ProviderRecord>
40
+
41
+ export function listUniversalProvidersMasked(): UniversalProviderRecord[]
42
+ export function getUniversalProviderRaw(id: string): UniversalProviderRecord
43
+ export function addUniversalProviderRecord(values: {
44
+ name: string
45
+ baseUrl: string
46
+ apiKey: string
47
+ websiteUrl?: string
48
+ notes?: string
49
+ apps?: Partial<UniversalProviderApps>
50
+ models?: UniversalProviderModels
51
+ }): UniversalProviderRecord
52
+ export function updateUniversalProviderRecord(values: {
53
+ id: string
54
+ name?: string
55
+ baseUrl?: string
56
+ apiKey?: string
57
+ websiteUrl?: string
58
+ notes?: string
59
+ apps?: Partial<UniversalProviderApps>
60
+ models?: UniversalProviderModels
61
+ }): UniversalProviderRecord
62
+ export function deleteUniversalProviderRecord(id: string): boolean
63
+ export function applyUniversalProviderRecord(id: string): ProviderRecord[]
@@ -0,0 +1,183 @@
1
+ import {
2
+ APP_TYPES,
3
+ addProvider,
4
+ addUniversalProvider,
5
+ applyUniversalProvider,
6
+ captureProviderFromLive,
7
+ deleteProvider,
8
+ deleteUniversalProvider,
9
+ getCurrentProvider,
10
+ getLatestBackup,
11
+ getProviderById,
12
+ getUniversalProviderById,
13
+ listProviders,
14
+ listUniversalProviders,
15
+ maskProvider,
16
+ maskProviders,
17
+ restoreBackup,
18
+ switchProvider,
19
+ updateProvider,
20
+ updateUniversalProvider,
21
+ } from '../core/provider-core.mjs'
22
+
23
+ function assertAppType(appType) {
24
+ if (!APP_TYPES.includes(appType)) {
25
+ throw new Error(`Unsupported app type: ${appType}`)
26
+ }
27
+ }
28
+
29
+ function normalizeProviderConfig(config) {
30
+ if (typeof config === 'string') {
31
+ const parsed = JSON.parse(config)
32
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
33
+ throw new Error('Provider config must be a JSON object.')
34
+ }
35
+ return parsed
36
+ }
37
+
38
+ if (!config || typeof config !== 'object' || Array.isArray(config)) {
39
+ throw new Error('Provider config must be an object.')
40
+ }
41
+
42
+ return config
43
+ }
44
+
45
+ function maskUniversalProvider(provider) {
46
+ return {
47
+ ...provider,
48
+ apiKey: provider.apiKey.trim() ? `${provider.apiKey.slice(0, 3)}****` : '',
49
+ }
50
+ }
51
+
52
+ function listProvidersMasked(appType) {
53
+ if (appType) assertAppType(appType)
54
+ const providers = listProviders(appType)
55
+ return maskProviders(providers)
56
+ }
57
+
58
+ function getCurrentProviderMasked(appType) {
59
+ assertAppType(appType)
60
+ return maskProvider(getCurrentProvider(appType))
61
+ }
62
+
63
+ function getProviderRaw(id) {
64
+ const provider = getProviderById(id)
65
+ if (!provider) {
66
+ throw new Error(`Provider not found: ${id}`)
67
+ }
68
+ return provider
69
+ }
70
+
71
+ function addProviderRecord(values) {
72
+ assertAppType(values.appType)
73
+ return maskProvider(
74
+ addProvider({
75
+ appType: values.appType,
76
+ name: values.name,
77
+ config: normalizeProviderConfig(values.config),
78
+ })
79
+ )
80
+ }
81
+
82
+ function updateProviderRecord(values) {
83
+ return maskProvider(
84
+ updateProvider({
85
+ id: values.id,
86
+ name: values.name,
87
+ config: values.config === undefined ? undefined : normalizeProviderConfig(values.config),
88
+ })
89
+ )
90
+ }
91
+
92
+ function deleteProviderRecord(id) {
93
+ return deleteProvider(id)
94
+ }
95
+
96
+ async function switchProviderRecord(values) {
97
+ assertAppType(values.appType)
98
+ return switchProvider({
99
+ appType: values.appType,
100
+ providerId: values.providerId,
101
+ })
102
+ }
103
+
104
+ function getLatestProviderBackup(appType) {
105
+ assertAppType(appType)
106
+ return getLatestBackup(appType)
107
+ }
108
+
109
+ async function restoreLatestProviderBackup(appType) {
110
+ assertAppType(appType)
111
+ return restoreBackup(appType)
112
+ }
113
+
114
+ async function captureProviderFromLiveRecord(values) {
115
+ assertAppType(values.appType)
116
+ return maskProvider(
117
+ await captureProviderFromLive({
118
+ appType: values.appType,
119
+ name: values.name,
120
+ profile: values.profile,
121
+ })
122
+ )
123
+ }
124
+
125
+ function listUniversalProvidersMasked() {
126
+ const providers = listUniversalProviders()
127
+ return providers.map(maskUniversalProvider)
128
+ }
129
+
130
+ function getUniversalProviderRaw(id) {
131
+ const provider = getUniversalProviderById(id)
132
+ if (!provider) {
133
+ throw new Error(`Universal provider not found: ${id}`)
134
+ }
135
+ return provider
136
+ }
137
+
138
+ function addUniversalProviderRecord(values) {
139
+ const provider = addUniversalProvider(values)
140
+ if (!provider) {
141
+ throw new Error('Failed to create universal provider')
142
+ }
143
+ return maskUniversalProvider(provider)
144
+ }
145
+
146
+ function updateUniversalProviderRecord(values) {
147
+ const provider = updateUniversalProvider(values)
148
+ if (!provider) {
149
+ throw new Error('Failed to update universal provider')
150
+ }
151
+ return maskUniversalProvider(provider)
152
+ }
153
+
154
+ function deleteUniversalProviderRecord(id) {
155
+ return deleteUniversalProvider(id)
156
+ }
157
+
158
+ function applyUniversalProviderRecord(id) {
159
+ const applied = applyUniversalProvider({ id })
160
+ return maskProviders(applied)
161
+ }
162
+
163
+ export {
164
+ APP_TYPES,
165
+ assertAppType,
166
+ normalizeProviderConfig,
167
+ listProvidersMasked,
168
+ getCurrentProviderMasked,
169
+ getProviderRaw,
170
+ addProviderRecord,
171
+ updateProviderRecord,
172
+ deleteProviderRecord,
173
+ switchProviderRecord,
174
+ getLatestProviderBackup,
175
+ restoreLatestProviderBackup,
176
+ captureProviderFromLiveRecord,
177
+ listUniversalProvidersMasked,
178
+ getUniversalProviderRaw,
179
+ addUniversalProviderRecord,
180
+ updateUniversalProviderRecord,
181
+ deleteUniversalProviderRecord,
182
+ applyUniversalProviderRecord,
183
+ }
package/package.json ADDED
@@ -0,0 +1,94 @@
1
+ {
2
+ "name": "@skillshub-labs/cli",
3
+ "version": "0.1.17",
4
+ "description": "Unify your AI toolbox. A local hub to visualize, manage, and sync skills across Antigravity, Claude, Cursor, Trae, and other AI agents.",
5
+ "keywords": [
6
+ "skills",
7
+ "ai",
8
+ "agent",
9
+ "claude",
10
+ "cursor",
11
+ "antigravity",
12
+ "cli"
13
+ ],
14
+ "author": "PotatoDog1669",
15
+ "license": "MIT",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "https://github.com/PotatoDog1669/skills-hub.git"
19
+ },
20
+ "homepage": "https://github.com/PotatoDog1669/skills-hub",
21
+ "publishConfig": {
22
+ "access": "public"
23
+ },
24
+ "bin": {
25
+ "skills-hub": "bin/skills-hub"
26
+ },
27
+ "files": [
28
+ "bin/skills-hub",
29
+ "lib/core/provider-core.mjs",
30
+ "lib/core/provider-core.d.ts",
31
+ "lib/core/kit-core.mjs",
32
+ "lib/core/kit-core.d.ts",
33
+ "lib/core/kit-types.ts",
34
+ "lib/services/provider-service.mjs",
35
+ "lib/services/provider-service.d.ts",
36
+ "lib/services/kit-service.mjs",
37
+ "lib/services/kit-service.d.ts",
38
+ "README.md",
39
+ "LICENSE"
40
+ ],
41
+ "scripts": {
42
+ "dev": "npm run tauri:dev",
43
+ "build": "npm run tauri:ui:build",
44
+ "tauri:ui:dev": "vite --config apps/desktop-ui/vite.config.ts --host 127.0.0.1 --port 1420",
45
+ "tauri:ui:build": "vite build --config apps/desktop-ui/vite.config.ts",
46
+ "tauri:dev": "tauri dev --config src-tauri/tauri.conf.json",
47
+ "tauri:build": "tauri build --config src-tauri/tauri.conf.json",
48
+ "lint": "eslint",
49
+ "typecheck": "tsc --noEmit",
50
+ "test": "vitest",
51
+ "prepack": "npm run typecheck"
52
+ },
53
+ "dependencies": {
54
+ "@iarna/toml": "^2.2.5",
55
+ "@tailwindcss/typography": "^0.5.19",
56
+ "@tauri-apps/api": "^2.8.0",
57
+ "@tauri-apps/plugin-dialog": "^2.0.0",
58
+ "@types/react-syntax-highlighter": "^15.5.13",
59
+ "better-sqlite3": "^12.6.2",
60
+ "clsx": "^2.1.1",
61
+ "fs-extra": "^11.3.3",
62
+ "gray-matter": "^4.0.3",
63
+ "lucide-react": "^0.562.0",
64
+ "react": "19.2.3",
65
+ "react-dom": "19.2.3",
66
+ "react-markdown": "^10.1.0",
67
+ "react-syntax-highlighter": "^16.1.0",
68
+ "remark-gfm": "^4.0.1",
69
+ "simple-git": "^3.30.0"
70
+ },
71
+ "devDependencies": {
72
+ "@eslint/js": "^9.39.2",
73
+ "@tauri-apps/cli": "^2.9.2",
74
+ "@testing-library/dom": "^10.4.1",
75
+ "@testing-library/react": "^16.3.2",
76
+ "@types/fs-extra": "^11.0.4",
77
+ "@types/node": "^20",
78
+ "@types/react": "^19",
79
+ "@types/react-dom": "^19",
80
+ "@vitejs/plugin-react": "^5.1.2",
81
+ "autoprefixer": "^10.4.23",
82
+ "eslint": "^9",
83
+ "globals": "^17.3.0",
84
+ "jsdom": "^27.4.0",
85
+ "lint-staged": "^16.2.7",
86
+ "postcss": "^8.5.6",
87
+ "prettier": "^3.8.0",
88
+ "tailwindcss": "^3.4.17",
89
+ "typescript": "^5",
90
+ "typescript-eslint": "^8.55.0",
91
+ "vite": "^7.1.11",
92
+ "vitest": "^4.0.17"
93
+ }
94
+ }