@logbookfordevs/afk 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +240 -0
- package/dist/agents.js +59 -0
- package/dist/brand.js +60 -0
- package/dist/cli.js +625 -0
- package/dist/delegates.js +220 -0
- package/dist/fs-utils.js +120 -0
- package/dist/hooks.js +217 -0
- package/dist/index.js +4 -0
- package/dist/interactive.js +273 -0
- package/dist/manifest-configure.js +300 -0
- package/dist/manifest-show.js +207 -0
- package/dist/manifest.js +409 -0
- package/dist/paths.js +17 -0
- package/dist/prompt-ui.js +109 -0
- package/dist/prompt.js +8 -0
- package/dist/rules.js +209 -0
- package/dist/setup.js +225 -0
- package/dist/skills.js +114 -0
- package/dist/terminal-theme.js +37 -0
- package/dist/types.js +1 -0
- package/package.json +44 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { sectionTitle, muted } from "./brand.js";
|
|
4
|
+
import { localManifestDir } from "./manifest.js";
|
|
5
|
+
import { bold, paint, reset, terminalPalette } from "./terminal-theme.js";
|
|
6
|
+
const categories = [
|
|
7
|
+
{ id: "rules", label: "Rules", filename: "rules.json" },
|
|
8
|
+
{ id: "skills", label: "Skills", filename: "skills.json" },
|
|
9
|
+
{ id: "mcps", label: "MCPs", filename: "mcps.json" },
|
|
10
|
+
{ id: "utils", label: "Utils", filename: "utils.json" },
|
|
11
|
+
{ id: "hooks", label: "Hooks", filename: "hooks.json" },
|
|
12
|
+
{ id: "presets", label: "Presets", filename: "presets.json" },
|
|
13
|
+
];
|
|
14
|
+
export function runManifestShow(runtime, options) {
|
|
15
|
+
const selected = selectedCategories(options);
|
|
16
|
+
const manifestDir = manifestShowDir(options);
|
|
17
|
+
const scopeLabel = options.setupScope === "project" ? "Project" : "Global";
|
|
18
|
+
runtime.io.stdout("");
|
|
19
|
+
runtime.io.stdout(sectionTitle("AFK manifests"));
|
|
20
|
+
runtime.io.stdout(`${muted("Scope")} ${scopeBadge(scopeLabel)} ${muted("Directory")} ${manifestDir}`);
|
|
21
|
+
for (const category of selected) {
|
|
22
|
+
const loaded = loadManifest(manifestDir, category.filename);
|
|
23
|
+
runtime.io.stdout(renderCardHeader(category.label, loaded));
|
|
24
|
+
if (!loaded.content) {
|
|
25
|
+
runtime.io.stdout(`${muted(" status")} ${paint(terminalPalette.ember, "missing")}`);
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
runtime.io.stdout(renderManifestSummary(category.id, loaded.content));
|
|
29
|
+
}
|
|
30
|
+
return 0;
|
|
31
|
+
}
|
|
32
|
+
function selectedCategories(options) {
|
|
33
|
+
const flags = options.selectedManifestCategories;
|
|
34
|
+
if (flags.length === 0) {
|
|
35
|
+
return categories.filter((category) => category.id !== "presets");
|
|
36
|
+
}
|
|
37
|
+
return categories.filter((category) => flags.includes(category.id));
|
|
38
|
+
}
|
|
39
|
+
function manifestShowDir(options) {
|
|
40
|
+
return options.setupScope === "project" ? join(options.cwd, "afk", "manifests") : localManifestDir(options.homeDir);
|
|
41
|
+
}
|
|
42
|
+
function loadManifest(manifestDir, filename) {
|
|
43
|
+
const localPath = join(manifestDir, filename);
|
|
44
|
+
if (existsSync(localPath)) {
|
|
45
|
+
return {
|
|
46
|
+
source: "local",
|
|
47
|
+
path: localPath,
|
|
48
|
+
content: JSON.parse(readFileSync(localPath, "utf8")),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
source: "missing",
|
|
53
|
+
path: localPath,
|
|
54
|
+
content: null,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function renderCardHeader(label, loaded) {
|
|
58
|
+
const status = loaded.source === "missing" ? paint(terminalPalette.ember, "missing") : paint(terminalPalette.harbor, "ready");
|
|
59
|
+
return [
|
|
60
|
+
"",
|
|
61
|
+
`${paint(terminalPalette.brass, "┌")} ${bold}${label}${reset} ${muted(`(${loaded.source})`)} ${status}`,
|
|
62
|
+
`${muted(" file")} ${loaded.path}`,
|
|
63
|
+
].join("\n");
|
|
64
|
+
}
|
|
65
|
+
function renderManifestSummary(category, manifest) {
|
|
66
|
+
if (!isRecord(manifest)) {
|
|
67
|
+
return "- Invalid manifest shape";
|
|
68
|
+
}
|
|
69
|
+
switch (category) {
|
|
70
|
+
case "rules":
|
|
71
|
+
return renderRules(manifest);
|
|
72
|
+
case "skills":
|
|
73
|
+
return renderSkills(manifest);
|
|
74
|
+
case "mcps":
|
|
75
|
+
return renderItems(manifest, "MCP");
|
|
76
|
+
case "utils":
|
|
77
|
+
return renderUtils(manifest);
|
|
78
|
+
case "hooks":
|
|
79
|
+
return renderHooks(manifest);
|
|
80
|
+
case "presets":
|
|
81
|
+
return renderPresets(manifest);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function renderRules(manifest) {
|
|
85
|
+
const lines = [summaryLine("version", valueOrUnknown(manifest.version))];
|
|
86
|
+
if (typeof manifest.source === "string") {
|
|
87
|
+
lines.push(summaryLine("source", manifest.source));
|
|
88
|
+
}
|
|
89
|
+
if (typeof manifest.url === "string") {
|
|
90
|
+
lines.push(summaryLine("url", manifest.url || "(empty)"));
|
|
91
|
+
}
|
|
92
|
+
if (Array.isArray(manifest.files)) {
|
|
93
|
+
lines.push(summaryLine("files", String(manifest.files.length)));
|
|
94
|
+
for (const item of manifest.files) {
|
|
95
|
+
if (!isRecord(item)) {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
lines.push(itemLine(`${String(item.id ?? "unnamed")} ${muted(String(item.path ?? item.url ?? "(no path)"))}${defaultSuffix(item.default)}`));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return lines.join("\n");
|
|
102
|
+
}
|
|
103
|
+
function renderSkills(manifest) {
|
|
104
|
+
return [
|
|
105
|
+
summaryLine("version", valueOrUnknown(manifest.version)),
|
|
106
|
+
summaryLine("default source", typeof manifest.defaultSource === "string" && manifest.defaultSource ? manifest.defaultSource : "(none)"),
|
|
107
|
+
...renderItemList(manifest.items, "skill", (item) => {
|
|
108
|
+
const args = Array.isArray(item.args) && item.args.length > 0 ? item.args.filter((value) => typeof value === "string").join(" ") : "";
|
|
109
|
+
const auto = item.autoInvocation === false ? ` ${paint(terminalPalette.ember, "auto:off")}` : "";
|
|
110
|
+
return sourceItemLine(`${labelFor(item)}${defaultSuffix(item.default)}${auto}`, args);
|
|
111
|
+
}),
|
|
112
|
+
].join("\n");
|
|
113
|
+
}
|
|
114
|
+
function renderUtils(manifest) {
|
|
115
|
+
return [
|
|
116
|
+
summaryLine("version", valueOrUnknown(manifest.version)),
|
|
117
|
+
...renderItemList(manifest.items, "utility", (item) => {
|
|
118
|
+
const description = typeof item.description === "string" ? item.description : "";
|
|
119
|
+
return sourceItemLine(`${labelFor(item)}${defaultSuffix(item.default)}`, description);
|
|
120
|
+
}),
|
|
121
|
+
].join("\n");
|
|
122
|
+
}
|
|
123
|
+
function renderHooks(manifest) {
|
|
124
|
+
return [
|
|
125
|
+
summaryLine("version", valueOrUnknown(manifest.version)),
|
|
126
|
+
...renderItemList(manifest.items, "hook", (item) => {
|
|
127
|
+
const agents = Array.isArray(item.agents) ? ` [${item.agents.filter((value) => typeof value === "string").join(", ")}]` : "";
|
|
128
|
+
const description = typeof item.description === "string" ? item.description : "";
|
|
129
|
+
return sourceItemLine(`${labelFor(item)}${defaultSuffix(item.default)}${agents}`, description);
|
|
130
|
+
}),
|
|
131
|
+
].join("\n");
|
|
132
|
+
}
|
|
133
|
+
function renderPresets(manifest) {
|
|
134
|
+
return [
|
|
135
|
+
summaryLine("version", valueOrUnknown(manifest.version)),
|
|
136
|
+
summaryLine("defaults source", typeof manifest.defaultsSource === "string" && manifest.defaultsSource ? manifest.defaultsSource : "(none)"),
|
|
137
|
+
...renderItemList(manifest.presets, "preset", (item) => {
|
|
138
|
+
const areas = Array.isArray(item.areas) ? ` [${item.areas.filter((value) => typeof value === "string").join(", ")}]` : "";
|
|
139
|
+
return `${labelFor(item)}${areas}`;
|
|
140
|
+
}),
|
|
141
|
+
].join("\n");
|
|
142
|
+
}
|
|
143
|
+
function renderItems(manifest, singular) {
|
|
144
|
+
return [
|
|
145
|
+
summaryLine("version", valueOrUnknown(manifest.version)),
|
|
146
|
+
...(typeof manifest.source === "string" ? [summaryLine("source", manifest.source)] : []),
|
|
147
|
+
...renderItemList(manifest.items, singular, (item) => {
|
|
148
|
+
const source = item.url ?? item.source ?? "";
|
|
149
|
+
return sourceItemLine(`${labelFor(item)}${defaultSuffix(item.default)}`, typeof source === "string" ? source : "");
|
|
150
|
+
}),
|
|
151
|
+
].join("\n");
|
|
152
|
+
}
|
|
153
|
+
function renderItemList(items, singular, format) {
|
|
154
|
+
if (!Array.isArray(items) || items.length === 0) {
|
|
155
|
+
return [summaryLine(pluralize(singular).toLowerCase(), "0")];
|
|
156
|
+
}
|
|
157
|
+
const records = items.filter(isRecord);
|
|
158
|
+
const selected = records.filter((item) => item.default === true).length;
|
|
159
|
+
return [
|
|
160
|
+
summaryLine(pluralize(singular).toLowerCase(), `${records.length}${selected > 0 ? ` (${selected} default)` : ""}`),
|
|
161
|
+
...records.map((item) => itemLine(format(item))),
|
|
162
|
+
];
|
|
163
|
+
}
|
|
164
|
+
function labelFor(item) {
|
|
165
|
+
const id = typeof item.id === "string" ? item.id : "unnamed";
|
|
166
|
+
const label = typeof item.label === "string" && item.label !== id ? ` (${item.label})` : "";
|
|
167
|
+
return `${id}${label}`;
|
|
168
|
+
}
|
|
169
|
+
function defaultSuffix(value) {
|
|
170
|
+
return value === true ? ` ${paint(terminalPalette.harbor, "[default]")}` : "";
|
|
171
|
+
}
|
|
172
|
+
function summaryLine(label, value) {
|
|
173
|
+
return `${muted(` ${label}`)} ${value}`;
|
|
174
|
+
}
|
|
175
|
+
function itemLine(value) {
|
|
176
|
+
return `${paint(terminalPalette.brass, " •")} ${value}`;
|
|
177
|
+
}
|
|
178
|
+
function sourceItemLine(title, source) {
|
|
179
|
+
const trimmed = source.trim();
|
|
180
|
+
if (!trimmed) {
|
|
181
|
+
return title;
|
|
182
|
+
}
|
|
183
|
+
return `${title}\n${muted(` ${truncateMiddle(trimmed, 96)}`)}`;
|
|
184
|
+
}
|
|
185
|
+
function scopeBadge(value) {
|
|
186
|
+
return paint(value === "Project" ? terminalPalette.harbor : terminalPalette.brass, value);
|
|
187
|
+
}
|
|
188
|
+
function valueOrUnknown(value) {
|
|
189
|
+
return value === undefined || value === null ? "unknown" : String(value);
|
|
190
|
+
}
|
|
191
|
+
function pluralize(value) {
|
|
192
|
+
if (value === "utility") {
|
|
193
|
+
return "Utilities";
|
|
194
|
+
}
|
|
195
|
+
return `${value.slice(0, 1).toUpperCase()}${value.slice(1)}s`;
|
|
196
|
+
}
|
|
197
|
+
function truncateMiddle(value, maxLength) {
|
|
198
|
+
if (value.length <= maxLength) {
|
|
199
|
+
return value;
|
|
200
|
+
}
|
|
201
|
+
const marker = "...";
|
|
202
|
+
const sideLength = Math.floor((maxLength - marker.length) / 2);
|
|
203
|
+
return `${value.slice(0, sideLength)}${marker}${value.slice(value.length - sideLength)}`;
|
|
204
|
+
}
|
|
205
|
+
function isRecord(value) {
|
|
206
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
207
|
+
}
|
package/dist/manifest.js
ADDED
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
3
|
+
import { manifestPath } from "./paths.js";
|
|
4
|
+
const manifestNames = ["skills.json", "mcps.json", "presets.json", "rules.json", "utils.json", "hooks.json"];
|
|
5
|
+
const rawBaseUrl = "https://raw.githubusercontent.com/logbookfordevs/ai-field-kit";
|
|
6
|
+
const builtInDefaultsSource = "logbookfordevs/ai-field-kit";
|
|
7
|
+
export function localAfkDir(homeDir) {
|
|
8
|
+
return join(homeDir, ".agents", "afk");
|
|
9
|
+
}
|
|
10
|
+
export function localManifestDir(homeDir) {
|
|
11
|
+
return join(localAfkDir(homeDir), "manifests");
|
|
12
|
+
}
|
|
13
|
+
export function projectManifestDir(cwd) {
|
|
14
|
+
return join(cwd, "afk", "manifests");
|
|
15
|
+
}
|
|
16
|
+
export async function ensureLocalManifests(options) {
|
|
17
|
+
const operations = [];
|
|
18
|
+
const manifestDir = manifestDirForOptions(options);
|
|
19
|
+
const effectiveDefaultsSource = options.defaultsSource || rememberedDefaultsSource(manifestDir) || builtInDefaultsSource;
|
|
20
|
+
const shouldRefreshDefaults = options.refreshDefaults || Boolean(options.defaultsSource);
|
|
21
|
+
if (!existsSync(manifestDir)) {
|
|
22
|
+
operations.push({ type: "mkdir", path: manifestDir });
|
|
23
|
+
}
|
|
24
|
+
for (const name of manifestNames) {
|
|
25
|
+
const target = join(manifestDir, name);
|
|
26
|
+
if (!shouldRefreshDefaults && existsSync(target)) {
|
|
27
|
+
const migrated = migrateLocalManifest(name, readFileSync(target, "utf8"));
|
|
28
|
+
if (migrated) {
|
|
29
|
+
operations.push({ type: "write", path: target, content: migrated });
|
|
30
|
+
}
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
const content = options.empty
|
|
34
|
+
? emptyManifestContent(name, options, effectiveDefaultsSource)
|
|
35
|
+
: await defaultManifestContent(name, options, effectiveDefaultsSource);
|
|
36
|
+
if (content) {
|
|
37
|
+
operations.push({ type: "write", path: target, content });
|
|
38
|
+
}
|
|
39
|
+
else if (existsSync(target)) {
|
|
40
|
+
operations.push({ type: "skip", path: target, reason: "not provided by defaults source" });
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
operations.push({ type: "write", path: target, content: emptyManifestContent(name, options, effectiveDefaultsSource) });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return operations;
|
|
47
|
+
}
|
|
48
|
+
export function loadSkillManifest(options) {
|
|
49
|
+
return parseLocalManifest(options.homeDir, "skills.json", isSkillManifest);
|
|
50
|
+
}
|
|
51
|
+
export function loadMcpManifest(options) {
|
|
52
|
+
return parseLocalManifest(options.homeDir, "mcps.json", isMcpManifest);
|
|
53
|
+
}
|
|
54
|
+
export function loadRulesManifest(options) {
|
|
55
|
+
return parseLocalManifest(options.homeDir, "rules.json", isRulesManifest);
|
|
56
|
+
}
|
|
57
|
+
export function loadUtilityManifest(options) {
|
|
58
|
+
return parseLocalManifest(options.homeDir, "utils.json", isUtilityManifest);
|
|
59
|
+
}
|
|
60
|
+
export function loadHookManifest(options) {
|
|
61
|
+
return parseLocalManifest(options.homeDir, "hooks.json", isHookManifest);
|
|
62
|
+
}
|
|
63
|
+
function parseLocalManifest(homeDir, name, guard) {
|
|
64
|
+
const path = join(localManifestDir(homeDir), name);
|
|
65
|
+
if (!existsSync(path)) {
|
|
66
|
+
throw new Error(`Missing AFK manifest: ${path}. Run "afk setup refresh" to prepare local manifests.`);
|
|
67
|
+
}
|
|
68
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
69
|
+
if (!guard(parsed)) {
|
|
70
|
+
throw new Error(`Invalid AFK manifest: ${path}`);
|
|
71
|
+
}
|
|
72
|
+
return parsed;
|
|
73
|
+
}
|
|
74
|
+
async function defaultManifestContent(name, options, defaultsSource) {
|
|
75
|
+
if (name === "presets.json") {
|
|
76
|
+
const content = await fetchDefaultManifest(name, options, defaultsSource);
|
|
77
|
+
return content ? withRememberedDefaultsSource(content, defaultsSource) : null;
|
|
78
|
+
}
|
|
79
|
+
return fetchDefaultManifest(name, options, defaultsSource);
|
|
80
|
+
}
|
|
81
|
+
async function fetchDefaultManifest(name, options, defaultsSource) {
|
|
82
|
+
if (options.rulesSource === "local") {
|
|
83
|
+
return readLocalPackageManifest(name, options);
|
|
84
|
+
}
|
|
85
|
+
const localContent = readLocalDefaultManifest(name, options, defaultsSource);
|
|
86
|
+
if (localContent) {
|
|
87
|
+
return localContent;
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
for (const baseUrl of defaultsManifestBaseUrls(defaultsSource, options.rulesRef)) {
|
|
91
|
+
const url = `${baseUrl}/${name}`;
|
|
92
|
+
const response = await fetch(url);
|
|
93
|
+
if (response.ok) {
|
|
94
|
+
return ensureTrailingNewline(await response.text());
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
function readLocalPackageManifest(name, options) {
|
|
104
|
+
const cwd = options.cwd ?? process.cwd();
|
|
105
|
+
const candidates = [
|
|
106
|
+
join(cwd, "packages", "afk", "manifests", name),
|
|
107
|
+
join(cwd, "manifests", name),
|
|
108
|
+
join(options.repoDir, "packages", "afk", "manifests", name),
|
|
109
|
+
manifestPath(name),
|
|
110
|
+
];
|
|
111
|
+
for (const candidate of unique(candidates)) {
|
|
112
|
+
if (existsSync(candidate)) {
|
|
113
|
+
return ensureTrailingNewline(readFileSync(candidate, "utf8"));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
function readLocalDefaultManifest(name, options, defaultsSource) {
|
|
119
|
+
const normalized = defaultsSource.trim().replace(/\/$/, "");
|
|
120
|
+
if (!normalized || normalized.startsWith("http://") || normalized.startsWith("https://") || normalized.includes("github.com")) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
if (/^[^/\s]+\/[^/\s]+$/.test(normalized)) {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
const basePath = isAbsolute(normalized) ? normalized : resolve(options.cwd ?? process.cwd(), normalized);
|
|
127
|
+
const candidates = [
|
|
128
|
+
join(basePath, name),
|
|
129
|
+
join(basePath, "afk", "manifests", name),
|
|
130
|
+
];
|
|
131
|
+
for (const candidate of unique(candidates)) {
|
|
132
|
+
if (existsSync(candidate)) {
|
|
133
|
+
return ensureTrailingNewline(readFileSync(candidate, "utf8"));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
function unique(values) {
|
|
139
|
+
return [...new Set(values)];
|
|
140
|
+
}
|
|
141
|
+
function manifestDirForOptions(options) {
|
|
142
|
+
return options.manifestLocal ? projectManifestDir(options.cwd ?? process.cwd()) : localManifestDir(options.homeDir);
|
|
143
|
+
}
|
|
144
|
+
function rememberedDefaultsSource(manifestDir) {
|
|
145
|
+
const path = join(manifestDir, "presets.json");
|
|
146
|
+
if (!existsSync(path)) {
|
|
147
|
+
return "";
|
|
148
|
+
}
|
|
149
|
+
try {
|
|
150
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
151
|
+
if (isRecord(parsed) && typeof parsed.defaultsSource === "string") {
|
|
152
|
+
return parsed.defaultsSource.trim();
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
return "";
|
|
157
|
+
}
|
|
158
|
+
return "";
|
|
159
|
+
}
|
|
160
|
+
export function defaultsManifestBaseUrl(source, ref) {
|
|
161
|
+
return defaultsManifestBaseUrls(source, ref)[0] ?? `${rawBaseUrl}/${encodeURIComponent(ref)}/packages/afk/manifests`;
|
|
162
|
+
}
|
|
163
|
+
export function defaultsManifestBaseUrls(source, ref) {
|
|
164
|
+
const normalized = source.trim().replace(/\/$/, "");
|
|
165
|
+
if (!normalized) {
|
|
166
|
+
return [`${rawBaseUrl}/${encodeURIComponent(ref)}/packages/afk/manifests`];
|
|
167
|
+
}
|
|
168
|
+
const rawMatch = normalized.match(/^https:\/\/raw\.githubusercontent\.com\/([^/]+)\/([^/]+)\/([^/]+)\/(.+)$/);
|
|
169
|
+
if (rawMatch) {
|
|
170
|
+
const [, owner, repo, sourceRef, path] = rawMatch;
|
|
171
|
+
return [`https://raw.githubusercontent.com/${owner}/${repo}/${sourceRef}/${path?.replace(/\/$/, "")}`];
|
|
172
|
+
}
|
|
173
|
+
const githubTreeMatch = normalized.match(/^(?:https:\/\/)?github\.com\/([^/]+)\/([^/]+)\/tree\/([^/]+)\/(.+)$/);
|
|
174
|
+
if (githubTreeMatch) {
|
|
175
|
+
const [, owner, repo, sourceRef, path] = githubTreeMatch;
|
|
176
|
+
return [`https://raw.githubusercontent.com/${owner}/${repo}/${sourceRef}/${path?.replace(/\/$/, "")}`];
|
|
177
|
+
}
|
|
178
|
+
const githubRepoMatch = normalized.match(/^(?:https:\/\/)?github\.com\/([^/]+)\/([^/]+)$/);
|
|
179
|
+
if (githubRepoMatch) {
|
|
180
|
+
const [, owner, repo] = githubRepoMatch;
|
|
181
|
+
return defaultRepoManifestUrls(owner ?? "", repo ?? "", ref);
|
|
182
|
+
}
|
|
183
|
+
const shorthandMatch = normalized.match(/^([^/\s]+)\/([^/\s]+)$/);
|
|
184
|
+
if (shorthandMatch) {
|
|
185
|
+
const [, owner, repo] = shorthandMatch;
|
|
186
|
+
return defaultRepoManifestUrls(owner ?? "", repo ?? "", ref);
|
|
187
|
+
}
|
|
188
|
+
return [normalized];
|
|
189
|
+
}
|
|
190
|
+
function defaultRepoManifestUrls(owner, repo, ref) {
|
|
191
|
+
const base = `https://raw.githubusercontent.com/${owner}/${repo}/${encodeURIComponent(ref)}`;
|
|
192
|
+
return [
|
|
193
|
+
`${base}/afk/manifests`,
|
|
194
|
+
`${base}/packages/afk/manifests`,
|
|
195
|
+
];
|
|
196
|
+
}
|
|
197
|
+
function emptyManifestContent(name, options, defaultsSource) {
|
|
198
|
+
if (name === "skills.json") {
|
|
199
|
+
return `${JSON.stringify({ version: 1, defaultSource: "", items: [] }, null, 2)}\n`;
|
|
200
|
+
}
|
|
201
|
+
if (name === "mcps.json") {
|
|
202
|
+
return `${JSON.stringify({ version: 1, items: [] }, null, 2)}\n`;
|
|
203
|
+
}
|
|
204
|
+
if (name === "rules.json") {
|
|
205
|
+
return `${JSON.stringify({ version: 1, source: "github", url: "" }, null, 2)}\n`;
|
|
206
|
+
}
|
|
207
|
+
if (name === "utils.json") {
|
|
208
|
+
return `${JSON.stringify({ version: 1, items: [] }, null, 2)}\n`;
|
|
209
|
+
}
|
|
210
|
+
if (name === "hooks.json") {
|
|
211
|
+
return `${JSON.stringify({ version: 1, items: [] }, null, 2)}\n`;
|
|
212
|
+
}
|
|
213
|
+
return `${JSON.stringify({ version: 1, defaultsSource, presets: [] }, null, 2)}\n`;
|
|
214
|
+
}
|
|
215
|
+
function withRememberedDefaultsSource(content, defaultsSource) {
|
|
216
|
+
try {
|
|
217
|
+
const parsed = JSON.parse(content);
|
|
218
|
+
if (isRecord(parsed)) {
|
|
219
|
+
return `${JSON.stringify({ ...parsed, defaultsSource }, null, 2)}\n`;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
catch {
|
|
223
|
+
return content;
|
|
224
|
+
}
|
|
225
|
+
return content;
|
|
226
|
+
}
|
|
227
|
+
function ensureTrailingNewline(content) {
|
|
228
|
+
return content.endsWith("\n") ? content : `${content}\n`;
|
|
229
|
+
}
|
|
230
|
+
function migrateLocalManifest(name, content) {
|
|
231
|
+
if (name === "skills.json") {
|
|
232
|
+
return migrateSkillsManifest(content);
|
|
233
|
+
}
|
|
234
|
+
if (name === "presets.json") {
|
|
235
|
+
return migratePresetsManifest(content);
|
|
236
|
+
}
|
|
237
|
+
if (name !== "mcps.json") {
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
let parsed;
|
|
241
|
+
try {
|
|
242
|
+
parsed = JSON.parse(content);
|
|
243
|
+
}
|
|
244
|
+
catch {
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
if (!isMcpManifest(parsed)) {
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
let changed = false;
|
|
251
|
+
const items = parsed.items.map((item) => {
|
|
252
|
+
if (item.id !== "stitch" || item.source !== "https://stitch.googleapis.com/mcp") {
|
|
253
|
+
return item;
|
|
254
|
+
}
|
|
255
|
+
const args = removeArgPair(item.args, "--header", "X-Goog-Api-Key: KEY_STITCH");
|
|
256
|
+
if (args.length === item.args.length) {
|
|
257
|
+
return item;
|
|
258
|
+
}
|
|
259
|
+
changed = true;
|
|
260
|
+
return { ...item, args };
|
|
261
|
+
});
|
|
262
|
+
if (!changed) {
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
return `${JSON.stringify({ ...parsed, version: Math.max(parsed.version, 2), items }, null, 2)}\n`;
|
|
266
|
+
}
|
|
267
|
+
function migrateSkillsManifest(content) {
|
|
268
|
+
let parsed;
|
|
269
|
+
try {
|
|
270
|
+
parsed = JSON.parse(content);
|
|
271
|
+
}
|
|
272
|
+
catch {
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
if (!isSkillManifest(parsed)) {
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
let changed = false;
|
|
279
|
+
const items = parsed.items.map((item) => {
|
|
280
|
+
if (item.autoInvocation !== undefined) {
|
|
281
|
+
return item;
|
|
282
|
+
}
|
|
283
|
+
changed = true;
|
|
284
|
+
return { ...item, autoInvocation: true };
|
|
285
|
+
});
|
|
286
|
+
if (!changed) {
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
return `${JSON.stringify({ ...parsed, items }, null, 2)}\n`;
|
|
290
|
+
}
|
|
291
|
+
function migratePresetsManifest(content) {
|
|
292
|
+
let parsed;
|
|
293
|
+
try {
|
|
294
|
+
parsed = JSON.parse(content);
|
|
295
|
+
}
|
|
296
|
+
catch {
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
299
|
+
if (!isRecord(parsed) || typeof parsed.version !== "number" || !Array.isArray(parsed.presets) || typeof parsed.defaultsSource === "string") {
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
return `${JSON.stringify({ ...parsed, defaultsSource: builtInDefaultsSource }, null, 2)}\n`;
|
|
303
|
+
}
|
|
304
|
+
function removeArgPair(args, flag, value) {
|
|
305
|
+
const next = [];
|
|
306
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
307
|
+
if (args[index] === flag && args[index + 1] === value) {
|
|
308
|
+
index += 1;
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
const arg = args[index];
|
|
312
|
+
if (arg) {
|
|
313
|
+
next.push(arg);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return next;
|
|
317
|
+
}
|
|
318
|
+
export function writeLocalManifestNow(homeDir, name, content) {
|
|
319
|
+
const path = join(localManifestDir(homeDir), name);
|
|
320
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
321
|
+
writeFileSync(path, content);
|
|
322
|
+
}
|
|
323
|
+
function isRecord(value) {
|
|
324
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
325
|
+
}
|
|
326
|
+
function isStringArray(value) {
|
|
327
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
328
|
+
}
|
|
329
|
+
function isSkillManifest(value) {
|
|
330
|
+
if (!isRecord(value) || typeof value.version !== "number" || typeof value.defaultSource !== "string" || !Array.isArray(value.items)) {
|
|
331
|
+
return false;
|
|
332
|
+
}
|
|
333
|
+
return value.items.every((item) => {
|
|
334
|
+
if (!isRecord(item)) {
|
|
335
|
+
return false;
|
|
336
|
+
}
|
|
337
|
+
return (typeof item.id === "string" &&
|
|
338
|
+
typeof item.label === "string" &&
|
|
339
|
+
typeof item.source === "string" &&
|
|
340
|
+
isStringArray(item.args) &&
|
|
341
|
+
typeof item.default === "boolean" &&
|
|
342
|
+
(item.autoInvocation === undefined || typeof item.autoInvocation === "boolean"));
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
function isMcpManifest(value) {
|
|
346
|
+
if (!isRecord(value) || typeof value.version !== "number" || !Array.isArray(value.items)) {
|
|
347
|
+
return false;
|
|
348
|
+
}
|
|
349
|
+
return value.items.every((item) => {
|
|
350
|
+
if (!isRecord(item)) {
|
|
351
|
+
return false;
|
|
352
|
+
}
|
|
353
|
+
return (typeof item.id === "string" &&
|
|
354
|
+
typeof item.label === "string" &&
|
|
355
|
+
typeof item.source === "string" &&
|
|
356
|
+
isStringArray(item.args) &&
|
|
357
|
+
typeof item.default === "boolean");
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
function isRulesManifest(value) {
|
|
361
|
+
if (!isRecord(value) || typeof value.version !== "number" || (value.source !== "github" && value.source !== "local")) {
|
|
362
|
+
return false;
|
|
363
|
+
}
|
|
364
|
+
return typeof value.url === "string";
|
|
365
|
+
}
|
|
366
|
+
function isUtilityManifest(value) {
|
|
367
|
+
if (!isRecord(value) || typeof value.version !== "number" || !Array.isArray(value.items)) {
|
|
368
|
+
return false;
|
|
369
|
+
}
|
|
370
|
+
return value.items.every((item) => {
|
|
371
|
+
if (!isRecord(item) || !isRecord(item.install)) {
|
|
372
|
+
return false;
|
|
373
|
+
}
|
|
374
|
+
return (typeof item.id === "string" &&
|
|
375
|
+
typeof item.label === "string" &&
|
|
376
|
+
typeof item.description === "string" &&
|
|
377
|
+
typeof item.install.command === "string" &&
|
|
378
|
+
isStringArray(item.install.args) &&
|
|
379
|
+
(item.postInstall === undefined || item.postInstall === "rtk-init" || isUtilityPostInstallCommand(item.postInstall)) &&
|
|
380
|
+
typeof item.default === "boolean");
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
function isUtilityPostInstallCommand(value) {
|
|
384
|
+
return (isRecord(value) &&
|
|
385
|
+
(value.label === undefined || typeof value.label === "string") &&
|
|
386
|
+
typeof value.command === "string" &&
|
|
387
|
+
isStringArray(value.args));
|
|
388
|
+
}
|
|
389
|
+
function isHookManifest(value) {
|
|
390
|
+
if (!isRecord(value) || typeof value.version !== "number" || !Array.isArray(value.items)) {
|
|
391
|
+
return false;
|
|
392
|
+
}
|
|
393
|
+
return value.items.every((item) => {
|
|
394
|
+
if (!isRecord(item)) {
|
|
395
|
+
return false;
|
|
396
|
+
}
|
|
397
|
+
return (typeof item.id === "string" &&
|
|
398
|
+
typeof item.label === "string" &&
|
|
399
|
+
typeof item.description === "string" &&
|
|
400
|
+
typeof item.source === "string" &&
|
|
401
|
+
typeof item.command === "string" &&
|
|
402
|
+
isStringArray(item.args) &&
|
|
403
|
+
Array.isArray(item.events) &&
|
|
404
|
+
item.events.every((event) => event === "stop") &&
|
|
405
|
+
Array.isArray(item.agents) &&
|
|
406
|
+
item.agents.every((agent) => agent === "codex" || agent === "claude" || agent === "cursor-local") &&
|
|
407
|
+
typeof item.default === "boolean");
|
|
408
|
+
});
|
|
409
|
+
}
|
package/dist/paths.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { dirname, resolve } from "node:path";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
export function packageRoot() {
|
|
4
|
+
return resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
5
|
+
}
|
|
6
|
+
export function defaultRepoDir() {
|
|
7
|
+
return resolve(packageRoot(), "..", "..");
|
|
8
|
+
}
|
|
9
|
+
export function resolveHome(input = process.env) {
|
|
10
|
+
return input.HOME ?? input.USERPROFILE ?? process.cwd();
|
|
11
|
+
}
|
|
12
|
+
export function resolveRepoDir(input = process.env) {
|
|
13
|
+
return input.AI_RULES_REPO ? resolve(input.AI_RULES_REPO) : defaultRepoDir();
|
|
14
|
+
}
|
|
15
|
+
export function manifestPath(name) {
|
|
16
|
+
return resolve(packageRoot(), "manifests", name);
|
|
17
|
+
}
|