skill-os 0.1.2 โ 0.1.5
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 -621
package/skill-os.js
CHANGED
|
@@ -1,657 +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');
|
|
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
|
+
*/
|
|
53
13
|
|
|
54
|
-
|
|
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
|
-
}
|
|
14
|
+
const { program } = require('commander');
|
|
89
15
|
|
|
90
|
-
|
|
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');
|
|
91
21
|
|
|
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
|
-
}
|
|
22
|
+
// โโ ็ๆฌ & ๆ่ฟฐ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
104
23
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
24
|
+
program
|
|
25
|
+
.name('skill-os')
|
|
26
|
+
.description('Skill-OS CLI โ AI-Native OS ๆ่ฝ็ฎก็ๅนณๅฐ')
|
|
27
|
+
.version('0.1.4');
|
|
108
28
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
29
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
30
|
+
// ่ฎค่ฏๅฝไปค
|
|
31
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
112
32
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
}
|
|
33
|
+
program.command('login')
|
|
34
|
+
.description('็ปๅฝ Skill-OS ๆณจๅไธญๅฟ๏ผ้่ฟๆต่งๅจ BUC SSO ่ฎค่ฏ๏ผ')
|
|
35
|
+
.option('--url <url>', 'ๆๅฎๆๅก็ซฏๅฐๅ', DEFAULT_DAILY_API_BASE)
|
|
36
|
+
.action(cmdLogin);
|
|
125
37
|
|
|
126
|
-
|
|
127
|
-
|
|
38
|
+
program.command('logout')
|
|
39
|
+
.description('ๆณจ้็ปๅฝ๏ผๆธ
้คๆฌๅฐๅญๆฎ')
|
|
40
|
+
.option('--url <url>', 'ๆๅฎๆๅก็ซฏๅฐๅ', DEFAULT_DAILY_API_BASE)
|
|
41
|
+
.action(cmdLogout);
|
|
128
42
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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');
|
|
43
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
44
|
+
// ่ฟ็จๆณจๅไธญๅฟๅฝไปค (Remote)
|
|
45
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
134
46
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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);
|
|
138
55
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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);
|
|
142
65
|
|
|
143
|
-
|
|
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);
|
|
144
75
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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);
|
|
150
87
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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);
|
|
158
99
|
|
|
159
|
-
|
|
160
|
-
|
|
100
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
101
|
+
// ๆฌๅฐๅผๅๅฝไปค (Local)
|
|
102
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
161
103
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
104
|
+
program.command('create')
|
|
105
|
+
.alias('new')
|
|
106
|
+
.description('ๅๅปบๆ่ฝ่ๆๆถ๏ผ็ๆ SKILL.md ๆจกๆฟ๏ผ')
|
|
107
|
+
.argument('<ๆ่ฝ่ทฏๅพ>', 'ๆ่ฝ่ทฏๅพ๏ผๆ ผๅผ๏ผ<ๅฑ็บง>/<ๅ็ฑป>/<ๅ็งฐ>๏ผๅฆ system/security/cve-repair')
|
|
108
|
+
.option('-f, --force', 'ๅฆๆ็ฎๅฝๅทฒๅญๅจๅๅผบๅถ่ฆ็')
|
|
109
|
+
.action(cmdCreate);
|
|
165
110
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
111
|
+
program.command('validate')
|
|
112
|
+
.alias('check')
|
|
113
|
+
.description('ๆ ก้ชๆ่ฝๆฏๅฆ็ฌฆๅ Skill-OS ่ง่')
|
|
114
|
+
.argument('<ๆ่ฝ็ฎๅฝ>', '่ฆๆ ก้ช็ๆ่ฝ็ฎๅฝ่ทฏๅพ๏ผๅฆ skills/system/security/cve-repair')
|
|
115
|
+
.action(cmdValidate);
|
|
169
116
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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());
|
|
175
137
|
}
|
|
176
|
-
}
|
|
177
138
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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
|
+
};
|
|
182
144
|
|
|
183
|
-
|
|
184
|
-
|
|
145
|
+
process.stderr.write(`\n# ๅฎ่ฃ
ๆ็คบ (ๆทปๅ ๅฐ shell ้
็ฝฎๆไปถ):\n# ${installHint[shell] || installHint.bash}\n`);
|
|
146
|
+
});
|
|
185
147
|
|
|
186
|
-
|
|
187
|
-
const skillPath = info.path || info.name; // Fallback to name if path isn't provided separately
|
|
188
|
-
const icon = formatLayerIcon(skillPath);
|
|
189
|
-
const status = formatStatus(info.status);
|
|
190
|
-
const version = info.version || '?';
|
|
191
|
-
const desc = info.description || '';
|
|
148
|
+
// โโ ่กฅๅ
จ่ๆฌ็ๆ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
192
149
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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';
|
|
197
155
|
}
|
|
198
156
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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
|
|
227
188
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
function cmdCreate(skillPath, options) {
|
|
232
|
-
const targetDir = path.resolve(skillPath);
|
|
233
|
-
|
|
234
|
-
if (fs.existsSync(targetDir)) {
|
|
235
|
-
if (!options.force) {
|
|
236
|
-
console.error(chalk.red(`Error: Directory already exists: ${targetDir}`));
|
|
237
|
-
console.log(chalk.dim("Use --force to overwrite"));
|
|
238
|
-
process.exit(1);
|
|
239
|
-
}
|
|
240
|
-
fs.rmSync(targetDir, { recursive: true, force: true });
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
fs.mkdirSync(targetDir, { recursive: true });
|
|
244
|
-
|
|
245
|
-
const parts = skillPath.split('/');
|
|
246
|
-
if (parts.length < 3) {
|
|
247
|
-
console.error(chalk.red("Error: Invalid path format"));
|
|
248
|
-
console.log(chalk.dim("Expected: <layer>/<category>/<skill>"));
|
|
249
|
-
console.log(chalk.dim("Example: system/security/cve-repair"));
|
|
250
|
-
process.exit(1);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
const layer = parts[0];
|
|
254
|
-
const category = parts[1];
|
|
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');
|
|
313
|
-
|
|
314
|
-
console.log(`\n${chalk.green('โ Created skill scaffold:')}`);
|
|
315
|
-
console.log(` ${chalk.cyan(targetDir)}`);
|
|
316
|
-
console.log(" โโโ SKILL.md");
|
|
317
|
-
|
|
318
|
-
console.log(`\n${chalk.bold('๐ Next Steps:')}`);
|
|
319
|
-
console.log(` 1. Edit ${chalk.cyan(skillMdPath)}`);
|
|
320
|
-
console.log(` 2. Validate: ${chalk.dim(`skill-os validate ${skillPath}`)}`);
|
|
321
|
-
console.log(` 3. Sync: ${chalk.dim(`skill-os sync ${skillPath}`)}`);
|
|
189
|
+
complete -F _skill_os_completions skill-os
|
|
190
|
+
`.trim();
|
|
322
191
|
}
|
|
323
192
|
|
|
324
|
-
function
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
rawTarget = path.join(process.cwd(), `.${options.platform}`, "skills");
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
const targetDir = path.resolve(rawTarget);
|
|
338
|
-
fs.mkdirSync(targetDir, { recursive: true });
|
|
339
|
-
|
|
340
|
-
// 2. Determine API source
|
|
341
|
-
const serverUrl = getApiBase(options);
|
|
342
|
-
const downloadUrl = `${serverUrl}/skills/${skillPath}/download`;
|
|
343
|
-
|
|
344
|
-
console.log(`\n${chalk.bold(`๐ฅ Downloading and extracting ${skillPath}...`)}`);
|
|
345
|
-
console.log(` Source: ${chalk.cyan(downloadUrl)}`);
|
|
346
|
-
console.log(` Target: ${chalk.cyan(targetDir)}\n`);
|
|
347
|
-
|
|
348
|
-
try {
|
|
349
|
-
// Stream the curl output directly into tar to extract the directory structure
|
|
350
|
-
// -s: silent curl, -L: follow redirects
|
|
351
|
-
// tar -xzf -: extract gzipped tar from stdin
|
|
352
|
-
execSync(`curl -s -L "${downloadUrl}" | tar -xzf -`, { cwd: targetDir, stdio: 'inherit' });
|
|
353
|
-
console.log(`\n${chalk.green(`โ Successfully downloaded and extracted to: ${targetDir}`)}`);
|
|
354
|
-
} catch (e) {
|
|
355
|
-
console.error(`\n${chalk.red(`โ Failed to download. The API may have returned an error instead of a tarball.`)}`);
|
|
356
|
-
process.exit(1);
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
// ----------------------------------------------------------------------
|
|
361
|
-
// Publishing / Uploading
|
|
362
|
-
// ----------------------------------------------------------------------
|
|
363
|
-
|
|
364
|
-
const FormData = require('form-data');
|
|
365
|
-
|
|
366
|
-
function parseSkillMdFrontmatter(skillMdPath) {
|
|
367
|
-
if (!fs.existsSync(skillMdPath)) return {};
|
|
368
|
-
const content = fs.readFileSync(skillMdPath, 'utf8');
|
|
369
|
-
const match = content.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
370
|
-
if (!match) return {};
|
|
371
|
-
|
|
372
|
-
try {
|
|
373
|
-
return yaml.load(match[1]) || {};
|
|
374
|
-
} catch (e) {
|
|
375
|
-
return {};
|
|
376
|
-
}
|
|
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
|
|
377
203
|
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
const skillDir = path.resolve(skillPath);
|
|
381
|
-
const skillMdPath = path.join(skillDir, 'SKILL.md');
|
|
382
|
-
|
|
383
|
-
console.log(`\n${chalk.bold(`๐ Publishing Skill: ${skillPath}`)}`);
|
|
384
|
-
console.log(`${chalk.dim('โ'.repeat(50))}\n`);
|
|
385
|
-
|
|
386
|
-
// 1. Validation (Requires hoisted functions)
|
|
387
|
-
const { isValid: isDirValid, errors: dirErrors } = validateDirectoryStructure(skillDir);
|
|
388
|
-
if (!isDirValid) {
|
|
389
|
-
console.error(chalk.red('โ Validation failed: Directory structure errors:'));
|
|
390
|
-
dirErrors.forEach(err => console.error(` - ${err}`));
|
|
391
|
-
process.exit(1);
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
if (!fs.existsSync(skillMdPath)) {
|
|
395
|
-
console.log(`${chalk.red('โ Fatal: SKILL.md not found')}`);
|
|
396
|
-
process.exit(1);
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
const fm = parseSkillMdFrontmatter(skillMdPath);
|
|
400
|
-
if (!fm || Object.keys(fm).length === 0) {
|
|
401
|
-
console.log(`${chalk.red('โ SKILL.md missing or invalid frontmatter')}`);
|
|
402
|
-
process.exit(1);
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
const v = new Validator();
|
|
406
|
-
const result = v.validate(fm, skillSchema);
|
|
407
|
-
if (fm.category && fm.tags && fm.tags.length > 0 && fm.tags[0] !== fm.category) {
|
|
408
|
-
result.errors.push({ stack: `tags[0] must equal category name '${fm.category}'` });
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
if (!result.valid || result.errors.length > 0) {
|
|
412
|
-
console.error(chalk.red('โ Validation failed: SKILL.md has errors:'));
|
|
413
|
-
result.errors.forEach(err => console.error(` - ${err.stack.replace('instance.', '')}`));
|
|
414
|
-
process.exit(1);
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
console.log(chalk.green('โ Local validation passed.'));
|
|
418
|
-
|
|
419
|
-
// 2. Prepare tarball
|
|
420
|
-
const tarFilename = `${fm.name}-${fm.version}.tar.gz`;
|
|
421
|
-
const tmpDir = require('os').tmpdir();
|
|
422
|
-
const tarPath = path.join(tmpDir, tarFilename);
|
|
423
|
-
|
|
424
|
-
console.log(chalk.dim(`๐ฆ Creating package archive...`));
|
|
425
|
-
try {
|
|
426
|
-
// -C changes to directory, . archives contents
|
|
427
|
-
// COPYFILE_DISABLE=1 prevents macOS tar from including ._* extended attribute files
|
|
428
|
-
execSync(`tar -czf "${tarPath}" -C "${skillDir}" .`, {
|
|
429
|
-
env: { ...process.env, COPYFILE_DISABLE: '1' }
|
|
430
|
-
});
|
|
431
|
-
} catch (e) {
|
|
432
|
-
console.error(chalk.red('โ Failed to create tar archive.'));
|
|
433
|
-
process.exit(1);
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
// 3. Prepare Form Data
|
|
437
|
-
const form = new FormData();
|
|
438
|
-
form.append('package', fs.createReadStream(tarPath), {
|
|
439
|
-
filename: tarFilename,
|
|
440
|
-
contentType: 'application/gzip'
|
|
441
|
-
});
|
|
442
|
-
form.append('metadata', JSON.stringify(fm));
|
|
443
|
-
|
|
444
|
-
if (options.update) {
|
|
445
|
-
form.append('is_update', 'true');
|
|
446
|
-
console.log(chalk.yellow(`โ Uploading as an UPDATE (Version: ${fm.version}). Ensure the version number has been bumped!`));
|
|
447
|
-
} else {
|
|
448
|
-
console.log(chalk.cyan(`โจ Uploading as a NEW skill (Version: ${fm.version}).`));
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
const serverUrl = getApiBase(options);
|
|
452
|
-
const uploadUrl = `${serverUrl}/skills/upload`;
|
|
453
|
-
|
|
454
|
-
const fetchOptions = {
|
|
455
|
-
method: 'POST',
|
|
456
|
-
body: form,
|
|
457
|
-
headers: form.getHeaders()
|
|
458
|
-
};
|
|
459
|
-
|
|
460
|
-
if (options.token) {
|
|
461
|
-
fetchOptions.headers['Authorization'] = `Bearer ${options.token}`;
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
console.log(chalk.dim(`\n๐ก Uploading to ${uploadUrl}...`));
|
|
465
|
-
|
|
466
|
-
let fetchFn = require('node-fetch');
|
|
467
|
-
if (typeof fetchFn !== 'function' && fetchFn.default) {
|
|
468
|
-
fetchFn = fetchFn.default;
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
try {
|
|
472
|
-
const response = await fetchFn(uploadUrl, fetchOptions);
|
|
473
|
-
if (!response.ok) {
|
|
474
|
-
let errorMsg = response.statusText;
|
|
475
|
-
try {
|
|
476
|
-
const errBody = await response.json();
|
|
477
|
-
if (errBody.error || errBody.message) errorMsg = errBody.error || errBody.message;
|
|
478
|
-
} catch (e) { }
|
|
479
|
-
throw new Error(`HTTP ${response.status}: ${errorMsg}`);
|
|
480
|
-
}
|
|
481
|
-
console.log(`\n${chalk.green('โ
Publish successful!')}`);
|
|
482
|
-
} catch (error) {
|
|
483
|
-
console.error(chalk.red(`\nโ Publish failed.`));
|
|
484
|
-
console.error(chalk.red(` ${error.message}`));
|
|
485
|
-
if (!options.update && error.message.includes('already exists')) {
|
|
486
|
-
console.log(chalk.yellow(`\n๐ก If you intended to update an existing skill, use the --update flag.`));
|
|
487
|
-
}
|
|
488
|
-
process.exit(1);
|
|
489
|
-
} finally {
|
|
490
|
-
if (fs.existsSync(tarPath)) {
|
|
491
|
-
fs.unlinkSync(tarPath);
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
// ----------------------------------------------------------------------
|
|
497
|
-
// Validation Helpers
|
|
498
|
-
// ----------------------------------------------------------------------
|
|
499
|
-
|
|
500
|
-
const { Validator } = require('jsonschema');
|
|
501
|
-
|
|
502
|
-
const skillSchema = {
|
|
503
|
-
id: "/SkillMetadata",
|
|
504
|
-
type: "object",
|
|
505
|
-
properties: {
|
|
506
|
-
name: { type: "string", minLength: 1 },
|
|
507
|
-
version: { type: ["string", "number"] },
|
|
508
|
-
description: { type: "string" },
|
|
509
|
-
layer: { enum: ["core", "system", "runtime", "application"] },
|
|
510
|
-
lifecycle: { enum: ["production", "maintenance", "operations", "usage", "meta"] },
|
|
511
|
-
category: { type: ["string", "null"] },
|
|
512
|
-
tags: { type: "array", items: { type: "string" } },
|
|
513
|
-
status: { enum: ["stable", "beta", "placeholder"] },
|
|
514
|
-
dependencies: { type: "array", items: { type: "string" } },
|
|
515
|
-
permissions: {
|
|
516
|
-
type: "object",
|
|
517
|
-
properties: {
|
|
518
|
-
requires_root: { type: "boolean" },
|
|
519
|
-
dangerous_operations: { type: "array", items: { type: "string" } }
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
},
|
|
523
|
-
required: ["name", "version", "layer", "lifecycle", "status"]
|
|
524
|
-
};
|
|
525
|
-
|
|
526
|
-
function validateDirectoryStructure(skillDir) {
|
|
527
|
-
const errors = [];
|
|
528
|
-
if (!fs.existsSync(skillDir)) {
|
|
529
|
-
return { isValid: false, errors: ["Directory does not exist"] };
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
const skillMd = path.join(skillDir, 'SKILL.md');
|
|
533
|
-
if (!fs.existsSync(skillMd)) {
|
|
534
|
-
errors.push("Missing SKILL.md file");
|
|
535
|
-
} else {
|
|
536
|
-
const content = fs.readFileSync(skillMd, 'utf8');
|
|
537
|
-
if (!content.includes('---')) {
|
|
538
|
-
errors.push("SKILL.md missing frontmatter");
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
return { isValid: errors.length === 0, errors };
|
|
204
|
+
compdef _skill_os skill-os
|
|
205
|
+
`.trim();
|
|
543
206
|
}
|
|
544
207
|
|
|
545
|
-
function
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
console.log(`\n${chalk.bold(`๐ Validating Skill: ${skillPath}`)}`);
|
|
550
|
-
console.log(`${chalk.dim('โ'.repeat(50))}\n`);
|
|
551
|
-
|
|
552
|
-
const { isValid: isDirValid, errors: dirErrors } = validateDirectoryStructure(skillDir);
|
|
553
|
-
if (isDirValid) {
|
|
554
|
-
console.log(`${chalk.green('โ Directory structure valid')}`);
|
|
555
|
-
} else {
|
|
556
|
-
console.log(`${chalk.red('โ Directory structure errors:')}`);
|
|
557
|
-
dirErrors.forEach(err => console.log(` - ${err}`));
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
if (!fs.existsSync(skillMdPath)) {
|
|
561
|
-
console.log(`${chalk.red('โ Fatal: SKILL.md not found')}`);
|
|
562
|
-
process.exit(1);
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
const fm = parseSkillMdFrontmatter(skillMdPath);
|
|
566
|
-
|
|
567
|
-
// Check if empty frontmatter
|
|
568
|
-
if (!fm || Object.keys(fm).length === 0) {
|
|
569
|
-
console.log(`${chalk.red('โ SKILL.md missing or invalid frontmatter')}`);
|
|
570
|
-
process.exit(1);
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
const v = new Validator();
|
|
574
|
-
const result = v.validate(fm, skillSchema);
|
|
575
|
-
|
|
576
|
-
// Custom tag validation: first tag MUST be category
|
|
577
|
-
if (fm.category && fm.tags && fm.tags.length > 0) {
|
|
578
|
-
if (fm.tags[0] !== fm.category) {
|
|
579
|
-
result.errors.push({ stack: `tags[0] must equal category name '${fm.category}'` });
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
if (result.valid && result.errors.length === 0) {
|
|
584
|
-
console.log(`${chalk.green('โ SKILL.md is spec compliant')}`);
|
|
585
|
-
console.log(`\n${chalk.bold('๐ Parsed Metadata:')}`);
|
|
586
|
-
console.log(` name: ${fm.name}`);
|
|
587
|
-
console.log(` version: ${fm.version}`);
|
|
588
|
-
console.log(` layer: ${fm.layer}`);
|
|
589
|
-
console.log(` lifecycle: ${fm.lifecycle}`);
|
|
590
|
-
console.log(` category: ${fm.category || '(missing)'}`);
|
|
591
|
-
console.log(` tags: ${JSON.stringify(fm.tags || [])}`);
|
|
592
|
-
if (fm.permissions) {
|
|
593
|
-
console.log(` requires_root: ${fm.permissions.requires_root}`);
|
|
594
|
-
}
|
|
595
|
-
} else {
|
|
596
|
-
console.log(`${chalk.red('โ SKILL.md validation errors:')}`);
|
|
597
|
-
result.errors.forEach(err => console.log(` - ${err.stack.replace('instance.', '')}`));
|
|
598
|
-
process.exit(1);
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
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');
|
|
602
212
|
}
|
|
603
213
|
|
|
604
|
-
//
|
|
605
|
-
// CLI Setup
|
|
606
|
-
// ----------------------------------------------------------------------
|
|
607
|
-
|
|
608
|
-
program
|
|
609
|
-
.name('skill-os')
|
|
610
|
-
.description('Skill-OS CLI');
|
|
611
|
-
|
|
612
|
-
program.command('list')
|
|
613
|
-
.description('List all available skills from the remote registry')
|
|
614
|
-
.option('--url <url>', 'Override the base URL for the registry', DEFAULT_API_BASE)
|
|
615
|
-
.action(cmdList);
|
|
616
|
-
|
|
617
|
-
program.command('search')
|
|
618
|
-
.description('Search for skills in the remote registry')
|
|
619
|
-
.argument('<query>', 'Search query')
|
|
620
|
-
.option('--url <url>', 'Override the base URL for the registry', DEFAULT_API_BASE)
|
|
621
|
-
.action(cmdSearch);
|
|
622
|
-
|
|
623
|
-
program.command('info')
|
|
624
|
-
.description('Show skill details from the remote registry')
|
|
625
|
-
.argument('<path>', 'Skill path (e.g., package/rpm_search)')
|
|
626
|
-
.option('--url <url>', 'Override the base URL for the registry', DEFAULT_API_BASE)
|
|
627
|
-
.action(cmdInfo);
|
|
628
|
-
|
|
629
|
-
program.command('create')
|
|
630
|
-
.description('Create a new skill scaffold locally')
|
|
631
|
-
.argument('<path>', 'Skill path to create (e.g., system/migration)')
|
|
632
|
-
.option('-f, --force', 'Force overwrite if exists', false)
|
|
633
|
-
.action(cmdCreate);
|
|
634
|
-
|
|
635
|
-
program.command('download')
|
|
636
|
-
.description('Download and extract a skill package from the remote registry')
|
|
637
|
-
.argument('<skill_path>', 'Path of the skill to download (e.g., core/kernel/kernel-info)')
|
|
638
|
-
.option('-t, --target <dir>', 'Target directory to extract into (defaults to current dir)')
|
|
639
|
-
.option('--platform <platform>', 'Specify the platform (e.g., qoder will download to .qoder/skills/)')
|
|
640
|
-
.option('--url <url>', 'Override the base URL for the registry', DEFAULT_API_BASE)
|
|
641
|
-
.action(cmdDownload);
|
|
642
|
-
|
|
643
|
-
program.command('upload')
|
|
644
|
-
.alias('publish')
|
|
645
|
-
.description('Upload a local skill to the remote registry')
|
|
646
|
-
.argument('<path>', 'Skill path to upload (e.g., system/migration)')
|
|
647
|
-
.option('-u, --update', 'Publish as an update to an existing skill', false)
|
|
648
|
-
.option('--token <token>', 'Authentication token for the remote registry')
|
|
649
|
-
.option('--url <url>', 'Override the base URL for the registry', DEFAULT_API_BASE)
|
|
650
|
-
.action(cmdUpload);
|
|
651
|
-
|
|
652
|
-
program.command('validate')
|
|
653
|
-
.description('Validate a skill against spec')
|
|
654
|
-
.argument('<path>', 'Skill path to validate (e.g., system/security/cve-repair)')
|
|
655
|
-
.action(cmdValidate);
|
|
214
|
+
// โโ ๅฏๅจ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
656
215
|
|
|
657
216
|
program.parse(process.argv);
|