@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.
Files changed (2) hide show
  1. package/bin/ff-deploy.js +153 -42
  2. 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 DEFAULT_MCP_CONFIG = {
31
- mcp: {
32
- 'jina-ai': {
33
- type: 'remote',
34
- url: 'https://mcp.jina.ai/v1',
35
- headers: {
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
- gh_grep: {
40
- type: 'remote',
41
- url: 'https://mcp.grep.app',
42
- },
43
- context7: {
44
- type: 'remote',
45
- url: 'https://mcp.context7.com/mcp',
46
- headers: {
47
- CONTEXT7_API_KEY: '{env:CONTEXT7_API_KEY}',
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 updateMCPConfig() {
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(DEFAULT_MCP_CONFIG.mcp, null, 2));
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
- ...DEFAULT_MCP_CONFIG.mcp,
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
- console.log('šŸš€ Feature Factory OpenCode Plugin - Deployment');
179
- console.log('================================================\n');
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
- console.log(`šŸ“ Ensuring directories exist: ${GLOBAL_CONFIG_DIR}`);
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
- console.log('\nšŸ“¦ Deploying Skills...');
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
- const isUpdate = existingSkills.includes(skill);
202
- console.log(` ${isUpdate ? 'šŸ”„' : 'āœ…'} ${skill} ${isUpdate ? '(updated)' : '(new)'}`);
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
- console.log('\nšŸ¤– Deploying Agents...');
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
- const agentName = agent.replace('.md', '');
215
- const isUpdate = existingAgents.includes(agent);
216
- console.log(` ${isUpdate ? 'šŸ”„' : 'āœ…'} ${agentName} ${isUpdate ? '(updated)' : '(new)'}`);
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
- console.log('\n✨ Deployment Complete!');
221
- console.log(` Skills: ${skills.length} deployed`);
222
- console.log(` Agents: ${agents.length} deployed`);
223
- console.log(` Location: ${GLOBAL_CONFIG_DIR}`);
224
- console.log('\nšŸ“ Next Steps:');
225
- console.log(' - Restart OpenCode or run /reload to load new agents');
226
- console.log(' - Use @planning to create implementation plans');
227
- console.log(' - Use @building to execute plans');
228
- console.log(' - Use @reviewing to validate changes');
229
- console.log(' - Use @research to investigate external topics');
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
- console.error('\nāŒ Deployment failed:', error.message);
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.16",
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",