@descix/cli 1.0.7 → 1.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/README.md +1 -1
  2. package/agent-assets/guides/README.md +1 -1
  3. package/agent-assets/guides/dynamic-site-pattern.md +7 -16
  4. package/agent-assets/instructions/app-asset-validation.md +1 -1
  5. package/agent-assets/reference/architecture.md +20 -25
  6. package/agent-assets/reference/local-dev.md +1 -6
  7. package/agent-assets/reference/mcp-integration.md +15 -9
  8. package/agent-assets/reference/platform-api-consumer.md +11 -5
  9. package/agent-assets/reference/workspace-config.md +2 -2
  10. package/bin/descix.js +211 -688
  11. package/lib/command-visibility.js +213 -0
  12. package/lib/commands/corpus.js +2 -5
  13. package/lib/commands/kb.js +2 -2
  14. package/lib/commands/media-upload.js +176 -0
  15. package/lib/commands/retired-kb-sync.js +5 -27
  16. package/lib/commands/status.js +0 -9
  17. package/lib/output.js +31 -0
  18. package/lib/sdk-assets.js +28 -0
  19. package/lib/verb-invokes.generated.json +233 -0
  20. package/lib/workspace-config.js +9 -44
  21. package/package.json +6 -2
  22. package/templates/agent-claude.md +1 -3
  23. package/templates/agent-cline.md +1 -2
  24. package/templates/agent-copilot.md +1 -2
  25. package/templates/agent-cursor.md +1 -2
  26. package/templates/scaffolds/microservice/SERVICE_README_sdk.md +1 -1
  27. package/templates/scaffolds/microservice/templates/SERVICE_README_TEMPLATE.md +2 -3
  28. package/agent-assets/rules/descix_mcp.template.mdc +0 -243
  29. package/lib/commands/briefer/errors.js +0 -88
  30. package/lib/commands/briefer/index.js +0 -322
  31. package/lib/commands/briefer/sources/canonical-sources.js +0 -129
  32. package/lib/commands/briefer/sources/entitlements.js +0 -220
  33. package/lib/commands/briefer/sources/environments.js +0 -205
  34. package/lib/commands/briefer/sources/identifiers.js +0 -214
  35. package/lib/commands/briefer/sources/microservice-deploy.js +0 -267
  36. package/lib/commands/briefer/sources/routing.js +0 -275
  37. package/lib/commands/briefer/sources/what-is-not.js +0 -223
  38. package/lib/commands/briefer/stitcher.js +0 -134
  39. package/lib/commands/briefer/util/source-reader.js +0 -502
  40. package/lib/commands/mcp.js +0 -273
  41. package/lib/commands/update.js +0 -581
  42. package/lib/wizard/setup.js +0 -585
  43. package/templates/descix_mcp.mdc +0 -122
@@ -1,585 +0,0 @@
1
- /**
2
- * Interactive Setup Wizard
3
- *
4
- * Bootstraps MCP connectivity so the AI agent can guide workspace configuration.
5
- *
6
- * Steps:
7
- * 0. API Configuration (select environment)
8
- * 1. Authentication (device login or wallet)
9
- * 2. Fetch Entitlements (communities/apps user owns)
10
- * 3. Pull SDK Agent Assets (instructions, templates, rules)
11
- * 4. MCP Configuration (mcp.json + rules file with entitlements)
12
- * 5. Create workspace.json (with _needsAgentSetup flag)
13
- * 6. Register SDK Service (vectorize for tell_me_how)
14
- *
15
- * After setup, the AI agent uses the rules file and local SDK assets
16
- * to help the user map folders to apps and configure workspace.json.
17
- *
18
- * All operations use HTTP API client - no service imports
19
- */
20
-
21
- import chalk from 'chalk';
22
- import ora from 'ora';
23
- import inquirer from 'inquirer';
24
- // "May I prompt?" has ONE OWNER. The wizard is interactive BY NATURE; it still asks the owner.
25
- import { requireInteractive } from '../interactive.js';
26
- import { DeSciXApiClient } from '../api-client.js';
27
- import { ENV_ORIGINS } from '@descix/app-sdk/dev';
28
- import { requireAuth, isAuthenticated } from '../auth-guard.js';
29
- import { WorkspaceConfig } from '../workspace-config.js';
30
- import * as authCommands from '../commands/auth.js';
31
- import * as fs from 'fs/promises';
32
- import * as path from 'path';
33
- import { fileURLToPath } from 'url';
34
- import { dirname } from 'path';
35
-
36
- const __filename = fileURLToPath(import.meta.url);
37
- const __dirname = dirname(__filename);
38
-
39
- // SDK version for tracking
40
- const SDK_VERSION = '2.0.0';
41
-
42
- // ============ Helper Functions ============
43
-
44
- /**
45
- * Copy directory recursively
46
- * @param {string} src - Source directory
47
- * @param {string} dest - Destination directory
48
- */
49
- async function copyDir(src, dest) {
50
- await fs.mkdir(dest, { recursive: true });
51
- const entries = await fs.readdir(src, { withFileTypes: true });
52
-
53
- for (const entry of entries) {
54
- const srcPath = path.join(src, entry.name);
55
- const destPath = path.join(dest, entry.name);
56
-
57
- if (entry.isDirectory()) {
58
- await copyDir(srcPath, destPath);
59
- } else {
60
- await fs.copyFile(srcPath, destPath);
61
- }
62
- }
63
- }
64
-
65
- /**
66
- * Pull SDK agent assets to local workspace
67
- * Copies agent-assets/ to .descix/sdk-assets/ for local AI agent access
68
- * @param {string} workspacePath - Workspace root path
69
- * @returns {Promise<boolean>} True if assets were copied
70
- */
71
- async function pullSdkAssets(workspacePath) {
72
- // agent-assets is in the SDK root, which is ../../.. from wizard/setup.js
73
- const sdkAssetsSource = path.join(__dirname, '../../../agent-assets');
74
- const sdkAssetsTarget = path.join(workspacePath, '.descix/sdk-assets');
75
-
76
- try {
77
- // Check if source exists
78
- await fs.access(sdkAssetsSource);
79
-
80
- // Copy recursively
81
- await copyDir(sdkAssetsSource, sdkAssetsTarget);
82
-
83
- return true;
84
- } catch (error) {
85
- // Source doesn't exist (development mode or not packaged)
86
- return false;
87
- }
88
- }
89
-
90
- /**
91
- * Generate customized rules file from template
92
- * Uses agent-assets/rules/descix_mcp.template.mdc with placeholder substitution
93
- * @param {string} workspacePath - Workspace root path
94
- * @param {Object} entitlements - User's entitlements from fetch_my_purchases
95
- * @param {string} primaryCommunity - User's primary community
96
- * @returns {Promise<boolean>} True if rules file was generated
97
- */
98
- async function generateRulesFile(workspacePath, entitlements, primaryCommunity) {
99
- const templatePath = path.join(__dirname, '../../../agent-assets/rules/descix_mcp.template.mdc');
100
- const rulesDir = path.join(workspacePath, '.cursor/rules');
101
- const rulesPath = path.join(rulesDir, 'descix_mcp.mdc');
102
-
103
- try {
104
- // Read template
105
- const template = await fs.readFile(templatePath, 'utf-8');
106
-
107
- // Summarize entitlements for embedding (avoid huge JSON in rules file)
108
- const entitlementsSummary = {
109
- communities: (entitlements.communities || []).map(c => ({
110
- id: c.community_id || c.id,
111
- name: c.community_name || c.name
112
- })),
113
- apps: (entitlements.apps || []).map(a => ({
114
- community: a.community_id,
115
- id: a.app_id || a.id,
116
- name: a.app_name || a.name
117
- }))
118
- };
119
-
120
- // Customize with workspace-specific values
121
- const customized = template
122
- .replace(/\{\{SDK_ASSETS_PATH\}\}/g, '.descix/sdk-assets')
123
- .replace(/\{\{PRIMARY_COMMUNITY\}\}/g, primaryCommunity || 'daita')
124
- .replace(/\{\{ENTITLEMENTS_JSON\}\}/g, JSON.stringify(entitlementsSummary, null, 2))
125
- .replace(/\{\{SDK_VERSION\}\}/g, SDK_VERSION)
126
- .replace(/\{\{WORKSPACE_PATH\}\}/g, workspacePath);
127
-
128
- // Write customized rules
129
- await fs.mkdir(rulesDir, { recursive: true });
130
- await fs.writeFile(rulesPath, customized, 'utf-8');
131
-
132
- return true;
133
- } catch (error) {
134
- // Template doesn't exist, fall back to simple template
135
- return false;
136
- }
137
- }
138
-
139
- /**
140
- * Auto-register SDK service for tell_me_how discovery
141
- * Vectorizes SERVICE_README_sdk.md so SDK tools are discoverable
142
- * @param {DeSciXApiClient} apiClient - Authenticated API client
143
- * @returns {Promise<boolean>} True if registration succeeded
144
- */
145
- async function autoRegisterSdkService(apiClient) {
146
- // SDK SERVICE_README is in the SDK root
147
- const readmePath = path.join(__dirname, '../../../SERVICE_README_sdk.md');
148
-
149
- try {
150
- const readmeContent = await fs.readFile(readmePath, 'utf-8');
151
-
152
- // Vectorize for tell_me_how discovery
153
- await apiClient.invoke('vectorize_service_readme', {
154
- service_name: 'sdk',
155
- community_id: 'daita',
156
- app_id: 'docs',
157
- readme_content: readmeContent
158
- });
159
-
160
- return true;
161
- } catch (error) {
162
- // README doesn't exist or vectorization failed
163
- return false;
164
- }
165
- }
166
-
167
- /**
168
- * Get MCP server configuration
169
- * Uses direct command if CLI is installed, otherwise suggests install
170
- * @returns {Object} { command, args } for MCP server
171
- */
172
- function getMcpServerConfig() {
173
- // Use direct descix command - simpler and no npm download needed
174
- // Requires CLI to be installed globally first via:
175
- // npm install -g https://app.descix.net/sdk/descix-cli-1.0.0.tgz
176
- // or during development:
177
- // npm install -g /path/to/descix-cli-1.0.0.tgz
178
- return {
179
- command: 'descix',
180
- args: ['mcp-serve'],
181
- type: 'global'
182
- };
183
- }
184
-
185
- /**
186
- * Refuse to run when a workspace already exists here.
187
- *
188
- * quickstart CREATES a workspace. It has no migration or merge semantics, so running it over a
189
- * present one can only destroy information. This is a REFUSAL and deliberately NOT a prompt:
190
- * an agent caller hangs forever on a question, and a refusal is machine-readable where a
191
- * question is not.
192
- *
193
- * WHERE THE WORKSPACE ROOT IS has ONE OWNER: WorkspaceConfig.findWorkspaceRoot, which walks UP
194
- * from the start directory exactly as load() does. This guard CONSUMES it. It used to derive the
195
- * answer a SECOND time by reading the TARGET PATH ONLY, and the two derivations disagreed in the
196
- * case that matters: run from a SUBDIRECTORY of an existing workspace, the target-path check saw
197
- * nothing, the wizard proceeded, and it wrote a NESTED workspace.json that SHADOWS the parent for
198
- * every later load() — because load() walks up and stops at the FIRST one it finds. The second
199
- * derivation is DELETED, not left beside the owner.
200
- *
201
- * A nested workspace is not an unsupported topology, it is the BUG: because resolution walks UP,
202
- * a nested file is unreachable by design from below EXCEPT by shadowing its parent. There is no
203
- * escape hatch here and no flag to request one.
204
- *
205
- * findWorkspaceRoot stats the file, so a PRESENT-BUT-UNREADABLE workspace is still FOUND and still
206
- * refused — the corrupt-looks-absent case this guard exists to stop is preserved.
207
- *
208
- * @param {string} startDir - directory the wizard was invoked in
209
- * @throws {Error} naming the root it found and what is there, when a workspace exists at or ABOVE
210
- * startDir
211
- */
212
- async function refuseIfWorkspacePresent(startDir) {
213
- const workspaceRoot = await WorkspaceConfig.findWorkspaceRoot(startDir);
214
- if (!workspaceRoot) return; // ordinary first run — nothing up the tree to protect
215
-
216
- const configPath = path.join(workspaceRoot, '.descix', 'workspace.json');
217
- const nested = path.resolve(workspaceRoot) !== path.resolve(startDir);
218
-
219
- let raw;
220
- try {
221
- raw = await fs.readFile(configPath, 'utf-8');
222
- } catch (err) {
223
- if (err.code === 'ENOENT') return; // stat'd a moment ago; it vanished in between
224
- throw new Error(
225
- `Refusing to run quickstart: ${configPath} exists but could not be read (${err.message}).\n` +
226
- (nested ? `It is the workspace root for ${workspaceRoot}, which CONTAINS ${startDir}.\n` : '') +
227
- 'Quickstart will not write over a file it cannot inspect.'
228
- );
229
- }
230
-
231
- let found;
232
- try {
233
- const parsed = JSON.parse(raw);
234
- const version = parsed && parsed.version ? parsed.version : 'no version field';
235
- found = `version ${version}`;
236
- } catch (err) {
237
- found = `unparseable JSON (${err.message})`;
238
- }
239
-
240
- throw new Error(
241
- (nested
242
- ? `Refusing to create a workspace here — ${startDir} is already INSIDE the workspace ` +
243
- `rooted at ${workspaceRoot}.\n` +
244
- ` Existing workspace: ${configPath} (${found})\n` +
245
- '\nCreating a second workspace.json in this subdirectory would SHADOW that one: workspace ' +
246
- 'resolution walks UP and stops at the FIRST file it finds, so every later command run from ' +
247
- 'here would silently use the new file instead of the real one.\n'
248
- : `Refusing to overwrite ${configPath} — a workspace is already configured here (${found}).\n` +
249
- '\nquickstart CREATES a workspace; it does not migrate or merge an existing one, and ' +
250
- 'writing over this file would destroy whatever it holds.\n') +
251
- '\nTo change the workspace you already have, use the verbs that own it' +
252
- (nested ? ` (run them from ${workspaceRoot}):\n` : ':\n') +
253
- ' descix config show # what is configured right now\n' +
254
- ' descix config init --env <env> # retarget to a known environment (dev|demo|prod)\n' +
255
- ' descix config set-env <name> --url <origin> # a custom API origin under your own env name\n' +
256
- ' descix config set-gateway-port <n> # change the gateway port\n' +
257
- ' descix app init # register another app in this workspace\n'
258
- );
259
- }
260
-
261
- // ============ Main Wizard ============
262
-
263
- /**
264
- * Run the interactive setup wizard
265
- */
266
- export async function runSetupWizard(options = {}) {
267
- // ONE derivation of the workspace root, shared by the guard and the write it protects.
268
- const workspaceRoot = process.cwd();
269
-
270
- // BEFORE the banner and before every side effect: this wizard also writes
271
- // .cursor/mcp.json and pulls SDK assets, so a guard placed further down would
272
- // refuse only after those had already landed.
273
- await refuseIfWorkspacePresent(workspaceRoot);
274
-
275
- console.log(chalk.cyan('\nā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”'));
276
- console.log(chalk.cyan('│ Welcome to DeSciX CLI/MCP Setup │'));
277
- console.log(chalk.cyan('ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤'));
278
- console.log(chalk.cyan('│ This wizard will: │'));
279
- console.log(chalk.cyan('│ 1. Configure API endpoint │'));
280
- console.log(chalk.cyan('│ 2. Authenticate with DeSciX (required) │'));
281
- console.log(chalk.cyan('│ 3. Fetch your entitlements │'));
282
- console.log(chalk.cyan('│ 4. Pull SDK agent assets locally │'));
283
- console.log(chalk.cyan('│ 5. Configure Cursor MCP integration │'));
284
- console.log(chalk.cyan('│ 6. Register SDK for tool discovery │'));
285
- console.log(chalk.cyan('│ │'));
286
- console.log(chalk.cyan('│ After setup, the AI agent will help you │'));
287
- console.log(chalk.cyan('│ configure your workspace folders. │'));
288
- console.log(chalk.cyan('ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜\n'));
289
-
290
- const spinner = ora('Initializing...').start();
291
-
292
- try {
293
- // Step 0: Configure API endpoint
294
- spinner.stop();
295
- console.log(chalk.cyan('\nšŸ“‹ Step 0: API Configuration\n'));
296
-
297
- let apiUrl = options.url || process.env.DESCIX_API_URL;
298
-
299
- if (!apiUrl) {
300
- requireInteractive({ what: 'descix mcp quickstart', question: 'which environment?', nonInteractiveForm: ['descix mcp quickstart is an interactive wizard and has no non-interactive form. Run it in a terminal.'] });
301
- const { environment } = await inquirer.prompt([
302
- {
303
- type: 'list',
304
- name: 'environment',
305
- message: 'Which environment are you connecting to?',
306
- choices: [
307
- { name: `Production (${ENV_ORIGINS.prod})`, value: 'production' },
308
- { name: 'Local Development (localhost:4000)', value: 'local' },
309
- { name: 'Custom URL', value: 'custom' }
310
- ],
311
- default: 'production'
312
- }
313
- ]);
314
-
315
- if (environment === 'production') {
316
- apiUrl = ENV_ORIGINS.prod;
317
- } else if (environment === 'local') {
318
- apiUrl = 'https://localhost:4000';
319
- } else {
320
- requireInteractive({ what: 'descix mcp quickstart', question: 'custom API URL?', nonInteractiveForm: ['descix mcp quickstart is an interactive wizard and has no non-interactive form. Run it in a terminal.'] });
321
- const { customUrl } = await inquirer.prompt([
322
- {
323
- type: 'input',
324
- name: 'customUrl',
325
- message: 'Enter API URL:',
326
- default: 'https://localhost:4000',
327
- validate: (input) => {
328
- if (!input.startsWith('http://') && !input.startsWith('https://')) {
329
- return 'URL must start with http:// or https://';
330
- }
331
- return true;
332
- }
333
- }
334
- ]);
335
- apiUrl = customUrl;
336
- }
337
- }
338
-
339
- console.log(chalk.green(`āœ“ API endpoint: ${apiUrl}\n`));
340
-
341
- spinner.start('Connecting to DeSciX...');
342
- const apiClient = new DeSciXApiClient({ baseUrl: apiUrl });
343
- spinner.succeed('Connected to DeSciX');
344
-
345
- // Step 1: Authentication (required)
346
- console.log(chalk.cyan('\nšŸ“‹ Step 1: Authentication\n'));
347
-
348
- // Pass the API URL to login commands
349
- const authOptions = { url: apiUrl };
350
-
351
- const isAuth = await isAuthenticated(apiClient);
352
-
353
- if (!isAuth) {
354
- console.log(chalk.yellow('āš ļø Authentication is required to use DeSciX CLI/MCP.\n'));
355
-
356
- requireInteractive({ what: 'descix mcp quickstart', question: 'which authentication method?', nonInteractiveForm: ['descix mcp quickstart is an interactive wizard and has no non-interactive form. Run it in a terminal.'] });
357
- const { authMethod } = await inquirer.prompt([
358
- {
359
- type: 'list',
360
- name: 'authMethod',
361
- message: 'How would you like to authenticate?',
362
- choices: [
363
- { name: 'Device Login (recommended - opens browser)', value: 'device' },
364
- { name: 'Direct Wallet Connection (advanced)', value: 'wallet' }
365
- ],
366
- default: 'device'
367
- }
368
- ]);
369
-
370
- if (authMethod === 'device') {
371
- await authCommands.loginDevice(authOptions);
372
- } else {
373
- await authCommands.loginWallet();
374
- }
375
- } else {
376
- console.log(chalk.green('āœ… Already authenticated\n'));
377
- }
378
-
379
- // Verify authentication
380
- await requireAuth(apiClient);
381
- console.log(chalk.green('āœ… Authentication verified\n'));
382
-
383
- // Step 2: Fetch Entitlements
384
- console.log(chalk.cyan('\nšŸ“‹ Step 2: Fetching Entitlements\n'));
385
-
386
- let entitlements = { communities: [], apps: [], service_slots: [] };
387
- spinner.start('Fetching your entitlements...');
388
-
389
- try {
390
- const purchasesResult = await apiClient.invoke('fetch_my_purchases', {});
391
- if (purchasesResult && purchasesResult.status !== 'ERROR') {
392
- entitlements = {
393
- communities: purchasesResult.communities || [],
394
- apps: purchasesResult.apps || [],
395
- service_slots: purchasesResult.service_slots || []
396
- };
397
- spinner.succeed(`Found ${entitlements.communities.length} communities, ${entitlements.apps.length} apps`);
398
-
399
- // Display entitlements
400
- if (entitlements.communities.length > 0) {
401
- console.log(chalk.gray('\n Communities:'));
402
- for (const c of entitlements.communities) {
403
- console.log(chalk.gray(` - ${c.community_name || c.name || c.community_id} (${c.community_id || c.id})`));
404
- }
405
- }
406
- if (entitlements.apps.length > 0) {
407
- console.log(chalk.gray('\n Apps:'));
408
- for (const a of entitlements.apps.slice(0, 5)) {
409
- console.log(chalk.gray(` - ${a.community_id}/${a.app_id || a.id}`));
410
- }
411
- if (entitlements.apps.length > 5) {
412
- console.log(chalk.gray(` ... and ${entitlements.apps.length - 5} more`));
413
- }
414
- }
415
- console.log('');
416
- } else {
417
- spinner.warn('Could not fetch entitlements (will use defaults)');
418
- }
419
- } catch (error) {
420
- spinner.warn('Could not fetch entitlements: ' + error.message);
421
- }
422
-
423
- // Step 3: Pull SDK Assets
424
- console.log(chalk.cyan('\nšŸ“‹ Step 3: Pulling SDK Agent Assets\n'));
425
-
426
- spinner.start('Pulling SDK agent assets...');
427
- const assetsPulled = await pullSdkAssets(workspaceRoot);
428
-
429
- if (assetsPulled) {
430
- spinner.succeed('SDK agent assets pulled');
431
- console.log(chalk.gray(' Created: .descix/sdk-assets/'));
432
- console.log(chalk.gray(' - instructions/ (onboarding, packaging types, etc.)'));
433
- console.log(chalk.gray(' - templates/ (app scaffolds)'));
434
- console.log(chalk.gray(' - rules/ (agent instructions template)\n'));
435
- } else {
436
- spinner.warn('SDK agent assets not found (development mode)');
437
- console.log(chalk.gray(' Skipped: .descix/sdk-assets/\n'));
438
- }
439
-
440
- // Derive primary community from entitlements
441
- let primaryCommunity = 'daita';
442
- if (entitlements.communities.length > 0) {
443
- primaryCommunity = entitlements.communities[0].community_id || entitlements.communities[0].id || 'daita';
444
- }
445
-
446
- // Step 4: MCP Configuration
447
- console.log(chalk.cyan('\nšŸ“‹ Step 4: MCP Configuration\n'));
448
-
449
- requireInteractive({ what: 'descix mcp quickstart', question: 'configure MCP now?', nonInteractiveForm: ['descix mcp quickstart is an interactive wizard and has no non-interactive form. Run it in a terminal.'] });
450
- const { setupMCP } = await inquirer.prompt([
451
- {
452
- type: 'confirm',
453
- name: 'setupMCP',
454
- message: 'Set up Cursor MCP integration?',
455
- default: true
456
- }
457
- ]);
458
-
459
- if (setupMCP) {
460
- spinner.start('Creating MCP configuration...');
461
-
462
- // Use npx approach for reliability (like Pinecone does)
463
- const mcpServer = getMcpServerConfig();
464
-
465
- // Create .cursor/mcp.json
466
- const cursorDir = path.join(workspaceRoot, '.cursor');
467
- await fs.mkdir(cursorDir, { recursive: true });
468
-
469
- // Build MCP server config - npx handles all dependencies
470
- const mcpServerConfig = {
471
- command: mcpServer.command,
472
- args: mcpServer.args
473
- };
474
-
475
- // Read existing config if present to preserve other servers
476
- let existingConfig = { mcpServers: {} };
477
- const cursorConfigPath = path.join(cursorDir, 'mcp.json');
478
- try {
479
- const existing = await fs.readFile(cursorConfigPath, 'utf-8');
480
- existingConfig = JSON.parse(existing);
481
- if (!existingConfig.mcpServers) {
482
- existingConfig.mcpServers = {};
483
- }
484
- } catch {
485
- // No existing config, that's fine
486
- }
487
-
488
- // Add/update descix server
489
- existingConfig.mcpServers['descix'] = mcpServerConfig;
490
-
491
- await fs.writeFile(cursorConfigPath, JSON.stringify(existingConfig, null, 2));
492
-
493
- spinner.succeed('MCP configuration created');
494
- console.log(chalk.gray(` Created: .cursor/mcp.json`));
495
- console.log(chalk.gray(` Server: ${mcpServer.command} ${mcpServer.args.join(' ')}`));
496
-
497
- // Deploy customized rules file with entitlements
498
- spinner.start('Generating customized agent rules...');
499
-
500
- const rulesGenerated = await generateRulesFile(workspaceRoot, entitlements, primaryCommunity);
501
-
502
- if (rulesGenerated) {
503
- spinner.succeed('Agent rules file generated with entitlements');
504
- console.log(chalk.gray(` Created: .cursor/rules/descix_mcp.mdc`));
505
- console.log(chalk.gray(` - Embedded ${entitlements.communities.length} communities`));
506
- console.log(chalk.gray(` - Embedded ${entitlements.apps.length} apps`));
507
- console.log(chalk.gray(` - References local SDK assets at .descix/sdk-assets/\n`));
508
- } else {
509
- // Fall back to simple template
510
- spinner.info('Using simple rules template');
511
- const rulesDir = path.join(workspaceRoot, '.cursor', 'rules');
512
- await fs.mkdir(rulesDir, { recursive: true });
513
-
514
- const templatePath = path.join(__dirname, '../../templates/descix_mcp.mdc');
515
- const rulesPath = path.join(rulesDir, 'descix_mcp.mdc');
516
-
517
- try {
518
- const templateContent = await fs.readFile(templatePath, 'utf-8');
519
- await fs.writeFile(rulesPath, templateContent, 'utf-8');
520
- console.log(chalk.gray(` Created: .cursor/rules/descix_mcp.mdc\n`));
521
- } catch (error) {
522
- console.log(chalk.gray(` Skipped: .cursor/rules/descix_mcp.mdc\n`));
523
- }
524
- }
525
- }
526
-
527
- // Step 5: Create workspace config
528
- spinner.start('Creating workspace configuration...');
529
-
530
- // The SHAPE of this file is owned by WorkspaceConfig and it is written ONLY via save().
531
- // This used to build a config literal and call fs.writeFile directly — no existence check,
532
- // no parse check, no save() — which both clobbered present workspaces and stamped a
533
- // version '2.0' shape on a v2.1 platform. The version is the owner's concern now.
534
- //
535
- // The env NAME comes from the owner's own ENV_MAP rather than being re-derived from the URL
536
- // here; an origin the owner does not know is recorded as DEV carrying its explicit URL,
537
- // because inventing a label at this call site would be a second derivation of one fact.
538
- const envName = (Object.entries(WorkspaceConfig.ENV_MAP)
539
- .find(([, entry]) => entry.url === apiUrl) || ['dev'])[0];
540
-
541
- const workspace = new WorkspaceConfig({ type: 'workspace' }, workspaceRoot);
542
- const { configPath } = await workspace.setEnvironment(envName, apiUrl);
543
-
544
- spinner.succeed('Workspace configured');
545
- console.log(chalk.gray(` Created: .descix/workspace.json\n`));
546
-
547
- // Step 6: Auto-register SDK service for tell_me_how discovery
548
- console.log(chalk.cyan('\nšŸ“‹ Step 6: Registering SDK Service\n'));
549
-
550
- spinner.start('Registering SDK for tell_me_how discovery...');
551
- const sdkRegistered = await autoRegisterSdkService(apiClient);
552
-
553
- if (sdkRegistered) {
554
- spinner.succeed('SDK service registered');
555
- console.log(chalk.gray(' SDK tools now discoverable via tell_me_how\n'));
556
- } else {
557
- spinner.warn('Could not register SDK service (will work without discovery)');
558
- console.log(chalk.gray(' SDK tools available but not indexed for search\n'));
559
- }
560
-
561
- // Done!
562
- console.log(chalk.green('\nāœ… MCP Setup Complete!\n'));
563
- console.log(chalk.white('Next steps:'));
564
- console.log(chalk.gray(' 1. Restart Cursor (Cmd/Ctrl + Shift + P → "Reload Window")'));
565
- console.log(chalk.gray(' 2. Ask the AI agent: "Help me configure my workspace"\n'));
566
-
567
- console.log(chalk.white('The AI agent will:'));
568
- console.log(chalk.gray(' - Read your entitlements and show available apps'));
569
- console.log(chalk.gray(' - Help you map local folders to DeSciX apps'));
570
- console.log(chalk.gray(' - Analyze folder structure and recommend packaging types'));
571
- console.log(chalk.gray(' - Register apps in workspace.json\n'));
572
-
573
- console.log(chalk.white('If MCP fails, use CLI directly:'));
574
- console.log(chalk.gray(' descix purchases # View your entitlements'));
575
- console.log(chalk.gray(' descix tell-me-how "question" # Discover tools'));
576
- console.log(chalk.gray(' descix app sync-assets -a Y # Sync app assets'));
577
- console.log(chalk.gray(' descix kb corpus sync -a Y # Sync knowledge base\n'));
578
-
579
- } catch (error) {
580
- spinner.fail('Setup failed');
581
- console.error(chalk.red(error.message));
582
- throw error;
583
- }
584
- }
585
-