@descix/cli 1.0.1 → 1.0.4
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/LICENSE.md +671 -0
- package/README.md +43 -4
- package/agent-assets/guides/README.md +2 -2
- package/agent-assets/guides/dynamic-site-pattern.md +29 -14
- package/agent-assets/guides/knowledge-only-pattern.md +5 -5
- package/agent-assets/guides/microservice-pattern.md +48 -4
- package/agent-assets/guides/static-site-pattern.md +8 -0
- package/agent-assets/instructions/app-asset-validation.md +5 -5
- package/agent-assets/instructions/onboarding-flow.md +4 -4
- package/agent-assets/instructions/standard-folder-structure.md +20 -11
- package/agent-assets/reference/app-creation.md +42 -20
- package/agent-assets/reference/architecture.md +24 -22
- package/agent-assets/reference/local-dev.md +230 -67
- package/agent-assets/reference/mcp-integration.md +21 -15
- package/agent-assets/reference/platform-api-consumer.md +244 -0
- package/agent-assets/reference/workspace-config.md +7 -7
- package/agent-assets/rules/descix_mcp.template.mdc +8 -8
- package/bin/descix.js +2559 -1435
- package/bin/kb-deny-lint.mjs +91 -0
- package/bin/mcp-server.js +175 -91
- package/lib/agent-files.js +37 -25
- package/lib/api-client.js +223 -56
- package/lib/auth-guard.js +13 -3
- package/lib/chat-session.js +187 -0
- package/lib/cli-version.js +59 -0
- package/lib/commands/airdrop.js +251 -0
- package/lib/commands/auth.js +254 -72
- package/lib/commands/briefer/errors.js +88 -0
- package/lib/commands/briefer/index.js +319 -0
- package/lib/commands/briefer/sources/canonical-sources.js +129 -0
- package/lib/commands/briefer/sources/entitlements.js +220 -0
- package/lib/commands/briefer/sources/environments.js +205 -0
- package/lib/commands/briefer/sources/identifiers.js +214 -0
- package/lib/commands/briefer/sources/microservice-deploy.js +267 -0
- package/lib/commands/briefer/sources/routing.js +275 -0
- package/lib/commands/briefer/sources/what-is-not.js +223 -0
- package/lib/commands/briefer/stitcher.js +134 -0
- package/lib/commands/briefer/util/source-reader.js +502 -0
- package/lib/commands/config.js +210 -90
- package/lib/commands/corpus.js +978 -0
- package/lib/commands/credits.js +122 -0
- package/lib/commands/health.js +627 -0
- package/lib/commands/kb-list-render.js +32 -0
- package/lib/commands/kb.js +190 -413
- package/lib/commands/mcp.js +5 -5
- package/lib/commands/microservice-restart.js +359 -0
- package/lib/commands/model-config.js +313 -0
- package/lib/commands/retired-kb-sync.js +134 -0
- package/lib/commands/serve.js +26 -1
- package/lib/commands/status.js +23 -51
- package/lib/commands/update.js +36 -107
- package/lib/core/Chunker.js +491 -17
- package/lib/core/CorpusDenyLint.js +326 -0
- package/lib/core/CorpusWalker.js +357 -0
- package/lib/core/Hydrator.js +75 -29
- package/lib/core/ManifestLoader.js +533 -0
- package/lib/core/SiteWalker.js +218 -0
- package/lib/core/Syncer.js +163 -11
- package/lib/diagnostic-log.js +41 -0
- package/lib/environment-report.js +100 -0
- package/lib/global-config.js +4 -1
- package/lib/oauth-client.js +164 -0
- package/lib/origin.js +143 -0
- package/lib/service-api-client.js +95 -0
- package/lib/wallet-environment.js +49 -0
- package/lib/wallet-file.js +16 -2
- package/lib/wizard/setup.js +10 -9
- package/lib/workspace-config.js +380 -235
- package/lib/workspace-identity.js +92 -0
- package/lib/workspace-utils.js +41 -61
- package/package.json +19 -6
- package/templates/agent-claude.md +157 -9
- package/templates/agent-cline.md +68 -8
- package/templates/agent-copilot.md +68 -8
- package/templates/agent-cursor.md +68 -8
- package/templates/alignment-schema.json +20 -0
- package/templates/cloud-brain-hook.sh +20 -0
- package/templates/drive/README.md +1 -1
- package/templates/drive_templates/README.md +2 -2
- package/templates/scaffolds/README.md +6 -10
- package/templates/scaffolds/microservice/SERVICE_README_sdk.md +104 -149
- package/templates/scaffolds/microservice/app.js +51 -10
- package/templates/scaffolds/microservice/app.yaml +2 -2
- package/templates/scaffolds/microservice/defaults-config-dev.json +4 -0
- package/templates/scaffolds/microservice/dev-overrides.example.json +5 -0
- package/templates/scaffolds/microservice/manifest.json +0 -1
- package/templates/scaffolds/microservice/package.json +3 -3
- package/templates/scaffolds/microservice/scripts/register.js +56 -41
- package/templates/scaffolds/microservice/services/mcpClient.js +55 -65
- package/templates/scaffolds/microservice/services/utils.js +20 -238
- package/templates/scaffolds/microservice/templates/SERVICE_README_TEMPLATE.md +24 -0
- package/lib/commands/app-wizard.js +0 -627
- package/lib/commands/folder.js +0 -291
- package/lib/core/PathContext.js +0 -335
- package/templates/scaffolds/microservice/dev-overrides.json.example +0 -5
- package/templates/scaffolds/site/DeSciXAppSDK.js +0 -62
- package/templates/scaffolds/site/README.md +0 -51
- package/templates/scaffolds/site/app.js +0 -22
- package/templates/scaffolds/site/index.html +0 -42
- package/templates/scaffolds/site/styles.css +0 -123
package/lib/commands/folder.js
DELETED
|
@@ -1,291 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Folder Commands - Drive Folder Management
|
|
3
|
-
*
|
|
4
|
-
* Commands for managing Drive folder IDs for entities:
|
|
5
|
-
* descix folder set --user/--community/--app/--kb <folder-id>
|
|
6
|
-
* descix folder get --user/--community/--app/--kb
|
|
7
|
-
* descix folder allocate --app/--kb
|
|
8
|
-
* descix folder validate <folder-id>
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import chalk from 'chalk';
|
|
12
|
-
import ora from 'ora';
|
|
13
|
-
import { DeSciXApiClient } from '../api-client.js';
|
|
14
|
-
import { requireAuth } from '../auth-guard.js';
|
|
15
|
-
import { GlobalConfig } from '../global-config.js';
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Extract folder ID from Drive URL or return as-is
|
|
19
|
-
*/
|
|
20
|
-
function extractFolderId(input) {
|
|
21
|
-
if (!input) return null;
|
|
22
|
-
|
|
23
|
-
const trimmed = input.trim();
|
|
24
|
-
|
|
25
|
-
// Handle full URLs
|
|
26
|
-
if (trimmed.includes('drive.google.com')) {
|
|
27
|
-
const foldersMatch = trimmed.match(/\/folders\/([a-zA-Z0-9_-]+)/);
|
|
28
|
-
if (foldersMatch && foldersMatch[1]) return foldersMatch[1];
|
|
29
|
-
|
|
30
|
-
const dMatch = trimmed.match(/\/d\/([a-zA-Z0-9_-]+)/);
|
|
31
|
-
if (dMatch && dMatch[1]) return dMatch[1];
|
|
32
|
-
|
|
33
|
-
const idMatch = trimmed.match(/[?&]id=([a-zA-Z0-9_-]+)/);
|
|
34
|
-
if (idMatch && idMatch[1]) return idMatch[1];
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
return trimmed;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Set folder ID for an entity
|
|
42
|
-
*/
|
|
43
|
-
export async function setFolder(options) {
|
|
44
|
-
const apiClient = new DeSciXApiClient();
|
|
45
|
-
await requireAuth(apiClient);
|
|
46
|
-
|
|
47
|
-
const spinner = ora('Setting folder...').start();
|
|
48
|
-
|
|
49
|
-
try {
|
|
50
|
-
let entityType, entityIds, folderId;
|
|
51
|
-
|
|
52
|
-
if (options.user) {
|
|
53
|
-
entityType = 'user';
|
|
54
|
-
folderId = extractFolderId(options.user);
|
|
55
|
-
} else if (options.community) {
|
|
56
|
-
entityType = 'community';
|
|
57
|
-
entityIds = { community_id: options.community };
|
|
58
|
-
folderId = extractFolderId(options.folderId);
|
|
59
|
-
} else if (options.app) {
|
|
60
|
-
entityType = 'app';
|
|
61
|
-
// Parse "community app" format
|
|
62
|
-
const parts = options.app.split(/\s+/);
|
|
63
|
-
if (parts.length < 2) {
|
|
64
|
-
throw new Error('App requires both community and app ID: --app "community app"');
|
|
65
|
-
}
|
|
66
|
-
entityIds = { community_id: parts[0], app_id: parts[1] };
|
|
67
|
-
folderId = extractFolderId(options.folderId);
|
|
68
|
-
} else if (options.kb) {
|
|
69
|
-
entityType = 'kb';
|
|
70
|
-
// Parse "community app kb" format
|
|
71
|
-
const parts = options.kb.split(/\s+/);
|
|
72
|
-
if (parts.length < 3) {
|
|
73
|
-
throw new Error('KB requires community, app, and kb ID: --kb "community app kb"');
|
|
74
|
-
}
|
|
75
|
-
entityIds = { community_id: parts[0], app_id: parts[1], kb_id: parts[2] };
|
|
76
|
-
folderId = extractFolderId(options.folderId);
|
|
77
|
-
} else {
|
|
78
|
-
throw new Error('Specify entity type: --user, --community, --app, or --kb');
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if (!folderId) {
|
|
82
|
-
throw new Error('Folder ID is required');
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const response = await apiClient.invoke('set_folder_id', {
|
|
86
|
-
entity_type: entityType,
|
|
87
|
-
entity_ids: entityIds,
|
|
88
|
-
folder_id: folderId
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
const result = response.message || response;
|
|
92
|
-
|
|
93
|
-
spinner.succeed('Folder set successfully');
|
|
94
|
-
|
|
95
|
-
console.log(chalk.green(`\n✓ ${result.message}`));
|
|
96
|
-
console.log(chalk.gray(` Entity: ${entityType}`));
|
|
97
|
-
console.log(chalk.gray(` Folder: ${folderId}`));
|
|
98
|
-
console.log(chalk.cyan(` URL: https://drive.google.com/drive/folders/${folderId}\n`));
|
|
99
|
-
|
|
100
|
-
// Update global config if setting user folder
|
|
101
|
-
if (entityType === 'user') {
|
|
102
|
-
const globalConfig = await GlobalConfig.load();
|
|
103
|
-
globalConfig.user_base_folder_id = folderId;
|
|
104
|
-
globalConfig.registered_at = new Date().toISOString();
|
|
105
|
-
await globalConfig.save();
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
return result;
|
|
109
|
-
|
|
110
|
-
} catch (error) {
|
|
111
|
-
spinner.fail('Failed to set folder');
|
|
112
|
-
console.error(chalk.red(`\n❌ ${error.message}\n`));
|
|
113
|
-
throw error;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Get folder info for an entity
|
|
119
|
-
*/
|
|
120
|
-
export async function getFolder(options) {
|
|
121
|
-
const apiClient = new DeSciXApiClient();
|
|
122
|
-
await requireAuth(apiClient);
|
|
123
|
-
|
|
124
|
-
const spinner = ora('Getting folder info...').start();
|
|
125
|
-
|
|
126
|
-
try {
|
|
127
|
-
let entityType, entityIds;
|
|
128
|
-
|
|
129
|
-
if (options.user) {
|
|
130
|
-
entityType = 'user';
|
|
131
|
-
} else if (options.community) {
|
|
132
|
-
entityType = 'community';
|
|
133
|
-
entityIds = { community_id: options.community };
|
|
134
|
-
} else if (options.app) {
|
|
135
|
-
entityType = 'app';
|
|
136
|
-
const parts = options.app.split(/\s+/);
|
|
137
|
-
if (parts.length < 2) {
|
|
138
|
-
throw new Error('App requires both community and app ID: --app "community app"');
|
|
139
|
-
}
|
|
140
|
-
entityIds = { community_id: parts[0], app_id: parts[1] };
|
|
141
|
-
} else if (options.kb) {
|
|
142
|
-
entityType = 'kb';
|
|
143
|
-
const parts = options.kb.split(/\s+/);
|
|
144
|
-
if (parts.length < 3) {
|
|
145
|
-
throw new Error('KB requires community, app, and kb ID: --kb "community app kb"');
|
|
146
|
-
}
|
|
147
|
-
entityIds = { community_id: parts[0], app_id: parts[1], kb_id: parts[2] };
|
|
148
|
-
} else {
|
|
149
|
-
throw new Error('Specify entity type: --user, --community, --app, or --kb');
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
const response = await apiClient.invoke('get_folder_info', {
|
|
153
|
-
entity_type: entityType,
|
|
154
|
-
entity_ids: entityIds
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
const result = response.message || response;
|
|
158
|
-
|
|
159
|
-
spinner.succeed('Folder info retrieved');
|
|
160
|
-
|
|
161
|
-
console.log(chalk.cyan(`\n📁 Folder Info: ${entityType}\n`));
|
|
162
|
-
|
|
163
|
-
if (result.has_folder) {
|
|
164
|
-
console.log(chalk.green(` ✓ Folder registered`));
|
|
165
|
-
console.log(chalk.white(` ID: ${result.folder_id}`));
|
|
166
|
-
if (result.folder_name) {
|
|
167
|
-
console.log(chalk.white(` Name: ${result.folder_name}`));
|
|
168
|
-
}
|
|
169
|
-
if (result.kb_folder_id) {
|
|
170
|
-
console.log(chalk.white(` KB Folder: ${result.kb_folder_id}`));
|
|
171
|
-
}
|
|
172
|
-
console.log(chalk.cyan(` URL: https://drive.google.com/drive/folders/${result.folder_id}`));
|
|
173
|
-
} else {
|
|
174
|
-
console.log(chalk.yellow(` ⚠ No folder registered`));
|
|
175
|
-
console.log(chalk.gray(` Use 'descix folder set' to register a folder`));
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
console.log('');
|
|
179
|
-
return result;
|
|
180
|
-
|
|
181
|
-
} catch (error) {
|
|
182
|
-
spinner.fail('Failed to get folder info');
|
|
183
|
-
console.error(chalk.red(`\n❌ ${error.message}\n`));
|
|
184
|
-
throw error;
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Allocate a new folder for an entity
|
|
190
|
-
*/
|
|
191
|
-
export async function allocateFolder(options) {
|
|
192
|
-
const apiClient = new DeSciXApiClient();
|
|
193
|
-
await requireAuth(apiClient);
|
|
194
|
-
|
|
195
|
-
const spinner = ora('Allocating folder...').start();
|
|
196
|
-
|
|
197
|
-
try {
|
|
198
|
-
let entityType, entityIds, folderName;
|
|
199
|
-
|
|
200
|
-
if (options.app) {
|
|
201
|
-
entityType = 'app';
|
|
202
|
-
const parts = options.app.split(/\s+/);
|
|
203
|
-
if (parts.length < 2) {
|
|
204
|
-
throw new Error('App requires both community and app ID: --app "community app"');
|
|
205
|
-
}
|
|
206
|
-
entityIds = { community_id: parts[0], app_id: parts[1] };
|
|
207
|
-
folderName = options.name || `${parts[0]}_${parts[1]}`;
|
|
208
|
-
} else if (options.kb) {
|
|
209
|
-
entityType = 'kb';
|
|
210
|
-
const parts = options.kb.split(/\s+/);
|
|
211
|
-
if (parts.length < 3) {
|
|
212
|
-
throw new Error('KB requires community, app, and kb ID: --kb "community app kb"');
|
|
213
|
-
}
|
|
214
|
-
entityIds = { community_id: parts[0], app_id: parts[1], kb_id: parts[2] };
|
|
215
|
-
folderName = options.name || parts[2];
|
|
216
|
-
} else {
|
|
217
|
-
throw new Error('Specify entity type: --app or --kb');
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
const response = await apiClient.invoke('allocate_folder', {
|
|
221
|
-
entity_type: entityType,
|
|
222
|
-
entity_ids: entityIds,
|
|
223
|
-
folder_name: folderName
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
const result = response.message || response;
|
|
227
|
-
|
|
228
|
-
spinner.succeed('Folder allocated successfully');
|
|
229
|
-
|
|
230
|
-
console.log(chalk.green(`\n✓ New folder created`));
|
|
231
|
-
console.log(chalk.white(` Name: ${result.folder_name}`));
|
|
232
|
-
console.log(chalk.white(` ID: ${result.folder_id}`));
|
|
233
|
-
console.log(chalk.cyan(` URL: ${result.drive_url}\n`));
|
|
234
|
-
|
|
235
|
-
return result;
|
|
236
|
-
|
|
237
|
-
} catch (error) {
|
|
238
|
-
spinner.fail('Failed to allocate folder');
|
|
239
|
-
console.error(chalk.red(`\n❌ ${error.message}\n`));
|
|
240
|
-
throw error;
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
/**
|
|
245
|
-
* Validate folder access
|
|
246
|
-
*/
|
|
247
|
-
export async function validateFolder(folderId, options = {}) {
|
|
248
|
-
const apiClient = new DeSciXApiClient();
|
|
249
|
-
await requireAuth(apiClient);
|
|
250
|
-
|
|
251
|
-
const spinner = ora('Validating folder...').start();
|
|
252
|
-
|
|
253
|
-
try {
|
|
254
|
-
const parsedFolderId = extractFolderId(folderId);
|
|
255
|
-
|
|
256
|
-
if (!parsedFolderId) {
|
|
257
|
-
throw new Error('Folder ID is required');
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
const response = await apiClient.invoke('validate_folder_access', {
|
|
261
|
-
folder_id: parsedFolderId
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
const result = response.message || response;
|
|
265
|
-
|
|
266
|
-
if (result.accessible) {
|
|
267
|
-
spinner.succeed('Folder is accessible');
|
|
268
|
-
console.log(chalk.green(`\n✓ ${result.message}`));
|
|
269
|
-
console.log(chalk.white(` Name: ${result.folder_name}`));
|
|
270
|
-
console.log(chalk.white(` ID: ${parsedFolderId}`));
|
|
271
|
-
console.log(chalk.cyan(` URL: https://drive.google.com/drive/folders/${parsedFolderId}\n`));
|
|
272
|
-
} else {
|
|
273
|
-
spinner.fail('Folder not accessible');
|
|
274
|
-
console.log(chalk.red(`\n❌ ${result.message}\n`));
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
return result;
|
|
278
|
-
|
|
279
|
-
} catch (error) {
|
|
280
|
-
spinner.fail('Validation failed');
|
|
281
|
-
console.error(chalk.red(`\n❌ ${error.message}\n`));
|
|
282
|
-
throw error;
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
export default {
|
|
287
|
-
setFolder,
|
|
288
|
-
getFolder,
|
|
289
|
-
allocateFolder,
|
|
290
|
-
validateFolder
|
|
291
|
-
};
|
package/lib/core/PathContext.js
DELETED
|
@@ -1,335 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* PathContext - Unified filesystem context for DeSciX CLI
|
|
3
|
-
*
|
|
4
|
-
* Design principle: No "finding" after setup.
|
|
5
|
-
* - Setup time: Detect IDE markers, persist absolute paths to workspace.json
|
|
6
|
-
* - Runtime: Load workspace.json, all paths are simple lookups
|
|
7
|
-
*
|
|
8
|
-
* The workspace.json is the single source of truth for all paths.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import * as fs from 'fs/promises';
|
|
12
|
-
import * as path from 'path';
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Find IDE workspace root by searching for .cursor/ or .vscode/ markers
|
|
16
|
-
* This is the only "searching" allowed - IDE markers are guaranteed in Cursor/VSCode
|
|
17
|
-
*
|
|
18
|
-
* @param {string} startDir - Directory to start searching from
|
|
19
|
-
* @returns {Promise<string|null>} Workspace root path, or null if not in IDE workspace
|
|
20
|
-
*/
|
|
21
|
-
export async function findIDEMarker(startDir = process.cwd()) {
|
|
22
|
-
let currentDir = path.resolve(startDir);
|
|
23
|
-
const root = path.parse(currentDir).root;
|
|
24
|
-
|
|
25
|
-
// Only look for IDE markers (not .descix - that's created by setup)
|
|
26
|
-
const ideMarkers = ['.cursor', '.vscode'];
|
|
27
|
-
|
|
28
|
-
while (currentDir !== root) {
|
|
29
|
-
for (const marker of ideMarkers) {
|
|
30
|
-
const markerDir = path.join(currentDir, marker);
|
|
31
|
-
try {
|
|
32
|
-
const stat = await fs.stat(markerDir);
|
|
33
|
-
if (stat.isDirectory()) {
|
|
34
|
-
return currentDir;
|
|
35
|
-
}
|
|
36
|
-
} catch {
|
|
37
|
-
// Marker not found, continue
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const parentDir = path.dirname(currentDir);
|
|
42
|
-
if (parentDir === currentDir) break;
|
|
43
|
-
currentDir = parentDir;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return null;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* PathContext - Unified path resolution from workspace.json
|
|
51
|
-
*
|
|
52
|
-
* All paths come from workspace.json - no runtime "finding" of app contexts.
|
|
53
|
-
*/
|
|
54
|
-
export class PathContext {
|
|
55
|
-
/**
|
|
56
|
-
* @param {string} workspaceRoot - Absolute path to workspace root
|
|
57
|
-
* @param {Object} config - Parsed workspace.json contents
|
|
58
|
-
*/
|
|
59
|
-
constructor(workspaceRoot, config) {
|
|
60
|
-
this.workspaceRoot = workspaceRoot;
|
|
61
|
-
this.config = config;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Load PathContext from workspace.json
|
|
66
|
-
*
|
|
67
|
-
* @param {string} startDir - Directory to start searching from (default: cwd)
|
|
68
|
-
* @returns {Promise<PathContext>} Loaded context
|
|
69
|
-
* @throws {Error} If not in IDE workspace or workspace not configured
|
|
70
|
-
*/
|
|
71
|
-
static async load(startDir = process.cwd()) {
|
|
72
|
-
// Find IDE marker (only .cursor/ or .vscode/)
|
|
73
|
-
const wsRoot = await findIDEMarker(startDir);
|
|
74
|
-
if (!wsRoot) {
|
|
75
|
-
throw new Error(
|
|
76
|
-
'Not in a Cursor/VSCode workspace.\n' +
|
|
77
|
-
'Open this folder in Cursor or VSCode first.'
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Load workspace.json
|
|
82
|
-
const configPath = path.join(wsRoot, '.descix', 'workspace.json');
|
|
83
|
-
let configData;
|
|
84
|
-
try {
|
|
85
|
-
const content = await fs.readFile(configPath, 'utf-8');
|
|
86
|
-
configData = JSON.parse(content);
|
|
87
|
-
} catch (error) {
|
|
88
|
-
throw new Error(
|
|
89
|
-
'Workspace not configured.\n' +
|
|
90
|
-
'Run "npx descix init" first to configure your workspace.'
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
return new PathContext(wsRoot, configData);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Try to load PathContext, return null if not configured
|
|
99
|
-
* Useful for commands that need to check if workspace exists
|
|
100
|
-
*
|
|
101
|
-
* @param {string} startDir - Directory to start searching from
|
|
102
|
-
* @returns {Promise<PathContext|null>}
|
|
103
|
-
*/
|
|
104
|
-
static async tryLoad(startDir = process.cwd()) {
|
|
105
|
-
try {
|
|
106
|
-
return await PathContext.load(startDir);
|
|
107
|
-
} catch {
|
|
108
|
-
return null;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// ============ Path Getters (all from workspace.json) ============
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Get workspace root path
|
|
116
|
-
* @returns {string} Absolute path to workspace root
|
|
117
|
-
*/
|
|
118
|
-
getWorkspaceRoot() {
|
|
119
|
-
return this.config.workspaceRoot || this.workspaceRoot;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Get community path
|
|
124
|
-
* @param {string} communityId - Community identifier
|
|
125
|
-
* @returns {string|null} Absolute path to community folder, or null if not found
|
|
126
|
-
*/
|
|
127
|
-
getCommunityPath(communityId) {
|
|
128
|
-
const community = this.config.communities?.[communityId];
|
|
129
|
-
if (!community) return null;
|
|
130
|
-
|
|
131
|
-
// Use absolutePath if available, otherwise compute from localPath
|
|
132
|
-
if (community.absolutePath) {
|
|
133
|
-
return community.absolutePath;
|
|
134
|
-
}
|
|
135
|
-
if (community.localPath) {
|
|
136
|
-
return path.join(this.getWorkspaceRoot(), community.localPath);
|
|
137
|
-
}
|
|
138
|
-
return null;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Get app path
|
|
143
|
-
* @param {string} communityId - Community identifier
|
|
144
|
-
* @param {string} appId - App identifier
|
|
145
|
-
* @returns {string|null} Absolute path to app folder, or null if not found
|
|
146
|
-
*/
|
|
147
|
-
getAppPath(communityId, appId) {
|
|
148
|
-
const app = this.config.communities?.[communityId]?.apps?.[appId];
|
|
149
|
-
if (!app) return null;
|
|
150
|
-
|
|
151
|
-
// Use absolutePath if available, otherwise compute from localPath
|
|
152
|
-
if (app.absolutePath) {
|
|
153
|
-
return app.absolutePath;
|
|
154
|
-
}
|
|
155
|
-
if (app.localPath) {
|
|
156
|
-
return path.join(this.getWorkspaceRoot(), app.localPath);
|
|
157
|
-
}
|
|
158
|
-
return null;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Get KB path
|
|
163
|
-
* @param {string} communityId - Community identifier
|
|
164
|
-
* @param {string} appId - App identifier
|
|
165
|
-
* @param {string} kbId - KB identifier (default: 'General')
|
|
166
|
-
* @returns {string|null} Absolute path to KB folder, or null if app not found
|
|
167
|
-
*/
|
|
168
|
-
getKbPath(communityId, appId, kbId = 'General') {
|
|
169
|
-
const appPath = this.getAppPath(communityId, appId);
|
|
170
|
-
return appPath ? path.join(appPath, 'kb', kbId) : null;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Get site path
|
|
175
|
-
* @param {string} communityId - Community identifier
|
|
176
|
-
* @param {string} appId - App identifier
|
|
177
|
-
* @returns {string|null} Absolute path to site folder, or null if app not found
|
|
178
|
-
*/
|
|
179
|
-
getSitePath(communityId, appId) {
|
|
180
|
-
const appPath = this.getAppPath(communityId, appId);
|
|
181
|
-
return appPath ? path.join(appPath, 'site') : null;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* Get microservice path
|
|
186
|
-
* @param {string} communityId - Community identifier
|
|
187
|
-
* @param {string} appId - App identifier
|
|
188
|
-
* @returns {string|null} Absolute path to microservice folder, or null if app not found
|
|
189
|
-
*/
|
|
190
|
-
getMicroservicePath(communityId, appId) {
|
|
191
|
-
const appPath = this.getAppPath(communityId, appId);
|
|
192
|
-
return appPath ? path.join(appPath, 'microservice') : null;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// ============ Context Detection (match cwd against known paths) ============
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* Detect community/app context from current working directory
|
|
199
|
-
* Matches cwd against known absolutePaths in workspace.json
|
|
200
|
-
*
|
|
201
|
-
* @returns {{ communityId: string, appId: string, kbId: string }|null}
|
|
202
|
-
*/
|
|
203
|
-
detectContext() {
|
|
204
|
-
const cwd = process.cwd();
|
|
205
|
-
|
|
206
|
-
for (const [commId, comm] of Object.entries(this.config.communities || {})) {
|
|
207
|
-
for (const [appId, app] of Object.entries(comm.apps || {})) {
|
|
208
|
-
const appPath = app.absolutePath ||
|
|
209
|
-
(app.localPath ? path.join(this.getWorkspaceRoot(), app.localPath) : null);
|
|
210
|
-
|
|
211
|
-
if (appPath && cwd.startsWith(appPath)) {
|
|
212
|
-
return {
|
|
213
|
-
communityId: commId,
|
|
214
|
-
appId,
|
|
215
|
-
kbId: app.kbId || 'General'
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
return null;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* Resolve context by merging CLI options with detected context
|
|
226
|
-
* CLI options take priority over detected context
|
|
227
|
-
*
|
|
228
|
-
* @param {Object} options - CLI options { community, app, kb }
|
|
229
|
-
* @returns {{ communityId: string|null, appId: string|null, kbId: string }}
|
|
230
|
-
*/
|
|
231
|
-
resolveContext(options = {}) {
|
|
232
|
-
const detected = this.detectContext();
|
|
233
|
-
|
|
234
|
-
const resolved = {
|
|
235
|
-
communityId: options.community || detected?.communityId || null,
|
|
236
|
-
appId: options.app || detected?.appId || null,
|
|
237
|
-
kbId: options.kb || detected?.kbId || 'General'
|
|
238
|
-
};
|
|
239
|
-
|
|
240
|
-
return resolved;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
/**
|
|
244
|
-
* Resolve context and throw if community/app not determined
|
|
245
|
-
*
|
|
246
|
-
* @param {Object} options - CLI options { community, app, kb }
|
|
247
|
-
* @returns {{ communityId: string, appId: string, kbId: string }}
|
|
248
|
-
* @throws {Error} If community or app cannot be determined
|
|
249
|
-
*/
|
|
250
|
-
requireContext(options = {}) {
|
|
251
|
-
const ctx = this.resolveContext(options);
|
|
252
|
-
|
|
253
|
-
if (!ctx.communityId || !ctx.appId) {
|
|
254
|
-
throw new Error(
|
|
255
|
-
'Could not determine app context.\n\n' +
|
|
256
|
-
'Options:\n' +
|
|
257
|
-
' 1. cd into an app directory\n' +
|
|
258
|
-
' 2. Use flags: -c <community> -a <app>\n\n' +
|
|
259
|
-
'Example: npx descix kb build -c descix -a appsdk'
|
|
260
|
-
);
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
return ctx;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// ============ Config Access ============
|
|
267
|
-
|
|
268
|
-
/**
|
|
269
|
-
* Get raw workspace.json config
|
|
270
|
-
* @returns {Object}
|
|
271
|
-
*/
|
|
272
|
-
getWorkspaceConfig() {
|
|
273
|
-
return this.config;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
/**
|
|
277
|
-
* Get Drive configuration
|
|
278
|
-
* @returns {{ base_folder_id: string }|null}
|
|
279
|
-
*/
|
|
280
|
-
getDriveConfig() {
|
|
281
|
-
return this.config.driveConfig || null;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
/**
|
|
285
|
-
* Get API URL
|
|
286
|
-
* Derives from env.platform.microservice.port in DEV mode (v2.1 workspace format).
|
|
287
|
-
* @returns {string}
|
|
288
|
-
*/
|
|
289
|
-
getApiUrl() {
|
|
290
|
-
if (this.config.apiUrl) return this.config.apiUrl;
|
|
291
|
-
const env = this.config.env || {};
|
|
292
|
-
const platformPort = env.platform?.microservice?.port;
|
|
293
|
-
if (platformPort && env.environment === 'DEV') return `https://localhost:${platformPort}`;
|
|
294
|
-
return 'https://descix.net';
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
/**
|
|
298
|
-
* Get environment
|
|
299
|
-
* @returns {string}
|
|
300
|
-
*/
|
|
301
|
-
getEnvironment() {
|
|
302
|
-
return this.config.environment || 'production';
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
// ============ Listing ============
|
|
306
|
-
|
|
307
|
-
/**
|
|
308
|
-
* List all community IDs
|
|
309
|
-
* @returns {string[]}
|
|
310
|
-
*/
|
|
311
|
-
listCommunities() {
|
|
312
|
-
return Object.keys(this.config.communities || {});
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
/**
|
|
316
|
-
* List all app IDs (optionally filtered by community)
|
|
317
|
-
* @param {string} communityId - Optional community filter
|
|
318
|
-
* @returns {Array<{ communityId: string, appId: string }>}
|
|
319
|
-
*/
|
|
320
|
-
listApps(communityId = null) {
|
|
321
|
-
const apps = [];
|
|
322
|
-
|
|
323
|
-
for (const [commId, comm] of Object.entries(this.config.communities || {})) {
|
|
324
|
-
if (communityId && commId !== communityId) continue;
|
|
325
|
-
|
|
326
|
-
for (const appId of Object.keys(comm.apps || {})) {
|
|
327
|
-
apps.push({ communityId: commId, appId });
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
return apps;
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
export default PathContext;
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* DeSciXAppSDK.js (V2 - App Shell Edition)
|
|
3
|
-
*
|
|
4
|
-
* A thin wrapper for CodeSites and ChatWidgets running within the DeSciX PWA.
|
|
5
|
-
* Leverages Same-Origin interframe scripting to provide direct access to
|
|
6
|
-
* PWA helpers and the backend API.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
window.DeSciX = (() => {
|
|
10
|
-
const isEmbedded = window.top !== window;
|
|
11
|
-
|
|
12
|
-
// Check for same-origin access
|
|
13
|
-
const hasParentAccess = () => {
|
|
14
|
-
try {
|
|
15
|
-
return isEmbedded && !!window.top.DeSciX;
|
|
16
|
-
} catch (e) {
|
|
17
|
-
return false;
|
|
18
|
-
}
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
if (!hasParentAccess()) {
|
|
22
|
-
console.warn('[DeSciX SDK] Running in standalone mode or cross-origin. Direct PWA helpers unavailable.');
|
|
23
|
-
return {
|
|
24
|
-
mode: 'standalone',
|
|
25
|
-
call: async (command, params) => {
|
|
26
|
-
throw new Error('SDK not initialized in PWA App Shell mode.');
|
|
27
|
-
}
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
console.log('[DeSciX SDK] Initialized in App Shell mode.');
|
|
32
|
-
|
|
33
|
-
// Proxy to the PWA's global DeSciX object
|
|
34
|
-
return {
|
|
35
|
-
mode: 'shell',
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Access to global application data (Communities, Apps, User Profile)
|
|
39
|
-
*/
|
|
40
|
-
get AppData() { return window.top.DeSciX.AppData; },
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Access to the PWA state machine and navigation
|
|
44
|
-
*/
|
|
45
|
-
get AppContext() { return window.top.DeSciX.AppContext; },
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Direct API call helper (proxied through PWA session)
|
|
49
|
-
*/
|
|
50
|
-
call: (command, params) => window.top.DeSciX.call(command, params),
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Chat interaction helpers
|
|
54
|
-
*/
|
|
55
|
-
Chat: {
|
|
56
|
-
suggestPrompt: (text) => {
|
|
57
|
-
// Future: trigger text injection into ChatWidget
|
|
58
|
-
console.log('[DeSciX SDK] Suggesting prompt:', text);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
};
|
|
62
|
-
})();
|