@syntesseraai/opencode-feature-factory 0.2.16 ā 0.2.17
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/bin/ff-deploy.js +153 -42
- package/package.json +1 -1
package/bin/ff-deploy.js
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
* Deploys skills and agents to the global OpenCode configuration directory.
|
|
7
7
|
* Run manually with: npx @syntesseraai/opencode-feature-factory install
|
|
8
8
|
* With MCP update: npx @syntesseraai/opencode-feature-factory install --mcp
|
|
9
|
+
*
|
|
10
|
+
* Non-interactive mode: Automatically deploys without prompts when no TTY detected
|
|
9
11
|
*/
|
|
10
12
|
|
|
11
13
|
import { promises as fs } from 'fs';
|
|
@@ -27,29 +29,30 @@ const SOURCE_SKILLS_DIR = join(PACKAGE_ROOT, 'skills');
|
|
|
27
29
|
const SOURCE_AGENTS_DIR = join(PACKAGE_ROOT, 'agents');
|
|
28
30
|
|
|
29
31
|
// Default MCP configuration
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
'
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
Authorization: 'Bearer {env:JINAAI_API_KEY}',
|
|
37
|
-
},
|
|
32
|
+
const DEFAULT_MCP_SERVERS = {
|
|
33
|
+
'jina-ai': {
|
|
34
|
+
type: 'remote',
|
|
35
|
+
url: 'https://mcp.jina.ai/v1',
|
|
36
|
+
headers: {
|
|
37
|
+
Authorization: 'Bearer {env:JINAAI_API_KEY}',
|
|
38
38
|
},
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
},
|
|
39
|
+
},
|
|
40
|
+
gh_grep: {
|
|
41
|
+
type: 'remote',
|
|
42
|
+
url: 'https://mcp.grep.app',
|
|
43
|
+
},
|
|
44
|
+
context7: {
|
|
45
|
+
type: 'remote',
|
|
46
|
+
url: 'https://mcp.context7.com/mcp',
|
|
47
|
+
headers: {
|
|
48
|
+
CONTEXT7_API_KEY: '{env:CONTEXT7_API_KEY}',
|
|
49
49
|
},
|
|
50
50
|
},
|
|
51
51
|
};
|
|
52
52
|
|
|
53
|
+
// Check if running in interactive mode (has TTY)
|
|
54
|
+
const isInteractive = process.stdin.isTTY && process.stdout.isTTY;
|
|
55
|
+
|
|
53
56
|
async function ensureDir(dir) {
|
|
54
57
|
try {
|
|
55
58
|
await fs.mkdir(dir, { recursive: true });
|
|
@@ -111,7 +114,91 @@ function promptUser(question) {
|
|
|
111
114
|
});
|
|
112
115
|
}
|
|
113
116
|
|
|
114
|
-
async function
|
|
117
|
+
async function updateMCPConfigNonInteractive() {
|
|
118
|
+
if (isInteractive) {
|
|
119
|
+
console.log('\nš§ MCP Configuration Update (Non-Interactive Mode)');
|
|
120
|
+
console.log('====================================================\n');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
// Read existing config if it exists
|
|
125
|
+
let existingConfig = {};
|
|
126
|
+
try {
|
|
127
|
+
const configContent = await fs.readFile(GLOBAL_CONFIG_FILE, 'utf8');
|
|
128
|
+
existingConfig = JSON.parse(configContent);
|
|
129
|
+
} catch {
|
|
130
|
+
// No existing config, will create new
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Check which MCP servers need to be added
|
|
134
|
+
const existingMcp = existingConfig.mcp || {};
|
|
135
|
+
const serversToAdd = {};
|
|
136
|
+
let serversAdded = 0;
|
|
137
|
+
let serversSkipped = 0;
|
|
138
|
+
|
|
139
|
+
for (const [serverName, serverConfig] of Object.entries(DEFAULT_MCP_SERVERS)) {
|
|
140
|
+
if (existingMcp[serverName]) {
|
|
141
|
+
// Server already exists, skip
|
|
142
|
+
serversSkipped++;
|
|
143
|
+
if (isInteractive) {
|
|
144
|
+
console.log(` āļø ${serverName}: already exists, skipping`);
|
|
145
|
+
}
|
|
146
|
+
} else {
|
|
147
|
+
// Server doesn't exist, add it
|
|
148
|
+
serversToAdd[serverName] = serverConfig;
|
|
149
|
+
serversAdded++;
|
|
150
|
+
if (isInteractive) {
|
|
151
|
+
console.log(` ā
${serverName}: will be added`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (serversAdded === 0) {
|
|
157
|
+
if (isInteractive) {
|
|
158
|
+
console.log('\nā
All MCP servers already configured. No changes needed.');
|
|
159
|
+
}
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Backup existing config if it exists
|
|
164
|
+
if (existingConfig && Object.keys(existingConfig).length > 0) {
|
|
165
|
+
const timestamp = new Date().toISOString().split('T')[0].replace(/-/g, '');
|
|
166
|
+
const backupFile = `${GLOBAL_CONFIG_FILE}.backup.${timestamp}`;
|
|
167
|
+
await fs.writeFile(backupFile, JSON.stringify(existingConfig, null, 2));
|
|
168
|
+
if (isInteractive) {
|
|
169
|
+
console.log(`\nš¾ Backed up existing config to: ${backupFile}`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Merge MCP config into existing config
|
|
174
|
+
const updatedConfig = {
|
|
175
|
+
...existingConfig,
|
|
176
|
+
mcp: {
|
|
177
|
+
...existingMcp,
|
|
178
|
+
...serversToAdd,
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
// Write updated config
|
|
183
|
+
await ensureDir(GLOBAL_CONFIG_DIR);
|
|
184
|
+
await fs.writeFile(GLOBAL_CONFIG_FILE, JSON.stringify(updatedConfig, null, 2));
|
|
185
|
+
|
|
186
|
+
if (isInteractive) {
|
|
187
|
+
console.log(`\nā
Added ${serversAdded} MCP server(s) to: ${GLOBAL_CONFIG_FILE}`);
|
|
188
|
+
if (serversSkipped > 0) {
|
|
189
|
+
console.log(` Skipped ${serversSkipped} existing server(s)`);
|
|
190
|
+
}
|
|
191
|
+
console.log('\nš Note: Restart OpenCode to load new MCP configuration');
|
|
192
|
+
}
|
|
193
|
+
} catch (error) {
|
|
194
|
+
if (isInteractive) {
|
|
195
|
+
console.error(`\nā Failed to update MCP config: ${error.message}`);
|
|
196
|
+
}
|
|
197
|
+
throw error;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async function updateMCPConfigInteractive() {
|
|
115
202
|
console.log('\nš§ MCP Configuration Update');
|
|
116
203
|
console.log('============================\n');
|
|
117
204
|
|
|
@@ -137,7 +224,7 @@ async function updateMCPConfig() {
|
|
|
137
224
|
|
|
138
225
|
// Show proposed MCP config
|
|
139
226
|
console.log('\nš Proposed MCP Configuration:');
|
|
140
|
-
console.log(JSON.stringify(
|
|
227
|
+
console.log(JSON.stringify(DEFAULT_MCP_SERVERS, null, 2));
|
|
141
228
|
|
|
142
229
|
// Prompt user
|
|
143
230
|
const answer = await promptUser('\nā Update MCP configuration? (y/n): ');
|
|
@@ -160,7 +247,7 @@ async function updateMCPConfig() {
|
|
|
160
247
|
...existingConfig,
|
|
161
248
|
mcp: {
|
|
162
249
|
...(existingConfig.mcp || {}),
|
|
163
|
-
...
|
|
250
|
+
...DEFAULT_MCP_SERVERS,
|
|
164
251
|
},
|
|
165
252
|
};
|
|
166
253
|
|
|
@@ -174,13 +261,25 @@ async function updateMCPConfig() {
|
|
|
174
261
|
}
|
|
175
262
|
}
|
|
176
263
|
|
|
264
|
+
async function updateMCPConfig() {
|
|
265
|
+
if (isInteractive) {
|
|
266
|
+
await updateMCPConfigInteractive();
|
|
267
|
+
} else {
|
|
268
|
+
await updateMCPConfigNonInteractive();
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
177
272
|
async function deploy(updateMCP) {
|
|
178
|
-
|
|
179
|
-
|
|
273
|
+
if (isInteractive) {
|
|
274
|
+
console.log('š Feature Factory OpenCode Plugin - Deployment');
|
|
275
|
+
console.log('================================================\n');
|
|
276
|
+
}
|
|
180
277
|
|
|
181
278
|
try {
|
|
182
279
|
// Ensure global config directories exist
|
|
183
|
-
|
|
280
|
+
if (isInteractive) {
|
|
281
|
+
console.log(`š Ensuring directories exist: ${GLOBAL_CONFIG_DIR}`);
|
|
282
|
+
}
|
|
184
283
|
await ensureDir(GLOBAL_CONFIG_DIR);
|
|
185
284
|
await ensureDir(SKILLS_DIR);
|
|
186
285
|
await ensureDir(AGENTS_DIR);
|
|
@@ -190,7 +289,9 @@ async function deploy(updateMCP) {
|
|
|
190
289
|
const existingAgents = await getFileNames(AGENTS_DIR);
|
|
191
290
|
|
|
192
291
|
// Deploy skills
|
|
193
|
-
|
|
292
|
+
if (isInteractive) {
|
|
293
|
+
console.log('\nš¦ Deploying Skills...');
|
|
294
|
+
}
|
|
194
295
|
const skills = await getDirectoryNames(SOURCE_SKILLS_DIR);
|
|
195
296
|
|
|
196
297
|
for (const skill of skills) {
|
|
@@ -198,12 +299,16 @@ async function deploy(updateMCP) {
|
|
|
198
299
|
const destDir = join(SKILLS_DIR, skill);
|
|
199
300
|
|
|
200
301
|
await copyDir(srcDir, destDir);
|
|
201
|
-
|
|
202
|
-
|
|
302
|
+
if (isInteractive) {
|
|
303
|
+
const isUpdate = existingSkills.includes(skill);
|
|
304
|
+
console.log(` ${isUpdate ? 'š' : 'ā
'} ${skill} ${isUpdate ? '(updated)' : '(new)'}`);
|
|
305
|
+
}
|
|
203
306
|
}
|
|
204
307
|
|
|
205
308
|
// Deploy agents
|
|
206
|
-
|
|
309
|
+
if (isInteractive) {
|
|
310
|
+
console.log('\nš¤ Deploying Agents...');
|
|
311
|
+
}
|
|
207
312
|
const agents = await getFileNames(SOURCE_AGENTS_DIR);
|
|
208
313
|
|
|
209
314
|
for (const agent of agents) {
|
|
@@ -211,29 +316,35 @@ async function deploy(updateMCP) {
|
|
|
211
316
|
const destFile = join(AGENTS_DIR, agent);
|
|
212
317
|
|
|
213
318
|
await fs.copyFile(srcFile, destFile);
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
319
|
+
if (isInteractive) {
|
|
320
|
+
const agentName = agent.replace('.md', '');
|
|
321
|
+
const isUpdate = existingAgents.includes(agent);
|
|
322
|
+
console.log(` ${isUpdate ? 'š' : 'ā
'} ${agentName} ${isUpdate ? '(updated)' : '(new)'}`);
|
|
323
|
+
}
|
|
217
324
|
}
|
|
218
325
|
|
|
219
326
|
// Summary
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
327
|
+
if (isInteractive) {
|
|
328
|
+
console.log('\n⨠Deployment Complete!');
|
|
329
|
+
console.log(` Skills: ${skills.length} deployed`);
|
|
330
|
+
console.log(` Agents: ${agents.length} deployed`);
|
|
331
|
+
console.log(` Location: ${GLOBAL_CONFIG_DIR}`);
|
|
332
|
+
console.log('\nš Next Steps:');
|
|
333
|
+
console.log(' - Restart OpenCode or run /reload to load new agents');
|
|
334
|
+
console.log(' - Use @planning to create implementation plans');
|
|
335
|
+
console.log(' - Use @building to execute plans');
|
|
336
|
+
console.log(' - Use @reviewing to validate changes');
|
|
337
|
+
console.log(' - Use @research to investigate external topics');
|
|
338
|
+
}
|
|
230
339
|
|
|
231
340
|
// Update MCP config if requested
|
|
232
341
|
if (updateMCP) {
|
|
233
342
|
await updateMCPConfig();
|
|
234
343
|
}
|
|
235
344
|
} catch (error) {
|
|
236
|
-
|
|
345
|
+
if (isInteractive) {
|
|
346
|
+
console.error('\nā Deployment failed:', error.message);
|
|
347
|
+
}
|
|
237
348
|
process.exit(1);
|
|
238
349
|
}
|
|
239
350
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "@syntesseraai/opencode-feature-factory",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.17",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "OpenCode plugin for Feature Factory agents - provides sub-agents and skills for validation, review, security, and architecture assessment",
|
|
7
7
|
"license": "MIT",
|