@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.
@@ -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
+ }
@@ -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 {};