skill-os 0.1.3 โ 0.1.6
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/lib/auth.js +114 -0
- package/lib/config.js +95 -0
- package/lib/local.js +173 -0
- package/lib/remote.js +304 -0
- package/lib/sync.js +190 -0
- package/lib/utils.js +139 -0
- package/package.json +2 -2
- package/skill-os.js +180 -631
package/skill-os.js
CHANGED
|
@@ -1,667 +1,216 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
system: "๐ง",
|
|
14
|
-
runtime: "๐ฆ",
|
|
15
|
-
application: "๐",
|
|
16
|
-
cloud: "โ๏ธ",
|
|
17
|
-
package: "๐ฆ",
|
|
18
|
-
network: "๐",
|
|
19
|
-
storage: "๐พ",
|
|
20
|
-
user: "๐ค",
|
|
21
|
-
meta: "๐ ๏ธ",
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
function formatLayerIcon(skillPath) {
|
|
25
|
-
const layer = skillPath.split("/")[0];
|
|
26
|
-
return layerIcons[layer] || "๐";
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function formatStatus(status) {
|
|
30
|
-
switch (status?.toLowerCase()) {
|
|
31
|
-
case 'stable': return chalk.green(status);
|
|
32
|
-
case 'beta': return chalk.yellow(status);
|
|
33
|
-
case 'placeholder': return chalk.dim(status);
|
|
34
|
-
default: return status || 'unknown';
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function getRepoRoot() {
|
|
39
|
-
let current = path.dirname(__dirname); // Usually cli is in the repo root
|
|
40
|
-
if (fs.existsSync(path.join(current, 'SKILL_INDEX.json'))) {
|
|
41
|
-
return current;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
if (fs.existsSync(path.join(process.cwd(), 'SKILL_INDEX.json'))) {
|
|
45
|
-
return process.cwd();
|
|
46
|
-
}
|
|
47
|
-
return current;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function loadIndex() {
|
|
51
|
-
const repoRoot = getRepoRoot();
|
|
52
|
-
const indexPath = path.join(repoRoot, 'SKILL_INDEX.json');
|
|
53
|
-
|
|
54
|
-
if (!fs.existsSync(indexPath)) {
|
|
55
|
-
console.error(chalk.red('Error: SKILL_INDEX.json not found'));
|
|
56
|
-
process.exit(1);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const raw = fs.readFileSync(indexPath, 'utf-8');
|
|
60
|
-
return JSON.parse(raw);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// ----------------------------------------------------------------------
|
|
64
|
-
// API Configuration
|
|
65
|
-
// ----------------------------------------------------------------------
|
|
66
|
-
|
|
67
|
-
const DEFAULT_API_BASE = "https://oscopilot.alibaba-inc.com/skills/api/v1";
|
|
68
|
-
|
|
69
|
-
function getApiBase(options) {
|
|
70
|
-
let url = options?.url || process.env.SKILL_OS_REGISTRY || DEFAULT_API_BASE;
|
|
71
|
-
return url.replace(/\/$/, "");
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
async function fetchFromApi(endpoint, options = {}) {
|
|
75
|
-
// Only require node-fetch dynamically when needed
|
|
76
|
-
let fetchFn = require('node-fetch');
|
|
77
|
-
if (typeof fetchFn !== 'function' && fetchFn.default) {
|
|
78
|
-
fetchFn = fetchFn.default;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
let baseUrl;
|
|
82
|
-
// Extract url option if passed inside options object
|
|
83
|
-
if (options && options.url) {
|
|
84
|
-
baseUrl = getApiBase({ url: options.url });
|
|
85
|
-
delete options.url;
|
|
86
|
-
} else {
|
|
87
|
-
baseUrl = getApiBase({});
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const url = `${baseUrl}${endpoint}`;
|
|
3
|
+
/**
|
|
4
|
+
* Skill-OS CLI โ ๅ
ฅๅฃๆไปถ
|
|
5
|
+
*
|
|
6
|
+
* ่ด่ดฃ commander ๅฝไปคๆณจๅ๏ผไธๅก้ป่พๆๅๅจ lib/ ไธ๏ผ
|
|
7
|
+
* lib/utils.js โ ๅ
ฑไบซๅทฅๅ
ท
|
|
8
|
+
* lib/config.js โ API ้
็ฝฎ & ๅญๆฎ
|
|
9
|
+
* lib/local.js โ ๆฌๅฐๅฝไปค (create, validate)
|
|
10
|
+
* lib/remote.js โ ่ฟ็จๅฝไปค (list, search, info, download, upload)
|
|
11
|
+
* lib/auth.js โ ่ฎค่ฏๅฝไปค (login, logout)
|
|
12
|
+
*/
|
|
91
13
|
|
|
92
|
-
|
|
93
|
-
const response = await fetchFn(url, options);
|
|
94
|
-
if (!response.ok) {
|
|
95
|
-
throw new Error(`HTTP Error: ${response.status} ${response.statusText}`);
|
|
96
|
-
}
|
|
97
|
-
return await response.json();
|
|
98
|
-
} catch (error) {
|
|
99
|
-
console.error(chalk.red(`\nโ Error connecting to remote registry at ${url}`));
|
|
100
|
-
console.error(chalk.dim(error.message));
|
|
101
|
-
process.exit(1);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// ----------------------------------------------------------------------
|
|
106
|
-
// Commmands Implementations
|
|
107
|
-
// ----------------------------------------------------------------------
|
|
108
|
-
|
|
109
|
-
async function cmdList(options) {
|
|
110
|
-
console.log(`\n${chalk.dim('Fetching skills from external registry...')}`);
|
|
111
|
-
const index = await fetchFromApi('/skills', options);
|
|
112
|
-
|
|
113
|
-
// API returns an array of skills, or an object with a data/skills array property
|
|
114
|
-
let skillsList = [];
|
|
115
|
-
if (Array.isArray(index)) {
|
|
116
|
-
skillsList = index;
|
|
117
|
-
} else if (Array.isArray(index.data)) {
|
|
118
|
-
skillsList = index.data;
|
|
119
|
-
} else if (Array.isArray(index.skills)) {
|
|
120
|
-
skillsList = index.skills;
|
|
121
|
-
} else if (typeof index === 'object') {
|
|
122
|
-
// Fallback if it is an object map
|
|
123
|
-
skillsList = Object.entries(index.skills || index).map(([k, v]) => ({ path: k, ...v }));
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
console.log(`\n${chalk.bold('๐ Skill-OS Available Skills')}`);
|
|
127
|
-
console.log(`${chalk.dim('โ'.repeat(60))}\n`);
|
|
128
|
-
|
|
129
|
-
const layers = {};
|
|
130
|
-
for (const info of skillsList) {
|
|
131
|
-
// Fallback path resolution. If the API returns 'path', use it. Otherwise, construct it or use name.
|
|
132
|
-
const skillPath = info.path || (info.layer ? `${info.layer}/${info.category || 'misc'}/${info.name}` : info.name || 'unknown');
|
|
133
|
-
const layer = info.layer || (skillPath.includes('/') ? skillPath.split('/')[0] : 'misc');
|
|
134
|
-
|
|
135
|
-
if (!layers[layer]) layers[layer] = [];
|
|
136
|
-
layers[layer].push({ path: skillPath, info });
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
for (const layer of Object.keys(layers).sort()) {
|
|
140
|
-
const icon = layerIcons[layer] || "๐";
|
|
141
|
-
console.log(`${chalk.bold(icon + ' ' + layer.toUpperCase())}`);
|
|
142
|
-
|
|
143
|
-
const sortedSkills = layers[layer].sort((a, b) => a.path.localeCompare(b.path));
|
|
144
|
-
|
|
145
|
-
for (const { path: skillPath, info } of sortedSkills) {
|
|
146
|
-
const status = formatStatus(info.status);
|
|
147
|
-
const name = info.name || skillPath;
|
|
148
|
-
const version = info.version || '?';
|
|
149
|
-
const desc = (info.description || '').substring(0, 50);
|
|
150
|
-
|
|
151
|
-
console.log(` ${chalk.cyan(skillPath)}`);
|
|
152
|
-
console.log(` ${name} (${version}) [${status}]`);
|
|
153
|
-
console.log(` ${chalk.dim(desc + '...')}`);
|
|
154
|
-
}
|
|
155
|
-
console.log();
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
async function cmdSearch(query, options) {
|
|
160
|
-
console.log(`\n${chalk.dim(`Searching remote registry for '${query}'...`)}`);
|
|
161
|
-
|
|
162
|
-
// API Endpoint: /api/v1/search?q={{skill.name}}
|
|
163
|
-
const encodedQuery = encodeURIComponent(query);
|
|
164
|
-
const response = await fetchFromApi(`/search?q=${encodedQuery}`, options);
|
|
14
|
+
const { program } = require('commander');
|
|
165
15
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
16
|
+
const { DEFAULT_API_BASE, DEFAULT_DAILY_API_BASE } = require('./lib/config');
|
|
17
|
+
const { cmdCreate, cmdValidate } = require('./lib/local');
|
|
18
|
+
const { cmdSync } = require('./lib/sync');
|
|
19
|
+
const { cmdList, cmdSearch, cmdInfo, cmdDownload, cmdUpload } = require('./lib/remote');
|
|
20
|
+
const { cmdLogin, cmdLogout } = require('./lib/auth');
|
|
169
21
|
|
|
170
|
-
|
|
171
|
-
matches = rawResults;
|
|
172
|
-
} else if (typeof rawResults === 'object') {
|
|
173
|
-
for (const [skillPath, info] of Object.entries(rawResults)) {
|
|
174
|
-
matches.push({ path: skillPath, ...info });
|
|
175
|
-
}
|
|
176
|
-
}
|
|
22
|
+
// โโ ็ๆฌ & ๆ่ฟฐ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
177
23
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
24
|
+
program
|
|
25
|
+
.name('skill-os')
|
|
26
|
+
.description('Skill-OS CLI โ AI-Native OS ๆ่ฝ็ฎก็ๅนณๅฐ')
|
|
27
|
+
.version('0.1.4');
|
|
182
28
|
|
|
183
|
-
|
|
184
|
-
|
|
29
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
30
|
+
// ่ฎค่ฏๅฝไปค
|
|
31
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
185
32
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
const version = info.version || '?';
|
|
191
|
-
const desc = info.description || '';
|
|
33
|
+
program.command('login')
|
|
34
|
+
.description('็ปๅฝ Skill-OS ๆณจๅไธญๅฟ๏ผ้่ฟๆต่งๅจ BUC SSO ่ฎค่ฏ๏ผ')
|
|
35
|
+
.option('--url <url>', 'ๆๅฎๆๅก็ซฏๅฐๅ', DEFAULT_DAILY_API_BASE)
|
|
36
|
+
.action(cmdLogin);
|
|
192
37
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
}
|
|
38
|
+
program.command('logout')
|
|
39
|
+
.description('ๆณจ้็ปๅฝ๏ผๆธ
้คๆฌๅฐๅญๆฎ')
|
|
40
|
+
.option('--url <url>', 'ๆๅฎๆๅก็ซฏๅฐๅ', DEFAULT_DAILY_API_BASE)
|
|
41
|
+
.action(cmdLogout);
|
|
198
42
|
|
|
199
|
-
|
|
200
|
-
|
|
43
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
44
|
+
// ่ฟ็จๆณจๅไธญๅฟๅฝไปค (Remote)
|
|
45
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
201
46
|
|
|
202
|
-
|
|
203
|
-
|
|
47
|
+
program.command('list')
|
|
48
|
+
.alias('ls')
|
|
49
|
+
.description('ๅๅบๆณจๅไธญๅฟไธญๆๆๅฏ็จ็ๆ่ฝ')
|
|
50
|
+
.addOption(
|
|
51
|
+
new (require('commander').Option)('--url <url>', 'ๆๅฎๆณจๅไธญๅฟๅฐๅ')
|
|
52
|
+
.default(DEFAULT_DAILY_API_BASE).hideHelp()
|
|
53
|
+
)
|
|
54
|
+
.action(cmdList);
|
|
204
55
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
56
|
+
program.command('search')
|
|
57
|
+
.alias('s')
|
|
58
|
+
.description('ๅจๆณจๅไธญๅฟไธญๆ็ดขๆ่ฝ')
|
|
59
|
+
.argument('<ๅ
ณ้ฎ่ฏ>', 'ๆ็ดขๅ
ณ้ฎ่ฏ๏ผๅฆ firewallใrpm')
|
|
60
|
+
.addOption(
|
|
61
|
+
new (require('commander').Option)('--url <url>', 'ๆๅฎๆณจๅไธญๅฟๅฐๅ')
|
|
62
|
+
.default(DEFAULT_API_BASE).hideHelp()
|
|
63
|
+
)
|
|
64
|
+
.action(cmdSearch);
|
|
208
65
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
66
|
+
program.command('info')
|
|
67
|
+
.alias('i')
|
|
68
|
+
.description('ๆฅ็่ฟ็จๆ่ฝ็่ฏฆ็ปไฟกๆฏ')
|
|
69
|
+
.argument('<ๆ่ฝ่ทฏๅพ>', 'ๆ่ฝ่ทฏๅพ๏ผๅฆ system/network/firewall')
|
|
70
|
+
.addOption(
|
|
71
|
+
new (require('commander').Option)('--url <url>', 'ๆๅฎๆณจๅไธญๅฟๅฐๅ')
|
|
72
|
+
.default(DEFAULT_API_BASE).hideHelp()
|
|
73
|
+
)
|
|
74
|
+
.action(cmdInfo);
|
|
215
75
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
76
|
+
program.command('download')
|
|
77
|
+
.alias('dl')
|
|
78
|
+
.description('ไปๆณจๅไธญๅฟไธ่ฝฝๆ่ฝๅ
ๅนถ่งฃๅ')
|
|
79
|
+
.argument('<ๆ่ฝ่ทฏๅพ>', '่ฆไธ่ฝฝ็ๆ่ฝ่ทฏๅพ๏ผๅฆ core/kernel/kernel-info')
|
|
80
|
+
.option('-t, --target <็ฎๅฝ>', 'ๆๅฎ่งฃๅ็ฎๅฝ๏ผ้ป่ฎคไธบๅฝๅ็ฎๅฝ๏ผ')
|
|
81
|
+
.option('--platform <ๅนณๅฐ>', 'ๆๅฎๅนณๅฐ๏ผ่ชๅจไธ่ฝฝๅฐ .<ๅนณๅฐ>/skills/ ็ฎๅฝ๏ผๅฆ --platform qoder')
|
|
82
|
+
.addOption(
|
|
83
|
+
new (require('commander').Option)('--url <url>', 'ๆๅฎๆณจๅไธญๅฟๅฐๅ')
|
|
84
|
+
.default(DEFAULT_API_BASE).hideHelp()
|
|
85
|
+
)
|
|
86
|
+
.action(cmdDownload);
|
|
221
87
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
88
|
+
program.command('upload')
|
|
89
|
+
.alias('publish')
|
|
90
|
+
.description('ๅฐๆฌๅฐๆ่ฝๅๅธๅฐๆณจๅไธญๅฟ')
|
|
91
|
+
.argument('<ๆ่ฝ็ฎๅฝ>', '่ฆไธไผ ็ๆ่ฝ็ฎๅฝ่ทฏๅพ๏ผๅฆ skills/system/network/firewall')
|
|
92
|
+
.option('-u, --update', 'ๆดๆฐๅทฒๆๆ่ฝ๏ผๅๅ่ฆ็๏ผ๏ผ้ๅ
ๆดๆฐ SKILL.md ไธญ็็ๆฌๅท')
|
|
93
|
+
.option('--token <token>', 'ๆๅจๆๅฎ่ฎค่ฏ Token๏ผ่ฆ็ๅทฒไฟๅญ็ๅญๆฎ๏ผ')
|
|
94
|
+
.addOption(
|
|
95
|
+
new (require('commander').Option)('--url <url>', 'ๆๅฎๆณจๅไธญๅฟๅฐๅ')
|
|
96
|
+
.default(DEFAULT_DAILY_API_BASE).hideHelp()
|
|
97
|
+
)
|
|
98
|
+
.action(cmdUpload);
|
|
228
99
|
|
|
100
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
101
|
+
// ๆฌๅฐๅผๅๅฝไปค (Local)
|
|
102
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
229
103
|
|
|
104
|
+
program.command('create')
|
|
105
|
+
.alias('new')
|
|
106
|
+
.description('ๅๅปบๆ่ฝ่ๆๆถ๏ผ็ๆ SKILL.md ๆจกๆฟ๏ผ')
|
|
107
|
+
.argument('<ๆ่ฝ่ทฏๅพ>', 'ๆ่ฝ่ทฏๅพ๏ผๆ ผๅผ๏ผ<ๅฑ็บง>/<ๅ็ฑป>/<ๅ็งฐ>๏ผๅฆ system/security/cve-repair')
|
|
108
|
+
.option('-f, --force', 'ๅฆๆ็ฎๅฝๅทฒๅญๅจๅๅผบๅถ่ฆ็')
|
|
109
|
+
.action(cmdCreate);
|
|
230
110
|
|
|
231
|
-
|
|
232
|
-
|
|
111
|
+
program.command('validate')
|
|
112
|
+
.alias('check')
|
|
113
|
+
.description('ๆ ก้ชๆ่ฝๆฏๅฆ็ฌฆๅ Skill-OS ่ง่')
|
|
114
|
+
.argument('<ๆ่ฝ็ฎๅฝ>', '่ฆๆ ก้ช็ๆ่ฝ็ฎๅฝ่ทฏๅพ๏ผๅฆ skills/system/security/cve-repair')
|
|
115
|
+
.action(cmdValidate);
|
|
233
116
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
117
|
+
program.command('sync')
|
|
118
|
+
.description('ๆซๆ skills/ ็ฎๅฝ๏ผ่ชๅจ็ๆ SKILL_INDEX.json')
|
|
119
|
+
.action(cmdSync);
|
|
120
|
+
|
|
121
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
122
|
+
// Shell ๅฝไปค่กฅๅ
จ
|
|
123
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
124
|
+
|
|
125
|
+
program.command('completion')
|
|
126
|
+
.description('่พๅบ Shell ๅฝไปค่กฅๅ
จ่ๆฌ๏ผๆฏๆ bash/zsh/fish๏ผ')
|
|
127
|
+
.option('--shell <shell>', 'ๆๅฎ Shell ็ฑปๅ๏ผbashใzshใfish๏ผ้ป่ฎค่ชๅจๆฃๆต๏ผ')
|
|
128
|
+
.action((options) => {
|
|
129
|
+
const shell = options.shell || detectShell();
|
|
130
|
+
|
|
131
|
+
if (shell === 'fish') {
|
|
132
|
+
console.log(generateFishCompletion());
|
|
133
|
+
} else if (shell === 'zsh') {
|
|
134
|
+
console.log(generateZshCompletion());
|
|
135
|
+
} else {
|
|
136
|
+
console.log(generateBashCompletion());
|
|
239
137
|
}
|
|
240
|
-
fs.rmSync(targetDir, { recursive: true, force: true });
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
fs.mkdirSync(targetDir, { recursive: true });
|
|
244
138
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
process.exit(1);
|
|
251
|
-
}
|
|
139
|
+
const installHint = {
|
|
140
|
+
bash: "eval \"$(skill-os completion --shell bash)\" # ๆทปๅ ๅฐ ~/.bashrc",
|
|
141
|
+
zsh: "eval \"$(skill-os completion --shell zsh)\" # ๆทปๅ ๅฐ ~/.zshrc",
|
|
142
|
+
fish: "skill-os completion --shell fish | source # ๆไฟๅญๅฐ ~/.config/fish/completions/skill-os.fish",
|
|
143
|
+
};
|
|
252
144
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
const skillName = parts[parts.length - 1];
|
|
256
|
-
|
|
257
|
-
const validLayers = ["core", "system", "runtime", "application"];
|
|
258
|
-
if (!validLayers.includes(layer)) {
|
|
259
|
-
console.log(chalk.yellow(`Warning: Layer '${layer}' not in spec`));
|
|
260
|
-
console.log(chalk.dim(`Valid layers: ${validLayers.join(', ')}`));
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
const titleCaseName = skillName.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ');
|
|
264
|
-
|
|
265
|
-
const skillMdContent = `---
|
|
266
|
-
name: ${skillName}
|
|
267
|
-
version: 0.1.0
|
|
268
|
-
description: TODO: Add skill description here
|
|
269
|
-
author: Your Name
|
|
270
|
-
|
|
271
|
-
layer: ${layer}
|
|
272
|
-
category: ${category}
|
|
273
|
-
lifecycle: usage # production | maintenance | operations | usage | meta
|
|
274
|
-
|
|
275
|
-
# Tags: first tag MUST be the category name
|
|
276
|
-
tags:
|
|
277
|
-
- ${category}
|
|
278
|
-
- TODO
|
|
279
|
-
|
|
280
|
-
status: placeholder
|
|
281
|
-
dependencies: []
|
|
282
|
-
|
|
283
|
-
# Optional: Permissions (for security classification)
|
|
284
|
-
# permissions:
|
|
285
|
-
# requires_root: false
|
|
286
|
-
# dangerous_operations: []
|
|
287
|
-
---
|
|
288
|
-
|
|
289
|
-
# ${titleCaseName}
|
|
290
|
-
|
|
291
|
-
TODO: Add skill documentation here.
|
|
292
|
-
|
|
293
|
-
## ่ฝๅๆฆ่ง
|
|
294
|
-
|
|
295
|
-
- TODO: List capabilities
|
|
296
|
-
|
|
297
|
-
## ไฝฟ็จ็คบไพ
|
|
298
|
-
|
|
299
|
-
\`\`\`bash
|
|
300
|
-
# TODO: Add usage examples
|
|
301
|
-
\`\`\`
|
|
302
|
-
|
|
303
|
-
## TODO
|
|
304
|
-
|
|
305
|
-
- [ ] Implement core functionality
|
|
306
|
-
- [ ] Add scripts if needed
|
|
307
|
-
- [ ] Run: skill-os validate ${skillPath}
|
|
308
|
-
- [ ] Run: skill-os sync ${skillPath}
|
|
309
|
-
`;
|
|
310
|
-
|
|
311
|
-
const skillMdPath = path.join(targetDir, "SKILL.md");
|
|
312
|
-
fs.writeFileSync(skillMdPath, skillMdContent, 'utf-8');
|
|
145
|
+
process.stderr.write(`\n# ๅฎ่ฃ
ๆ็คบ (ๆทปๅ ๅฐ shell ้
็ฝฎๆไปถ):\n# ${installHint[shell] || installHint.bash}\n`);
|
|
146
|
+
});
|
|
313
147
|
|
|
314
|
-
|
|
315
|
-
console.log(` ${chalk.cyan(targetDir)}`);
|
|
316
|
-
console.log(" โโโ SKILL.md");
|
|
148
|
+
// โโ ่กฅๅ
จ่ๆฌ็ๆ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
317
149
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
150
|
+
function detectShell() {
|
|
151
|
+
const shellEnv = process.env.SHELL || '';
|
|
152
|
+
if (shellEnv.includes('fish')) return 'fish';
|
|
153
|
+
if (shellEnv.includes('zsh')) return 'zsh';
|
|
154
|
+
return 'bash';
|
|
322
155
|
}
|
|
323
156
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
// -q: quiet, -o: overwrite
|
|
356
|
-
execSync(`unzip -q -o "${tmpZip}" -d "${targetDir}"`);
|
|
357
|
-
|
|
358
|
-
// Cleanup temp file
|
|
359
|
-
if (fs.existsSync(tmpZip)) {
|
|
360
|
-
fs.unlinkSync(tmpZip);
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
console.log(`\n${chalk.green(`โ Successfully downloaded and extracted to: ${targetDir}`)}`);
|
|
364
|
-
} catch (e) {
|
|
365
|
-
console.error(`\n${chalk.red(`โ Failed to download. The API may have returned an error, or 'unzip' is not installed.`)}`);
|
|
366
|
-
process.exit(1);
|
|
367
|
-
}
|
|
157
|
+
const COMMANDS = [
|
|
158
|
+
{ name: 'login', desc: '็ปๅฝๆณจๅไธญๅฟ' },
|
|
159
|
+
{ name: 'logout', desc: 'ๆณจ้็ปๅฝ' },
|
|
160
|
+
{ name: 'list', desc: 'ๅๅบๆๆๆ่ฝ' },
|
|
161
|
+
{ name: 'search', desc: 'ๆ็ดขๆ่ฝ' },
|
|
162
|
+
{ name: 'info', desc: 'ๆฅ็ๆ่ฝ่ฏฆๆ
' },
|
|
163
|
+
{ name: 'download', desc: 'ไธ่ฝฝๆ่ฝๅ
' },
|
|
164
|
+
{ name: 'upload', desc: 'ๅๅธๆ่ฝๅฐๆณจๅไธญๅฟ' },
|
|
165
|
+
{ name: 'create', desc: 'ๅๅปบๆ่ฝ่ๆๆถ' },
|
|
166
|
+
{ name: 'validate', desc: 'ๆ ก้ชๆ่ฝ่ง่' },
|
|
167
|
+
{ name: 'sync', desc: 'ๅๆญฅๆ่ฝ็ดขๅผ' },
|
|
168
|
+
{ name: 'completion', desc: '่พๅบๅฝไปค่กฅๅ
จ่ๆฌ' },
|
|
169
|
+
// ๅซๅ
|
|
170
|
+
{ name: 'ls', desc: 'list ็ๅซๅ' },
|
|
171
|
+
{ name: 's', desc: 'search ็ๅซๅ' },
|
|
172
|
+
{ name: 'i', desc: 'info ็ๅซๅ' },
|
|
173
|
+
{ name: 'dl', desc: 'download ็ๅซๅ' },
|
|
174
|
+
{ name: 'publish', desc: 'upload ็ๅซๅ' },
|
|
175
|
+
{ name: 'new', desc: 'create ็ๅซๅ' },
|
|
176
|
+
{ name: 'check', desc: 'validate ็ๅซๅ' },
|
|
177
|
+
];
|
|
178
|
+
|
|
179
|
+
function generateBashCompletion() {
|
|
180
|
+
const cmds = COMMANDS.map(c => c.name).join(' ');
|
|
181
|
+
return `
|
|
182
|
+
# skill-os bash completion
|
|
183
|
+
_skill_os_completions() {
|
|
184
|
+
local cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
185
|
+
if [ "\${#COMP_WORDS[@]}" -eq 2 ]; then
|
|
186
|
+
COMPREPLY=($(compgen -W "${cmds}" -- "\${cur}"))
|
|
187
|
+
fi
|
|
368
188
|
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
// Publishing / Uploading
|
|
372
|
-
// ----------------------------------------------------------------------
|
|
373
|
-
|
|
374
|
-
const FormData = require('form-data');
|
|
375
|
-
|
|
376
|
-
function parseSkillMdFrontmatter(skillMdPath) {
|
|
377
|
-
if (!fs.existsSync(skillMdPath)) return {};
|
|
378
|
-
const content = fs.readFileSync(skillMdPath, 'utf8');
|
|
379
|
-
const match = content.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
380
|
-
if (!match) return {};
|
|
381
|
-
|
|
382
|
-
try {
|
|
383
|
-
return yaml.load(match[1]) || {};
|
|
384
|
-
} catch (e) {
|
|
385
|
-
return {};
|
|
386
|
-
}
|
|
189
|
+
complete -F _skill_os_completions skill-os
|
|
190
|
+
`.trim();
|
|
387
191
|
}
|
|
388
192
|
|
|
389
|
-
|
|
390
|
-
const
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
console.error(chalk.red('โ Validation failed: Directory structure errors:'));
|
|
400
|
-
dirErrors.forEach(err => console.error(` - ${err}`));
|
|
401
|
-
process.exit(1);
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
if (!fs.existsSync(skillMdPath)) {
|
|
405
|
-
console.log(`${chalk.red('โ Fatal: SKILL.md not found')}`);
|
|
406
|
-
process.exit(1);
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
const fm = parseSkillMdFrontmatter(skillMdPath);
|
|
410
|
-
if (!fm || Object.keys(fm).length === 0) {
|
|
411
|
-
console.log(`${chalk.red('โ SKILL.md missing or invalid frontmatter')}`);
|
|
412
|
-
process.exit(1);
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
const v = new Validator();
|
|
416
|
-
const result = v.validate(fm, skillSchema);
|
|
417
|
-
if (fm.category && fm.tags && fm.tags.length > 0 && fm.tags[0] !== fm.category) {
|
|
418
|
-
result.errors.push({ stack: `tags[0] must equal category name '${fm.category}'` });
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
if (!result.valid || result.errors.length > 0) {
|
|
422
|
-
console.error(chalk.red('โ Validation failed: SKILL.md has errors:'));
|
|
423
|
-
result.errors.forEach(err => console.error(` - ${err.stack.replace('instance.', '')}`));
|
|
424
|
-
process.exit(1);
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
console.log(chalk.green('โ Local validation passed.'));
|
|
428
|
-
|
|
429
|
-
// 2. Prepare tarball
|
|
430
|
-
const tarFilename = `${fm.name}-${fm.version}.tar.gz`;
|
|
431
|
-
const tmpDir = require('os').tmpdir();
|
|
432
|
-
const tarPath = path.join(tmpDir, tarFilename);
|
|
433
|
-
|
|
434
|
-
console.log(chalk.dim(`๐ฆ Creating package archive...`));
|
|
435
|
-
try {
|
|
436
|
-
// -C changes to directory, . archives contents
|
|
437
|
-
// COPYFILE_DISABLE=1 prevents macOS tar from including ._* extended attribute files
|
|
438
|
-
execSync(`tar -czf "${tarPath}" -C "${skillDir}" .`, {
|
|
439
|
-
env: { ...process.env, COPYFILE_DISABLE: '1' }
|
|
440
|
-
});
|
|
441
|
-
} catch (e) {
|
|
442
|
-
console.error(chalk.red('โ Failed to create tar archive.'));
|
|
443
|
-
process.exit(1);
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
// 3. Prepare Form Data
|
|
447
|
-
const form = new FormData();
|
|
448
|
-
form.append('package', fs.createReadStream(tarPath), {
|
|
449
|
-
filename: tarFilename,
|
|
450
|
-
contentType: 'application/gzip'
|
|
451
|
-
});
|
|
452
|
-
form.append('metadata', JSON.stringify(fm));
|
|
453
|
-
|
|
454
|
-
if (options.update) {
|
|
455
|
-
form.append('is_update', 'true');
|
|
456
|
-
console.log(chalk.yellow(`โ Uploading as an UPDATE (Version: ${fm.version}). Ensure the version number has been bumped!`));
|
|
457
|
-
} else {
|
|
458
|
-
console.log(chalk.cyan(`โจ Uploading as a NEW skill (Version: ${fm.version}).`));
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
const serverUrl = getApiBase(options);
|
|
462
|
-
const uploadUrl = `${serverUrl}/skills/upload`;
|
|
463
|
-
|
|
464
|
-
const fetchOptions = {
|
|
465
|
-
method: 'POST',
|
|
466
|
-
body: form,
|
|
467
|
-
headers: form.getHeaders()
|
|
468
|
-
};
|
|
469
|
-
|
|
470
|
-
if (options.token) {
|
|
471
|
-
fetchOptions.headers['Authorization'] = `Bearer ${options.token}`;
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
console.log(chalk.dim(`\n๐ก Uploading to ${uploadUrl}...`));
|
|
475
|
-
|
|
476
|
-
let fetchFn = require('node-fetch');
|
|
477
|
-
if (typeof fetchFn !== 'function' && fetchFn.default) {
|
|
478
|
-
fetchFn = fetchFn.default;
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
try {
|
|
482
|
-
const response = await fetchFn(uploadUrl, fetchOptions);
|
|
483
|
-
if (!response.ok) {
|
|
484
|
-
let errorMsg = response.statusText;
|
|
485
|
-
try {
|
|
486
|
-
const errBody = await response.json();
|
|
487
|
-
if (errBody.error || errBody.message) errorMsg = errBody.error || errBody.message;
|
|
488
|
-
} catch (e) { }
|
|
489
|
-
throw new Error(`HTTP ${response.status}: ${errorMsg}`);
|
|
490
|
-
}
|
|
491
|
-
console.log(`\n${chalk.green('โ
Publish successful!')}`);
|
|
492
|
-
} catch (error) {
|
|
493
|
-
console.error(chalk.red(`\nโ Publish failed.`));
|
|
494
|
-
console.error(chalk.red(` ${error.message}`));
|
|
495
|
-
if (!options.update && error.message.includes('already exists')) {
|
|
496
|
-
console.log(chalk.yellow(`\n๐ก If you intended to update an existing skill, use the --update flag.`));
|
|
497
|
-
}
|
|
498
|
-
process.exit(1);
|
|
499
|
-
} finally {
|
|
500
|
-
if (fs.existsSync(tarPath)) {
|
|
501
|
-
fs.unlinkSync(tarPath);
|
|
502
|
-
}
|
|
503
|
-
}
|
|
193
|
+
function generateZshCompletion() {
|
|
194
|
+
const pairs = COMMANDS.map(c => `'${c.name}:${c.desc}'`).join('\n ');
|
|
195
|
+
return `
|
|
196
|
+
# skill-os zsh completion
|
|
197
|
+
_skill_os() {
|
|
198
|
+
local -a commands
|
|
199
|
+
commands=(
|
|
200
|
+
${pairs}
|
|
201
|
+
)
|
|
202
|
+
_describe 'skill-os commands' commands
|
|
504
203
|
}
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
// Validation Helpers
|
|
508
|
-
// ----------------------------------------------------------------------
|
|
509
|
-
|
|
510
|
-
const { Validator } = require('jsonschema');
|
|
511
|
-
|
|
512
|
-
const skillSchema = {
|
|
513
|
-
id: "/SkillMetadata",
|
|
514
|
-
type: "object",
|
|
515
|
-
properties: {
|
|
516
|
-
name: { type: "string", minLength: 1 },
|
|
517
|
-
version: { type: ["string", "number"] },
|
|
518
|
-
description: { type: "string" },
|
|
519
|
-
layer: { enum: ["core", "system", "runtime", "application"] },
|
|
520
|
-
lifecycle: { enum: ["production", "maintenance", "operations", "usage", "meta"] },
|
|
521
|
-
category: { type: ["string", "null"] },
|
|
522
|
-
tags: { type: "array", items: { type: "string" } },
|
|
523
|
-
status: { enum: ["stable", "beta", "placeholder"] },
|
|
524
|
-
dependencies: { type: "array", items: { type: "string" } },
|
|
525
|
-
permissions: {
|
|
526
|
-
type: "object",
|
|
527
|
-
properties: {
|
|
528
|
-
requires_root: { type: "boolean" },
|
|
529
|
-
dangerous_operations: { type: "array", items: { type: "string" } }
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
},
|
|
533
|
-
required: ["name", "version", "layer", "lifecycle", "status"]
|
|
534
|
-
};
|
|
535
|
-
|
|
536
|
-
function validateDirectoryStructure(skillDir) {
|
|
537
|
-
const errors = [];
|
|
538
|
-
if (!fs.existsSync(skillDir)) {
|
|
539
|
-
return { isValid: false, errors: ["Directory does not exist"] };
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
const skillMd = path.join(skillDir, 'SKILL.md');
|
|
543
|
-
if (!fs.existsSync(skillMd)) {
|
|
544
|
-
errors.push("Missing SKILL.md file");
|
|
545
|
-
} else {
|
|
546
|
-
const content = fs.readFileSync(skillMd, 'utf8');
|
|
547
|
-
if (!content.includes('---')) {
|
|
548
|
-
errors.push("SKILL.md missing frontmatter");
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
return { isValid: errors.length === 0, errors };
|
|
204
|
+
compdef _skill_os skill-os
|
|
205
|
+
`.trim();
|
|
553
206
|
}
|
|
554
207
|
|
|
555
|
-
function
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
console.log(`\n${chalk.bold(`๐ Validating Skill: ${skillPath}`)}`);
|
|
560
|
-
console.log(`${chalk.dim('โ'.repeat(50))}\n`);
|
|
561
|
-
|
|
562
|
-
const { isValid: isDirValid, errors: dirErrors } = validateDirectoryStructure(skillDir);
|
|
563
|
-
if (isDirValid) {
|
|
564
|
-
console.log(`${chalk.green('โ Directory structure valid')}`);
|
|
565
|
-
} else {
|
|
566
|
-
console.log(`${chalk.red('โ Directory structure errors:')}`);
|
|
567
|
-
dirErrors.forEach(err => console.log(` - ${err}`));
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
if (!fs.existsSync(skillMdPath)) {
|
|
571
|
-
console.log(`${chalk.red('โ Fatal: SKILL.md not found')}`);
|
|
572
|
-
process.exit(1);
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
const fm = parseSkillMdFrontmatter(skillMdPath);
|
|
576
|
-
|
|
577
|
-
// Check if empty frontmatter
|
|
578
|
-
if (!fm || Object.keys(fm).length === 0) {
|
|
579
|
-
console.log(`${chalk.red('โ SKILL.md missing or invalid frontmatter')}`);
|
|
580
|
-
process.exit(1);
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
const v = new Validator();
|
|
584
|
-
const result = v.validate(fm, skillSchema);
|
|
585
|
-
|
|
586
|
-
// Custom tag validation: first tag MUST be category
|
|
587
|
-
if (fm.category && fm.tags && fm.tags.length > 0) {
|
|
588
|
-
if (fm.tags[0] !== fm.category) {
|
|
589
|
-
result.errors.push({ stack: `tags[0] must equal category name '${fm.category}'` });
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
if (result.valid && result.errors.length === 0) {
|
|
594
|
-
console.log(`${chalk.green('โ SKILL.md is spec compliant')}`);
|
|
595
|
-
console.log(`\n${chalk.bold('๐ Parsed Metadata:')}`);
|
|
596
|
-
console.log(` name: ${fm.name}`);
|
|
597
|
-
console.log(` version: ${fm.version}`);
|
|
598
|
-
console.log(` layer: ${fm.layer}`);
|
|
599
|
-
console.log(` lifecycle: ${fm.lifecycle}`);
|
|
600
|
-
console.log(` category: ${fm.category || '(missing)'}`);
|
|
601
|
-
console.log(` tags: ${JSON.stringify(fm.tags || [])}`);
|
|
602
|
-
if (fm.permissions) {
|
|
603
|
-
console.log(` requires_root: ${fm.permissions.requires_root}`);
|
|
604
|
-
}
|
|
605
|
-
} else {
|
|
606
|
-
console.log(`${chalk.red('โ SKILL.md validation errors:')}`);
|
|
607
|
-
result.errors.forEach(err => console.log(` - ${err.stack.replace('instance.', '')}`));
|
|
608
|
-
process.exit(1);
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
console.log(`\n${chalk.green('โ
Validation passed!')}\n`);
|
|
208
|
+
function generateFishCompletion() {
|
|
209
|
+
return COMMANDS.map(c =>
|
|
210
|
+
`complete -c skill-os -n "__fish_use_subcommand" -a "${c.name}" -d "${c.desc}"`
|
|
211
|
+
).join('\n');
|
|
612
212
|
}
|
|
613
213
|
|
|
614
|
-
//
|
|
615
|
-
// CLI Setup
|
|
616
|
-
// ----------------------------------------------------------------------
|
|
617
|
-
|
|
618
|
-
program
|
|
619
|
-
.name('skill-os')
|
|
620
|
-
.description('Skill-OS CLI');
|
|
621
|
-
|
|
622
|
-
program.command('list')
|
|
623
|
-
.description('List all available skills from the remote registry')
|
|
624
|
-
.option('--url <url>', 'Override the base URL for the registry', DEFAULT_API_BASE)
|
|
625
|
-
.action(cmdList);
|
|
626
|
-
|
|
627
|
-
program.command('search')
|
|
628
|
-
.description('Search for skills in the remote registry')
|
|
629
|
-
.argument('<query>', 'Search query')
|
|
630
|
-
.option('--url <url>', 'Override the base URL for the registry', DEFAULT_API_BASE)
|
|
631
|
-
.action(cmdSearch);
|
|
632
|
-
|
|
633
|
-
program.command('info')
|
|
634
|
-
.description('Show skill details from the remote registry')
|
|
635
|
-
.argument('<path>', 'Skill path (e.g., package/rpm_search)')
|
|
636
|
-
.option('--url <url>', 'Override the base URL for the registry', DEFAULT_API_BASE)
|
|
637
|
-
.action(cmdInfo);
|
|
638
|
-
|
|
639
|
-
program.command('create')
|
|
640
|
-
.description('Create a new skill scaffold locally')
|
|
641
|
-
.argument('<path>', 'Skill path to create (e.g., system/migration)')
|
|
642
|
-
.option('-f, --force', 'Force overwrite if exists', false)
|
|
643
|
-
.action(cmdCreate);
|
|
644
|
-
|
|
645
|
-
program.command('download')
|
|
646
|
-
.description('Download and extract a skill package from the remote registry')
|
|
647
|
-
.argument('<skill_path>', 'Path of the skill to download (e.g., core/kernel/kernel-info)')
|
|
648
|
-
.option('-t, --target <dir>', 'Target directory to extract into (defaults to current dir)')
|
|
649
|
-
.option('--platform <platform>', 'Specify the platform (e.g., qoder will download to .qoder/skills/)')
|
|
650
|
-
.option('--url <url>', 'Override the base URL for the registry', DEFAULT_API_BASE)
|
|
651
|
-
.action(cmdDownload);
|
|
652
|
-
|
|
653
|
-
program.command('upload')
|
|
654
|
-
.alias('publish')
|
|
655
|
-
.description('Upload a local skill to the remote registry')
|
|
656
|
-
.argument('<path>', 'Skill path to upload (e.g., system/migration)')
|
|
657
|
-
.option('-u, --update', 'Publish as an update to an existing skill', false)
|
|
658
|
-
.option('--token <token>', 'Authentication token for the remote registry')
|
|
659
|
-
.option('--url <url>', 'Override the base URL for the registry', DEFAULT_API_BASE)
|
|
660
|
-
.action(cmdUpload);
|
|
661
|
-
|
|
662
|
-
program.command('validate')
|
|
663
|
-
.description('Validate a skill against spec')
|
|
664
|
-
.argument('<path>', 'Skill path to validate (e.g., system/security/cve-repair)')
|
|
665
|
-
.action(cmdValidate);
|
|
214
|
+
// โโ ๅฏๅจ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
666
215
|
|
|
667
216
|
program.parse(process.argv);
|