specdrive-cli 0.1.9 → 0.1.12

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.
Files changed (74) hide show
  1. package/README.md +955 -506
  2. package/agents/00-onboarding.md +261 -0
  3. package/agents/01-constitution.md +214 -201
  4. package/agents/02-specification.md +249 -226
  5. package/agents/03-uiux.md +156 -144
  6. package/agents/04-cascade.md +151 -122
  7. package/agents/05-discover-skills.md +136 -136
  8. package/agents/06-documentation.md +158 -145
  9. package/agents/07-implementation.md +201 -169
  10. package/agents/08-performance.md +179 -165
  11. package/agents/09-review-complete.md +239 -168
  12. package/agents/10-security.md +180 -167
  13. package/agents/11-test.md +195 -0
  14. package/commands/gates.js +73 -73
  15. package/commands/manifest.json +113 -95
  16. package/commands/permissions.json +39 -0
  17. package/commands/router.js +151 -127
  18. package/commands/tools.json +19 -19
  19. package/dashboard/app.js +394 -0
  20. package/dashboard/index.html +74 -0
  21. package/dashboard/server.js +166 -0
  22. package/dashboard/style.css +157 -0
  23. package/mcp/mcp.json +31 -0
  24. package/mcp/server.js +108 -0
  25. package/package.json +35 -32
  26. package/schemas/config.schema.json +20 -0
  27. package/schemas/workflow-state.schema.json +152 -35
  28. package/scripts/anti-redundancy.js +176 -176
  29. package/scripts/audit-log.js +46 -46
  30. package/scripts/check-permission.js +87 -0
  31. package/scripts/diff-spec.js +51 -0
  32. package/scripts/diff-version.js +96 -0
  33. package/scripts/generate-adapters.js +80 -80
  34. package/scripts/generate-from-template.js +97 -70
  35. package/scripts/generate-openapi.js +76 -0
  36. package/scripts/github-team-sync.js +81 -0
  37. package/scripts/install-hooks.js +21 -0
  38. package/scripts/load-plugins.js +65 -65
  39. package/scripts/migrate-openspec.js +318 -0
  40. package/scripts/migrate-speckit.js +322 -0
  41. package/scripts/migrate.js +12 -0
  42. package/scripts/onboard.js +312 -0
  43. package/scripts/pre-commit.js +56 -20
  44. package/scripts/team.js +113 -113
  45. package/scripts/test-adapters.js +118 -118
  46. package/scripts/test-create.js +13 -13
  47. package/scripts/test-end-to-end.js +137 -137
  48. package/scripts/test-router.js +110 -110
  49. package/scripts/test-state-transitions.js +146 -146
  50. package/scripts/test-validator.js +152 -152
  51. package/scripts/validate-config.js +36 -0
  52. package/scripts/validate-governance.js +150 -130
  53. package/scripts/verify.js +525 -0
  54. package/scripts/version-new.js +202 -0
  55. package/src/index.js +1010 -400
  56. package/templates/expo/plan.json +12 -0
  57. package/templates/expo/spec.json +12 -0
  58. package/templates/expo/tasks.json +5 -0
  59. package/templates/fastapi/plan.json +12 -0
  60. package/templates/fastapi/spec.json +12 -0
  61. package/templates/fastapi/tasks.json +5 -0
  62. package/templates/generic/plan.json +12 -0
  63. package/templates/generic/spec.json +11 -0
  64. package/templates/generic/tasks.json +5 -0
  65. package/templates/nextjs/plan.json +23 -0
  66. package/templates/nextjs/spec.json +12 -0
  67. package/templates/nextjs/tasks.json +5 -0
  68. package/templates/react-node/plan.json +15 -0
  69. package/templates/react-node/spec.json +12 -0
  70. package/templates/react-node/tasks.json +5 -0
  71. package/templates/registry.json +30 -0
  72. package/templates/turborepo/plan.json +12 -0
  73. package/templates/turborepo/spec.json +12 -0
  74. package/templates/turborepo/tasks.json +5 -0
@@ -1,81 +1,81 @@
1
- const fs = require("fs");
2
- const path = require("path");
3
-
4
- const projectRoot = process.cwd();
5
-
6
- function loadTools() {
7
- const toolsPath = path.join(projectRoot, ".sdrive", "commands", "tools.json");
8
-
9
- if (!fs.existsSync(toolsPath)) {
10
- console.error("❌ tools.json not found.");
11
- process.exit(1);
12
- }
13
-
14
- return JSON.parse(fs.readFileSync(toolsPath, "utf8")).tools;
15
- }
16
-
17
- function adapterContent(toolName) {
18
- return `# SpecDrive Commands
19
-
20
- This project uses SpecDrive for spec-driven development.
21
-
22
- ## How to use
23
-
24
- When the user types a /sdrive:* command, follow these steps:
25
-
26
- 1. Read .sdrive/commands/manifest.json
27
- 2. Find the matching command
28
- 3. Read the agent file listed in agentFile
29
- 4. Follow that agent's instructions exactly
30
- 5. Run validators after writing files
31
- 6. Stop at human approval gates
32
-
33
- Always read the manifest first. Never hardcode the command list.
34
- `;
35
- }
36
-
37
- function generateForTools(tools) {
38
- tools.forEach((tool) => {
39
- const adapterFullPath = path.join(projectRoot, tool.adapterPath);
40
- const adapterDir = path.dirname(adapterFullPath);
41
-
42
- if (!fs.existsSync(adapterDir)) {
43
- fs.mkdirSync(adapterDir, { recursive: true });
44
- }
45
-
46
- if (fs.existsSync(adapterFullPath)) {
47
- console.log(`⚠️ Skipped existing: ${tool.adapterPath}`);
48
- return;
49
- }
50
-
51
- fs.writeFileSync(adapterFullPath, adapterContent(tool.name));
52
- console.log(`✅ Generated: ${tool.adapterPath}`);
53
- });
54
- }
55
-
56
- function generateAll() {
57
- const tools = loadTools();
58
- generateForTools(tools);
59
- console.log("\n🎉 All adapters generated successfully.");
60
- }
61
-
62
- function generateSelected(indexes) {
63
- const tools = loadTools();
64
- const selectedTools = indexes
65
- .map((i) => tools[i])
66
- .filter((tool) => tool);
67
-
68
- if (selectedTools.length === 0) {
69
- console.error("❌ No valid tools selected.");
70
- process.exit(1);
71
- }
72
-
73
- generateForTools(selectedTools);
74
- console.log("\n🎉 Selected adapters generated successfully.");
75
- }
76
-
77
- module.exports = {
78
- generateAll,
79
- generateSelected,
80
- loadTools
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+
4
+ const projectRoot = process.cwd();
5
+
6
+ function loadTools() {
7
+ const toolsPath = path.join(projectRoot, ".sdrive", "commands", "tools.json");
8
+
9
+ if (!fs.existsSync(toolsPath)) {
10
+ console.error("❌ tools.json not found.");
11
+ process.exit(1);
12
+ }
13
+
14
+ return JSON.parse(fs.readFileSync(toolsPath, "utf8")).tools;
15
+ }
16
+
17
+ function adapterContent(toolName) {
18
+ return `# SpecDrive Commands
19
+
20
+ This project uses SpecDrive for spec-driven development.
21
+
22
+ ## How to use
23
+
24
+ When the user types a /sdrive:* command, follow these steps:
25
+
26
+ 1. Read .sdrive/commands/manifest.json
27
+ 2. Find the matching command
28
+ 3. Read the agent file listed in agentFile
29
+ 4. Follow that agent's instructions exactly
30
+ 5. Run validators after writing files
31
+ 6. Stop at human approval gates
32
+
33
+ Always read the manifest first. Never hardcode the command list.
34
+ `;
35
+ }
36
+
37
+ function generateForTools(tools) {
38
+ tools.forEach((tool) => {
39
+ const adapterFullPath = path.join(projectRoot, tool.adapterPath);
40
+ const adapterDir = path.dirname(adapterFullPath);
41
+
42
+ if (!fs.existsSync(adapterDir)) {
43
+ fs.mkdirSync(adapterDir, { recursive: true });
44
+ }
45
+
46
+ if (fs.existsSync(adapterFullPath)) {
47
+ console.log(`⚠️ Skipped existing: ${tool.adapterPath}`);
48
+ return;
49
+ }
50
+
51
+ fs.writeFileSync(adapterFullPath, adapterContent(tool.name));
52
+ console.log(`✅ Generated: ${tool.adapterPath}`);
53
+ });
54
+ }
55
+
56
+ function generateAll() {
57
+ const tools = loadTools();
58
+ generateForTools(tools);
59
+ console.log("\n🎉 All adapters generated successfully.");
60
+ }
61
+
62
+ function generateSelected(indexes) {
63
+ const tools = loadTools();
64
+ const selectedTools = indexes
65
+ .map((i) => tools[i])
66
+ .filter((tool) => tool);
67
+
68
+ if (selectedTools.length === 0) {
69
+ console.error("❌ No valid tools selected.");
70
+ process.exit(1);
71
+ }
72
+
73
+ generateForTools(selectedTools);
74
+ console.log("\n🎉 Selected adapters generated successfully.");
75
+ }
76
+
77
+ module.exports = {
78
+ generateAll,
79
+ generateSelected,
80
+ loadTools
81
81
  };
@@ -1,71 +1,98 @@
1
- const fs = require("fs");
2
- const path = require("path");
3
-
4
- const projectRoot = process.cwd();
5
-
6
- function loadTemplate(templateName, fileName) {
7
- const templatePath = path.join(projectRoot, ".sdrive", "templates", templateName, fileName);
8
- return fs.readFileSync(templatePath, "utf8");
9
- }
10
-
11
- function applyVariables(content, variables) {
12
- let result = content;
13
- Object.keys(variables).forEach((key) => {
14
- result = result.replaceAll(`{{${key}}}`, variables[key]);
15
- });
16
- return result;
17
- }
18
-
19
- function generate(templateName, featureName, variables) {
20
- const templateDir = path.join(projectRoot, ".sdrive", "templates", templateName);
21
- const govDir = path.join(projectRoot, ".sdrive", "governance", featureName);
22
-
23
- if (!fs.existsSync(templateDir)) {
24
- console.error(`❌ Template "${templateName}" not found.`);
25
- process.exit(1);
26
- }
27
-
28
- fs.mkdirSync(govDir, { recursive: true });
29
-
30
- const files = ["spec.json", "plan.json", "tasks.json"];
31
-
32
- files.forEach((file) => {
33
- const templateFile = path.join(templateDir, file);
34
-
35
- if (!fs.existsSync(templateFile)) {
36
- return;
37
- }
38
-
39
- let content = loadTemplate(templateName, file);
40
- content = applyVariables(content, {
41
- featureName,
42
- ...variables
43
- });
44
-
45
- fs.writeFileSync(path.join(govDir, file), content);
46
- console.log(`✅ Generated: ${featureName}/${file}`);
47
- });
48
-
49
- console.log(`\n🎉 Feature "${featureName}" generated from "${templateName}" template.`);
50
- }
51
-
52
- const templateName = process.argv[2];
53
- const featureName = process.argv[3];
54
- const problemStatement = process.argv[4] || "";
55
- const proposedSolution = process.argv[5] || "";
56
- const targetAudience = process.argv[6] || "";
57
- const approach = process.argv[7] || "";
58
- const flowDescription = process.argv[8] || "";
59
-
60
- if (!templateName || !featureName) {
61
- console.error("❌ Usage: node generate-from-template.js <template> <feature> [problem] [solution] [audience] [approach] [flow]");
62
- process.exit(1);
63
- }
64
-
65
- generate(templateName, featureName, {
66
- problemStatement,
67
- proposedSolution,
68
- targetAudience,
69
- approach,
70
- flowDescription
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+
4
+ const projectRoot = process.cwd();
5
+
6
+ function loadRegistry() {
7
+ const registryPath = path.join(projectRoot, ".sdrive", "templates", "registry.json");
8
+
9
+ if (!fs.existsSync(registryPath)) {
10
+ console.error("❌ registry.json not found.");
11
+ process.exit(1);
12
+ }
13
+
14
+ return JSON.parse(fs.readFileSync(registryPath, "utf8")).templates;
15
+ }
16
+
17
+ function templateExists(templateName) {
18
+ const registry = loadRegistry();
19
+ return registry.some((t) => t.name === templateName || t.path === templateName);
20
+ }
21
+
22
+ function listTemplates() {
23
+ const registry = loadRegistry();
24
+ console.log("\nAvailable SpecDrive Templates\n");
25
+ registry.forEach((t) => {
26
+ console.log(` ${t.name} — ${t.description}`);
27
+ });
28
+ }
29
+
30
+ function loadTemplate(templateName, fileName) {
31
+ const templatePath = path.join(projectRoot, ".sdrive", "templates", templateName, fileName);
32
+ return fs.readFileSync(templatePath, "utf8");
33
+ }
34
+
35
+ function applyVariables(content, variables) {
36
+ let result = content;
37
+ Object.keys(variables).forEach((key) => {
38
+ result = result.replaceAll(`{{${key}}}`, variables[key]);
39
+ });
40
+ return result;
41
+ }
42
+
43
+ function generate(templateName, featureName, variables) {
44
+ const templateDir = path.join(projectRoot, ".sdrive", "templates", templateName);
45
+ const govDir = path.join(projectRoot, ".sdrive", "governance", featureName);
46
+
47
+ if (!fs.existsSync(templateDir)) {
48
+ console.error(`❌ Template "${templateName}" not found.`);
49
+ listTemplates();
50
+ process.exit(1);
51
+ }
52
+
53
+ fs.mkdirSync(govDir, { recursive: true });
54
+
55
+ const files = ["spec.json", "plan.json", "tasks.json"];
56
+
57
+ files.forEach((file) => {
58
+ const templateFile = path.join(templateDir, file);
59
+
60
+ if (!fs.existsSync(templateFile)) {
61
+ return;
62
+ }
63
+
64
+ let content = loadTemplate(templateName, file);
65
+ content = applyVariables(content, {
66
+ featureName,
67
+ ...variables
68
+ });
69
+
70
+ fs.writeFileSync(path.join(govDir, file), content);
71
+ console.log(`✅ Generated: ${featureName}/${file}`);
72
+ });
73
+
74
+ console.log(`\n🎉 Feature "${featureName}" generated from "${templateName}" template.`);
75
+ }
76
+
77
+ const templateName = process.argv[2];
78
+ const featureName = process.argv[3];
79
+ const problemStatement = process.argv[4] || "";
80
+ const proposedSolution = process.argv[5] || "";
81
+ const targetAudience = process.argv[6] || "";
82
+ const approach = process.argv[7] || "";
83
+ const flowDescription = process.argv[8] || "";
84
+
85
+ if (!templateName || !featureName) {
86
+ console.error("❌ Usage: node generate-from-template.js <template> <feature> [problem] [solution] [audience] [approach] [flow]");
87
+ listTemplates();
88
+ process.exit(1);
89
+ }
90
+
91
+ generate(templateName, featureName, {
92
+ title: featureName,
93
+ problemStatement,
94
+ proposedSolution,
95
+ targetAudience,
96
+ approach,
97
+ flowDescription
71
98
  });
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+
6
+ const projectRoot = process.cwd();
7
+
8
+ function loadPlan(featureName) {
9
+ const planPath = path.join(projectRoot, ".sdrive", "governance", featureName, "plan.json");
10
+
11
+ if (!fs.existsSync(planPath)) {
12
+ console.error(`❌ plan.json not found for feature "${featureName}".`);
13
+ process.exit(1);
14
+ }
15
+
16
+ return JSON.parse(fs.readFileSync(planPath, "utf8"));
17
+ }
18
+
19
+ function generateOpenAPI(featureName) {
20
+ const plan = loadPlan(featureName);
21
+
22
+ const openapi = {
23
+ openapi: "3.0.0",
24
+ info: {
25
+ title: `${featureName} API`,
26
+ version: "1.0.0",
27
+ description: `API specification for ${featureName}`
28
+ },
29
+ paths: {}
30
+ };
31
+
32
+ const contracts = plan.apiContracts || [];
33
+
34
+ contracts.forEach((contract) => {
35
+ const method = contract.method.toLowerCase();
36
+ const apiPath = contract.path;
37
+
38
+ if (!openapi.paths[apiPath]) {
39
+ openapi.paths[apiPath] = {};
40
+ }
41
+
42
+ openapi.paths[apiPath][method] = {
43
+ summary: `${contract.method} ${apiPath}`,
44
+ security: contract.authenticationRequired ? [{ bearerAuth: [] }] : [],
45
+ responses: {
46
+ 200: {
47
+ description: "Successful response"
48
+ }
49
+ }
50
+ };
51
+ });
52
+
53
+ openapi.components = {
54
+ securitySchemes: {
55
+ bearerAuth: {
56
+ type: "http",
57
+ scheme: "bearer"
58
+ }
59
+ }
60
+ };
61
+
62
+ const outputPath = path.join(projectRoot, ".sdrive", "governance", featureName, "openapi.json");
63
+ fs.writeFileSync(outputPath, JSON.stringify(openapi, null, 2));
64
+
65
+ console.log(`✅ Generated OpenAPI spec.`);
66
+ console.log(` File: ${outputPath}`);
67
+ }
68
+
69
+ const featureName = process.argv[2];
70
+
71
+ if (!featureName) {
72
+ console.error("❌ Usage: node generate-openapi.js <feature-name>");
73
+ process.exit(1);
74
+ }
75
+
76
+ generateOpenAPI(featureName);
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { execSync } = require("child_process");
4
+ const fs = require("fs");
5
+ const path = require("path");
6
+
7
+ const projectRoot = process.cwd();
8
+
9
+ function getRepoInfo() {
10
+ try {
11
+ const remote = execSync("git config --get remote.origin.url", {
12
+ stdio: "pipe",
13
+ encoding: "utf8"
14
+ }).trim();
15
+
16
+ const match = remote.match(/github\.com[:/](.+?)\/(.+?)(\.git)?$/);
17
+
18
+ if (!match) {
19
+ console.error("❌ Could not detect GitHub repository.");
20
+ return null;
21
+ }
22
+
23
+ return { owner: match[1], repo: match[2] };
24
+ } catch (error) {
25
+ console.error("❌ Git not initialized or remote missing.");
26
+ return null;
27
+ }
28
+ }
29
+
30
+ function fetchCollaborators(owner, repo) {
31
+ try {
32
+ const result = execSync(`gh api repos/${owner}/${repo}/collaborators`, {
33
+ stdio: "pipe",
34
+ encoding: "utf8"
35
+ });
36
+
37
+ return JSON.parse(result);
38
+ } catch (error) {
39
+ console.error("❌ Could not fetch collaborators. Is gh CLI installed and authenticated?");
40
+ return null;
41
+ }
42
+ }
43
+
44
+ function syncTeam() {
45
+ const repoInfo = getRepoInfo();
46
+
47
+ if (!repoInfo) {
48
+ process.exit(1);
49
+ }
50
+
51
+ const collaborators = fetchCollaborators(repoInfo.owner, repoInfo.repo);
52
+
53
+ if (!collaborators) {
54
+ process.exit(1);
55
+ }
56
+
57
+ const teamPath = path.join(projectRoot, ".sdrive", "team.json");
58
+
59
+ if (!fs.existsSync(teamPath)) {
60
+ console.error("❌ team.json not found. Run `sdrive init` first.");
61
+ process.exit(1);
62
+ }
63
+
64
+ const team = JSON.parse(fs.readFileSync(teamPath, "utf8"));
65
+
66
+ collaborators.forEach((user) => {
67
+ const existing = team.members.find((m) => m.username === user.login);
68
+
69
+ if (!existing) {
70
+ team.members.push({
71
+ username: user.login,
72
+ role: user.permissions?.admin ? "admin" : "developer"
73
+ });
74
+ }
75
+ });
76
+
77
+ fs.writeFileSync(teamPath, JSON.stringify(team, null, 2));
78
+ console.log(`✅ Synced ${collaborators.length} collaborators from GitHub.`);
79
+ }
80
+
81
+ syncTeam();
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+
6
+ const projectRoot = process.cwd();
7
+ const hooksDir = path.join(projectRoot, ".git", "hooks");
8
+ const preCommitPath = path.join(hooksDir, "pre-commit");
9
+
10
+ const hookContent = `#!/bin/sh
11
+ node .sdrive/scripts/pre-commit.js
12
+ `;
13
+
14
+ if (!fs.existsSync(hooksDir)) {
15
+ console.error("❌ .git/hooks not found. Are you in a Git repository?");
16
+ process.exit(1);
17
+ }
18
+
19
+ fs.writeFileSync(preCommitPath, hookContent, { mode: 0o755 });
20
+ console.log("✅ Installed SpecDrive pre-commit hook.");
21
+ console.log(" Governance validation will now run before every commit.");
@@ -1,66 +1,66 @@
1
- const fs = require("fs");
2
- const path = require("path");
3
-
4
- const projectRoot = process.cwd();
5
- const pluginsDir = path.join(projectRoot, ".sdrive", "plugins");
6
-
7
- function loadPlugins() {
8
- if (!fs.existsSync(pluginsDir)) {
9
- return [];
10
- }
11
-
12
- const pluginFolders = fs.readdirSync(pluginsDir, { withFileTypes: true })
13
- .filter((d) => d.isDirectory())
14
- .map((d) => d.name);
15
-
16
- const plugins = [];
17
-
18
- pluginFolders.forEach((folder) => {
19
- const pluginJsonPath = path.join(pluginsDir, folder, "plugin.json");
20
-
21
- if (!fs.existsSync(pluginJsonPath)) {
22
- return;
23
- }
24
-
25
- try {
26
- const pluginDef = JSON.parse(fs.readFileSync(pluginJsonPath, "utf8"));
27
- plugins.push({
28
- ...pluginDef,
29
- path: path.join(pluginsDir, folder)
30
- });
31
- } catch (error) {
32
- console.warn(`⚠️ Skipping invalid plugin: ${folder}`);
33
- }
34
- });
35
-
36
- return plugins;
37
- }
38
-
39
- function runPlugin(pluginName) {
40
- const plugins = loadPlugins();
41
- const plugin = plugins.find((p) => p.name === pluginName);
42
-
43
- if (!plugin) {
44
- console.error(`❌ Plugin "${pluginName}" not found.`);
45
- process.exit(1);
46
- }
47
-
48
- const entryPath = path.join(plugin.path, plugin.entry);
49
-
50
- if (!fs.existsSync(entryPath)) {
51
- console.error(`❌ Plugin entry not found: ${plugin.entry}`);
52
- process.exit(1);
53
- }
54
-
55
- require(entryPath);
56
- }
57
-
58
- function listPlugins() {
59
- return loadPlugins();
60
- }
61
-
62
- module.exports = {
63
- loadPlugins,
64
- runPlugin,
65
- listPlugins
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+
4
+ const projectRoot = process.cwd();
5
+ const pluginsDir = path.join(projectRoot, ".sdrive", "plugins");
6
+
7
+ function loadPlugins() {
8
+ if (!fs.existsSync(pluginsDir)) {
9
+ return [];
10
+ }
11
+
12
+ const pluginFolders = fs.readdirSync(pluginsDir, { withFileTypes: true })
13
+ .filter((d) => d.isDirectory())
14
+ .map((d) => d.name);
15
+
16
+ const plugins = [];
17
+
18
+ pluginFolders.forEach((folder) => {
19
+ const pluginJsonPath = path.join(pluginsDir, folder, "plugin.json");
20
+
21
+ if (!fs.existsSync(pluginJsonPath)) {
22
+ return;
23
+ }
24
+
25
+ try {
26
+ const pluginDef = JSON.parse(fs.readFileSync(pluginJsonPath, "utf8"));
27
+ plugins.push({
28
+ ...pluginDef,
29
+ path: path.join(pluginsDir, folder)
30
+ });
31
+ } catch (error) {
32
+ console.warn(`⚠️ Skipping invalid plugin: ${folder}`);
33
+ }
34
+ });
35
+
36
+ return plugins;
37
+ }
38
+
39
+ function runPlugin(pluginName) {
40
+ const plugins = loadPlugins();
41
+ const plugin = plugins.find((p) => p.name === pluginName);
42
+
43
+ if (!plugin) {
44
+ console.error(`❌ Plugin "${pluginName}" not found.`);
45
+ process.exit(1);
46
+ }
47
+
48
+ const entryPath = path.join(plugin.path, plugin.entry);
49
+
50
+ if (!fs.existsSync(entryPath)) {
51
+ console.error(`❌ Plugin entry not found: ${plugin.entry}`);
52
+ process.exit(1);
53
+ }
54
+
55
+ require(entryPath);
56
+ }
57
+
58
+ function listPlugins() {
59
+ return loadPlugins();
60
+ }
61
+
62
+ module.exports = {
63
+ loadPlugins,
64
+ runPlugin,
65
+ listPlugins
66
66
  };