@orth/cli 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +192 -0
- package/dist/analytics.d.ts +5 -0
- package/dist/analytics.js +53 -0
- package/dist/api.d.ts +81 -0
- package/dist/api.js +85 -0
- package/dist/commands/account.d.ts +4 -0
- package/dist/commands/account.js +60 -0
- package/dist/commands/api.d.ts +6 -0
- package/dist/commands/api.js +150 -0
- package/dist/commands/apiRequest.d.ts +6 -0
- package/dist/commands/apiRequest.js +36 -0
- package/dist/commands/auth.d.ts +5 -0
- package/dist/commands/auth.js +42 -0
- package/dist/commands/code.d.ts +3 -0
- package/dist/commands/code.js +43 -0
- package/dist/commands/run.d.ts +7 -0
- package/dist/commands/run.js +78 -0
- package/dist/commands/search.d.ts +3 -0
- package/dist/commands/search.js +47 -0
- package/dist/commands/skills.d.ts +28 -0
- package/dist/commands/skills.js +638 -0
- package/dist/config.d.ts +10 -0
- package/dist/config.js +36 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +249 -0
- package/package.json +57 -0
|
@@ -0,0 +1,638 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.skillsListCommand = skillsListCommand;
|
|
40
|
+
exports.skillsSearchCommand = skillsSearchCommand;
|
|
41
|
+
exports.skillsShowCommand = skillsShowCommand;
|
|
42
|
+
exports.skillsCreateCommand = skillsCreateCommand;
|
|
43
|
+
exports.skillsInstallCommand = skillsInstallCommand;
|
|
44
|
+
exports.skillsInitCommand = skillsInitCommand;
|
|
45
|
+
exports.skillsSubmitCommand = skillsSubmitCommand;
|
|
46
|
+
exports.skillsRequestVerificationCommand = skillsRequestVerificationCommand;
|
|
47
|
+
exports.skillsPublishCommand = skillsPublishCommand;
|
|
48
|
+
exports.skillsRequestCommand = skillsRequestCommand;
|
|
49
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
50
|
+
const ora_1 = __importDefault(require("ora"));
|
|
51
|
+
const api_js_1 = require("../api.js");
|
|
52
|
+
const fs = __importStar(require("fs"));
|
|
53
|
+
const path = __importStar(require("path"));
|
|
54
|
+
const os = __importStar(require("os"));
|
|
55
|
+
// Agent skill directories (user-level global skills)
|
|
56
|
+
const AGENT_DIRS = {
|
|
57
|
+
cursor: path.join(os.homedir(), ".cursor", "skills"),
|
|
58
|
+
"claude-code": path.join(os.homedir(), ".claude", "skills"),
|
|
59
|
+
copilot: path.join(os.homedir(), ".github", "skills"),
|
|
60
|
+
windsurf: path.join(os.homedir(), ".codeium", "windsurf", "skills"),
|
|
61
|
+
codex: path.join(os.homedir(), ".agents", "skills"), // Codex uses ~/.agents/skills/ for user-scoped skills
|
|
62
|
+
gemini: path.join(os.homedir(), ".gemini", "skills"),
|
|
63
|
+
openclaw: path.join(os.homedir(), ".openclaw", "skills"),
|
|
64
|
+
};
|
|
65
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
66
|
+
// orth skills list
|
|
67
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
68
|
+
async function skillsListCommand(options) {
|
|
69
|
+
const spinner = (0, ora_1.default)("Loading skills...").start();
|
|
70
|
+
try {
|
|
71
|
+
const limit = parseInt(options.limit, 10);
|
|
72
|
+
const data = await (0, api_js_1.apiRequest)(`/skills?limit=${limit}`);
|
|
73
|
+
spinner.stop();
|
|
74
|
+
if (!data.skills || data.skills.length === 0) {
|
|
75
|
+
console.log(chalk_1.default.yellow("No discoverable skills found."));
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
console.log(chalk_1.default.bold(`\nDiscoverable Skills (${data.total} total):\n`));
|
|
79
|
+
for (const skill of data.skills) {
|
|
80
|
+
const verified = skill.verified ? chalk_1.default.blue(" ✓") : "";
|
|
81
|
+
const installs = chalk_1.default.gray(`(${skill.installCount} installs)`);
|
|
82
|
+
const source = skill.sourceType === "github" && skill.githubOwner
|
|
83
|
+
? chalk_1.default.gray(` [${skill.githubOwner}/${skill.githubRepo}]`)
|
|
84
|
+
: chalk_1.default.gray(" [uploaded]");
|
|
85
|
+
console.log(chalk_1.default.cyan.bold(skill.name) + verified + source + " " + installs);
|
|
86
|
+
if (skill.description) {
|
|
87
|
+
console.log(chalk_1.default.gray(` ${skill.description.slice(0, 100)}${skill.description.length > 100 ? "..." : ""}`));
|
|
88
|
+
}
|
|
89
|
+
if (skill.tags && skill.tags.length > 0) {
|
|
90
|
+
console.log(chalk_1.default.gray(" Tags: ") +
|
|
91
|
+
skill.tags.map((t) => chalk_1.default.magenta(t)).join(", "));
|
|
92
|
+
}
|
|
93
|
+
console.log(chalk_1.default.gray(` Slug: ${skill.slug}`));
|
|
94
|
+
console.log();
|
|
95
|
+
}
|
|
96
|
+
console.log(chalk_1.default.gray("Run 'orth skills show <slug>' to see skill details"));
|
|
97
|
+
console.log(chalk_1.default.gray("Run 'orth skills add <slug>' to add a skill locally"));
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
spinner.stop();
|
|
101
|
+
console.error(chalk_1.default.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
106
|
+
// orth skills search <query>
|
|
107
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
108
|
+
async function skillsSearchCommand(query, options) {
|
|
109
|
+
const spinner = (0, ora_1.default)("Searching skills...").start();
|
|
110
|
+
try {
|
|
111
|
+
const data = await (0, api_js_1.apiRequest)("/skills/search", {
|
|
112
|
+
method: "POST",
|
|
113
|
+
body: { query, limit: parseInt(options.limit, 10) },
|
|
114
|
+
});
|
|
115
|
+
spinner.stop();
|
|
116
|
+
if (!data.results || data.results.length === 0) {
|
|
117
|
+
console.log(chalk_1.default.yellow("No skills found matching your query."));
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
console.log(chalk_1.default.bold(`\nFound ${data.count} skills:\n`));
|
|
121
|
+
for (const skill of data.results) {
|
|
122
|
+
const verified = skill.verified ? chalk_1.default.blue(" ✓") : "";
|
|
123
|
+
const installs = chalk_1.default.gray(`(${skill.installCount} installs)`);
|
|
124
|
+
console.log(chalk_1.default.cyan.bold(skill.name) + verified + " " + installs);
|
|
125
|
+
if (skill.description) {
|
|
126
|
+
console.log(chalk_1.default.gray(` ${skill.description.slice(0, 100)}${skill.description.length > 100 ? "..." : ""}`));
|
|
127
|
+
}
|
|
128
|
+
console.log(chalk_1.default.gray(` Slug: ${skill.slug}`));
|
|
129
|
+
console.log();
|
|
130
|
+
}
|
|
131
|
+
console.log(chalk_1.default.gray("Run 'orth skills show <slug>' for full details"));
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
spinner.stop();
|
|
135
|
+
console.error(chalk_1.default.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
140
|
+
// orth skills show <slug>
|
|
141
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
142
|
+
async function skillsShowCommand(slug) {
|
|
143
|
+
const spinner = (0, ora_1.default)("Loading skill...").start();
|
|
144
|
+
try {
|
|
145
|
+
const data = await (0, api_js_1.apiRequest)(`/skills/${slug}`);
|
|
146
|
+
spinner.stop();
|
|
147
|
+
const skill = data.skill;
|
|
148
|
+
console.log(chalk_1.default.bold(`\n${chalk_1.default.cyan(skill.name)}\n`));
|
|
149
|
+
if (skill.description) {
|
|
150
|
+
console.log(chalk_1.default.white(skill.description));
|
|
151
|
+
console.log();
|
|
152
|
+
}
|
|
153
|
+
// Metadata
|
|
154
|
+
console.log(chalk_1.default.bold("Details:"));
|
|
155
|
+
console.log(chalk_1.default.gray(" Slug: ") + skill.slug);
|
|
156
|
+
console.log(chalk_1.default.gray(" Source: ") +
|
|
157
|
+
(skill.sourceType === "github"
|
|
158
|
+
? `GitHub (${skill.githubOwner}/${skill.githubRepo})`
|
|
159
|
+
: "Uploaded"));
|
|
160
|
+
console.log(chalk_1.default.gray(" Installs: ") + chalk_1.default.green(String(skill.installCount)));
|
|
161
|
+
if (skill.verified) {
|
|
162
|
+
console.log(chalk_1.default.gray(" Verified: ") + chalk_1.default.blue("Yes ✓"));
|
|
163
|
+
}
|
|
164
|
+
if (skill.tags && skill.tags.length > 0) {
|
|
165
|
+
console.log(chalk_1.default.gray(" Tags: ") +
|
|
166
|
+
skill.tags.map((t) => chalk_1.default.magenta(t)).join(", "));
|
|
167
|
+
}
|
|
168
|
+
console.log();
|
|
169
|
+
// Files
|
|
170
|
+
if (skill.files && skill.files.length > 0) {
|
|
171
|
+
console.log(chalk_1.default.bold("Files:"));
|
|
172
|
+
for (const file of skill.files) {
|
|
173
|
+
const primary = file.isPrimary ? chalk_1.default.green(" (primary)") : "";
|
|
174
|
+
console.log(chalk_1.default.gray(" ") + chalk_1.default.white(file.filePath) + primary);
|
|
175
|
+
}
|
|
176
|
+
console.log();
|
|
177
|
+
// Show primary file content preview
|
|
178
|
+
const primaryFile = skill.files.find((f) => f.isPrimary);
|
|
179
|
+
if (primaryFile) {
|
|
180
|
+
console.log(chalk_1.default.bold(`── ${primaryFile.filePath} ──\n`));
|
|
181
|
+
const lines = primaryFile.content.split("\n");
|
|
182
|
+
const preview = lines.slice(0, 30).join("\n");
|
|
183
|
+
console.log(preview);
|
|
184
|
+
if (lines.length > 30) {
|
|
185
|
+
console.log(chalk_1.default.gray(`\n... ${lines.length - 30} more lines`));
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
console.log();
|
|
190
|
+
if (skill.installCommand) {
|
|
191
|
+
console.log(chalk_1.default.bold("Install:"));
|
|
192
|
+
console.log(chalk_1.default.white(` ${skill.installCommand}`));
|
|
193
|
+
}
|
|
194
|
+
console.log(chalk_1.default.gray(`\nRun 'orth skills add ${skill.slug}' to add locally`));
|
|
195
|
+
}
|
|
196
|
+
catch (error) {
|
|
197
|
+
spinner.stop();
|
|
198
|
+
console.error(chalk_1.default.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
|
|
199
|
+
process.exit(1);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
203
|
+
// orth skills create <githubRepo>
|
|
204
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
205
|
+
async function skillsCreateCommand(githubRepo, options) {
|
|
206
|
+
const spinner = (0, ora_1.default)("Creating skill from GitHub...").start();
|
|
207
|
+
try {
|
|
208
|
+
// Parse GitHub URL or owner/repo format
|
|
209
|
+
let githubOwner;
|
|
210
|
+
let githubRepoName;
|
|
211
|
+
const urlMatch = githubRepo.match(/github\.com\/([^/]+)\/([^/\s#?]+)/);
|
|
212
|
+
if (urlMatch) {
|
|
213
|
+
githubOwner = urlMatch[1];
|
|
214
|
+
githubRepoName = urlMatch[2].replace(/\.git$/, "");
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
const parts = githubRepo.split("/");
|
|
218
|
+
if (parts.length < 2) {
|
|
219
|
+
spinner.stop();
|
|
220
|
+
console.error(chalk_1.default.red("Error: Provide a GitHub URL or owner/repo format"));
|
|
221
|
+
process.exit(1);
|
|
222
|
+
}
|
|
223
|
+
githubOwner = parts[0];
|
|
224
|
+
githubRepoName = parts[1];
|
|
225
|
+
}
|
|
226
|
+
const data = await (0, api_js_1.apiRequest)("/skills", {
|
|
227
|
+
method: "POST",
|
|
228
|
+
body: {
|
|
229
|
+
githubOwner,
|
|
230
|
+
githubRepo: githubRepoName,
|
|
231
|
+
githubPath: options.path,
|
|
232
|
+
githubRef: options.ref || "main",
|
|
233
|
+
name: options.name,
|
|
234
|
+
sourceType: "github",
|
|
235
|
+
},
|
|
236
|
+
});
|
|
237
|
+
spinner.stop();
|
|
238
|
+
console.log(chalk_1.default.green(`\n✓ ${data.message}`));
|
|
239
|
+
console.log(chalk_1.default.bold(`\n${data.skill.name}`));
|
|
240
|
+
console.log(chalk_1.default.gray(` Slug: ${data.skill.slug}`));
|
|
241
|
+
if (data.skill.description) {
|
|
242
|
+
console.log(chalk_1.default.gray(` ${data.skill.description}`));
|
|
243
|
+
}
|
|
244
|
+
console.log(chalk_1.default.bold("\nYour skill is on the platform but not yet verified."));
|
|
245
|
+
console.log(chalk_1.default.white(`To request verification: ${chalk_1.default.cyan(`orth skills request-verification ${data.skill.slug}`)}`));
|
|
246
|
+
console.log(chalk_1.default.gray("Once verified, you can toggle discoverability anytime."));
|
|
247
|
+
}
|
|
248
|
+
catch (error) {
|
|
249
|
+
spinner.stop();
|
|
250
|
+
console.error(chalk_1.default.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
|
|
251
|
+
process.exit(1);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
255
|
+
// orth skills add <slug>
|
|
256
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
257
|
+
async function skillsInstallCommand(slug, options) {
|
|
258
|
+
const spinner = (0, ora_1.default)("Fetching skill...").start();
|
|
259
|
+
try {
|
|
260
|
+
const data = await (0, api_js_1.apiRequest)(`/skills/${slug}`);
|
|
261
|
+
const skill = data.skill;
|
|
262
|
+
spinner.stop();
|
|
263
|
+
if (!skill.files || skill.files.length === 0) {
|
|
264
|
+
console.log(chalk_1.default.yellow("This skill has no files to install."));
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
const primaryFile = skill.files.find((f) => f.isPrimary);
|
|
268
|
+
if (!primaryFile) {
|
|
269
|
+
console.log(chalk_1.default.yellow("No primary SKILL.md file found."));
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
// Determine which agents to install for
|
|
273
|
+
if (options.agent && !AGENT_DIRS[options.agent]) {
|
|
274
|
+
spinner.stop();
|
|
275
|
+
console.error(chalk_1.default.red(`Error: Unknown agent '${options.agent}'. Valid agents: ${Object.keys(AGENT_DIRS).join(", ")}`));
|
|
276
|
+
process.exit(1);
|
|
277
|
+
}
|
|
278
|
+
const agents = options.agent
|
|
279
|
+
? { [options.agent]: AGENT_DIRS[options.agent] }
|
|
280
|
+
: AGENT_DIRS;
|
|
281
|
+
const skillDirName = skill.slug.replace(/\//g, "-");
|
|
282
|
+
let installed = 0;
|
|
283
|
+
for (const [agentName, baseDir] of Object.entries(agents)) {
|
|
284
|
+
if (!baseDir)
|
|
285
|
+
continue;
|
|
286
|
+
// Only install for agents whose base directory already exists
|
|
287
|
+
if (!fs.existsSync(path.dirname(baseDir)))
|
|
288
|
+
continue;
|
|
289
|
+
const skillDir = path.join(baseDir, skillDirName);
|
|
290
|
+
try {
|
|
291
|
+
// Create directories
|
|
292
|
+
fs.mkdirSync(skillDir, { recursive: true });
|
|
293
|
+
// Write all files (with path traversal protection)
|
|
294
|
+
for (const file of skill.files) {
|
|
295
|
+
// Sanitize file path to prevent path traversal
|
|
296
|
+
const sanitized = file.filePath.replace(/\.\.\//g, "").replace(/\.\.\\/g, "");
|
|
297
|
+
const filePath = path.resolve(skillDir, sanitized);
|
|
298
|
+
// Ensure resolved path is within skillDir
|
|
299
|
+
if (!filePath.startsWith(path.resolve(skillDir))) {
|
|
300
|
+
console.log(chalk_1.default.yellow(` Skipped unsafe file path: ${file.filePath}`));
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
const fileDir = path.dirname(filePath);
|
|
304
|
+
fs.mkdirSync(fileDir, { recursive: true });
|
|
305
|
+
fs.writeFileSync(filePath, file.content, "utf-8");
|
|
306
|
+
}
|
|
307
|
+
console.log(chalk_1.default.green(` ✓ Installed for ${agentName}: ${skillDir}`));
|
|
308
|
+
installed++;
|
|
309
|
+
}
|
|
310
|
+
catch {
|
|
311
|
+
// Skip agents whose directories don't exist / can't be created
|
|
312
|
+
console.log(chalk_1.default.gray(` - Skipped ${agentName} (directory not accessible)`));
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
// Record the install
|
|
316
|
+
try {
|
|
317
|
+
await (0, api_js_1.apiRequest)(`/skills/${slug}/install`, { method: "POST" });
|
|
318
|
+
}
|
|
319
|
+
catch {
|
|
320
|
+
// Non-critical - don't fail the install
|
|
321
|
+
}
|
|
322
|
+
if (installed > 0) {
|
|
323
|
+
console.log(chalk_1.default.green(`\n✓ Installed "${skill.name}" for ${installed} agent(s)`));
|
|
324
|
+
}
|
|
325
|
+
else {
|
|
326
|
+
console.log(chalk_1.default.yellow("\nNo agents were found. Install manually by copying SKILL.md to your agent's skills directory."));
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
catch (error) {
|
|
330
|
+
spinner.stop();
|
|
331
|
+
console.error(chalk_1.default.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
|
|
332
|
+
process.exit(1);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
336
|
+
// orth skills init [name]
|
|
337
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
338
|
+
// Known binary/large file extensions to skip when reading local files
|
|
339
|
+
const SKIP_EXTENSIONS = new Set([
|
|
340
|
+
".png", ".jpg", ".jpeg", ".gif", ".webp", ".ico", ".svg",
|
|
341
|
+
".mp4", ".mp3", ".wav", ".ogg",
|
|
342
|
+
".zip", ".tar", ".gz", ".bz2", ".rar", ".7z",
|
|
343
|
+
".woff", ".woff2", ".ttf", ".eot",
|
|
344
|
+
".exe", ".dll", ".so", ".dylib",
|
|
345
|
+
".pdf", ".doc", ".docx", ".xls", ".xlsx",
|
|
346
|
+
".lock", ".bin",
|
|
347
|
+
]);
|
|
348
|
+
const SKIP_DIRS = new Set([
|
|
349
|
+
"node_modules", ".git", "__pycache__", ".venv", "venv",
|
|
350
|
+
"dist", "build", ".next", ".cache",
|
|
351
|
+
]);
|
|
352
|
+
async function skillsInitCommand(name, options) {
|
|
353
|
+
try {
|
|
354
|
+
const skillName = name || "my-skill";
|
|
355
|
+
const dirPath = options.path || path.join(process.cwd(), skillName);
|
|
356
|
+
// Check if directory already exists and has content
|
|
357
|
+
if (fs.existsSync(dirPath)) {
|
|
358
|
+
const contents = fs.readdirSync(dirPath);
|
|
359
|
+
if (contents.length > 0) {
|
|
360
|
+
const skillMdExists = contents.includes("SKILL.md");
|
|
361
|
+
if (skillMdExists) {
|
|
362
|
+
console.error(chalk_1.default.red(`Error: ${dirPath} already contains a SKILL.md file`));
|
|
363
|
+
process.exit(1);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
// Create directory
|
|
368
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
369
|
+
// Generate SKILL.md template
|
|
370
|
+
const slugName = skillName
|
|
371
|
+
.toLowerCase()
|
|
372
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
373
|
+
.replace(/-+/g, "-")
|
|
374
|
+
.replace(/^-+|-+$/g, "");
|
|
375
|
+
const skillMdContent = `---
|
|
376
|
+
name: ${slugName}
|
|
377
|
+
description: TODO - Describe what this skill does and when an agent should use it. Be specific about triggers.
|
|
378
|
+
---
|
|
379
|
+
|
|
380
|
+
# ${skillName.charAt(0).toUpperCase() + skillName.slice(1).replace(/-/g, " ")}
|
|
381
|
+
|
|
382
|
+
TODO - Add instructions for the AI agent here.
|
|
383
|
+
|
|
384
|
+
## When to Use This Skill
|
|
385
|
+
|
|
386
|
+
Use this skill when the user:
|
|
387
|
+
- TODO - Describe specific triggers
|
|
388
|
+
- TODO - Add more triggers
|
|
389
|
+
|
|
390
|
+
## Workflow
|
|
391
|
+
|
|
392
|
+
### Step 1: TODO
|
|
393
|
+
|
|
394
|
+
Describe the first step.
|
|
395
|
+
|
|
396
|
+
### Step 2: TODO
|
|
397
|
+
|
|
398
|
+
Describe the next step.
|
|
399
|
+
`;
|
|
400
|
+
fs.writeFileSync(path.join(dirPath, "SKILL.md"), skillMdContent, "utf-8");
|
|
401
|
+
// Create optional subdirectories unless --bare
|
|
402
|
+
if (!options.bare) {
|
|
403
|
+
const subDirs = ["scripts", "references", "assets"];
|
|
404
|
+
for (const sub of subDirs) {
|
|
405
|
+
const subPath = path.join(dirPath, sub);
|
|
406
|
+
fs.mkdirSync(subPath, { recursive: true });
|
|
407
|
+
// Add .gitkeep to keep empty dirs in git
|
|
408
|
+
fs.writeFileSync(path.join(subPath, ".gitkeep"), "", "utf-8");
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
console.log(chalk_1.default.green(`\n✓ Skill initialized at ${dirPath}\n`));
|
|
412
|
+
console.log(chalk_1.default.bold("Files created:"));
|
|
413
|
+
console.log(chalk_1.default.white(" SKILL.md") + chalk_1.default.gray(" (primary - edit this!)"));
|
|
414
|
+
if (!options.bare) {
|
|
415
|
+
console.log(chalk_1.default.gray(" scripts/ - Executable scripts"));
|
|
416
|
+
console.log(chalk_1.default.gray(" references/ - Reference docs loaded on demand"));
|
|
417
|
+
console.log(chalk_1.default.gray(" assets/ - Files used in output"));
|
|
418
|
+
}
|
|
419
|
+
console.log(chalk_1.default.bold("\nNext steps:"));
|
|
420
|
+
console.log(chalk_1.default.white(" 1. Edit SKILL.md with your skill's instructions"));
|
|
421
|
+
console.log(chalk_1.default.white(" 2. Add any supporting files to scripts/, references/, or assets/"));
|
|
422
|
+
console.log(chalk_1.default.white(` 3. Submit to Orthogonal: ${chalk_1.default.cyan(`orth skills submit ${dirPath}`)}`));
|
|
423
|
+
}
|
|
424
|
+
catch (error) {
|
|
425
|
+
console.error(chalk_1.default.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
|
|
426
|
+
process.exit(1);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
430
|
+
// orth skills submit [path]
|
|
431
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
432
|
+
function readFilesRecursive(dirPath, basePath = "") {
|
|
433
|
+
const files = [];
|
|
434
|
+
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
435
|
+
for (const entry of entries) {
|
|
436
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
437
|
+
const relativePath = basePath ? `${basePath}/${entry.name}` : entry.name;
|
|
438
|
+
if (entry.isDirectory()) {
|
|
439
|
+
if (SKIP_DIRS.has(entry.name) || entry.name.startsWith("."))
|
|
440
|
+
continue;
|
|
441
|
+
files.push(...readFilesRecursive(fullPath, relativePath));
|
|
442
|
+
}
|
|
443
|
+
else if (entry.isFile()) {
|
|
444
|
+
// Skip dotfiles and binary files
|
|
445
|
+
if (entry.name.startsWith("."))
|
|
446
|
+
continue;
|
|
447
|
+
const ext = entry.name.includes(".")
|
|
448
|
+
? "." + entry.name.split(".").pop().toLowerCase()
|
|
449
|
+
: "";
|
|
450
|
+
if (SKIP_EXTENSIONS.has(ext))
|
|
451
|
+
continue;
|
|
452
|
+
try {
|
|
453
|
+
const content = fs.readFileSync(fullPath, "utf-8");
|
|
454
|
+
files.push({
|
|
455
|
+
filePath: relativePath,
|
|
456
|
+
content,
|
|
457
|
+
isPrimary: entry.name === "SKILL.md" && basePath === "",
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
catch {
|
|
461
|
+
// Skip files that can't be read as UTF-8
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
return files;
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Simple YAML frontmatter parser for SKILL.md files.
|
|
469
|
+
* Supports single-line `name:` and `description:` values.
|
|
470
|
+
* Multi-line YAML values (block scalars, folded strings) are not supported —
|
|
471
|
+
* descriptions should be kept to a single line in frontmatter.
|
|
472
|
+
*/
|
|
473
|
+
function parseFrontmatter(content) {
|
|
474
|
+
const match = content.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
475
|
+
if (!match)
|
|
476
|
+
return {};
|
|
477
|
+
const fm = match[1] || "";
|
|
478
|
+
const result = {};
|
|
479
|
+
for (const line of fm.split("\n")) {
|
|
480
|
+
const nameMatch = line.match(/^name:\s*(.+)$/);
|
|
481
|
+
if (nameMatch?.[1])
|
|
482
|
+
result.name = nameMatch[1].trim();
|
|
483
|
+
const descMatch = line.match(/^description:\s*(.+)$/);
|
|
484
|
+
if (descMatch?.[1])
|
|
485
|
+
result.description = descMatch[1].trim();
|
|
486
|
+
}
|
|
487
|
+
return result;
|
|
488
|
+
}
|
|
489
|
+
async function skillsSubmitCommand(inputPath, options) {
|
|
490
|
+
const dirPath = inputPath ? path.resolve(inputPath) : process.cwd();
|
|
491
|
+
const spinner = (0, ora_1.default)("Reading skill files...").start();
|
|
492
|
+
try {
|
|
493
|
+
// Validate directory exists
|
|
494
|
+
if (!fs.existsSync(dirPath) || !fs.statSync(dirPath).isDirectory()) {
|
|
495
|
+
spinner.stop();
|
|
496
|
+
console.error(chalk_1.default.red(`Error: ${dirPath} is not a directory`));
|
|
497
|
+
process.exit(1);
|
|
498
|
+
}
|
|
499
|
+
// Read all files
|
|
500
|
+
const files = readFilesRecursive(dirPath);
|
|
501
|
+
if (files.length === 0) {
|
|
502
|
+
spinner.stop();
|
|
503
|
+
console.error(chalk_1.default.red("Error: No files found in the directory"));
|
|
504
|
+
process.exit(1);
|
|
505
|
+
}
|
|
506
|
+
// Validate SKILL.md exists
|
|
507
|
+
const primaryFile = files.find((f) => f.isPrimary);
|
|
508
|
+
if (!primaryFile) {
|
|
509
|
+
spinner.stop();
|
|
510
|
+
console.error(chalk_1.default.red("Error: No SKILL.md found in the root of the directory"));
|
|
511
|
+
console.log(chalk_1.default.gray("Run 'orth skills init' to create a skill template"));
|
|
512
|
+
process.exit(1);
|
|
513
|
+
}
|
|
514
|
+
// Parse frontmatter
|
|
515
|
+
const frontmatter = parseFrontmatter(primaryFile.content);
|
|
516
|
+
const skillName = options.name || frontmatter.name;
|
|
517
|
+
const skillDescription = frontmatter.description;
|
|
518
|
+
if (!skillName) {
|
|
519
|
+
spinner.stop();
|
|
520
|
+
console.error(chalk_1.default.red("Error: Skill name is required. Add 'name:' to SKILL.md frontmatter or use --name"));
|
|
521
|
+
process.exit(1);
|
|
522
|
+
}
|
|
523
|
+
if (!skillDescription) {
|
|
524
|
+
spinner.stop();
|
|
525
|
+
console.error(chalk_1.default.red("Error: Skill description is required. Add 'description:' to SKILL.md frontmatter"));
|
|
526
|
+
process.exit(1);
|
|
527
|
+
}
|
|
528
|
+
// Check size limits
|
|
529
|
+
const totalSize = files.reduce((acc, f) => acc + f.content.length, 0);
|
|
530
|
+
if (files.length > 50) {
|
|
531
|
+
spinner.stop();
|
|
532
|
+
console.error(chalk_1.default.red("Error: Too many files (max 50)"));
|
|
533
|
+
process.exit(1);
|
|
534
|
+
}
|
|
535
|
+
if (totalSize > 1024 * 1024) {
|
|
536
|
+
spinner.stop();
|
|
537
|
+
console.error(chalk_1.default.red("Error: Total content too large (max 1MB)"));
|
|
538
|
+
process.exit(1);
|
|
539
|
+
}
|
|
540
|
+
spinner.text = "Submitting skill to Orthogonal...";
|
|
541
|
+
const tags = options.tags
|
|
542
|
+
? options.tags.split(",").map((t) => t.trim())
|
|
543
|
+
: undefined;
|
|
544
|
+
const data = await (0, api_js_1.apiRequest)("/skills", {
|
|
545
|
+
method: "POST",
|
|
546
|
+
body: {
|
|
547
|
+
sourceType: "upload",
|
|
548
|
+
name: skillName,
|
|
549
|
+
description: skillDescription,
|
|
550
|
+
files: files.map((f) => ({
|
|
551
|
+
filePath: f.filePath,
|
|
552
|
+
content: f.content,
|
|
553
|
+
isPrimary: f.isPrimary,
|
|
554
|
+
})),
|
|
555
|
+
tags,
|
|
556
|
+
discoverable: false,
|
|
557
|
+
},
|
|
558
|
+
});
|
|
559
|
+
spinner.stop();
|
|
560
|
+
console.log(chalk_1.default.green(`\n✓ ${data.message}`));
|
|
561
|
+
console.log(chalk_1.default.bold(`\n${data.skill.name}`));
|
|
562
|
+
console.log(chalk_1.default.gray(` Slug: ${data.skill.slug}`));
|
|
563
|
+
console.log(chalk_1.default.gray(` Files: ${files.length}`));
|
|
564
|
+
if (data.skill.description) {
|
|
565
|
+
console.log(chalk_1.default.gray(` ${data.skill.description.slice(0, 100)}`));
|
|
566
|
+
}
|
|
567
|
+
console.log(chalk_1.default.bold("\nYour skill is on the platform but not yet verified."));
|
|
568
|
+
console.log(chalk_1.default.white(`To request verification: ${chalk_1.default.cyan(`orth skills request-verification ${data.skill.slug}`)}`));
|
|
569
|
+
console.log(chalk_1.default.gray("Once verified, you can toggle discoverability anytime."));
|
|
570
|
+
console.log(chalk_1.default.gray(`Dashboard: https://orthogonal.com/dashboard/skills`));
|
|
571
|
+
}
|
|
572
|
+
catch (error) {
|
|
573
|
+
spinner.stop();
|
|
574
|
+
console.error(chalk_1.default.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
|
|
575
|
+
process.exit(1);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
579
|
+
// orth skills request-verification <slug>
|
|
580
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
581
|
+
async function skillsRequestVerificationCommand(slug) {
|
|
582
|
+
const spinner = (0, ora_1.default)("Submitting verification request...").start();
|
|
583
|
+
try {
|
|
584
|
+
// First get the skill to find its ID
|
|
585
|
+
const skillData = await (0, api_js_1.apiRequest)(`/skills/${slug}`);
|
|
586
|
+
if (!skillData.skill) {
|
|
587
|
+
spinner.stop();
|
|
588
|
+
console.error(chalk_1.default.red(`Error: Skill '${slug}' not found`));
|
|
589
|
+
process.exit(1);
|
|
590
|
+
}
|
|
591
|
+
// Request verification via the public API endpoint
|
|
592
|
+
await (0, api_js_1.apiRequest)(`/skills/${slug}/request-verification`, {
|
|
593
|
+
method: "POST",
|
|
594
|
+
});
|
|
595
|
+
spinner.stop();
|
|
596
|
+
console.log(chalk_1.default.green("\n✓ Verification request submitted!"));
|
|
597
|
+
console.log(chalk_1.default.gray("Our team will review your skill. Once verified, you can toggle discoverability anytime."));
|
|
598
|
+
console.log(chalk_1.default.gray("Check the status on your dashboard."));
|
|
599
|
+
}
|
|
600
|
+
catch (error) {
|
|
601
|
+
spinner.stop();
|
|
602
|
+
console.error(chalk_1.default.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
|
|
603
|
+
process.exit(1);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
607
|
+
// orth skills publish <slug> (deprecated - redirects to request-verification)
|
|
608
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
609
|
+
async function skillsPublishCommand(slug, options) {
|
|
610
|
+
console.log(chalk_1.default.yellow("Note: Direct publishing has been replaced with a verification workflow."));
|
|
611
|
+
console.log(chalk_1.default.white(`\nTo request your skill to be verified, run:\n ${chalk_1.default.cyan(`orth skills request-verification ${slug}`)}`));
|
|
612
|
+
console.log(chalk_1.default.white("Once verified, you can toggle discoverability from your dashboard."));
|
|
613
|
+
console.log(chalk_1.default.gray("\nOr manage from the dashboard: https://orthogonal.com/dashboard/skills"));
|
|
614
|
+
}
|
|
615
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
616
|
+
// orth skills request <input>
|
|
617
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
618
|
+
async function skillsRequestCommand(input) {
|
|
619
|
+
const spinner = (0, ora_1.default)("Submitting skill request...").start();
|
|
620
|
+
try {
|
|
621
|
+
// Determine if input is a GitHub URL or description
|
|
622
|
+
const isGitHub = input.includes("github.com") ||
|
|
623
|
+
/^[a-zA-Z0-9_-]+\/[a-zA-Z0-9_.-]+$/.test(input);
|
|
624
|
+
const body = isGitHub ? { githubRepo: input } : { description: input };
|
|
625
|
+
await (0, api_js_1.apiRequest)("/requests/skill", {
|
|
626
|
+
method: "POST",
|
|
627
|
+
body,
|
|
628
|
+
});
|
|
629
|
+
spinner.stop();
|
|
630
|
+
console.log(chalk_1.default.green("\n✓ Skill request submitted!"));
|
|
631
|
+
console.log(chalk_1.default.gray("Our team has been notified and will review your request."));
|
|
632
|
+
}
|
|
633
|
+
catch (error) {
|
|
634
|
+
spinner.stop();
|
|
635
|
+
console.error(chalk_1.default.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
|
|
636
|
+
process.exit(1);
|
|
637
|
+
}
|
|
638
|
+
}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import Conf from "conf";
|
|
2
|
+
interface ConfigSchema {
|
|
3
|
+
apiKey?: string;
|
|
4
|
+
}
|
|
5
|
+
export declare const config: Conf<ConfigSchema>;
|
|
6
|
+
export declare function getApiKey(): string | undefined;
|
|
7
|
+
export declare function setApiKey(key: string): void;
|
|
8
|
+
export declare function clearApiKey(): void;
|
|
9
|
+
export declare function requireApiKey(): string;
|
|
10
|
+
export {};
|