@olorehq/olore 0.1.2 → 0.1.4
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.js +854 -386
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -3,107 +3,25 @@
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import { createRequire } from "module";
|
|
5
5
|
import { Command } from "commander";
|
|
6
|
-
import
|
|
6
|
+
import pc10 from "picocolors";
|
|
7
7
|
|
|
8
|
-
// src/commands/
|
|
9
|
-
import fs from "fs";
|
|
10
|
-
import path from "path";
|
|
11
|
-
import readline from "readline";
|
|
8
|
+
// src/commands/doctor.ts
|
|
12
9
|
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
10
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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";
|
|
11
|
+
// src/core/doctor.ts
|
|
12
|
+
import fs4 from "fs";
|
|
13
|
+
import os4 from "os";
|
|
14
|
+
import path4 from "path";
|
|
15
|
+
import fsExtra from "fs-extra";
|
|
98
16
|
|
|
99
17
|
// src/core/download.ts
|
|
100
18
|
import { createHash } from "crypto";
|
|
101
19
|
import { createWriteStream } from "fs";
|
|
102
20
|
import os from "os";
|
|
103
|
-
import
|
|
21
|
+
import path from "path";
|
|
104
22
|
import { Readable } from "stream";
|
|
105
23
|
import { pipeline } from "stream/promises";
|
|
106
|
-
import
|
|
24
|
+
import fs from "fs-extra";
|
|
107
25
|
import * as tar from "tar";
|
|
108
26
|
|
|
109
27
|
// src/core/constants.ts
|
|
@@ -138,7 +56,7 @@ async function downloadFile(url, dest) {
|
|
|
138
56
|
if (!response.body) {
|
|
139
57
|
throw new DownloadError("No response body", "NETWORK_ERROR");
|
|
140
58
|
}
|
|
141
|
-
await
|
|
59
|
+
await fs.ensureDir(path.dirname(dest));
|
|
142
60
|
const nodeReadable = Readable.fromWeb(response.body);
|
|
143
61
|
const writeStream = createWriteStream(dest);
|
|
144
62
|
await pipeline(nodeReadable, writeStream);
|
|
@@ -159,7 +77,7 @@ async function downloadFile(url, dest) {
|
|
|
159
77
|
}
|
|
160
78
|
async function calculateChecksum(filePath) {
|
|
161
79
|
const hash = createHash("sha256");
|
|
162
|
-
const stream =
|
|
80
|
+
const stream = fs.createReadStream(filePath);
|
|
163
81
|
return new Promise((resolve, reject) => {
|
|
164
82
|
stream.on("data", (data) => hash.update(data));
|
|
165
83
|
stream.on("end", () => resolve(`sha256-${hash.digest("base64")}`));
|
|
@@ -171,7 +89,7 @@ async function verifyChecksum(filePath, expected) {
|
|
|
171
89
|
return actual === expected;
|
|
172
90
|
}
|
|
173
91
|
async function extractTarball(tarball, dest) {
|
|
174
|
-
await
|
|
92
|
+
await fs.ensureDir(dest);
|
|
175
93
|
try {
|
|
176
94
|
await tar.extract({
|
|
177
95
|
file: tarball,
|
|
@@ -187,8 +105,8 @@ async function extractTarball(tarball, dest) {
|
|
|
187
105
|
}
|
|
188
106
|
}
|
|
189
107
|
async function downloadAndInstall(url, dest, integrity) {
|
|
190
|
-
const tempDir = await
|
|
191
|
-
const tarballPath =
|
|
108
|
+
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "olore-"));
|
|
109
|
+
const tarballPath = path.join(tempDir, "package.tar.gz");
|
|
192
110
|
try {
|
|
193
111
|
await downloadFile(url, tarballPath);
|
|
194
112
|
const checksumValid = await verifyChecksum(tarballPath, integrity);
|
|
@@ -201,39 +119,14 @@ Actual: ${actual}`,
|
|
|
201
119
|
"CHECKSUM_MISMATCH"
|
|
202
120
|
);
|
|
203
121
|
}
|
|
204
|
-
await
|
|
122
|
+
await fs.remove(dest);
|
|
205
123
|
await extractTarball(tarballPath, dest);
|
|
206
124
|
return dest;
|
|
207
125
|
} finally {
|
|
208
|
-
await
|
|
126
|
+
await fs.remove(tempDir).catch(() => {
|
|
209
127
|
});
|
|
210
128
|
}
|
|
211
129
|
}
|
|
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
130
|
function formatBytes(bytes) {
|
|
238
131
|
if (bytes === 0) return "0 B";
|
|
239
132
|
const k = 1024;
|
|
@@ -243,28 +136,32 @@ function formatBytes(bytes) {
|
|
|
243
136
|
}
|
|
244
137
|
|
|
245
138
|
// src/core/paths.ts
|
|
246
|
-
import
|
|
139
|
+
import fs3 from "fs";
|
|
247
140
|
import os3 from "os";
|
|
248
|
-
import
|
|
141
|
+
import path3 from "path";
|
|
249
142
|
import { fileURLToPath } from "url";
|
|
250
143
|
|
|
251
144
|
// src/core/platform.ts
|
|
252
145
|
import os2 from "os";
|
|
253
|
-
import
|
|
254
|
-
import
|
|
146
|
+
import path2 from "path";
|
|
147
|
+
import fs2 from "fs-extra";
|
|
255
148
|
var isWindows = process.platform === "win32";
|
|
256
149
|
async function linkOrCopy(source, target) {
|
|
257
150
|
if (isWindows) {
|
|
258
|
-
|
|
151
|
+
try {
|
|
152
|
+
await fs2.symlink(source, target, "junction");
|
|
153
|
+
} catch {
|
|
154
|
+
await fs2.copy(source, target);
|
|
155
|
+
}
|
|
259
156
|
} else {
|
|
260
|
-
await
|
|
157
|
+
await fs2.symlink(source, target, "dir");
|
|
261
158
|
}
|
|
262
159
|
}
|
|
263
160
|
function expandPath(p) {
|
|
264
161
|
if (p.startsWith("~")) {
|
|
265
|
-
return
|
|
162
|
+
return path2.join(os2.homedir(), p.slice(1));
|
|
266
163
|
}
|
|
267
|
-
return
|
|
164
|
+
return path2.resolve(p);
|
|
268
165
|
}
|
|
269
166
|
function isLocalPath(p) {
|
|
270
167
|
if (p.startsWith(".") || p.startsWith("/") || p.startsWith("~")) {
|
|
@@ -285,33 +182,33 @@ function pathStartsWith(child, parent) {
|
|
|
285
182
|
return child.startsWith(parent);
|
|
286
183
|
}
|
|
287
184
|
function getLinkActionText() {
|
|
288
|
-
return
|
|
185
|
+
return "Linking";
|
|
289
186
|
}
|
|
290
187
|
function getLinkTypeText() {
|
|
291
|
-
return
|
|
188
|
+
return "linked to";
|
|
292
189
|
}
|
|
293
190
|
|
|
294
191
|
// src/core/paths.ts
|
|
295
192
|
var __filename2 = fileURLToPath(import.meta.url);
|
|
296
|
-
var __dirname2 =
|
|
193
|
+
var __dirname2 = path3.dirname(__filename2);
|
|
297
194
|
function getOloreHome() {
|
|
298
|
-
return
|
|
195
|
+
return path3.join(os3.homedir(), ".olore");
|
|
299
196
|
}
|
|
300
197
|
function getOlorePackagePath(name, version2) {
|
|
301
|
-
return
|
|
198
|
+
return path3.join(getOloreHome(), "packages", name, version2);
|
|
302
199
|
}
|
|
303
200
|
function isInstalledPackage(symlinkPath) {
|
|
304
201
|
try {
|
|
305
|
-
const target =
|
|
202
|
+
const target = fs3.readlinkSync(symlinkPath);
|
|
306
203
|
const oloreHome = getOloreHome();
|
|
307
204
|
return pathStartsWith(target, oloreHome);
|
|
308
205
|
} catch {
|
|
309
|
-
return
|
|
206
|
+
return fs3.existsSync(symlinkPath) && fs3.statSync(symlinkPath).isDirectory();
|
|
310
207
|
}
|
|
311
208
|
}
|
|
312
209
|
function getSymlinkTarget(symlinkPath) {
|
|
313
210
|
try {
|
|
314
|
-
return
|
|
211
|
+
return fs3.readlinkSync(symlinkPath);
|
|
315
212
|
} catch {
|
|
316
213
|
return null;
|
|
317
214
|
}
|
|
@@ -319,17 +216,17 @@ function getSymlinkTarget(symlinkPath) {
|
|
|
319
216
|
function getAgentPaths() {
|
|
320
217
|
const home = os3.homedir();
|
|
321
218
|
return {
|
|
322
|
-
claude:
|
|
323
|
-
codex:
|
|
324
|
-
opencode:
|
|
219
|
+
claude: path3.join(home, ".claude", "skills"),
|
|
220
|
+
codex: path3.join(home, ".codex", "skills"),
|
|
221
|
+
opencode: path3.join(home, ".config", "opencode", "skills")
|
|
325
222
|
};
|
|
326
223
|
}
|
|
327
224
|
function detectAgents() {
|
|
328
225
|
const agents = [];
|
|
329
226
|
const paths = getAgentPaths();
|
|
330
227
|
for (const [agent, agentPath] of Object.entries(paths)) {
|
|
331
|
-
const parentDir =
|
|
332
|
-
if (
|
|
228
|
+
const parentDir = path3.dirname(agentPath);
|
|
229
|
+
if (fs3.existsSync(parentDir)) {
|
|
333
230
|
agents.push(agent);
|
|
334
231
|
}
|
|
335
232
|
}
|
|
@@ -340,18 +237,18 @@ async function getInstalledPackages() {
|
|
|
340
237
|
const packages = [];
|
|
341
238
|
const seen = /* @__PURE__ */ new Set();
|
|
342
239
|
for (const [agent, skillsDir] of Object.entries(agentPaths)) {
|
|
343
|
-
if (!
|
|
240
|
+
if (!fs3.existsSync(skillsDir)) {
|
|
344
241
|
continue;
|
|
345
242
|
}
|
|
346
|
-
const entries =
|
|
243
|
+
const entries = fs3.readdirSync(skillsDir, { withFileTypes: true });
|
|
347
244
|
for (const entry of entries) {
|
|
348
245
|
const isValidEntry = entry.isDirectory() || entry.isSymbolicLink();
|
|
349
246
|
if (!isValidEntry || !entry.name.startsWith("olore-")) {
|
|
350
247
|
continue;
|
|
351
248
|
}
|
|
352
|
-
const pkgPath =
|
|
353
|
-
const lockPath =
|
|
354
|
-
if (!
|
|
249
|
+
const pkgPath = path3.join(skillsDir, entry.name);
|
|
250
|
+
const lockPath = path3.join(pkgPath, "olore-lock.json");
|
|
251
|
+
if (!fs3.existsSync(lockPath)) {
|
|
355
252
|
continue;
|
|
356
253
|
}
|
|
357
254
|
const pkgName = entry.name;
|
|
@@ -360,7 +257,7 @@ async function getInstalledPackages() {
|
|
|
360
257
|
}
|
|
361
258
|
seen.add(pkgName);
|
|
362
259
|
try {
|
|
363
|
-
const lock = JSON.parse(
|
|
260
|
+
const lock = JSON.parse(fs3.readFileSync(lockPath, "utf-8"));
|
|
364
261
|
const stats = await getDirectoryStats(pkgPath);
|
|
365
262
|
const symlinkTarget = getSymlinkTarget(pkgPath);
|
|
366
263
|
let installType;
|
|
@@ -383,27 +280,437 @@ async function getInstalledPackages() {
|
|
|
383
280
|
}
|
|
384
281
|
}
|
|
385
282
|
}
|
|
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 =
|
|
393
|
-
for (const entry of entries) {
|
|
394
|
-
const fullPath =
|
|
395
|
-
if (entry.isDirectory()) {
|
|
396
|
-
walk(fullPath);
|
|
397
|
-
} else if (entry.isFile()) {
|
|
398
|
-
files++;
|
|
399
|
-
size +=
|
|
400
|
-
}
|
|
401
|
-
}
|
|
283
|
+
return packages.sort((a, b) => a.name.localeCompare(b.name));
|
|
284
|
+
}
|
|
285
|
+
async function getDirectoryStats(dir) {
|
|
286
|
+
let files = 0;
|
|
287
|
+
let size = 0;
|
|
288
|
+
function walk(currentDir) {
|
|
289
|
+
const entries = fs3.readdirSync(currentDir, { withFileTypes: true });
|
|
290
|
+
for (const entry of entries) {
|
|
291
|
+
const fullPath = path3.join(currentDir, entry.name);
|
|
292
|
+
if (entry.isDirectory()) {
|
|
293
|
+
walk(fullPath);
|
|
294
|
+
} else if (entry.isFile()) {
|
|
295
|
+
files++;
|
|
296
|
+
size += fs3.statSync(fullPath).size;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
walk(dir);
|
|
301
|
+
return { files, size };
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// src/core/doctor.ts
|
|
305
|
+
async function diagnose() {
|
|
306
|
+
const issues = [];
|
|
307
|
+
const [dangling, orphaned, partial] = await Promise.all([
|
|
308
|
+
scanAgentDirs(),
|
|
309
|
+
scanOrphanedPackages(),
|
|
310
|
+
scanPartialInstalls()
|
|
311
|
+
]);
|
|
312
|
+
issues.push(...dangling, ...orphaned, ...partial);
|
|
313
|
+
return {
|
|
314
|
+
ok: issues.length === 0,
|
|
315
|
+
issues
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
async function scanAgentDirs() {
|
|
319
|
+
const issues = [];
|
|
320
|
+
const agentPaths = getAgentPaths();
|
|
321
|
+
for (const [agent, skillsDir] of Object.entries(agentPaths)) {
|
|
322
|
+
if (!fs4.existsSync(skillsDir)) {
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
let entries;
|
|
326
|
+
try {
|
|
327
|
+
entries = fs4.readdirSync(skillsDir, { withFileTypes: true });
|
|
328
|
+
} catch {
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
for (const entry of entries) {
|
|
332
|
+
if (!entry.name.startsWith("olore-")) {
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
if (!entry.isSymbolicLink()) {
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
const fullPath = path4.join(skillsDir, entry.name);
|
|
339
|
+
const target = readSymlinkTarget(fullPath);
|
|
340
|
+
if (target === null) {
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
const resolvedTarget = path4.resolve(skillsDir, target);
|
|
344
|
+
if (!fs4.existsSync(resolvedTarget)) {
|
|
345
|
+
issues.push({
|
|
346
|
+
type: "dangling-symlink",
|
|
347
|
+
path: fullPath,
|
|
348
|
+
target: resolvedTarget,
|
|
349
|
+
agent
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
return issues;
|
|
355
|
+
}
|
|
356
|
+
async function scanOrphanedPackages() {
|
|
357
|
+
const issues = [];
|
|
358
|
+
const packagesDir = path4.join(getOloreHome(), "packages");
|
|
359
|
+
if (!fs4.existsSync(packagesDir)) {
|
|
360
|
+
return issues;
|
|
361
|
+
}
|
|
362
|
+
const activeTargets = await collectActiveSymlinkTargets();
|
|
363
|
+
let nameEntries;
|
|
364
|
+
try {
|
|
365
|
+
nameEntries = fs4.readdirSync(packagesDir, { withFileTypes: true });
|
|
366
|
+
} catch {
|
|
367
|
+
return issues;
|
|
368
|
+
}
|
|
369
|
+
for (const nameEntry of nameEntries) {
|
|
370
|
+
if (!nameEntry.isDirectory()) {
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
const nameDir = path4.join(packagesDir, nameEntry.name);
|
|
374
|
+
let versionEntries;
|
|
375
|
+
try {
|
|
376
|
+
versionEntries = fs4.readdirSync(nameDir, { withFileTypes: true });
|
|
377
|
+
} catch {
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
for (const versionEntry of versionEntries) {
|
|
381
|
+
if (!versionEntry.isDirectory()) {
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
const versionDir = path4.join(nameDir, versionEntry.name);
|
|
385
|
+
const lockPath = path4.join(versionDir, "olore-lock.json");
|
|
386
|
+
if (!fs4.existsSync(lockPath)) {
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
const realPath = fs4.realpathSync(versionDir);
|
|
390
|
+
if (activeTargets.has(realPath)) {
|
|
391
|
+
continue;
|
|
392
|
+
}
|
|
393
|
+
const stats = getDirectoryStats2(versionDir);
|
|
394
|
+
issues.push({
|
|
395
|
+
type: "orphaned",
|
|
396
|
+
path: versionDir,
|
|
397
|
+
name: nameEntry.name,
|
|
398
|
+
version: versionEntry.name,
|
|
399
|
+
files: stats.files,
|
|
400
|
+
size: stats.size
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
return issues;
|
|
405
|
+
}
|
|
406
|
+
async function scanPartialInstalls() {
|
|
407
|
+
const issues = [];
|
|
408
|
+
const packagesDir = path4.join(getOloreHome(), "packages");
|
|
409
|
+
if (!fs4.existsSync(packagesDir)) {
|
|
410
|
+
return issues;
|
|
411
|
+
}
|
|
412
|
+
let nameEntries;
|
|
413
|
+
try {
|
|
414
|
+
nameEntries = fs4.readdirSync(packagesDir, { withFileTypes: true });
|
|
415
|
+
} catch {
|
|
416
|
+
return issues;
|
|
417
|
+
}
|
|
418
|
+
for (const nameEntry of nameEntries) {
|
|
419
|
+
if (!nameEntry.isDirectory()) {
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
422
|
+
const nameDir = path4.join(packagesDir, nameEntry.name);
|
|
423
|
+
let versionEntries;
|
|
424
|
+
try {
|
|
425
|
+
versionEntries = fs4.readdirSync(nameDir, { withFileTypes: true });
|
|
426
|
+
} catch {
|
|
427
|
+
continue;
|
|
428
|
+
}
|
|
429
|
+
for (const versionEntry of versionEntries) {
|
|
430
|
+
if (!versionEntry.isDirectory()) {
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
const versionDir = path4.join(nameDir, versionEntry.name);
|
|
434
|
+
const lockPath = path4.join(versionDir, "olore-lock.json");
|
|
435
|
+
if (!fs4.existsSync(lockPath)) {
|
|
436
|
+
issues.push({
|
|
437
|
+
type: "partial-install",
|
|
438
|
+
path: versionDir,
|
|
439
|
+
name: nameEntry.name,
|
|
440
|
+
version: versionEntry.name
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
return issues;
|
|
446
|
+
}
|
|
447
|
+
async function collectActiveSymlinkTargets() {
|
|
448
|
+
const targets = /* @__PURE__ */ new Set();
|
|
449
|
+
const agentPaths = getAgentPaths();
|
|
450
|
+
for (const skillsDir of Object.values(agentPaths)) {
|
|
451
|
+
if (!fs4.existsSync(skillsDir)) {
|
|
452
|
+
continue;
|
|
453
|
+
}
|
|
454
|
+
let entries;
|
|
455
|
+
try {
|
|
456
|
+
entries = fs4.readdirSync(skillsDir, { withFileTypes: true });
|
|
457
|
+
} catch {
|
|
458
|
+
continue;
|
|
459
|
+
}
|
|
460
|
+
for (const entry of entries) {
|
|
461
|
+
if (!entry.name.startsWith("olore-") || !entry.isSymbolicLink()) {
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
464
|
+
const fullPath = path4.join(skillsDir, entry.name);
|
|
465
|
+
try {
|
|
466
|
+
const realTarget = fs4.realpathSync(fullPath);
|
|
467
|
+
targets.add(realTarget);
|
|
468
|
+
} catch {
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
return targets;
|
|
473
|
+
}
|
|
474
|
+
async function pruneIssues(issues) {
|
|
475
|
+
const removed = [];
|
|
476
|
+
const failed = [];
|
|
477
|
+
for (const issue of issues) {
|
|
478
|
+
if (!pathExists(issue.path)) {
|
|
479
|
+
continue;
|
|
480
|
+
}
|
|
481
|
+
try {
|
|
482
|
+
let freedBytes = 0;
|
|
483
|
+
if (issue.type === "dangling-symlink") {
|
|
484
|
+
await fsExtra.remove(issue.path);
|
|
485
|
+
} else if (issue.type === "orphaned") {
|
|
486
|
+
freedBytes = issue.size;
|
|
487
|
+
await fsExtra.remove(issue.path);
|
|
488
|
+
await cleanEmptyParentDir(issue.path);
|
|
489
|
+
} else if (issue.type === "partial-install") {
|
|
490
|
+
const stats = getDirectoryStats2(issue.path);
|
|
491
|
+
freedBytes = stats.size;
|
|
492
|
+
await fsExtra.remove(issue.path);
|
|
493
|
+
await cleanEmptyParentDir(issue.path);
|
|
494
|
+
}
|
|
495
|
+
removed.push({ issue, freedBytes });
|
|
496
|
+
} catch (error) {
|
|
497
|
+
failed.push({
|
|
498
|
+
issue,
|
|
499
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
return { removed, failed };
|
|
504
|
+
}
|
|
505
|
+
function pathExists(p) {
|
|
506
|
+
try {
|
|
507
|
+
fs4.lstatSync(p);
|
|
508
|
+
return true;
|
|
509
|
+
} catch {
|
|
510
|
+
return false;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
function readSymlinkTarget(symlinkPath) {
|
|
514
|
+
try {
|
|
515
|
+
return fs4.readlinkSync(symlinkPath);
|
|
516
|
+
} catch {
|
|
517
|
+
return null;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
async function cleanEmptyParentDir(removedPath) {
|
|
521
|
+
const parentDir = path4.dirname(removedPath);
|
|
522
|
+
try {
|
|
523
|
+
const remaining = fs4.readdirSync(parentDir);
|
|
524
|
+
if (remaining.length === 0) {
|
|
525
|
+
await fsExtra.remove(parentDir);
|
|
526
|
+
}
|
|
527
|
+
} catch {
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
function getDirectoryStats2(dir) {
|
|
531
|
+
let files = 0;
|
|
532
|
+
let size = 0;
|
|
533
|
+
function walk(currentDir) {
|
|
534
|
+
let entries;
|
|
535
|
+
try {
|
|
536
|
+
entries = fs4.readdirSync(currentDir, { withFileTypes: true });
|
|
537
|
+
} catch {
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
for (const entry of entries) {
|
|
541
|
+
const fullPath = path4.join(currentDir, entry.name);
|
|
542
|
+
if (entry.isDirectory()) {
|
|
543
|
+
walk(fullPath);
|
|
544
|
+
} else if (entry.isFile()) {
|
|
545
|
+
try {
|
|
546
|
+
files++;
|
|
547
|
+
size += fs4.statSync(fullPath).size;
|
|
548
|
+
} catch {
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
walk(dir);
|
|
554
|
+
return { files, size };
|
|
555
|
+
}
|
|
556
|
+
function displayPath(p) {
|
|
557
|
+
const home = os4.homedir();
|
|
558
|
+
if (p.startsWith(home)) {
|
|
559
|
+
return "~" + p.slice(home.length);
|
|
560
|
+
}
|
|
561
|
+
return p;
|
|
562
|
+
}
|
|
563
|
+
function formatIssue(issue) {
|
|
564
|
+
switch (issue.type) {
|
|
565
|
+
case "dangling-symlink":
|
|
566
|
+
return `${displayPath(issue.path)} -> ${displayPath(issue.target)} (missing)`;
|
|
567
|
+
case "orphaned":
|
|
568
|
+
return `${displayPath(issue.path)} (no agent links, ${issue.files} files, ${formatBytes(issue.size)})`;
|
|
569
|
+
case "partial-install":
|
|
570
|
+
return `${displayPath(issue.path)} (missing olore-lock.json)`;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// src/commands/doctor.ts
|
|
575
|
+
async function doctor(options) {
|
|
576
|
+
if (!options.json) {
|
|
577
|
+
console.log(pc.bold("\nChecking installed packages...\n"));
|
|
578
|
+
}
|
|
579
|
+
const result = await diagnose();
|
|
580
|
+
if (options.json) {
|
|
581
|
+
console.log(JSON.stringify(result, null, 2));
|
|
582
|
+
if (!result.ok) {
|
|
583
|
+
process.exit(1);
|
|
584
|
+
}
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
if (result.ok) {
|
|
588
|
+
console.log(pc.green("No issues found. Everything looks good."));
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
const dangling = result.issues.filter((i) => i.type === "dangling-symlink");
|
|
592
|
+
const orphaned = result.issues.filter((i) => i.type === "orphaned");
|
|
593
|
+
const partial = result.issues.filter((i) => i.type === "partial-install");
|
|
594
|
+
console.log(
|
|
595
|
+
`Found ${pc.yellow(String(result.issues.length))} issue${result.issues.length === 1 ? "" : "s"}:
|
|
596
|
+
`
|
|
597
|
+
);
|
|
598
|
+
if (dangling.length > 0) {
|
|
599
|
+
console.log(pc.bold(`Dangling symlinks (${dangling.length}):`));
|
|
600
|
+
for (const issue of dangling) {
|
|
601
|
+
console.log(` ${formatIssue(issue)}`);
|
|
602
|
+
}
|
|
603
|
+
console.log("");
|
|
604
|
+
}
|
|
605
|
+
if (orphaned.length > 0) {
|
|
606
|
+
console.log(pc.bold(`Orphaned packages (${orphaned.length}):`));
|
|
607
|
+
for (const issue of orphaned) {
|
|
608
|
+
console.log(` ${formatIssue(issue)}`);
|
|
609
|
+
}
|
|
610
|
+
console.log("");
|
|
611
|
+
}
|
|
612
|
+
if (partial.length > 0) {
|
|
613
|
+
console.log(pc.bold(`Partial installs (${partial.length}):`));
|
|
614
|
+
for (const issue of partial) {
|
|
615
|
+
console.log(` ${formatIssue(issue)}`);
|
|
616
|
+
}
|
|
617
|
+
console.log("");
|
|
618
|
+
}
|
|
619
|
+
console.log(`Run ${pc.cyan("olore prune")} to fix these issues.`);
|
|
620
|
+
process.exit(1);
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// src/commands/init.ts
|
|
624
|
+
import fs5 from "fs";
|
|
625
|
+
import path5 from "path";
|
|
626
|
+
import readline from "readline";
|
|
627
|
+
import pc2 from "picocolors";
|
|
628
|
+
async function prompt(question, defaultValue) {
|
|
629
|
+
const rl = readline.createInterface({
|
|
630
|
+
input: process.stdin,
|
|
631
|
+
output: process.stdout
|
|
632
|
+
});
|
|
633
|
+
return new Promise((resolve) => {
|
|
634
|
+
const q = defaultValue ? `${question} (${defaultValue}): ` : `${question}: `;
|
|
635
|
+
rl.question(q, (answer) => {
|
|
636
|
+
rl.close();
|
|
637
|
+
resolve(answer || defaultValue || "");
|
|
638
|
+
});
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
async function init(options) {
|
|
642
|
+
const cwd = process.cwd();
|
|
643
|
+
const folderName = path5.basename(cwd);
|
|
644
|
+
if (fs5.existsSync(path5.join(cwd, "olore.config.json"))) {
|
|
645
|
+
throw new Error("olore.config.json already exists. This folder is already initialized.");
|
|
646
|
+
}
|
|
647
|
+
let name;
|
|
648
|
+
let version2;
|
|
649
|
+
let description;
|
|
650
|
+
if (options.yes) {
|
|
651
|
+
name = options.name || folderName;
|
|
652
|
+
version2 = options.version || "1.0.0";
|
|
653
|
+
description = `Documentation for ${name}`;
|
|
654
|
+
} else {
|
|
655
|
+
console.log(pc2.bold("\nInitialize olore documentation package\n"));
|
|
656
|
+
name = await prompt("Package name", options.name || folderName);
|
|
657
|
+
version2 = await prompt("Version", options.version || "1.0.0");
|
|
658
|
+
description = await prompt("Description", `Documentation for ${name}`);
|
|
659
|
+
}
|
|
660
|
+
const fullName = `olore-${name}-${version2}`;
|
|
661
|
+
const configContent = {
|
|
662
|
+
name,
|
|
663
|
+
version: version2,
|
|
664
|
+
description,
|
|
665
|
+
contentPath: "./docs",
|
|
666
|
+
outputPath: "./olore-package",
|
|
667
|
+
extensions: [".md", ".mdx"],
|
|
668
|
+
exclude: []
|
|
669
|
+
};
|
|
670
|
+
fs5.writeFileSync(
|
|
671
|
+
path5.join(cwd, "olore.config.json"),
|
|
672
|
+
JSON.stringify(configContent, null, 2) + "\n"
|
|
673
|
+
);
|
|
674
|
+
const docsDir = path5.join(cwd, "docs");
|
|
675
|
+
if (!fs5.existsSync(docsDir)) {
|
|
676
|
+
fs5.mkdirSync(docsDir);
|
|
677
|
+
fs5.writeFileSync(
|
|
678
|
+
path5.join(docsDir, "getting-started.md"),
|
|
679
|
+
`# Getting Started with ${name}
|
|
680
|
+
|
|
681
|
+
Add your documentation content here.
|
|
682
|
+
|
|
683
|
+
## Overview
|
|
684
|
+
|
|
685
|
+
Describe what this documentation covers.
|
|
686
|
+
|
|
687
|
+
## Topics
|
|
688
|
+
|
|
689
|
+
- Topic 1
|
|
690
|
+
- Topic 2
|
|
691
|
+
`
|
|
692
|
+
);
|
|
402
693
|
}
|
|
403
|
-
|
|
404
|
-
|
|
694
|
+
console.log(pc2.bold(`
|
|
695
|
+
Initialized olore package: `) + pc2.cyan(fullName));
|
|
696
|
+
console.log("");
|
|
697
|
+
console.log(pc2.gray("Created:"));
|
|
698
|
+
console.log(pc2.green(" \u2713 olore.config.json"));
|
|
699
|
+
console.log(pc2.green(" \u2713 docs/") + pc2.gray(" (add your documentation here)"));
|
|
700
|
+
console.log("");
|
|
701
|
+
console.log(pc2.gray("Next steps:"));
|
|
702
|
+
console.log(" 1. Add your .md files to the " + pc2.cyan("docs/") + " folder");
|
|
703
|
+
console.log(" 2. Run " + pc2.cyan("/olore-docs-packager-1.0.0") + " in Claude Code to build");
|
|
704
|
+
console.log(" 3. Run " + pc2.cyan("olore install ./olore-package") + " to install");
|
|
705
|
+
console.log("");
|
|
405
706
|
}
|
|
406
707
|
|
|
708
|
+
// src/commands/install.ts
|
|
709
|
+
import path6 from "path";
|
|
710
|
+
import fs6 from "fs-extra";
|
|
711
|
+
import ora from "ora";
|
|
712
|
+
import pc3 from "picocolors";
|
|
713
|
+
|
|
407
714
|
// src/core/registry.ts
|
|
408
715
|
var RegistryError = class extends Error {
|
|
409
716
|
constructor(message, code) {
|
|
@@ -435,6 +742,22 @@ async function fetchWithTimeout(url, timeout = DOWNLOAD_TIMEOUT) {
|
|
|
435
742
|
clearTimeout(timeoutId);
|
|
436
743
|
}
|
|
437
744
|
}
|
|
745
|
+
async function fetchPackageIndex() {
|
|
746
|
+
const url = `${REGISTRY_URL}/index.json`;
|
|
747
|
+
const response = await fetchWithTimeout(url);
|
|
748
|
+
if (response.status === 404) {
|
|
749
|
+
throw new RegistryError("Registry not found", "NOT_FOUND");
|
|
750
|
+
}
|
|
751
|
+
if (!response.ok) {
|
|
752
|
+
throw new RegistryError(`Failed to fetch registry: ${response.status}`, "NETWORK_ERROR");
|
|
753
|
+
}
|
|
754
|
+
try {
|
|
755
|
+
const data = await response.json();
|
|
756
|
+
return data;
|
|
757
|
+
} catch {
|
|
758
|
+
throw new RegistryError("Invalid registry response", "INVALID_RESPONSE");
|
|
759
|
+
}
|
|
760
|
+
}
|
|
438
761
|
async function fetchPackageVersions(name) {
|
|
439
762
|
const url = `${REGISTRY_URL}/packages/${name}.json`;
|
|
440
763
|
const response = await fetchWithTimeout(url);
|
|
@@ -468,22 +791,22 @@ async function resolveVersion(name, version2) {
|
|
|
468
791
|
// src/commands/install.ts
|
|
469
792
|
async function installFromLocal(localPath) {
|
|
470
793
|
const fullPath = expandPath(localPath);
|
|
471
|
-
if (!await
|
|
794
|
+
if (!await fs6.pathExists(fullPath)) {
|
|
472
795
|
throw new Error(`Path not found: ${fullPath}`);
|
|
473
796
|
}
|
|
474
|
-
const stat = await
|
|
797
|
+
const stat = await fs6.stat(fullPath);
|
|
475
798
|
if (!stat.isDirectory()) {
|
|
476
799
|
throw new Error(`Not a directory: ${fullPath}`);
|
|
477
800
|
}
|
|
478
|
-
const lockPath =
|
|
479
|
-
if (!await
|
|
801
|
+
const lockPath = path6.join(fullPath, "olore-lock.json");
|
|
802
|
+
if (!await fs6.pathExists(lockPath)) {
|
|
480
803
|
throw new Error(`Missing olore-lock.json in ${fullPath}`);
|
|
481
804
|
}
|
|
482
|
-
const skillPath =
|
|
483
|
-
if (!await
|
|
805
|
+
const skillPath = path6.join(fullPath, "SKILL.md");
|
|
806
|
+
if (!await fs6.pathExists(skillPath)) {
|
|
484
807
|
throw new Error(`Missing SKILL.md in ${fullPath}. Run /generate-agent-skills first.`);
|
|
485
808
|
}
|
|
486
|
-
const lock = await
|
|
809
|
+
const lock = await fs6.readJson(lockPath);
|
|
487
810
|
const packageName = lock.name;
|
|
488
811
|
const packageVersion = lock.version;
|
|
489
812
|
if (!packageName) {
|
|
@@ -493,72 +816,57 @@ async function installFromLocal(localPath) {
|
|
|
493
816
|
throw new Error(`Invalid olore-lock.json: missing "version" field`);
|
|
494
817
|
}
|
|
495
818
|
const skillName = `olore-${packageName}-${packageVersion}`;
|
|
496
|
-
const skillContent = await
|
|
819
|
+
const skillContent = await fs6.readFile(skillPath, "utf-8");
|
|
497
820
|
const nameMatch = skillContent.match(/^name:\s*(.+)$/m);
|
|
498
821
|
const skillMdName = nameMatch ? nameMatch[1].trim() : null;
|
|
499
822
|
if (skillMdName !== skillName) {
|
|
500
|
-
console.log(
|
|
823
|
+
console.log(pc3.yellow(`
|
|
501
824
|
Warning: SKILL.md name mismatch`));
|
|
502
|
-
console.log(
|
|
503
|
-
console.log(
|
|
504
|
-
console.log(
|
|
825
|
+
console.log(pc3.gray(` Expected: ${skillName}`));
|
|
826
|
+
console.log(pc3.gray(` Found: ${skillMdName || "(none)"}`));
|
|
827
|
+
console.log(pc3.yellow(` Updating SKILL.md to fix...`));
|
|
505
828
|
const updatedContent = skillMdName ? skillContent.replace(/^name:\s*.+$/m, `name: ${skillName}`) : skillContent.replace(/^---\n/, `---
|
|
506
829
|
name: ${skillName}
|
|
507
830
|
`);
|
|
508
|
-
await
|
|
831
|
+
await fs6.writeFile(skillPath, updatedContent);
|
|
509
832
|
}
|
|
510
|
-
console.log(
|
|
833
|
+
console.log(pc3.bold(`
|
|
511
834
|
Installing ${packageName}@${packageVersion} from local path...
|
|
512
835
|
`));
|
|
513
836
|
const agents = detectAgents();
|
|
514
837
|
if (agents.length === 0) {
|
|
515
|
-
console.log(
|
|
838
|
+
console.log(pc3.yellow("No agents detected. Creating directories anyway."));
|
|
516
839
|
}
|
|
517
840
|
const agentPaths = getAgentPaths();
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
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}`));
|
|
841
|
+
const olorePath = getOlorePackagePath(packageName, packageVersion);
|
|
842
|
+
const spinner = ora("Copying to ~/.olore...").start();
|
|
843
|
+
await fs6.ensureDir(path6.dirname(olorePath));
|
|
844
|
+
await fs6.remove(olorePath);
|
|
845
|
+
await fs6.copy(fullPath, olorePath);
|
|
846
|
+
spinner.succeed(`Copied to ${pc3.gray(olorePath)}`);
|
|
847
|
+
const linkSpinner = ora("Linking to agent directories...").start();
|
|
848
|
+
const linked = [];
|
|
849
|
+
for (const [agent, skillsDir] of Object.entries(agentPaths)) {
|
|
850
|
+
const targetDir = path6.join(skillsDir, skillName);
|
|
851
|
+
await fs6.ensureDir(skillsDir);
|
|
852
|
+
await fs6.remove(targetDir);
|
|
853
|
+
await linkOrCopy(olorePath, targetDir);
|
|
854
|
+
linked.push(`${pc3.green("\u2713")} ${agent} ${pc3.gray("\u2192")} ${pc3.gray(targetDir)}`);
|
|
550
855
|
}
|
|
856
|
+
linkSpinner.stop();
|
|
857
|
+
linked.forEach((line) => console.log(` ${line}`));
|
|
858
|
+
console.log(pc3.gray(` \u2514\u2500 all linked to ${olorePath}`));
|
|
551
859
|
console.log("");
|
|
552
|
-
console.log(
|
|
860
|
+
console.log(pc3.green("Installation complete!"));
|
|
553
861
|
console.log("");
|
|
554
|
-
console.log(
|
|
555
|
-
console.log(
|
|
556
|
-
console.log(
|
|
557
|
-
console.log(
|
|
862
|
+
console.log(pc3.gray("Skill is now available as:"));
|
|
863
|
+
console.log(pc3.cyan(` /${skillName}`) + pc3.gray(" (Claude Code)"));
|
|
864
|
+
console.log(pc3.cyan(` $${skillName}`) + pc3.gray(" (Codex)"));
|
|
865
|
+
console.log(pc3.cyan(` ${skillName}`) + pc3.gray(" (OpenCode)"));
|
|
558
866
|
}
|
|
559
867
|
async function install(pkg, options) {
|
|
560
868
|
if (options.force) {
|
|
561
|
-
console.log(
|
|
869
|
+
console.log(pc3.cyan("\n\u2728 May the Skill be with you.\n"));
|
|
562
870
|
}
|
|
563
871
|
if (isLocalPath(pkg)) {
|
|
564
872
|
await installFromLocal(pkg);
|
|
@@ -579,7 +887,7 @@ function parsePackageSpec(spec) {
|
|
|
579
887
|
async function installFromRemote(pkg, optionsVersion) {
|
|
580
888
|
const { name, version: specVersion } = parsePackageSpec(pkg);
|
|
581
889
|
const requestedVersion = optionsVersion || specVersion || "latest";
|
|
582
|
-
console.log(
|
|
890
|
+
console.log(pc3.bold(`
|
|
583
891
|
Installing ${name}@${requestedVersion} from registry...
|
|
584
892
|
`));
|
|
585
893
|
const spinner = ora("Fetching package info...").start();
|
|
@@ -591,18 +899,18 @@ Installing ${name}@${requestedVersion} from registry...
|
|
|
591
899
|
spinner.fail("Failed to resolve package");
|
|
592
900
|
if (error instanceof RegistryError) {
|
|
593
901
|
if (error.code === "NOT_FOUND") {
|
|
594
|
-
console.log(
|
|
902
|
+
console.log(pc3.yellow(`
|
|
595
903
|
Package "${name}" not found in registry.`));
|
|
596
|
-
console.log(
|
|
597
|
-
console.log(
|
|
904
|
+
console.log(pc3.gray("\nFor local packages, use a path:"));
|
|
905
|
+
console.log(pc3.cyan(` olore install ./vault/packages/${name}/<version>`));
|
|
598
906
|
console.log("");
|
|
599
907
|
} else if (error.code === "NETWORK_ERROR" || error.code === "TIMEOUT") {
|
|
600
|
-
console.log(
|
|
908
|
+
console.log(pc3.red(`
|
|
601
909
|
Network error: ${error.message}`));
|
|
602
|
-
console.log(
|
|
910
|
+
console.log(pc3.gray("Please check your internet connection and try again."));
|
|
603
911
|
}
|
|
604
912
|
} else {
|
|
605
|
-
console.log(
|
|
913
|
+
console.log(pc3.red(`
|
|
606
914
|
Error: ${error instanceof Error ? error.message : "Unknown error"}`));
|
|
607
915
|
}
|
|
608
916
|
process.exit(1);
|
|
@@ -610,114 +918,74 @@ Error: ${error instanceof Error ? error.message : "Unknown error"}`));
|
|
|
610
918
|
const skillName = `olore-${name}-${versionInfo.version}`;
|
|
611
919
|
const agents = detectAgents();
|
|
612
920
|
if (agents.length === 0) {
|
|
613
|
-
console.log(
|
|
921
|
+
console.log(pc3.yellow("No agents detected. Creating directories anyway."));
|
|
614
922
|
}
|
|
615
923
|
const agentPaths = getAgentPaths();
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
if (error
|
|
625
|
-
|
|
626
|
-
|
|
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
|
-
}
|
|
924
|
+
const olorePath = getOlorePackagePath(name, versionInfo.version);
|
|
925
|
+
const downloadSpinner = ora("Downloading package...").start();
|
|
926
|
+
try {
|
|
927
|
+
await downloadAndInstall(versionInfo.downloadUrl, olorePath, versionInfo.integrity);
|
|
928
|
+
downloadSpinner.succeed(`Downloaded to ${pc3.gray(olorePath)}`);
|
|
929
|
+
} catch (error) {
|
|
930
|
+
downloadSpinner.fail("Download failed");
|
|
931
|
+
if (error instanceof DownloadError) {
|
|
932
|
+
if (error.code === "CHECKSUM_MISMATCH") {
|
|
933
|
+
console.log(pc3.red("\nChecksum verification failed!"));
|
|
934
|
+
console.log(pc3.gray("The downloaded package may be corrupted or tampered with."));
|
|
632
935
|
} else {
|
|
633
|
-
console.log(
|
|
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(`
|
|
936
|
+
console.log(pc3.red(`
|
|
668
937
|
Download error: ${error.message}`));
|
|
669
|
-
}
|
|
670
|
-
} else {
|
|
671
|
-
console.log(pc2.red(`
|
|
672
|
-
Error: ${error instanceof Error ? error.message : "Unknown error"}`));
|
|
673
938
|
}
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
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)}`);
|
|
939
|
+
} else {
|
|
940
|
+
console.log(pc3.red(`
|
|
941
|
+
Error: ${error instanceof Error ? error.message : "Unknown error"}`));
|
|
684
942
|
}
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
943
|
+
process.exit(1);
|
|
944
|
+
}
|
|
945
|
+
const linkSpinner = ora("Linking to agent directories...").start();
|
|
946
|
+
const linked = [];
|
|
947
|
+
for (const [agent, skillsDir] of Object.entries(agentPaths)) {
|
|
948
|
+
const targetDir = path6.join(skillsDir, skillName);
|
|
949
|
+
await fs6.ensureDir(skillsDir);
|
|
950
|
+
await fs6.remove(targetDir);
|
|
951
|
+
await linkOrCopy(olorePath, targetDir);
|
|
952
|
+
linked.push(`${pc3.green("\u2713")} ${agent} ${pc3.gray("\u2192")} ${pc3.gray(targetDir)}`);
|
|
688
953
|
}
|
|
954
|
+
linkSpinner.stop();
|
|
955
|
+
linked.forEach((line) => console.log(` ${line}`));
|
|
956
|
+
console.log(pc3.gray(` \u2514\u2500 all linked to ${olorePath}`));
|
|
689
957
|
console.log("");
|
|
690
|
-
console.log(
|
|
958
|
+
console.log(pc3.green("Installation complete!"));
|
|
691
959
|
console.log("");
|
|
692
|
-
console.log(
|
|
693
|
-
console.log(
|
|
694
|
-
console.log(
|
|
695
|
-
console.log(
|
|
960
|
+
console.log(pc3.gray("Skill is now available as:"));
|
|
961
|
+
console.log(pc3.cyan(` /${skillName}`) + pc3.gray(" (Claude Code)"));
|
|
962
|
+
console.log(pc3.cyan(` $${skillName}`) + pc3.gray(" (Codex)"));
|
|
963
|
+
console.log(pc3.cyan(` ${skillName}`) + pc3.gray(" (OpenCode)"));
|
|
696
964
|
}
|
|
697
965
|
|
|
698
966
|
// src/commands/link.ts
|
|
699
|
-
import
|
|
700
|
-
import
|
|
967
|
+
import path7 from "path";
|
|
968
|
+
import fs7 from "fs-extra";
|
|
701
969
|
import ora2 from "ora";
|
|
702
|
-
import
|
|
970
|
+
import pc4 from "picocolors";
|
|
703
971
|
async function link(localPath) {
|
|
704
972
|
const fullPath = expandPath(localPath);
|
|
705
|
-
if (!await
|
|
973
|
+
if (!await fs7.pathExists(fullPath)) {
|
|
706
974
|
throw new Error(`Path not found: ${fullPath}`);
|
|
707
975
|
}
|
|
708
|
-
const stat = await
|
|
976
|
+
const stat = await fs7.stat(fullPath);
|
|
709
977
|
if (!stat.isDirectory()) {
|
|
710
978
|
throw new Error(`Not a directory: ${fullPath}`);
|
|
711
979
|
}
|
|
712
|
-
const lockPath =
|
|
713
|
-
if (!await
|
|
980
|
+
const lockPath = path7.join(fullPath, "olore-lock.json");
|
|
981
|
+
if (!await fs7.pathExists(lockPath)) {
|
|
714
982
|
throw new Error(`Missing olore-lock.json in ${fullPath}`);
|
|
715
983
|
}
|
|
716
|
-
const skillPath =
|
|
717
|
-
if (!await
|
|
984
|
+
const skillPath = path7.join(fullPath, "SKILL.md");
|
|
985
|
+
if (!await fs7.pathExists(skillPath)) {
|
|
718
986
|
throw new Error(`Missing SKILL.md in ${fullPath}. Run /build-docs first.`);
|
|
719
987
|
}
|
|
720
|
-
const lock = await
|
|
988
|
+
const lock = await fs7.readJson(lockPath);
|
|
721
989
|
const packageName = lock.name;
|
|
722
990
|
const packageVersion = lock.version;
|
|
723
991
|
if (!packageName) {
|
|
@@ -727,87 +995,96 @@ async function link(localPath) {
|
|
|
727
995
|
throw new Error(`Invalid olore-lock.json: missing "version" field`);
|
|
728
996
|
}
|
|
729
997
|
const skillName = `olore-${packageName}-${packageVersion}`;
|
|
730
|
-
const skillContent = await
|
|
998
|
+
const skillContent = await fs7.readFile(skillPath, "utf-8");
|
|
731
999
|
const nameMatch = skillContent.match(/^name:\s*(.+)$/m);
|
|
732
1000
|
const skillMdName = nameMatch ? nameMatch[1].trim() : null;
|
|
733
1001
|
if (skillMdName !== skillName) {
|
|
734
|
-
console.log(
|
|
1002
|
+
console.log(pc4.yellow(`
|
|
735
1003
|
Warning: SKILL.md name mismatch`));
|
|
736
|
-
console.log(
|
|
737
|
-
console.log(
|
|
738
|
-
console.log(
|
|
1004
|
+
console.log(pc4.gray(` Expected: ${skillName}`));
|
|
1005
|
+
console.log(pc4.gray(` Found: ${skillMdName || "(none)"}`));
|
|
1006
|
+
console.log(pc4.yellow(` Updating SKILL.md to fix...`));
|
|
739
1007
|
const updatedContent = skillMdName ? skillContent.replace(/^name:\s*.+$/m, `name: ${skillName}`) : skillContent.replace(/^---\n/, `---
|
|
740
1008
|
name: ${skillName}
|
|
741
1009
|
`);
|
|
742
|
-
await
|
|
1010
|
+
await fs7.writeFile(skillPath, updatedContent);
|
|
743
1011
|
}
|
|
744
|
-
console.log(
|
|
1012
|
+
console.log(pc4.bold(`
|
|
745
1013
|
Linking ${packageName}@${packageVersion}...
|
|
746
1014
|
`));
|
|
747
1015
|
const agents = detectAgents();
|
|
748
1016
|
if (agents.length === 0) {
|
|
749
|
-
console.log(
|
|
1017
|
+
console.log(pc4.yellow("No agents detected. Creating directories anyway."));
|
|
750
1018
|
}
|
|
751
1019
|
const agentPaths = getAgentPaths();
|
|
752
1020
|
const spinner = ora2(`${getLinkActionText()}...`).start();
|
|
753
1021
|
const linked = [];
|
|
754
1022
|
for (const [agent, skillsDir] of Object.entries(agentPaths)) {
|
|
755
|
-
const targetDir =
|
|
756
|
-
await
|
|
757
|
-
await
|
|
1023
|
+
const targetDir = path7.join(skillsDir, skillName);
|
|
1024
|
+
await fs7.ensureDir(skillsDir);
|
|
1025
|
+
await fs7.remove(targetDir);
|
|
758
1026
|
await linkOrCopy(fullPath, targetDir);
|
|
759
|
-
linked.push(`${
|
|
1027
|
+
linked.push(`${pc4.blue("\u26D3")} ${agent} ${pc4.gray("\u2192")} ${pc4.gray(targetDir)}`);
|
|
760
1028
|
}
|
|
761
1029
|
spinner.stop();
|
|
762
1030
|
linked.forEach((line) => console.log(` ${line}`));
|
|
763
|
-
console.log(
|
|
1031
|
+
console.log(pc4.gray(` \u2514\u2500 ${getLinkTypeText()} ${fullPath}`));
|
|
764
1032
|
console.log("");
|
|
765
|
-
console.log(
|
|
1033
|
+
console.log(pc4.blue("Link complete!"));
|
|
766
1034
|
console.log("");
|
|
767
|
-
console.log(
|
|
768
|
-
console.log(
|
|
769
|
-
console.log(
|
|
770
|
-
console.log(
|
|
1035
|
+
console.log(pc4.gray("Skill is now available as:"));
|
|
1036
|
+
console.log(pc4.cyan(` /${skillName}`) + pc4.gray(" (Claude Code)"));
|
|
1037
|
+
console.log(pc4.cyan(` $${skillName}`) + pc4.gray(" (Codex)"));
|
|
1038
|
+
console.log(pc4.cyan(` ${skillName}`) + pc4.gray(" (OpenCode)"));
|
|
771
1039
|
console.log("");
|
|
772
|
-
console.log(
|
|
773
|
-
console.log(
|
|
774
|
-
console.log(pc3.gray("On Windows, re-run link after changes."));
|
|
1040
|
+
console.log(pc4.gray(`Development mode: ${getLinkTypeText()} source (bypasses ~/.olore).`));
|
|
1041
|
+
console.log(pc4.gray("Changes to source are immediately visible."));
|
|
775
1042
|
console.log(
|
|
776
|
-
|
|
1043
|
+
pc4.gray("Use ") + pc4.cyan("olore install") + pc4.gray(" for a stable copy in ~/.olore.")
|
|
777
1044
|
);
|
|
778
1045
|
}
|
|
779
1046
|
|
|
780
1047
|
// src/commands/list.ts
|
|
781
|
-
import
|
|
1048
|
+
import pc5 from "picocolors";
|
|
782
1049
|
async function list(options) {
|
|
783
1050
|
const packages = await getInstalledPackages();
|
|
784
1051
|
if (packages.length === 0) {
|
|
785
|
-
console.log(
|
|
786
|
-
console.log(
|
|
787
|
-
console.log(`Run ${
|
|
1052
|
+
console.log(pc5.yellow("\nI find your lack of skills disturbing.\n"));
|
|
1053
|
+
console.log(pc5.gray("No packages installed."));
|
|
1054
|
+
console.log(`Run ${pc5.cyan("olore install <package>")} to install documentation.`);
|
|
788
1055
|
return;
|
|
789
1056
|
}
|
|
790
1057
|
if (options.json) {
|
|
791
|
-
|
|
1058
|
+
const { issues: issues2 } = await diagnose();
|
|
1059
|
+
console.log(JSON.stringify({ packages, issueCount: issues2.length }, null, 2));
|
|
792
1060
|
return;
|
|
793
1061
|
}
|
|
794
|
-
console.log(
|
|
1062
|
+
console.log(pc5.bold("\nInstalled packages:\n"));
|
|
795
1063
|
console.log(
|
|
796
|
-
|
|
1064
|
+
pc5.gray(
|
|
797
1065
|
"PACKAGE".padEnd(25) + "VERSION".padEnd(12) + "TYPE".padEnd(10) + "FILES".padStart(8) + "SIZE".padStart(12)
|
|
798
1066
|
)
|
|
799
1067
|
);
|
|
800
|
-
console.log(
|
|
1068
|
+
console.log(pc5.gray("-".repeat(67)));
|
|
801
1069
|
for (const pkg of packages) {
|
|
802
|
-
const typeLabel = pkg.installType === "linked" ?
|
|
1070
|
+
const typeLabel = pkg.installType === "linked" ? pc5.blue("linked") : pkg.installType;
|
|
803
1071
|
console.log(
|
|
804
1072
|
pkg.name.padEnd(25) + pkg.version.padEnd(12) + typeLabel.padEnd(10) + String(pkg.files).padStart(8) + formatSize(pkg.size).padStart(12)
|
|
805
1073
|
);
|
|
806
1074
|
}
|
|
807
|
-
console.log(
|
|
808
|
-
console.log(
|
|
1075
|
+
console.log(pc5.gray("-".repeat(67)));
|
|
1076
|
+
console.log(pc5.gray(`Total: ${packages.length} packages`));
|
|
809
1077
|
console.log("");
|
|
810
|
-
console.log(
|
|
1078
|
+
console.log(pc5.gray("Types: installed = in ~/.olore, linked = dev symlink, copied = legacy"));
|
|
1079
|
+
const { issues } = await diagnose();
|
|
1080
|
+
if (issues.length > 0) {
|
|
1081
|
+
console.log("");
|
|
1082
|
+
console.log(
|
|
1083
|
+
pc5.yellow(
|
|
1084
|
+
`\u26A0 ${issues.length} issue${issues.length === 1 ? "" : "s"} detected. Run ${pc5.cyan("olore doctor")} for details.`
|
|
1085
|
+
)
|
|
1086
|
+
);
|
|
1087
|
+
}
|
|
811
1088
|
}
|
|
812
1089
|
function formatSize(bytes) {
|
|
813
1090
|
if (bytes < 1024) return `${bytes} B`;
|
|
@@ -816,54 +1093,129 @@ function formatSize(bytes) {
|
|
|
816
1093
|
}
|
|
817
1094
|
|
|
818
1095
|
// src/commands/order66.ts
|
|
819
|
-
import
|
|
820
|
-
import
|
|
1096
|
+
import path8 from "path";
|
|
1097
|
+
import fs8 from "fs-extra";
|
|
821
1098
|
import ora3 from "ora";
|
|
822
|
-
import
|
|
1099
|
+
import pc6 from "picocolors";
|
|
823
1100
|
async function order66() {
|
|
824
|
-
console.log(
|
|
825
|
-
console.log(
|
|
1101
|
+
console.log(pc6.red("\n\u26A0\uFE0F Execute Order 66?\n"));
|
|
1102
|
+
console.log(pc6.yellow("This will remove ALL installed documentation packages."));
|
|
826
1103
|
const packages = await getInstalledPackages();
|
|
827
1104
|
if (packages.length === 0) {
|
|
828
|
-
console.log(
|
|
1105
|
+
console.log(pc6.gray("\nNo packages to remove. The Jedi are already gone."));
|
|
829
1106
|
return;
|
|
830
1107
|
}
|
|
831
|
-
console.log(
|
|
1108
|
+
console.log(pc6.gray(`
|
|
832
1109
|
Packages to be removed: ${packages.length}`));
|
|
833
1110
|
for (const pkg of packages) {
|
|
834
|
-
console.log(
|
|
1111
|
+
console.log(pc6.gray(` - ${pkg.name}@${pkg.version}`));
|
|
835
1112
|
}
|
|
836
|
-
console.log(
|
|
1113
|
+
console.log(pc6.red('\n"It will be done, my lord."\n'));
|
|
837
1114
|
const spinner = ora3("Executing Order 66...").start();
|
|
838
1115
|
let removedCount = 0;
|
|
839
1116
|
const agentPaths = getAgentPaths();
|
|
840
1117
|
for (const pkg of packages) {
|
|
841
1118
|
const skillName = `olore-${pkg.name}-${pkg.version}`;
|
|
842
1119
|
for (const [, basePath] of Object.entries(agentPaths)) {
|
|
843
|
-
const skillPath =
|
|
844
|
-
if (await
|
|
845
|
-
await
|
|
1120
|
+
const skillPath = path8.join(basePath, skillName);
|
|
1121
|
+
if (await fs8.pathExists(skillPath)) {
|
|
1122
|
+
await fs8.remove(skillPath);
|
|
846
1123
|
}
|
|
847
1124
|
}
|
|
848
1125
|
if (pkg.installType === "installed") {
|
|
849
1126
|
const olorePath = getOlorePackagePath(pkg.name, pkg.version);
|
|
850
|
-
if (await
|
|
851
|
-
await
|
|
1127
|
+
if (await fs8.pathExists(olorePath)) {
|
|
1128
|
+
await fs8.remove(olorePath);
|
|
852
1129
|
}
|
|
853
1130
|
}
|
|
854
1131
|
removedCount++;
|
|
855
1132
|
}
|
|
856
1133
|
spinner.succeed(`Removed ${removedCount} packages`);
|
|
857
|
-
console.log(
|
|
1134
|
+
console.log(pc6.gray("\nThe Jedi have been eliminated."));
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
// src/commands/prune.ts
|
|
1138
|
+
import pc7 from "picocolors";
|
|
1139
|
+
async function prune(options) {
|
|
1140
|
+
if (!options.json) {
|
|
1141
|
+
console.log(pc7.bold("\nScanning for issues...\n"));
|
|
1142
|
+
}
|
|
1143
|
+
const { issues } = await diagnose();
|
|
1144
|
+
if (issues.length === 0) {
|
|
1145
|
+
if (options.json) {
|
|
1146
|
+
console.log(JSON.stringify({ removed: [], failed: [] }, null, 2));
|
|
1147
|
+
} else {
|
|
1148
|
+
console.log(pc7.green("Nothing to prune. Everything is clean."));
|
|
1149
|
+
}
|
|
1150
|
+
return;
|
|
1151
|
+
}
|
|
1152
|
+
if (options.dryRun) {
|
|
1153
|
+
if (options.json) {
|
|
1154
|
+
console.log(JSON.stringify({ dryRun: true, wouldRemove: issues }, null, 2));
|
|
1155
|
+
return;
|
|
1156
|
+
}
|
|
1157
|
+
console.log(
|
|
1158
|
+
pc7.yellow(`Would remove ${issues.length} item${issues.length === 1 ? "" : "s"}:
|
|
1159
|
+
`)
|
|
1160
|
+
);
|
|
1161
|
+
for (const issue of issues) {
|
|
1162
|
+
console.log(` ${issueLabel(issue.type)} ${displayPath(issue.path)}`);
|
|
1163
|
+
}
|
|
1164
|
+
console.log(`
|
|
1165
|
+
Run ${pc7.cyan("olore prune")} (without --dry-run) to remove them.`);
|
|
1166
|
+
return;
|
|
1167
|
+
}
|
|
1168
|
+
const result = await pruneIssues(issues);
|
|
1169
|
+
if (options.json) {
|
|
1170
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1171
|
+
return;
|
|
1172
|
+
}
|
|
1173
|
+
if (result.removed.length > 0) {
|
|
1174
|
+
let totalFreed = 0;
|
|
1175
|
+
console.log(`Removed ${result.removed.length} item${result.removed.length === 1 ? "" : "s"}:`);
|
|
1176
|
+
for (const entry of result.removed) {
|
|
1177
|
+
totalFreed += entry.freedBytes;
|
|
1178
|
+
console.log(
|
|
1179
|
+
` ${pc7.green("\u2713")} ${displayPath(entry.issue.path)} (${issueLabel(entry.issue.type)})`
|
|
1180
|
+
);
|
|
1181
|
+
}
|
|
1182
|
+
if (totalFreed > 0) {
|
|
1183
|
+
console.log(`
|
|
1184
|
+
${formatBytes(totalFreed)} freed.`);
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
if (result.failed.length > 0) {
|
|
1188
|
+
console.log("");
|
|
1189
|
+
console.log(
|
|
1190
|
+
pc7.red(
|
|
1191
|
+
`Failed to remove ${result.failed.length} item${result.failed.length === 1 ? "" : "s"}:`
|
|
1192
|
+
)
|
|
1193
|
+
);
|
|
1194
|
+
for (const entry of result.failed) {
|
|
1195
|
+
console.log(` ${pc7.red("\u2717")} ${displayPath(entry.issue.path)}: ${entry.error}`);
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
function issueLabel(type) {
|
|
1200
|
+
switch (type) {
|
|
1201
|
+
case "dangling-symlink":
|
|
1202
|
+
return "dangling symlink";
|
|
1203
|
+
case "orphaned":
|
|
1204
|
+
return "orphaned package";
|
|
1205
|
+
case "partial-install":
|
|
1206
|
+
return "partial install";
|
|
1207
|
+
default:
|
|
1208
|
+
return type;
|
|
1209
|
+
}
|
|
858
1210
|
}
|
|
859
1211
|
|
|
860
1212
|
// src/commands/remove.ts
|
|
861
|
-
import
|
|
862
|
-
import
|
|
1213
|
+
import path9 from "path";
|
|
1214
|
+
import fs9 from "fs-extra";
|
|
863
1215
|
import ora4 from "ora";
|
|
864
|
-
import
|
|
1216
|
+
import pc8 from "picocolors";
|
|
865
1217
|
async function remove(pkg) {
|
|
866
|
-
console.log(
|
|
1218
|
+
console.log(pc8.bold(`
|
|
867
1219
|
Removing ${pkg}...
|
|
868
1220
|
`));
|
|
869
1221
|
const packages = await getInstalledPackages();
|
|
@@ -879,20 +1231,20 @@ Removing ${pkg}...
|
|
|
879
1231
|
return p.name === name;
|
|
880
1232
|
});
|
|
881
1233
|
if (matches.length === 0) {
|
|
882
|
-
console.error(
|
|
1234
|
+
console.error(pc8.red(`Package not found: ${pkg}`));
|
|
883
1235
|
console.log("\nInstalled packages:");
|
|
884
1236
|
for (const p of packages) {
|
|
885
|
-
console.log(` ${
|
|
1237
|
+
console.log(` ${pc8.cyan(p.name)}@${p.version}`);
|
|
886
1238
|
}
|
|
887
1239
|
process.exit(1);
|
|
888
1240
|
}
|
|
889
1241
|
if (matches.length > 1 && !version2) {
|
|
890
|
-
console.error(
|
|
1242
|
+
console.error(pc8.red(`Multiple versions found for ${name}:`));
|
|
891
1243
|
for (const p of matches) {
|
|
892
|
-
console.log(` ${
|
|
1244
|
+
console.log(` ${pc8.cyan(p.name)}@${p.version} (${p.installType})`);
|
|
893
1245
|
}
|
|
894
1246
|
console.log(`
|
|
895
|
-
Specify version: ${
|
|
1247
|
+
Specify version: ${pc8.cyan(`olore remove ${name}@<version>`)}`);
|
|
896
1248
|
process.exit(1);
|
|
897
1249
|
}
|
|
898
1250
|
const found = matches[0];
|
|
@@ -901,27 +1253,119 @@ Specify version: ${pc6.cyan(`olore remove ${name}@<version>`)}`);
|
|
|
901
1253
|
const removed = [];
|
|
902
1254
|
const agentPaths = getAgentPaths();
|
|
903
1255
|
for (const [agent, basePath] of Object.entries(agentPaths)) {
|
|
904
|
-
const skillPath =
|
|
905
|
-
if (await
|
|
906
|
-
await
|
|
907
|
-
removed.push(`${
|
|
1256
|
+
const skillPath = path9.join(basePath, skillName);
|
|
1257
|
+
if (await fs9.pathExists(skillPath)) {
|
|
1258
|
+
await fs9.remove(skillPath);
|
|
1259
|
+
removed.push(`${pc8.green("\u2713")} ${agent}`);
|
|
908
1260
|
}
|
|
909
1261
|
}
|
|
910
1262
|
if (found.installType === "installed") {
|
|
911
1263
|
const olorePath = getOlorePackagePath(found.name, found.version);
|
|
912
|
-
if (await
|
|
913
|
-
await
|
|
914
|
-
removed.push(`${
|
|
1264
|
+
if (await fs9.pathExists(olorePath)) {
|
|
1265
|
+
await fs9.remove(olorePath);
|
|
1266
|
+
removed.push(`${pc8.green("\u2713")} ~/.olore`);
|
|
915
1267
|
}
|
|
916
1268
|
}
|
|
917
1269
|
spinner.stop();
|
|
918
1270
|
if (removed.length === 0) {
|
|
919
|
-
console.log(
|
|
1271
|
+
console.log(pc8.yellow("No files found to remove."));
|
|
920
1272
|
} else {
|
|
921
1273
|
removed.forEach((line) => console.log(` ${line}`));
|
|
922
1274
|
console.log("");
|
|
923
|
-
console.log(
|
|
1275
|
+
console.log(pc8.green(`Removed ${found.name}@${found.version}`));
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
// src/commands/search.ts
|
|
1280
|
+
import ora5 from "ora";
|
|
1281
|
+
import pc9 from "picocolors";
|
|
1282
|
+
async function search(query, options) {
|
|
1283
|
+
const spinner = ora5("Fetching package registry...").start();
|
|
1284
|
+
let packages;
|
|
1285
|
+
try {
|
|
1286
|
+
const index = await fetchPackageIndex();
|
|
1287
|
+
packages = index.packages;
|
|
1288
|
+
spinner.stop();
|
|
1289
|
+
} catch (error) {
|
|
1290
|
+
spinner.fail("Failed to fetch registry");
|
|
1291
|
+
if (error instanceof RegistryError) {
|
|
1292
|
+
if (error.code === "NETWORK_ERROR" || error.code === "TIMEOUT") {
|
|
1293
|
+
console.log(pc9.red(`
|
|
1294
|
+
Network error: ${error.message}`));
|
|
1295
|
+
console.log(pc9.gray("Please check your internet connection and try again."));
|
|
1296
|
+
} else {
|
|
1297
|
+
console.log(pc9.red(`
|
|
1298
|
+
Error: ${error.message}`));
|
|
1299
|
+
}
|
|
1300
|
+
} else {
|
|
1301
|
+
console.log(pc9.red(`
|
|
1302
|
+
Error: ${error instanceof Error ? error.message : "Unknown error"}`));
|
|
1303
|
+
}
|
|
1304
|
+
process.exit(1);
|
|
1305
|
+
}
|
|
1306
|
+
let entries = Object.entries(packages);
|
|
1307
|
+
if (query) {
|
|
1308
|
+
const q = query.toLowerCase();
|
|
1309
|
+
entries = entries.filter(
|
|
1310
|
+
([name, info]) => name.toLowerCase().includes(q) || info.description.toLowerCase().includes(q)
|
|
1311
|
+
);
|
|
1312
|
+
}
|
|
1313
|
+
if (entries.length === 0) {
|
|
1314
|
+
if (query) {
|
|
1315
|
+
console.log(pc9.yellow(`
|
|
1316
|
+
No packages matching '${query}'`));
|
|
1317
|
+
} else {
|
|
1318
|
+
console.log(pc9.yellow("\nNo packages available in the registry"));
|
|
1319
|
+
}
|
|
1320
|
+
return;
|
|
1321
|
+
}
|
|
1322
|
+
const installed = await getInstalledPackages();
|
|
1323
|
+
const installedNames = new Set(installed.map((p) => p.name));
|
|
1324
|
+
const installedVersions = /* @__PURE__ */ new Map();
|
|
1325
|
+
for (const pkg of installed) {
|
|
1326
|
+
installedVersions.set(pkg.name, pkg.version);
|
|
1327
|
+
}
|
|
1328
|
+
if (options.json) {
|
|
1329
|
+
const result = {
|
|
1330
|
+
packages: entries.map(([name, info]) => ({
|
|
1331
|
+
name,
|
|
1332
|
+
description: info.description,
|
|
1333
|
+
versions: info.versions,
|
|
1334
|
+
installed: installedNames.has(name),
|
|
1335
|
+
installedVersion: installedVersions.get(name) || null
|
|
1336
|
+
})),
|
|
1337
|
+
...query ? { query } : {}
|
|
1338
|
+
};
|
|
1339
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1340
|
+
return;
|
|
1341
|
+
}
|
|
1342
|
+
const colName = 20;
|
|
1343
|
+
const colDesc = 44;
|
|
1344
|
+
const colVersions = 12;
|
|
1345
|
+
console.log(pc9.bold("\nAvailable packages:\n"));
|
|
1346
|
+
console.log(
|
|
1347
|
+
pc9.gray(
|
|
1348
|
+
"PACKAGE".padEnd(colName) + "DESCRIPTION".padEnd(colDesc) + "VERSIONS".padEnd(colVersions) + "INSTALLED"
|
|
1349
|
+
)
|
|
1350
|
+
);
|
|
1351
|
+
console.log(pc9.gray("\u2500".repeat(colName + colDesc + colVersions + 12)));
|
|
1352
|
+
for (const [name, info] of entries.sort(([a], [b]) => a.localeCompare(b))) {
|
|
1353
|
+
const desc = truncate(info.description, colDesc - 2);
|
|
1354
|
+
const versions = info.versions.join(", ");
|
|
1355
|
+
const installedVersion = installedVersions.get(name);
|
|
1356
|
+
const status = installedVersion ? pc9.green(`\u2713 ${installedVersion}`) : "";
|
|
1357
|
+
console.log(
|
|
1358
|
+
name.padEnd(colName) + desc.padEnd(colDesc) + versions.padEnd(colVersions) + status
|
|
1359
|
+
);
|
|
924
1360
|
}
|
|
1361
|
+
console.log(pc9.gray("\u2500".repeat(colName + colDesc + colVersions + 12)));
|
|
1362
|
+
console.log(pc9.gray(`${entries.length} package${entries.length === 1 ? "" : "s"} available`));
|
|
1363
|
+
console.log(`
|
|
1364
|
+
Install with: ${pc9.cyan("olore install <package>")}`);
|
|
1365
|
+
}
|
|
1366
|
+
function truncate(str, maxLen) {
|
|
1367
|
+
if (str.length <= maxLen) return str;
|
|
1368
|
+
return str.slice(0, maxLen - 1) + "\u2026";
|
|
925
1369
|
}
|
|
926
1370
|
|
|
927
1371
|
// src/cli.ts
|
|
@@ -929,12 +1373,12 @@ var require2 = createRequire(import.meta.url);
|
|
|
929
1373
|
var { version } = require2("../package.json");
|
|
930
1374
|
var program = new Command();
|
|
931
1375
|
program.name("olore").description("Universal documentation for any AI coding agent").version(version).addHelpText("after", `
|
|
932
|
-
${
|
|
1376
|
+
${pc10.gray("May the Skill be with you.")}`);
|
|
933
1377
|
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
1378
|
try {
|
|
935
1379
|
await init(options);
|
|
936
1380
|
} catch (error) {
|
|
937
|
-
console.error(
|
|
1381
|
+
console.error(pc10.red(`Error: ${error.message}`));
|
|
938
1382
|
process.exit(1);
|
|
939
1383
|
}
|
|
940
1384
|
});
|
|
@@ -942,7 +1386,7 @@ program.command("install <package>").alias("i").description("Install a documenta
|
|
|
942
1386
|
try {
|
|
943
1387
|
await install(pkg, options);
|
|
944
1388
|
} catch (error) {
|
|
945
|
-
console.error(
|
|
1389
|
+
console.error(pc10.red(`Error: ${error.message}`));
|
|
946
1390
|
process.exit(1);
|
|
947
1391
|
}
|
|
948
1392
|
});
|
|
@@ -950,7 +1394,7 @@ program.command("link <path>").description("Link a local package for development
|
|
|
950
1394
|
try {
|
|
951
1395
|
await link(localPath);
|
|
952
1396
|
} catch (error) {
|
|
953
|
-
console.error(
|
|
1397
|
+
console.error(pc10.red(`Error: ${error.message}`));
|
|
954
1398
|
process.exit(1);
|
|
955
1399
|
}
|
|
956
1400
|
});
|
|
@@ -958,7 +1402,15 @@ program.command("list").alias("ls").description("List installed documentation pa
|
|
|
958
1402
|
try {
|
|
959
1403
|
await list(options);
|
|
960
1404
|
} catch (error) {
|
|
961
|
-
console.error(
|
|
1405
|
+
console.error(pc10.red(`Error: ${error.message}`));
|
|
1406
|
+
process.exit(1);
|
|
1407
|
+
}
|
|
1408
|
+
});
|
|
1409
|
+
program.command("search [query]").description("Search available packages in the registry").option("--json", "Output as JSON").action(async (query, options) => {
|
|
1410
|
+
try {
|
|
1411
|
+
await search(query, options);
|
|
1412
|
+
} catch (error) {
|
|
1413
|
+
console.error(pc10.red(`Error: ${error.message}`));
|
|
962
1414
|
process.exit(1);
|
|
963
1415
|
}
|
|
964
1416
|
});
|
|
@@ -966,7 +1418,23 @@ program.command("remove <package>").alias("rm").description("Remove an installed
|
|
|
966
1418
|
try {
|
|
967
1419
|
await remove(pkg);
|
|
968
1420
|
} catch (error) {
|
|
969
|
-
console.error(
|
|
1421
|
+
console.error(pc10.red(`Error: ${error.message}`));
|
|
1422
|
+
process.exit(1);
|
|
1423
|
+
}
|
|
1424
|
+
});
|
|
1425
|
+
program.command("doctor").description("Check for issues with installed packages").option("--json", "Output as JSON").action(async (options) => {
|
|
1426
|
+
try {
|
|
1427
|
+
await doctor(options);
|
|
1428
|
+
} catch (error) {
|
|
1429
|
+
console.error(pc10.red(`Error: ${error.message}`));
|
|
1430
|
+
process.exit(1);
|
|
1431
|
+
}
|
|
1432
|
+
});
|
|
1433
|
+
program.command("prune").description("Remove dangling symlinks, orphaned packages, and partial installs").option("--dry-run", "Show what would be removed without acting").option("--json", "Output as JSON").action(async (options) => {
|
|
1434
|
+
try {
|
|
1435
|
+
await prune(options);
|
|
1436
|
+
} catch (error) {
|
|
1437
|
+
console.error(pc10.red(`Error: ${error.message}`));
|
|
970
1438
|
process.exit(1);
|
|
971
1439
|
}
|
|
972
1440
|
});
|
|
@@ -974,7 +1442,7 @@ program.command("order66").description(false).action(async () => {
|
|
|
974
1442
|
try {
|
|
975
1443
|
await order66();
|
|
976
1444
|
} catch (error) {
|
|
977
|
-
console.error(
|
|
1445
|
+
console.error(pc10.red(`Error: ${error.message}`));
|
|
978
1446
|
process.exit(1);
|
|
979
1447
|
}
|
|
980
1448
|
});
|