@sporesec/arcana 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +219 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/audit.d.ts +17 -0
- package/dist/commands/audit.d.ts.map +1 -0
- package/dist/commands/audit.js +157 -0
- package/dist/commands/audit.js.map +1 -0
- package/dist/commands/audit.test.d.ts +2 -0
- package/dist/commands/audit.test.d.ts.map +1 -0
- package/dist/commands/audit.test.js +217 -0
- package/dist/commands/audit.test.js.map +1 -0
- package/dist/commands/clean.d.ts +5 -0
- package/dist/commands/clean.d.ts.map +1 -0
- package/dist/commands/clean.js +125 -0
- package/dist/commands/clean.js.map +1 -0
- package/dist/commands/config.d.ts +4 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +135 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/create.d.ts +2 -0
- package/dist/commands/create.d.ts.map +1 -0
- package/dist/commands/create.js +100 -0
- package/dist/commands/create.js.map +1 -0
- package/dist/commands/doctor.d.ts +6 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +213 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/info.d.ts +5 -0
- package/dist/commands/info.d.ts.map +1 -0
- package/dist/commands/info.js +114 -0
- package/dist/commands/info.js.map +1 -0
- package/dist/commands/init.d.ts +13 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +216 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/install.d.ts +8 -0
- package/dist/commands/install.d.ts.map +1 -0
- package/dist/commands/install.js +404 -0
- package/dist/commands/install.js.map +1 -0
- package/dist/commands/list.d.ts +8 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.js +100 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/providers.d.ts +6 -0
- package/dist/commands/providers.d.ts.map +1 -0
- package/dist/commands/providers.js +103 -0
- package/dist/commands/providers.js.map +1 -0
- package/dist/commands/scan.d.ts +5 -0
- package/dist/commands/scan.d.ts.map +1 -0
- package/dist/commands/scan.js +110 -0
- package/dist/commands/scan.js.map +1 -0
- package/dist/commands/search.d.ts +6 -0
- package/dist/commands/search.d.ts.map +1 -0
- package/dist/commands/search.js +58 -0
- package/dist/commands/search.js.map +1 -0
- package/dist/commands/stats.d.ts +4 -0
- package/dist/commands/stats.d.ts.map +1 -0
- package/dist/commands/stats.js +143 -0
- package/dist/commands/stats.js.map +1 -0
- package/dist/commands/uninstall.d.ts +6 -0
- package/dist/commands/uninstall.d.ts.map +1 -0
- package/dist/commands/uninstall.js +163 -0
- package/dist/commands/uninstall.js.map +1 -0
- package/dist/commands/update.d.ts +7 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +348 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/commands/validate.d.ts +6 -0
- package/dist/commands/validate.d.ts.map +1 -0
- package/dist/commands/validate.js +140 -0
- package/dist/commands/validate.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +39 -0
- package/dist/index.js.map +1 -0
- package/dist/interactive.d.ts +2 -0
- package/dist/interactive.d.ts.map +1 -0
- package/dist/interactive.js +812 -0
- package/dist/interactive.js.map +1 -0
- package/dist/providers/arcana.d.ts +5 -0
- package/dist/providers/arcana.d.ts.map +1 -0
- package/dist/providers/arcana.js +11 -0
- package/dist/providers/arcana.js.map +1 -0
- package/dist/providers/base.d.ts +11 -0
- package/dist/providers/base.d.ts.map +1 -0
- package/dist/providers/base.js +10 -0
- package/dist/providers/base.js.map +1 -0
- package/dist/providers/github.d.ts +25 -0
- package/dist/providers/github.d.ts.map +1 -0
- package/dist/providers/github.js +146 -0
- package/dist/providers/github.js.map +1 -0
- package/dist/registry.d.ts +9 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +71 -0
- package/dist/registry.js.map +1 -0
- package/dist/types.d.ts +67 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/atomic.d.ts +2 -0
- package/dist/utils/atomic.d.ts.map +1 -0
- package/dist/utils/atomic.js +20 -0
- package/dist/utils/atomic.js.map +1 -0
- package/dist/utils/atomic.test.d.ts +2 -0
- package/dist/utils/atomic.test.d.ts.map +1 -0
- package/dist/utils/atomic.test.js +31 -0
- package/dist/utils/atomic.test.js.map +1 -0
- package/dist/utils/cache.d.ts +4 -0
- package/dist/utils/cache.d.ts.map +1 -0
- package/dist/utils/cache.js +47 -0
- package/dist/utils/cache.js.map +1 -0
- package/dist/utils/config.d.ts +6 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +90 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/config.test.d.ts +2 -0
- package/dist/utils/config.test.d.ts.map +1 -0
- package/dist/utils/config.test.js +38 -0
- package/dist/utils/config.test.js.map +1 -0
- package/dist/utils/errors.d.ts +6 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +11 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/frontmatter.d.ts +12 -0
- package/dist/utils/frontmatter.d.ts.map +1 -0
- package/dist/utils/frontmatter.js +172 -0
- package/dist/utils/frontmatter.js.map +1 -0
- package/dist/utils/frontmatter.test.d.ts +2 -0
- package/dist/utils/frontmatter.test.d.ts.map +1 -0
- package/dist/utils/frontmatter.test.js +152 -0
- package/dist/utils/frontmatter.test.js.map +1 -0
- package/dist/utils/fs.d.ts +16 -0
- package/dist/utils/fs.d.ts.map +1 -0
- package/dist/utils/fs.js +118 -0
- package/dist/utils/fs.js.map +1 -0
- package/dist/utils/fs.test.d.ts +2 -0
- package/dist/utils/fs.test.d.ts.map +1 -0
- package/dist/utils/fs.test.js +145 -0
- package/dist/utils/fs.test.js.map +1 -0
- package/dist/utils/help.d.ts +6 -0
- package/dist/utils/help.d.ts.map +1 -0
- package/dist/utils/help.js +117 -0
- package/dist/utils/help.js.map +1 -0
- package/dist/utils/help.test.d.ts +2 -0
- package/dist/utils/help.test.d.ts.map +1 -0
- package/dist/utils/help.test.js +66 -0
- package/dist/utils/help.test.js.map +1 -0
- package/dist/utils/history.d.ts +10 -0
- package/dist/utils/history.d.ts.map +1 -0
- package/dist/utils/history.js +58 -0
- package/dist/utils/history.js.map +1 -0
- package/dist/utils/http.d.ts +17 -0
- package/dist/utils/http.d.ts.map +1 -0
- package/dist/utils/http.js +165 -0
- package/dist/utils/http.js.map +1 -0
- package/dist/utils/http.test.d.ts +2 -0
- package/dist/utils/http.test.d.ts.map +1 -0
- package/dist/utils/http.test.js +55 -0
- package/dist/utils/http.test.js.map +1 -0
- package/dist/utils/parallel.d.ts +2 -0
- package/dist/utils/parallel.d.ts.map +1 -0
- package/dist/utils/parallel.js +17 -0
- package/dist/utils/parallel.js.map +1 -0
- package/dist/utils/scanner.d.ts +27 -0
- package/dist/utils/scanner.d.ts.map +1 -0
- package/dist/utils/scanner.js +195 -0
- package/dist/utils/scanner.js.map +1 -0
- package/dist/utils/ui.d.ts +27 -0
- package/dist/utils/ui.d.ts.map +1 -0
- package/dist/utils/ui.js +99 -0
- package/dist/utils/ui.js.map +1 -0
- package/dist/utils/ui.test.d.ts +2 -0
- package/dist/utils/ui.test.d.ts.map +1 -0
- package/dist/utils/ui.test.js +31 -0
- package/dist/utils/ui.test.js.map +1 -0
- package/dist/utils/validate.d.ts +2 -0
- package/dist/utils/validate.d.ts.map +1 -0
- package/dist/utils/validate.js +7 -0
- package/dist/utils/validate.js.map +1 -0
- package/package.json +62 -0
package/dist/utils/fs.js
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync, rmSync, renameSync, lstatSync, readlinkSync } from "node:fs";
|
|
2
|
+
import { join, dirname, resolve, sep } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { loadConfig } from "../utils/config.js";
|
|
5
|
+
import { atomicWriteSync } from "./atomic.js";
|
|
6
|
+
export function getInstallDir() {
|
|
7
|
+
return loadConfig().installDir;
|
|
8
|
+
}
|
|
9
|
+
export function getSkillDir(name) {
|
|
10
|
+
return join(getInstallDir(), name);
|
|
11
|
+
}
|
|
12
|
+
export function getDirSize(dir) {
|
|
13
|
+
let size = 0;
|
|
14
|
+
const queue = [dir];
|
|
15
|
+
while (queue.length > 0) {
|
|
16
|
+
const current = queue.pop();
|
|
17
|
+
try {
|
|
18
|
+
for (const entry of readdirSync(current)) {
|
|
19
|
+
const full = join(current, entry);
|
|
20
|
+
try {
|
|
21
|
+
const stat = lstatSync(full);
|
|
22
|
+
if (stat.isSymbolicLink())
|
|
23
|
+
continue;
|
|
24
|
+
if (stat.isDirectory())
|
|
25
|
+
queue.push(full);
|
|
26
|
+
else
|
|
27
|
+
size += stat.size;
|
|
28
|
+
}
|
|
29
|
+
catch { /* skip unreadable entries */ }
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
catch { /* skip unreadable dirs */ }
|
|
33
|
+
}
|
|
34
|
+
return size;
|
|
35
|
+
}
|
|
36
|
+
export function installSkill(skillName, files) {
|
|
37
|
+
const installDir = getInstallDir();
|
|
38
|
+
const skillDir = join(installDir, skillName);
|
|
39
|
+
const tempDir = `${skillDir}.installing.${process.pid}`;
|
|
40
|
+
// Crash recovery: remove leftover temp dir from a previous failed install
|
|
41
|
+
if (existsSync(tempDir)) {
|
|
42
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
43
|
+
}
|
|
44
|
+
mkdirSync(tempDir, { recursive: true });
|
|
45
|
+
try {
|
|
46
|
+
for (const file of files) {
|
|
47
|
+
// Reject paths containing .. before resolving
|
|
48
|
+
if (file.path.includes("..") || file.path.includes("~")) {
|
|
49
|
+
throw new Error(`Path traversal blocked: ${file.path}`);
|
|
50
|
+
}
|
|
51
|
+
const filePath = resolve(tempDir, file.path);
|
|
52
|
+
// Normalize to lowercase on Windows for case-insensitive comparison
|
|
53
|
+
const normalizedFile = process.platform === "win32" ? filePath.toLowerCase() : filePath;
|
|
54
|
+
const normalizedTemp = process.platform === "win32" ? (tempDir + sep).toLowerCase() : tempDir + sep;
|
|
55
|
+
if (!normalizedFile.startsWith(normalizedTemp) && normalizedFile !== (process.platform === "win32" ? tempDir.toLowerCase() : tempDir)) {
|
|
56
|
+
throw new Error(`Path traversal blocked: ${file.path}`);
|
|
57
|
+
}
|
|
58
|
+
const dir = dirname(filePath);
|
|
59
|
+
if (!existsSync(dir)) {
|
|
60
|
+
mkdirSync(dir, { recursive: true });
|
|
61
|
+
}
|
|
62
|
+
writeFileSync(filePath, file.content, { encoding: "utf-8", mode: 0o644 });
|
|
63
|
+
}
|
|
64
|
+
// Remove old skill dir if it exists, then atomically rename temp to final
|
|
65
|
+
if (existsSync(skillDir)) {
|
|
66
|
+
rmSync(skillDir, { recursive: true, force: true });
|
|
67
|
+
}
|
|
68
|
+
renameSync(tempDir, skillDir);
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
// Clean up temp dir on failure
|
|
72
|
+
try {
|
|
73
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
74
|
+
}
|
|
75
|
+
catch { /* best-effort */ }
|
|
76
|
+
throw err;
|
|
77
|
+
}
|
|
78
|
+
return skillDir;
|
|
79
|
+
}
|
|
80
|
+
export function isSkillInstalled(skillName) {
|
|
81
|
+
return existsSync(join(getSkillDir(skillName), "SKILL.md"));
|
|
82
|
+
}
|
|
83
|
+
const META_FILE = ".arcana-meta.json";
|
|
84
|
+
export function readSkillMeta(skillName) {
|
|
85
|
+
const metaPath = join(getSkillDir(skillName), META_FILE);
|
|
86
|
+
if (!existsSync(metaPath))
|
|
87
|
+
return null;
|
|
88
|
+
try {
|
|
89
|
+
return JSON.parse(readFileSync(metaPath, "utf-8"));
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
export function writeSkillMeta(skillName, meta) {
|
|
96
|
+
const skillDir = getSkillDir(skillName);
|
|
97
|
+
mkdirSync(skillDir, { recursive: true });
|
|
98
|
+
atomicWriteSync(join(skillDir, META_FILE), JSON.stringify(meta, null, 2) + "\n", 0o644);
|
|
99
|
+
}
|
|
100
|
+
export function listSymlinks() {
|
|
101
|
+
const symlinkDir = join(homedir(), ".claude", "skills");
|
|
102
|
+
if (!existsSync(symlinkDir))
|
|
103
|
+
return [];
|
|
104
|
+
const results = [];
|
|
105
|
+
for (const entry of readdirSync(symlinkDir)) {
|
|
106
|
+
const fullPath = join(symlinkDir, entry);
|
|
107
|
+
try {
|
|
108
|
+
const stat = lstatSync(fullPath);
|
|
109
|
+
if (stat.isSymbolicLink()) {
|
|
110
|
+
const target = readlinkSync(fullPath);
|
|
111
|
+
results.push({ name: entry, fullPath, target, broken: !existsSync(target) });
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
catch { /* skip unreadable */ }
|
|
115
|
+
}
|
|
116
|
+
return results;
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=fs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fs.js","sourceRoot":"","sources":["../../src/utils/fs.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fs.test.d.ts","sourceRoot":"","sources":["../../src/utils/fs.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { mkdtempSync, writeFileSync, mkdirSync, existsSync, rmSync, readFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { installSkill, isSkillInstalled, readSkillMeta, writeSkillMeta, getDirSize } from "./fs.js";
|
|
6
|
+
let testTempDir = null;
|
|
7
|
+
function makeTempBase() {
|
|
8
|
+
const dir = mkdtempSync(join(tmpdir(), "arcana-test-"));
|
|
9
|
+
testTempDir = dir;
|
|
10
|
+
return dir;
|
|
11
|
+
}
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
testTempDir = null;
|
|
14
|
+
});
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
if (testTempDir && existsSync(testTempDir)) {
|
|
17
|
+
try {
|
|
18
|
+
rmSync(testTempDir, { recursive: true, force: true });
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
// Best-effort cleanup
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
describe("installSkill", () => {
|
|
26
|
+
it("blocks path traversal (file.path containing ../)", () => {
|
|
27
|
+
makeTempBase();
|
|
28
|
+
// Path traversal is validated before directory resolution,
|
|
29
|
+
// so this test works without mocking getInstallDir
|
|
30
|
+
const files = [
|
|
31
|
+
{ path: "../escape/SKILL.md", content: "Malicious content" },
|
|
32
|
+
];
|
|
33
|
+
expect(() => installSkill("test-skill", files)).toThrow("Path traversal blocked");
|
|
34
|
+
});
|
|
35
|
+
it("creates skill directory with correct files", () => {
|
|
36
|
+
const base = makeTempBase();
|
|
37
|
+
const files = [
|
|
38
|
+
{ path: "SKILL.md", content: "---\nname: test\n---\nBody" },
|
|
39
|
+
{ path: "scripts/helper.sh", content: "#!/bin/bash\necho test" },
|
|
40
|
+
{ path: "references/doc.txt", content: "Documentation" },
|
|
41
|
+
];
|
|
42
|
+
const skillDir = installSkill("test-skill", files);
|
|
43
|
+
expect(existsSync(join(skillDir, "SKILL.md"))).toBe(true);
|
|
44
|
+
expect(existsSync(join(skillDir, "scripts", "helper.sh"))).toBe(true);
|
|
45
|
+
expect(existsSync(join(skillDir, "references", "doc.txt"))).toBe(true);
|
|
46
|
+
const content = readFileSync(join(skillDir, "SKILL.md"), "utf-8");
|
|
47
|
+
expect(content).toBe("---\nname: test\n---\nBody");
|
|
48
|
+
});
|
|
49
|
+
it("removes temp dir on failure", () => {
|
|
50
|
+
const base = makeTempBase();
|
|
51
|
+
const files = [
|
|
52
|
+
{ path: "../invalid", content: "Bad" },
|
|
53
|
+
];
|
|
54
|
+
try {
|
|
55
|
+
installSkill("test-skill", files);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// Expected to fail
|
|
59
|
+
}
|
|
60
|
+
// Check that temp dir was cleaned up
|
|
61
|
+
const tempDir = join(base, "test-skill.installing");
|
|
62
|
+
expect(existsSync(tempDir)).toBe(false);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
describe("isSkillInstalled", () => {
|
|
66
|
+
it("returns true when SKILL.md exists", () => {
|
|
67
|
+
const base = makeTempBase();
|
|
68
|
+
const skillDir = join(base, "test-skill");
|
|
69
|
+
mkdirSync(skillDir, { recursive: true });
|
|
70
|
+
writeFileSync(join(skillDir, "SKILL.md"), "content", "utf-8");
|
|
71
|
+
const result = isSkillInstalled("test-skill");
|
|
72
|
+
expect(result).toBe(true);
|
|
73
|
+
});
|
|
74
|
+
it("returns false when directory doesn't exist", () => {
|
|
75
|
+
makeTempBase();
|
|
76
|
+
const result = isSkillInstalled("nonexistent-skill");
|
|
77
|
+
expect(result).toBe(false);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
describe("readSkillMeta", () => {
|
|
81
|
+
it("returns null for non-existent skill", () => {
|
|
82
|
+
makeTempBase();
|
|
83
|
+
const result = readSkillMeta("nonexistent");
|
|
84
|
+
expect(result).toBeNull();
|
|
85
|
+
});
|
|
86
|
+
it("returns null for invalid JSON", () => {
|
|
87
|
+
const base = makeTempBase();
|
|
88
|
+
const skillDir = join(base, "bad-meta");
|
|
89
|
+
mkdirSync(skillDir, { recursive: true });
|
|
90
|
+
writeFileSync(join(skillDir, ".arcana-meta.json"), "invalid json{", "utf-8");
|
|
91
|
+
const result = readSkillMeta("bad-meta");
|
|
92
|
+
expect(result).toBeNull();
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
describe("writeSkillMeta and readSkillMeta", () => {
|
|
96
|
+
it("roundtrip correctly", () => {
|
|
97
|
+
makeTempBase();
|
|
98
|
+
const meta = {
|
|
99
|
+
version: "1.0.0",
|
|
100
|
+
installedAt: "2026-02-14T12:00:00Z",
|
|
101
|
+
source: "local",
|
|
102
|
+
};
|
|
103
|
+
writeSkillMeta("roundtrip-skill", meta);
|
|
104
|
+
const result = readSkillMeta("roundtrip-skill");
|
|
105
|
+
expect(result).not.toBeNull();
|
|
106
|
+
expect(result?.version).toBe(meta.version);
|
|
107
|
+
expect(result?.installedAt).toBe(meta.installedAt);
|
|
108
|
+
expect(result?.source).toBe(meta.source);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
describe("getDirSize", () => {
|
|
112
|
+
it("calculates directory size correctly", () => {
|
|
113
|
+
const base = makeTempBase();
|
|
114
|
+
const testDir = join(base, "size-test");
|
|
115
|
+
mkdirSync(testDir, { recursive: true });
|
|
116
|
+
// Create files of known sizes
|
|
117
|
+
writeFileSync(join(testDir, "file1.txt"), "a".repeat(100), "utf-8"); // 100 bytes
|
|
118
|
+
writeFileSync(join(testDir, "file2.txt"), "b".repeat(200), "utf-8"); // 200 bytes
|
|
119
|
+
// Create subdirectory with file
|
|
120
|
+
const subDir = join(testDir, "subdir");
|
|
121
|
+
mkdirSync(subDir, { recursive: true });
|
|
122
|
+
writeFileSync(join(subDir, "file3.txt"), "c".repeat(50), "utf-8"); // 50 bytes
|
|
123
|
+
const size = getDirSize(testDir);
|
|
124
|
+
expect(size).toBe(350); // 100 + 200 + 50
|
|
125
|
+
});
|
|
126
|
+
it("returns 0 for empty directory", () => {
|
|
127
|
+
const base = makeTempBase();
|
|
128
|
+
const emptyDir = join(base, "empty");
|
|
129
|
+
mkdirSync(emptyDir, { recursive: true });
|
|
130
|
+
const size = getDirSize(emptyDir);
|
|
131
|
+
expect(size).toBe(0);
|
|
132
|
+
});
|
|
133
|
+
it("handles nested directories", () => {
|
|
134
|
+
const base = makeTempBase();
|
|
135
|
+
const nestedDir = join(base, "nested");
|
|
136
|
+
mkdirSync(join(nestedDir, "a", "b", "c"), { recursive: true });
|
|
137
|
+
writeFileSync(join(nestedDir, "root.txt"), "x".repeat(10), "utf-8");
|
|
138
|
+
writeFileSync(join(nestedDir, "a", "level1.txt"), "y".repeat(20), "utf-8");
|
|
139
|
+
writeFileSync(join(nestedDir, "a", "b", "level2.txt"), "z".repeat(30), "utf-8");
|
|
140
|
+
writeFileSync(join(nestedDir, "a", "b", "c", "level3.txt"), "w".repeat(40), "utf-8");
|
|
141
|
+
const size = getDirSize(nestedDir);
|
|
142
|
+
expect(size).toBe(100); // 10 + 20 + 30 + 40
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
//# sourceMappingURL=fs.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fs.test.js","sourceRoot":"","sources":["../../src/utils/fs.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare function renderBanner(): string;
|
|
2
|
+
export declare function buildCustomHelp(version: string): string;
|
|
3
|
+
export declare function isFirstRun(): boolean;
|
|
4
|
+
export declare function markInitialized(): void;
|
|
5
|
+
export declare function showWelcome(version: string): void;
|
|
6
|
+
//# sourceMappingURL=help.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"help.d.ts","sourceRoot":"","sources":["../../src/utils/help.ts"],"names":[],"mappings":"AAgCA,wBAAgB,YAAY,IAAI,MAAM,CAKrC;AA2CD,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAgCvD;AAID,wBAAgB,UAAU,IAAI,OAAO,CAEpC;AAED,wBAAgB,eAAe,IAAI,IAAI,CAMtC;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CASjD"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import * as p from "@clack/prompts";
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
import { ui } from "./ui.js";
|
|
7
|
+
const noColor = !!(process.env.NO_COLOR || process.env.TERM === "dumb");
|
|
8
|
+
function amberShade(hex, text) {
|
|
9
|
+
if (noColor)
|
|
10
|
+
return text;
|
|
11
|
+
return chalk.hex(hex)(text);
|
|
12
|
+
}
|
|
13
|
+
const AMBER_HEXES = [
|
|
14
|
+
"#e8a84c", // bright amber
|
|
15
|
+
"#d4943a", // brand amber
|
|
16
|
+
"#c0842f", // mid
|
|
17
|
+
"#a87228", // darker
|
|
18
|
+
"#8f6020", // dimmer
|
|
19
|
+
"#755019", // darkest
|
|
20
|
+
];
|
|
21
|
+
const BANNER_LINES = [
|
|
22
|
+
" █████╗ ██████╗ ██████╗ █████╗ ███╗ ██╗ █████╗ ",
|
|
23
|
+
"██╔══██╗██╔══██╗██╔════╝██╔══██╗████╗ ██║██╔══██╗",
|
|
24
|
+
"███████║██████╔╝██║ ███████║██╔██╗ ██║███████║",
|
|
25
|
+
"██╔══██║██╔══██╗██║ ██╔══██║██║╚██╗██║██╔══██║",
|
|
26
|
+
"██║ ██║██║ ██║╚██████╗██║ ██║██║ ╚████║██║ ██║",
|
|
27
|
+
"╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝",
|
|
28
|
+
];
|
|
29
|
+
export function renderBanner() {
|
|
30
|
+
if (noColor) {
|
|
31
|
+
return BANNER_LINES.map((l) => ` ${l}`).join("\n");
|
|
32
|
+
}
|
|
33
|
+
return BANNER_LINES.map((line, i) => ` ${amberShade(AMBER_HEXES[i], line)}`).join("\n");
|
|
34
|
+
}
|
|
35
|
+
const COMMAND_GROUPS = {
|
|
36
|
+
"GETTING STARTED": [
|
|
37
|
+
{ cmd: "init", desc: "Initialize arcana in current project" },
|
|
38
|
+
{ cmd: "doctor", desc: "Check environment and diagnose issues" },
|
|
39
|
+
],
|
|
40
|
+
SKILLS: [
|
|
41
|
+
{ cmd: "list", desc: "List available skills" },
|
|
42
|
+
{ cmd: "search <query>", desc: "Search across providers" },
|
|
43
|
+
{ cmd: "info <skill>", desc: "Show skill details" },
|
|
44
|
+
{ cmd: "install [skills...]", desc: "Install one or more skills" },
|
|
45
|
+
{ cmd: "update [skills...]", desc: "Update installed skills" },
|
|
46
|
+
{ cmd: "uninstall [skills...]", desc: "Remove one or more skills" },
|
|
47
|
+
],
|
|
48
|
+
DEVELOPMENT: [
|
|
49
|
+
{ cmd: "create <name>", desc: "Create a new skill from template" },
|
|
50
|
+
{ cmd: "validate [skill]", desc: "Validate skill structure" },
|
|
51
|
+
{ cmd: "audit [skill]", desc: "Audit skill quality" },
|
|
52
|
+
],
|
|
53
|
+
CONFIGURATION: [
|
|
54
|
+
{ cmd: "config [key] [val]", desc: "View or modify configuration" },
|
|
55
|
+
{ cmd: "providers", desc: "Manage skill providers" },
|
|
56
|
+
{ cmd: "clean", desc: "Remove orphaned data" },
|
|
57
|
+
{ cmd: "stats", desc: "Show session analytics" },
|
|
58
|
+
],
|
|
59
|
+
};
|
|
60
|
+
const EXAMPLES = [
|
|
61
|
+
"$ arcana install code-reviewer typescript golang",
|
|
62
|
+
'$ arcana search "testing"',
|
|
63
|
+
"$ arcana init --tool claude",
|
|
64
|
+
];
|
|
65
|
+
function padRight(str, width) {
|
|
66
|
+
return str + " ".repeat(Math.max(0, width - str.length));
|
|
67
|
+
}
|
|
68
|
+
export function buildCustomHelp(version) {
|
|
69
|
+
const lines = [];
|
|
70
|
+
lines.push("");
|
|
71
|
+
lines.push(renderBanner());
|
|
72
|
+
lines.push("");
|
|
73
|
+
lines.push(` ${ui.bold("Supercharge any AI coding agent.")}${" ".repeat(20)}${ui.dim(`v${version}`)}`);
|
|
74
|
+
lines.push("");
|
|
75
|
+
lines.push(` ${ui.dim("USAGE")}`);
|
|
76
|
+
lines.push(" arcana <command> [options]");
|
|
77
|
+
for (const [group, commands] of Object.entries(COMMAND_GROUPS)) {
|
|
78
|
+
lines.push("");
|
|
79
|
+
lines.push(` ${ui.dim(group)}`);
|
|
80
|
+
for (const { cmd, desc } of commands) {
|
|
81
|
+
lines.push(` ${ui.cyan(padRight(cmd, 22))}${ui.dim(desc)}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
lines.push("");
|
|
85
|
+
lines.push(` ${ui.dim("EXAMPLES")}`);
|
|
86
|
+
for (const ex of EXAMPLES) {
|
|
87
|
+
lines.push(` ${ui.cyan(ex)}`);
|
|
88
|
+
}
|
|
89
|
+
lines.push("");
|
|
90
|
+
lines.push(` ${ui.dim("LEARN MORE")}`);
|
|
91
|
+
lines.push(` arcana <command> --help ${ui.dim("Show help for a command")}`);
|
|
92
|
+
lines.push(` ${ui.dim("https://github.com/mahdy-gribkov/arcana")}`);
|
|
93
|
+
lines.push("");
|
|
94
|
+
return lines.join("\n");
|
|
95
|
+
}
|
|
96
|
+
const FIRST_RUN_FLAG = join(homedir(), ".arcana", ".initialized");
|
|
97
|
+
export function isFirstRun() {
|
|
98
|
+
return !existsSync(FIRST_RUN_FLAG);
|
|
99
|
+
}
|
|
100
|
+
export function markInitialized() {
|
|
101
|
+
const dir = join(homedir(), ".arcana");
|
|
102
|
+
if (!existsSync(dir)) {
|
|
103
|
+
mkdirSync(dir, { recursive: true });
|
|
104
|
+
}
|
|
105
|
+
writeFileSync(FIRST_RUN_FLAG, new Date().toISOString(), "utf-8");
|
|
106
|
+
}
|
|
107
|
+
export function showWelcome(version) {
|
|
108
|
+
console.log();
|
|
109
|
+
console.log(renderBanner());
|
|
110
|
+
console.log();
|
|
111
|
+
p.intro(chalk.hex("#d4943a").bold(`arcana v${version}`));
|
|
112
|
+
p.log.step(chalk.bold("Welcome! Arcana is a universal skill manager for AI coding agents."));
|
|
113
|
+
p.log.info("Skills extend your agent (Claude, Cursor, Codex, etc.) with expert knowledge.");
|
|
114
|
+
p.log.info("They install on-demand and only load when relevant, not all at once.");
|
|
115
|
+
console.log();
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=help.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"help.js","sourceRoot":"","sources":["../../src/utils/help.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"help.test.d.ts","sourceRoot":"","sources":["../../src/utils/help.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { buildCustomHelp, renderBanner } from "./help.js";
|
|
3
|
+
describe("renderBanner", () => {
|
|
4
|
+
it("contains ARCANA block characters", () => {
|
|
5
|
+
const banner = renderBanner();
|
|
6
|
+
expect(banner).toContain("█████");
|
|
7
|
+
expect(banner).toContain("╔══");
|
|
8
|
+
});
|
|
9
|
+
it("has 6 lines", () => {
|
|
10
|
+
const banner = renderBanner();
|
|
11
|
+
// Strip ANSI codes then count non-empty lines
|
|
12
|
+
const stripped = banner.replace(/\x1b\[[0-9;]*m/g, "");
|
|
13
|
+
const lines = stripped.split("\n").filter((l) => l.trim().length > 0);
|
|
14
|
+
expect(lines.length).toBe(6);
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
describe("buildCustomHelp", () => {
|
|
18
|
+
it("contains all section headers", () => {
|
|
19
|
+
const help = buildCustomHelp("2.1.1");
|
|
20
|
+
expect(help).toContain("GETTING STARTED");
|
|
21
|
+
expect(help).toContain("SKILLS");
|
|
22
|
+
expect(help).toContain("DEVELOPMENT");
|
|
23
|
+
expect(help).toContain("CONFIGURATION");
|
|
24
|
+
expect(help).toContain("EXAMPLES");
|
|
25
|
+
expect(help).toContain("LEARN MORE");
|
|
26
|
+
});
|
|
27
|
+
it("contains USAGE section", () => {
|
|
28
|
+
const help = buildCustomHelp("2.1.1");
|
|
29
|
+
expect(help).toContain("USAGE");
|
|
30
|
+
expect(help).toContain("arcana <command> [options]");
|
|
31
|
+
});
|
|
32
|
+
it("contains tagline", () => {
|
|
33
|
+
const help = buildCustomHelp("2.1.1");
|
|
34
|
+
expect(help).toContain("Supercharge any AI coding agent");
|
|
35
|
+
});
|
|
36
|
+
it("contains all 15 commands", () => {
|
|
37
|
+
const help = buildCustomHelp("1.0.0");
|
|
38
|
+
const commands = [
|
|
39
|
+
"init", "doctor", "list", "search", "info", "install",
|
|
40
|
+
"update", "uninstall", "create", "validate", "audit",
|
|
41
|
+
"config", "providers", "clean", "stats",
|
|
42
|
+
];
|
|
43
|
+
for (const cmd of commands) {
|
|
44
|
+
expect(help).toContain(cmd);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
it("contains version string", () => {
|
|
48
|
+
const help = buildCustomHelp("3.5.7");
|
|
49
|
+
expect(help).toContain("v3.5.7");
|
|
50
|
+
});
|
|
51
|
+
it("contains examples with actual commands", () => {
|
|
52
|
+
const help = buildCustomHelp("1.0.0");
|
|
53
|
+
expect(help).toContain("arcana install code-reviewer");
|
|
54
|
+
expect(help).toContain("arcana search");
|
|
55
|
+
expect(help).toContain("arcana init --tool claude");
|
|
56
|
+
});
|
|
57
|
+
it("contains GitHub URL", () => {
|
|
58
|
+
const help = buildCustomHelp("1.0.0");
|
|
59
|
+
expect(help).toContain("github.com/mahdy-gribkov/arcana");
|
|
60
|
+
});
|
|
61
|
+
it("contains ASCII banner", () => {
|
|
62
|
+
const help = buildCustomHelp("1.0.0");
|
|
63
|
+
expect(help).toContain("█████");
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
//# sourceMappingURL=help.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"help.test.js","sourceRoot":"","sources":["../../src/utils/help.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface HistoryEntry {
|
|
2
|
+
action: string;
|
|
3
|
+
target?: string;
|
|
4
|
+
timestamp: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function readHistory(): HistoryEntry[];
|
|
7
|
+
export declare function appendHistory(action: string, target?: string): void;
|
|
8
|
+
export declare function clearHistory(): void;
|
|
9
|
+
export declare function getRecentSkills(limit?: number): string[];
|
|
10
|
+
//# sourceMappingURL=history.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"history.d.ts","sourceRoot":"","sources":["../../src/utils/history.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAMD,wBAAgB,WAAW,IAAI,YAAY,EAAE,CAS5C;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAWnE;AAED,wBAAgB,YAAY,IAAI,IAAI,CAQnC;AAED,wBAAgB,eAAe,CAAC,KAAK,SAAI,GAAG,MAAM,EAAE,CAUnD"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { existsSync, readFileSync, mkdirSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { atomicWriteSync } from "./atomic.js";
|
|
5
|
+
const MAX_ENTRIES = 50;
|
|
6
|
+
function historyPath() {
|
|
7
|
+
return join(homedir(), ".arcana", "history.json");
|
|
8
|
+
}
|
|
9
|
+
export function readHistory() {
|
|
10
|
+
const p = historyPath();
|
|
11
|
+
if (!existsSync(p))
|
|
12
|
+
return [];
|
|
13
|
+
try {
|
|
14
|
+
const data = JSON.parse(readFileSync(p, "utf-8"));
|
|
15
|
+
return Array.isArray(data) ? data : [];
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export function appendHistory(action, target) {
|
|
22
|
+
const entries = readHistory();
|
|
23
|
+
entries.push({ action, target, timestamp: new Date().toISOString() });
|
|
24
|
+
while (entries.length > MAX_ENTRIES)
|
|
25
|
+
entries.shift();
|
|
26
|
+
const dir = join(homedir(), ".arcana");
|
|
27
|
+
if (!existsSync(dir))
|
|
28
|
+
mkdirSync(dir, { recursive: true });
|
|
29
|
+
try {
|
|
30
|
+
atomicWriteSync(historyPath(), JSON.stringify(entries, null, 2));
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
// Best effort
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export function clearHistory() {
|
|
37
|
+
const dir = join(homedir(), ".arcana");
|
|
38
|
+
if (!existsSync(dir))
|
|
39
|
+
mkdirSync(dir, { recursive: true });
|
|
40
|
+
try {
|
|
41
|
+
atomicWriteSync(historyPath(), "[]");
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// Best effort
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export function getRecentSkills(limit = 5) {
|
|
48
|
+
const entries = readHistory();
|
|
49
|
+
const skills = [];
|
|
50
|
+
for (let i = entries.length - 1; i >= 0 && skills.length < limit; i--) {
|
|
51
|
+
const e = entries[i];
|
|
52
|
+
if (e.target && (e.action === "install" || e.action === "search") && !skills.includes(e.target)) {
|
|
53
|
+
skills.push(e.target);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return skills;
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=history.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"history.js","sourceRoot":"","sources":["../../src/utils/history.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAC9D,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9C,MAAM,WAAW,GAAG,EAAE,CAAC;AAQvB,SAAS,WAAW;IAClB,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,cAAc,CAAC,CAAC;AACpD,CAAC;AAED,MAAM,UAAU,WAAW;IACzB,MAAM,CAAC,GAAG,WAAW,EAAE,CAAC;IACxB,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;QAAE,OAAO,EAAE,CAAC;IAC9B,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;QAClD,OAAO,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;IACzC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,MAAc,EAAE,MAAe;IAC3D,MAAM,OAAO,GAAG,WAAW,EAAE,CAAC;IAC9B,OAAO,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;IACtE,OAAO,OAAO,CAAC,MAAM,GAAG,WAAW;QAAE,OAAO,CAAC,KAAK,EAAE,CAAC;IACrD,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC,CAAC;IACvC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC1D,IAAI,CAAC;QACH,eAAe,CAAC,WAAW,EAAE,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IACnE,CAAC;IAAC,MAAM,CAAC;QACP,cAAc;IAChB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,YAAY;IAC1B,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC,CAAC;IACvC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC1D,IAAI,CAAC;QACH,eAAe,CAAC,WAAW,EAAE,EAAE,IAAI,CAAC,CAAC;IACvC,CAAC;IAAC,MAAM,CAAC;QACP,cAAc;IAChB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,KAAK,GAAG,CAAC;IACvC,MAAM,OAAO,GAAG,WAAW,EAAE,CAAC;IAC9B,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,KAAK,IAAI,CAAC,GAAG,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,MAAM,GAAG,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC;QACtE,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAE,CAAC;QACtB,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,MAAM,KAAK,SAAS,IAAI,CAAC,CAAC,MAAM,KAAK,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;YAChG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;QACxB,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface HttpResponse {
|
|
2
|
+
body: string;
|
|
3
|
+
statusCode: number;
|
|
4
|
+
headers: Record<string, string | string[] | undefined>;
|
|
5
|
+
}
|
|
6
|
+
export declare function sanitizeUrl(url: string): string;
|
|
7
|
+
export declare class HttpError extends Error {
|
|
8
|
+
readonly statusCode: number;
|
|
9
|
+
readonly url: string;
|
|
10
|
+
constructor(statusCode: number, url: string, message?: string);
|
|
11
|
+
}
|
|
12
|
+
export declare class RateLimitError extends HttpError {
|
|
13
|
+
readonly resetAt: Date | null;
|
|
14
|
+
constructor(url: string, resetAt: Date | null);
|
|
15
|
+
}
|
|
16
|
+
export declare function httpGet(url: string, timeout?: number): Promise<HttpResponse>;
|
|
17
|
+
//# sourceMappingURL=http.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/utils/http.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC;CACxD;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAc/C;AAED,qBAAa,SAAU,SAAQ,KAAK;aAEhB,UAAU,EAAE,MAAM;aAClB,GAAG,EAAE,MAAM;gBADX,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,MAAM,EAC3B,OAAO,CAAC,EAAE,MAAM;CAKnB;AAED,qBAAa,cAAe,SAAQ,SAAS;aAGzB,OAAO,EAAE,IAAI,GAAG,IAAI;gBADpC,GAAG,EAAE,MAAM,EACK,OAAO,EAAE,IAAI,GAAG,IAAI;CAMvC;AAkBD,wBAAsB,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,SAAQ,GAAG,OAAO,CAAC,YAAY,CAAC,CA+CjF"}
|