@descix/cli 1.0.5 → 1.0.7
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 +130 -34
- package/agent-assets/guides/dynamic-site-pattern.md +10 -14
- package/agent-assets/guides/knowledge-only-pattern.md +11 -15
- package/agent-assets/guides/microservice-pattern.md +12 -14
- package/agent-assets/guides/static-site-pattern.md +12 -15
- package/agent-assets/instructions/onboarding-flow.md +4 -4
- package/agent-assets/reference/app-creation.md +36 -101
- package/agent-assets/reference/architecture.md +24 -27
- package/agent-assets/reference/local-dev.md +0 -4
- package/agent-assets/reference/mcp-integration.md +14 -22
- package/agent-assets/reference/workspace-config.md +97 -340
- package/agent-assets/rules/descix_mcp.template.mdc +1 -1
- package/bin/descix.js +159 -98
- package/lib/api-client.js +30 -22
- package/lib/commands/airdrop.js +10 -0
- package/lib/commands/auth.js +33 -3
- package/lib/commands/briefer/index.js +8 -5
- package/lib/commands/config.js +13 -31
- package/lib/commands/corpus.js +161 -54
- package/lib/commands/health.js +116 -21
- package/lib/commands/init.js +25 -48
- package/lib/commands/kb.js +14 -4
- package/lib/commands/update.js +0 -1
- package/lib/core/Chunker.js +1 -1
- package/lib/core/CorpusWalker.js +238 -47
- package/lib/core/Hydrator.js +88 -11
- package/lib/core/ManifestLoader.js +68 -0
- package/lib/core/Syncer.js +147 -46
- package/lib/interactive.js +158 -0
- package/lib/origin.js +3 -3
- package/lib/wallet-file.js +36 -37
- package/lib/wizard/setup.js +104 -22
- package/lib/workspace-config.js +325 -167
- package/lib/workspace-identity.js +0 -1
- package/package.json +3 -3
- package/templates/agent-claude.md +0 -3
- package/templates/agent-cline.md +0 -3
- package/templates/agent-copilot.md +0 -3
- package/templates/agent-cursor.md +0 -3
- package/templates/drive/app/kb/General/README.md +1 -1
- package/templates/drive_templates/agent_app_template/kb/General/README.md +1 -1
- package/templates/scaffolds/microservice/SERVICE_README_sdk.md +3 -3
- package/templates/scaffolds/microservice/defaults-config.json +3 -1
- package/templates/scaffolds/microservice/manifest.json +9 -5
- package/templates/scaffolds/microservice/package.json +1 -1
- package/templates/scaffolds/microservice/services/commandHandlers/sampleCommands.js +2 -2
package/lib/wizard/setup.js
CHANGED
|
@@ -21,9 +21,12 @@
|
|
|
21
21
|
import chalk from 'chalk';
|
|
22
22
|
import ora from 'ora';
|
|
23
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';
|
|
24
26
|
import { DeSciXApiClient } from '../api-client.js';
|
|
25
27
|
import { ENV_ORIGINS } from '@descix/app-sdk/dev';
|
|
26
28
|
import { requireAuth, isAuthenticated } from '../auth-guard.js';
|
|
29
|
+
import { WorkspaceConfig } from '../workspace-config.js';
|
|
27
30
|
import * as authCommands from '../commands/auth.js';
|
|
28
31
|
import * as fs from 'fs/promises';
|
|
29
32
|
import * as path from 'path';
|
|
@@ -179,12 +182,96 @@ function getMcpServerConfig() {
|
|
|
179
182
|
};
|
|
180
183
|
}
|
|
181
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
|
+
|
|
182
261
|
// ============ Main Wizard ============
|
|
183
262
|
|
|
184
263
|
/**
|
|
185
264
|
* Run the interactive setup wizard
|
|
186
265
|
*/
|
|
187
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
|
+
|
|
188
275
|
console.log(chalk.cyan('\n┌─────────────────────────────────────────────────┐'));
|
|
189
276
|
console.log(chalk.cyan('│ Welcome to DeSciX CLI/MCP Setup │'));
|
|
190
277
|
console.log(chalk.cyan('├─────────────────────────────────────────────────┤'));
|
|
@@ -200,7 +287,6 @@ export async function runSetupWizard(options = {}) {
|
|
|
200
287
|
console.log(chalk.cyan('│ configure your workspace folders. │'));
|
|
201
288
|
console.log(chalk.cyan('└─────────────────────────────────────────────────┘\n'));
|
|
202
289
|
|
|
203
|
-
const workspaceRoot = process.cwd();
|
|
204
290
|
const spinner = ora('Initializing...').start();
|
|
205
291
|
|
|
206
292
|
try {
|
|
@@ -211,6 +297,7 @@ export async function runSetupWizard(options = {}) {
|
|
|
211
297
|
let apiUrl = options.url || process.env.DESCIX_API_URL;
|
|
212
298
|
|
|
213
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.'] });
|
|
214
301
|
const { environment } = await inquirer.prompt([
|
|
215
302
|
{
|
|
216
303
|
type: 'list',
|
|
@@ -230,6 +317,7 @@ export async function runSetupWizard(options = {}) {
|
|
|
230
317
|
} else if (environment === 'local') {
|
|
231
318
|
apiUrl = 'https://localhost:4000';
|
|
232
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.'] });
|
|
233
321
|
const { customUrl } = await inquirer.prompt([
|
|
234
322
|
{
|
|
235
323
|
type: 'input',
|
|
@@ -265,6 +353,7 @@ export async function runSetupWizard(options = {}) {
|
|
|
265
353
|
if (!isAuth) {
|
|
266
354
|
console.log(chalk.yellow('⚠️ Authentication is required to use DeSciX CLI/MCP.\n'));
|
|
267
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.'] });
|
|
268
357
|
const { authMethod } = await inquirer.prompt([
|
|
269
358
|
{
|
|
270
359
|
type: 'list',
|
|
@@ -357,6 +446,7 @@ export async function runSetupWizard(options = {}) {
|
|
|
357
446
|
// Step 4: MCP Configuration
|
|
358
447
|
console.log(chalk.cyan('\n📋 Step 4: MCP Configuration\n'));
|
|
359
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.'] });
|
|
360
450
|
const { setupMCP } = await inquirer.prompt([
|
|
361
451
|
{
|
|
362
452
|
type: 'confirm',
|
|
@@ -437,27 +527,19 @@ export async function runSetupWizard(options = {}) {
|
|
|
437
527
|
// Step 5: Create workspace config
|
|
438
528
|
spinner.start('Creating workspace configuration...');
|
|
439
529
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
_needsAgentSetup: true, // Flag for AI agent to help with workspace config
|
|
454
|
-
_sdkVersion: SDK_VERSION
|
|
455
|
-
};
|
|
456
|
-
|
|
457
|
-
const descixDir = path.join(workspaceRoot, '.descix');
|
|
458
|
-
await fs.mkdir(descixDir, { recursive: true });
|
|
459
|
-
const configPath = path.join(descixDir, 'workspace.json');
|
|
460
|
-
await fs.writeFile(configPath, JSON.stringify(config, null, 2));
|
|
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);
|
|
461
543
|
|
|
462
544
|
spinner.succeed('Workspace configured');
|
|
463
545
|
console.log(chalk.gray(` Created: .descix/workspace.json\n`));
|