@mohammadhprp/system-prompt 0.13.1 → 0.13.2
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/system-prompt.js +1 -1
- package/package.json +1 -1
- package/src/cli/args.js +51 -0
- package/src/cli/clack-ui.js +13 -0
- package/src/cli/index.js +47 -0
- package/src/cli/interactive.js +213 -0
- package/src/cli/plan.js +85 -0
- package/src/cli/tui-preferences.js +118 -0
- package/src/config/mcp.js +25 -0
- package/src/config/opencode.js +47 -0
- package/src/{agent-configs.js → config/tui.js} +0 -76
- package/src/doctor.js +10 -7
- package/src/hash.js +5 -0
- package/src/install/env.js +69 -0
- package/src/install/files.js +151 -0
- package/src/install/index.js +124 -0
- package/src/install/lock.js +159 -0
- package/src/install/merge.js +62 -0
- package/src/install/templates.js +76 -0
- package/src/item-layout.js +5 -0
- package/src/paths.js +59 -0
- package/src/ui.js +32 -0
- package/src/cli.js +0 -508
- package/src/installer.js +0 -653
package/src/cli.js
DELETED
|
@@ -1,508 +0,0 @@
|
|
|
1
|
-
import { intro, outro, confirm, multiselect, spinner, select, isCancel } from '@clack/prompts';
|
|
2
|
-
import { readFile } from 'node:fs/promises';
|
|
3
|
-
import { resolve } from 'node:path';
|
|
4
|
-
|
|
5
|
-
import { categories } from './catalog.js';
|
|
6
|
-
import { getPackageVersion, install, loadLockFile, lockToSelections } from './installer.js';
|
|
7
|
-
import { doctor } from './doctor.js';
|
|
8
|
-
import { normalizeTuiPreferences, tuiPreferencesFromConfig, TUI_THEMES } from './agent-configs.js';
|
|
9
|
-
|
|
10
|
-
const CATEGORY_FLAGS = new Set(Object.keys(categories));
|
|
11
|
-
|
|
12
|
-
const defaultUi = {
|
|
13
|
-
intro,
|
|
14
|
-
outro,
|
|
15
|
-
confirm,
|
|
16
|
-
multiselect,
|
|
17
|
-
spinner,
|
|
18
|
-
select,
|
|
19
|
-
isCancel,
|
|
20
|
-
log: (...args) => console.log(...args),
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
export function parseArgs(argv) {
|
|
24
|
-
const options = {
|
|
25
|
-
targetDir: '.opencode',
|
|
26
|
-
selections: {},
|
|
27
|
-
all: false,
|
|
28
|
-
dryRun: false,
|
|
29
|
-
force: false,
|
|
30
|
-
doctor: false,
|
|
31
|
-
includeAgentsMd: true,
|
|
32
|
-
};
|
|
33
|
-
for (let index = 0; index < argv.length; index += 1) {
|
|
34
|
-
const arg = argv[index];
|
|
35
|
-
if (arg === '--all') options.all = true;
|
|
36
|
-
else if (arg === '--dry-run') options.dryRun = true;
|
|
37
|
-
else if (arg === '--force') options.force = true;
|
|
38
|
-
else if (arg === '--doctor') options.doctor = true;
|
|
39
|
-
else if (arg === '--no-agents-md') options.includeAgentsMd = false;
|
|
40
|
-
else if (arg === '--target') {
|
|
41
|
-
const value = argv[++index];
|
|
42
|
-
if (!value || value.startsWith('--')) throw new Error('Missing value for --target');
|
|
43
|
-
options.targetDir = value;
|
|
44
|
-
}
|
|
45
|
-
else if (arg.startsWith('--target=')) {
|
|
46
|
-
const value = arg.slice('--target='.length);
|
|
47
|
-
if (!value) throw new Error('Missing value for --target');
|
|
48
|
-
options.targetDir = value;
|
|
49
|
-
}
|
|
50
|
-
else if (arg.startsWith('--')) {
|
|
51
|
-
const [flag, inlineValue] = arg.slice(2).split('=', 2);
|
|
52
|
-
if (!CATEGORY_FLAGS.has(flag)) throw new Error(`Unknown option: ${arg}`);
|
|
53
|
-
const value = inlineValue ?? argv[++index];
|
|
54
|
-
if (!value) throw new Error(`Missing value for --${flag}`);
|
|
55
|
-
options.selections[flag] = value.split(',').filter(Boolean);
|
|
56
|
-
} else {
|
|
57
|
-
throw new Error(`Unknown argument: ${arg}`);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
options.nonInteractive = options.all || Object.keys(options.selections).length > 0;
|
|
61
|
-
return options;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export function allSelections() {
|
|
65
|
-
return Object.fromEntries(Object.entries(categories).map(([category, config]) => [
|
|
66
|
-
category,
|
|
67
|
-
config.items.filter(item => !item.removed).map(item => item.id),
|
|
68
|
-
]));
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function itemNames(config, ids) {
|
|
72
|
-
return ids.map(id => config?.items?.find(item => item.id === id)?.name || id);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function buildSummary(selections) {
|
|
76
|
-
const lines = [];
|
|
77
|
-
for (const [cat, ids] of Object.entries(selections)) {
|
|
78
|
-
if (!ids?.length) continue;
|
|
79
|
-
const catConfig = categories[cat];
|
|
80
|
-
lines.push(` ${catConfig?.title || cat}: ${itemNames(catConfig, ids).join(', ')}`);
|
|
81
|
-
}
|
|
82
|
-
return lines.join('\n');
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export function computeDiff(oldLock, selections) {
|
|
86
|
-
const oldSels = lockToSelections(oldLock);
|
|
87
|
-
const added = {};
|
|
88
|
-
const removed = {};
|
|
89
|
-
const kept = {};
|
|
90
|
-
|
|
91
|
-
const allCats = [...new Set([...Object.keys(oldSels), ...Object.keys(selections)])];
|
|
92
|
-
|
|
93
|
-
for (const cat of allCats) {
|
|
94
|
-
const catConfig = categories[cat] || null;
|
|
95
|
-
const oldIds = new Set(oldSels[cat] || []);
|
|
96
|
-
const newIds = new Set(selections[cat] || []);
|
|
97
|
-
|
|
98
|
-
const addedIds = [...newIds].filter(id => !oldIds.has(id));
|
|
99
|
-
const removedIds = [...oldIds].filter(id => !newIds.has(id));
|
|
100
|
-
const keptIds = [...newIds].filter(id => oldIds.has(id));
|
|
101
|
-
|
|
102
|
-
if (addedIds.length) added[cat] = { config: catConfig, ids: addedIds };
|
|
103
|
-
if (removedIds.length) removed[cat] = { config: catConfig, ids: removedIds };
|
|
104
|
-
if (keptIds.length) kept[cat] = { config: catConfig, ids: keptIds };
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
return { added, removed, kept };
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
export function formatDiff(diff) {
|
|
111
|
-
const sections = [
|
|
112
|
-
['+ Added', diff.added],
|
|
113
|
-
['- Removed', diff.removed],
|
|
114
|
-
['~ Unchanged', diff.kept],
|
|
115
|
-
];
|
|
116
|
-
const lines = [];
|
|
117
|
-
|
|
118
|
-
for (const [label, group] of sections) {
|
|
119
|
-
const entries = Object.entries(group);
|
|
120
|
-
if (!entries.length) continue;
|
|
121
|
-
if (lines.length) lines.push('');
|
|
122
|
-
lines.push(` ${label}:`);
|
|
123
|
-
for (const [cat, data] of entries) {
|
|
124
|
-
lines.push(` ${data.config?.title || cat}: ${itemNames(data.config, data.ids).join(', ')}`);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
return lines.join('\n');
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
export function initialItemValues(visibleItems, existingIds = []) {
|
|
132
|
-
const visible = new Set(visibleItems.map(item => item.id));
|
|
133
|
-
return existingIds.filter(id => visible.has(id));
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
export function shouldPreselectAll(visibleItems, existingIds = []) {
|
|
137
|
-
if (existingIds.length) return existingIds.length === visibleItems.length;
|
|
138
|
-
return visibleItems.length <= 12;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
async function readTuiPreferences(absTarget) {
|
|
142
|
-
try {
|
|
143
|
-
return tuiPreferencesFromConfig(JSON.parse(await readFile(resolve(absTarget, 'tui.json'), 'utf-8')));
|
|
144
|
-
} catch (error) {
|
|
145
|
-
if (error.code === 'ENOENT' || error instanceof SyntaxError) return {};
|
|
146
|
-
throw error;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
async function collectTuiPreferences(existing, ui) {
|
|
151
|
-
const base = normalizeTuiPreferences(existing);
|
|
152
|
-
|
|
153
|
-
const theme = await ui.select({
|
|
154
|
-
message: 'TUI theme',
|
|
155
|
-
options: TUI_THEMES.map(value => ({
|
|
156
|
-
value,
|
|
157
|
-
label: value,
|
|
158
|
-
hint: value === 'system' ? 'Adapts to your terminal' : undefined,
|
|
159
|
-
})),
|
|
160
|
-
initialValue: base.theme,
|
|
161
|
-
});
|
|
162
|
-
if (ui.isCancel(theme)) return null;
|
|
163
|
-
|
|
164
|
-
const diffStyle = await ui.select({
|
|
165
|
-
message: 'Diff style',
|
|
166
|
-
options: [
|
|
167
|
-
{ value: 'auto', label: 'auto', hint: 'Adapts to terminal width' },
|
|
168
|
-
{ value: 'stacked', label: 'stacked', hint: 'Always single column' },
|
|
169
|
-
],
|
|
170
|
-
initialValue: base.diff_style,
|
|
171
|
-
});
|
|
172
|
-
if (ui.isCancel(diffStyle)) return null;
|
|
173
|
-
|
|
174
|
-
const cursorStyle = await ui.select({
|
|
175
|
-
message: 'Cursor style',
|
|
176
|
-
options: ['block', 'underline', 'line', 'default'].map(value => ({ value, label: value })),
|
|
177
|
-
initialValue: base.cursor.style,
|
|
178
|
-
});
|
|
179
|
-
if (ui.isCancel(cursorStyle)) return null;
|
|
180
|
-
|
|
181
|
-
const scrollSpeed = await ui.select({
|
|
182
|
-
message: 'Scroll speed',
|
|
183
|
-
options: [
|
|
184
|
-
{ value: 1, label: '1', hint: 'Slow' },
|
|
185
|
-
{ value: 2, label: '2' },
|
|
186
|
-
{ value: 3, label: '3', hint: 'Default' },
|
|
187
|
-
{ value: 4, label: '4' },
|
|
188
|
-
{ value: 5, label: '5', hint: 'Fast' },
|
|
189
|
-
],
|
|
190
|
-
initialValue: base.scroll_speed,
|
|
191
|
-
});
|
|
192
|
-
if (ui.isCancel(scrollSpeed)) return null;
|
|
193
|
-
|
|
194
|
-
const scrollAcceleration = await ui.confirm({
|
|
195
|
-
message: 'Enable scroll acceleration?',
|
|
196
|
-
initialValue: base.scroll_acceleration,
|
|
197
|
-
});
|
|
198
|
-
if (ui.isCancel(scrollAcceleration)) return null;
|
|
199
|
-
|
|
200
|
-
const mouse = await ui.confirm({
|
|
201
|
-
message: 'Enable mouse support?',
|
|
202
|
-
initialValue: base.mouse,
|
|
203
|
-
});
|
|
204
|
-
if (ui.isCancel(mouse)) return null;
|
|
205
|
-
|
|
206
|
-
const attentionEnabled = await ui.confirm({
|
|
207
|
-
message: 'Enable attention notifications and sounds?',
|
|
208
|
-
initialValue: base.attention.enabled,
|
|
209
|
-
});
|
|
210
|
-
if (ui.isCancel(attentionEnabled)) return null;
|
|
211
|
-
|
|
212
|
-
const attention = { ...base.attention, enabled: attentionEnabled };
|
|
213
|
-
if (attentionEnabled) {
|
|
214
|
-
const notifications = await ui.confirm({
|
|
215
|
-
message: 'Desktop notifications?',
|
|
216
|
-
initialValue: base.attention.notifications,
|
|
217
|
-
});
|
|
218
|
-
if (ui.isCancel(notifications)) return null;
|
|
219
|
-
|
|
220
|
-
const sound = await ui.confirm({
|
|
221
|
-
message: 'Sound alerts?',
|
|
222
|
-
initialValue: base.attention.sound,
|
|
223
|
-
});
|
|
224
|
-
if (ui.isCancel(sound)) return null;
|
|
225
|
-
|
|
226
|
-
const volume = await ui.select({
|
|
227
|
-
message: 'Alert volume',
|
|
228
|
-
options: [
|
|
229
|
-
{ value: 0.2, label: '20%' },
|
|
230
|
-
{ value: 0.4, label: '40%', hint: 'Default' },
|
|
231
|
-
{ value: 0.6, label: '60%' },
|
|
232
|
-
{ value: 0.8, label: '80%' },
|
|
233
|
-
{ value: 1, label: '100%' },
|
|
234
|
-
],
|
|
235
|
-
initialValue: base.attention.volume,
|
|
236
|
-
});
|
|
237
|
-
if (ui.isCancel(volume)) return null;
|
|
238
|
-
|
|
239
|
-
attention.notifications = notifications;
|
|
240
|
-
attention.sound = sound;
|
|
241
|
-
attention.volume = volume;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
return normalizeTuiPreferences({
|
|
245
|
-
theme,
|
|
246
|
-
diff_style: diffStyle,
|
|
247
|
-
cursor: { style: cursorStyle, blinking: base.cursor.blinking },
|
|
248
|
-
scroll_speed: scrollSpeed,
|
|
249
|
-
scroll_acceleration: scrollAcceleration,
|
|
250
|
-
mouse,
|
|
251
|
-
attention,
|
|
252
|
-
});
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
function cancelled(ui) {
|
|
256
|
-
ui.outro('Cancelled.');
|
|
257
|
-
return { status: 'cancelled' };
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
export function buildCategoryOptions() {
|
|
261
|
-
return Object.entries(categories).map(([key, cat]) => {
|
|
262
|
-
const visible = cat.items.filter(item => !item.removed);
|
|
263
|
-
return {
|
|
264
|
-
value: key,
|
|
265
|
-
label: cat.title,
|
|
266
|
-
hint: `${visible.length} ${key === 'mcps' ? 'MCPs' : cat.title.toLowerCase()}`,
|
|
267
|
-
};
|
|
268
|
-
});
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
async function installGeneratedFiles({ ui, targetDir, agentType, force, dryRun, oldLock, existingSelections, existingTuiPreferences }) {
|
|
272
|
-
const includeAgentsMd = await ui.confirm({
|
|
273
|
-
message: 'Generate AGENTS.md?',
|
|
274
|
-
initialValue: oldLock ? oldLock.includeAgentsMd : true,
|
|
275
|
-
});
|
|
276
|
-
if (ui.isCancel(includeAgentsMd)) return cancelled(ui);
|
|
277
|
-
|
|
278
|
-
const hasExisting = Object.keys(existingSelections).length > 0;
|
|
279
|
-
let keepExisting = false;
|
|
280
|
-
if (hasExisting) {
|
|
281
|
-
const removeAll = await ui.confirm({
|
|
282
|
-
message: 'Remove all previously installed components?',
|
|
283
|
-
initialValue: false,
|
|
284
|
-
});
|
|
285
|
-
if (ui.isCancel(removeAll)) return cancelled(ui);
|
|
286
|
-
keepExisting = !removeAll;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
const progress = ui.spinner();
|
|
290
|
-
progress.start('Writing files...');
|
|
291
|
-
await install({
|
|
292
|
-
targetDir,
|
|
293
|
-
agentType,
|
|
294
|
-
selections: keepExisting ? existingSelections : {},
|
|
295
|
-
includeAgentsMd,
|
|
296
|
-
oldSelections: hasExisting ? existingSelections : undefined,
|
|
297
|
-
oldLock,
|
|
298
|
-
tuiPreferences: existingTuiPreferences,
|
|
299
|
-
force,
|
|
300
|
-
dryRun,
|
|
301
|
-
});
|
|
302
|
-
progress.stop('Done.');
|
|
303
|
-
|
|
304
|
-
const installed = includeAgentsMd ? ['AGENTS.md'] : [];
|
|
305
|
-
ui.outro(`${installed.join(' and ')} written. Open them in your project to get started.`);
|
|
306
|
-
return { status: 'installed' };
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
function reportPlan({ ui, oldLock, selections, includeAgentsMd }) {
|
|
310
|
-
if (oldLock) {
|
|
311
|
-
const diffText = formatDiff(computeDiff(oldLock, selections));
|
|
312
|
-
ui.log('\n📦 Changes from previous installation:\n');
|
|
313
|
-
if (diffText) {
|
|
314
|
-
ui.log(diffText);
|
|
315
|
-
ui.log();
|
|
316
|
-
} else {
|
|
317
|
-
ui.log(' No changes — same selections as before.\n');
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
const generatedFiles = [];
|
|
321
|
-
if (includeAgentsMd) generatedFiles.push('AGENTS.md');
|
|
322
|
-
generatedFiles.push('opencode.json', 'tui.json', '.gitignore');
|
|
323
|
-
if (selections.mcps?.length) generatedFiles.push('.env');
|
|
324
|
-
if (selections.memory?.length) generatedFiles.push('memory/');
|
|
325
|
-
|
|
326
|
-
if (generatedFiles.length) {
|
|
327
|
-
ui.log(' Generated files:');
|
|
328
|
-
for (const file of generatedFiles) ui.log(` 📄 ${file}`);
|
|
329
|
-
ui.log();
|
|
330
|
-
}
|
|
331
|
-
return;
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
ui.log('\n📦 Summary of what will be installed:\n');
|
|
335
|
-
if (includeAgentsMd) ui.log(' 📄 AGENTS.md');
|
|
336
|
-
ui.log(' 📄 opencode.json');
|
|
337
|
-
ui.log(' 📄 tui.json');
|
|
338
|
-
ui.log(' 📄 .gitignore');
|
|
339
|
-
ui.log(buildSummary(selections));
|
|
340
|
-
ui.log();
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
async function collectSelections({ ui, selectedCategories, existingSelections }) {
|
|
344
|
-
const selections = {};
|
|
345
|
-
for (const cat of selectedCategories) {
|
|
346
|
-
const catConfig = categories[cat];
|
|
347
|
-
const visibleItems = catConfig.items.filter(item => !item.removed);
|
|
348
|
-
const existingIds = initialItemValues(visibleItems, existingSelections[cat] || []);
|
|
349
|
-
|
|
350
|
-
const all = await ui.confirm({
|
|
351
|
-
message: `Install all ${catConfig.title.toLowerCase()}?`,
|
|
352
|
-
initialValue: shouldPreselectAll(visibleItems, existingIds),
|
|
353
|
-
});
|
|
354
|
-
if (ui.isCancel(all)) return null;
|
|
355
|
-
|
|
356
|
-
if (all) {
|
|
357
|
-
selections[cat] = visibleItems.map(item => item.id);
|
|
358
|
-
continue;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
const picked = await ui.multiselect({
|
|
362
|
-
message: `Which ${catConfig.title.toLowerCase()} do you want?`,
|
|
363
|
-
options: visibleItems.map(item => ({
|
|
364
|
-
value: item.id,
|
|
365
|
-
label: item.deprecated ? `${item.name} (deprecated)` : item.name,
|
|
366
|
-
hint: item.deprecated ? '⚠ Deprecated — consider alternatives' : item.description,
|
|
367
|
-
})),
|
|
368
|
-
initialValues: existingIds,
|
|
369
|
-
required: true,
|
|
370
|
-
});
|
|
371
|
-
if (ui.isCancel(picked)) return null;
|
|
372
|
-
selections[cat] = picked;
|
|
373
|
-
}
|
|
374
|
-
return selections;
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
async function installSelectedItems({ ui, targetDir, agentType, force, dryRun, oldLock, existingSelections, existingTuiPreferences }, selectedCategories) {
|
|
378
|
-
const includeAgentsMd = await ui.confirm({
|
|
379
|
-
message: 'Generate AGENTS.md?',
|
|
380
|
-
initialValue: oldLock ? oldLock.includeAgentsMd : true,
|
|
381
|
-
});
|
|
382
|
-
if (ui.isCancel(includeAgentsMd)) return cancelled(ui);
|
|
383
|
-
|
|
384
|
-
const selections = await collectSelections({ ui, selectedCategories, existingSelections });
|
|
385
|
-
if (!selections) return cancelled(ui);
|
|
386
|
-
|
|
387
|
-
let tuiPreferences = existingTuiPreferences;
|
|
388
|
-
const customizeTui = await ui.confirm({
|
|
389
|
-
message: 'Customize OpenCode TUI settings?',
|
|
390
|
-
initialValue: false,
|
|
391
|
-
});
|
|
392
|
-
if (ui.isCancel(customizeTui)) return cancelled(ui);
|
|
393
|
-
if (customizeTui) {
|
|
394
|
-
const custom = await collectTuiPreferences(existingTuiPreferences, ui);
|
|
395
|
-
if (!custom) return cancelled(ui);
|
|
396
|
-
tuiPreferences = custom;
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
if (oldLock) {
|
|
400
|
-
const initialDiff = computeDiff(oldLock, selections);
|
|
401
|
-
if (Object.keys(initialDiff.removed).length) {
|
|
402
|
-
const removeDeselected = await ui.confirm({
|
|
403
|
-
message: 'Remove deselected items from the previous installation?',
|
|
404
|
-
initialValue: false,
|
|
405
|
-
});
|
|
406
|
-
if (ui.isCancel(removeDeselected)) return cancelled(ui);
|
|
407
|
-
if (!removeDeselected) {
|
|
408
|
-
for (const [cat, data] of Object.entries(initialDiff.removed)) {
|
|
409
|
-
selections[cat] = [...new Set([...(selections[cat] || []), ...data.ids])];
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
reportPlan({ ui, oldLock, selections, includeAgentsMd });
|
|
416
|
-
|
|
417
|
-
const confirmed = await ui.confirm({
|
|
418
|
-
message: 'Proceed with installation?',
|
|
419
|
-
initialValue: true,
|
|
420
|
-
});
|
|
421
|
-
if (ui.isCancel(confirmed) || !confirmed) {
|
|
422
|
-
ui.outro('Installation cancelled.');
|
|
423
|
-
return { status: 'cancelled' };
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
const progress = ui.spinner();
|
|
427
|
-
progress.start(oldLock ? 'Updating files...' : 'Installing files...');
|
|
428
|
-
|
|
429
|
-
const finalTarget = await install({
|
|
430
|
-
targetDir,
|
|
431
|
-
agentType,
|
|
432
|
-
selections,
|
|
433
|
-
includeAgentsMd,
|
|
434
|
-
oldSelections: oldLock ? existingSelections : undefined,
|
|
435
|
-
oldLock,
|
|
436
|
-
tuiPreferences,
|
|
437
|
-
force,
|
|
438
|
-
dryRun,
|
|
439
|
-
});
|
|
440
|
-
|
|
441
|
-
progress.stop('Installation complete!');
|
|
442
|
-
|
|
443
|
-
const fileCount = Object.values(selections).reduce((sum, ids) => sum + (ids?.length || 0), 0);
|
|
444
|
-
const verb = oldLock ? 'Updated' : 'Installed';
|
|
445
|
-
ui.outro(`${verb} ${fileCount} components to ${finalTarget}
|
|
446
|
-
|
|
447
|
-
Next steps:
|
|
448
|
-
${agentType === 'opencode' ? '- Open your project in OpenCode — it will read opencode.json and AGENTS.md automatically' : '- Point your AI coding agent to AGENTS.md as the entry point'}
|
|
449
|
-
- Run /help in your agent to see available commands
|
|
450
|
-
`);
|
|
451
|
-
return { status: 'installed', target: finalTarget };
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
export async function runInteractive({ targetDir, force = false, dryRun = false, agentType = 'opencode', ui = defaultUi }) {
|
|
455
|
-
const absTarget = resolve(process.cwd(), targetDir);
|
|
456
|
-
const oldLock = await loadLockFile(absTarget);
|
|
457
|
-
const existingSelections = lockToSelections(oldLock);
|
|
458
|
-
const existingTuiPreferences = await readTuiPreferences(absTarget);
|
|
459
|
-
const context = { ui, targetDir, agentType, force, dryRun, oldLock, existingSelections, existingTuiPreferences };
|
|
460
|
-
|
|
461
|
-
const selectedCategories = await ui.multiselect({
|
|
462
|
-
message: 'What would you like to install?',
|
|
463
|
-
options: buildCategoryOptions(),
|
|
464
|
-
initialValues: Object.keys(existingSelections),
|
|
465
|
-
required: false,
|
|
466
|
-
});
|
|
467
|
-
if (ui.isCancel(selectedCategories)) return cancelled(ui);
|
|
468
|
-
|
|
469
|
-
if (!selectedCategories?.length) return installGeneratedFiles(context);
|
|
470
|
-
return installSelectedItems(context, selectedCategories);
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
export async function runNonInteractive({ targetDir, selections, all, includeAgentsMd, force = false, dryRun = false, agentType = 'opencode', ui = defaultUi }) {
|
|
474
|
-
const resolvedSelections = all ? allSelections() : selections;
|
|
475
|
-
const absTarget = resolve(process.cwd(), targetDir);
|
|
476
|
-
const oldLock = await loadLockFile(absTarget);
|
|
477
|
-
if (!dryRun) ui.log(`Installing selected components into ${absTarget}`);
|
|
478
|
-
await install({
|
|
479
|
-
targetDir,
|
|
480
|
-
agentType,
|
|
481
|
-
selections: resolvedSelections,
|
|
482
|
-
includeAgentsMd,
|
|
483
|
-
oldSelections: oldLock ? lockToSelections(oldLock) : undefined,
|
|
484
|
-
oldLock,
|
|
485
|
-
force,
|
|
486
|
-
dryRun,
|
|
487
|
-
});
|
|
488
|
-
ui.log(dryRun ? 'Dry run complete.' : 'Installation complete.');
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
export async function main(argv = process.argv.slice(2)) {
|
|
492
|
-
const args = parseArgs(argv);
|
|
493
|
-
if (args.doctor) {
|
|
494
|
-
const healthy = await doctor(args.targetDir);
|
|
495
|
-
if (!healthy) process.exitCode = 1;
|
|
496
|
-
return healthy;
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
const version = await getPackageVersion();
|
|
500
|
-
defaultUi.intro(`System prompt (v${version})`);
|
|
501
|
-
|
|
502
|
-
const agentType = 'opencode';
|
|
503
|
-
|
|
504
|
-
if (args.nonInteractive) {
|
|
505
|
-
return runNonInteractive({ ...args, agentType });
|
|
506
|
-
}
|
|
507
|
-
return runInteractive({ ...args, agentType });
|
|
508
|
-
}
|