@nicnocquee/dataqueue 1.33.0 → 1.34.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/ai/build-docs-content.ts +96 -0
- package/ai/build-llms-full.ts +42 -0
- package/ai/docs-content.json +278 -0
- package/ai/rules/advanced.md +94 -0
- package/ai/rules/basic.md +90 -0
- package/ai/rules/react-dashboard.md +83 -0
- package/ai/skills/dataqueue-advanced/SKILL.md +211 -0
- package/ai/skills/dataqueue-core/SKILL.md +131 -0
- package/ai/skills/dataqueue-react/SKILL.md +189 -0
- package/dist/cli.cjs +577 -32
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.d.cts +52 -2
- package/dist/cli.d.ts +52 -2
- package/dist/cli.js +575 -32
- package/dist/cli.js.map +1 -1
- package/dist/mcp-server.cjs +186 -0
- package/dist/mcp-server.cjs.map +1 -0
- package/dist/mcp-server.d.cts +32 -0
- package/dist/mcp-server.d.ts +32 -0
- package/dist/mcp-server.js +175 -0
- package/dist/mcp-server.js.map +1 -0
- package/package.json +10 -4
- package/src/cli.test.ts +65 -0
- package/src/cli.ts +56 -19
- package/src/install-mcp-command.test.ts +216 -0
- package/src/install-mcp-command.ts +185 -0
- package/src/install-rules-command.test.ts +218 -0
- package/src/install-rules-command.ts +233 -0
- package/src/install-skills-command.test.ts +176 -0
- package/src/install-skills-command.ts +124 -0
- package/src/mcp-server.test.ts +162 -0
- package/src/mcp-server.ts +231 -0
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { spawnSync } from 'child_process';
|
|
2
|
-
import
|
|
2
|
+
import path3 from 'path';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
|
-
import { readFileSync, existsSync, chmodSync, writeFileSync, mkdirSync } from 'fs';
|
|
4
|
+
import fs, { readFileSync, existsSync, chmodSync, writeFileSync, mkdirSync } from 'fs';
|
|
5
|
+
import readline from 'readline';
|
|
6
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
7
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
8
|
+
import { z } from 'zod';
|
|
5
9
|
|
|
6
10
|
// src/cli.ts
|
|
7
11
|
var DEPENDENCIES_TO_ADD = [
|
|
@@ -366,7 +370,7 @@ function detectNextJsAndRouter({
|
|
|
366
370
|
existsSyncImpl,
|
|
367
371
|
readFileSyncImpl
|
|
368
372
|
}) {
|
|
369
|
-
const packageJsonPath =
|
|
373
|
+
const packageJsonPath = path3.join(cwd, "package.json");
|
|
370
374
|
if (!existsSyncImpl(packageJsonPath)) {
|
|
371
375
|
throw new Error("package.json not found in current directory.");
|
|
372
376
|
}
|
|
@@ -379,10 +383,10 @@ function detectNextJsAndRouter({
|
|
|
379
383
|
"Not a Next.js project. Could not find 'next' in package.json dependencies."
|
|
380
384
|
);
|
|
381
385
|
}
|
|
382
|
-
const srcDir =
|
|
386
|
+
const srcDir = path3.join(cwd, "src");
|
|
383
387
|
const srcRoot = existsSyncImpl(srcDir) ? "src" : ".";
|
|
384
|
-
const appDir =
|
|
385
|
-
const pagesDir =
|
|
388
|
+
const appDir = path3.join(cwd, srcRoot, "app");
|
|
389
|
+
const pagesDir = path3.join(cwd, srcRoot, "pages");
|
|
386
390
|
const hasAppDir = existsSyncImpl(appDir);
|
|
387
391
|
const hasPagesDir = existsSyncImpl(pagesDir);
|
|
388
392
|
if (!hasAppDir && !hasPagesDir) {
|
|
@@ -443,7 +447,7 @@ function createScaffoldFiles({
|
|
|
443
447
|
writeFileSyncImpl,
|
|
444
448
|
chmodSyncImpl
|
|
445
449
|
}) {
|
|
446
|
-
const appRoutePath =
|
|
450
|
+
const appRoutePath = path3.join(
|
|
447
451
|
details.cwd,
|
|
448
452
|
details.srcRoot,
|
|
449
453
|
"app",
|
|
@@ -453,7 +457,7 @@ function createScaffoldFiles({
|
|
|
453
457
|
"[[...task]]",
|
|
454
458
|
"route.ts"
|
|
455
459
|
);
|
|
456
|
-
const pagesRoutePath =
|
|
460
|
+
const pagesRoutePath = path3.join(
|
|
457
461
|
details.cwd,
|
|
458
462
|
details.srcRoot,
|
|
459
463
|
"pages",
|
|
@@ -462,14 +466,14 @@ function createScaffoldFiles({
|
|
|
462
466
|
"manage",
|
|
463
467
|
"[[...task]].ts"
|
|
464
468
|
);
|
|
465
|
-
const queuePath =
|
|
469
|
+
const queuePath = path3.join(
|
|
466
470
|
details.cwd,
|
|
467
471
|
details.srcRoot,
|
|
468
472
|
"lib",
|
|
469
473
|
"dataqueue",
|
|
470
474
|
"queue.ts"
|
|
471
475
|
);
|
|
472
|
-
const cronPath =
|
|
476
|
+
const cronPath = path3.join(details.cwd, "cron.sh");
|
|
473
477
|
if (details.router === "app") {
|
|
474
478
|
createFileIfMissing({
|
|
475
479
|
absolutePath: appRoutePath,
|
|
@@ -532,7 +536,7 @@ function createFileIfMissing({
|
|
|
532
536
|
log(` [skipped] ${logPath} (already exists)`);
|
|
533
537
|
return;
|
|
534
538
|
}
|
|
535
|
-
mkdirSyncImpl(
|
|
539
|
+
mkdirSyncImpl(path3.dirname(absolutePath), { recursive: true });
|
|
536
540
|
writeFileSyncImpl(absolutePath, content);
|
|
537
541
|
log(` [created] ${logPath}`);
|
|
538
542
|
}
|
|
@@ -568,21 +572,541 @@ function ensureStringMapSection(packageJson, sectionName) {
|
|
|
568
572
|
return packageJson[sectionName];
|
|
569
573
|
}
|
|
570
574
|
function toRelativePath(cwd, absolutePath) {
|
|
571
|
-
const relative =
|
|
575
|
+
const relative = path3.relative(cwd, absolutePath);
|
|
572
576
|
return relative || ".";
|
|
573
577
|
}
|
|
578
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
579
|
+
var __dirname = path3.dirname(__filename);
|
|
580
|
+
var SKILL_DIRS = ["dataqueue-core", "dataqueue-advanced", "dataqueue-react"];
|
|
581
|
+
function detectAiTools(cwd, existsSync2 = fs.existsSync) {
|
|
582
|
+
const tools = [];
|
|
583
|
+
const checks = [
|
|
584
|
+
{
|
|
585
|
+
name: "Cursor",
|
|
586
|
+
indicator: ".cursor",
|
|
587
|
+
targetDir: ".cursor/skills"
|
|
588
|
+
},
|
|
589
|
+
{
|
|
590
|
+
name: "Claude Code",
|
|
591
|
+
indicator: ".claude",
|
|
592
|
+
targetDir: ".claude/skills"
|
|
593
|
+
},
|
|
594
|
+
{
|
|
595
|
+
name: "GitHub Copilot",
|
|
596
|
+
indicator: ".github",
|
|
597
|
+
targetDir: ".github/skills"
|
|
598
|
+
}
|
|
599
|
+
];
|
|
600
|
+
for (const check of checks) {
|
|
601
|
+
if (existsSync2(path3.join(cwd, check.indicator))) {
|
|
602
|
+
tools.push({ name: check.name, targetDir: check.targetDir });
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
return tools;
|
|
606
|
+
}
|
|
607
|
+
function runInstallSkills({
|
|
608
|
+
log = console.log,
|
|
609
|
+
error = console.error,
|
|
610
|
+
exit = (code) => process.exit(code),
|
|
611
|
+
cwd = process.cwd(),
|
|
612
|
+
existsSync: existsSync2 = fs.existsSync,
|
|
613
|
+
mkdirSync: mkdirSync2 = fs.mkdirSync,
|
|
614
|
+
copyFileSync = fs.copyFileSync,
|
|
615
|
+
readdirSync = fs.readdirSync,
|
|
616
|
+
skillsSourceDir = path3.join(__dirname, "../ai/skills")
|
|
617
|
+
} = {}) {
|
|
618
|
+
const tools = detectAiTools(cwd, existsSync2);
|
|
619
|
+
if (tools.length === 0) {
|
|
620
|
+
log("No AI tool directories detected (.cursor/, .claude/, .github/).");
|
|
621
|
+
log("Creating .cursor/skills/ as the default target.");
|
|
622
|
+
tools.push({ name: "Cursor", targetDir: ".cursor/skills" });
|
|
623
|
+
}
|
|
624
|
+
let installed = 0;
|
|
625
|
+
for (const tool of tools) {
|
|
626
|
+
log(`
|
|
627
|
+
Installing skills for ${tool.name}...`);
|
|
628
|
+
for (const skillDir of SKILL_DIRS) {
|
|
629
|
+
const srcDir = path3.join(skillsSourceDir, skillDir);
|
|
630
|
+
const destDir = path3.join(cwd, tool.targetDir, skillDir);
|
|
631
|
+
try {
|
|
632
|
+
mkdirSync2(destDir, { recursive: true });
|
|
633
|
+
const files = readdirSync(srcDir);
|
|
634
|
+
for (const file of files) {
|
|
635
|
+
copyFileSync(path3.join(srcDir, file), path3.join(destDir, file));
|
|
636
|
+
}
|
|
637
|
+
log(` \u2713 ${skillDir}`);
|
|
638
|
+
installed++;
|
|
639
|
+
} catch (err) {
|
|
640
|
+
error(` \u2717 Failed to install ${skillDir}:`, err);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
if (installed > 0) {
|
|
645
|
+
log(
|
|
646
|
+
`
|
|
647
|
+
Done! Installed ${installed} skill(s) for ${tools.map((t) => t.name).join(", ")}.`
|
|
648
|
+
);
|
|
649
|
+
} else {
|
|
650
|
+
error("No skills were installed.");
|
|
651
|
+
exit(1);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
var __filename2 = fileURLToPath(import.meta.url);
|
|
655
|
+
var __dirname2 = path3.dirname(__filename2);
|
|
656
|
+
var RULE_FILES = ["basic.md", "advanced.md", "react-dashboard.md"];
|
|
657
|
+
var MARKER_START = "<!-- DATAQUEUE RULES START -->";
|
|
658
|
+
var MARKER_END = "<!-- DATAQUEUE RULES END -->";
|
|
659
|
+
function upsertMarkedSection(filePath, content, deps) {
|
|
660
|
+
const block = `${MARKER_START}
|
|
661
|
+
${content}
|
|
662
|
+
${MARKER_END}`;
|
|
663
|
+
if (!deps.existsSync(filePath)) {
|
|
664
|
+
deps.writeFileSync(filePath, block + "\n");
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
const existing = deps.readFileSync(filePath, "utf-8");
|
|
668
|
+
const startIdx = existing.indexOf(MARKER_START);
|
|
669
|
+
const endIdx = existing.indexOf(MARKER_END);
|
|
670
|
+
if (startIdx !== -1 && endIdx !== -1) {
|
|
671
|
+
const before = existing.slice(0, startIdx);
|
|
672
|
+
const after = existing.slice(endIdx + MARKER_END.length);
|
|
673
|
+
deps.writeFileSync(filePath, before + block + after);
|
|
674
|
+
} else {
|
|
675
|
+
deps.writeFileSync(filePath, existing.trimEnd() + "\n\n" + block + "\n");
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
function getAllRulesContent(rulesSourceDir, readFileSync2) {
|
|
679
|
+
return RULE_FILES.map(
|
|
680
|
+
(f) => readFileSync2(path3.join(rulesSourceDir, f), "utf-8")
|
|
681
|
+
).join("\n\n");
|
|
682
|
+
}
|
|
683
|
+
var CLIENTS = {
|
|
684
|
+
"1": {
|
|
685
|
+
label: "Cursor",
|
|
686
|
+
install: (deps) => {
|
|
687
|
+
const rulesDir = path3.join(deps.cwd, ".cursor", "rules");
|
|
688
|
+
deps.mkdirSync(rulesDir, { recursive: true });
|
|
689
|
+
for (const file of RULE_FILES) {
|
|
690
|
+
const src = deps.readFileSync(
|
|
691
|
+
path3.join(deps.rulesSourceDir, file),
|
|
692
|
+
"utf-8"
|
|
693
|
+
);
|
|
694
|
+
const destName = `dataqueue-${file.replace(/\.md$/, ".mdc")}`;
|
|
695
|
+
deps.writeFileSync(path3.join(rulesDir, destName), src);
|
|
696
|
+
deps.log(` \u2713 .cursor/rules/${destName}`);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
},
|
|
700
|
+
"2": {
|
|
701
|
+
label: "Claude Code",
|
|
702
|
+
install: (deps) => {
|
|
703
|
+
const content = getAllRulesContent(
|
|
704
|
+
deps.rulesSourceDir,
|
|
705
|
+
deps.readFileSync
|
|
706
|
+
);
|
|
707
|
+
const filePath = path3.join(deps.cwd, "CLAUDE.md");
|
|
708
|
+
upsertMarkedSection(filePath, content, deps);
|
|
709
|
+
deps.log(` \u2713 CLAUDE.md`);
|
|
710
|
+
}
|
|
711
|
+
},
|
|
712
|
+
"3": {
|
|
713
|
+
label: "AGENTS.md (Codex, Jules, OpenCode)",
|
|
714
|
+
install: (deps) => {
|
|
715
|
+
const content = getAllRulesContent(
|
|
716
|
+
deps.rulesSourceDir,
|
|
717
|
+
deps.readFileSync
|
|
718
|
+
);
|
|
719
|
+
const filePath = path3.join(deps.cwd, "AGENTS.md");
|
|
720
|
+
upsertMarkedSection(filePath, content, deps);
|
|
721
|
+
deps.log(` \u2713 AGENTS.md`);
|
|
722
|
+
}
|
|
723
|
+
},
|
|
724
|
+
"4": {
|
|
725
|
+
label: "GitHub Copilot",
|
|
726
|
+
install: (deps) => {
|
|
727
|
+
const content = getAllRulesContent(
|
|
728
|
+
deps.rulesSourceDir,
|
|
729
|
+
deps.readFileSync
|
|
730
|
+
);
|
|
731
|
+
deps.mkdirSync(path3.join(deps.cwd, ".github"), { recursive: true });
|
|
732
|
+
const filePath = path3.join(
|
|
733
|
+
deps.cwd,
|
|
734
|
+
".github",
|
|
735
|
+
"copilot-instructions.md"
|
|
736
|
+
);
|
|
737
|
+
upsertMarkedSection(filePath, content, deps);
|
|
738
|
+
deps.log(` \u2713 .github/copilot-instructions.md`);
|
|
739
|
+
}
|
|
740
|
+
},
|
|
741
|
+
"5": {
|
|
742
|
+
label: "Windsurf",
|
|
743
|
+
install: (deps) => {
|
|
744
|
+
const content = getAllRulesContent(
|
|
745
|
+
deps.rulesSourceDir,
|
|
746
|
+
deps.readFileSync
|
|
747
|
+
);
|
|
748
|
+
const filePath = path3.join(deps.cwd, "CONVENTIONS.md");
|
|
749
|
+
upsertMarkedSection(filePath, content, deps);
|
|
750
|
+
deps.log(` \u2713 CONVENTIONS.md`);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
};
|
|
754
|
+
async function runInstallRules({
|
|
755
|
+
log = console.log,
|
|
756
|
+
error = console.error,
|
|
757
|
+
exit = (code) => process.exit(code),
|
|
758
|
+
cwd = process.cwd(),
|
|
759
|
+
readFileSync: readFileSync2 = fs.readFileSync,
|
|
760
|
+
writeFileSync: writeFileSync2 = fs.writeFileSync,
|
|
761
|
+
appendFileSync = fs.appendFileSync,
|
|
762
|
+
mkdirSync: mkdirSync2 = fs.mkdirSync,
|
|
763
|
+
existsSync: existsSync2 = fs.existsSync,
|
|
764
|
+
rulesSourceDir = path3.join(__dirname2, "../ai/rules"),
|
|
765
|
+
selectedClient
|
|
766
|
+
} = {}) {
|
|
767
|
+
log("DataQueue Agent Rules Installer\n");
|
|
768
|
+
log("Select your AI client:\n");
|
|
769
|
+
for (const [key, client2] of Object.entries(CLIENTS)) {
|
|
770
|
+
log(` ${key}) ${client2.label}`);
|
|
771
|
+
}
|
|
772
|
+
log("");
|
|
773
|
+
let choice = selectedClient;
|
|
774
|
+
if (!choice) {
|
|
775
|
+
const rl = readline.createInterface({
|
|
776
|
+
input: process.stdin,
|
|
777
|
+
output: process.stdout
|
|
778
|
+
});
|
|
779
|
+
choice = await new Promise((resolve) => {
|
|
780
|
+
rl.question("Enter choice (1-5): ", (answer) => {
|
|
781
|
+
rl.close();
|
|
782
|
+
resolve(answer.trim());
|
|
783
|
+
});
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
const client = CLIENTS[choice];
|
|
787
|
+
if (!client) {
|
|
788
|
+
error(`Invalid choice: "${choice}". Expected 1-5.`);
|
|
789
|
+
exit(1);
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
792
|
+
log(`
|
|
793
|
+
Installing rules for ${client.label}...`);
|
|
794
|
+
try {
|
|
795
|
+
client.install({
|
|
796
|
+
cwd,
|
|
797
|
+
readFileSync: readFileSync2,
|
|
798
|
+
writeFileSync: writeFileSync2,
|
|
799
|
+
appendFileSync,
|
|
800
|
+
mkdirSync: mkdirSync2,
|
|
801
|
+
existsSync: existsSync2,
|
|
802
|
+
log,
|
|
803
|
+
rulesSourceDir
|
|
804
|
+
});
|
|
805
|
+
log("\nDone!");
|
|
806
|
+
} catch (err) {
|
|
807
|
+
error("Failed to install rules:", err);
|
|
808
|
+
exit(1);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
function upsertMcpConfig(filePath, serverKey, serverConfig, deps) {
|
|
812
|
+
let config = {};
|
|
813
|
+
if (deps.existsSync(filePath)) {
|
|
814
|
+
try {
|
|
815
|
+
config = JSON.parse(deps.readFileSync(filePath, "utf-8"));
|
|
816
|
+
} catch {
|
|
817
|
+
config = {};
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
if (!config.mcpServers || typeof config.mcpServers !== "object") {
|
|
821
|
+
config.mcpServers = {};
|
|
822
|
+
}
|
|
823
|
+
config.mcpServers[serverKey] = serverConfig;
|
|
824
|
+
deps.writeFileSync(filePath, JSON.stringify(config, null, 2) + "\n");
|
|
825
|
+
}
|
|
826
|
+
var MCP_SERVER_CONFIG = {
|
|
827
|
+
command: "npx",
|
|
828
|
+
args: ["dataqueue-cli", "mcp"]
|
|
829
|
+
};
|
|
830
|
+
var MCP_CLIENTS = {
|
|
831
|
+
"1": {
|
|
832
|
+
label: "Cursor",
|
|
833
|
+
install: (deps) => {
|
|
834
|
+
const configDir = path3.join(deps.cwd, ".cursor");
|
|
835
|
+
deps.mkdirSync(configDir, { recursive: true });
|
|
836
|
+
const configFile = path3.join(configDir, "mcp.json");
|
|
837
|
+
upsertMcpConfig(configFile, "dataqueue", MCP_SERVER_CONFIG, deps);
|
|
838
|
+
deps.log(` \u2713 .cursor/mcp.json`);
|
|
839
|
+
}
|
|
840
|
+
},
|
|
841
|
+
"2": {
|
|
842
|
+
label: "Claude Code",
|
|
843
|
+
install: (deps) => {
|
|
844
|
+
const configFile = path3.join(deps.cwd, ".mcp.json");
|
|
845
|
+
upsertMcpConfig(configFile, "dataqueue", MCP_SERVER_CONFIG, deps);
|
|
846
|
+
deps.log(` \u2713 .mcp.json`);
|
|
847
|
+
}
|
|
848
|
+
},
|
|
849
|
+
"3": {
|
|
850
|
+
label: "VS Code (Copilot)",
|
|
851
|
+
install: (deps) => {
|
|
852
|
+
const configDir = path3.join(deps.cwd, ".vscode");
|
|
853
|
+
deps.mkdirSync(configDir, { recursive: true });
|
|
854
|
+
const configFile = path3.join(configDir, "mcp.json");
|
|
855
|
+
upsertMcpConfig(configFile, "dataqueue", MCP_SERVER_CONFIG, deps);
|
|
856
|
+
deps.log(` \u2713 .vscode/mcp.json`);
|
|
857
|
+
}
|
|
858
|
+
},
|
|
859
|
+
"4": {
|
|
860
|
+
label: "Windsurf",
|
|
861
|
+
install: (deps) => {
|
|
862
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE || "";
|
|
863
|
+
const configFile = path3.join(
|
|
864
|
+
homeDir,
|
|
865
|
+
".codeium",
|
|
866
|
+
"windsurf",
|
|
867
|
+
"mcp_config.json"
|
|
868
|
+
);
|
|
869
|
+
deps.mkdirSync(path3.dirname(configFile), { recursive: true });
|
|
870
|
+
upsertMcpConfig(configFile, "dataqueue", MCP_SERVER_CONFIG, deps);
|
|
871
|
+
deps.log(` \u2713 ~/.codeium/windsurf/mcp_config.json`);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
};
|
|
875
|
+
async function runInstallMcp({
|
|
876
|
+
log = console.log,
|
|
877
|
+
error = console.error,
|
|
878
|
+
exit = (code) => process.exit(code),
|
|
879
|
+
cwd = process.cwd(),
|
|
880
|
+
readFileSync: readFileSync2 = fs.readFileSync,
|
|
881
|
+
writeFileSync: writeFileSync2 = fs.writeFileSync,
|
|
882
|
+
mkdirSync: mkdirSync2 = fs.mkdirSync,
|
|
883
|
+
existsSync: existsSync2 = fs.existsSync,
|
|
884
|
+
selectedClient
|
|
885
|
+
} = {}) {
|
|
886
|
+
log("DataQueue MCP Server Installer\n");
|
|
887
|
+
log("Select your AI client:\n");
|
|
888
|
+
for (const [key, client2] of Object.entries(MCP_CLIENTS)) {
|
|
889
|
+
log(` ${key}) ${client2.label}`);
|
|
890
|
+
}
|
|
891
|
+
log("");
|
|
892
|
+
let choice = selectedClient;
|
|
893
|
+
if (!choice) {
|
|
894
|
+
const rl = readline.createInterface({
|
|
895
|
+
input: process.stdin,
|
|
896
|
+
output: process.stdout
|
|
897
|
+
});
|
|
898
|
+
choice = await new Promise((resolve) => {
|
|
899
|
+
rl.question("Enter choice (1-4): ", (answer) => {
|
|
900
|
+
rl.close();
|
|
901
|
+
resolve(answer.trim());
|
|
902
|
+
});
|
|
903
|
+
});
|
|
904
|
+
}
|
|
905
|
+
const client = MCP_CLIENTS[choice];
|
|
906
|
+
if (!client) {
|
|
907
|
+
error(`Invalid choice: "${choice}". Expected 1-4.`);
|
|
908
|
+
exit(1);
|
|
909
|
+
return;
|
|
910
|
+
}
|
|
911
|
+
log(`
|
|
912
|
+
Installing MCP config for ${client.label}...`);
|
|
913
|
+
try {
|
|
914
|
+
client.install({
|
|
915
|
+
cwd,
|
|
916
|
+
readFileSync: readFileSync2,
|
|
917
|
+
writeFileSync: writeFileSync2,
|
|
918
|
+
mkdirSync: mkdirSync2,
|
|
919
|
+
existsSync: existsSync2,
|
|
920
|
+
log
|
|
921
|
+
});
|
|
922
|
+
log("\nDone! The MCP server will run via: npx dataqueue-cli mcp");
|
|
923
|
+
} catch (err) {
|
|
924
|
+
error("Failed to install MCP config:", err);
|
|
925
|
+
exit(1);
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
var __filename3 = fileURLToPath(import.meta.url);
|
|
929
|
+
var __dirname3 = path3.dirname(__filename3);
|
|
930
|
+
function loadDocsContent(docsPath = path3.join(__dirname3, "../ai/docs-content.json")) {
|
|
931
|
+
const raw = fs.readFileSync(docsPath, "utf-8");
|
|
932
|
+
return JSON.parse(raw);
|
|
933
|
+
}
|
|
934
|
+
function scorePageForQuery(page, queryTerms) {
|
|
935
|
+
const titleLower = page.title.toLowerCase();
|
|
936
|
+
const descLower = page.description.toLowerCase();
|
|
937
|
+
const contentLower = page.content.toLowerCase();
|
|
938
|
+
let score = 0;
|
|
939
|
+
for (const term of queryTerms) {
|
|
940
|
+
if (titleLower.includes(term)) score += 10;
|
|
941
|
+
if (descLower.includes(term)) score += 5;
|
|
942
|
+
const contentMatches = contentLower.split(term).length - 1;
|
|
943
|
+
score += Math.min(contentMatches, 10);
|
|
944
|
+
}
|
|
945
|
+
return score;
|
|
946
|
+
}
|
|
947
|
+
function extractExcerpt(content, queryTerms, maxLength = 500) {
|
|
948
|
+
const lower = content.toLowerCase();
|
|
949
|
+
let earliestIndex = -1;
|
|
950
|
+
for (const term of queryTerms) {
|
|
951
|
+
const idx = lower.indexOf(term);
|
|
952
|
+
if (idx !== -1 && (earliestIndex === -1 || idx < earliestIndex)) {
|
|
953
|
+
earliestIndex = idx;
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
if (earliestIndex === -1) {
|
|
957
|
+
return content.slice(0, maxLength);
|
|
958
|
+
}
|
|
959
|
+
const start = Math.max(0, earliestIndex - 100);
|
|
960
|
+
const end = Math.min(content.length, start + maxLength);
|
|
961
|
+
let excerpt = content.slice(start, end);
|
|
962
|
+
if (start > 0) excerpt = "..." + excerpt;
|
|
963
|
+
if (end < content.length) excerpt = excerpt + "...";
|
|
964
|
+
return excerpt;
|
|
965
|
+
}
|
|
966
|
+
async function startMcpServer(deps = {}) {
|
|
967
|
+
const pages = loadDocsContent(deps.docsPath);
|
|
968
|
+
const server = new McpServer({
|
|
969
|
+
name: "dataqueue-docs",
|
|
970
|
+
version: "1.0.0"
|
|
971
|
+
});
|
|
972
|
+
server.resource("llms-txt", "dataqueue://llms.txt", async () => {
|
|
973
|
+
const llmsPath = path3.join(
|
|
974
|
+
__dirname3,
|
|
975
|
+
"../ai/skills/dataqueue-core/SKILL.md"
|
|
976
|
+
);
|
|
977
|
+
let content;
|
|
978
|
+
try {
|
|
979
|
+
content = fs.readFileSync(llmsPath, "utf-8");
|
|
980
|
+
} catch {
|
|
981
|
+
content = pages.map((p) => `## ${p.title}
|
|
982
|
+
|
|
983
|
+
Slug: ${p.slug}
|
|
984
|
+
|
|
985
|
+
${p.description}`).join("\n\n");
|
|
986
|
+
}
|
|
987
|
+
return { contents: [{ uri: "dataqueue://llms.txt", text: content }] };
|
|
988
|
+
});
|
|
989
|
+
server.tool(
|
|
990
|
+
"list-doc-pages",
|
|
991
|
+
"List all available DataQueue documentation pages with titles and descriptions.",
|
|
992
|
+
{},
|
|
993
|
+
async () => {
|
|
994
|
+
const listing = pages.map((p) => ({
|
|
995
|
+
slug: p.slug,
|
|
996
|
+
title: p.title,
|
|
997
|
+
description: p.description
|
|
998
|
+
}));
|
|
999
|
+
return {
|
|
1000
|
+
content: [
|
|
1001
|
+
{ type: "text", text: JSON.stringify(listing, null, 2) }
|
|
1002
|
+
]
|
|
1003
|
+
};
|
|
1004
|
+
}
|
|
1005
|
+
);
|
|
1006
|
+
server.tool(
|
|
1007
|
+
"get-doc-page",
|
|
1008
|
+
"Fetch a specific DataQueue doc page by slug. Returns full page content as markdown.",
|
|
1009
|
+
{
|
|
1010
|
+
slug: z.string().describe('The doc page slug, e.g. "usage/add-job" or "api/job-queue"')
|
|
1011
|
+
},
|
|
1012
|
+
async ({ slug }) => {
|
|
1013
|
+
const page = pages.find((p) => p.slug === slug);
|
|
1014
|
+
if (!page) {
|
|
1015
|
+
return {
|
|
1016
|
+
content: [
|
|
1017
|
+
{
|
|
1018
|
+
type: "text",
|
|
1019
|
+
text: `Page not found: "${slug}". Use list-doc-pages to see available slugs.`
|
|
1020
|
+
}
|
|
1021
|
+
],
|
|
1022
|
+
isError: true
|
|
1023
|
+
};
|
|
1024
|
+
}
|
|
1025
|
+
const header = page.description ? `# ${page.title}
|
|
1026
|
+
|
|
1027
|
+
> ${page.description}
|
|
1028
|
+
|
|
1029
|
+
` : `# ${page.title}
|
|
1030
|
+
|
|
1031
|
+
`;
|
|
1032
|
+
return {
|
|
1033
|
+
content: [{ type: "text", text: header + page.content }]
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
1036
|
+
);
|
|
1037
|
+
server.tool(
|
|
1038
|
+
"search-docs",
|
|
1039
|
+
"Full-text search across all DataQueue documentation pages. Returns matching sections with page titles and content excerpts.",
|
|
1040
|
+
{
|
|
1041
|
+
query: z.string().describe('Search query, e.g. "cron scheduling" or "waitForToken"')
|
|
1042
|
+
},
|
|
1043
|
+
async ({ query }) => {
|
|
1044
|
+
const queryTerms = query.toLowerCase().split(/\s+/).filter((t) => t.length > 1);
|
|
1045
|
+
if (queryTerms.length === 0) {
|
|
1046
|
+
return {
|
|
1047
|
+
content: [
|
|
1048
|
+
{ type: "text", text: "Please provide a search query." }
|
|
1049
|
+
],
|
|
1050
|
+
isError: true
|
|
1051
|
+
};
|
|
1052
|
+
}
|
|
1053
|
+
const scored = pages.map((page) => ({
|
|
1054
|
+
page,
|
|
1055
|
+
score: scorePageForQuery(page, queryTerms)
|
|
1056
|
+
})).filter((r) => r.score > 0).sort((a, b) => b.score - a.score).slice(0, 5);
|
|
1057
|
+
if (scored.length === 0) {
|
|
1058
|
+
return {
|
|
1059
|
+
content: [
|
|
1060
|
+
{
|
|
1061
|
+
type: "text",
|
|
1062
|
+
text: `No results for "${query}". Try different keywords or use list-doc-pages to browse.`
|
|
1063
|
+
}
|
|
1064
|
+
]
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
const results = scored.map((r) => {
|
|
1068
|
+
const excerpt = extractExcerpt(r.page.content, queryTerms);
|
|
1069
|
+
return `## ${r.page.title} (${r.page.slug})
|
|
1070
|
+
|
|
1071
|
+
${r.page.description}
|
|
1072
|
+
|
|
1073
|
+
${excerpt}`;
|
|
1074
|
+
});
|
|
1075
|
+
return {
|
|
1076
|
+
content: [{ type: "text", text: results.join("\n\n---\n\n") }]
|
|
1077
|
+
};
|
|
1078
|
+
}
|
|
1079
|
+
);
|
|
1080
|
+
const transport = deps.transport ?? new StdioServerTransport();
|
|
1081
|
+
await server.connect(transport);
|
|
1082
|
+
return server;
|
|
1083
|
+
}
|
|
1084
|
+
var isDirectRun = process.argv[1] && (process.argv[1].endsWith("/mcp-server.js") || process.argv[1].endsWith("/mcp-server.cjs"));
|
|
1085
|
+
if (isDirectRun) {
|
|
1086
|
+
startMcpServer().catch((err) => {
|
|
1087
|
+
console.error("Failed to start MCP server:", err);
|
|
1088
|
+
process.exit(1);
|
|
1089
|
+
});
|
|
1090
|
+
}
|
|
574
1091
|
|
|
575
1092
|
// src/cli.ts
|
|
576
|
-
var
|
|
577
|
-
var
|
|
1093
|
+
var __filename4 = fileURLToPath(import.meta.url);
|
|
1094
|
+
var __dirname4 = path3.dirname(__filename4);
|
|
578
1095
|
function runCli(argv, {
|
|
579
1096
|
log = console.log,
|
|
580
1097
|
error = console.error,
|
|
581
1098
|
exit = (code) => process.exit(code),
|
|
582
1099
|
spawnSyncImpl = spawnSync,
|
|
583
|
-
migrationsDir =
|
|
1100
|
+
migrationsDir = path3.join(__dirname4, "../migrations"),
|
|
584
1101
|
initDeps,
|
|
585
|
-
runInitImpl = runInit
|
|
1102
|
+
runInitImpl = runInit,
|
|
1103
|
+
installSkillsDeps,
|
|
1104
|
+
runInstallSkillsImpl = runInstallSkills,
|
|
1105
|
+
installRulesDeps,
|
|
1106
|
+
runInstallRulesImpl = runInstallRules,
|
|
1107
|
+
installMcpDeps,
|
|
1108
|
+
runInstallMcpImpl = runInstallMcp,
|
|
1109
|
+
startMcpServerImpl = startMcpServer
|
|
586
1110
|
} = {}) {
|
|
587
1111
|
const [, , command, ...restArgs] = argv;
|
|
588
1112
|
function printUsage() {
|
|
@@ -591,6 +1115,10 @@ function runCli(argv, {
|
|
|
591
1115
|
" dataqueue-cli migrate [--envPath <path>] [-s <schema> | --schema <schema>]"
|
|
592
1116
|
);
|
|
593
1117
|
log(" dataqueue-cli init");
|
|
1118
|
+
log(" dataqueue-cli install-skills");
|
|
1119
|
+
log(" dataqueue-cli install-rules");
|
|
1120
|
+
log(" dataqueue-cli install-mcp");
|
|
1121
|
+
log(" dataqueue-cli mcp");
|
|
594
1122
|
log("");
|
|
595
1123
|
log("Options for migrate:");
|
|
596
1124
|
log(
|
|
@@ -600,24 +1128,13 @@ function runCli(argv, {
|
|
|
600
1128
|
" -s, --schema <schema> Set the schema to use (passed to node-pg-migrate)"
|
|
601
1129
|
);
|
|
602
1130
|
log("");
|
|
603
|
-
log("
|
|
604
|
-
log(
|
|
605
|
-
|
|
606
|
-
);
|
|
607
|
-
log(
|
|
608
|
-
" - For managed Postgres (e.g., DigitalOcean) with SSL, set PGSSLMODE=require and PGSSLROOTCERT to your CA .crt file."
|
|
609
|
-
);
|
|
610
|
-
log(
|
|
611
|
-
" Example: PGSSLMODE=require NODE_EXTRA_CA_CERTS=/absolute/path/to/ca.crt PG_DATAQUEUE_DATABASE=... npx dataqueue-cli migrate"
|
|
612
|
-
);
|
|
613
|
-
log("");
|
|
614
|
-
log("Notes for init:");
|
|
615
|
-
log(
|
|
616
|
-
" - Supports both Next.js App Router and Pages Router (prefers App Router if both exist)."
|
|
617
|
-
);
|
|
1131
|
+
log("AI tooling commands:");
|
|
1132
|
+
log(" install-skills Install DataQueue skill files for AI assistants");
|
|
1133
|
+
log(" install-rules Install DataQueue agent rules for AI clients");
|
|
618
1134
|
log(
|
|
619
|
-
" -
|
|
1135
|
+
" install-mcp Configure the DataQueue MCP server for AI clients"
|
|
620
1136
|
);
|
|
1137
|
+
log(" mcp Start the DataQueue MCP server (stdio)");
|
|
621
1138
|
exit(1);
|
|
622
1139
|
}
|
|
623
1140
|
if (command === "migrate") {
|
|
@@ -661,6 +1178,32 @@ function runCli(argv, {
|
|
|
661
1178
|
exit,
|
|
662
1179
|
...initDeps
|
|
663
1180
|
});
|
|
1181
|
+
} else if (command === "install-skills") {
|
|
1182
|
+
runInstallSkillsImpl({
|
|
1183
|
+
log,
|
|
1184
|
+
error,
|
|
1185
|
+
exit,
|
|
1186
|
+
...installSkillsDeps
|
|
1187
|
+
});
|
|
1188
|
+
} else if (command === "install-rules") {
|
|
1189
|
+
runInstallRulesImpl({
|
|
1190
|
+
log,
|
|
1191
|
+
error,
|
|
1192
|
+
exit,
|
|
1193
|
+
...installRulesDeps
|
|
1194
|
+
});
|
|
1195
|
+
} else if (command === "install-mcp") {
|
|
1196
|
+
runInstallMcpImpl({
|
|
1197
|
+
log,
|
|
1198
|
+
error,
|
|
1199
|
+
exit,
|
|
1200
|
+
...installMcpDeps
|
|
1201
|
+
});
|
|
1202
|
+
} else if (command === "mcp") {
|
|
1203
|
+
startMcpServerImpl().catch((err) => {
|
|
1204
|
+
error("Failed to start MCP server:", err);
|
|
1205
|
+
exit(1);
|
|
1206
|
+
});
|
|
664
1207
|
} else {
|
|
665
1208
|
printUsage();
|
|
666
1209
|
}
|