@contextium/cli 0.6.7 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +149 -227
- package/dist/commands/marketplace.d.ts +3 -0
- package/dist/commands/marketplace.d.ts.map +1 -0
- package/dist/commands/marketplace.js +178 -0
- package/dist/commands/marketplace.js.map +1 -0
- package/dist/commands/new-file.d.ts.map +1 -1
- package/dist/commands/new-file.js +4 -0
- package/dist/commands/new-file.js.map +1 -1
- package/dist/commands/setup-claude.js +3 -3
- package/dist/commands/setup.d.ts +3 -0
- package/dist/commands/setup.d.ts.map +1 -0
- package/dist/commands/setup.js +9 -0
- package/dist/commands/setup.js.map +1 -0
- package/dist/index.js +44 -36
- package/dist/index.js.map +1 -1
- package/dist/lib/api-client.d.ts.map +1 -1
- package/dist/lib/api-client.js +2 -1
- package/dist/lib/api-client.js.map +1 -1
- package/dist/lib/claude-permissions.d.ts +4 -0
- package/dist/lib/claude-permissions.d.ts.map +1 -0
- package/dist/lib/claude-permissions.js +41 -0
- package/dist/lib/claude-permissions.js.map +1 -0
- package/dist/lib/msal.js +18 -18
- package/dist/lib/wizard-state.d.ts +22 -0
- package/dist/lib/wizard-state.d.ts.map +1 -0
- package/dist/lib/wizard-state.js +23 -0
- package/dist/lib/wizard-state.js.map +1 -0
- package/dist/lib/wizard.d.ts +4 -0
- package/dist/lib/wizard.d.ts.map +1 -0
- package/dist/lib/wizard.js +910 -0
- package/dist/lib/wizard.js.map +1 -0
- package/package.json +62 -58
|
@@ -0,0 +1,910 @@
|
|
|
1
|
+
import * as p from '@clack/prompts';
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { ApiClient } from './api-client.js';
|
|
5
|
+
import { loadConfig, saveConfig } from './config.js';
|
|
6
|
+
import { getCachedAccount, loginInteractive, getAccessToken } from './msal.js';
|
|
7
|
+
import { addClaudePermission, isClaudePermissionConfigured } from './claude-permissions.js';
|
|
8
|
+
import { createInitialState } from './wizard-state.js';
|
|
9
|
+
const DEFAULT_API_URL = 'https://api.contextium.io/api/v1';
|
|
10
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
11
|
+
function cancel(msg = 'Setup cancelled. Run "contextium setup" again at any time.') {
|
|
12
|
+
p.cancel(msg);
|
|
13
|
+
process.exit(0);
|
|
14
|
+
}
|
|
15
|
+
function guard(value) {
|
|
16
|
+
if (p.isCancel(value))
|
|
17
|
+
cancel();
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
function scoreMatch(description, name, agentDesc = '') {
|
|
21
|
+
const words = description.toLowerCase().split(/\W+/).filter(w => w.length > 3);
|
|
22
|
+
const target = `${name} ${agentDesc}`.toLowerCase();
|
|
23
|
+
return words.filter(w => target.includes(w)).length;
|
|
24
|
+
}
|
|
25
|
+
function suggestLibraries(description) {
|
|
26
|
+
const d = description.toLowerCase();
|
|
27
|
+
const domains = [
|
|
28
|
+
{
|
|
29
|
+
keywords: ['market', 'campaign', 'brand', 'advertis', 'social', 'content', 'copy', 'creative'],
|
|
30
|
+
libraries: [
|
|
31
|
+
{ name: 'Campaign Assets', purpose: 'campaign briefs, creative files, copy' },
|
|
32
|
+
{ name: 'Brand Guidelines', purpose: 'tone of voice, logo usage, colour palette' },
|
|
33
|
+
],
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
keywords: ['api', 'developer', 'technical', 'documentation', 'code', 'engineer', 'software'],
|
|
37
|
+
libraries: [
|
|
38
|
+
{ name: 'API Documentation', purpose: 'endpoints, authentication, examples' },
|
|
39
|
+
{ name: 'Technical Specs', purpose: 'architecture, requirements, decisions' },
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
keywords: ['product', 'feature', 'roadmap', 'launch', 'user research', 'pricing', 'spec'],
|
|
44
|
+
libraries: [
|
|
45
|
+
{ name: 'Product Documentation', purpose: 'features, requirements, roadmap' },
|
|
46
|
+
{ name: 'User Research', purpose: 'interviews, insights, personas' },
|
|
47
|
+
],
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
keywords: ['sales', 'customer', 'crm', 'deal', 'pipeline', 'prospect', 'pitch'],
|
|
51
|
+
libraries: [
|
|
52
|
+
{ name: 'Sales Playbook', purpose: 'pitches, objection handling, scripts' },
|
|
53
|
+
{ name: 'Customer Intelligence', purpose: 'personas, case studies, win/loss' },
|
|
54
|
+
],
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
keywords: ['hr', 'hiring', 'recruit', 'onboard', 'employee', 'people', 'talent'],
|
|
58
|
+
libraries: [
|
|
59
|
+
{ name: 'HR Documentation', purpose: 'policies, processes, job descriptions' },
|
|
60
|
+
{ name: 'Onboarding Materials', purpose: 'guides, checklists, welcome docs' },
|
|
61
|
+
],
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
keywords: ['legal', 'compliance', 'contract', 'policy', 'regulation', 'gdpr', 'terms'],
|
|
65
|
+
libraries: [
|
|
66
|
+
{ name: 'Legal & Compliance', purpose: 'contracts, policies, regulatory docs' },
|
|
67
|
+
],
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
keywords: ['finance', 'budget', 'forecast', 'report', 'account', 'revenue', 'cost'],
|
|
71
|
+
libraries: [
|
|
72
|
+
{ name: 'Financial Documentation', purpose: 'budgets, forecasts, reports' },
|
|
73
|
+
],
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
keywords: ['design', 'ux', 'ui', 'figma', 'prototype', 'wireframe', 'user experience'],
|
|
77
|
+
libraries: [
|
|
78
|
+
{ name: 'Design System', purpose: 'components, patterns, guidelines' },
|
|
79
|
+
{ name: 'UX Research', purpose: 'usability tests, user journeys, findings' },
|
|
80
|
+
],
|
|
81
|
+
},
|
|
82
|
+
];
|
|
83
|
+
const scored = domains.map(domain => ({
|
|
84
|
+
libraries: domain.libraries,
|
|
85
|
+
score: domain.keywords.filter(k => d.includes(k)).length,
|
|
86
|
+
})).filter(d => d.score > 0).sort((a, b) => b.score - a.score);
|
|
87
|
+
if (scored.length > 0) {
|
|
88
|
+
return scored[0].libraries;
|
|
89
|
+
}
|
|
90
|
+
// Generic fallback
|
|
91
|
+
return [{ name: 'Project Documentation', purpose: 'key documents, references, notes' }];
|
|
92
|
+
}
|
|
93
|
+
// ─── API client factory ───────────────────────────────────────────────────────
|
|
94
|
+
function makeClient(state) {
|
|
95
|
+
return new ApiClient(state.apiKey || '', state.apiUrl);
|
|
96
|
+
}
|
|
97
|
+
// ─── Step 0 — Silent state detection ─────────────────────────────────────────
|
|
98
|
+
async function detectState(force) {
|
|
99
|
+
const account = await getCachedAccount();
|
|
100
|
+
const token = account ? await getAccessToken() : null;
|
|
101
|
+
const existingConfig = await loadConfig().catch(() => null);
|
|
102
|
+
const apiKey = existingConfig?.api_key || process.env.CONTEXTIUM_API_KEY || '';
|
|
103
|
+
const isAuthed = !!(token || apiKey);
|
|
104
|
+
const hasConfig = await fs.access(path.join(process.cwd(), '.contextiumrc')).then(() => true).catch(() => false);
|
|
105
|
+
const hasWorkspace = !!(existingConfig?.workspace_id);
|
|
106
|
+
const isClaudeConfigured = await isClaudePermissionConfigured('global') || await isClaudePermissionConfigured('project');
|
|
107
|
+
if (!force && isAuthed && hasConfig && hasWorkspace && isClaudeConfigured) {
|
|
108
|
+
p.intro('Contextium — already configured');
|
|
109
|
+
p.note([
|
|
110
|
+
`Workspace: ${existingConfig?.workspace || existingConfig?.workspace_id || 'set'}`,
|
|
111
|
+
`Config: .contextiumrc`,
|
|
112
|
+
`Claude: permissions configured`,
|
|
113
|
+
'',
|
|
114
|
+
'Run with --force to reconfigure.',
|
|
115
|
+
].join('\n'), 'You\'re all set!');
|
|
116
|
+
p.outro('Run "contextium setup --force" to reconfigure everything.');
|
|
117
|
+
process.exit(0);
|
|
118
|
+
}
|
|
119
|
+
return { isAuthed, account, hasConfig, hasWorkspace, isClaudeConfigured, existingConfig };
|
|
120
|
+
}
|
|
121
|
+
// ─── Step 2 — Authentication ──────────────────────────────────────────────────
|
|
122
|
+
async function stepAuth(state, detected) {
|
|
123
|
+
if (detected.isAuthed && !state.account) {
|
|
124
|
+
state.account = detected.account;
|
|
125
|
+
state.authMethod = 'existing';
|
|
126
|
+
if (detected.existingConfig?.api_key)
|
|
127
|
+
state.apiKey = detected.existingConfig.api_key;
|
|
128
|
+
if (detected.existingConfig?.api_url)
|
|
129
|
+
state.apiUrl = detected.existingConfig.api_url;
|
|
130
|
+
p.log.success(`Already authenticated${detected.account?.name ? ` as ${detected.account.name}` : ''}`);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
const method = guard(await p.select({
|
|
134
|
+
message: 'How would you like to authenticate?',
|
|
135
|
+
options: [
|
|
136
|
+
{ value: 'entra', label: 'Browser login (Microsoft Entra ID)', hint: 'recommended' },
|
|
137
|
+
{ value: 'apikey', label: 'API key' },
|
|
138
|
+
],
|
|
139
|
+
}));
|
|
140
|
+
if (method === 'entra') {
|
|
141
|
+
state.authMethod = 'entra';
|
|
142
|
+
const s = p.spinner();
|
|
143
|
+
s.start('Opening browser for authentication...');
|
|
144
|
+
try {
|
|
145
|
+
const result = await loginInteractive();
|
|
146
|
+
state.account = result.account;
|
|
147
|
+
s.stop(`Authenticated as ${result.account?.name || result.account?.username || 'unknown'}`);
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
s.stop();
|
|
151
|
+
if (error.errorCode === 'user_cancelled' || error.message?.includes('cancelled')) {
|
|
152
|
+
cancel('Login cancelled.');
|
|
153
|
+
}
|
|
154
|
+
throw new Error(`Authentication failed: ${error.message}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
state.authMethod = 'apikey';
|
|
159
|
+
let valid = false;
|
|
160
|
+
while (!valid) {
|
|
161
|
+
const key = guard(await p.text({
|
|
162
|
+
message: 'Paste your API key:',
|
|
163
|
+
validate: v => (!v ? 'API key is required' : undefined),
|
|
164
|
+
}));
|
|
165
|
+
const s = p.spinner();
|
|
166
|
+
s.start('Validating API key...');
|
|
167
|
+
try {
|
|
168
|
+
const client = new ApiClient(key, state.apiUrl);
|
|
169
|
+
await client.get('/workspaces');
|
|
170
|
+
state.apiKey = key;
|
|
171
|
+
s.stop('API key validated');
|
|
172
|
+
valid = true;
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
s.stop();
|
|
176
|
+
p.log.error('Invalid API key — please try again.');
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
// ─── Step 3 — Workspace ───────────────────────────────────────────────────────
|
|
182
|
+
async function stepWorkspace(state, _detected) {
|
|
183
|
+
const s = p.spinner();
|
|
184
|
+
s.start('Loading workspaces...');
|
|
185
|
+
const client = makeClient(state);
|
|
186
|
+
const { data: wsData } = await client.get('/workspaces');
|
|
187
|
+
state.workspaces = wsData.data || wsData || [];
|
|
188
|
+
s.stop();
|
|
189
|
+
if (state.workspaces.length === 0) {
|
|
190
|
+
p.log.warn('No workspaces found in your account.');
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
if (state.workspaces.length === 1) {
|
|
194
|
+
state.selectedWorkspace = state.workspaces[0];
|
|
195
|
+
p.log.success(`Workspace: ${state.selectedWorkspace.name}`);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
const selected = guard(await p.select({
|
|
199
|
+
message: 'Which workspace would you like to work with today?',
|
|
200
|
+
options: state.workspaces.map((ws) => ({
|
|
201
|
+
value: ws.id,
|
|
202
|
+
label: ws.name,
|
|
203
|
+
hint: ws.slug,
|
|
204
|
+
})),
|
|
205
|
+
}));
|
|
206
|
+
state.selectedWorkspace = state.workspaces.find((ws) => ws.id === selected) || null;
|
|
207
|
+
}
|
|
208
|
+
// ─── Step 4 — New or existing? ────────────────────────────────────────────────
|
|
209
|
+
async function stepProjectMode(state) {
|
|
210
|
+
const mode = guard(await p.select({
|
|
211
|
+
message: 'Are you starting a new project or continuing with an existing one?',
|
|
212
|
+
options: [
|
|
213
|
+
{ value: 'existing', label: 'Continue with an existing project' },
|
|
214
|
+
{ value: 'new', label: 'Start a new project' },
|
|
215
|
+
],
|
|
216
|
+
}));
|
|
217
|
+
state.projectMode = mode;
|
|
218
|
+
}
|
|
219
|
+
// ─── Step 4a — Existing project path ─────────────────────────────────────────
|
|
220
|
+
async function stepExistingProject(state) {
|
|
221
|
+
const client = makeClient(state);
|
|
222
|
+
const ws = state.selectedWorkspace;
|
|
223
|
+
const s = p.spinner();
|
|
224
|
+
s.start('Looking for workflows...');
|
|
225
|
+
let workflows = [];
|
|
226
|
+
try {
|
|
227
|
+
const { data } = await client.get(`/workspaces/${ws.id}/workflows`);
|
|
228
|
+
workflows = data.data || data || [];
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
workflows = [];
|
|
232
|
+
}
|
|
233
|
+
s.stop();
|
|
234
|
+
if (workflows.length === 0) {
|
|
235
|
+
const action = guard(await p.select({
|
|
236
|
+
message: 'No workflows found in this workspace. What would you like to do?',
|
|
237
|
+
options: [
|
|
238
|
+
{ value: 'build', label: 'Walk me through building one now' },
|
|
239
|
+
{ value: 'exit', label: 'Exit — I\'ll set things up manually' },
|
|
240
|
+
],
|
|
241
|
+
}));
|
|
242
|
+
if (action === 'exit') {
|
|
243
|
+
p.outro('No problem. Run "contextium setup" anytime to come back.');
|
|
244
|
+
process.exit(0);
|
|
245
|
+
}
|
|
246
|
+
return false; // drop into new project path
|
|
247
|
+
}
|
|
248
|
+
const options = [
|
|
249
|
+
...workflows.map((wf) => {
|
|
250
|
+
const parts = [];
|
|
251
|
+
if (wf.agents?.length)
|
|
252
|
+
parts.push(`${wf.agents.length} agent${wf.agents.length > 1 ? 's' : ''}`);
|
|
253
|
+
if (wf.projects?.length)
|
|
254
|
+
parts.push(`${wf.projects.length} librar${wf.projects.length > 1 ? 'ies' : 'y'}`);
|
|
255
|
+
if (wf.skills?.length)
|
|
256
|
+
parts.push(`${wf.skills.length} skill${wf.skills.length > 1 ? 's' : ''}`);
|
|
257
|
+
const hint = parts.join(' + ');
|
|
258
|
+
return {
|
|
259
|
+
value: wf.id,
|
|
260
|
+
label: wf.name,
|
|
261
|
+
...(hint ? { hint } : {}),
|
|
262
|
+
};
|
|
263
|
+
}),
|
|
264
|
+
{ value: '__none__', label: 'None of these — start fresh' },
|
|
265
|
+
];
|
|
266
|
+
const selected = guard(await p.select({
|
|
267
|
+
message: `Found ${workflows.length} workflow${workflows.length > 1 ? 's' : ''} — which would you like to load?`,
|
|
268
|
+
options,
|
|
269
|
+
}));
|
|
270
|
+
if (selected === '__none__')
|
|
271
|
+
return false;
|
|
272
|
+
state.selectedWorkflow = workflows.find((wf) => wf.id === selected) || null;
|
|
273
|
+
if (state.selectedWorkflow) {
|
|
274
|
+
p.note([
|
|
275
|
+
`Workflow: ${state.selectedWorkflow.name}`,
|
|
276
|
+
state.selectedWorkflow.agents?.length ? `Agents: ${state.selectedWorkflow.agents.map((a) => a.name).join(', ')}` : '',
|
|
277
|
+
state.selectedWorkflow.projects?.length ? `Libraries: ${state.selectedWorkflow.projects.map((p) => p.name).join(', ')}` : '',
|
|
278
|
+
state.selectedWorkflow.skills?.length ? `Skills: ${state.selectedWorkflow.skills.map((s) => s.title || s.name).join(', ')}` : '',
|
|
279
|
+
].filter(Boolean).join('\n'), 'Workflow loaded');
|
|
280
|
+
}
|
|
281
|
+
return true; // done — skip remaining steps
|
|
282
|
+
}
|
|
283
|
+
// ─── Step 4b — New project path ───────────────────────────────────────────────
|
|
284
|
+
async function stepNewProject(state) {
|
|
285
|
+
const description = guard(await p.text({
|
|
286
|
+
message: 'What is this project about?',
|
|
287
|
+
placeholder: 'e.g. Marketing campaign for a new product launch',
|
|
288
|
+
validate: v => (!v ? 'Please describe your project' : undefined),
|
|
289
|
+
}));
|
|
290
|
+
state.projectDescription = description;
|
|
291
|
+
const suggested = suggestLibraries(state.projectDescription);
|
|
292
|
+
p.note([
|
|
293
|
+
'Based on your project, here\'s a suggested layout:',
|
|
294
|
+
'',
|
|
295
|
+
...suggested.map(l => ` • "${l.name}" — ${l.purpose}`),
|
|
296
|
+
].join('\n'), 'Suggested libraries');
|
|
297
|
+
const libraryAction = guard(await p.select({
|
|
298
|
+
message: 'What would you like to do with this layout?',
|
|
299
|
+
options: [
|
|
300
|
+
{ value: 'create', label: `Create ${suggested.length > 1 ? 'these libraries' : 'this library'}` },
|
|
301
|
+
{ value: 'custom', label: 'Let me specify different names' },
|
|
302
|
+
{ value: 'skip', label: 'Skip — I\'ll create libraries myself' },
|
|
303
|
+
],
|
|
304
|
+
}));
|
|
305
|
+
let librariesToCreate = suggested;
|
|
306
|
+
if (libraryAction === 'custom') {
|
|
307
|
+
const count = guard(await p.select({
|
|
308
|
+
message: 'How many libraries do you need?',
|
|
309
|
+
options: [
|
|
310
|
+
{ value: '1', label: '1 library' },
|
|
311
|
+
{ value: '2', label: '2 libraries' },
|
|
312
|
+
{ value: '3', label: '3 libraries' },
|
|
313
|
+
],
|
|
314
|
+
}));
|
|
315
|
+
librariesToCreate = [];
|
|
316
|
+
for (let i = 0; i < parseInt(count); i++) {
|
|
317
|
+
const name = guard(await p.text({
|
|
318
|
+
message: `Library ${i + 1} name:`,
|
|
319
|
+
validate: v => (!v ? 'Name is required' : undefined),
|
|
320
|
+
}));
|
|
321
|
+
const purpose = guard(await p.text({
|
|
322
|
+
message: `Library ${i + 1} description (optional):`,
|
|
323
|
+
}));
|
|
324
|
+
librariesToCreate.push({ name: name, purpose: purpose || '' });
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
if (libraryAction !== 'skip') {
|
|
328
|
+
const client = makeClient(state);
|
|
329
|
+
const ws = state.selectedWorkspace;
|
|
330
|
+
const s = p.spinner();
|
|
331
|
+
s.start(`Creating ${librariesToCreate.length > 1 ? 'libraries' : 'library'}...`);
|
|
332
|
+
for (const lib of librariesToCreate) {
|
|
333
|
+
try {
|
|
334
|
+
const { data } = await client.post(`/workspaces/${ws.id}/projects`, {
|
|
335
|
+
name: lib.name,
|
|
336
|
+
description: lib.purpose,
|
|
337
|
+
});
|
|
338
|
+
state.createdLibraries.push(data.data || data);
|
|
339
|
+
}
|
|
340
|
+
catch (error) {
|
|
341
|
+
s.stop();
|
|
342
|
+
p.log.warn(`Could not create "${lib.name}": ${error.message}`);
|
|
343
|
+
s.start('Continuing...');
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
s.stop(`Created ${state.createdLibraries.length} ${state.createdLibraries.length > 1 ? 'libraries' : 'library'}`);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
// ─── Step 5 — Agents ─────────────────────────────────────────────────────────
|
|
350
|
+
async function stepAgents(state) {
|
|
351
|
+
const client = makeClient(state);
|
|
352
|
+
const ws = state.selectedWorkspace;
|
|
353
|
+
const s = p.spinner();
|
|
354
|
+
s.start('Checking agents...');
|
|
355
|
+
let agents = [];
|
|
356
|
+
try {
|
|
357
|
+
const { data } = await client.get(`/workspaces/${ws.id}/agents`);
|
|
358
|
+
agents = data.data || data || [];
|
|
359
|
+
}
|
|
360
|
+
catch {
|
|
361
|
+
agents = [];
|
|
362
|
+
}
|
|
363
|
+
s.stop();
|
|
364
|
+
// Score agents against project description
|
|
365
|
+
const scored = agents
|
|
366
|
+
.map(a => ({ agent: a, score: scoreMatch(state.projectDescription, a.name, a.description || '') }))
|
|
367
|
+
.filter(a => a.score > 0)
|
|
368
|
+
.sort((a, b) => b.score - a.score);
|
|
369
|
+
const relevantAgents = scored.slice(0, 3).map(s => s.agent);
|
|
370
|
+
if (relevantAgents.length > 0) {
|
|
371
|
+
const options = [
|
|
372
|
+
...relevantAgents.map((a) => ({
|
|
373
|
+
value: a.id,
|
|
374
|
+
label: a.name,
|
|
375
|
+
hint: a.description || undefined,
|
|
376
|
+
})),
|
|
377
|
+
{ value: '__marketplace__', label: 'None of these — look in the marketplace' },
|
|
378
|
+
{ value: '__custom__', label: 'Create a custom agent' },
|
|
379
|
+
{ value: '__skip__', label: 'Skip agents for now' },
|
|
380
|
+
];
|
|
381
|
+
const selected = guard(await p.select({
|
|
382
|
+
message: `Agents in your workspace that match this project:`,
|
|
383
|
+
options,
|
|
384
|
+
}));
|
|
385
|
+
if (selected === '__skip__')
|
|
386
|
+
return;
|
|
387
|
+
if (selected === '__marketplace__')
|
|
388
|
+
return await stepAgentsMarketplace(state, client, ws);
|
|
389
|
+
if (selected === '__custom__')
|
|
390
|
+
return await stepCreateAgent(state, client, ws);
|
|
391
|
+
const agent = agents.find((a) => a.id === selected);
|
|
392
|
+
if (agent)
|
|
393
|
+
state.selectedAgents.push(agent);
|
|
394
|
+
}
|
|
395
|
+
else {
|
|
396
|
+
p.log.info('No matching agents found in your workspace.');
|
|
397
|
+
const action = guard(await p.select({
|
|
398
|
+
message: 'What would you like to do?',
|
|
399
|
+
options: [
|
|
400
|
+
{ value: 'marketplace', label: 'Browse Contextium Marketplace for agents' },
|
|
401
|
+
{ value: 'custom', label: 'Create a custom agent' },
|
|
402
|
+
{ value: 'skip', label: 'Skip for now' },
|
|
403
|
+
],
|
|
404
|
+
}));
|
|
405
|
+
if (action === 'marketplace')
|
|
406
|
+
return await stepAgentsMarketplace(state, client, ws);
|
|
407
|
+
if (action === 'custom')
|
|
408
|
+
return await stepCreateAgent(state, client, ws);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
async function stepAgentsMarketplace(state, client, ws) {
|
|
412
|
+
const query = state.projectDescription.split(' ').slice(0, 4).join(' ');
|
|
413
|
+
const s = p.spinner();
|
|
414
|
+
s.start('Searching marketplace for agents...');
|
|
415
|
+
let listings = [];
|
|
416
|
+
try {
|
|
417
|
+
const { data } = await client.get('/marketplace/listings', { params: { q: query, type: 'agent' } });
|
|
418
|
+
listings = (data.data || data || []).slice(0, 5);
|
|
419
|
+
}
|
|
420
|
+
catch {
|
|
421
|
+
listings = [];
|
|
422
|
+
}
|
|
423
|
+
s.stop();
|
|
424
|
+
if (listings.length === 0) {
|
|
425
|
+
p.log.info('No matching agents found in the marketplace.');
|
|
426
|
+
const createOne = guard(await p.confirm({ message: 'Would you like to create a custom agent instead?' }));
|
|
427
|
+
if (createOne)
|
|
428
|
+
return await stepCreateAgent(state, client, ws);
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
const selected = guard(await p.select({
|
|
432
|
+
message: `Marketplace agents for "${query}":`,
|
|
433
|
+
options: [
|
|
434
|
+
...listings.map((l) => ({
|
|
435
|
+
value: l.slug,
|
|
436
|
+
label: l.name,
|
|
437
|
+
hint: l.shortDescription || undefined,
|
|
438
|
+
})),
|
|
439
|
+
{ value: '__skip__', label: 'Skip — none of these' },
|
|
440
|
+
],
|
|
441
|
+
}));
|
|
442
|
+
if (selected === '__skip__')
|
|
443
|
+
return;
|
|
444
|
+
const s2 = p.spinner();
|
|
445
|
+
s2.start(`Installing ${selected}...`);
|
|
446
|
+
try {
|
|
447
|
+
const { data } = await client.post(`/marketplace/listings/${selected}/import`, { workspaceId: ws.id });
|
|
448
|
+
const result = data.data || data;
|
|
449
|
+
const agent = result.agent || result;
|
|
450
|
+
state.selectedAgents.push(agent);
|
|
451
|
+
state.newAgents.push(agent);
|
|
452
|
+
s2.stop(`Installed: ${agent.name || selected}`);
|
|
453
|
+
}
|
|
454
|
+
catch (error) {
|
|
455
|
+
s2.stop();
|
|
456
|
+
p.log.error(`Install failed: ${error.message}`);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
async function stepCreateAgent(state, client, ws) {
|
|
460
|
+
const name = guard(await p.text({
|
|
461
|
+
message: 'Agent name:',
|
|
462
|
+
validate: v => (!v ? 'Name is required' : undefined),
|
|
463
|
+
}));
|
|
464
|
+
const description = guard(await p.text({
|
|
465
|
+
message: 'Description:',
|
|
466
|
+
placeholder: 'What does this agent help with?',
|
|
467
|
+
}));
|
|
468
|
+
const prompt = guard(await p.text({
|
|
469
|
+
message: 'System prompt:',
|
|
470
|
+
placeholder: `You are ${name}. ${description}`,
|
|
471
|
+
}));
|
|
472
|
+
const s = p.spinner();
|
|
473
|
+
s.start('Creating agent...');
|
|
474
|
+
try {
|
|
475
|
+
const { data } = await client.post(`/workspaces/${ws.id}/agents`, {
|
|
476
|
+
name,
|
|
477
|
+
description,
|
|
478
|
+
systemPrompt: prompt || `You are ${name}. ${description}`,
|
|
479
|
+
});
|
|
480
|
+
const agent = data.data || data;
|
|
481
|
+
state.selectedAgents.push(agent);
|
|
482
|
+
state.newAgents.push(agent);
|
|
483
|
+
s.stop(`Agent created: ${agent.name}`);
|
|
484
|
+
}
|
|
485
|
+
catch (error) {
|
|
486
|
+
s.stop();
|
|
487
|
+
p.log.error(`Failed to create agent: ${error.message}`);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
// ─── Step 6 — Skills ─────────────────────────────────────────────────────────
|
|
491
|
+
async function stepSkills(state) {
|
|
492
|
+
const client = makeClient(state);
|
|
493
|
+
const ws = state.selectedWorkspace;
|
|
494
|
+
const s = p.spinner();
|
|
495
|
+
s.start('Checking skills...');
|
|
496
|
+
let skills = [];
|
|
497
|
+
try {
|
|
498
|
+
const { data: projectsData } = await client.get(`/workspaces/${ws.id}/projects`);
|
|
499
|
+
const projects = projectsData.data || projectsData || [];
|
|
500
|
+
const skillsProject = projects.find((p) => p.isSystem && p.systemType === 'skills');
|
|
501
|
+
if (skillsProject) {
|
|
502
|
+
const { data: filesData } = await client.get(`/projects/${skillsProject.id}/files`);
|
|
503
|
+
skills = (filesData.data || filesData || []).filter((f) => !f.deletedAt);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
catch {
|
|
507
|
+
skills = [];
|
|
508
|
+
}
|
|
509
|
+
s.stop();
|
|
510
|
+
const scored = skills
|
|
511
|
+
.map(sk => ({ skill: sk, score: scoreMatch(state.projectDescription, sk.title, '') }))
|
|
512
|
+
.filter(s => s.score > 0)
|
|
513
|
+
.sort((a, b) => b.score - a.score);
|
|
514
|
+
const relevantSkills = scored.slice(0, 3).map(s => s.skill);
|
|
515
|
+
if (relevantSkills.length > 0) {
|
|
516
|
+
const selected = guard(await p.select({
|
|
517
|
+
message: 'Relevant skills found in your workspace:',
|
|
518
|
+
options: [
|
|
519
|
+
...relevantSkills.map((sk) => ({
|
|
520
|
+
value: sk.id,
|
|
521
|
+
label: sk.title,
|
|
522
|
+
})),
|
|
523
|
+
{ value: '__marketplace__', label: 'Browse marketplace for more skills' },
|
|
524
|
+
{ value: '__custom__', label: 'Create a custom skill' },
|
|
525
|
+
{ value: '__skip__', label: 'Skip skills for now' },
|
|
526
|
+
],
|
|
527
|
+
}));
|
|
528
|
+
if (selected === '__skip__')
|
|
529
|
+
return;
|
|
530
|
+
if (selected === '__marketplace__')
|
|
531
|
+
return await stepSkillsMarketplace(state, client, ws);
|
|
532
|
+
if (selected === '__custom__')
|
|
533
|
+
return await stepCreateSkill(state, client, ws);
|
|
534
|
+
const skill = skills.find((sk) => sk.id === selected);
|
|
535
|
+
if (skill)
|
|
536
|
+
await connectSkill(state, client, ws, skill, false);
|
|
537
|
+
}
|
|
538
|
+
else {
|
|
539
|
+
p.log.info('No matching skills found in your workspace.');
|
|
540
|
+
const action = guard(await p.select({
|
|
541
|
+
message: 'What would you like to do?',
|
|
542
|
+
options: [
|
|
543
|
+
{ value: 'marketplace', label: 'Browse Contextium Marketplace for skills' },
|
|
544
|
+
{ value: 'custom', label: 'Create a custom skill' },
|
|
545
|
+
{ value: 'skip', label: 'Skip for now' },
|
|
546
|
+
],
|
|
547
|
+
}));
|
|
548
|
+
if (action === 'marketplace')
|
|
549
|
+
return await stepSkillsMarketplace(state, client, ws);
|
|
550
|
+
if (action === 'custom')
|
|
551
|
+
return await stepCreateSkill(state, client, ws);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
async function stepSkillsMarketplace(state, client, ws) {
|
|
555
|
+
const query = state.projectDescription.split(' ').slice(0, 4).join(' ');
|
|
556
|
+
const s = p.spinner();
|
|
557
|
+
s.start('Searching marketplace for skills...');
|
|
558
|
+
let listings = [];
|
|
559
|
+
try {
|
|
560
|
+
const { data } = await client.get('/marketplace/listings', { params: { q: query, type: 'skill' } });
|
|
561
|
+
listings = (data.data || data || []).slice(0, 5);
|
|
562
|
+
}
|
|
563
|
+
catch {
|
|
564
|
+
listings = [];
|
|
565
|
+
}
|
|
566
|
+
s.stop();
|
|
567
|
+
if (listings.length === 0) {
|
|
568
|
+
p.log.info('No matching skills found in the marketplace.');
|
|
569
|
+
const createOne = guard(await p.confirm({ message: 'Would you like to create a custom skill instead?' }));
|
|
570
|
+
if (createOne)
|
|
571
|
+
return await stepCreateSkill(state, client, ws);
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
const selected = guard(await p.select({
|
|
575
|
+
message: `Marketplace skills for "${query}":`,
|
|
576
|
+
options: [
|
|
577
|
+
...listings.map((l) => ({
|
|
578
|
+
value: l.slug,
|
|
579
|
+
label: l.name,
|
|
580
|
+
hint: l.shortDescription || undefined,
|
|
581
|
+
})),
|
|
582
|
+
{ value: '__skip__', label: 'Skip — none of these' },
|
|
583
|
+
],
|
|
584
|
+
}));
|
|
585
|
+
if (selected === '__skip__')
|
|
586
|
+
return;
|
|
587
|
+
const s2 = p.spinner();
|
|
588
|
+
s2.start(`Installing ${selected}...`);
|
|
589
|
+
try {
|
|
590
|
+
const { data } = await client.post(`/marketplace/listings/${selected}/import`, { workspaceId: ws.id });
|
|
591
|
+
const result = data.data || data;
|
|
592
|
+
const skill = result.skill || result;
|
|
593
|
+
s2.stop(`Installed: ${skill.title || skill.name || selected}`);
|
|
594
|
+
await connectSkill(state, client, ws, skill, true);
|
|
595
|
+
}
|
|
596
|
+
catch (error) {
|
|
597
|
+
s2.stop();
|
|
598
|
+
p.log.error(`Install failed: ${error.message}`);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
async function stepCreateSkill(state, client, ws) {
|
|
602
|
+
const name = guard(await p.text({
|
|
603
|
+
message: 'Skill name:',
|
|
604
|
+
validate: v => (!v ? 'Name is required' : undefined),
|
|
605
|
+
}));
|
|
606
|
+
const description = guard(await p.text({
|
|
607
|
+
message: 'Description:',
|
|
608
|
+
placeholder: 'What does this skill teach the agent?',
|
|
609
|
+
}));
|
|
610
|
+
const content = guard(await p.text({
|
|
611
|
+
message: 'Skill content/instructions:',
|
|
612
|
+
placeholder: 'Write the instructions, guidelines, or knowledge this skill provides...',
|
|
613
|
+
validate: v => (!v ? 'Content is required — skills must have instructions' : undefined),
|
|
614
|
+
}));
|
|
615
|
+
const s = p.spinner();
|
|
616
|
+
s.start('Creating skill...');
|
|
617
|
+
try {
|
|
618
|
+
const { data: projectsData } = await client.get(`/workspaces/${ws.id}/projects`);
|
|
619
|
+
const projects = projectsData.data || projectsData || [];
|
|
620
|
+
const skillsProject = projects.find((p) => p.isSystem && p.systemType === 'skills');
|
|
621
|
+
if (!skillsProject)
|
|
622
|
+
throw new Error('Skills Library not found in workspace');
|
|
623
|
+
const slug = name.toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, '').replace(/-+/g, '-').replace(/^-|-$/g, '');
|
|
624
|
+
const { data: fileData } = await client.post(`/projects/${skillsProject.id}/files`, {
|
|
625
|
+
title: name,
|
|
626
|
+
path: `${slug}.md`,
|
|
627
|
+
content: `# ${name}\n\n${description}\n\n${content}`,
|
|
628
|
+
});
|
|
629
|
+
const skill = fileData.data || fileData;
|
|
630
|
+
state.newSkills.push(skill);
|
|
631
|
+
s.stop(`Skill created: ${skill.title}`);
|
|
632
|
+
await connectSkill(state, client, ws, skill, true);
|
|
633
|
+
}
|
|
634
|
+
catch (error) {
|
|
635
|
+
s.stop();
|
|
636
|
+
p.log.error(`Failed to create skill: ${error.message}`);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
async function connectSkill(state, client, ws, skill, _isNew) {
|
|
640
|
+
if (state.selectedAgents.length === 0) {
|
|
641
|
+
state.standaloneSkills.push(skill);
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
const agent = state.selectedAgents[0];
|
|
645
|
+
const isNewAgent = state.newAgents.some((a) => a.id === agent.id);
|
|
646
|
+
if (isNewAgent) {
|
|
647
|
+
const attach = guard(await p.confirm({
|
|
648
|
+
message: `Attach "${skill.title || skill.name}" to your new agent "${agent.name}"?`,
|
|
649
|
+
initialValue: true,
|
|
650
|
+
}));
|
|
651
|
+
if (attach) {
|
|
652
|
+
await doAttachSkill(state, client, ws, agent, skill);
|
|
653
|
+
}
|
|
654
|
+
else {
|
|
655
|
+
state.standaloneSkills.push(skill);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
else {
|
|
659
|
+
const action = guard(await p.select({
|
|
660
|
+
message: `"${agent.name}" is an existing agent used across projects. How should this skill be added?`,
|
|
661
|
+
options: [
|
|
662
|
+
{ value: 'workflow', label: 'Add to this workflow only', hint: 'recommended' },
|
|
663
|
+
{ value: 'agent', label: 'Add it to the agent (affects all projects using this agent)' },
|
|
664
|
+
{ value: 'skip', label: 'Skip' },
|
|
665
|
+
],
|
|
666
|
+
}));
|
|
667
|
+
if (action === 'agent') {
|
|
668
|
+
await doAttachSkill(state, client, ws, agent, skill);
|
|
669
|
+
}
|
|
670
|
+
else if (action === 'workflow') {
|
|
671
|
+
state.standaloneSkills.push(skill);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
async function doAttachSkill(state, client, ws, agent, skill) {
|
|
676
|
+
const s = p.spinner();
|
|
677
|
+
s.start(`Attaching skill to ${agent.name}...`);
|
|
678
|
+
try {
|
|
679
|
+
await client.post(`/workspaces/${ws.id}/agents/${agent.id}/skills`, { skillId: skill.id });
|
|
680
|
+
state.selectedSkills.push(skill);
|
|
681
|
+
s.stop('Skill attached');
|
|
682
|
+
}
|
|
683
|
+
catch (error) {
|
|
684
|
+
s.stop();
|
|
685
|
+
p.log.warn(`Could not attach skill: ${error.message} — adding to workflow instead`);
|
|
686
|
+
state.standaloneSkills.push(skill);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
// ─── Step 7 — Build the workflow ──────────────────────────────────────────────
|
|
690
|
+
async function stepBuildWorkflow(state) {
|
|
691
|
+
const hasAnything = state.selectedAgents.length > 0 || state.standaloneSkills.length > 0 || state.createdLibraries.length > 0;
|
|
692
|
+
if (!hasAnything) {
|
|
693
|
+
p.log.info('Nothing to bundle into a workflow — skipping workflow creation.');
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
const summaryLines = [
|
|
697
|
+
state.selectedAgents.length ? `Agents: ${state.selectedAgents.map((a) => a.name).join(', ')}` : '',
|
|
698
|
+
state.selectedSkills.length ? `Skills: ${state.selectedSkills.map((s) => s.title || s.name).join(', ')} (via agent)` : '',
|
|
699
|
+
state.standaloneSkills.length ? `Skills: ${state.standaloneSkills.map((s) => s.title || s.name).join(', ')} (standalone)` : '',
|
|
700
|
+
state.createdLibraries.length ? `Libraries: ${state.createdLibraries.map((l) => l.name).join(', ')}` : '',
|
|
701
|
+
].filter(Boolean);
|
|
702
|
+
p.note(summaryLines.join('\n'), 'Workflow contents');
|
|
703
|
+
const action = guard(await p.select({
|
|
704
|
+
message: 'Ready to create your workflow?',
|
|
705
|
+
options: [
|
|
706
|
+
{ value: 'create', label: 'Create this workflow' },
|
|
707
|
+
{ value: 'skip', label: 'Skip — I\'ll create the workflow manually' },
|
|
708
|
+
],
|
|
709
|
+
}));
|
|
710
|
+
if (action === 'skip')
|
|
711
|
+
return;
|
|
712
|
+
const defaultName = state.projectDescription
|
|
713
|
+
? state.projectDescription.split(' ').slice(0, 4).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(' ')
|
|
714
|
+
: 'My Workflow';
|
|
715
|
+
const workflowName = guard(await p.text({
|
|
716
|
+
message: 'Workflow name:',
|
|
717
|
+
initialValue: defaultName,
|
|
718
|
+
validate: v => (!v ? 'Name is required' : undefined),
|
|
719
|
+
}));
|
|
720
|
+
const client = makeClient(state);
|
|
721
|
+
const ws = state.selectedWorkspace;
|
|
722
|
+
const s = p.spinner();
|
|
723
|
+
s.start('Creating workflow...');
|
|
724
|
+
try {
|
|
725
|
+
const payload = { name: workflowName };
|
|
726
|
+
if (state.selectedAgents.length)
|
|
727
|
+
payload.agentIds = state.selectedAgents.map((a) => a.id);
|
|
728
|
+
if (state.standaloneSkills.length)
|
|
729
|
+
payload.skillIds = state.standaloneSkills.map((s) => s.id);
|
|
730
|
+
if (state.createdLibraries.length)
|
|
731
|
+
payload.projectIds = state.createdLibraries.map((l) => l.id);
|
|
732
|
+
const { data } = await client.post(`/workspaces/${ws.id}/workflows`, payload);
|
|
733
|
+
state.createdWorkflow = data.data || data;
|
|
734
|
+
s.stop(`Workflow created: ${state.createdWorkflow.name}`);
|
|
735
|
+
}
|
|
736
|
+
catch (error) {
|
|
737
|
+
s.stop();
|
|
738
|
+
p.log.error(`Failed to create workflow: ${error.message}`);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
// ─── Step 8 — Write config ────────────────────────────────────────────────────
|
|
742
|
+
async function stepWriteConfig(state, detected) {
|
|
743
|
+
if (detected.hasConfig) {
|
|
744
|
+
const overwrite = guard(await p.confirm({
|
|
745
|
+
message: 'A .contextiumrc already exists here. Update it?',
|
|
746
|
+
initialValue: true,
|
|
747
|
+
}));
|
|
748
|
+
if (!overwrite) {
|
|
749
|
+
p.log.info('Keeping existing .contextiumrc');
|
|
750
|
+
state.configWritten = false;
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
const config = {
|
|
755
|
+
api_key: state.apiKey || '',
|
|
756
|
+
api_url: state.apiUrl || DEFAULT_API_URL,
|
|
757
|
+
...(state.selectedWorkspace && {
|
|
758
|
+
workspace_id: state.selectedWorkspace.id,
|
|
759
|
+
workspace: state.selectedWorkspace.slug,
|
|
760
|
+
}),
|
|
761
|
+
cache: { enabled: true, ttl: 3600, directory: '.contextium-cache', max_size_mb: 100 },
|
|
762
|
+
sync: { auto: true, on_command: ['build', 'test'], notify_changes: true, interval: 300 },
|
|
763
|
+
files: { include: [], exclude: [], tags: [], auto_include_tags: true },
|
|
764
|
+
};
|
|
765
|
+
await saveConfig(config);
|
|
766
|
+
// Create cache dir and update .gitignore
|
|
767
|
+
await fs.mkdir(path.join(process.cwd(), '.contextium-cache'), { recursive: true });
|
|
768
|
+
const gitignorePath = path.join(process.cwd(), '.gitignore');
|
|
769
|
+
try {
|
|
770
|
+
let gitignore = await fs.readFile(gitignorePath, 'utf-8');
|
|
771
|
+
if (!gitignore.includes('.contextium-cache')) {
|
|
772
|
+
gitignore += '\n.contextium-cache/\n.contextiumrc\n';
|
|
773
|
+
await fs.writeFile(gitignorePath, gitignore);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
catch {
|
|
777
|
+
await fs.writeFile(gitignorePath, '.contextium-cache/\n.contextiumrc\n');
|
|
778
|
+
}
|
|
779
|
+
state.configWritten = true;
|
|
780
|
+
p.log.success('Config written to .contextiumrc');
|
|
781
|
+
}
|
|
782
|
+
// ─── Step 9 — Claude Code setup ───────────────────────────────────────────────
|
|
783
|
+
async function stepClaudeCode(state, detected) {
|
|
784
|
+
if (detected.isClaudeConfigured) {
|
|
785
|
+
p.log.success('Claude Code permissions already configured');
|
|
786
|
+
state.claudeConfigured = true;
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
const useClaude = guard(await p.confirm({
|
|
790
|
+
message: 'Do you use Claude Code (claude.ai/claude-code)?',
|
|
791
|
+
initialValue: true,
|
|
792
|
+
}));
|
|
793
|
+
if (!useClaude)
|
|
794
|
+
return;
|
|
795
|
+
const scope = guard(await p.select({
|
|
796
|
+
message: 'Where should permissions be configured?',
|
|
797
|
+
options: [
|
|
798
|
+
{ value: 'global', label: 'Global (~/.claude/settings.json)', hint: 'applies to all projects' },
|
|
799
|
+
{ value: 'project', label: 'Project (.claude/settings.json)', hint: 'this project only' },
|
|
800
|
+
],
|
|
801
|
+
}));
|
|
802
|
+
const s = p.spinner();
|
|
803
|
+
s.start('Configuring Claude Code permissions...');
|
|
804
|
+
try {
|
|
805
|
+
await addClaudePermission(scope);
|
|
806
|
+
state.claudeConfigured = true;
|
|
807
|
+
s.stop('Claude Code permissions configured');
|
|
808
|
+
}
|
|
809
|
+
catch (error) {
|
|
810
|
+
s.stop();
|
|
811
|
+
p.log.error(`Failed to configure Claude Code: ${error.message}`);
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
// ─── Step 10 — Outro ─────────────────────────────────────────────────────────
|
|
815
|
+
function stepOutro(state) {
|
|
816
|
+
const lines = [];
|
|
817
|
+
if (state.account)
|
|
818
|
+
lines.push(`Authenticated as: ${state.account.name || state.account.username}`);
|
|
819
|
+
if (state.selectedWorkspace)
|
|
820
|
+
lines.push(`Workspace: ${state.selectedWorkspace.name}`);
|
|
821
|
+
if (state.createdLibraries.length)
|
|
822
|
+
lines.push(`Libraries created: ${state.createdLibraries.map((l) => l.name).join(', ')}`);
|
|
823
|
+
if (state.createdWorkflow)
|
|
824
|
+
lines.push(`Workflow created: ${state.createdWorkflow.name}`);
|
|
825
|
+
if (state.selectedWorkflow)
|
|
826
|
+
lines.push(`Workflow loaded: ${state.selectedWorkflow.name}`);
|
|
827
|
+
if (state.configWritten)
|
|
828
|
+
lines.push(`Config written: .contextiumrc`);
|
|
829
|
+
if (state.claudeConfigured)
|
|
830
|
+
lines.push(`Claude Code: permissions configured`);
|
|
831
|
+
if (state.claudeConfigured) {
|
|
832
|
+
lines.push('');
|
|
833
|
+
lines.push('Available /ium: commands in Claude Code:');
|
|
834
|
+
lines.push(' /ium:setup Run this wizard again');
|
|
835
|
+
lines.push(' /ium:workflow Load a workflow into context');
|
|
836
|
+
lines.push(' /ium:new Create a new file');
|
|
837
|
+
lines.push(' /ium:search Search your documentation');
|
|
838
|
+
}
|
|
839
|
+
const ws = state.selectedWorkspace?.slug || state.selectedWorkspace?.name || 'my-workspace';
|
|
840
|
+
const wf = state.createdWorkflow?.name || state.selectedWorkflow?.name;
|
|
841
|
+
lines.push('');
|
|
842
|
+
lines.push('Next steps:');
|
|
843
|
+
if (wf)
|
|
844
|
+
lines.push(` contextium workflow "${wf}" --sync`);
|
|
845
|
+
lines.push(` contextium libraries -w ${ws}`);
|
|
846
|
+
lines.push(` contextium cat --all -w ${ws}`);
|
|
847
|
+
p.note(lines.join('\n'), "You're all set!");
|
|
848
|
+
p.outro('Happy building with Contextium.');
|
|
849
|
+
}
|
|
850
|
+
// ─── Main entry point ─────────────────────────────────────────────────────────
|
|
851
|
+
export async function runWizard(options) {
|
|
852
|
+
p.intro('Contextium Setup');
|
|
853
|
+
let detected;
|
|
854
|
+
try {
|
|
855
|
+
detected = await detectState(options.force);
|
|
856
|
+
}
|
|
857
|
+
catch (error) {
|
|
858
|
+
p.log.error(`Setup error: ${error.message}`);
|
|
859
|
+
process.exit(1);
|
|
860
|
+
}
|
|
861
|
+
const state = createInitialState();
|
|
862
|
+
// Step 2 — Auth
|
|
863
|
+
await stepAuth(state, detected);
|
|
864
|
+
// Step 3 — Workspace
|
|
865
|
+
if (!state.selectedWorkspace) {
|
|
866
|
+
if (!detected.hasWorkspace || options.force) {
|
|
867
|
+
await stepWorkspace(state, detected);
|
|
868
|
+
}
|
|
869
|
+
else if (detected.existingConfig?.workspace_id) {
|
|
870
|
+
state.selectedWorkspace = {
|
|
871
|
+
id: detected.existingConfig.workspace_id,
|
|
872
|
+
slug: detected.existingConfig.workspace,
|
|
873
|
+
name: detected.existingConfig.workspace || detected.existingConfig.workspace_id,
|
|
874
|
+
};
|
|
875
|
+
p.log.success(`Workspace: ${state.selectedWorkspace.name}`);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
if (!state.selectedWorkspace) {
|
|
879
|
+
p.log.warn('No workspace selected — skipping project steps.');
|
|
880
|
+
await stepWriteConfig(state, detected);
|
|
881
|
+
await stepClaudeCode(state, detected);
|
|
882
|
+
stepOutro(state);
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
885
|
+
// Step 4 — New or existing project?
|
|
886
|
+
await stepProjectMode(state);
|
|
887
|
+
let doneAfterWorkflowLoad = false;
|
|
888
|
+
if (state.projectMode === 'existing') {
|
|
889
|
+
doneAfterWorkflowLoad = await stepExistingProject(state);
|
|
890
|
+
}
|
|
891
|
+
if (!doneAfterWorkflowLoad) {
|
|
892
|
+
// Step 4b — New project setup
|
|
893
|
+
if (state.projectMode === 'new' || !doneAfterWorkflowLoad) {
|
|
894
|
+
await stepNewProject(state);
|
|
895
|
+
}
|
|
896
|
+
// Step 5 — Agents
|
|
897
|
+
await stepAgents(state);
|
|
898
|
+
// Step 6 — Skills
|
|
899
|
+
await stepSkills(state);
|
|
900
|
+
// Step 7 — Build workflow
|
|
901
|
+
await stepBuildWorkflow(state);
|
|
902
|
+
}
|
|
903
|
+
// Step 8 — Write config
|
|
904
|
+
await stepWriteConfig(state, detected);
|
|
905
|
+
// Step 9 — Claude Code
|
|
906
|
+
await stepClaudeCode(state, detected);
|
|
907
|
+
// Step 10 — Summary
|
|
908
|
+
stepOutro(state);
|
|
909
|
+
}
|
|
910
|
+
//# sourceMappingURL=wizard.js.map
|