@infinitedusky/indusk-mcp 0.9.1 → 1.0.1
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/bin/cli.js +53 -4
- package/dist/bin/commands/extensions.d.ts +7 -0
- package/dist/bin/commands/extensions.js +288 -0
- package/dist/bin/commands/init.d.ts +0 -2
- package/dist/bin/commands/init.js +38 -150
- package/dist/lib/extension-loader.d.ts +60 -0
- package/dist/lib/extension-loader.js +97 -0
- package/dist/lib/verification-discovery.d.ts +2 -2
- package/dist/lib/verification-discovery.js +21 -1
- package/dist/tools/graph-tools.js +2 -2
- package/dist/tools/quality-tools.js +1 -1
- package/dist/tools/system-tools.js +49 -82
- package/extensions/cgc/manifest.json +25 -0
- package/extensions/cgc/skill.md +35 -0
- package/extensions/docker/manifest.json +9 -0
- package/extensions/falkordb/manifest.json +19 -0
- package/extensions/nextjs/manifest.json +6 -0
- package/extensions/react/manifest.json +6 -0
- package/extensions/solidity/manifest.json +6 -0
- package/extensions/tailwind/manifest.json +6 -0
- package/extensions/testing/manifest.json +12 -0
- package/extensions/typescript/manifest.json +11 -0
- package/extensions/vitepress/manifest.json +6 -0
- package/hooks/validate-impl-structure.js +1 -2
- package/lessons/community/community-add-to-sidebar.md +7 -0
- package/lessons/community/community-orbstack-no-port-forwarding.md +11 -0
- package/package.json +2 -1
- package/skills/context.md +10 -31
- package/skills/document.md +20 -38
- package/skills/onboard.md +8 -4
- package/skills/plan.md +9 -21
- package/skills/retrospective.md +1 -6
- package/skills/toolbelt.md +24 -75
- package/skills/verify.md +2 -1
- package/skills/work.md +3 -8
- /package/{skills/domain/docker.md → extensions/docker/skill.md} +0 -0
- /package/{skills/domain/nextjs.md → extensions/nextjs/skill.md} +0 -0
- /package/{skills/domain/react.md → extensions/react/skill.md} +0 -0
- /package/{skills/domain/solidity.md → extensions/solidity/skill.md} +0 -0
- /package/{skills/domain/tailwind.md → extensions/tailwind/skill.md} +0 -0
- /package/{skills/domain/testing.md → extensions/testing/skill.md} +0 -0
- /package/{skills/domain/typescript.md → extensions/typescript/skill.md} +0 -0
- /package/{skills/domain/vitepress.md → extensions/vitepress/skill.md} +0 -0
package/dist/bin/cli.js
CHANGED
|
@@ -14,15 +14,11 @@ program
|
|
|
14
14
|
.command("init")
|
|
15
15
|
.description("Initialize a project with InDusk dev system")
|
|
16
16
|
.option("-f, --force", "Overwrite existing files (except CLAUDE.md and planning/)")
|
|
17
|
-
.option("--skills <list>", "Comma-separated domain skills to install (e.g., nextjs,tailwind)")
|
|
18
|
-
.option("--no-domain-skills", "Skip domain skill detection and installation")
|
|
19
17
|
.option("--no-index", "Skip code graph indexing")
|
|
20
18
|
.action(async (opts) => {
|
|
21
19
|
const { init } = await import("./commands/init.js");
|
|
22
20
|
await init(process.cwd(), {
|
|
23
21
|
force: opts.force ?? false,
|
|
24
|
-
skills: opts.skills,
|
|
25
|
-
noDomainSkills: opts.domainSkills === false,
|
|
26
22
|
noIndex: opts.index === false,
|
|
27
23
|
});
|
|
28
24
|
});
|
|
@@ -33,6 +29,59 @@ program
|
|
|
33
29
|
const { update } = await import("./commands/update.js");
|
|
34
30
|
await update(process.cwd());
|
|
35
31
|
});
|
|
32
|
+
const ext = program
|
|
33
|
+
.command("extensions")
|
|
34
|
+
.description("Manage extensions (built-in and third-party)");
|
|
35
|
+
ext
|
|
36
|
+
.command("list")
|
|
37
|
+
.description("Show all available extensions")
|
|
38
|
+
.action(async () => {
|
|
39
|
+
const { extensionsList } = await import("./commands/extensions.js");
|
|
40
|
+
await extensionsList(process.cwd());
|
|
41
|
+
});
|
|
42
|
+
ext
|
|
43
|
+
.command("status")
|
|
44
|
+
.description("Show enabled extensions with health")
|
|
45
|
+
.action(async () => {
|
|
46
|
+
const { extensionsStatus } = await import("./commands/extensions.js");
|
|
47
|
+
await extensionsStatus(process.cwd());
|
|
48
|
+
});
|
|
49
|
+
ext
|
|
50
|
+
.command("enable <names...>")
|
|
51
|
+
.description("Enable extensions")
|
|
52
|
+
.action(async (names) => {
|
|
53
|
+
const { extensionsEnable } = await import("./commands/extensions.js");
|
|
54
|
+
await extensionsEnable(process.cwd(), names);
|
|
55
|
+
});
|
|
56
|
+
ext
|
|
57
|
+
.command("disable <names...>")
|
|
58
|
+
.description("Disable extensions")
|
|
59
|
+
.action(async (names) => {
|
|
60
|
+
const { extensionsDisable } = await import("./commands/extensions.js");
|
|
61
|
+
await extensionsDisable(process.cwd(), names);
|
|
62
|
+
});
|
|
63
|
+
ext
|
|
64
|
+
.command("add <name>")
|
|
65
|
+
.description("Add a third-party extension")
|
|
66
|
+
.requiredOption("--from <source>", "Source: npm:pkg, github:user/repo, URL, or local path")
|
|
67
|
+
.action(async (name, opts) => {
|
|
68
|
+
const { extensionsAdd } = await import("./commands/extensions.js");
|
|
69
|
+
await extensionsAdd(process.cwd(), name, opts.from);
|
|
70
|
+
});
|
|
71
|
+
ext
|
|
72
|
+
.command("remove <names...>")
|
|
73
|
+
.description("Remove extensions")
|
|
74
|
+
.action(async (names) => {
|
|
75
|
+
const { extensionsRemove } = await import("./commands/extensions.js");
|
|
76
|
+
await extensionsRemove(process.cwd(), names);
|
|
77
|
+
});
|
|
78
|
+
ext
|
|
79
|
+
.command("suggest")
|
|
80
|
+
.description("Recommend extensions based on project contents")
|
|
81
|
+
.action(async () => {
|
|
82
|
+
const { extensionsSuggest } = await import("./commands/extensions.js");
|
|
83
|
+
await extensionsSuggest(process.cwd());
|
|
84
|
+
});
|
|
36
85
|
program
|
|
37
86
|
.command("init-docs")
|
|
38
87
|
.description("Scaffold a VitePress documentation site with Mermaid, llms.txt, and FullscreenDiagram")
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare function extensionsList(projectRoot: string): Promise<void>;
|
|
2
|
+
export declare function extensionsStatus(projectRoot: string): Promise<void>;
|
|
3
|
+
export declare function extensionsEnable(projectRoot: string, names: string[]): Promise<void>;
|
|
4
|
+
export declare function extensionsDisable(projectRoot: string, names: string[]): Promise<void>;
|
|
5
|
+
export declare function extensionsAdd(projectRoot: string, name: string, from: string): Promise<void>;
|
|
6
|
+
export declare function extensionsRemove(projectRoot: string, names: string[]): Promise<void>;
|
|
7
|
+
export declare function extensionsSuggest(projectRoot: string): Promise<void>;
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
import { cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { globSync } from "glob";
|
|
6
|
+
import { disableExtension, enableExtension, ensureExtensionsDirs, extensionsDir, getEnabledExtensions, isEnabled, loadExtension, loadExtensions, } from "../../lib/extension-loader.js";
|
|
7
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const packageRoot = join(__dirname, "../../..");
|
|
9
|
+
const builtinDir = join(packageRoot, "extensions");
|
|
10
|
+
function getBuiltinExtensions() {
|
|
11
|
+
if (!existsSync(builtinDir))
|
|
12
|
+
return [];
|
|
13
|
+
const dirs = globSync("*/manifest.json", { cwd: builtinDir });
|
|
14
|
+
return dirs
|
|
15
|
+
.map((d) => loadExtension(join(builtinDir, d)))
|
|
16
|
+
.filter((m) => m !== null);
|
|
17
|
+
}
|
|
18
|
+
export async function extensionsList(projectRoot) {
|
|
19
|
+
const builtins = getBuiltinExtensions();
|
|
20
|
+
const installed = loadExtensions(projectRoot);
|
|
21
|
+
console.info("Built-in extensions:\n");
|
|
22
|
+
for (const ext of builtins) {
|
|
23
|
+
const status = isEnabled(projectRoot, ext.name)
|
|
24
|
+
? "enabled"
|
|
25
|
+
: installed.some((i) => i.manifest.name === ext.name)
|
|
26
|
+
? "disabled"
|
|
27
|
+
: "not installed";
|
|
28
|
+
console.info(` ${ext.name} — ${ext.description} [${status}]`);
|
|
29
|
+
}
|
|
30
|
+
const thirdParty = installed.filter((i) => !builtins.some((b) => b.name === i.manifest.name));
|
|
31
|
+
if (thirdParty.length > 0) {
|
|
32
|
+
console.info("\nThird-party extensions:\n");
|
|
33
|
+
for (const ext of thirdParty) {
|
|
34
|
+
const status = ext.enabled ? "enabled" : "disabled";
|
|
35
|
+
console.info(` ${ext.manifest.name} — ${ext.manifest.description} [${status}]`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export async function extensionsStatus(projectRoot) {
|
|
40
|
+
const enabled = getEnabledExtensions(projectRoot);
|
|
41
|
+
if (enabled.length === 0) {
|
|
42
|
+
console.info("No extensions enabled. Run `extensions list` to see available.");
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
console.info("Enabled extensions:\n");
|
|
46
|
+
for (const ext of enabled) {
|
|
47
|
+
const checks = ext.manifest.provides.health_checks ?? [];
|
|
48
|
+
let healthStatus = "no health check";
|
|
49
|
+
if (checks.length > 0) {
|
|
50
|
+
const results = checks.map((check) => {
|
|
51
|
+
try {
|
|
52
|
+
execSync(check.command, {
|
|
53
|
+
cwd: projectRoot,
|
|
54
|
+
timeout: 10000,
|
|
55
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
56
|
+
});
|
|
57
|
+
return { name: check.name, ok: true };
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return { name: check.name, ok: false };
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
const allOk = results.every((r) => r.ok);
|
|
64
|
+
healthStatus = allOk
|
|
65
|
+
? "healthy"
|
|
66
|
+
: `unhealthy: ${results
|
|
67
|
+
.filter((r) => !r.ok)
|
|
68
|
+
.map((r) => r.name)
|
|
69
|
+
.join(", ")}`;
|
|
70
|
+
}
|
|
71
|
+
console.info(` ${ext.manifest.name} — ${healthStatus}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
export async function extensionsEnable(projectRoot, names) {
|
|
75
|
+
ensureExtensionsDirs(projectRoot);
|
|
76
|
+
for (const name of names) {
|
|
77
|
+
// Check if already enabled
|
|
78
|
+
if (isEnabled(projectRoot, name)) {
|
|
79
|
+
console.info(` ${name}: already enabled`);
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
// Try to move from disabled
|
|
83
|
+
if (enableExtension(projectRoot, name)) {
|
|
84
|
+
console.info(` ${name}: enabled (was disabled)`);
|
|
85
|
+
runHook(projectRoot, name, "on_init");
|
|
86
|
+
installSkill(projectRoot, name);
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
// Try to copy from built-in
|
|
90
|
+
const builtinManifest = join(builtinDir, name, "manifest.json");
|
|
91
|
+
if (existsSync(builtinManifest)) {
|
|
92
|
+
const targetPath = join(extensionsDir(projectRoot), `${name}.json`);
|
|
93
|
+
cpSync(builtinManifest, targetPath);
|
|
94
|
+
console.info(` ${name}: enabled (built-in)`);
|
|
95
|
+
runHook(projectRoot, name, "on_init");
|
|
96
|
+
installSkill(projectRoot, name);
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
console.info(` ${name}: not found — use 'extensions add ${name} --from <source>' for third-party`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
export async function extensionsDisable(projectRoot, names) {
|
|
103
|
+
for (const name of names) {
|
|
104
|
+
if (disableExtension(projectRoot, name)) {
|
|
105
|
+
console.info(` ${name}: disabled`);
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
console.info(` ${name}: not found or already disabled`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
export async function extensionsAdd(projectRoot, name, from) {
|
|
113
|
+
ensureExtensionsDirs(projectRoot);
|
|
114
|
+
let manifestContent = null;
|
|
115
|
+
if (from.startsWith("npm:")) {
|
|
116
|
+
// Fetch from npm package
|
|
117
|
+
const pkg = from.slice(4);
|
|
118
|
+
try {
|
|
119
|
+
const result = execSync(`npm pack ${pkg} --dry-run --json 2>/dev/null || echo "[]"`, {
|
|
120
|
+
encoding: "utf-8",
|
|
121
|
+
timeout: 30000,
|
|
122
|
+
});
|
|
123
|
+
// Try to read the manifest from the installed package
|
|
124
|
+
const npmRoot = execSync(`npm root`, { encoding: "utf-8", cwd: projectRoot }).trim();
|
|
125
|
+
const manifestPath = join(npmRoot, pkg, "indusk-extension.json");
|
|
126
|
+
if (existsSync(manifestPath)) {
|
|
127
|
+
manifestContent = readFileSync(manifestPath, "utf-8");
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
console.info(` ${name}: no indusk-extension.json found in ${pkg}`);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
console.info(` ${name}: failed to fetch from npm:${pkg}`);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
else if (from.startsWith("github:")) {
|
|
140
|
+
// Fetch from GitHub raw
|
|
141
|
+
const repo = from.slice(7);
|
|
142
|
+
const url = `https://raw.githubusercontent.com/${repo}/main/indusk-extension.json`;
|
|
143
|
+
try {
|
|
144
|
+
const result = execSync(`curl -sf "${url}"`, { encoding: "utf-8", timeout: 15000 });
|
|
145
|
+
manifestContent = result;
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
console.info(` ${name}: failed to fetch from ${url}`);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
else if (from.startsWith("http://") || from.startsWith("https://")) {
|
|
153
|
+
try {
|
|
154
|
+
manifestContent = execSync(`curl -sf "${from}"`, { encoding: "utf-8", timeout: 15000 });
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
console.info(` ${name}: failed to fetch from ${from}`);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
// Local path
|
|
163
|
+
if (existsSync(from)) {
|
|
164
|
+
manifestContent = readFileSync(from, "utf-8");
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
console.info(` ${name}: file not found at ${from}`);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (!manifestContent)
|
|
172
|
+
return;
|
|
173
|
+
// Validate
|
|
174
|
+
try {
|
|
175
|
+
const manifest = JSON.parse(manifestContent);
|
|
176
|
+
if (!manifest.name || !manifest.provides) {
|
|
177
|
+
console.info(` ${name}: invalid manifest (missing name or provides)`);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
console.info(` ${name}: invalid JSON in manifest`);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
const targetPath = join(extensionsDir(projectRoot), `${name}.json`);
|
|
186
|
+
writeFileSync(targetPath, manifestContent);
|
|
187
|
+
console.info(` ${name}: added from ${from}`);
|
|
188
|
+
}
|
|
189
|
+
export async function extensionsRemove(projectRoot, names) {
|
|
190
|
+
for (const name of names) {
|
|
191
|
+
const enPath = join(extensionsDir(projectRoot), `${name}.json`);
|
|
192
|
+
const disPath = join(extensionsDir(projectRoot), ".disabled", `${name}.json`);
|
|
193
|
+
if (existsSync(enPath)) {
|
|
194
|
+
rmSync(enPath);
|
|
195
|
+
console.info(` ${name}: removed`);
|
|
196
|
+
}
|
|
197
|
+
else if (existsSync(disPath)) {
|
|
198
|
+
rmSync(disPath);
|
|
199
|
+
console.info(` ${name}: removed (was disabled)`);
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
console.info(` ${name}: not found`);
|
|
203
|
+
}
|
|
204
|
+
// Remove skill if installed
|
|
205
|
+
const skillDir = join(projectRoot, ".claude/skills", name);
|
|
206
|
+
if (existsSync(skillDir)) {
|
|
207
|
+
rmSync(skillDir, { recursive: true });
|
|
208
|
+
console.info(` ${name}: skill removed from .claude/skills/`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
export async function extensionsSuggest(projectRoot) {
|
|
213
|
+
const builtins = getBuiltinExtensions();
|
|
214
|
+
const suggestions = [];
|
|
215
|
+
for (const ext of builtins) {
|
|
216
|
+
if (isEnabled(projectRoot, ext.name))
|
|
217
|
+
continue;
|
|
218
|
+
if (!ext.detect)
|
|
219
|
+
continue;
|
|
220
|
+
if (ext.detect.file && existsSync(join(projectRoot, ext.detect.file))) {
|
|
221
|
+
suggestions.push({ name: ext.name, reason: `${ext.detect.file} found` });
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
if (ext.detect.file_pattern) {
|
|
225
|
+
const matches = globSync(ext.detect.file_pattern, { cwd: projectRoot, maxDepth: 3 });
|
|
226
|
+
if (matches.length > 0) {
|
|
227
|
+
suggestions.push({ name: ext.name, reason: `${ext.detect.file_pattern} found` });
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
if (ext.detect.dependency || ext.detect.devDependency) {
|
|
232
|
+
const pkgPath = join(projectRoot, "package.json");
|
|
233
|
+
if (existsSync(pkgPath)) {
|
|
234
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
235
|
+
const deps = pkg.dependencies ?? {};
|
|
236
|
+
const devDeps = pkg.devDependencies ?? {};
|
|
237
|
+
if (ext.detect.dependency && deps[ext.detect.dependency]) {
|
|
238
|
+
suggestions.push({ name: ext.name, reason: `${ext.detect.dependency} in dependencies` });
|
|
239
|
+
}
|
|
240
|
+
else if (ext.detect.devDependency && devDeps[ext.detect.devDependency]) {
|
|
241
|
+
suggestions.push({
|
|
242
|
+
name: ext.name,
|
|
243
|
+
reason: `${ext.detect.devDependency} in devDependencies`,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
if (suggestions.length === 0) {
|
|
250
|
+
console.info("No extension suggestions — all detected extensions are already enabled.");
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
console.info("Suggested extensions:\n");
|
|
254
|
+
for (const s of suggestions) {
|
|
255
|
+
console.info(` ${s.name} — ${s.reason}`);
|
|
256
|
+
}
|
|
257
|
+
console.info(`\nEnable with: extensions enable ${suggestions.map((s) => s.name).join(" ")}`);
|
|
258
|
+
}
|
|
259
|
+
// --- Helpers ---
|
|
260
|
+
function runHook(projectRoot, name, hook) {
|
|
261
|
+
const extPath = join(extensionsDir(projectRoot), `${name}.json`);
|
|
262
|
+
const manifest = loadExtension(extPath);
|
|
263
|
+
if (!manifest?.hooks)
|
|
264
|
+
return;
|
|
265
|
+
const command = manifest.hooks[hook];
|
|
266
|
+
if (!command)
|
|
267
|
+
return;
|
|
268
|
+
try {
|
|
269
|
+
execSync(command, { cwd: projectRoot, stdio: "inherit", timeout: 30000 });
|
|
270
|
+
}
|
|
271
|
+
catch {
|
|
272
|
+
console.info(` ${name}: ${hook} hook failed`);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
function installSkill(projectRoot, name) {
|
|
276
|
+
const extPath = join(extensionsDir(projectRoot), `${name}.json`);
|
|
277
|
+
const manifest = loadExtension(extPath);
|
|
278
|
+
if (!manifest?.provides.skill)
|
|
279
|
+
return;
|
|
280
|
+
// Look for skill.md in built-in extensions
|
|
281
|
+
const builtinSkill = join(builtinDir, name, "skill.md");
|
|
282
|
+
if (existsSync(builtinSkill)) {
|
|
283
|
+
const targetDir = join(projectRoot, ".claude/skills", name);
|
|
284
|
+
mkdirSync(targetDir, { recursive: true });
|
|
285
|
+
cpSync(builtinSkill, join(targetDir, "SKILL.md"));
|
|
286
|
+
console.info(` ${name}: skill installed to .claude/skills/${name}/SKILL.md`);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
@@ -17,34 +17,6 @@ function run(cmd, options) {
|
|
|
17
17
|
return "";
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
|
-
function ensureFalkorDB() {
|
|
21
|
-
// Check if FalkorDB container exists and is running
|
|
22
|
-
const status = run('docker ps --filter name=falkordb --format "{{.Status}}"');
|
|
23
|
-
if (status) {
|
|
24
|
-
console.info(" ok: FalkorDB container running");
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
// Check if container exists but is stopped
|
|
28
|
-
const stopped = run('docker ps -a --filter name=falkordb --format "{{.Status}}"');
|
|
29
|
-
if (stopped) {
|
|
30
|
-
console.info(" start: FalkorDB container (was stopped)");
|
|
31
|
-
run("docker start falkordb");
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
// Create and start the container
|
|
35
|
-
console.info(" create: FalkorDB container (global, persistent)");
|
|
36
|
-
run("docker run -d --name falkordb --restart unless-stopped -p 6379:6379 -v falkordb-global:/data falkordb/falkordb:latest");
|
|
37
|
-
}
|
|
38
|
-
function checkCGC() {
|
|
39
|
-
const cgcPaths = [join(process.env.HOME ?? "", ".local/bin/cgc"), "/usr/local/bin/cgc"];
|
|
40
|
-
const found = cgcPaths.find((p) => existsSync(p));
|
|
41
|
-
if (found) {
|
|
42
|
-
console.info(` ok: CGC found at ${found}`);
|
|
43
|
-
return true;
|
|
44
|
-
}
|
|
45
|
-
console.info(" missing: CGC — install via: pipx install codegraphcontext");
|
|
46
|
-
return false;
|
|
47
|
-
}
|
|
48
20
|
function createCgcIgnore(projectRoot) {
|
|
49
21
|
const ignorePath = join(projectRoot, ".cgcignore");
|
|
50
22
|
if (existsSync(ignorePath)) {
|
|
@@ -70,56 +42,8 @@ function createCgcIgnore(projectRoot) {
|
|
|
70
42
|
].join("\n"));
|
|
71
43
|
console.info(" create: .cgcignore");
|
|
72
44
|
}
|
|
73
|
-
const DOMAIN_DETECTION_MAP = [
|
|
74
|
-
{ signal: "dependency", match: "next", skill: "nextjs" },
|
|
75
|
-
{ signal: "dependency", match: "tailwindcss", skill: "tailwind" },
|
|
76
|
-
{ signal: "dependency", match: "react", skill: "react" },
|
|
77
|
-
{ signal: "devDependency", match: "typescript", skill: "typescript" },
|
|
78
|
-
{ signal: "devDependency", match: "vitest", skill: "testing" },
|
|
79
|
-
{ signal: "devDependency", match: "jest", skill: "testing" },
|
|
80
|
-
{ signal: "dependency", match: "vitepress", skill: "vitepress" },
|
|
81
|
-
];
|
|
82
|
-
const FILE_PATTERN_DETECTIONS = [
|
|
83
|
-
{ pattern: "*.sol", skill: "solidity" },
|
|
84
|
-
{ pattern: "Dockerfile*", skill: "docker" },
|
|
85
|
-
];
|
|
86
|
-
function detectDomainSkills(projectRoot) {
|
|
87
|
-
const detections = [];
|
|
88
|
-
const seen = new Set();
|
|
89
|
-
// Check package.json dependencies
|
|
90
|
-
const pkgPath = join(projectRoot, "package.json");
|
|
91
|
-
if (existsSync(pkgPath)) {
|
|
92
|
-
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
93
|
-
const deps = pkg.dependencies ?? {};
|
|
94
|
-
const devDeps = pkg.devDependencies ?? {};
|
|
95
|
-
for (const rule of DOMAIN_DETECTION_MAP) {
|
|
96
|
-
if (seen.has(rule.skill))
|
|
97
|
-
continue;
|
|
98
|
-
const source = rule.signal === "dependency" ? deps : devDeps;
|
|
99
|
-
if (source[rule.match]) {
|
|
100
|
-
detections.push({
|
|
101
|
-
skill: rule.skill,
|
|
102
|
-
signal: rule.signal,
|
|
103
|
-
match: rule.match,
|
|
104
|
-
});
|
|
105
|
-
seen.add(rule.skill);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
// Check file patterns
|
|
110
|
-
for (const rule of FILE_PATTERN_DETECTIONS) {
|
|
111
|
-
if (seen.has(rule.skill))
|
|
112
|
-
continue;
|
|
113
|
-
const matches = globSync(rule.pattern, { cwd: projectRoot, maxDepth: 3 });
|
|
114
|
-
if (matches.length > 0) {
|
|
115
|
-
detections.push({ skill: rule.skill, signal: "file-pattern", match: rule.pattern });
|
|
116
|
-
seen.add(rule.skill);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
return detections;
|
|
120
|
-
}
|
|
121
45
|
export async function init(projectRoot, options = {}) {
|
|
122
|
-
const { force = false,
|
|
46
|
+
const { force = false, noIndex = false } = options;
|
|
123
47
|
const projectName = basename(projectRoot);
|
|
124
48
|
console.info(`Initializing InDusk dev system...${force ? " (--force)" : ""}\n`);
|
|
125
49
|
// 1. Copy skills
|
|
@@ -139,49 +63,7 @@ export async function init(projectRoot, options = {}) {
|
|
|
139
63
|
cpSync(join(skillsSource, file), targetFile);
|
|
140
64
|
console.info(` ${existsSync(targetFile) ? "overwrite" : "create"}: .claude/skills/${skillName}/SKILL.md`);
|
|
141
65
|
}
|
|
142
|
-
// 2.
|
|
143
|
-
if (!noDomainSkills) {
|
|
144
|
-
console.info("\n[Domain Skills]");
|
|
145
|
-
const domainSource = join(packageRoot, "skills/domain");
|
|
146
|
-
let skillsToInstall;
|
|
147
|
-
if (skills) {
|
|
148
|
-
skillsToInstall = skills.split(",").map((s) => s.trim());
|
|
149
|
-
console.info(` manual: installing ${skillsToInstall.join(", ")}`);
|
|
150
|
-
}
|
|
151
|
-
else {
|
|
152
|
-
const detections = detectDomainSkills(projectRoot);
|
|
153
|
-
skillsToInstall = detections.map((d) => d.skill);
|
|
154
|
-
if (detections.length > 0) {
|
|
155
|
-
for (const d of detections) {
|
|
156
|
-
console.info(` detected: ${d.skill} (${d.signal}: ${d.match})`);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
else {
|
|
160
|
-
console.info(" none detected");
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
for (const skillName of skillsToInstall) {
|
|
164
|
-
const sourceFile = join(domainSource, `${skillName}.md`);
|
|
165
|
-
if (!existsSync(sourceFile)) {
|
|
166
|
-
console.info(` skip: ${skillName} (not found in registry)`);
|
|
167
|
-
continue;
|
|
168
|
-
}
|
|
169
|
-
const targetDir = join(skillsTarget, skillName);
|
|
170
|
-
const targetFile = join(targetDir, "SKILL.md");
|
|
171
|
-
if (existsSync(targetFile) && !force) {
|
|
172
|
-
console.info(` skip: .claude/skills/${skillName}/SKILL.md (already exists)`);
|
|
173
|
-
continue;
|
|
174
|
-
}
|
|
175
|
-
mkdirSync(targetDir, { recursive: true });
|
|
176
|
-
cpSync(sourceFile, targetFile);
|
|
177
|
-
console.info(` install: .claude/skills/${skillName}/SKILL.md`);
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
else {
|
|
181
|
-
console.info("\n[Domain Skills]");
|
|
182
|
-
console.info(" skipped (--no-domain-skills)");
|
|
183
|
-
}
|
|
184
|
-
// 3. Copy community lessons
|
|
66
|
+
// 2. Copy community lessons
|
|
185
67
|
console.info("\n[Lessons]");
|
|
186
68
|
const lessonsSource = join(packageRoot, "lessons/community");
|
|
187
69
|
const lessonsTarget = join(projectRoot, ".claude/lessons");
|
|
@@ -232,8 +114,7 @@ export async function init(projectRoot, options = {}) {
|
|
|
232
114
|
args: ["mcp", "start"],
|
|
233
115
|
env: {
|
|
234
116
|
DATABASE_TYPE: "falkordb-remote",
|
|
235
|
-
FALKORDB_HOST: "
|
|
236
|
-
FALKORDB_PORT: "6379",
|
|
117
|
+
FALKORDB_HOST: "falkordb.orb.local",
|
|
237
118
|
FALKORDB_GRAPH_NAME: projectName,
|
|
238
119
|
},
|
|
239
120
|
};
|
|
@@ -367,47 +248,54 @@ export async function init(projectRoot, options = {}) {
|
|
|
367
248
|
}
|
|
368
249
|
// 8. Create .cgcignore (always overwrite — package-owned)
|
|
369
250
|
createCgcIgnore(projectRoot);
|
|
370
|
-
//
|
|
371
|
-
console.info("\n[
|
|
372
|
-
const
|
|
373
|
-
|
|
374
|
-
|
|
251
|
+
// 9. Run on_init hooks from enabled extensions
|
|
252
|
+
console.info("\n[Extension Hooks]");
|
|
253
|
+
const { getEnabledExtensions } = await import("../../lib/extension-loader.js");
|
|
254
|
+
const enabledExts = getEnabledExtensions(projectRoot);
|
|
255
|
+
for (const ext of enabledExts) {
|
|
256
|
+
if (ext.manifest.hooks?.on_init) {
|
|
257
|
+
console.info(` ${ext.manifest.name}: running on_init...`);
|
|
258
|
+
const result = run(ext.manifest.hooks.on_init);
|
|
259
|
+
if (result) {
|
|
260
|
+
console.info(` ${ext.manifest.name}: ${result.slice(0, 100)}`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
375
263
|
}
|
|
376
|
-
|
|
377
|
-
console.info("
|
|
264
|
+
if (enabledExts.length === 0) {
|
|
265
|
+
console.info(" no extensions enabled — run 'extensions enable' to add tool integrations");
|
|
378
266
|
}
|
|
379
|
-
|
|
380
|
-
|
|
267
|
+
// 10. Auto-index the codebase into the graph (if CGC extension is enabled)
|
|
268
|
+
const cgcEnabled = enabledExts.some((e) => e.manifest.name === "cgc");
|
|
381
269
|
if (noIndex) {
|
|
382
270
|
console.info("\n[Code Graph]");
|
|
383
271
|
console.info(" skipped (--no-index)");
|
|
384
272
|
}
|
|
385
|
-
else if (
|
|
273
|
+
else if (cgcEnabled) {
|
|
386
274
|
console.info("\n[Code Graph]");
|
|
387
275
|
console.info(" indexing: scanning codebase...");
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
276
|
+
try {
|
|
277
|
+
const { indexProject } = await import("../../tools/graph-tools.js");
|
|
278
|
+
const result = indexProject(projectRoot);
|
|
279
|
+
if (result.success) {
|
|
280
|
+
console.info(` done: ${result.output}`);
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
console.info(` error: ${result.output}`);
|
|
284
|
+
}
|
|
392
285
|
}
|
|
393
|
-
|
|
394
|
-
console.info(
|
|
286
|
+
catch {
|
|
287
|
+
console.info(" skipped (CGC not available)");
|
|
395
288
|
}
|
|
396
289
|
}
|
|
290
|
+
// 11. Suggest extensions
|
|
291
|
+
console.info("\n[Extensions]");
|
|
292
|
+
const { extensionsSuggest } = await import("./extensions.js");
|
|
293
|
+
await extensionsSuggest(projectRoot);
|
|
397
294
|
// Summary
|
|
398
295
|
console.info("\nDone!");
|
|
399
|
-
if (!cgcInstalled || !dockerAvailable) {
|
|
400
|
-
console.info("\nManual steps needed:");
|
|
401
|
-
if (!dockerAvailable) {
|
|
402
|
-
console.info(" 1. Install Docker or OrbStack");
|
|
403
|
-
console.info(" 2. Re-run init to set up FalkorDB");
|
|
404
|
-
}
|
|
405
|
-
if (!cgcInstalled) {
|
|
406
|
-
console.info(" - Install CGC: pipx install codegraphcontext");
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
296
|
console.info("\nNext steps:");
|
|
410
297
|
console.info(" 1. Edit CLAUDE.md with your project details");
|
|
411
|
-
console.info(" 2.
|
|
412
|
-
console.info(" 3. Start
|
|
298
|
+
console.info(" 2. Enable extensions: extensions enable falkordb cgc typescript");
|
|
299
|
+
console.info(" 3. Start a Claude Code session — MCP tools will be available");
|
|
300
|
+
console.info(" 4. Start planning: /plan your-first-feature");
|
|
413
301
|
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export interface DetectRule {
|
|
2
|
+
file?: string;
|
|
3
|
+
file_pattern?: string;
|
|
4
|
+
dependency?: string;
|
|
5
|
+
devDependency?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface HealthCheck {
|
|
8
|
+
name: string;
|
|
9
|
+
command: string;
|
|
10
|
+
}
|
|
11
|
+
export interface VerificationEntry {
|
|
12
|
+
name: string;
|
|
13
|
+
command: string;
|
|
14
|
+
detect?: DetectRule;
|
|
15
|
+
}
|
|
16
|
+
export interface ExtensionManifest {
|
|
17
|
+
name: string;
|
|
18
|
+
description: string;
|
|
19
|
+
version?: string;
|
|
20
|
+
provides: {
|
|
21
|
+
skill?: boolean;
|
|
22
|
+
networking?: {
|
|
23
|
+
env_file?: string;
|
|
24
|
+
command?: string;
|
|
25
|
+
description?: string;
|
|
26
|
+
};
|
|
27
|
+
services?: {
|
|
28
|
+
command?: string;
|
|
29
|
+
description?: string;
|
|
30
|
+
};
|
|
31
|
+
health_checks?: HealthCheck[];
|
|
32
|
+
verification?: VerificationEntry[];
|
|
33
|
+
env_vars?: Record<string, string> | {
|
|
34
|
+
source: string;
|
|
35
|
+
files?: string[];
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
hooks?: {
|
|
39
|
+
on_init?: string;
|
|
40
|
+
on_update?: string;
|
|
41
|
+
on_health_check?: string;
|
|
42
|
+
on_onboard?: string;
|
|
43
|
+
};
|
|
44
|
+
detect?: DetectRule;
|
|
45
|
+
}
|
|
46
|
+
export interface LoadedExtension {
|
|
47
|
+
manifest: ExtensionManifest;
|
|
48
|
+
path: string;
|
|
49
|
+
enabled: boolean;
|
|
50
|
+
}
|
|
51
|
+
export declare function extensionsDir(projectRoot: string): string;
|
|
52
|
+
export declare function disabledDir(projectRoot: string): string;
|
|
53
|
+
export declare function ensureExtensionsDirs(projectRoot: string): void;
|
|
54
|
+
export declare function loadExtension(manifestPath: string): ExtensionManifest | null;
|
|
55
|
+
export declare function loadExtensions(projectRoot: string): LoadedExtension[];
|
|
56
|
+
export declare function getEnabledExtensions(projectRoot: string): LoadedExtension[];
|
|
57
|
+
export declare function enableExtension(projectRoot: string, name: string): boolean;
|
|
58
|
+
export declare function disableExtension(projectRoot: string, name: string): boolean;
|
|
59
|
+
export declare function isEnabled(projectRoot: string, name: string): boolean;
|
|
60
|
+
export declare function getExtension(projectRoot: string, name: string): LoadedExtension | null;
|