@uniswap/ai-toolkit-nx-claude 0.5.7-next.10
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 +60 -0
- package/dist/cli-generator.cjs +447 -0
- package/dist/content/agents/agnostic/agent-capability-analyst.md +575 -0
- package/dist/content/agents/agnostic/agent-optimizer.md +396 -0
- package/dist/content/agents/agnostic/agent-orchestrator.md +475 -0
- package/dist/content/agents/agnostic/cicd-agent.md +301 -0
- package/dist/content/agents/agnostic/claude-agent-discovery.md +304 -0
- package/dist/content/agents/agnostic/code-explainer.md +269 -0
- package/dist/content/agents/agnostic/code-generator.md +785 -0
- package/dist/content/agents/agnostic/commit-message-generator.md +101 -0
- package/dist/content/agents/agnostic/context-loader.md +419 -0
- package/dist/content/agents/agnostic/debug-assistant.md +316 -0
- package/dist/content/agents/agnostic/doc-writer.md +539 -0
- package/dist/content/agents/agnostic/feedback-collector.md +165 -0
- package/dist/content/agents/agnostic/infrastructure-agent.md +406 -0
- package/dist/content/agents/agnostic/migration-assistant.md +489 -0
- package/dist/content/agents/agnostic/pattern-learner.md +481 -0
- package/dist/content/agents/agnostic/performance-analyzer.md +528 -0
- package/dist/content/agents/agnostic/plan-reviewer.md +144 -0
- package/dist/content/agents/agnostic/planner.md +246 -0
- package/dist/content/agents/agnostic/pr-creator.md +494 -0
- package/dist/content/agents/agnostic/prompt-engineer.md +541 -0
- package/dist/content/agents/agnostic/refactorer.md +311 -0
- package/dist/content/agents/agnostic/researcher.md +347 -0
- package/dist/content/agents/agnostic/security-analyzer.md +1087 -0
- package/dist/content/agents/agnostic/style-enforcer.md +568 -0
- package/dist/content/agents/agnostic/test-runner.md +481 -0
- package/dist/content/agents/agnostic/test-writer.md +292 -0
- package/dist/content/commands/agnostic/auto-spec.md +386 -0
- package/dist/content/commands/agnostic/create-pr.md +79 -0
- package/dist/content/commands/agnostic/deploy.md +441 -0
- package/dist/content/commands/agnostic/execute-plan.md +367 -0
- package/dist/content/commands/agnostic/explain-file.md +303 -0
- package/dist/content/commands/agnostic/explore.md +82 -0
- package/dist/content/commands/agnostic/fix-bug.md +273 -0
- package/dist/content/commands/agnostic/gen-tests.md +185 -0
- package/dist/content/commands/agnostic/generate-commit-message.md +92 -0
- package/dist/content/commands/agnostic/implement-spec.md +270 -0
- package/dist/content/commands/agnostic/monitor.md +581 -0
- package/dist/content/commands/agnostic/plan.md +335 -0
- package/dist/content/commands/agnostic/refactor.md +315 -0
- package/dist/content/commands/agnostic/research.md +49 -0
- package/dist/content/commands/agnostic/review-code.md +321 -0
- package/dist/content/commands/agnostic/review-plan.md +110 -0
- package/dist/content/commands/agnostic/review-pr.md +393 -0
- package/dist/generators/add-agent/files/__name__.md.template +37 -0
- package/dist/generators/add-agent/generator.cjs +534 -0
- package/dist/generators/add-agent/schema.json +59 -0
- package/dist/generators/add-command/files/__name__.md.template +46 -0
- package/dist/generators/add-command/generator.cjs +537 -0
- package/dist/generators/add-command/schema.json +50 -0
- package/dist/generators/addons/generator.cjs +1285 -0
- package/dist/generators/addons/schema.json +55 -0
- package/dist/generators/files/src/index.ts.template +1 -0
- package/dist/generators/hooks/CLAUDE.md +326 -0
- package/dist/generators/hooks/README.md +220 -0
- package/dist/generators/hooks/generator.cjs +1086 -0
- package/dist/generators/hooks/schema.json +38 -0
- package/dist/generators/init/CLAUDE.md +291 -0
- package/dist/generators/init/generator.cjs +1268 -0
- package/dist/generators/init/schema.json +82 -0
- package/dist/generators/init/setup-github-packages.sh +132 -0
- package/dist/generators/setup-registry-proxy/README.md +111 -0
- package/dist/generators/setup-registry-proxy/files/uniswap-ai-toolkit.__shell__rc.template +178 -0
- package/dist/generators/setup-registry-proxy/generator.cjs +227 -0
- package/dist/generators/setup-registry-proxy/schema.json +43 -0
- package/dist/index.cjs +1 -0
- package/dist/packages/ai-toolkit-nx-claude/src/cli-generator.d.ts +16 -0
- package/dist/packages/ai-toolkit-nx-claude/src/cli-generator.d.ts.map +1 -0
- package/dist/packages/ai-toolkit-nx-claude/src/cli-utils.d.ts +6 -0
- package/dist/packages/ai-toolkit-nx-claude/src/cli-utils.d.ts.map +1 -0
- package/dist/packages/ai-toolkit-nx-claude/src/generators/add-agent/generator.d.ts +5 -0
- package/dist/packages/ai-toolkit-nx-claude/src/generators/add-agent/generator.d.ts.map +1 -0
- package/dist/packages/ai-toolkit-nx-claude/src/generators/add-command/generator.d.ts +5 -0
- package/dist/packages/ai-toolkit-nx-claude/src/generators/add-command/generator.d.ts.map +1 -0
- package/dist/packages/ai-toolkit-nx-claude/src/generators/addons/addon-registry.d.ts +76 -0
- package/dist/packages/ai-toolkit-nx-claude/src/generators/addons/addon-registry.d.ts.map +1 -0
- package/dist/packages/ai-toolkit-nx-claude/src/generators/addons/claude-mcp-installer.d.ts +47 -0
- package/dist/packages/ai-toolkit-nx-claude/src/generators/addons/claude-mcp-installer.d.ts.map +1 -0
- package/dist/packages/ai-toolkit-nx-claude/src/generators/addons/generator.d.ts +7 -0
- package/dist/packages/ai-toolkit-nx-claude/src/generators/addons/generator.d.ts.map +1 -0
- package/dist/packages/ai-toolkit-nx-claude/src/generators/addons/github-auth.d.ts +36 -0
- package/dist/packages/ai-toolkit-nx-claude/src/generators/addons/github-auth.d.ts.map +1 -0
- package/dist/packages/ai-toolkit-nx-claude/src/generators/addons/spec-workflow-setup.d.ts +52 -0
- package/dist/packages/ai-toolkit-nx-claude/src/generators/addons/spec-workflow-setup.d.ts.map +1 -0
- package/dist/packages/ai-toolkit-nx-claude/src/generators/hooks/cli-parser.d.ts +2 -0
- package/dist/packages/ai-toolkit-nx-claude/src/generators/hooks/cli-parser.d.ts.map +1 -0
- package/dist/packages/ai-toolkit-nx-claude/src/generators/hooks/dependency-checker.d.ts +46 -0
- package/dist/packages/ai-toolkit-nx-claude/src/generators/hooks/dependency-checker.d.ts.map +1 -0
- package/dist/packages/ai-toolkit-nx-claude/src/generators/hooks/generator.d.ts +9 -0
- package/dist/packages/ai-toolkit-nx-claude/src/generators/hooks/generator.d.ts.map +1 -0
- package/dist/packages/ai-toolkit-nx-claude/src/generators/hooks/install-orchestrator.d.ts +55 -0
- package/dist/packages/ai-toolkit-nx-claude/src/generators/hooks/install-orchestrator.d.ts.map +1 -0
- package/dist/packages/ai-toolkit-nx-claude/src/generators/hooks/repo-manager.d.ts +63 -0
- package/dist/packages/ai-toolkit-nx-claude/src/generators/hooks/repo-manager.d.ts.map +1 -0
- package/dist/packages/ai-toolkit-nx-claude/src/generators/init/generator.d.ts +5 -0
- package/dist/packages/ai-toolkit-nx-claude/src/generators/init/generator.d.ts.map +1 -0
- package/dist/packages/ai-toolkit-nx-claude/src/generators/setup-registry-proxy/generator.d.ts +5 -0
- package/dist/packages/ai-toolkit-nx-claude/src/generators/setup-registry-proxy/generator.d.ts.map +1 -0
- package/dist/packages/ai-toolkit-nx-claude/src/index.d.ts +2 -0
- package/dist/packages/ai-toolkit-nx-claude/src/index.d.ts.map +1 -0
- package/dist/packages/ai-toolkit-nx-claude/src/utils/cli-parser.d.ts +15 -0
- package/dist/packages/ai-toolkit-nx-claude/src/utils/cli-parser.d.ts.map +1 -0
- package/dist/packages/ai-toolkit-nx-claude/src/utils/prompt-utils.d.ts +42 -0
- package/dist/packages/ai-toolkit-nx-claude/src/utils/prompt-utils.d.ts.map +1 -0
- package/generators.json +34 -0
- package/package.json +166 -0
|
@@ -0,0 +1,1285 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
9
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
10
|
+
};
|
|
11
|
+
var __export = (target, all) => {
|
|
12
|
+
for (var name in all)
|
|
13
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
14
|
+
};
|
|
15
|
+
var __copyProps = (to, from, except, desc) => {
|
|
16
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
17
|
+
for (let key of __getOwnPropNames(from))
|
|
18
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
19
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
20
|
+
}
|
|
21
|
+
return to;
|
|
22
|
+
};
|
|
23
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
24
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
25
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
26
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
27
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
28
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
29
|
+
mod
|
|
30
|
+
));
|
|
31
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
32
|
+
|
|
33
|
+
// packages/ai-toolkit-nx-claude/src/generators/addons/schema.json
|
|
34
|
+
var require_schema = __commonJS({
|
|
35
|
+
"packages/ai-toolkit-nx-claude/src/generators/addons/schema.json"(exports2, module2) {
|
|
36
|
+
module2.exports = {
|
|
37
|
+
$schema: "https://json-schema.org/schema",
|
|
38
|
+
$id: "SchemaForAddons",
|
|
39
|
+
title: "Addons Installation",
|
|
40
|
+
type: "object",
|
|
41
|
+
description: "Install and configure Claude Code addons including MCP servers.",
|
|
42
|
+
properties: {
|
|
43
|
+
addon: {
|
|
44
|
+
type: "string",
|
|
45
|
+
description: "The addon to install",
|
|
46
|
+
enum: ["spec-workflow-mcp"],
|
|
47
|
+
default: "spec-workflow-mcp",
|
|
48
|
+
"x-prompt": "Select addon to install"
|
|
49
|
+
},
|
|
50
|
+
dashboardMode: {
|
|
51
|
+
type: "string",
|
|
52
|
+
description: "Dashboard startup mode",
|
|
53
|
+
enum: ["always", "manual"],
|
|
54
|
+
default: "always",
|
|
55
|
+
"prompt-message": "\u{1F4CA} When should the spec-workflow dashboard start?",
|
|
56
|
+
"prompt-type": "select",
|
|
57
|
+
"prompt-items": [
|
|
58
|
+
{ value: "always", label: "Always (auto-start on Claude launch)" },
|
|
59
|
+
{ value: "manual", label: "Manual (start via command only)" }
|
|
60
|
+
]
|
|
61
|
+
},
|
|
62
|
+
port: {
|
|
63
|
+
type: "number",
|
|
64
|
+
description: "Dashboard port (default: auto)",
|
|
65
|
+
default: 0,
|
|
66
|
+
"prompt-message": "\u{1F50C} Dashboard port (0 for auto)"
|
|
67
|
+
},
|
|
68
|
+
githubToken: {
|
|
69
|
+
type: "string",
|
|
70
|
+
description: "GitHub personal access token for private packages",
|
|
71
|
+
"prompt-message": "\u{1F511} GitHub personal access token (for private @uniswap packages)"
|
|
72
|
+
},
|
|
73
|
+
force: {
|
|
74
|
+
type: "boolean",
|
|
75
|
+
description: "Force installation even if already exists",
|
|
76
|
+
default: false
|
|
77
|
+
},
|
|
78
|
+
skipVerification: {
|
|
79
|
+
type: "boolean",
|
|
80
|
+
description: "Skip installation verification",
|
|
81
|
+
default: false
|
|
82
|
+
},
|
|
83
|
+
projectPath: {
|
|
84
|
+
type: "string",
|
|
85
|
+
description: "Path to the project where spec-workflow should be installed",
|
|
86
|
+
"x-skip-prompt": true
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
required: []
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// packages/ai-toolkit-nx-claude/src/generators/addons/generator.ts
|
|
95
|
+
var generator_exports = {};
|
|
96
|
+
__export(generator_exports, {
|
|
97
|
+
default: () => generator
|
|
98
|
+
});
|
|
99
|
+
module.exports = __toCommonJS(generator_exports);
|
|
100
|
+
var import_devkit = require("@nx/devkit");
|
|
101
|
+
|
|
102
|
+
// packages/ai-toolkit-nx-claude/src/utils/prompt-utils.ts
|
|
103
|
+
var import_enquirer = require("enquirer");
|
|
104
|
+
var fs = __toESM(require("fs"));
|
|
105
|
+
async function promptForMissingOptions(options, schemaPath, context = {}, explicitlyProvidedOptions) {
|
|
106
|
+
let schema;
|
|
107
|
+
if (typeof schemaPath === "string") {
|
|
108
|
+
const schemaContent = fs.readFileSync(schemaPath, "utf-8");
|
|
109
|
+
schema = JSON.parse(schemaContent);
|
|
110
|
+
} else {
|
|
111
|
+
schema = schemaPath;
|
|
112
|
+
}
|
|
113
|
+
const result = { ...options };
|
|
114
|
+
if (result["no-interactive"] || result.noInteractive || result["non-interactive"] || result.nonInteractive) {
|
|
115
|
+
for (const [key, property] of Object.entries(schema.properties)) {
|
|
116
|
+
if (result[key] === void 0 && property.default !== void 0) {
|
|
117
|
+
result[key] = property.default;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return result;
|
|
121
|
+
}
|
|
122
|
+
for (const [key, property] of Object.entries(schema.properties)) {
|
|
123
|
+
let wasExplicitlyProvided = false;
|
|
124
|
+
if (explicitlyProvidedOptions) {
|
|
125
|
+
if (explicitlyProvidedOptions instanceof Map) {
|
|
126
|
+
wasExplicitlyProvided = explicitlyProvidedOptions.has(key) || explicitlyProvidedOptions.has(key.replace(/-/g, ""));
|
|
127
|
+
} else {
|
|
128
|
+
wasExplicitlyProvided = explicitlyProvidedOptions.has(key) || explicitlyProvidedOptions.has(key.replace(/-/g, ""));
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (property["x-skip-prompt"]) {
|
|
132
|
+
if (result[key] === void 0 && property.default !== void 0) {
|
|
133
|
+
result[key] = property.default;
|
|
134
|
+
}
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const shouldPrompt = property["always-prompt"] ? (
|
|
138
|
+
// For always-prompt fields, only skip if value was explicitly provided
|
|
139
|
+
!wasExplicitlyProvided
|
|
140
|
+
) : (
|
|
141
|
+
// For regular fields (backward compatibility with x-prompt),
|
|
142
|
+
// skip if value exists at all (provided or defaulted)
|
|
143
|
+
(result[key] === void 0 || result[key] === null) && !wasExplicitlyProvided
|
|
144
|
+
);
|
|
145
|
+
if (!shouldPrompt) {
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
if (key === "confirmLocalPath") {
|
|
149
|
+
if (result.installationType !== "local") {
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (key === "nonInteractive" || key === "non-interactive" || key === "no-interactive" || key === "noInteractive") {
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
if (key === "force") {
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
if (property["prompt-when"]) {
|
|
160
|
+
const [field, operator, value] = property["prompt-when"].split(" ");
|
|
161
|
+
if (operator === "===" && result[field] !== value.replace(/['"]/g, "")) {
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
const promptResult = await promptForProperty(
|
|
166
|
+
key,
|
|
167
|
+
property,
|
|
168
|
+
context,
|
|
169
|
+
result
|
|
170
|
+
);
|
|
171
|
+
if (promptResult !== void 0) {
|
|
172
|
+
result[key] = promptResult;
|
|
173
|
+
if (key === "confirmLocalPath" && result.installationType === "local" && promptResult === false) {
|
|
174
|
+
throw new Error(
|
|
175
|
+
"Installation cancelled - please run from your project root"
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return result;
|
|
181
|
+
}
|
|
182
|
+
async function promptForProperty(key, property, context, currentValues) {
|
|
183
|
+
const promptMessage = getPromptMessage(key, property);
|
|
184
|
+
const promptType = property["prompt-type"] || (property.type === "boolean" ? "confirm" : property.enum ? "select" : property.type === "string" ? "input" : null);
|
|
185
|
+
if (promptType === "confirm" || property.type === "boolean") {
|
|
186
|
+
const { value } = await (0, import_enquirer.prompt)({
|
|
187
|
+
type: "confirm",
|
|
188
|
+
name: "value",
|
|
189
|
+
message: promptMessage,
|
|
190
|
+
initial: property.default ?? false
|
|
191
|
+
});
|
|
192
|
+
return value;
|
|
193
|
+
}
|
|
194
|
+
if (promptType === "list" && property["prompt-items"]) {
|
|
195
|
+
const { value } = await (0, import_enquirer.prompt)({
|
|
196
|
+
type: "select",
|
|
197
|
+
name: "value",
|
|
198
|
+
message: promptMessage,
|
|
199
|
+
choices: property["prompt-items"].map((item) => ({
|
|
200
|
+
name: item.value,
|
|
201
|
+
value: item.value,
|
|
202
|
+
message: item.label
|
|
203
|
+
}))
|
|
204
|
+
});
|
|
205
|
+
return value;
|
|
206
|
+
}
|
|
207
|
+
if (property.enum || key === "package" && context.availablePackages) {
|
|
208
|
+
const choices = property.enum || context.availablePackages || [];
|
|
209
|
+
const { value } = await (0, import_enquirer.prompt)({
|
|
210
|
+
type: "select",
|
|
211
|
+
name: "value",
|
|
212
|
+
message: promptMessage,
|
|
213
|
+
choices: choices.map((choice) => {
|
|
214
|
+
if (key === "package" && choice === "__create_new__") {
|
|
215
|
+
return {
|
|
216
|
+
name: "__create_new__",
|
|
217
|
+
value: "__create_new__",
|
|
218
|
+
message: "\u2795 Create new package"
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
if (key === "package") {
|
|
222
|
+
return { name: choice, value: choice, message: `\u{1F4E6} ${choice}` };
|
|
223
|
+
}
|
|
224
|
+
return { name: choice, value: choice };
|
|
225
|
+
})
|
|
226
|
+
});
|
|
227
|
+
return value;
|
|
228
|
+
}
|
|
229
|
+
if (property.type === "array") {
|
|
230
|
+
if (key === "commands" && context.availableCommands) {
|
|
231
|
+
const installationType = currentValues?.installationType;
|
|
232
|
+
let existingSet;
|
|
233
|
+
let otherLocationSet;
|
|
234
|
+
if (installationType === "global") {
|
|
235
|
+
existingSet = context.globalExistingCommands;
|
|
236
|
+
otherLocationSet = context.localExistingCommands;
|
|
237
|
+
} else if (installationType === "local") {
|
|
238
|
+
existingSet = context.localExistingCommands;
|
|
239
|
+
otherLocationSet = context.globalExistingCommands;
|
|
240
|
+
}
|
|
241
|
+
return await promptMultiSelectWithAll(
|
|
242
|
+
promptMessage,
|
|
243
|
+
context.availableCommands,
|
|
244
|
+
"commands",
|
|
245
|
+
context.commandDescriptions,
|
|
246
|
+
existingSet,
|
|
247
|
+
otherLocationSet,
|
|
248
|
+
installationType
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
if (key === "agents" && context.availableAgents) {
|
|
252
|
+
const installationType = currentValues?.installationType;
|
|
253
|
+
let existingSet;
|
|
254
|
+
let otherLocationSet;
|
|
255
|
+
if (installationType === "global") {
|
|
256
|
+
existingSet = context.globalExistingAgents;
|
|
257
|
+
otherLocationSet = context.localExistingAgents;
|
|
258
|
+
} else if (installationType === "local") {
|
|
259
|
+
existingSet = context.localExistingAgents;
|
|
260
|
+
otherLocationSet = context.globalExistingAgents;
|
|
261
|
+
}
|
|
262
|
+
return await promptMultiSelectWithAll(
|
|
263
|
+
promptMessage,
|
|
264
|
+
context.availableAgents,
|
|
265
|
+
"agents",
|
|
266
|
+
context.agentDescriptions,
|
|
267
|
+
existingSet,
|
|
268
|
+
otherLocationSet,
|
|
269
|
+
installationType
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
return [];
|
|
273
|
+
}
|
|
274
|
+
if (property.type === "string") {
|
|
275
|
+
if (key === "targetPath") {
|
|
276
|
+
const { value: value2 } = await (0, import_enquirer.prompt)({
|
|
277
|
+
type: "input",
|
|
278
|
+
name: "value",
|
|
279
|
+
message: promptMessage,
|
|
280
|
+
initial: process.cwd()
|
|
281
|
+
});
|
|
282
|
+
return value2;
|
|
283
|
+
}
|
|
284
|
+
const { value } = await (0, import_enquirer.prompt)({
|
|
285
|
+
type: "input",
|
|
286
|
+
name: "value",
|
|
287
|
+
message: promptMessage,
|
|
288
|
+
initial: property.default ?? ""
|
|
289
|
+
});
|
|
290
|
+
return value;
|
|
291
|
+
}
|
|
292
|
+
return void 0;
|
|
293
|
+
}
|
|
294
|
+
async function promptMultiSelectWithAll(message, choices, type, descriptions, existingItems, otherLocationItems, installationType) {
|
|
295
|
+
const displayChoices = choices.map((choice) => {
|
|
296
|
+
let display = descriptions?.[choice] ? `${choice}: ${descriptions[choice]}` : choice;
|
|
297
|
+
const indicators = [];
|
|
298
|
+
if (existingItems?.has(choice)) {
|
|
299
|
+
indicators.push("will overwrite");
|
|
300
|
+
}
|
|
301
|
+
if (otherLocationItems?.has(choice)) {
|
|
302
|
+
const otherLocation = installationType === "global" ? "locally" : "globally";
|
|
303
|
+
indicators.push(`exists ${otherLocation}`);
|
|
304
|
+
}
|
|
305
|
+
if (indicators.length > 0) {
|
|
306
|
+
display += ` (${indicators.join(", ")})`;
|
|
307
|
+
}
|
|
308
|
+
return display;
|
|
309
|
+
});
|
|
310
|
+
const response = await (0, import_enquirer.prompt)({
|
|
311
|
+
type: "multiselect",
|
|
312
|
+
name: "selected",
|
|
313
|
+
message,
|
|
314
|
+
choices: displayChoices,
|
|
315
|
+
initial: displayChoices.map((_, index) => index),
|
|
316
|
+
// Select all by default
|
|
317
|
+
hint: "Use <space> to select, <a> to toggle all, <return> to submit",
|
|
318
|
+
validate: (value) => {
|
|
319
|
+
if (value.length === 0) {
|
|
320
|
+
return `Please select at least one ${type.slice(0, -1)}`;
|
|
321
|
+
}
|
|
322
|
+
return true;
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
const selected = response.selected || [];
|
|
326
|
+
const actualSelections = [];
|
|
327
|
+
for (const selection of selected) {
|
|
328
|
+
const colonIndex = selection.indexOf(":");
|
|
329
|
+
const parenIndex = selection.indexOf("(");
|
|
330
|
+
let endIndex = selection.length;
|
|
331
|
+
if (colonIndex > -1 && (parenIndex === -1 || colonIndex < parenIndex)) {
|
|
332
|
+
endIndex = colonIndex;
|
|
333
|
+
} else if (parenIndex > -1 && (colonIndex === -1 || parenIndex < colonIndex)) {
|
|
334
|
+
endIndex = parenIndex;
|
|
335
|
+
}
|
|
336
|
+
const name = selection.substring(0, endIndex).trim();
|
|
337
|
+
if (name && !actualSelections.includes(name)) {
|
|
338
|
+
actualSelections.push(name);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return actualSelections;
|
|
342
|
+
}
|
|
343
|
+
function getPromptMessage(key, property) {
|
|
344
|
+
if (property["prompt-message"]) {
|
|
345
|
+
return property["prompt-message"];
|
|
346
|
+
}
|
|
347
|
+
if (property["x-prompt"]) {
|
|
348
|
+
if (typeof property["x-prompt"] === "string") {
|
|
349
|
+
return property["x-prompt"];
|
|
350
|
+
}
|
|
351
|
+
if (property["x-prompt"].message) {
|
|
352
|
+
return property["x-prompt"].message;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
const description = property.description || "";
|
|
356
|
+
switch (key) {
|
|
357
|
+
case "installationType":
|
|
358
|
+
return "Would you like to install agents and commands globally or locally?";
|
|
359
|
+
case "confirmLocalPath":
|
|
360
|
+
return "Are you running this from the root of your project?";
|
|
361
|
+
case "commands":
|
|
362
|
+
return "Select commands to install (use <space> to select, <a> to toggle all):";
|
|
363
|
+
case "agents":
|
|
364
|
+
return "Select agents to install (use <space> to select, <a> to toggle all):";
|
|
365
|
+
case "force":
|
|
366
|
+
return "Overwrite existing installation?";
|
|
367
|
+
default:
|
|
368
|
+
return description || `Enter value for ${key}:`;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// packages/ai-toolkit-nx-claude/src/utils/cli-parser.ts
|
|
373
|
+
function isNxDryRunProvided() {
|
|
374
|
+
const args = process.argv;
|
|
375
|
+
return (
|
|
376
|
+
// Check for --dry-run
|
|
377
|
+
args.some((a) => a === "--dry-run") || args.some((a) => a.startsWith("--dry-run=") && !a.endsWith("=false")) || // Check for --dryRun
|
|
378
|
+
args.some((a) => a === "--dryRun") || args.some((a) => a.startsWith("--dryRun=") && !a.endsWith("=false")) || // Check for short form -d
|
|
379
|
+
args.some((a) => a === "-d")
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
function isNxNoInteractiveProvided() {
|
|
383
|
+
const args = process.argv;
|
|
384
|
+
return (
|
|
385
|
+
// Check for --no-interactive
|
|
386
|
+
args.some((a) => a === "--no-interactive") || args.some(
|
|
387
|
+
(a) => a.startsWith("--no-interactive=") && !a.endsWith("=false")
|
|
388
|
+
) || // Check for --noInteractive
|
|
389
|
+
args.some((a) => a === "--noInteractive") || args.some((a) => a.startsWith("--noInteractive=") && !a.endsWith("=false"))
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// packages/ai-toolkit-nx-claude/src/generators/addons/addon-registry.ts
|
|
394
|
+
var import_fs = require("fs");
|
|
395
|
+
var import_path = require("path");
|
|
396
|
+
var import_os = require("os");
|
|
397
|
+
var ADDON_REGISTRY = [
|
|
398
|
+
{
|
|
399
|
+
id: "spec-workflow-mcp",
|
|
400
|
+
name: "Spec Workflow MCP",
|
|
401
|
+
description: "MCP server for spec-driven development workflow with dashboard support",
|
|
402
|
+
type: "mcp-server",
|
|
403
|
+
packageName: "@uniswap/spec-workflow-mcp",
|
|
404
|
+
registry: "--@uniswap:registry=https://npm.pkg.github.com",
|
|
405
|
+
requiresAuth: true,
|
|
406
|
+
mcp: {
|
|
407
|
+
serverName: "spec-workflow",
|
|
408
|
+
command: "npx",
|
|
409
|
+
args: ["@uniswap/spec-workflow-mcp@latest"],
|
|
410
|
+
supportsDashboard: true,
|
|
411
|
+
defaultPort: 50014
|
|
412
|
+
},
|
|
413
|
+
projectSetup: {
|
|
414
|
+
repositoryUrl: "https://github.com/Uniswap/spec-workflow-mcp.git",
|
|
415
|
+
configSourcePath: "configs",
|
|
416
|
+
targetDirectory: ".spec-workflow",
|
|
417
|
+
requiresAuth: false
|
|
418
|
+
},
|
|
419
|
+
requirements: {
|
|
420
|
+
node: ">=22.0.0",
|
|
421
|
+
commands: ["git", "npm"]
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
// Future addons can be added here
|
|
425
|
+
];
|
|
426
|
+
function getAddonById(id) {
|
|
427
|
+
return ADDON_REGISTRY.find((addon) => addon.id === id);
|
|
428
|
+
}
|
|
429
|
+
async function isAddonInstalled(addonId) {
|
|
430
|
+
const addon = getAddonById(addonId);
|
|
431
|
+
if (!addon) {
|
|
432
|
+
return false;
|
|
433
|
+
}
|
|
434
|
+
const configs = [
|
|
435
|
+
(0, import_path.join)((0, import_os.homedir)(), ".claude", "config.json"),
|
|
436
|
+
(0, import_path.join)(process.cwd(), ".claude", "config.json")
|
|
437
|
+
];
|
|
438
|
+
for (const configPath of configs) {
|
|
439
|
+
if ((0, import_fs.existsSync)(configPath)) {
|
|
440
|
+
try {
|
|
441
|
+
const config = require(configPath);
|
|
442
|
+
if (addon.type === "mcp-server" && config.mcpServers) {
|
|
443
|
+
for (const [, serverConfig] of Object.entries(config.mcpServers)) {
|
|
444
|
+
if (serverConfig && typeof serverConfig === "object" && "command" in serverConfig) {
|
|
445
|
+
const command = serverConfig.command;
|
|
446
|
+
const args = serverConfig.args || [];
|
|
447
|
+
if (command === addon.mcp?.command && args.some((arg) => arg.includes(addon.packageName))) {
|
|
448
|
+
return true;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
} catch {
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
return false;
|
|
459
|
+
}
|
|
460
|
+
async function validateAddonRequirements(addonId) {
|
|
461
|
+
const addon = getAddonById(addonId);
|
|
462
|
+
if (!addon) {
|
|
463
|
+
return { valid: false, errors: [`Unknown addon: ${addonId}`] };
|
|
464
|
+
}
|
|
465
|
+
const errors = [];
|
|
466
|
+
if (addon.requirements?.node) {
|
|
467
|
+
const nodeVersion = process.version;
|
|
468
|
+
if (!nodeVersion.match(/v1[89]\.\d+\.\d+/) && !nodeVersion.match(/v2\d+\.\d+\.\d+/)) {
|
|
469
|
+
errors.push(
|
|
470
|
+
`Node.js ${addon.requirements.node} required, found ${nodeVersion}`
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
if (addon.requirements?.commands) {
|
|
475
|
+
const { execSync: execSync4 } = require("child_process");
|
|
476
|
+
for (const cmd of addon.requirements.commands) {
|
|
477
|
+
try {
|
|
478
|
+
execSync4(`which ${cmd}`, { stdio: "ignore" });
|
|
479
|
+
} catch {
|
|
480
|
+
errors.push(`Required command '${cmd}' not found`);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
return {
|
|
485
|
+
valid: errors.length === 0,
|
|
486
|
+
errors
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// packages/ai-toolkit-nx-claude/src/generators/addons/github-auth.ts
|
|
491
|
+
var import_child_process = require("child_process");
|
|
492
|
+
var import_fs2 = require("fs");
|
|
493
|
+
var import_path2 = require("path");
|
|
494
|
+
var import_os2 = require("os");
|
|
495
|
+
async function checkGitHubAuth() {
|
|
496
|
+
if (process.env.NPM_TOKEN || process.env.GITHUB_TOKEN) {
|
|
497
|
+
const token = process.env.NPM_TOKEN || process.env.GITHUB_TOKEN;
|
|
498
|
+
const valid = await validateToken(token);
|
|
499
|
+
return {
|
|
500
|
+
authenticated: true,
|
|
501
|
+
method: "env",
|
|
502
|
+
registry: "https://npm.pkg.github.com",
|
|
503
|
+
valid
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
const npmrcPaths = [(0, import_path2.join)((0, import_os2.homedir)(), ".npmrc"), (0, import_path2.join)(process.cwd(), ".npmrc")];
|
|
507
|
+
for (const npmrcPath of npmrcPaths) {
|
|
508
|
+
if ((0, import_fs2.existsSync)(npmrcPath)) {
|
|
509
|
+
const content = (0, import_fs2.readFileSync)(npmrcPath, "utf-8");
|
|
510
|
+
if (content.includes("//npm.pkg.github.com/:_authToken=")) {
|
|
511
|
+
const tokenMatch = content.match(
|
|
512
|
+
/\/\/npm\.pkg\.github\.com\/:_authToken=(.+)/
|
|
513
|
+
);
|
|
514
|
+
if (tokenMatch) {
|
|
515
|
+
const token = tokenMatch[1].trim();
|
|
516
|
+
const valid = await validateToken(token);
|
|
517
|
+
return {
|
|
518
|
+
authenticated: true,
|
|
519
|
+
method: "npmrc",
|
|
520
|
+
registry: "https://npm.pkg.github.com",
|
|
521
|
+
valid
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
return {
|
|
528
|
+
authenticated: false,
|
|
529
|
+
method: "none"
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
async function validateToken(token) {
|
|
533
|
+
try {
|
|
534
|
+
const result = (0, import_child_process.execSync)(
|
|
535
|
+
`npm view @uniswap/ai-toolkit-nx-claude --@uniswap:registry=https://npm.pkg.github.com --//npm.pkg.github.com/:_authToken=${token} --json`,
|
|
536
|
+
{
|
|
537
|
+
encoding: "utf-8",
|
|
538
|
+
stdio: "pipe"
|
|
539
|
+
}
|
|
540
|
+
);
|
|
541
|
+
return result.includes('"name"');
|
|
542
|
+
} catch {
|
|
543
|
+
return false;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
async function setupGitHubAuth(token, location = "global") {
|
|
547
|
+
const valid = await validateToken(token);
|
|
548
|
+
if (!valid) {
|
|
549
|
+
return {
|
|
550
|
+
authenticated: false,
|
|
551
|
+
method: "none",
|
|
552
|
+
error: "Invalid GitHub token. Please ensure you have a valid personal access token with read:packages scope."
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
const npmrcPath = location === "global" ? (0, import_path2.join)((0, import_os2.homedir)(), ".npmrc") : (0, import_path2.join)(process.cwd(), ".npmrc");
|
|
556
|
+
const registryLines = [`//npm.pkg.github.com/:_authToken=${token}`];
|
|
557
|
+
let npmrcContent = "";
|
|
558
|
+
if ((0, import_fs2.existsSync)(npmrcPath)) {
|
|
559
|
+
npmrcContent = (0, import_fs2.readFileSync)(npmrcPath, "utf-8");
|
|
560
|
+
const hasToken = npmrcContent.includes("//npm.pkg.github.com/:_authToken=");
|
|
561
|
+
if (hasToken) {
|
|
562
|
+
npmrcContent = npmrcContent.replace(
|
|
563
|
+
/\/\/npm\.pkg\.github\.com\/:_authToken=.*/,
|
|
564
|
+
`//npm.pkg.github.com/:_authToken=${token}`
|
|
565
|
+
);
|
|
566
|
+
} else {
|
|
567
|
+
if (!hasToken) {
|
|
568
|
+
npmrcContent += "\n" + registryLines[1];
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
} else {
|
|
572
|
+
npmrcContent = registryLines.join("\n") + "\n";
|
|
573
|
+
}
|
|
574
|
+
(0, import_fs2.writeFileSync)(npmrcPath, npmrcContent);
|
|
575
|
+
return {
|
|
576
|
+
authenticated: true,
|
|
577
|
+
method: "npmrc",
|
|
578
|
+
registry: "https://npm.pkg.github.com",
|
|
579
|
+
valid: true
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
async function validatePackageAccess(packageName = "@uniswap/spec-workflow-mcp") {
|
|
583
|
+
try {
|
|
584
|
+
const authStatus = await checkGitHubAuth();
|
|
585
|
+
if (!authStatus.authenticated) {
|
|
586
|
+
return {
|
|
587
|
+
accessible: false,
|
|
588
|
+
error: "No GitHub authentication configured. Please provide a personal access token."
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
let command = `npm view ${packageName} version --@uniswap:registry=https://npm.pkg.github.com`;
|
|
592
|
+
if (authStatus.method === "env" && (process.env.NPM_TOKEN || process.env.GITHUB_TOKEN)) {
|
|
593
|
+
const token = process.env.NPM_TOKEN || process.env.GITHUB_TOKEN;
|
|
594
|
+
command += ` --//npm.pkg.github.com/:_authToken=${token}`;
|
|
595
|
+
}
|
|
596
|
+
const version = (0, import_child_process.execSync)(command, {
|
|
597
|
+
encoding: "utf-8",
|
|
598
|
+
stdio: "pipe"
|
|
599
|
+
}).trim();
|
|
600
|
+
return {
|
|
601
|
+
accessible: true,
|
|
602
|
+
version
|
|
603
|
+
};
|
|
604
|
+
} catch (error) {
|
|
605
|
+
let errorMessage = "Failed to access package";
|
|
606
|
+
if (error.message?.includes("404")) {
|
|
607
|
+
errorMessage = `Package ${packageName} not found. Ensure you have access to the Uniswap organization.`;
|
|
608
|
+
} else if (error.message?.includes("401") || error.message?.includes("403")) {
|
|
609
|
+
errorMessage = "Authentication failed. Please check your GitHub token has read:packages scope.";
|
|
610
|
+
} else if (error.message?.includes("ENOTFOUND")) {
|
|
611
|
+
errorMessage = "Network error. Please check your internet connection.";
|
|
612
|
+
}
|
|
613
|
+
return {
|
|
614
|
+
accessible: false,
|
|
615
|
+
error: errorMessage
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
function getAuthInstructions() {
|
|
620
|
+
return `
|
|
621
|
+
To access @uniswap packages from GitHub Packages, you need a personal access token:
|
|
622
|
+
|
|
623
|
+
1. Go to https://github.com/settings/tokens/new
|
|
624
|
+
2. Create a token with 'read:packages' scope
|
|
625
|
+
3. Copy the generated token
|
|
626
|
+
|
|
627
|
+
The token will be saved to your ~/.npmrc file for future use.
|
|
628
|
+
|
|
629
|
+
For more information, see: https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-npm-registry
|
|
630
|
+
`.trim();
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// packages/ai-toolkit-nx-claude/src/generators/addons/claude-mcp-installer.ts
|
|
634
|
+
var import_child_process2 = require("child_process");
|
|
635
|
+
var import_fs3 = require("fs");
|
|
636
|
+
var import_path3 = require("path");
|
|
637
|
+
var import_os3 = require("os");
|
|
638
|
+
async function installMcpServer(options) {
|
|
639
|
+
const { addon, additionalArgs = [], dryRun = false } = options;
|
|
640
|
+
try {
|
|
641
|
+
(0, import_child_process2.execSync)("claude --version", { stdio: "ignore" });
|
|
642
|
+
} catch {
|
|
643
|
+
return {
|
|
644
|
+
success: false,
|
|
645
|
+
message: "Claude CLI not found",
|
|
646
|
+
error: "Please install Claude CLI first. Visit https://claude.ai/download"
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
const serverName = addon.mcp?.serverName || addon.id;
|
|
650
|
+
let command = `claude mcp add ${serverName} --scope user --`;
|
|
651
|
+
if (addon.mcp?.command) {
|
|
652
|
+
command += ` ${addon.mcp.command}`;
|
|
653
|
+
if (addon.mcp.command === "npx") {
|
|
654
|
+
command += " -y";
|
|
655
|
+
}
|
|
656
|
+
if (addon.registry) {
|
|
657
|
+
command += ` ${addon.registry}`;
|
|
658
|
+
}
|
|
659
|
+
const packageSpec = addon.mcp.args?.[0] || `${addon.packageName}@latest`;
|
|
660
|
+
command += ` ${packageSpec}`;
|
|
661
|
+
}
|
|
662
|
+
if (additionalArgs.length > 0) {
|
|
663
|
+
command += ` ${additionalArgs.join(" ")}`;
|
|
664
|
+
}
|
|
665
|
+
if (dryRun) {
|
|
666
|
+
console.log("\n\u{1F4CB} Would execute:");
|
|
667
|
+
console.log(command);
|
|
668
|
+
return {
|
|
669
|
+
success: true,
|
|
670
|
+
message: "Dry-run completed"
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
let attempts = 0;
|
|
674
|
+
const maxAttempts = 3;
|
|
675
|
+
let lastError;
|
|
676
|
+
while (attempts < maxAttempts) {
|
|
677
|
+
attempts++;
|
|
678
|
+
try {
|
|
679
|
+
console.log(
|
|
680
|
+
`
|
|
681
|
+
\u{1F527} Installing MCP server (attempt ${attempts}/${maxAttempts})...`
|
|
682
|
+
);
|
|
683
|
+
const output = (0, import_child_process2.execSync)(command, {
|
|
684
|
+
encoding: "utf-8",
|
|
685
|
+
stdio: "pipe"
|
|
686
|
+
});
|
|
687
|
+
if (output.includes("successfully") || output.includes("added")) {
|
|
688
|
+
return {
|
|
689
|
+
success: true,
|
|
690
|
+
message: "MCP server installed successfully"
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
console.log("Output:", output);
|
|
694
|
+
} catch (error) {
|
|
695
|
+
lastError = error;
|
|
696
|
+
if (error.message?.includes("already exists")) {
|
|
697
|
+
return {
|
|
698
|
+
success: true,
|
|
699
|
+
message: "MCP server already installed",
|
|
700
|
+
error: "Use --force to overwrite existing installation"
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
if (error.message?.includes("permission denied")) {
|
|
704
|
+
return {
|
|
705
|
+
success: false,
|
|
706
|
+
message: "Permission denied",
|
|
707
|
+
error: "Try running with administrator privileges"
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
if (error.message?.includes("network") || error.message?.includes("ENOTFOUND")) {
|
|
711
|
+
if (attempts < maxAttempts) {
|
|
712
|
+
console.log("Network error, retrying...");
|
|
713
|
+
await new Promise((resolve) => setTimeout(resolve, 2e3));
|
|
714
|
+
continue;
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
break;
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
return {
|
|
721
|
+
success: false,
|
|
722
|
+
message: "Failed to install MCP server",
|
|
723
|
+
error: lastError?.message || "Unknown error occurred"
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
async function verifyMcpInstallation(serverName) {
|
|
727
|
+
const configPaths = [
|
|
728
|
+
(0, import_path3.join)((0, import_os3.homedir)(), ".claude", "config.json"),
|
|
729
|
+
(0, import_path3.join)(process.cwd(), ".claude", "config.json")
|
|
730
|
+
];
|
|
731
|
+
for (const configPath of configPaths) {
|
|
732
|
+
if ((0, import_fs3.existsSync)(configPath)) {
|
|
733
|
+
try {
|
|
734
|
+
const config = JSON.parse((0, import_fs3.readFileSync)(configPath, "utf-8"));
|
|
735
|
+
if (config.mcpServers && config.mcpServers[serverName]) {
|
|
736
|
+
return {
|
|
737
|
+
installed: true,
|
|
738
|
+
configured: true,
|
|
739
|
+
configPath,
|
|
740
|
+
serverConfig: config.mcpServers[serverName]
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
} catch {
|
|
744
|
+
continue;
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
return {
|
|
749
|
+
installed: false,
|
|
750
|
+
configured: false
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
async function updateMcpServer(serverName, argUpdates) {
|
|
754
|
+
const verification = await verifyMcpInstallation(serverName);
|
|
755
|
+
if (!verification.installed || !verification.configPath) {
|
|
756
|
+
return {
|
|
757
|
+
success: false,
|
|
758
|
+
message: "MCP server not found. Please install it first."
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
try {
|
|
762
|
+
const config = JSON.parse((0, import_fs3.readFileSync)(verification.configPath, "utf-8"));
|
|
763
|
+
const serverConfig = config.mcpServers[serverName];
|
|
764
|
+
if (serverConfig.args && Array.isArray(serverConfig.args)) {
|
|
765
|
+
if (argUpdates.remove && argUpdates.remove.length > 0) {
|
|
766
|
+
serverConfig.args = serverConfig.args.filter((arg) => {
|
|
767
|
+
return !argUpdates.remove.some((pattern) => arg.startsWith(pattern));
|
|
768
|
+
});
|
|
769
|
+
}
|
|
770
|
+
if (argUpdates.add && argUpdates.add.length > 0) {
|
|
771
|
+
const lastArg = serverConfig.args[serverConfig.args.length - 1];
|
|
772
|
+
serverConfig.args = [
|
|
773
|
+
...serverConfig.args.slice(0, -1),
|
|
774
|
+
...argUpdates.add,
|
|
775
|
+
lastArg
|
|
776
|
+
];
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
const fs2 = require("fs");
|
|
780
|
+
fs2.writeFileSync(verification.configPath, JSON.stringify(config, null, 2));
|
|
781
|
+
return {
|
|
782
|
+
success: true,
|
|
783
|
+
message: "MCP server configuration updated successfully"
|
|
784
|
+
};
|
|
785
|
+
} catch (error) {
|
|
786
|
+
return {
|
|
787
|
+
success: false,
|
|
788
|
+
message: `Failed to update configuration: ${error.message}`
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
// packages/ai-toolkit-nx-claude/src/generators/addons/spec-workflow-setup.ts
|
|
794
|
+
var import_child_process3 = require("child_process");
|
|
795
|
+
var import_fs4 = require("fs");
|
|
796
|
+
var import_path4 = require("path");
|
|
797
|
+
var import_os4 = require("os");
|
|
798
|
+
var import_fs5 = require("fs");
|
|
799
|
+
async function setupSpecWorkflow(projectPath, options) {
|
|
800
|
+
const targetPath = projectPath || process.cwd();
|
|
801
|
+
const targetDir = (0, import_path4.join)(targetPath, ".spec-workflow");
|
|
802
|
+
console.log(`
|
|
803
|
+
\u{1F4C1} Setting up spec-workflow in: ${targetPath}`);
|
|
804
|
+
const isUpdate = (0, import_fs4.existsSync)(targetDir);
|
|
805
|
+
if (options.dryRun) {
|
|
806
|
+
console.log("[DRY-RUN] Would create/update directory: " + targetDir);
|
|
807
|
+
console.log("[DRY-RUN] Would clone repository: @uniswap/spec-workflow-mcp");
|
|
808
|
+
console.log("[DRY-RUN] Would copy/update configuration files");
|
|
809
|
+
console.log("[DRY-RUN] Would add .spec-workflow/ to .gitignore");
|
|
810
|
+
return {
|
|
811
|
+
success: true,
|
|
812
|
+
message: "[DRY-RUN] Spec-workflow setup simulated successfully",
|
|
813
|
+
projectPath: targetPath
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
if (isUpdate) {
|
|
817
|
+
if (!options.force) {
|
|
818
|
+
const { confirm } = await require("enquirer").prompt({
|
|
819
|
+
type: "confirm",
|
|
820
|
+
name: "confirm",
|
|
821
|
+
message: `.spec-workflow directory already exists. Update configuration files from repository?`,
|
|
822
|
+
initial: true
|
|
823
|
+
});
|
|
824
|
+
if (!confirm) {
|
|
825
|
+
return {
|
|
826
|
+
success: false,
|
|
827
|
+
message: "Installation cancelled by user",
|
|
828
|
+
projectPath: targetPath
|
|
829
|
+
};
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
console.log(
|
|
833
|
+
"\u{1F4DD} Updating configuration files in existing .spec-workflow directory..."
|
|
834
|
+
);
|
|
835
|
+
} else {
|
|
836
|
+
console.log("\u{1F4C1} Creating .spec-workflow directory...");
|
|
837
|
+
(0, import_fs4.mkdirSync)(targetDir, { recursive: true });
|
|
838
|
+
}
|
|
839
|
+
const tempDir = (0, import_path4.join)((0, import_os4.tmpdir)(), `spec-workflow-${Date.now()}`);
|
|
840
|
+
console.log("\u{1F504} Cloning spec-workflow repository...");
|
|
841
|
+
const cloneResult = await cloneRepository(
|
|
842
|
+
"https://github.com/Uniswap/spec-workflow-mcp.git",
|
|
843
|
+
tempDir
|
|
844
|
+
);
|
|
845
|
+
if (!cloneResult.success) {
|
|
846
|
+
return {
|
|
847
|
+
success: false,
|
|
848
|
+
message: cloneResult.error || "Failed to clone repository",
|
|
849
|
+
projectPath: targetPath
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
console.log("\u{1F4CB} Copying configuration files...");
|
|
853
|
+
const configSourcePath = (0, import_path4.join)(tempDir, "configs");
|
|
854
|
+
const copyResult = await copyConfigFiles(configSourcePath, targetDir);
|
|
855
|
+
console.log("\u{1F9F9} Cleaning up temporary files...");
|
|
856
|
+
(0, import_fs4.rmSync)(tempDir, { recursive: true, force: true });
|
|
857
|
+
if (!copyResult.success) {
|
|
858
|
+
return {
|
|
859
|
+
success: false,
|
|
860
|
+
message: copyResult.error || "Failed to copy configuration files",
|
|
861
|
+
projectPath: targetPath
|
|
862
|
+
};
|
|
863
|
+
}
|
|
864
|
+
console.log("\u{1F4DD} Updating .gitignore...");
|
|
865
|
+
const gitignoreResult = await updateGitignore(targetPath, ".spec-workflow/");
|
|
866
|
+
if (!gitignoreResult.success) {
|
|
867
|
+
console.warn("\u26A0\uFE0F Warning:", gitignoreResult.message);
|
|
868
|
+
} else {
|
|
869
|
+
console.log("\u2705", gitignoreResult.message);
|
|
870
|
+
}
|
|
871
|
+
return {
|
|
872
|
+
success: true,
|
|
873
|
+
message: isUpdate ? "The spec-workflow configuration has been updated with the latest templates and settings" : "The spec-workflow package has been added to the project with automatic agent orchestration enabled",
|
|
874
|
+
projectPath: targetPath,
|
|
875
|
+
createdFiles: copyResult.copiedFiles
|
|
876
|
+
};
|
|
877
|
+
}
|
|
878
|
+
async function cloneRepository(repositoryUrl, targetDir) {
|
|
879
|
+
try {
|
|
880
|
+
if (!(0, import_fs4.existsSync)(targetDir)) {
|
|
881
|
+
(0, import_fs4.mkdirSync)(targetDir, { recursive: true });
|
|
882
|
+
}
|
|
883
|
+
(0, import_child_process3.execSync)(`git clone --depth 1 "${repositoryUrl}" "${targetDir}"`, {
|
|
884
|
+
stdio: "pipe",
|
|
885
|
+
encoding: "utf8"
|
|
886
|
+
});
|
|
887
|
+
return {
|
|
888
|
+
success: true,
|
|
889
|
+
message: "Repository cloned successfully",
|
|
890
|
+
clonePath: targetDir
|
|
891
|
+
};
|
|
892
|
+
} catch (error) {
|
|
893
|
+
console.error("\u274C Git clone failed:", error.message);
|
|
894
|
+
if (error.message.includes("not found")) {
|
|
895
|
+
return {
|
|
896
|
+
success: false,
|
|
897
|
+
message: "Repository not found",
|
|
898
|
+
error: "The repository URL may be incorrect or you may not have access"
|
|
899
|
+
};
|
|
900
|
+
} else if (error.message.includes("Authentication")) {
|
|
901
|
+
return {
|
|
902
|
+
success: false,
|
|
903
|
+
message: "Authentication failed",
|
|
904
|
+
error: "Please check your git credentials"
|
|
905
|
+
};
|
|
906
|
+
} else if (error.message.includes("network")) {
|
|
907
|
+
return {
|
|
908
|
+
success: false,
|
|
909
|
+
message: "Network error",
|
|
910
|
+
error: "Please check your internet connection"
|
|
911
|
+
};
|
|
912
|
+
}
|
|
913
|
+
return {
|
|
914
|
+
success: false,
|
|
915
|
+
message: "Git clone failed",
|
|
916
|
+
error: error.message
|
|
917
|
+
};
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
async function updateGitignore(projectPath, pattern) {
|
|
921
|
+
const gitignorePath = (0, import_path4.join)(projectPath, ".gitignore");
|
|
922
|
+
try {
|
|
923
|
+
let gitignoreContent = "";
|
|
924
|
+
if ((0, import_fs4.existsSync)(gitignorePath)) {
|
|
925
|
+
gitignoreContent = (0, import_fs4.readFileSync)(gitignorePath, "utf8");
|
|
926
|
+
}
|
|
927
|
+
const lines = gitignoreContent.split("\n");
|
|
928
|
+
const patternExists = lines.some((line) => {
|
|
929
|
+
const trimmedLine = line.trim();
|
|
930
|
+
return trimmedLine === pattern || trimmedLine === pattern.replace(/\/$/, "");
|
|
931
|
+
});
|
|
932
|
+
if (patternExists) {
|
|
933
|
+
return {
|
|
934
|
+
success: true,
|
|
935
|
+
message: `.gitignore already contains ${pattern}`
|
|
936
|
+
};
|
|
937
|
+
}
|
|
938
|
+
const updatedContent = gitignoreContent.trim() === "" ? pattern : `${gitignoreContent.trim()}
|
|
939
|
+
|
|
940
|
+
# Spec workflow configuration
|
|
941
|
+
${pattern}`;
|
|
942
|
+
(0, import_fs4.writeFileSync)(gitignorePath, updatedContent + "\n", "utf8");
|
|
943
|
+
return {
|
|
944
|
+
success: true,
|
|
945
|
+
message: `Added ${pattern} to .gitignore`
|
|
946
|
+
};
|
|
947
|
+
} catch (error) {
|
|
948
|
+
console.error("\u274C Failed to update .gitignore:", error.message);
|
|
949
|
+
return {
|
|
950
|
+
success: false,
|
|
951
|
+
message: `Failed to update .gitignore: ${error.message}`
|
|
952
|
+
};
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
async function copyConfigFiles(sourceDir, targetDir) {
|
|
956
|
+
try {
|
|
957
|
+
let listFiles2 = function(dir, baseDir = dir, sourceBase = sourceDir) {
|
|
958
|
+
const files = fs2.readdirSync(dir);
|
|
959
|
+
for (const file of files) {
|
|
960
|
+
const filePath = (0, import_path4.join)(dir, file);
|
|
961
|
+
const stat = fs2.statSync(filePath);
|
|
962
|
+
if (stat.isDirectory()) {
|
|
963
|
+
listFiles2(filePath, baseDir, sourceBase);
|
|
964
|
+
} else {
|
|
965
|
+
const relativePath = filePath.replace(baseDir + "/", "");
|
|
966
|
+
const sourceFile = (0, import_path4.join)(sourceBase, relativePath);
|
|
967
|
+
if ((0, import_fs4.existsSync)(sourceFile)) {
|
|
968
|
+
copiedFiles.push(relativePath);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
};
|
|
973
|
+
var listFiles = listFiles2;
|
|
974
|
+
if (!(0, import_fs4.existsSync)(sourceDir)) {
|
|
975
|
+
return {
|
|
976
|
+
success: false,
|
|
977
|
+
message: "Source directory not found",
|
|
978
|
+
error: `The configs directory was not found in the cloned repository`
|
|
979
|
+
};
|
|
980
|
+
}
|
|
981
|
+
(0, import_fs5.cpSync)(sourceDir, targetDir, { recursive: true });
|
|
982
|
+
const fs2 = require("fs");
|
|
983
|
+
const copiedFiles = [];
|
|
984
|
+
listFiles2(targetDir, targetDir, sourceDir);
|
|
985
|
+
return {
|
|
986
|
+
success: true,
|
|
987
|
+
message: "Configuration files copied successfully",
|
|
988
|
+
copiedFiles
|
|
989
|
+
};
|
|
990
|
+
} catch (error) {
|
|
991
|
+
console.error("\u274C File copy failed:", error.message);
|
|
992
|
+
return {
|
|
993
|
+
success: false,
|
|
994
|
+
message: "Failed to copy configuration files",
|
|
995
|
+
error: error.message
|
|
996
|
+
};
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
// packages/ai-toolkit-nx-claude/src/generators/addons/generator.ts
|
|
1001
|
+
async function generator(tree, schema) {
|
|
1002
|
+
console.log("\n\u{1F3AF} Claude Code Addons Installer");
|
|
1003
|
+
console.log("================================\n");
|
|
1004
|
+
let projectSetupCompleted = false;
|
|
1005
|
+
const dryRunFlagProvided = isNxDryRunProvided();
|
|
1006
|
+
const noInteractive = isNxNoInteractiveProvided();
|
|
1007
|
+
let isDryRun = dryRunFlagProvided;
|
|
1008
|
+
if (!dryRunFlagProvided && !noInteractive) {
|
|
1009
|
+
const { runDryRun } = await require("enquirer").prompt({
|
|
1010
|
+
type: "confirm",
|
|
1011
|
+
name: "runDryRun",
|
|
1012
|
+
message: "\u{1F50D} Would you like to run in dry-run mode (preview changes without making them)?",
|
|
1013
|
+
initial: false
|
|
1014
|
+
});
|
|
1015
|
+
isDryRun = runDryRun;
|
|
1016
|
+
}
|
|
1017
|
+
if (isDryRun) {
|
|
1018
|
+
console.log("\u{1F50D} Dry-run mode activated\n");
|
|
1019
|
+
}
|
|
1020
|
+
const options = await promptForMissingOptions(
|
|
1021
|
+
schema,
|
|
1022
|
+
require_schema()
|
|
1023
|
+
);
|
|
1024
|
+
options.dryRun = isDryRun;
|
|
1025
|
+
const addon = getAddonById(options.addon || "spec-workflow-mcp");
|
|
1026
|
+
if (!addon) {
|
|
1027
|
+
throw new Error(`Unknown addon: ${options.addon}`);
|
|
1028
|
+
}
|
|
1029
|
+
console.log(`
|
|
1030
|
+
\u{1F4E6} Installing: ${addon.name}`);
|
|
1031
|
+
console.log(` ${addon.description}
|
|
1032
|
+
`);
|
|
1033
|
+
if (!options.force && !options.dryRun) {
|
|
1034
|
+
const installed = await isAddonInstalled(addon.id);
|
|
1035
|
+
if (installed) {
|
|
1036
|
+
console.log("\u2705 Addon is already installed");
|
|
1037
|
+
const { confirm } = await require("enquirer").prompt({
|
|
1038
|
+
type: "confirm",
|
|
1039
|
+
name: "confirm",
|
|
1040
|
+
message: "Would you like to update the configuration?",
|
|
1041
|
+
initial: false
|
|
1042
|
+
});
|
|
1043
|
+
if (confirm) {
|
|
1044
|
+
await updateConfiguration(addon.id, options);
|
|
1045
|
+
}
|
|
1046
|
+
return;
|
|
1047
|
+
}
|
|
1048
|
+
} else if (options.dryRun) {
|
|
1049
|
+
const installed = await isAddonInstalled(addon.id);
|
|
1050
|
+
if (installed && !options.force) {
|
|
1051
|
+
console.log(
|
|
1052
|
+
"\u2139\uFE0F [DRY-RUN] Addon is already installed, would prompt for update"
|
|
1053
|
+
);
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
console.log("\n\u{1F50D} Checking requirements...");
|
|
1057
|
+
const validation = await validateAddonRequirements(addon.id);
|
|
1058
|
+
if (!validation.valid) {
|
|
1059
|
+
console.error("\n\u274C Requirements not met:");
|
|
1060
|
+
validation.errors.forEach((error) => console.error(` \u2022 ${error}`));
|
|
1061
|
+
if (!options.force) {
|
|
1062
|
+
throw new Error(
|
|
1063
|
+
"Installation requirements not met. Use --force to override."
|
|
1064
|
+
);
|
|
1065
|
+
}
|
|
1066
|
+
console.log("\n\u26A0\uFE0F Continuing with --force flag...");
|
|
1067
|
+
}
|
|
1068
|
+
if (addon.requiresAuth) {
|
|
1069
|
+
if (options.dryRun) {
|
|
1070
|
+
console.log("\n\u{1F510} [DRY-RUN] Skipping GitHub authentication check");
|
|
1071
|
+
} else {
|
|
1072
|
+
console.log("\n\u{1F510} Checking GitHub authentication...");
|
|
1073
|
+
const authStatus = await checkGitHubAuth();
|
|
1074
|
+
if (!authStatus.authenticated || !authStatus.valid) {
|
|
1075
|
+
console.log(
|
|
1076
|
+
"\n\u{1F4DD} GitHub authentication required for private @uniswap packages"
|
|
1077
|
+
);
|
|
1078
|
+
console.log(getAuthInstructions());
|
|
1079
|
+
if (!process.env.CI && !options.githubToken) {
|
|
1080
|
+
const { token } = await require("enquirer").prompt({
|
|
1081
|
+
type: "password",
|
|
1082
|
+
name: "token",
|
|
1083
|
+
message: "GitHub Personal Access Token:",
|
|
1084
|
+
validate: (value) => value.length > 0
|
|
1085
|
+
});
|
|
1086
|
+
options.githubToken = token;
|
|
1087
|
+
}
|
|
1088
|
+
if (options.githubToken) {
|
|
1089
|
+
console.log("\n\u{1F527} Setting up GitHub authentication...");
|
|
1090
|
+
const setupResult = await setupGitHubAuth(options.githubToken);
|
|
1091
|
+
if (!setupResult.valid) {
|
|
1092
|
+
throw new Error(
|
|
1093
|
+
setupResult.error || "Failed to setup GitHub authentication"
|
|
1094
|
+
);
|
|
1095
|
+
}
|
|
1096
|
+
console.log("\u2705 Authentication configured successfully");
|
|
1097
|
+
} else {
|
|
1098
|
+
throw new Error(
|
|
1099
|
+
"GitHub authentication required. Please provide a token."
|
|
1100
|
+
);
|
|
1101
|
+
}
|
|
1102
|
+
} else {
|
|
1103
|
+
console.log("\u2705 GitHub authentication found");
|
|
1104
|
+
}
|
|
1105
|
+
console.log(`
|
|
1106
|
+
\u{1F50D} Validating access to ${addon.packageName}...`);
|
|
1107
|
+
const accessResult = await validatePackageAccess(addon.packageName);
|
|
1108
|
+
if (!accessResult.accessible) {
|
|
1109
|
+
throw new Error(accessResult.error || "Cannot access package");
|
|
1110
|
+
}
|
|
1111
|
+
console.log(`\u2705 Package accessible (version: ${accessResult.version})`);
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
if (addon.type === "mcp-server") {
|
|
1115
|
+
await installMcpAddon(addon, options);
|
|
1116
|
+
if (addon.projectSetup) {
|
|
1117
|
+
const { setupProject } = await require("enquirer").prompt({
|
|
1118
|
+
type: "confirm",
|
|
1119
|
+
name: "setupProject",
|
|
1120
|
+
message: "\u{1F4C1} Would you like to set up spec-workflow configuration for a particular project?",
|
|
1121
|
+
initial: true
|
|
1122
|
+
});
|
|
1123
|
+
if (setupProject) {
|
|
1124
|
+
const { projectPath } = await require("enquirer").prompt({
|
|
1125
|
+
type: "input",
|
|
1126
|
+
name: "projectPath",
|
|
1127
|
+
message: "\u{1F4C1} Enter the project path where spec-workflow config should be added:",
|
|
1128
|
+
initial: process.cwd(),
|
|
1129
|
+
result: (value) => value || process.cwd()
|
|
1130
|
+
});
|
|
1131
|
+
options.projectPath = projectPath;
|
|
1132
|
+
if (options.dryRun) {
|
|
1133
|
+
console.log(
|
|
1134
|
+
`
|
|
1135
|
+
\u{1F4C1} [DRY-RUN] Would set up project configuration at: ${projectPath}`
|
|
1136
|
+
);
|
|
1137
|
+
}
|
|
1138
|
+
await installProjectSetup(addon, options);
|
|
1139
|
+
projectSetupCompleted = true;
|
|
1140
|
+
} else if (options.dryRun) {
|
|
1141
|
+
console.log(
|
|
1142
|
+
"\n\u{1F4C1} [DRY-RUN] Skipping project configuration (user chose not to set up)"
|
|
1143
|
+
);
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
} else {
|
|
1147
|
+
throw new Error(`Addon type '${addon.type}' is not yet supported`);
|
|
1148
|
+
}
|
|
1149
|
+
if (!options.skipVerification && !options.dryRun) {
|
|
1150
|
+
console.log("\n\u{1F50D} Verifying installation...");
|
|
1151
|
+
const serverName = addon.mcp?.serverName || addon.id;
|
|
1152
|
+
const verification = await verifyMcpInstallation(serverName);
|
|
1153
|
+
if (verification.installed) {
|
|
1154
|
+
console.log("\u2705 Installation verified successfully");
|
|
1155
|
+
showUsageInstructions(addon, options, projectSetupCompleted);
|
|
1156
|
+
} else {
|
|
1157
|
+
console.log("\u26A0\uFE0F Could not verify installation");
|
|
1158
|
+
console.log(" The addon may still work correctly.");
|
|
1159
|
+
showUsageInstructions(addon, options, projectSetupCompleted);
|
|
1160
|
+
}
|
|
1161
|
+
} else if (options.dryRun) {
|
|
1162
|
+
console.log("\n\u{1F50D} [DRY-RUN] Skipping installation verification");
|
|
1163
|
+
showUsageInstructions(addon, options, projectSetupCompleted);
|
|
1164
|
+
}
|
|
1165
|
+
if (options.dryRun) {
|
|
1166
|
+
console.log("\n\u2728 Dry-run complete! No changes were made.\n");
|
|
1167
|
+
} else {
|
|
1168
|
+
console.log("\n\u2728 Installation complete!\n");
|
|
1169
|
+
}
|
|
1170
|
+
await (0, import_devkit.formatFiles)(tree);
|
|
1171
|
+
}
|
|
1172
|
+
async function installMcpAddon(addon, options) {
|
|
1173
|
+
if (options.dryRun) {
|
|
1174
|
+
console.log("\n\u{1F527} [DRY-RUN] Simulating MCP server installation...");
|
|
1175
|
+
} else {
|
|
1176
|
+
console.log("\n\u{1F527} Installing MCP server...");
|
|
1177
|
+
}
|
|
1178
|
+
const additionalArgs = [];
|
|
1179
|
+
if (addon.id === "spec-workflow-mcp") {
|
|
1180
|
+
if (options.dashboardMode === "always") {
|
|
1181
|
+
additionalArgs.push("--AutoStartDashboard");
|
|
1182
|
+
}
|
|
1183
|
+
if (options.port && options.port > 0) {
|
|
1184
|
+
additionalArgs.push(`--Port=${options.port}`);
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
const installResult = await installMcpServer({
|
|
1188
|
+
addon,
|
|
1189
|
+
additionalArgs,
|
|
1190
|
+
dryRun: options.dryRun
|
|
1191
|
+
});
|
|
1192
|
+
if (!installResult.success) {
|
|
1193
|
+
throw new Error(installResult.error || installResult.message);
|
|
1194
|
+
}
|
|
1195
|
+
console.log(`\u2705 ${installResult.message}`);
|
|
1196
|
+
}
|
|
1197
|
+
async function installProjectSetup(addon, options) {
|
|
1198
|
+
console.log("\n\u{1F527} Setting up project configuration...");
|
|
1199
|
+
const projectPath = options.projectPath || process.cwd();
|
|
1200
|
+
console.log(`\u{1F4CD} Using project directory: ${projectPath}`);
|
|
1201
|
+
const result = await setupSpecWorkflow(projectPath, options);
|
|
1202
|
+
if (!result.success) {
|
|
1203
|
+
throw new Error(result.message);
|
|
1204
|
+
}
|
|
1205
|
+
console.log(`\u2705 ${result.message}`);
|
|
1206
|
+
}
|
|
1207
|
+
async function updateConfiguration(addonId, options) {
|
|
1208
|
+
console.log("\n\u{1F527} Updating configuration...");
|
|
1209
|
+
const addon = getAddonById(addonId);
|
|
1210
|
+
if (!addon) {
|
|
1211
|
+
console.error(`\u274C Unknown addon: ${addonId}`);
|
|
1212
|
+
return;
|
|
1213
|
+
}
|
|
1214
|
+
const serverName = addon.mcp?.serverName || addon.id;
|
|
1215
|
+
const argUpdates = {
|
|
1216
|
+
remove: [],
|
|
1217
|
+
add: []
|
|
1218
|
+
};
|
|
1219
|
+
if (addon.id === "spec-workflow-mcp") {
|
|
1220
|
+
if (options.dashboardMode) {
|
|
1221
|
+
argUpdates.remove.push("--AutoStartDashboard");
|
|
1222
|
+
if (options.dashboardMode === "always") {
|
|
1223
|
+
argUpdates.add.push("--AutoStartDashboard");
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
if (options.port !== void 0) {
|
|
1227
|
+
argUpdates.remove.push("--Port=");
|
|
1228
|
+
if (options.port > 0) {
|
|
1229
|
+
argUpdates.add.push(`--Port=${options.port}`);
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
const result = await updateMcpServer(serverName, argUpdates);
|
|
1234
|
+
if (result.success) {
|
|
1235
|
+
console.log(`\u2705 ${result.message}`);
|
|
1236
|
+
} else {
|
|
1237
|
+
console.error(`\u274C ${result.message}`);
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
function showUsageInstructions(addon, options, projectSetupCompleted = false) {
|
|
1241
|
+
console.log("\n\u{1F4DA} Usage Instructions:");
|
|
1242
|
+
console.log("====================\n");
|
|
1243
|
+
if (addon.id === "spec-workflow-mcp") {
|
|
1244
|
+
console.log(
|
|
1245
|
+
"1. Start a new instance of Claude Code to load the new MCP server"
|
|
1246
|
+
);
|
|
1247
|
+
console.log("2. Open your project in Claude Code");
|
|
1248
|
+
if (options.dashboardMode === "always") {
|
|
1249
|
+
console.log("3. The spec-workflow dashboard will start automatically");
|
|
1250
|
+
console.log(
|
|
1251
|
+
` Dashboard URL: http://localhost:${options.port || 50014}`
|
|
1252
|
+
);
|
|
1253
|
+
} else {
|
|
1254
|
+
console.log(
|
|
1255
|
+
"3. Start the dashboard manually with: npx --@uniswap:registry=https://npm.pkg.github.com @uniswap/spec-workflow-mcp@latest --dashboard"
|
|
1256
|
+
);
|
|
1257
|
+
}
|
|
1258
|
+
console.log("\n\u{1F4CB} Available MCP Tools:");
|
|
1259
|
+
console.log(" \u2022 spec-workflow-guide - Get workflow documentation");
|
|
1260
|
+
console.log(" \u2022 create-spec-doc - Create spec documents");
|
|
1261
|
+
console.log(" \u2022 spec-status - Check specification status");
|
|
1262
|
+
console.log(" \u2022 manage-tasks - Manage implementation tasks");
|
|
1263
|
+
console.log(" \u2022 request-approval - Request human approval for documents");
|
|
1264
|
+
console.log(" \u2022 orchestrate-with-agents - Use AI agent orchestration");
|
|
1265
|
+
console.log(" \u2022 And more...");
|
|
1266
|
+
if (addon.projectSetup && projectSetupCompleted) {
|
|
1267
|
+
console.log("\n\u{1F4C1} Project Configuration:");
|
|
1268
|
+
console.log(
|
|
1269
|
+
" \u2705 Spec-workflow configuration has been added to your project"
|
|
1270
|
+
);
|
|
1271
|
+
console.log(" \u2022 .spec-workflow/ - Configuration directory");
|
|
1272
|
+
console.log(
|
|
1273
|
+
" \u2022 .spec-workflow/orchestration.yaml - Agent orchestration config"
|
|
1274
|
+
);
|
|
1275
|
+
console.log("\n\u{1F916} Agent Orchestration:");
|
|
1276
|
+
console.log(" Automatic agent orchestration is ENABLED by default");
|
|
1277
|
+
console.log(" Edit .spec-workflow/orchestration.yaml to customize");
|
|
1278
|
+
}
|
|
1279
|
+
console.log("\n\u{1F680} Quick Start - start a new instance of Claude Code and:");
|
|
1280
|
+
console.log(' 1. Ask Claude: "Show me the spec workflow guide"');
|
|
1281
|
+
console.log(' 2. Ask Claude: "Help me create a new spec for [feature]"');
|
|
1282
|
+
console.log(" 3. Visit the dashboard to monitor progress");
|
|
1283
|
+
console.log(" 4. Start creating specs for your features!");
|
|
1284
|
+
}
|
|
1285
|
+
}
|