clavix 3.2.1 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/commands/config.d.ts +4 -2
- package/dist/cli/commands/config.js +262 -91
- package/dist/cli/commands/init.js +61 -103
- package/dist/cli/commands/update.js +15 -43
- package/dist/core/adapters/base-adapter.d.ts +13 -0
- package/dist/core/adapters/base-adapter.js +37 -0
- package/dist/core/adapters/claude-code-adapter.d.ts +5 -0
- package/dist/core/adapters/claude-code-adapter.js +9 -0
- package/dist/index 2.js +13 -0
- package/dist/index.d 2.ts +4 -0
- package/dist/templates/slash-commands/_canonical/implement.md +22 -1
- package/dist/types/agent.d.ts +1 -0
- package/dist/utils/legacy-command-cleanup.js +5 -0
- package/dist/utils/provider-selector.d.ts +8 -0
- package/dist/utils/provider-selector.js +60 -0
- package/package.json +1 -1
- package/dist/templates/slash-commands/_canonical/task-complete.md +0 -188
|
@@ -12,17 +12,19 @@ export default class Config extends Command {
|
|
|
12
12
|
};
|
|
13
13
|
run(): Promise<void>;
|
|
14
14
|
private showInteractiveMenu;
|
|
15
|
+
private manageProviders;
|
|
16
|
+
private addProviders;
|
|
17
|
+
private removeProviders;
|
|
18
|
+
private replaceProviders;
|
|
15
19
|
private getConfig;
|
|
16
20
|
private setConfig;
|
|
17
21
|
private editConfig;
|
|
18
22
|
private resetConfig;
|
|
19
|
-
private changeAgent;
|
|
20
23
|
private editPreferences;
|
|
21
24
|
private loadConfig;
|
|
22
25
|
private saveConfig;
|
|
23
26
|
private displayConfig;
|
|
24
27
|
private getNestedValue;
|
|
25
28
|
private setNestedValue;
|
|
26
|
-
private getDefaultConfig;
|
|
27
29
|
}
|
|
28
30
|
//# sourceMappingURL=config.d.ts.map
|
|
@@ -4,13 +4,14 @@ import fs from 'fs-extra';
|
|
|
4
4
|
import * as path from 'path';
|
|
5
5
|
import inquirer from 'inquirer';
|
|
6
6
|
import { AgentManager } from '../../core/agent-manager.js';
|
|
7
|
+
import { DEFAULT_CONFIG, isLegacyConfig, migrateConfig } from '../../types/config.js';
|
|
8
|
+
import { loadCommandTemplates } from '../../utils/template-loader.js';
|
|
7
9
|
export default class Config extends Command {
|
|
8
10
|
static description = 'Manage Clavix configuration';
|
|
9
11
|
static examples = [
|
|
10
12
|
'<%= config.bin %> <%= command.id %>',
|
|
11
|
-
'<%= config.bin %> <%= command.id %> get
|
|
12
|
-
'<%= config.bin %> <%= command.id %> set
|
|
13
|
-
'<%= config.bin %> <%= command.id %> edit',
|
|
13
|
+
'<%= config.bin %> <%= command.id %> get providers',
|
|
14
|
+
'<%= config.bin %> <%= command.id %> set preferences.verboseLogging true',
|
|
14
15
|
];
|
|
15
16
|
static args = {
|
|
16
17
|
action: Args.string({
|
|
@@ -71,39 +72,246 @@ export default class Config extends Command {
|
|
|
71
72
|
}
|
|
72
73
|
}
|
|
73
74
|
async showInteractiveMenu(configPath) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
let continueMenu = true;
|
|
76
|
+
while (continueMenu) {
|
|
77
|
+
const config = this.loadConfig(configPath);
|
|
78
|
+
this.log(chalk.bold.cyan('\n⚙️ Clavix Configuration\n'));
|
|
79
|
+
this.displayConfig(config);
|
|
80
|
+
const { action } = await inquirer.prompt([
|
|
81
|
+
{
|
|
82
|
+
type: 'list',
|
|
83
|
+
name: 'action',
|
|
84
|
+
message: 'What would you like to do?',
|
|
85
|
+
choices: [
|
|
86
|
+
{ name: 'View current configuration', value: 'view' },
|
|
87
|
+
{ name: 'Manage providers (add/remove)', value: 'providers' },
|
|
88
|
+
{ name: 'Edit preferences', value: 'edit-preferences' },
|
|
89
|
+
{ name: 'Reset to defaults', value: 'reset' },
|
|
90
|
+
{ name: 'Exit', value: 'exit' },
|
|
91
|
+
],
|
|
92
|
+
},
|
|
93
|
+
]);
|
|
94
|
+
switch (action) {
|
|
95
|
+
case 'view':
|
|
96
|
+
// Already displayed above
|
|
97
|
+
break;
|
|
98
|
+
case 'providers':
|
|
99
|
+
await this.manageProviders(config, configPath);
|
|
100
|
+
break;
|
|
101
|
+
case 'edit-preferences':
|
|
102
|
+
await this.editPreferences(configPath, config);
|
|
103
|
+
break;
|
|
104
|
+
case 'reset':
|
|
105
|
+
await this.resetConfig(configPath);
|
|
106
|
+
break;
|
|
107
|
+
case 'exit':
|
|
108
|
+
continueMenu = false;
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
async manageProviders(config, configPath) {
|
|
114
|
+
const agentManager = new AgentManager();
|
|
115
|
+
while (true) {
|
|
116
|
+
// Show current providers
|
|
117
|
+
this.log(chalk.cyan('\n📦 Current Providers:'));
|
|
118
|
+
if (config.providers.length === 0) {
|
|
119
|
+
this.log(chalk.gray(' (none configured)'));
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
for (const providerName of config.providers) {
|
|
123
|
+
const adapter = agentManager.getAdapter(providerName);
|
|
124
|
+
const displayName = adapter?.displayName || providerName;
|
|
125
|
+
this.log(chalk.gray(` • ${displayName}`));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// Submenu
|
|
129
|
+
const { action } = await inquirer.prompt([
|
|
130
|
+
{
|
|
131
|
+
type: 'list',
|
|
132
|
+
name: 'action',
|
|
133
|
+
message: 'What would you like to do?',
|
|
134
|
+
choices: [
|
|
135
|
+
{ name: 'Add provider', value: 'add' },
|
|
136
|
+
{ name: 'Remove provider', value: 'remove' },
|
|
137
|
+
{ name: 'Replace all providers', value: 'replace' },
|
|
138
|
+
{ name: 'Back to main menu', value: 'back' },
|
|
139
|
+
],
|
|
140
|
+
},
|
|
141
|
+
]);
|
|
142
|
+
if (action === 'back')
|
|
143
|
+
break;
|
|
144
|
+
switch (action) {
|
|
145
|
+
case 'add':
|
|
146
|
+
await this.addProviders(config, configPath);
|
|
147
|
+
break;
|
|
148
|
+
case 'remove':
|
|
149
|
+
await this.removeProviders(config, configPath);
|
|
150
|
+
break;
|
|
151
|
+
case 'replace':
|
|
152
|
+
await this.replaceProviders(config, configPath);
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
// Reload config after modifications
|
|
156
|
+
config = this.loadConfig(configPath);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
async addProviders(config, configPath) {
|
|
160
|
+
const agentManager = new AgentManager();
|
|
161
|
+
const allProviders = agentManager.getAdapters();
|
|
162
|
+
// Show only non-selected providers
|
|
163
|
+
const availableToAdd = allProviders.filter((a) => !config.providers.includes(a.name));
|
|
164
|
+
if (availableToAdd.length === 0) {
|
|
165
|
+
this.log(chalk.yellow('\n✓ All providers already added!'));
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
// Multi-select checkbox
|
|
169
|
+
const { newProviders } = await inquirer.prompt([
|
|
78
170
|
{
|
|
79
|
-
type: '
|
|
80
|
-
name: '
|
|
81
|
-
message: '
|
|
82
|
-
choices:
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
{ name: 'Reset to defaults', value: 'reset' },
|
|
87
|
-
{ name: 'Exit', value: 'exit' },
|
|
88
|
-
],
|
|
171
|
+
type: 'checkbox',
|
|
172
|
+
name: 'newProviders',
|
|
173
|
+
message: 'Select providers to add:',
|
|
174
|
+
choices: availableToAdd.map((adapter) => ({
|
|
175
|
+
name: `${adapter.displayName} (${adapter.directory})`,
|
|
176
|
+
value: adapter.name,
|
|
177
|
+
})),
|
|
89
178
|
},
|
|
90
179
|
]);
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
break;
|
|
95
|
-
case 'change-agent':
|
|
96
|
-
await this.changeAgent(configPath, config);
|
|
97
|
-
break;
|
|
98
|
-
case 'edit-preferences':
|
|
99
|
-
await this.editPreferences(configPath, config);
|
|
100
|
-
break;
|
|
101
|
-
case 'reset':
|
|
102
|
-
await this.resetConfig(configPath);
|
|
103
|
-
break;
|
|
104
|
-
case 'exit':
|
|
105
|
-
return;
|
|
180
|
+
if (newProviders.length === 0) {
|
|
181
|
+
this.log(chalk.gray('No providers selected'));
|
|
182
|
+
return;
|
|
106
183
|
}
|
|
184
|
+
// Add to config
|
|
185
|
+
config.providers.push(...newProviders);
|
|
186
|
+
this.saveConfig(configPath, config);
|
|
187
|
+
this.log(chalk.gray('\n🔧 Generating commands for new providers...'));
|
|
188
|
+
// Generate commands for new providers
|
|
189
|
+
for (const providerName of newProviders) {
|
|
190
|
+
const adapter = agentManager.getAdapter(providerName);
|
|
191
|
+
if (!adapter)
|
|
192
|
+
continue;
|
|
193
|
+
const templates = await loadCommandTemplates(adapter);
|
|
194
|
+
await adapter.generateCommands(templates);
|
|
195
|
+
this.log(chalk.green(` ✓ Generated ${templates.length} command(s) for ${adapter.displayName}`));
|
|
196
|
+
}
|
|
197
|
+
this.log(chalk.green('\n✅ Providers added successfully!'));
|
|
198
|
+
}
|
|
199
|
+
async removeProviders(config, configPath) {
|
|
200
|
+
if (config.providers.length === 0) {
|
|
201
|
+
this.log(chalk.yellow('\n⚠ No providers configured!'));
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
const agentManager = new AgentManager();
|
|
205
|
+
// Multi-select from current providers
|
|
206
|
+
const { providersToRemove } = await inquirer.prompt([
|
|
207
|
+
{
|
|
208
|
+
type: 'checkbox',
|
|
209
|
+
name: 'providersToRemove',
|
|
210
|
+
message: 'Select providers to remove:',
|
|
211
|
+
choices: config.providers.map((name) => {
|
|
212
|
+
const adapter = agentManager.getAdapter(name);
|
|
213
|
+
return {
|
|
214
|
+
name: `${adapter?.displayName || name} (${adapter?.directory || 'unknown'})`,
|
|
215
|
+
value: name,
|
|
216
|
+
};
|
|
217
|
+
}),
|
|
218
|
+
validate: (answer) => {
|
|
219
|
+
if (answer.length === config.providers.length) {
|
|
220
|
+
return 'You must keep at least one provider. Use "Reset to defaults" to reconfigure completely.';
|
|
221
|
+
}
|
|
222
|
+
return true;
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
]);
|
|
226
|
+
if (providersToRemove.length === 0) {
|
|
227
|
+
this.log(chalk.gray('No providers selected'));
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
// Confirm cleanup
|
|
231
|
+
const { cleanup } = await inquirer.prompt([
|
|
232
|
+
{
|
|
233
|
+
type: 'confirm',
|
|
234
|
+
name: 'cleanup',
|
|
235
|
+
message: 'Remove command files for these providers?',
|
|
236
|
+
default: true,
|
|
237
|
+
},
|
|
238
|
+
]);
|
|
239
|
+
// Remove from config
|
|
240
|
+
config.providers = config.providers.filter((p) => !providersToRemove.includes(p));
|
|
241
|
+
this.saveConfig(configPath, config);
|
|
242
|
+
// Clean up command files
|
|
243
|
+
if (cleanup) {
|
|
244
|
+
this.log(chalk.gray('\n🗑️ Cleaning up command files...'));
|
|
245
|
+
for (const providerName of providersToRemove) {
|
|
246
|
+
const adapter = agentManager.getAdapter(providerName);
|
|
247
|
+
if (adapter) {
|
|
248
|
+
const removed = await adapter.removeAllCommands();
|
|
249
|
+
this.log(chalk.gray(` ✓ Removed ${removed} command(s) from ${adapter.displayName}`));
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
this.log(chalk.green('\n✅ Providers removed successfully!'));
|
|
254
|
+
}
|
|
255
|
+
async replaceProviders(config, configPath) {
|
|
256
|
+
const agentManager = new AgentManager();
|
|
257
|
+
// Use shared provider selector
|
|
258
|
+
const { selectProviders } = await import('../../utils/provider-selector.js');
|
|
259
|
+
const newProviders = await selectProviders(agentManager, config.providers);
|
|
260
|
+
if (newProviders.length === 0) {
|
|
261
|
+
this.log(chalk.gray('No providers selected'));
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
// Find deselected providers
|
|
265
|
+
const deselected = config.providers.filter((p) => !newProviders.includes(p));
|
|
266
|
+
// Handle cleanup if providers were deselected
|
|
267
|
+
if (deselected.length > 0) {
|
|
268
|
+
this.log(chalk.yellow('\n⚠ Previously configured but not selected:'));
|
|
269
|
+
for (const providerName of deselected) {
|
|
270
|
+
const adapter = agentManager.getAdapter(providerName);
|
|
271
|
+
const displayName = adapter?.displayName || providerName;
|
|
272
|
+
const directory = adapter?.directory || 'unknown';
|
|
273
|
+
this.log(chalk.gray(` • ${displayName} (${directory})`));
|
|
274
|
+
}
|
|
275
|
+
const { cleanupAction } = await inquirer.prompt([
|
|
276
|
+
{
|
|
277
|
+
type: 'list',
|
|
278
|
+
name: 'cleanupAction',
|
|
279
|
+
message: 'What would you like to do with these providers?',
|
|
280
|
+
choices: [
|
|
281
|
+
{ name: 'Clean up (remove all command files)', value: 'cleanup' },
|
|
282
|
+
{ name: 'Skip (leave as-is)', value: 'skip' },
|
|
283
|
+
],
|
|
284
|
+
},
|
|
285
|
+
]);
|
|
286
|
+
if (cleanupAction === 'cleanup') {
|
|
287
|
+
this.log(chalk.gray('\n🗑️ Cleaning up deselected providers...'));
|
|
288
|
+
for (const providerName of deselected) {
|
|
289
|
+
const adapter = agentManager.getAdapter(providerName);
|
|
290
|
+
if (adapter) {
|
|
291
|
+
const removed = await adapter.removeAllCommands();
|
|
292
|
+
this.log(chalk.gray(` ✓ Removed ${removed} command(s) from ${adapter.displayName}`));
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
// Update config
|
|
298
|
+
config.providers = newProviders;
|
|
299
|
+
this.saveConfig(configPath, config);
|
|
300
|
+
// Prompt to run update
|
|
301
|
+
const { runUpdate } = await inquirer.prompt([
|
|
302
|
+
{
|
|
303
|
+
type: 'confirm',
|
|
304
|
+
name: 'runUpdate',
|
|
305
|
+
message: 'Run update to regenerate all commands?',
|
|
306
|
+
default: true,
|
|
307
|
+
},
|
|
308
|
+
]);
|
|
309
|
+
if (runUpdate) {
|
|
310
|
+
this.log(chalk.gray('\n🔧 Regenerating commands for all providers...\n'));
|
|
311
|
+
const Update = (await import('./update.js')).default;
|
|
312
|
+
await Update.run([]);
|
|
313
|
+
}
|
|
314
|
+
this.log(chalk.green('\n✅ Providers replaced successfully!'));
|
|
107
315
|
}
|
|
108
316
|
async getConfig(configPath, key) {
|
|
109
317
|
const config = this.loadConfig(configPath);
|
|
@@ -153,57 +361,33 @@ export default class Config extends Command {
|
|
|
153
361
|
return;
|
|
154
362
|
}
|
|
155
363
|
const config = this.loadConfig(configPath);
|
|
156
|
-
const defaultConfig =
|
|
364
|
+
const defaultConfig = {
|
|
365
|
+
...DEFAULT_CONFIG,
|
|
366
|
+
providers: config.providers, // Keep existing providers
|
|
367
|
+
};
|
|
157
368
|
this.saveConfig(configPath, defaultConfig);
|
|
158
|
-
this.log(chalk.green('✅ Configuration reset to defaults'));
|
|
159
|
-
}
|
|
160
|
-
async changeAgent(configPath, config) {
|
|
161
|
-
const agentManager = new AgentManager();
|
|
162
|
-
const availableAgents = await agentManager.detectAgents();
|
|
163
|
-
if (availableAgents.length === 0) {
|
|
164
|
-
this.error('No supported agents detected in this project');
|
|
165
|
-
}
|
|
166
|
-
const { newAgent } = await inquirer.prompt([
|
|
167
|
-
{
|
|
168
|
-
type: 'list',
|
|
169
|
-
name: 'newAgent',
|
|
170
|
-
message: 'Select agent:',
|
|
171
|
-
choices: availableAgents.map(agent => ({
|
|
172
|
-
name: agent.displayName,
|
|
173
|
-
value: agent.name,
|
|
174
|
-
})),
|
|
175
|
-
default: config.agent,
|
|
176
|
-
},
|
|
177
|
-
]);
|
|
178
|
-
if (newAgent === config.agent) {
|
|
179
|
-
this.log(chalk.gray('No changes made'));
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
config.agent = newAgent;
|
|
183
|
-
this.saveConfig(configPath, config);
|
|
184
|
-
this.log(chalk.green(`✅ Agent changed to ${newAgent}`));
|
|
185
|
-
this.log(chalk.yellow('\n⚠️ Run ') + chalk.cyan('clavix update') + chalk.yellow(' to update slash commands'));
|
|
369
|
+
this.log(chalk.green('✅ Configuration reset to defaults (providers preserved)'));
|
|
186
370
|
}
|
|
187
371
|
async editPreferences(configPath, config) {
|
|
188
|
-
const preferences = config.preferences ||
|
|
372
|
+
const preferences = config.preferences || DEFAULT_CONFIG.preferences;
|
|
189
373
|
const answers = await inquirer.prompt([
|
|
190
374
|
{
|
|
191
375
|
type: 'confirm',
|
|
192
376
|
name: 'autoOpenOutputs',
|
|
193
377
|
message: 'Auto-open generated outputs?',
|
|
194
|
-
default: preferences.autoOpenOutputs
|
|
378
|
+
default: preferences.autoOpenOutputs,
|
|
195
379
|
},
|
|
196
380
|
{
|
|
197
381
|
type: 'confirm',
|
|
198
382
|
name: 'verboseLogging',
|
|
199
383
|
message: 'Enable verbose logging?',
|
|
200
|
-
default: preferences.verboseLogging
|
|
384
|
+
default: preferences.verboseLogging,
|
|
201
385
|
},
|
|
202
386
|
{
|
|
203
387
|
type: 'confirm',
|
|
204
388
|
name: 'preserveSessions',
|
|
205
389
|
message: 'Preserve completed sessions?',
|
|
206
|
-
default: preferences.preserveSessions
|
|
390
|
+
default: preferences.preserveSessions,
|
|
207
391
|
},
|
|
208
392
|
]);
|
|
209
393
|
config.preferences = answers;
|
|
@@ -212,7 +396,15 @@ export default class Config extends Command {
|
|
|
212
396
|
}
|
|
213
397
|
loadConfig(configPath) {
|
|
214
398
|
try {
|
|
215
|
-
|
|
399
|
+
const rawConfig = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
400
|
+
// Check if legacy config and migrate
|
|
401
|
+
if (isLegacyConfig(rawConfig)) {
|
|
402
|
+
this.warn(chalk.yellow('Detected legacy config format. Migrating to new format...'));
|
|
403
|
+
const migratedConfig = migrateConfig(rawConfig);
|
|
404
|
+
this.saveConfig(configPath, migratedConfig);
|
|
405
|
+
return migratedConfig;
|
|
406
|
+
}
|
|
407
|
+
return rawConfig;
|
|
216
408
|
}
|
|
217
409
|
catch (error) {
|
|
218
410
|
this.error(chalk.red(`Failed to load configuration: ${error.message}`));
|
|
@@ -228,17 +420,17 @@ export default class Config extends Command {
|
|
|
228
420
|
}
|
|
229
421
|
displayConfig(config) {
|
|
230
422
|
this.log(` ${chalk.gray('Version:')} ${config.version}`);
|
|
231
|
-
this.log(` ${chalk.gray('
|
|
423
|
+
this.log(` ${chalk.gray('Providers:')} ${config.providers.map(p => chalk.cyan(p)).join(', ') || chalk.gray('(none)')}`);
|
|
232
424
|
if (config.preferences) {
|
|
233
425
|
this.log(`\n ${chalk.bold('Preferences:')}`);
|
|
234
426
|
this.log(` ${chalk.gray('Auto-open outputs:')} ${config.preferences.autoOpenOutputs ? chalk.green('yes') : chalk.gray('no')}`);
|
|
235
427
|
this.log(` ${chalk.gray('Verbose logging:')} ${config.preferences.verboseLogging ? chalk.green('yes') : chalk.gray('no')}`);
|
|
236
|
-
this.log(` ${chalk.gray('Preserve sessions:')} ${config.preferences.preserveSessions
|
|
428
|
+
this.log(` ${chalk.gray('Preserve sessions:')} ${config.preferences.preserveSessions ? chalk.green('yes') : chalk.gray('no')}`);
|
|
237
429
|
}
|
|
238
430
|
if (config.outputs) {
|
|
239
431
|
this.log(`\n ${chalk.bold('Outputs:')}`);
|
|
240
|
-
this.log(` ${chalk.gray('Path:')} ${config.outputs.path
|
|
241
|
-
this.log(` ${chalk.gray('Format:')} ${config.outputs.format
|
|
432
|
+
this.log(` ${chalk.gray('Path:')} ${config.outputs.path}`);
|
|
433
|
+
this.log(` ${chalk.gray('Format:')} ${config.outputs.format}`);
|
|
242
434
|
}
|
|
243
435
|
this.log('');
|
|
244
436
|
}
|
|
@@ -260,26 +452,5 @@ export default class Config extends Command {
|
|
|
260
452
|
}, obj);
|
|
261
453
|
target[lastKey] = value;
|
|
262
454
|
}
|
|
263
|
-
getDefaultConfig(agent) {
|
|
264
|
-
return {
|
|
265
|
-
version: '1.0.0',
|
|
266
|
-
agent,
|
|
267
|
-
templates: {
|
|
268
|
-
prdQuestions: 'default',
|
|
269
|
-
fullPrd: 'default',
|
|
270
|
-
quickPrd: 'default',
|
|
271
|
-
},
|
|
272
|
-
outputs: {
|
|
273
|
-
path: '.clavix/outputs',
|
|
274
|
-
format: 'markdown',
|
|
275
|
-
},
|
|
276
|
-
preferences: {
|
|
277
|
-
autoOpenOutputs: false,
|
|
278
|
-
verboseLogging: false,
|
|
279
|
-
preserveSessions: true,
|
|
280
|
-
},
|
|
281
|
-
experimental: {},
|
|
282
|
-
};
|
|
283
|
-
}
|
|
284
455
|
}
|
|
285
456
|
//# sourceMappingURL=config.js.map
|
|
@@ -38,115 +38,67 @@ export default class Init extends Command {
|
|
|
38
38
|
return;
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
|
-
//
|
|
41
|
+
// Load existing config if re-initializing
|
|
42
42
|
const agentManager = new AgentManager();
|
|
43
|
+
let existingProviders = [];
|
|
44
|
+
if (await FileSystem.exists('.clavix/config.json')) {
|
|
45
|
+
try {
|
|
46
|
+
const configContent = await FileSystem.readFile('.clavix/config.json');
|
|
47
|
+
const config = JSON5.parse(configContent);
|
|
48
|
+
existingProviders = config.providers || [];
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
// Ignore parse errors, will use empty array
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// Select providers using shared utility
|
|
43
55
|
console.log(chalk.gray('Select AI development tools to support:\n'));
|
|
44
56
|
console.log(chalk.gray('(Space to select, Enter to confirm)\n'));
|
|
45
|
-
const {
|
|
46
|
-
|
|
47
|
-
type: 'checkbox',
|
|
48
|
-
name: 'selectedProviders',
|
|
49
|
-
message: 'Which AI tools are you using?',
|
|
50
|
-
choices: [
|
|
51
|
-
// CLI Tools
|
|
52
|
-
{
|
|
53
|
-
name: 'Amp (.agents/commands/)',
|
|
54
|
-
value: 'amp',
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
name: 'Augment CLI (.augment/commands/clavix/)',
|
|
58
|
-
value: 'augment',
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
name: 'Codex CLI (~/.codex/prompts)',
|
|
62
|
-
value: 'codex',
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
name: 'CodeBuddy (.codebuddy/commands/)',
|
|
66
|
-
value: 'codebuddy',
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
name: 'Crush CLI (.crush/commands/clavix/)',
|
|
70
|
-
value: 'crush',
|
|
71
|
-
},
|
|
72
|
-
{
|
|
73
|
-
name: 'Claude Code (.claude/commands/clavix/)',
|
|
74
|
-
value: 'claude-code',
|
|
75
|
-
},
|
|
76
|
-
{
|
|
77
|
-
name: 'Droid CLI (.factory/commands/)',
|
|
78
|
-
value: 'droid',
|
|
79
|
-
},
|
|
80
|
-
{
|
|
81
|
-
name: 'Gemini CLI (.gemini/commands/clavix/)',
|
|
82
|
-
value: 'gemini',
|
|
83
|
-
},
|
|
84
|
-
{
|
|
85
|
-
name: 'LLXPRT (.llxprt/commands/clavix/)',
|
|
86
|
-
value: 'llxprt',
|
|
87
|
-
},
|
|
88
|
-
{
|
|
89
|
-
name: 'OpenCode (.opencode/command/)',
|
|
90
|
-
value: 'opencode',
|
|
91
|
-
},
|
|
92
|
-
{
|
|
93
|
-
name: 'Qwen Code (.qwen/commands/clavix/)',
|
|
94
|
-
value: 'qwen',
|
|
95
|
-
},
|
|
96
|
-
new inquirer.Separator(),
|
|
97
|
-
// IDE & IDE Extensions
|
|
98
|
-
{
|
|
99
|
-
name: 'Cursor (.cursor/commands/)',
|
|
100
|
-
value: 'cursor',
|
|
101
|
-
},
|
|
102
|
-
{
|
|
103
|
-
name: 'Windsurf (.windsurf/workflows/)',
|
|
104
|
-
value: 'windsurf',
|
|
105
|
-
},
|
|
106
|
-
{
|
|
107
|
-
name: 'Kilocode (.kilocode/workflows/)',
|
|
108
|
-
value: 'kilocode',
|
|
109
|
-
},
|
|
110
|
-
{
|
|
111
|
-
name: 'Roocode (.roo/commands/)',
|
|
112
|
-
value: 'roocode',
|
|
113
|
-
},
|
|
114
|
-
{
|
|
115
|
-
name: 'Cline (.clinerules/workflows/)',
|
|
116
|
-
value: 'cline',
|
|
117
|
-
},
|
|
118
|
-
new inquirer.Separator(),
|
|
119
|
-
// Universal Adapters
|
|
120
|
-
{
|
|
121
|
-
name: 'Agents (AGENTS.md - Universal - for tools without slash commands)',
|
|
122
|
-
value: 'agents-md',
|
|
123
|
-
},
|
|
124
|
-
{
|
|
125
|
-
name: 'GitHub Copilot (.github/copilot-instructions.md)',
|
|
126
|
-
value: 'copilot-instructions',
|
|
127
|
-
},
|
|
128
|
-
{
|
|
129
|
-
name: 'Warp (WARP.md - optimized for Warp)',
|
|
130
|
-
value: 'warp-md',
|
|
131
|
-
},
|
|
132
|
-
{
|
|
133
|
-
name: 'Octofriend (OCTO.md - optimized for Octofriend)',
|
|
134
|
-
value: 'octo-md',
|
|
135
|
-
},
|
|
136
|
-
new inquirer.Separator(),
|
|
137
|
-
],
|
|
138
|
-
validate: (answer) => {
|
|
139
|
-
if (answer.length === 0) {
|
|
140
|
-
return 'You must select at least one provider.';
|
|
141
|
-
}
|
|
142
|
-
return true;
|
|
143
|
-
},
|
|
144
|
-
},
|
|
145
|
-
]);
|
|
57
|
+
const { selectProviders } = await import('../../utils/provider-selector.js');
|
|
58
|
+
const selectedProviders = await selectProviders(agentManager, existingProviders);
|
|
146
59
|
if (!selectedProviders || selectedProviders.length === 0) {
|
|
147
60
|
console.log(chalk.red('\n✗ No providers selected\n'));
|
|
148
61
|
return;
|
|
149
62
|
}
|
|
63
|
+
// Handle deselected providers (cleanup prompt)
|
|
64
|
+
const deselectedProviders = existingProviders.filter((p) => !selectedProviders.includes(p));
|
|
65
|
+
if (deselectedProviders.length > 0) {
|
|
66
|
+
console.log(chalk.yellow('\n⚠ Previously configured but not selected:'));
|
|
67
|
+
for (const providerName of deselectedProviders) {
|
|
68
|
+
const adapter = agentManager.getAdapter(providerName);
|
|
69
|
+
const displayName = adapter?.displayName || providerName;
|
|
70
|
+
const directory = adapter?.directory || 'unknown';
|
|
71
|
+
console.log(chalk.gray(` • ${displayName} (${directory})`));
|
|
72
|
+
}
|
|
73
|
+
const { cleanupAction } = await inquirer.prompt([
|
|
74
|
+
{
|
|
75
|
+
type: 'list',
|
|
76
|
+
name: 'cleanupAction',
|
|
77
|
+
message: 'What would you like to do with these providers?',
|
|
78
|
+
choices: [
|
|
79
|
+
{ name: 'Clean up (remove all command files)', value: 'cleanup' },
|
|
80
|
+
{ name: 'Keep (also update their commands)', value: 'update' },
|
|
81
|
+
{ name: 'Skip (leave as-is)', value: 'skip' },
|
|
82
|
+
],
|
|
83
|
+
},
|
|
84
|
+
]);
|
|
85
|
+
if (cleanupAction === 'cleanup') {
|
|
86
|
+
console.log(chalk.gray('\n🗑️ Cleaning up deselected providers...'));
|
|
87
|
+
for (const providerName of deselectedProviders) {
|
|
88
|
+
const adapter = agentManager.getAdapter(providerName);
|
|
89
|
+
if (adapter) {
|
|
90
|
+
const removed = await adapter.removeAllCommands();
|
|
91
|
+
console.log(chalk.gray(` ✓ Removed ${removed} command(s) from ${adapter.displayName}`));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
else if (cleanupAction === 'update') {
|
|
96
|
+
// Add them back to selection
|
|
97
|
+
selectedProviders.push(...deselectedProviders);
|
|
98
|
+
console.log(chalk.gray('\n✓ Keeping all providers\n'));
|
|
99
|
+
}
|
|
100
|
+
// If 'skip': do nothing
|
|
101
|
+
}
|
|
150
102
|
// Create .clavix directory structure
|
|
151
103
|
console.log(chalk.cyan('\n📁 Creating directory structure...'));
|
|
152
104
|
await this.createDirectoryStructure();
|
|
@@ -235,6 +187,11 @@ export default class Init extends Command {
|
|
|
235
187
|
}
|
|
236
188
|
}
|
|
237
189
|
}
|
|
190
|
+
// Remove all existing commands before regenerating (ensures clean state)
|
|
191
|
+
const removed = await adapter.removeAllCommands();
|
|
192
|
+
if (removed > 0) {
|
|
193
|
+
console.log(chalk.gray(` Removed ${removed} existing command(s)`));
|
|
194
|
+
}
|
|
238
195
|
// Generate slash commands
|
|
239
196
|
const generatedTemplates = await this.generateSlashCommands(adapter);
|
|
240
197
|
await this.handleLegacyCommands(adapter, generatedTemplates);
|
|
@@ -357,6 +314,8 @@ Welcome to Clavix! This directory contains your local Clavix configuration and d
|
|
|
357
314
|
|
|
358
315
|
If using Claude Code, Cursor, or Windsurf, the following slash commands are available:
|
|
359
316
|
|
|
317
|
+
**Note:** Running \`clavix init\` or \`clavix update\` will regenerate all slash commands from templates. Any manual edits to generated commands will be lost. If you need custom commands, create new command files instead of modifying generated ones.
|
|
318
|
+
|
|
360
319
|
### Prompt Improvement
|
|
361
320
|
- \`/clavix:fast [prompt]\` - Quick prompt improvements
|
|
362
321
|
- \`/clavix:deep [prompt]\` - Comprehensive prompt analysis
|
|
@@ -371,7 +330,6 @@ If using Claude Code, Cursor, or Windsurf, the following slash commands are avai
|
|
|
371
330
|
|
|
372
331
|
### Implementation
|
|
373
332
|
- \`/clavix:implement\` - Execute task workflow with git integration
|
|
374
|
-
- \`/clavix:task-complete\` - Mark task as complete
|
|
375
333
|
|
|
376
334
|
### Project Management
|
|
377
335
|
- \`/clavix:archive\` - Archive completed projects
|
|
@@ -146,53 +146,25 @@ export default class Update extends Command {
|
|
|
146
146
|
}
|
|
147
147
|
async updateCommands(adapter, force) {
|
|
148
148
|
this.log(chalk.cyan(`\n🔧 Updating slash commands for ${adapter.displayName}...`));
|
|
149
|
-
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
const templatesDir = path.join(__dirname, '..', '..', 'templates', 'slash-commands', adapter.name);
|
|
154
|
-
if (!fs.existsSync(templatesDir)) {
|
|
155
|
-
this.log(chalk.yellow(` ⚠ Templates directory not found: ${templatesDir}`));
|
|
156
|
-
return 0;
|
|
149
|
+
// Remove all existing commands first (force regeneration)
|
|
150
|
+
const removed = await adapter.removeAllCommands();
|
|
151
|
+
if (removed > 0) {
|
|
152
|
+
this.log(chalk.gray(` Removed ${removed} existing command(s)`));
|
|
157
153
|
}
|
|
158
|
-
//
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
if (templateFiles.length === 0) {
|
|
154
|
+
// Load templates using the canonical template loader
|
|
155
|
+
const { loadCommandTemplates } = await import('../../utils/template-loader.js');
|
|
156
|
+
const templates = await loadCommandTemplates(adapter);
|
|
157
|
+
if (templates.length === 0) {
|
|
163
158
|
this.log(chalk.yellow(' ⚠ No command templates found'));
|
|
164
159
|
return 0;
|
|
165
160
|
}
|
|
166
|
-
//
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
const filename = adapter.getTargetFilename(command);
|
|
174
|
-
const commandFile = path.join(commandsPath, filename);
|
|
175
|
-
const templatePath = path.join(templatesDir, `${command}${extension}`);
|
|
176
|
-
const newContent = fs.readFileSync(templatePath, 'utf-8');
|
|
177
|
-
if (fs.existsSync(commandFile)) {
|
|
178
|
-
const currentContent = fs.readFileSync(commandFile, 'utf-8');
|
|
179
|
-
if (force || currentContent !== newContent) {
|
|
180
|
-
fs.writeFileSync(commandFile, newContent);
|
|
181
|
-
this.log(chalk.gray(` ✓ Updated ${filename}`));
|
|
182
|
-
updated++;
|
|
183
|
-
}
|
|
184
|
-
else {
|
|
185
|
-
this.log(chalk.gray(` • ${filename} already up to date`));
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
else {
|
|
189
|
-
fs.writeFileSync(commandFile, newContent);
|
|
190
|
-
this.log(chalk.gray(` ✓ Created ${filename}`));
|
|
191
|
-
updated++;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
updated += await this.handleLegacyCommands(adapter, templateFiles, force);
|
|
195
|
-
return updated;
|
|
161
|
+
// Generate fresh commands from templates
|
|
162
|
+
await adapter.generateCommands(templates);
|
|
163
|
+
this.log(chalk.gray(` ✓ Generated ${templates.length} command(s)`));
|
|
164
|
+
// Handle legacy commands (cleanup old naming patterns)
|
|
165
|
+
const commandNames = templates.map(t => t.name);
|
|
166
|
+
const legacyRemoved = await this.handleLegacyCommands(adapter, commandNames, force);
|
|
167
|
+
return removed + templates.length + legacyRemoved;
|
|
196
168
|
}
|
|
197
169
|
async handleLegacyCommands(adapter, commandNames, force) {
|
|
198
170
|
if (commandNames.length === 0) {
|
|
@@ -21,6 +21,19 @@ export declare abstract class BaseAdapter implements AgentAdapter {
|
|
|
21
21
|
* Checks if directory can be created and is writable
|
|
22
22
|
*/
|
|
23
23
|
validate(): Promise<ValidationResult>;
|
|
24
|
+
/**
|
|
25
|
+
* Remove all existing Clavix-generated commands for this adapter
|
|
26
|
+
* Called before regenerating to ensure clean state
|
|
27
|
+
* @returns Number of files removed
|
|
28
|
+
*/
|
|
29
|
+
removeAllCommands(): Promise<number>;
|
|
30
|
+
/**
|
|
31
|
+
* Determine if a file is a Clavix-generated command
|
|
32
|
+
* Override in adapters for provider-specific patterns
|
|
33
|
+
* @param filename The filename to check
|
|
34
|
+
* @returns true if this is a Clavix-generated command file
|
|
35
|
+
*/
|
|
36
|
+
protected isClavixGeneratedCommand(filename: string): boolean;
|
|
24
37
|
/**
|
|
25
38
|
* Generate commands - default implementation
|
|
26
39
|
* Creates command files in the provider's directory
|
|
@@ -59,6 +59,43 @@ export class BaseAdapter {
|
|
|
59
59
|
warnings: warnings.length > 0 ? warnings : undefined,
|
|
60
60
|
};
|
|
61
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* Remove all existing Clavix-generated commands for this adapter
|
|
64
|
+
* Called before regenerating to ensure clean state
|
|
65
|
+
* @returns Number of files removed
|
|
66
|
+
*/
|
|
67
|
+
async removeAllCommands() {
|
|
68
|
+
const commandPath = this.getCommandPath();
|
|
69
|
+
// If directory doesn't exist, nothing to remove
|
|
70
|
+
if (!(await FileSystem.exists(commandPath))) {
|
|
71
|
+
return 0;
|
|
72
|
+
}
|
|
73
|
+
const files = await FileSystem.listFiles(commandPath);
|
|
74
|
+
const clavixCommands = files.filter((f) => this.isClavixGeneratedCommand(f));
|
|
75
|
+
let removed = 0;
|
|
76
|
+
for (const file of clavixCommands) {
|
|
77
|
+
const filePath = path.join(commandPath, file);
|
|
78
|
+
try {
|
|
79
|
+
await FileSystem.remove(filePath);
|
|
80
|
+
removed++;
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
// Log warning but continue with other files
|
|
84
|
+
console.warn(`Failed to remove ${filePath}: ${error}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return removed;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Determine if a file is a Clavix-generated command
|
|
91
|
+
* Override in adapters for provider-specific patterns
|
|
92
|
+
* @param filename The filename to check
|
|
93
|
+
* @returns true if this is a Clavix-generated command file
|
|
94
|
+
*/
|
|
95
|
+
isClavixGeneratedCommand(filename) {
|
|
96
|
+
// Default: match files with our extension
|
|
97
|
+
return filename.endsWith(this.fileExtension);
|
|
98
|
+
}
|
|
62
99
|
/**
|
|
63
100
|
* Generate commands - default implementation
|
|
64
101
|
* Creates command files in the provider's directory
|
|
@@ -20,6 +20,11 @@ export declare class ClaudeCodeAdapter extends BaseAdapter {
|
|
|
20
20
|
* Get command path for Claude Code
|
|
21
21
|
*/
|
|
22
22
|
getCommandPath(): string;
|
|
23
|
+
/**
|
|
24
|
+
* Determine if a file is a Clavix-generated command
|
|
25
|
+
* For Claude Code: Any .md file in .claude/commands/clavix/ is ours
|
|
26
|
+
*/
|
|
27
|
+
protected isClavixGeneratedCommand(filename: string): boolean;
|
|
23
28
|
/**
|
|
24
29
|
* Inject documentation blocks into CLAUDE.md
|
|
25
30
|
*/
|
|
@@ -27,6 +27,15 @@ export class ClaudeCodeAdapter extends BaseAdapter {
|
|
|
27
27
|
getCommandPath() {
|
|
28
28
|
return this.directory;
|
|
29
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* Determine if a file is a Clavix-generated command
|
|
32
|
+
* For Claude Code: Any .md file in .claude/commands/clavix/ is ours
|
|
33
|
+
*/
|
|
34
|
+
isClavixGeneratedCommand(filename) {
|
|
35
|
+
// Only remove .md files (our slash commands)
|
|
36
|
+
// This is safe because we control the entire .claude/commands/clavix/ directory
|
|
37
|
+
return filename.endsWith('.md');
|
|
38
|
+
}
|
|
30
39
|
// generateCommands is inherited from BaseAdapter
|
|
31
40
|
/**
|
|
32
41
|
* Inject documentation blocks into CLAUDE.md
|
package/dist/index 2.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { run, handle, settings } from '@oclif/core';
|
|
3
|
+
// Disable debug mode (stack traces) unless explicitly requested via DEBUG env var
|
|
4
|
+
if (!process.env.DEBUG) {
|
|
5
|
+
settings.debug = false;
|
|
6
|
+
}
|
|
7
|
+
// Run if called directly
|
|
8
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
9
|
+
run().catch(handle);
|
|
10
|
+
}
|
|
11
|
+
// Export for testing
|
|
12
|
+
export { run };
|
|
13
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -95,8 +95,23 @@ You are helping the user implement tasks from their task plan with AI assistance
|
|
|
95
95
|
|
|
96
96
|
**CRITICAL: Always use the `clavix task-complete` command**
|
|
97
97
|
|
|
98
|
+
### Why task-complete is CLI-Only
|
|
99
|
+
|
|
100
|
+
The `clavix task-complete` command requires:
|
|
101
|
+
- State validation across config files
|
|
102
|
+
- Atomic checkbox updates in tasks.md
|
|
103
|
+
- Conditional git commit execution
|
|
104
|
+
- Progress tracking and next-task resolution
|
|
105
|
+
|
|
106
|
+
Therefore it's implemented as a **CLI command** (not a slash command) and called **automatically by the agent** during implementation workflow.
|
|
107
|
+
|
|
108
|
+
**Agent Responsibility:** Run `clavix task-complete {task-id}` after implementing each task.
|
|
109
|
+
**User Responsibility:** None - agent handles task completion automatically.
|
|
110
|
+
|
|
111
|
+
### Usage
|
|
112
|
+
|
|
98
113
|
```bash
|
|
99
|
-
# After implementing a task
|
|
114
|
+
# After implementing a task, agent runs:
|
|
100
115
|
clavix task-complete {task-id}
|
|
101
116
|
|
|
102
117
|
# Example
|
|
@@ -206,6 +221,12 @@ Which option would you like?"
|
|
|
206
221
|
|
|
207
222
|
## Example Workflow
|
|
208
223
|
|
|
224
|
+
**CRITICAL WORKFLOW RULE:**
|
|
225
|
+
- Agent implements task → Agent runs `clavix task-complete` → Agent proceeds to next task
|
|
226
|
+
- User NEVER manually runs task-complete
|
|
227
|
+
- User NEVER manually edits tasks.md checkboxes
|
|
228
|
+
- This is an automated workflow, not a manual checklist
|
|
229
|
+
|
|
209
230
|
```
|
|
210
231
|
1. User runs: clavix implement
|
|
211
232
|
2. Command shows: "Next task (ID: phase-1-auth-1): Implement user authentication"
|
package/dist/types/agent.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export interface AgentAdapter {
|
|
|
9
9
|
features?: ProviderFeatures;
|
|
10
10
|
detectProject(): Promise<boolean>;
|
|
11
11
|
generateCommands(templates: CommandTemplate[]): Promise<void>;
|
|
12
|
+
removeAllCommands(): Promise<number>;
|
|
12
13
|
injectDocumentation(blocks: ManagedBlock[]): Promise<void>;
|
|
13
14
|
getCommandPath(): string;
|
|
14
15
|
getTargetFilename(name: string): string;
|
|
@@ -45,6 +45,11 @@ export async function collectLegacyCommandFiles(adapter, commandNames) {
|
|
|
45
45
|
for (const file of hyphenFiles) {
|
|
46
46
|
legacyPaths.add(path.resolve(path.join(clavixDir, file)));
|
|
47
47
|
}
|
|
48
|
+
// Remove task-complete.md (CLI-only command, not a user-facing slash command)
|
|
49
|
+
const taskCompleteFile = path.resolve(clavixDir, 'task-complete.md');
|
|
50
|
+
if (await FileSystem.exists(taskCompleteFile)) {
|
|
51
|
+
legacyPaths.add(taskCompleteFile);
|
|
52
|
+
}
|
|
48
53
|
}
|
|
49
54
|
return Array.from(legacyPaths);
|
|
50
55
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { AgentManager } from '../core/agent-manager.js';
|
|
2
|
+
/**
|
|
3
|
+
* Interactive provider selection utility
|
|
4
|
+
* Displays multi-select checkbox for all available providers
|
|
5
|
+
* Used by both init and config commands
|
|
6
|
+
*/
|
|
7
|
+
export declare function selectProviders(agentManager: AgentManager, preSelected?: string[]): Promise<string[]>;
|
|
8
|
+
//# sourceMappingURL=provider-selector.d.ts.map
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import inquirer from 'inquirer';
|
|
2
|
+
/**
|
|
3
|
+
* Interactive provider selection utility
|
|
4
|
+
* Displays multi-select checkbox for all available providers
|
|
5
|
+
* Used by both init and config commands
|
|
6
|
+
*/
|
|
7
|
+
export async function selectProviders(agentManager, preSelected = []) {
|
|
8
|
+
const { selectedProviders } = await inquirer.prompt([
|
|
9
|
+
{
|
|
10
|
+
type: 'checkbox',
|
|
11
|
+
name: 'selectedProviders',
|
|
12
|
+
message: 'Which AI tools are you using?',
|
|
13
|
+
choices: [
|
|
14
|
+
new inquirer.Separator('=== CLI Tools ==='),
|
|
15
|
+
{ name: 'Amp (.agents/commands/)', value: 'amp' },
|
|
16
|
+
{ name: 'Augment CLI (.augment/commands/clavix/)', value: 'augment' },
|
|
17
|
+
{ name: 'Codex CLI (~/.codex/prompts)', value: 'codex' },
|
|
18
|
+
{ name: 'Droid (.droid/clavix/)', value: 'droid' },
|
|
19
|
+
{ name: 'Gemini CLI (.gemini/commands/clavix/)', value: 'gemini' },
|
|
20
|
+
{ name: 'Kilocode (.kilo/clavix/)', value: 'kilocode' },
|
|
21
|
+
{ name: 'LLXPRT CLI (.llxprt/clavix/)', value: 'llxprt' },
|
|
22
|
+
{ name: 'OpenCode (.opencode/clavix/)', value: 'opencode' },
|
|
23
|
+
{ name: 'Qwen (通义灵码) (~/.qwen/commands/clavix/)', value: 'qwen' },
|
|
24
|
+
{ name: 'RooCode (.roo/clavix/)', value: 'roocode' },
|
|
25
|
+
new inquirer.Separator(),
|
|
26
|
+
new inquirer.Separator('=== IDE & IDE Extensions ==='),
|
|
27
|
+
{ name: 'Claude Code (.claude/commands/clavix/)', value: 'claude-code' },
|
|
28
|
+
{ name: 'Cline (.cline/workflows/)', value: 'cline' },
|
|
29
|
+
{ name: 'CodeBuddy (.codebuddy/prompts/)', value: 'codebuddy' },
|
|
30
|
+
{ name: 'Copilot Instructions (.github/copilot-instructions.md)', value: 'copilot-instructions' },
|
|
31
|
+
{ name: 'Crush (crush://prompts)', value: 'crush' },
|
|
32
|
+
{ name: 'Cursor (.cursor/commands/)', value: 'cursor' },
|
|
33
|
+
{ name: 'Windsurf (.windsurf/rules/)', value: 'windsurf' },
|
|
34
|
+
new inquirer.Separator(),
|
|
35
|
+
new inquirer.Separator('=== Universal Adapters ==='),
|
|
36
|
+
{ name: 'Agents (AGENTS.md - Universal)', value: 'agents-md' },
|
|
37
|
+
{ name: 'Octo (OCTO.md - Universal)', value: 'octo-md' },
|
|
38
|
+
{ name: 'Custom (custom/ directory)', value: 'custom' },
|
|
39
|
+
].map((choice) => {
|
|
40
|
+
// Keep separators as-is
|
|
41
|
+
if (choice instanceof inquirer.Separator) {
|
|
42
|
+
return choice;
|
|
43
|
+
}
|
|
44
|
+
// Add 'checked' property based on preSelected
|
|
45
|
+
return {
|
|
46
|
+
...choice,
|
|
47
|
+
checked: preSelected.includes(choice.value),
|
|
48
|
+
};
|
|
49
|
+
}),
|
|
50
|
+
validate: (answer) => {
|
|
51
|
+
if (answer.length === 0) {
|
|
52
|
+
return 'You must select at least one provider.';
|
|
53
|
+
}
|
|
54
|
+
return true;
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
]);
|
|
58
|
+
return selectedProviders;
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=provider-selector.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clavix",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.0",
|
|
4
4
|
"description": "Clavix Intelligence™ for AI coding. Automatically optimizes prompts with intent detection, quality assessment, and adaptive patterns—no framework to learn. Works with Claude Code, Cursor, Windsurf, and 19+ other AI coding tools.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: "Clavix: Task Complete"
|
|
3
|
-
description: Mark a task as completed with validation and optional git commit
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Clavix Task Complete - Mark Implementation Task Done
|
|
7
|
-
|
|
8
|
-
You are helping the user mark a task as completed during implementation workflow.
|
|
9
|
-
|
|
10
|
-
## Instructions
|
|
11
|
-
|
|
12
|
-
1. **Prerequisites**:
|
|
13
|
-
- User must have run `clavix implement` to start implementation
|
|
14
|
-
- `.clavix-implement-config.json` must exist in PRD folder
|
|
15
|
-
- `tasks.md` must be generated from `clavix plan`
|
|
16
|
-
|
|
17
|
-
2. **Verify the task ID**:
|
|
18
|
-
|
|
19
|
-
a. **Read tasks.md** to find the task ID:
|
|
20
|
-
- Task IDs are in format: `**phase-1-feature-1**`
|
|
21
|
-
- Located before each task description
|
|
22
|
-
- Example: `- [ ] **phase-1-auth-1** - Implement user authentication`
|
|
23
|
-
|
|
24
|
-
b. **Confirm with user** if task ID is unclear:
|
|
25
|
-
```
|
|
26
|
-
I see these incomplete tasks:
|
|
27
|
-
- phase-1-auth-1: Implement user authentication
|
|
28
|
-
- phase-1-auth-2: Add password reset flow
|
|
29
|
-
|
|
30
|
-
Which task did you complete? (or provide task ID)
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
3. **Mark task as completed**:
|
|
34
|
-
|
|
35
|
-
**Run the CLI command**:
|
|
36
|
-
```bash
|
|
37
|
-
clavix task-complete <task-id>
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
**Examples**:
|
|
41
|
-
```bash
|
|
42
|
-
clavix task-complete phase-1-auth-1
|
|
43
|
-
clavix task-complete phase-2-api-3
|
|
44
|
-
clavix task-complete setup-1
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
4. **The command will automatically**:
|
|
48
|
-
- Validate task exists in tasks.md
|
|
49
|
-
- Mark checkbox as `[x]` in tasks.md
|
|
50
|
-
- Update config file with completion tracking
|
|
51
|
-
- Show progress statistics (X/Y tasks completed)
|
|
52
|
-
- Create git commit (if strategy enabled and conditions met)
|
|
53
|
-
- Display next incomplete task
|
|
54
|
-
|
|
55
|
-
5. **Git auto-commit behavior**:
|
|
56
|
-
|
|
57
|
-
The command respects the git strategy configured in `clavix implement`:
|
|
58
|
-
|
|
59
|
-
- **`none`** (default): No automatic commits
|
|
60
|
-
- **`per-task`**: Creates commit after this completion
|
|
61
|
-
- **`per-5-tasks`**: Creates commit if 5 tasks completed (modulo 5 == 0)
|
|
62
|
-
- **`per-phase`**: Creates commit if all tasks in current phase are done
|
|
63
|
-
|
|
64
|
-
**Override for specific task**:
|
|
65
|
-
```bash
|
|
66
|
-
clavix task-complete phase-1-auth-1 --no-git
|
|
67
|
-
```
|
|
68
|
-
Skips git commit even if strategy is enabled (useful for experimental changes).
|
|
69
|
-
|
|
70
|
-
6. **Handle command output**:
|
|
71
|
-
|
|
72
|
-
a. **Success case**:
|
|
73
|
-
```
|
|
74
|
-
✓ Task marked as completed
|
|
75
|
-
✓ Configuration updated
|
|
76
|
-
|
|
77
|
-
Progress:
|
|
78
|
-
Completed: 5/20 tasks (25%)
|
|
79
|
-
Remaining: 15 tasks
|
|
80
|
-
|
|
81
|
-
Next Task:
|
|
82
|
-
ID: phase-1-auth-2
|
|
83
|
-
Add password reset flow
|
|
84
|
-
Reference: Full PRD section "Authentication > Password Reset"
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
b. **Already completed case**:
|
|
88
|
-
```
|
|
89
|
-
⚠ Task "phase-1-auth-1" is already marked as completed
|
|
90
|
-
|
|
91
|
-
Use --force to re-mark this task as completed.
|
|
92
|
-
|
|
93
|
-
Next Task:
|
|
94
|
-
ID: phase-1-auth-2
|
|
95
|
-
...
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
c. **All tasks complete**:
|
|
99
|
-
```
|
|
100
|
-
✓ Task marked as completed
|
|
101
|
-
🎉 All tasks completed!
|
|
102
|
-
|
|
103
|
-
Great work! All implementation tasks are done.
|
|
104
|
-
|
|
105
|
-
Hint: Run "clavix archive" to archive this project
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
7. **After task completion**:
|
|
109
|
-
|
|
110
|
-
a. **If more tasks remaining**:
|
|
111
|
-
- Acknowledge the completion
|
|
112
|
-
- Ask user: "Ready to start the next task? [display next task description]"
|
|
113
|
-
- Or: "Would you like to implement the next task now?"
|
|
114
|
-
|
|
115
|
-
b. **If all tasks complete**:
|
|
116
|
-
- Congratulate the user
|
|
117
|
-
- Suggest running `clavix archive` to archive the project
|
|
118
|
-
- Ask if they want to create a final git commit or tag
|
|
119
|
-
|
|
120
|
-
8. **Error recovery**:
|
|
121
|
-
|
|
122
|
-
**If task ID not found**:
|
|
123
|
-
```
|
|
124
|
-
✗ Task ID "phase-1-invalid" not found
|
|
125
|
-
|
|
126
|
-
Available task IDs:
|
|
127
|
-
Phase 1: Authentication
|
|
128
|
-
[ ] phase-1-auth-1 - Implement user authentication
|
|
129
|
-
[ ] phase-1-auth-2 - Add password reset flow
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
- The command lists all available tasks
|
|
133
|
-
- Ask user to verify task ID
|
|
134
|
-
- Or ask which task they meant to complete
|
|
135
|
-
|
|
136
|
-
**If config file not found**:
|
|
137
|
-
```
|
|
138
|
-
No config files found.
|
|
139
|
-
|
|
140
|
-
Hint: Run "clavix implement" first to initialize
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
- Tell user to run `clavix implement` first
|
|
144
|
-
- This starts the implementation workflow
|
|
145
|
-
|
|
146
|
-
**If file write error**:
|
|
147
|
-
```
|
|
148
|
-
✗ Failed to mark task as completed
|
|
149
|
-
|
|
150
|
-
Recovery Options:
|
|
151
|
-
1. Check if tasks.md file is readable and writable
|
|
152
|
-
2. Verify task ID matches exactly
|
|
153
|
-
3. Try running with --force flag
|
|
154
|
-
4. Check tasks.md.backup file if created
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
- Follow recovery suggestions
|
|
158
|
-
- Check file permissions
|
|
159
|
-
- Ask user if they manually edited tasks.md
|
|
160
|
-
|
|
161
|
-
## Command Flags
|
|
162
|
-
|
|
163
|
-
- `--no-git`: Skip git commit even if strategy is enabled
|
|
164
|
-
- `-f, --force`: Force completion even if already marked complete
|
|
165
|
-
- `-c, --config <path>`: Specify custom config file path (defaults to auto-discover)
|
|
166
|
-
|
|
167
|
-
## Best Practices
|
|
168
|
-
|
|
169
|
-
1. **Never manually edit tasks.md checkboxes** - Always use `clavix task-complete`
|
|
170
|
-
2. **Verify task completion** - Ensure implementation is done before marking
|
|
171
|
-
3. **Use --no-git for experiments** - Skip commits for work-in-progress changes
|
|
172
|
-
4. **Check next task** - Command automatically shows what to work on next
|
|
173
|
-
5. **Track progress** - Use progress stats to estimate remaining work
|
|
174
|
-
|
|
175
|
-
## Related Commands
|
|
176
|
-
|
|
177
|
-
- `clavix implement` - Start implementation workflow (shows current task)
|
|
178
|
-
- `clavix plan` - Generate tasks.md from PRD
|
|
179
|
-
- `clavix archive` - Archive completed project (run after all tasks done)
|
|
180
|
-
|
|
181
|
-
## Next Steps
|
|
182
|
-
|
|
183
|
-
After completing all tasks:
|
|
184
|
-
1. Run `clavix archive` to archive the implementation
|
|
185
|
-
2. Create final git commit or tag
|
|
186
|
-
3. Update CHANGELOG if needed
|
|
187
|
-
4. Merge feature branch
|
|
188
|
-
5. Deploy or release
|