@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.
- package/README.md +1 -1
- package/agent-assets/guides/README.md +1 -1
- package/agent-assets/guides/dynamic-site-pattern.md +7 -16
- package/agent-assets/instructions/app-asset-validation.md +1 -1
- package/agent-assets/reference/architecture.md +20 -25
- package/agent-assets/reference/local-dev.md +1 -6
- package/agent-assets/reference/mcp-integration.md +15 -9
- package/agent-assets/reference/platform-api-consumer.md +11 -5
- package/agent-assets/reference/workspace-config.md +2 -2
- package/bin/descix.js +211 -688
- package/lib/command-visibility.js +213 -0
- package/lib/commands/corpus.js +2 -5
- package/lib/commands/kb.js +2 -2
- package/lib/commands/media-upload.js +176 -0
- package/lib/commands/retired-kb-sync.js +5 -27
- package/lib/commands/status.js +0 -9
- package/lib/output.js +31 -0
- package/lib/sdk-assets.js +28 -0
- package/lib/verb-invokes.generated.json +233 -0
- package/lib/workspace-config.js +9 -44
- package/package.json +6 -2
- package/templates/agent-claude.md +1 -3
- package/templates/agent-cline.md +1 -2
- package/templates/agent-copilot.md +1 -2
- package/templates/agent-cursor.md +1 -2
- package/templates/scaffolds/microservice/SERVICE_README_sdk.md +1 -1
- package/templates/scaffolds/microservice/templates/SERVICE_README_TEMPLATE.md +2 -3
- package/agent-assets/rules/descix_mcp.template.mdc +0 -243
- package/lib/commands/briefer/errors.js +0 -88
- package/lib/commands/briefer/index.js +0 -322
- package/lib/commands/briefer/sources/canonical-sources.js +0 -129
- package/lib/commands/briefer/sources/entitlements.js +0 -220
- package/lib/commands/briefer/sources/environments.js +0 -205
- package/lib/commands/briefer/sources/identifiers.js +0 -214
- package/lib/commands/briefer/sources/microservice-deploy.js +0 -267
- package/lib/commands/briefer/sources/routing.js +0 -275
- package/lib/commands/briefer/sources/what-is-not.js +0 -223
- package/lib/commands/briefer/stitcher.js +0 -134
- package/lib/commands/briefer/util/source-reader.js +0 -502
- package/lib/commands/mcp.js +0 -273
- package/lib/commands/update.js +0 -581
- package/lib/wizard/setup.js +0 -585
- package/templates/descix_mcp.mdc +0 -122
package/lib/wizard/setup.js
DELETED
|
@@ -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
|
-
|