@olorehq/olore 0.1.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/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+
2
+ export { }
package/dist/cli.js ADDED
@@ -0,0 +1,981 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli.ts
4
+ import { createRequire } from "module";
5
+ import { Command } from "commander";
6
+ import pc7 from "picocolors";
7
+
8
+ // src/commands/init.ts
9
+ import fs from "fs";
10
+ import path from "path";
11
+ import readline from "readline";
12
+ import pc from "picocolors";
13
+ async function prompt(question, defaultValue) {
14
+ const rl = readline.createInterface({
15
+ input: process.stdin,
16
+ output: process.stdout
17
+ });
18
+ return new Promise((resolve) => {
19
+ const q = defaultValue ? `${question} (${defaultValue}): ` : `${question}: `;
20
+ rl.question(q, (answer) => {
21
+ rl.close();
22
+ resolve(answer || defaultValue || "");
23
+ });
24
+ });
25
+ }
26
+ async function init(options) {
27
+ const cwd = process.cwd();
28
+ const folderName = path.basename(cwd);
29
+ if (fs.existsSync(path.join(cwd, "olore.config.json"))) {
30
+ throw new Error("olore.config.json already exists. This folder is already initialized.");
31
+ }
32
+ let name;
33
+ let version2;
34
+ let description;
35
+ if (options.yes) {
36
+ name = options.name || folderName;
37
+ version2 = options.version || "1.0.0";
38
+ description = `Documentation for ${name}`;
39
+ } else {
40
+ console.log(pc.bold("\nInitialize olore documentation package\n"));
41
+ name = await prompt("Package name", options.name || folderName);
42
+ version2 = await prompt("Version", options.version || "1.0.0");
43
+ description = await prompt("Description", `Documentation for ${name}`);
44
+ }
45
+ const fullName = `olore-${name}-${version2}`;
46
+ const configContent = {
47
+ name,
48
+ version: version2,
49
+ description,
50
+ contentPath: "./docs",
51
+ outputPath: "./olore-package",
52
+ extensions: [".md", ".mdx"],
53
+ exclude: []
54
+ };
55
+ fs.writeFileSync(
56
+ path.join(cwd, "olore.config.json"),
57
+ JSON.stringify(configContent, null, 2) + "\n"
58
+ );
59
+ const docsDir = path.join(cwd, "docs");
60
+ if (!fs.existsSync(docsDir)) {
61
+ fs.mkdirSync(docsDir);
62
+ fs.writeFileSync(
63
+ path.join(docsDir, "getting-started.md"),
64
+ `# Getting Started with ${name}
65
+
66
+ Add your documentation content here.
67
+
68
+ ## Overview
69
+
70
+ Describe what this documentation covers.
71
+
72
+ ## Topics
73
+
74
+ - Topic 1
75
+ - Topic 2
76
+ `
77
+ );
78
+ }
79
+ console.log(pc.bold(`
80
+ Initialized olore package: `) + pc.cyan(fullName));
81
+ console.log("");
82
+ console.log(pc.gray("Created:"));
83
+ console.log(pc.green(" \u2713 olore.config.json"));
84
+ console.log(pc.green(" \u2713 docs/") + pc.gray(" (add your documentation here)"));
85
+ console.log("");
86
+ console.log(pc.gray("Next steps:"));
87
+ console.log(" 1. Add your .md files to the " + pc.cyan("docs/") + " folder");
88
+ console.log(" 2. Run " + pc.cyan("/olore-docs-packager-1.0.0") + " in Claude Code to build");
89
+ console.log(" 3. Run " + pc.cyan("olore install ./olore-package") + " to install");
90
+ console.log("");
91
+ }
92
+
93
+ // src/commands/install.ts
94
+ import path5 from "path";
95
+ import fs5 from "fs-extra";
96
+ import ora from "ora";
97
+ import pc2 from "picocolors";
98
+
99
+ // src/core/download.ts
100
+ import { createHash } from "crypto";
101
+ import { createWriteStream } from "fs";
102
+ import os from "os";
103
+ import path2 from "path";
104
+ import { Readable } from "stream";
105
+ import { pipeline } from "stream/promises";
106
+ import fs2 from "fs-extra";
107
+ import * as tar from "tar";
108
+
109
+ // src/core/constants.ts
110
+ var REGISTRY_URL = "https://olorehq.github.io/olore/registry";
111
+ var DOWNLOAD_TIMEOUT = 6e4;
112
+ var USER_AGENT = "olore-cli/0.1.0";
113
+
114
+ // src/core/download.ts
115
+ var DownloadError = class extends Error {
116
+ constructor(message, code) {
117
+ super(message);
118
+ this.code = code;
119
+ this.name = "DownloadError";
120
+ }
121
+ };
122
+ async function downloadFile(url, dest) {
123
+ const controller = new AbortController();
124
+ const timeoutId = setTimeout(() => controller.abort(), DOWNLOAD_TIMEOUT);
125
+ try {
126
+ const response = await fetch(url, {
127
+ signal: controller.signal,
128
+ headers: {
129
+ "User-Agent": USER_AGENT
130
+ }
131
+ });
132
+ if (!response.ok) {
133
+ throw new DownloadError(
134
+ `Download failed: ${response.status} ${response.statusText}`,
135
+ "NETWORK_ERROR"
136
+ );
137
+ }
138
+ if (!response.body) {
139
+ throw new DownloadError("No response body", "NETWORK_ERROR");
140
+ }
141
+ await fs2.ensureDir(path2.dirname(dest));
142
+ const nodeReadable = Readable.fromWeb(response.body);
143
+ const writeStream = createWriteStream(dest);
144
+ await pipeline(nodeReadable, writeStream);
145
+ } catch (error) {
146
+ if (error instanceof DownloadError) {
147
+ throw error;
148
+ }
149
+ if (error instanceof Error && error.name === "AbortError") {
150
+ throw new DownloadError(`Download timed out after ${DOWNLOAD_TIMEOUT}ms`, "TIMEOUT");
151
+ }
152
+ throw new DownloadError(
153
+ `Download error: ${error instanceof Error ? error.message : "Unknown error"}`,
154
+ "NETWORK_ERROR"
155
+ );
156
+ } finally {
157
+ clearTimeout(timeoutId);
158
+ }
159
+ }
160
+ async function calculateChecksum(filePath) {
161
+ const hash = createHash("sha256");
162
+ const stream = fs2.createReadStream(filePath);
163
+ return new Promise((resolve, reject) => {
164
+ stream.on("data", (data) => hash.update(data));
165
+ stream.on("end", () => resolve(`sha256-${hash.digest("base64")}`));
166
+ stream.on("error", reject);
167
+ });
168
+ }
169
+ async function verifyChecksum(filePath, expected) {
170
+ const actual = await calculateChecksum(filePath);
171
+ return actual === expected;
172
+ }
173
+ async function extractTarball(tarball, dest) {
174
+ await fs2.ensureDir(dest);
175
+ try {
176
+ await tar.extract({
177
+ file: tarball,
178
+ cwd: dest,
179
+ strip: 1
180
+ // Remove top-level directory from archive
181
+ });
182
+ } catch (error) {
183
+ throw new DownloadError(
184
+ `Failed to extract tarball: ${error instanceof Error ? error.message : "Unknown error"}`,
185
+ "EXTRACTION_FAILED"
186
+ );
187
+ }
188
+ }
189
+ async function downloadAndInstall(url, dest, integrity) {
190
+ const tempDir = await fs2.mkdtemp(path2.join(os.tmpdir(), "olore-"));
191
+ const tarballPath = path2.join(tempDir, "package.tar.gz");
192
+ try {
193
+ await downloadFile(url, tarballPath);
194
+ const checksumValid = await verifyChecksum(tarballPath, integrity);
195
+ if (!checksumValid) {
196
+ const actual = await calculateChecksum(tarballPath);
197
+ throw new DownloadError(
198
+ `Checksum verification failed.
199
+ Expected: ${integrity}
200
+ Actual: ${actual}`,
201
+ "CHECKSUM_MISMATCH"
202
+ );
203
+ }
204
+ await fs2.remove(dest);
205
+ await extractTarball(tarballPath, dest);
206
+ return dest;
207
+ } finally {
208
+ await fs2.remove(tempDir).catch(() => {
209
+ });
210
+ }
211
+ }
212
+ async function downloadAndExtractToTemp(url, integrity) {
213
+ const tempDir = await fs2.mkdtemp(path2.join(os.tmpdir(), "olore-"));
214
+ const tarballPath = path2.join(tempDir, "package.tar.gz");
215
+ const extractDir = path2.join(tempDir, "package");
216
+ try {
217
+ await downloadFile(url, tarballPath);
218
+ const checksumValid = await verifyChecksum(tarballPath, integrity);
219
+ if (!checksumValid) {
220
+ const actual = await calculateChecksum(tarballPath);
221
+ throw new DownloadError(
222
+ `Checksum verification failed.
223
+ Expected: ${integrity}
224
+ Actual: ${actual}`,
225
+ "CHECKSUM_MISMATCH"
226
+ );
227
+ }
228
+ await extractTarball(tarballPath, extractDir);
229
+ await fs2.remove(tarballPath);
230
+ return extractDir;
231
+ } catch (error) {
232
+ await fs2.remove(tempDir).catch(() => {
233
+ });
234
+ throw error;
235
+ }
236
+ }
237
+ function formatBytes(bytes) {
238
+ if (bytes === 0) return "0 B";
239
+ const k = 1024;
240
+ const sizes = ["B", "KB", "MB", "GB"];
241
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
242
+ return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`;
243
+ }
244
+
245
+ // src/core/paths.ts
246
+ import fs4 from "fs";
247
+ import os3 from "os";
248
+ import path4 from "path";
249
+ import { fileURLToPath } from "url";
250
+
251
+ // src/core/platform.ts
252
+ import os2 from "os";
253
+ import path3 from "path";
254
+ import fs3 from "fs-extra";
255
+ var isWindows = process.platform === "win32";
256
+ async function linkOrCopy(source, target) {
257
+ if (isWindows) {
258
+ await fs3.copy(source, target);
259
+ } else {
260
+ await fs3.symlink(source, target, "dir");
261
+ }
262
+ }
263
+ function expandPath(p) {
264
+ if (p.startsWith("~")) {
265
+ return path3.join(os2.homedir(), p.slice(1));
266
+ }
267
+ return path3.resolve(p);
268
+ }
269
+ function isLocalPath(p) {
270
+ if (p.startsWith(".") || p.startsWith("/") || p.startsWith("~")) {
271
+ return true;
272
+ }
273
+ if (/^[A-Za-z]:/.test(p)) {
274
+ return true;
275
+ }
276
+ if (p.startsWith("\\\\")) {
277
+ return true;
278
+ }
279
+ return false;
280
+ }
281
+ function pathStartsWith(child, parent) {
282
+ if (isWindows) {
283
+ return child.toLowerCase().startsWith(parent.toLowerCase());
284
+ }
285
+ return child.startsWith(parent);
286
+ }
287
+ function getLinkActionText() {
288
+ return isWindows ? "Copying" : "Creating symlinks";
289
+ }
290
+ function getLinkTypeText() {
291
+ return isWindows ? "copied to" : "symlinked to";
292
+ }
293
+
294
+ // src/core/paths.ts
295
+ var __filename2 = fileURLToPath(import.meta.url);
296
+ var __dirname2 = path4.dirname(__filename2);
297
+ function getOloreHome() {
298
+ return path4.join(os3.homedir(), ".olore");
299
+ }
300
+ function getOlorePackagePath(name, version2) {
301
+ return path4.join(getOloreHome(), "packages", name, version2);
302
+ }
303
+ function isInstalledPackage(symlinkPath) {
304
+ try {
305
+ const target = fs4.readlinkSync(symlinkPath);
306
+ const oloreHome = getOloreHome();
307
+ return pathStartsWith(target, oloreHome);
308
+ } catch {
309
+ return fs4.existsSync(symlinkPath) && fs4.statSync(symlinkPath).isDirectory();
310
+ }
311
+ }
312
+ function getSymlinkTarget(symlinkPath) {
313
+ try {
314
+ return fs4.readlinkSync(symlinkPath);
315
+ } catch {
316
+ return null;
317
+ }
318
+ }
319
+ function getAgentPaths() {
320
+ const home = os3.homedir();
321
+ return {
322
+ claude: path4.join(home, ".claude", "skills"),
323
+ codex: path4.join(home, ".codex", "skills"),
324
+ opencode: path4.join(home, ".config", "opencode", "skills")
325
+ };
326
+ }
327
+ function detectAgents() {
328
+ const agents = [];
329
+ const paths = getAgentPaths();
330
+ for (const [agent, agentPath] of Object.entries(paths)) {
331
+ const parentDir = path4.dirname(agentPath);
332
+ if (fs4.existsSync(parentDir)) {
333
+ agents.push(agent);
334
+ }
335
+ }
336
+ return agents;
337
+ }
338
+ async function getInstalledPackages() {
339
+ const agentPaths = getAgentPaths();
340
+ const packages = [];
341
+ const seen = /* @__PURE__ */ new Set();
342
+ for (const [agent, skillsDir] of Object.entries(agentPaths)) {
343
+ if (!fs4.existsSync(skillsDir)) {
344
+ continue;
345
+ }
346
+ const entries = fs4.readdirSync(skillsDir, { withFileTypes: true });
347
+ for (const entry of entries) {
348
+ const isValidEntry = entry.isDirectory() || entry.isSymbolicLink();
349
+ if (!isValidEntry || !entry.name.startsWith("olore-")) {
350
+ continue;
351
+ }
352
+ const pkgPath = path4.join(skillsDir, entry.name);
353
+ const lockPath = path4.join(pkgPath, "olore-lock.json");
354
+ if (!fs4.existsSync(lockPath)) {
355
+ continue;
356
+ }
357
+ const pkgName = entry.name;
358
+ if (seen.has(pkgName)) {
359
+ continue;
360
+ }
361
+ seen.add(pkgName);
362
+ try {
363
+ const lock = JSON.parse(fs4.readFileSync(lockPath, "utf-8"));
364
+ const stats = await getDirectoryStats(pkgPath);
365
+ const symlinkTarget = getSymlinkTarget(pkgPath);
366
+ let installType;
367
+ if (symlinkTarget) {
368
+ installType = isInstalledPackage(pkgPath) ? "installed" : "linked";
369
+ } else {
370
+ installType = "copied";
371
+ }
372
+ packages.push({
373
+ name: lock.name || pkgName.replace("olore-", ""),
374
+ version: lock.version || "unknown",
375
+ files: stats.files,
376
+ size: stats.size,
377
+ path: pkgPath,
378
+ agent,
379
+ installType,
380
+ symlinkTarget
381
+ });
382
+ } catch {
383
+ }
384
+ }
385
+ }
386
+ return packages.sort((a, b) => a.name.localeCompare(b.name));
387
+ }
388
+ async function getDirectoryStats(dir) {
389
+ let files = 0;
390
+ let size = 0;
391
+ function walk(currentDir) {
392
+ const entries = fs4.readdirSync(currentDir, { withFileTypes: true });
393
+ for (const entry of entries) {
394
+ const fullPath = path4.join(currentDir, entry.name);
395
+ if (entry.isDirectory()) {
396
+ walk(fullPath);
397
+ } else if (entry.isFile()) {
398
+ files++;
399
+ size += fs4.statSync(fullPath).size;
400
+ }
401
+ }
402
+ }
403
+ walk(dir);
404
+ return { files, size };
405
+ }
406
+
407
+ // src/core/registry.ts
408
+ var RegistryError = class extends Error {
409
+ constructor(message, code) {
410
+ super(message);
411
+ this.code = code;
412
+ this.name = "RegistryError";
413
+ }
414
+ };
415
+ async function fetchWithTimeout(url, timeout = DOWNLOAD_TIMEOUT) {
416
+ const controller = new AbortController();
417
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
418
+ try {
419
+ const response = await fetch(url, {
420
+ signal: controller.signal,
421
+ headers: {
422
+ "User-Agent": USER_AGENT
423
+ }
424
+ });
425
+ return response;
426
+ } catch (error) {
427
+ if (error instanceof Error && error.name === "AbortError") {
428
+ throw new RegistryError(`Request timed out after ${timeout}ms`, "TIMEOUT");
429
+ }
430
+ throw new RegistryError(
431
+ `Network error: ${error instanceof Error ? error.message : "Unknown error"}`,
432
+ "NETWORK_ERROR"
433
+ );
434
+ } finally {
435
+ clearTimeout(timeoutId);
436
+ }
437
+ }
438
+ async function fetchPackageVersions(name) {
439
+ const url = `${REGISTRY_URL}/packages/${name}.json`;
440
+ const response = await fetchWithTimeout(url);
441
+ if (response.status === 404) {
442
+ throw new RegistryError(`Package "${name}" not found in registry`, "NOT_FOUND");
443
+ }
444
+ if (!response.ok) {
445
+ throw new RegistryError(`Failed to fetch package info: ${response.status}`, "NETWORK_ERROR");
446
+ }
447
+ try {
448
+ const data = await response.json();
449
+ return data;
450
+ } catch {
451
+ throw new RegistryError("Invalid package response", "INVALID_RESPONSE");
452
+ }
453
+ }
454
+ async function resolveVersion(name, version2) {
455
+ const packageVersions = await fetchPackageVersions(name);
456
+ const targetVersion = version2 || "latest";
457
+ const versionInfo = packageVersions.versions[targetVersion];
458
+ if (!versionInfo) {
459
+ const availableVersions = Object.keys(packageVersions.versions).join(", ");
460
+ throw new RegistryError(
461
+ `Version "${targetVersion}" not found for package "${name}". Available: ${availableVersions}`,
462
+ "NOT_FOUND"
463
+ );
464
+ }
465
+ return versionInfo;
466
+ }
467
+
468
+ // src/commands/install.ts
469
+ async function installFromLocal(localPath) {
470
+ const fullPath = expandPath(localPath);
471
+ if (!await fs5.pathExists(fullPath)) {
472
+ throw new Error(`Path not found: ${fullPath}`);
473
+ }
474
+ const stat = await fs5.stat(fullPath);
475
+ if (!stat.isDirectory()) {
476
+ throw new Error(`Not a directory: ${fullPath}`);
477
+ }
478
+ const lockPath = path5.join(fullPath, "olore-lock.json");
479
+ if (!await fs5.pathExists(lockPath)) {
480
+ throw new Error(`Missing olore-lock.json in ${fullPath}`);
481
+ }
482
+ const skillPath = path5.join(fullPath, "SKILL.md");
483
+ if (!await fs5.pathExists(skillPath)) {
484
+ throw new Error(`Missing SKILL.md in ${fullPath}. Run /generate-agent-skills first.`);
485
+ }
486
+ const lock = await fs5.readJson(lockPath);
487
+ const packageName = lock.name;
488
+ const packageVersion = lock.version;
489
+ if (!packageName) {
490
+ throw new Error(`Invalid olore-lock.json: missing "name" field`);
491
+ }
492
+ if (!packageVersion) {
493
+ throw new Error(`Invalid olore-lock.json: missing "version" field`);
494
+ }
495
+ const skillName = `olore-${packageName}-${packageVersion}`;
496
+ const skillContent = await fs5.readFile(skillPath, "utf-8");
497
+ const nameMatch = skillContent.match(/^name:\s*(.+)$/m);
498
+ const skillMdName = nameMatch ? nameMatch[1].trim() : null;
499
+ if (skillMdName !== skillName) {
500
+ console.log(pc2.yellow(`
501
+ Warning: SKILL.md name mismatch`));
502
+ console.log(pc2.gray(` Expected: ${skillName}`));
503
+ console.log(pc2.gray(` Found: ${skillMdName || "(none)"}`));
504
+ console.log(pc2.yellow(` Updating SKILL.md to fix...`));
505
+ const updatedContent = skillMdName ? skillContent.replace(/^name:\s*.+$/m, `name: ${skillName}`) : skillContent.replace(/^---\n/, `---
506
+ name: ${skillName}
507
+ `);
508
+ await fs5.writeFile(skillPath, updatedContent);
509
+ }
510
+ console.log(pc2.bold(`
511
+ Installing ${packageName}@${packageVersion} from local path...
512
+ `));
513
+ const agents = detectAgents();
514
+ if (agents.length === 0) {
515
+ console.log(pc2.yellow("No agents detected. Creating directories anyway."));
516
+ }
517
+ const agentPaths = getAgentPaths();
518
+ if (isWindows) {
519
+ const spinner = ora("Copying to agent directories...").start();
520
+ const copied = [];
521
+ for (const [agent, skillsDir] of Object.entries(agentPaths)) {
522
+ const targetDir = path5.join(skillsDir, skillName);
523
+ await fs5.ensureDir(skillsDir);
524
+ await fs5.remove(targetDir);
525
+ await fs5.copy(fullPath, targetDir);
526
+ copied.push(`${pc2.green("\u2713")} ${agent} ${pc2.gray("\u2192")} ${pc2.gray(targetDir)}`);
527
+ }
528
+ spinner.stop();
529
+ copied.forEach((line) => console.log(` ${line}`));
530
+ console.log(pc2.gray(` \u2514\u2500 copied from ${fullPath}`));
531
+ } else {
532
+ const olorePath = getOlorePackagePath(packageName, packageVersion);
533
+ const spinner = ora("Copying to ~/.olore...").start();
534
+ await fs5.ensureDir(path5.dirname(olorePath));
535
+ await fs5.remove(olorePath);
536
+ await fs5.copy(fullPath, olorePath);
537
+ spinner.succeed(`Copied to ${pc2.gray(olorePath)}`);
538
+ const linkSpinner = ora("Creating symlinks...").start();
539
+ const linked = [];
540
+ for (const [agent, skillsDir] of Object.entries(agentPaths)) {
541
+ const targetDir = path5.join(skillsDir, skillName);
542
+ await fs5.ensureDir(skillsDir);
543
+ await fs5.remove(targetDir);
544
+ await linkOrCopy(olorePath, targetDir);
545
+ linked.push(`${pc2.green("\u2713")} ${agent} ${pc2.gray("\u2192")} ${pc2.gray(targetDir)}`);
546
+ }
547
+ linkSpinner.stop();
548
+ linked.forEach((line) => console.log(` ${line}`));
549
+ console.log(pc2.gray(` \u2514\u2500 all symlinked to ${olorePath}`));
550
+ }
551
+ console.log("");
552
+ console.log(pc2.green("Installation complete!"));
553
+ console.log("");
554
+ console.log(pc2.gray("Skill is now available as:"));
555
+ console.log(pc2.cyan(` /${skillName}`) + pc2.gray(" (Claude Code)"));
556
+ console.log(pc2.cyan(` $${skillName}`) + pc2.gray(" (Codex)"));
557
+ console.log(pc2.cyan(` ${skillName}`) + pc2.gray(" (OpenCode)"));
558
+ }
559
+ async function install(pkg, options) {
560
+ if (options.force) {
561
+ console.log(pc2.cyan("\n\u2728 May the Skill be with you.\n"));
562
+ }
563
+ if (isLocalPath(pkg)) {
564
+ await installFromLocal(pkg);
565
+ return;
566
+ }
567
+ await installFromRemote(pkg, options.version);
568
+ }
569
+ function parsePackageSpec(spec) {
570
+ const atIndex = spec.lastIndexOf("@");
571
+ if (atIndex > 0) {
572
+ return {
573
+ name: spec.slice(0, atIndex),
574
+ version: spec.slice(atIndex + 1)
575
+ };
576
+ }
577
+ return { name: spec };
578
+ }
579
+ async function installFromRemote(pkg, optionsVersion) {
580
+ const { name, version: specVersion } = parsePackageSpec(pkg);
581
+ const requestedVersion = optionsVersion || specVersion || "latest";
582
+ console.log(pc2.bold(`
583
+ Installing ${name}@${requestedVersion} from registry...
584
+ `));
585
+ const spinner = ora("Fetching package info...").start();
586
+ let versionInfo;
587
+ try {
588
+ versionInfo = await resolveVersion(name, requestedVersion);
589
+ spinner.succeed(`Found ${name}@${versionInfo.version} (${formatBytes(versionInfo.size)})`);
590
+ } catch (error) {
591
+ spinner.fail("Failed to resolve package");
592
+ if (error instanceof RegistryError) {
593
+ if (error.code === "NOT_FOUND") {
594
+ console.log(pc2.yellow(`
595
+ Package "${name}" not found in registry.`));
596
+ console.log(pc2.gray("\nFor local packages, use a path:"));
597
+ console.log(pc2.cyan(` olore install ./vault/packages/${name}/<version>`));
598
+ console.log("");
599
+ } else if (error.code === "NETWORK_ERROR" || error.code === "TIMEOUT") {
600
+ console.log(pc2.red(`
601
+ Network error: ${error.message}`));
602
+ console.log(pc2.gray("Please check your internet connection and try again."));
603
+ }
604
+ } else {
605
+ console.log(pc2.red(`
606
+ Error: ${error instanceof Error ? error.message : "Unknown error"}`));
607
+ }
608
+ process.exit(1);
609
+ }
610
+ const skillName = `olore-${name}-${versionInfo.version}`;
611
+ const agents = detectAgents();
612
+ if (agents.length === 0) {
613
+ console.log(pc2.yellow("No agents detected. Creating directories anyway."));
614
+ }
615
+ const agentPaths = getAgentPaths();
616
+ if (isWindows) {
617
+ const downloadSpinner = ora("Downloading package...").start();
618
+ let tempDir;
619
+ try {
620
+ tempDir = await downloadAndExtractToTemp(versionInfo.downloadUrl, versionInfo.integrity);
621
+ downloadSpinner.succeed("Downloaded and verified");
622
+ } catch (error) {
623
+ downloadSpinner.fail("Download failed");
624
+ if (error instanceof DownloadError) {
625
+ if (error.code === "CHECKSUM_MISMATCH") {
626
+ console.log(pc2.red("\nChecksum verification failed!"));
627
+ console.log(pc2.gray("The downloaded package may be corrupted or tampered with."));
628
+ } else {
629
+ console.log(pc2.red(`
630
+ Download error: ${error.message}`));
631
+ }
632
+ } else {
633
+ console.log(pc2.red(`
634
+ Error: ${error instanceof Error ? error.message : "Unknown error"}`));
635
+ }
636
+ process.exit(1);
637
+ }
638
+ try {
639
+ const copySpinner = ora("Copying to agent directories...").start();
640
+ const copied = [];
641
+ for (const [agent, skillsDir] of Object.entries(agentPaths)) {
642
+ const targetDir = path5.join(skillsDir, skillName);
643
+ await fs5.ensureDir(skillsDir);
644
+ await fs5.remove(targetDir);
645
+ await fs5.copy(tempDir, targetDir);
646
+ copied.push(`${pc2.green("\u2713")} ${agent} ${pc2.gray("\u2192")} ${pc2.gray(targetDir)}`);
647
+ }
648
+ copySpinner.stop();
649
+ copied.forEach((line) => console.log(` ${line}`));
650
+ } finally {
651
+ await fs5.remove(path5.dirname(tempDir)).catch(() => {
652
+ });
653
+ }
654
+ } else {
655
+ const olorePath = getOlorePackagePath(name, versionInfo.version);
656
+ const downloadSpinner = ora("Downloading package...").start();
657
+ try {
658
+ await downloadAndInstall(versionInfo.downloadUrl, olorePath, versionInfo.integrity);
659
+ downloadSpinner.succeed(`Downloaded to ${pc2.gray(olorePath)}`);
660
+ } catch (error) {
661
+ downloadSpinner.fail("Download failed");
662
+ if (error instanceof DownloadError) {
663
+ if (error.code === "CHECKSUM_MISMATCH") {
664
+ console.log(pc2.red("\nChecksum verification failed!"));
665
+ console.log(pc2.gray("The downloaded package may be corrupted or tampered with."));
666
+ } else {
667
+ console.log(pc2.red(`
668
+ Download error: ${error.message}`));
669
+ }
670
+ } else {
671
+ console.log(pc2.red(`
672
+ Error: ${error instanceof Error ? error.message : "Unknown error"}`));
673
+ }
674
+ process.exit(1);
675
+ }
676
+ const linkSpinner = ora("Creating symlinks...").start();
677
+ const linked = [];
678
+ for (const [agent, skillsDir] of Object.entries(agentPaths)) {
679
+ const targetDir = path5.join(skillsDir, skillName);
680
+ await fs5.ensureDir(skillsDir);
681
+ await fs5.remove(targetDir);
682
+ await linkOrCopy(olorePath, targetDir);
683
+ linked.push(`${pc2.green("\u2713")} ${agent} ${pc2.gray("\u2192")} ${pc2.gray(targetDir)}`);
684
+ }
685
+ linkSpinner.stop();
686
+ linked.forEach((line) => console.log(` ${line}`));
687
+ console.log(pc2.gray(` \u2514\u2500 all symlinked to ${olorePath}`));
688
+ }
689
+ console.log("");
690
+ console.log(pc2.green("Installation complete!"));
691
+ console.log("");
692
+ console.log(pc2.gray("Skill is now available as:"));
693
+ console.log(pc2.cyan(` /${skillName}`) + pc2.gray(" (Claude Code)"));
694
+ console.log(pc2.cyan(` $${skillName}`) + pc2.gray(" (Codex)"));
695
+ console.log(pc2.cyan(` ${skillName}`) + pc2.gray(" (OpenCode)"));
696
+ }
697
+
698
+ // src/commands/link.ts
699
+ import path6 from "path";
700
+ import fs6 from "fs-extra";
701
+ import ora2 from "ora";
702
+ import pc3 from "picocolors";
703
+ async function link(localPath) {
704
+ const fullPath = expandPath(localPath);
705
+ if (!await fs6.pathExists(fullPath)) {
706
+ throw new Error(`Path not found: ${fullPath}`);
707
+ }
708
+ const stat = await fs6.stat(fullPath);
709
+ if (!stat.isDirectory()) {
710
+ throw new Error(`Not a directory: ${fullPath}`);
711
+ }
712
+ const lockPath = path6.join(fullPath, "olore-lock.json");
713
+ if (!await fs6.pathExists(lockPath)) {
714
+ throw new Error(`Missing olore-lock.json in ${fullPath}`);
715
+ }
716
+ const skillPath = path6.join(fullPath, "SKILL.md");
717
+ if (!await fs6.pathExists(skillPath)) {
718
+ throw new Error(`Missing SKILL.md in ${fullPath}. Run /build-docs first.`);
719
+ }
720
+ const lock = await fs6.readJson(lockPath);
721
+ const packageName = lock.name;
722
+ const packageVersion = lock.version;
723
+ if (!packageName) {
724
+ throw new Error(`Invalid olore-lock.json: missing "name" field`);
725
+ }
726
+ if (!packageVersion) {
727
+ throw new Error(`Invalid olore-lock.json: missing "version" field`);
728
+ }
729
+ const skillName = `olore-${packageName}-${packageVersion}`;
730
+ const skillContent = await fs6.readFile(skillPath, "utf-8");
731
+ const nameMatch = skillContent.match(/^name:\s*(.+)$/m);
732
+ const skillMdName = nameMatch ? nameMatch[1].trim() : null;
733
+ if (skillMdName !== skillName) {
734
+ console.log(pc3.yellow(`
735
+ Warning: SKILL.md name mismatch`));
736
+ console.log(pc3.gray(` Expected: ${skillName}`));
737
+ console.log(pc3.gray(` Found: ${skillMdName || "(none)"}`));
738
+ console.log(pc3.yellow(` Updating SKILL.md to fix...`));
739
+ const updatedContent = skillMdName ? skillContent.replace(/^name:\s*.+$/m, `name: ${skillName}`) : skillContent.replace(/^---\n/, `---
740
+ name: ${skillName}
741
+ `);
742
+ await fs6.writeFile(skillPath, updatedContent);
743
+ }
744
+ console.log(pc3.bold(`
745
+ Linking ${packageName}@${packageVersion}...
746
+ `));
747
+ const agents = detectAgents();
748
+ if (agents.length === 0) {
749
+ console.log(pc3.yellow("No agents detected. Creating directories anyway."));
750
+ }
751
+ const agentPaths = getAgentPaths();
752
+ const spinner = ora2(`${getLinkActionText()}...`).start();
753
+ const linked = [];
754
+ for (const [agent, skillsDir] of Object.entries(agentPaths)) {
755
+ const targetDir = path6.join(skillsDir, skillName);
756
+ await fs6.ensureDir(skillsDir);
757
+ await fs6.remove(targetDir);
758
+ await linkOrCopy(fullPath, targetDir);
759
+ linked.push(`${pc3.blue("\u26D3")} ${agent} ${pc3.gray("\u2192")} ${pc3.gray(targetDir)}`);
760
+ }
761
+ spinner.stop();
762
+ linked.forEach((line) => console.log(` ${line}`));
763
+ console.log(pc3.gray(` \u2514\u2500 ${getLinkTypeText()} ${fullPath}`));
764
+ console.log("");
765
+ console.log(pc3.blue("Link complete!"));
766
+ console.log("");
767
+ console.log(pc3.gray("Skill is now available as:"));
768
+ console.log(pc3.cyan(` /${skillName}`) + pc3.gray(" (Claude Code)"));
769
+ console.log(pc3.cyan(` $${skillName}`) + pc3.gray(" (Codex)"));
770
+ console.log(pc3.cyan(` ${skillName}`) + pc3.gray(" (OpenCode)"));
771
+ console.log("");
772
+ console.log(pc3.gray(`Development mode: ${getLinkTypeText()} source (bypasses ~/.olore).`));
773
+ console.log(pc3.gray("On Unix, changes to source are immediately visible."));
774
+ console.log(pc3.gray("On Windows, re-run link after changes."));
775
+ console.log(
776
+ pc3.gray("Use ") + pc3.cyan("olore install") + pc3.gray(" for a stable copy in ~/.olore.")
777
+ );
778
+ }
779
+
780
+ // src/commands/list.ts
781
+ import pc4 from "picocolors";
782
+ async function list(options) {
783
+ const packages = await getInstalledPackages();
784
+ if (packages.length === 0) {
785
+ console.log(pc4.yellow("\nI find your lack of skills disturbing.\n"));
786
+ console.log(pc4.gray("No packages installed."));
787
+ console.log(`Run ${pc4.cyan("olore install <package>")} to install documentation.`);
788
+ return;
789
+ }
790
+ if (options.json) {
791
+ console.log(JSON.stringify(packages, null, 2));
792
+ return;
793
+ }
794
+ console.log(pc4.bold("\nInstalled packages:\n"));
795
+ console.log(
796
+ pc4.gray(
797
+ "PACKAGE".padEnd(25) + "VERSION".padEnd(12) + "TYPE".padEnd(10) + "FILES".padStart(8) + "SIZE".padStart(12)
798
+ )
799
+ );
800
+ console.log(pc4.gray("-".repeat(67)));
801
+ for (const pkg of packages) {
802
+ const typeLabel = pkg.installType === "linked" ? pc4.blue("linked") : pkg.installType;
803
+ console.log(
804
+ pkg.name.padEnd(25) + pkg.version.padEnd(12) + typeLabel.padEnd(10) + String(pkg.files).padStart(8) + formatSize(pkg.size).padStart(12)
805
+ );
806
+ }
807
+ console.log(pc4.gray("-".repeat(67)));
808
+ console.log(pc4.gray(`Total: ${packages.length} packages`));
809
+ console.log("");
810
+ console.log(pc4.gray("Types: installed = in ~/.olore, linked = dev symlink, copied = legacy"));
811
+ }
812
+ function formatSize(bytes) {
813
+ if (bytes < 1024) return `${bytes} B`;
814
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
815
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
816
+ }
817
+
818
+ // src/commands/order66.ts
819
+ import path7 from "path";
820
+ import fs7 from "fs-extra";
821
+ import ora3 from "ora";
822
+ import pc5 from "picocolors";
823
+ async function order66() {
824
+ console.log(pc5.red("\n\u26A0\uFE0F Execute Order 66?\n"));
825
+ console.log(pc5.yellow("This will remove ALL installed documentation packages."));
826
+ const packages = await getInstalledPackages();
827
+ if (packages.length === 0) {
828
+ console.log(pc5.gray("\nNo packages to remove. The Jedi are already gone."));
829
+ return;
830
+ }
831
+ console.log(pc5.gray(`
832
+ Packages to be removed: ${packages.length}`));
833
+ for (const pkg of packages) {
834
+ console.log(pc5.gray(` - ${pkg.name}@${pkg.version}`));
835
+ }
836
+ console.log(pc5.red('\n"It will be done, my lord."\n'));
837
+ const spinner = ora3("Executing Order 66...").start();
838
+ let removedCount = 0;
839
+ const agentPaths = getAgentPaths();
840
+ for (const pkg of packages) {
841
+ const skillName = `olore-${pkg.name}-${pkg.version}`;
842
+ for (const [, basePath] of Object.entries(agentPaths)) {
843
+ const skillPath = path7.join(basePath, skillName);
844
+ if (await fs7.pathExists(skillPath)) {
845
+ await fs7.remove(skillPath);
846
+ }
847
+ }
848
+ if (pkg.installType === "installed") {
849
+ const olorePath = getOlorePackagePath(pkg.name, pkg.version);
850
+ if (await fs7.pathExists(olorePath)) {
851
+ await fs7.remove(olorePath);
852
+ }
853
+ }
854
+ removedCount++;
855
+ }
856
+ spinner.succeed(`Removed ${removedCount} packages`);
857
+ console.log(pc5.gray("\nThe Jedi have been eliminated."));
858
+ }
859
+
860
+ // src/commands/remove.ts
861
+ import path8 from "path";
862
+ import fs8 from "fs-extra";
863
+ import ora4 from "ora";
864
+ import pc6 from "picocolors";
865
+ async function remove(pkg) {
866
+ console.log(pc6.bold(`
867
+ Removing ${pkg}...
868
+ `));
869
+ const packages = await getInstalledPackages();
870
+ let name = pkg;
871
+ let version2;
872
+ if (pkg.includes("@")) {
873
+ [name, version2] = pkg.split("@");
874
+ }
875
+ const matches = packages.filter((p) => {
876
+ if (version2) {
877
+ return p.name === name && p.version === version2;
878
+ }
879
+ return p.name === name;
880
+ });
881
+ if (matches.length === 0) {
882
+ console.error(pc6.red(`Package not found: ${pkg}`));
883
+ console.log("\nInstalled packages:");
884
+ for (const p of packages) {
885
+ console.log(` ${pc6.cyan(p.name)}@${p.version}`);
886
+ }
887
+ process.exit(1);
888
+ }
889
+ if (matches.length > 1 && !version2) {
890
+ console.error(pc6.red(`Multiple versions found for ${name}:`));
891
+ for (const p of matches) {
892
+ console.log(` ${pc6.cyan(p.name)}@${p.version} (${p.installType})`);
893
+ }
894
+ console.log(`
895
+ Specify version: ${pc6.cyan(`olore remove ${name}@<version>`)}`);
896
+ process.exit(1);
897
+ }
898
+ const found = matches[0];
899
+ const skillName = `olore-${found.name}-${found.version}`;
900
+ const spinner = ora4("Removing package...").start();
901
+ const removed = [];
902
+ const agentPaths = getAgentPaths();
903
+ for (const [agent, basePath] of Object.entries(agentPaths)) {
904
+ const skillPath = path8.join(basePath, skillName);
905
+ if (await fs8.pathExists(skillPath)) {
906
+ await fs8.remove(skillPath);
907
+ removed.push(`${pc6.green("\u2713")} ${agent}`);
908
+ }
909
+ }
910
+ if (found.installType === "installed") {
911
+ const olorePath = getOlorePackagePath(found.name, found.version);
912
+ if (await fs8.pathExists(olorePath)) {
913
+ await fs8.remove(olorePath);
914
+ removed.push(`${pc6.green("\u2713")} ~/.olore`);
915
+ }
916
+ }
917
+ spinner.stop();
918
+ if (removed.length === 0) {
919
+ console.log(pc6.yellow("No files found to remove."));
920
+ } else {
921
+ removed.forEach((line) => console.log(` ${line}`));
922
+ console.log("");
923
+ console.log(pc6.green(`Removed ${found.name}@${found.version}`));
924
+ }
925
+ }
926
+
927
+ // src/cli.ts
928
+ var require2 = createRequire(import.meta.url);
929
+ var { version } = require2("../package.json");
930
+ var program = new Command();
931
+ program.name("olore").description("Universal documentation for any AI coding agent").version(version).addHelpText("after", `
932
+ ${pc7.gray("May the Skill be with you.")}`);
933
+ program.command("init").description("Initialize a documentation package in the current directory").option("-n, --name <name>", "Package name (default: folder name)").option("-v, --version <version>", "Package version (default: latest)").option("-y, --yes", "Skip prompts, use defaults").action(async (options) => {
934
+ try {
935
+ await init(options);
936
+ } catch (error) {
937
+ console.error(pc7.red(`Error: ${error.message}`));
938
+ process.exit(1);
939
+ }
940
+ });
941
+ program.command("install <package>").alias("i").description("Install a documentation package from the registry").option("-v, --version <version>", "Install specific version").option("--keep", "Keep previous version active (for migrations)").option("--force", "Force installation").action(async (pkg, options) => {
942
+ try {
943
+ await install(pkg, options);
944
+ } catch (error) {
945
+ console.error(pc7.red(`Error: ${error.message}`));
946
+ process.exit(1);
947
+ }
948
+ });
949
+ program.command("link <path>").description("Link a local package for development (creates symlink)").action(async (localPath) => {
950
+ try {
951
+ await link(localPath);
952
+ } catch (error) {
953
+ console.error(pc7.red(`Error: ${error.message}`));
954
+ process.exit(1);
955
+ }
956
+ });
957
+ program.command("list").alias("ls").description("List installed documentation packages").option("--json", "Output as JSON").action(async (options) => {
958
+ try {
959
+ await list(options);
960
+ } catch (error) {
961
+ console.error(pc7.red(`Error: ${error.message}`));
962
+ process.exit(1);
963
+ }
964
+ });
965
+ program.command("remove <package>").alias("rm").description("Remove an installed documentation package").action(async (pkg) => {
966
+ try {
967
+ await remove(pkg);
968
+ } catch (error) {
969
+ console.error(pc7.red(`Error: ${error.message}`));
970
+ process.exit(1);
971
+ }
972
+ });
973
+ program.command("order66").description(false).action(async () => {
974
+ try {
975
+ await order66();
976
+ } catch (error) {
977
+ console.error(pc7.red(`Error: ${error.message}`));
978
+ process.exit(1);
979
+ }
980
+ });
981
+ program.parse();
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "@olorehq/olore",
3
+ "version": "0.1.0",
4
+ "description": "Universal documentation for any AI coding agent",
5
+ "keywords": [
6
+ "ai",
7
+ "documentation",
8
+ "claude",
9
+ "codex",
10
+ "coding-assistant",
11
+ "context",
12
+ "llm"
13
+ ],
14
+ "license": "AGPL-3.0",
15
+ "author": "olorehq",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/olorehq/olore.git",
19
+ "directory": "cli"
20
+ },
21
+ "homepage": "https://github.com/olorehq/olore",
22
+ "type": "module",
23
+ "files": [
24
+ "dist"
25
+ ],
26
+ "bin": {
27
+ "olore": "dist/cli.js"
28
+ },
29
+ "scripts": {
30
+ "build": "tsup",
31
+ "build:bin": "bun build src/cli.ts --compile --outfile dist/olore",
32
+ "build:bin:all": "npm run build:bin:darwin-arm64 && npm run build:bin:darwin-x64 && npm run build:bin:linux-x64 && npm run build:bin:linux-arm64",
33
+ "build:bin:darwin-arm64": "bun build src/cli.ts --compile --target=bun-darwin-arm64 --outfile dist/olore-darwin-arm64",
34
+ "build:bin:darwin-x64": "bun build src/cli.ts --compile --target=bun-darwin-x64 --outfile dist/olore-darwin-x64",
35
+ "build:bin:linux-arm64": "bun build src/cli.ts --compile --target=bun-linux-arm64 --outfile dist/olore-linux-arm64",
36
+ "build:bin:linux-x64": "bun build src/cli.ts --compile --target=bun-linux-x64 --outfile dist/olore-linux-x64",
37
+ "dev": "tsup --watch",
38
+ "format": "prettier --write src/",
39
+ "format:check": "prettier --check src/",
40
+ "lint": "eslint src/",
41
+ "test": "vitest",
42
+ "typecheck": "tsc --noEmit",
43
+ "generate-registry": "npx tsx scripts/generate-registry.ts"
44
+ },
45
+ "dependencies": {
46
+ "commander": "^12.1.0",
47
+ "fs-extra": "^11.3.3",
48
+ "ora": "^9.0.0",
49
+ "picocolors": "^1.1.1",
50
+ "tar": "^7.4.3"
51
+ },
52
+ "devDependencies": {
53
+ "@ianvs/prettier-plugin-sort-imports": "^4.7.0",
54
+ "@types/fs-extra": "^11.0.4",
55
+ "@types/node": "^22.10.7",
56
+ "prettier": "^3.8.0",
57
+ "prettier-plugin-packagejson": "^2.5.22",
58
+ "tsup": "^8.3.5",
59
+ "typescript": "^5.7.3",
60
+ "vitest": "^2.1.8"
61
+ },
62
+ "engines": {
63
+ "node": ">=18"
64
+ }
65
+ }