@descix/cli 1.0.0
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 +95 -0
- package/SERVICE_README_sdk.md +399 -0
- package/agent-assets/guides/README.md +61 -0
- package/agent-assets/guides/dynamic-site-pattern.md +125 -0
- package/agent-assets/guides/knowledge-only-pattern.md +93 -0
- package/agent-assets/guides/microservice-pattern.md +152 -0
- package/agent-assets/guides/static-site-pattern.md +111 -0
- package/agent-assets/instructions/app-asset-validation.md +477 -0
- package/agent-assets/instructions/onboarding-flow.md +408 -0
- package/agent-assets/instructions/standard-folder-structure.md +125 -0
- package/agent-assets/reference/app-creation.md +381 -0
- package/agent-assets/reference/architecture.md +406 -0
- package/agent-assets/reference/local-dev.md +138 -0
- package/agent-assets/reference/mcp-integration.md +424 -0
- package/agent-assets/reference/workspace-config.md +415 -0
- package/agent-assets/rules/descix_mcp.template.mdc +243 -0
- package/bin/descix.js +3930 -0
- package/bin/mcp-server.js +382 -0
- package/descix-cli-1.0.0.tgz +0 -0
- package/lib/agent-files.js +162 -0
- package/lib/api-client.js +458 -0
- package/lib/auth-guard.js +67 -0
- package/lib/commands/app-wizard.js +627 -0
- package/lib/commands/auth.js +407 -0
- package/lib/commands/buy.js +207 -0
- package/lib/commands/clone.js +91 -0
- package/lib/commands/config.js +155 -0
- package/lib/commands/doctor.js +277 -0
- package/lib/commands/folder.js +291 -0
- package/lib/commands/init.js +257 -0
- package/lib/commands/kb.js +654 -0
- package/lib/commands/mcp.js +273 -0
- package/lib/commands/serve.js +12 -0
- package/lib/commands/status.js +239 -0
- package/lib/commands/update.js +653 -0
- package/lib/core/Chunker.js +610 -0
- package/lib/core/Hydrator.js +801 -0
- package/lib/core/PathContext.js +335 -0
- package/lib/core/Syncer.js +310 -0
- package/lib/core/index.js +32 -0
- package/lib/global-config.js +86 -0
- package/lib/google-storage-adc.js +511 -0
- package/lib/wallet-file.js +232 -0
- package/lib/wizard/setup.js +502 -0
- package/lib/workspace-config.js +712 -0
- package/lib/workspace-utils.js +126 -0
- package/package.json +41 -0
- package/templates/agent-claude.md +226 -0
- package/templates/agent-cline.md +133 -0
- package/templates/agent-copilot.md +133 -0
- package/templates/agent-cursor.md +133 -0
- package/templates/descix_mcp.mdc +122 -0
- package/templates/drive/README.md +42 -0
- package/templates/drive/app/assets/app_description.md +13 -0
- package/templates/drive/app/assets/icon.png +0 -0
- package/templates/drive/app/assets/system_instructions.md +17 -0
- package/templates/drive/app/kb/General/README.md +25 -0
- package/templates/drive/community/Apps/.gitkeep +4 -0
- package/templates/drive/community/community_assets/community_description.md +15 -0
- package/templates/drive/community/community_assets/icon.png +0 -0
- package/templates/drive_templates/README.md +104 -0
- package/templates/drive_templates/agent_app_template/assets/app_description.txt +13 -0
- package/templates/drive_templates/agent_app_template/assets/system_instructions.txt +17 -0
- package/templates/drive_templates/agent_app_template/kb/General/README.md +25 -0
- package/templates/drive_templates/agent_app_template/microservice/.gitkeep +10 -0
- package/templates/drive_templates/agent_app_template/site/.gitkeep +4 -0
- package/templates/drive_templates/community_template/Apps/.gitkeep +4 -0
- package/templates/drive_templates/community_template/community_assets/community_description.txt +15 -0
- package/templates/scaffolds/README.md +62 -0
- package/templates/scaffolds/microservice/Dockerfile +19 -0
- package/templates/scaffolds/microservice/SERVICE_README_sdk.md +399 -0
- package/templates/scaffolds/microservice/app.js +50 -0
- package/templates/scaffolds/microservice/app.yaml +8 -0
- package/templates/scaffolds/microservice/defaults-config.json +5 -0
- package/templates/scaffolds/microservice/dev-overrides.json.example +5 -0
- package/templates/scaffolds/microservice/manifest.json +49 -0
- package/templates/scaffolds/microservice/package-lock.json +2645 -0
- package/templates/scaffolds/microservice/package.json +29 -0
- package/templates/scaffolds/microservice/scripts/register.js +55 -0
- package/templates/scaffolds/microservice/services/apiFront.js +75 -0
- package/templates/scaffolds/microservice/services/commandHandlers/sampleCommands.js +57 -0
- package/templates/scaffolds/microservice/services/mcpClient.js +78 -0
- package/templates/scaffolds/microservice/services/utils.js +247 -0
- package/templates/scaffolds/microservice/setup-schema.json +18 -0
- package/templates/scaffolds/microservice/templates/SERVICE_README_TEMPLATE.md +143 -0
- package/templates/scaffolds/site/DeSciXAppSDK.js +62 -0
- package/templates/scaffolds/site/README.md +51 -0
- package/templates/scaffolds/site/app.js +22 -0
- package/templates/scaffolds/site/index.html +42 -0
- package/templates/scaffolds/site/styles.css +123 -0
- package/test-hydration.js +150 -0
- package/vendor/mcp/transports/http-sse-transport.js +196 -0
- package/vendor/mcp/transports/index.js +15 -0
- package/vendor/mcp/transports/stdio-transport.js +118 -0
|
@@ -0,0 +1,291 @@
|
|
|
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
|
+
};
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DeSciX CLI Init Command
|
|
3
|
+
*
|
|
4
|
+
* Creates .descix/workspace.json (template-based creation is via PWA/Admin CLI).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import readline from 'readline';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
import fs from 'fs/promises';
|
|
11
|
+
import { fileURLToPath } from 'url';
|
|
12
|
+
import { dirname, join } from 'path';
|
|
13
|
+
// clone.js is imported dynamically inside the invite flow to avoid circular deps
|
|
14
|
+
import { WorkspaceConfig } from '../workspace-config.js';
|
|
15
|
+
import { generateAgentFiles } from '../agent-files.js';
|
|
16
|
+
|
|
17
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
18
|
+
const __dirname = dirname(__filename);
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Create readline interface for interactive prompts
|
|
22
|
+
*/
|
|
23
|
+
function createPrompt() {
|
|
24
|
+
return readline.createInterface({
|
|
25
|
+
input: process.stdin,
|
|
26
|
+
output: process.stdout
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Ask for text input with optional default
|
|
32
|
+
*/
|
|
33
|
+
function askInput(rl, question, defaultValue = '') {
|
|
34
|
+
return new Promise((resolve) => {
|
|
35
|
+
const prompt = defaultValue ? `${question} [${defaultValue}]: ` : `${question}: `;
|
|
36
|
+
rl.question(prompt, (answer) => {
|
|
37
|
+
resolve(answer.trim() || defaultValue);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Ask a yes/no question
|
|
44
|
+
*/
|
|
45
|
+
function askYesNo(rl, question, defaultYes = true) {
|
|
46
|
+
return new Promise((resolve) => {
|
|
47
|
+
const suffix = defaultYes ? '(Y/n)' : '(y/N)';
|
|
48
|
+
rl.question(`${question} ${suffix}: `, (answer) => {
|
|
49
|
+
const trimmed = answer.trim().toLowerCase();
|
|
50
|
+
if (trimmed === '') {
|
|
51
|
+
resolve(defaultYes);
|
|
52
|
+
} else {
|
|
53
|
+
resolve(trimmed === 'y' || trimmed === 'yes');
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Run the init command
|
|
61
|
+
* @param {Object} apiClient - API client (optional, for listing communities)
|
|
62
|
+
* @param {Object} options - Command options
|
|
63
|
+
*/
|
|
64
|
+
export async function runInit(apiClient, options = {}) {
|
|
65
|
+
const rl = createPrompt();
|
|
66
|
+
const projectPath = options.path ? path.resolve(options.path) : process.cwd();
|
|
67
|
+
|
|
68
|
+
// If --from-invite provided, resolve the invite first to pre-fill context
|
|
69
|
+
if (options.fromInvite) {
|
|
70
|
+
if (!apiClient) {
|
|
71
|
+
console.error(chalk.red('\n❌ Authentication required for --from-invite. Run "descix login" first.\n'));
|
|
72
|
+
rl.close();
|
|
73
|
+
return {};
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
console.log(chalk.cyan('Resolving invite...\n'));
|
|
77
|
+
const inviteData = await apiClient.invoke('resolve_invite', { invite_token: options.fromInvite });
|
|
78
|
+
|
|
79
|
+
// Write app.json seed file
|
|
80
|
+
const descixDir = join(projectPath, '.descix');
|
|
81
|
+
await fs.mkdir(descixDir, { recursive: true });
|
|
82
|
+
const appJson = {
|
|
83
|
+
version: '1.0',
|
|
84
|
+
invite_token: options.fromInvite,
|
|
85
|
+
invite_type: inviteData.invite_type,
|
|
86
|
+
app_id: inviteData.app_id,
|
|
87
|
+
community_id: inviteData.community_id,
|
|
88
|
+
app_name: inviteData.app_name,
|
|
89
|
+
agent_hint: inviteData.agent_hint,
|
|
90
|
+
kb_ready: inviteData.kb_ready,
|
|
91
|
+
has_repo: inviteData.has_repo
|
|
92
|
+
};
|
|
93
|
+
await fs.writeFile(join(descixDir, 'app.json'), JSON.stringify(appJson, null, 2));
|
|
94
|
+
|
|
95
|
+
console.log(chalk.green(`Invite resolved: ${inviteData.app_name} (${inviteData.community_id}/${inviteData.app_id})`));
|
|
96
|
+
if (inviteData.agent_hint) {
|
|
97
|
+
console.log(chalk.gray(`Agent hint: ${inviteData.agent_hint}`));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Auto-fill community and app for the rest of init
|
|
101
|
+
options.community = options.community || inviteData.community_id;
|
|
102
|
+
options.app = options.app || inviteData.app_id;
|
|
103
|
+
|
|
104
|
+
// Offer to clone if repo exists
|
|
105
|
+
if (inviteData.has_repo) {
|
|
106
|
+
console.log(chalk.cyan('\nThis app has a linked repository.'));
|
|
107
|
+
const { runClone } = await import('./clone.js');
|
|
108
|
+
await runClone(apiClient, { app_id: inviteData.app_id });
|
|
109
|
+
}
|
|
110
|
+
} catch (error) {
|
|
111
|
+
console.error(chalk.red(`\n❌ Failed to resolve invite: ${error.message}\n`));
|
|
112
|
+
rl.close();
|
|
113
|
+
return {};
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
console.log(chalk.cyan('\n╔════════════════════════════════════════════╗'));
|
|
118
|
+
console.log(chalk.cyan('║ DeSciX Workspace Initialization ║'));
|
|
119
|
+
console.log(chalk.cyan('╚════════════════════════════════════════════╝\n'));
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
const descixDir = join(projectPath, '.descix');
|
|
123
|
+
const configPath = join(descixDir, 'workspace.json');
|
|
124
|
+
let hasExisting = false;
|
|
125
|
+
try {
|
|
126
|
+
await fs.access(configPath);
|
|
127
|
+
hasExisting = true;
|
|
128
|
+
} catch {
|
|
129
|
+
// No existing config
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (hasExisting && !options.force) {
|
|
133
|
+
const overwrite = await askYesNo(rl, chalk.yellow('Workspace already initialized. Overwrite?'), false);
|
|
134
|
+
if (!overwrite) {
|
|
135
|
+
console.log(chalk.gray('\nInitialization cancelled.\n'));
|
|
136
|
+
rl.close();
|
|
137
|
+
return {};
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
let communityId = options.community;
|
|
142
|
+
if (!communityId) {
|
|
143
|
+
if (apiClient) {
|
|
144
|
+
try {
|
|
145
|
+
const response = await apiClient.invoke('find_communities', {});
|
|
146
|
+
const communities = response.message?.communities || [];
|
|
147
|
+
if (communities.length > 0) {
|
|
148
|
+
console.log(chalk.cyan('Available Communities:'));
|
|
149
|
+
communities.slice(0, 10).forEach((c, i) => {
|
|
150
|
+
console.log(chalk.gray(` ${i + 1}. ${c.community_id} (${c.community_name})`));
|
|
151
|
+
});
|
|
152
|
+
if (communities.length > 10) console.log(chalk.gray(` ... and ${communities.length - 10} more`));
|
|
153
|
+
console.log();
|
|
154
|
+
}
|
|
155
|
+
} catch {
|
|
156
|
+
// ignore
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
communityId = await askInput(rl, chalk.white('Community ID'));
|
|
160
|
+
if (!communityId) {
|
|
161
|
+
console.log(chalk.red('\n❌ Community ID is required.\n'));
|
|
162
|
+
rl.close();
|
|
163
|
+
return {};
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const defaultAppName = path.basename(projectPath).toLowerCase().replace(/[^a-z0-9]/g, '_');
|
|
168
|
+
let appName = options.app;
|
|
169
|
+
if (!appName) {
|
|
170
|
+
appName = await askInput(rl, chalk.white('App name'), defaultAppName);
|
|
171
|
+
if (!appName) {
|
|
172
|
+
console.log(chalk.red('\n❌ App name is required.\n'));
|
|
173
|
+
rl.close();
|
|
174
|
+
return {};
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const appId = appName.toLowerCase().replace(/[^a-z0-9_-]/g, '_');
|
|
179
|
+
console.log(chalk.cyan('\n─── Summary ───\n'));
|
|
180
|
+
console.log(chalk.white(` Project: ${projectPath}`));
|
|
181
|
+
console.log(chalk.white(` Community: ${communityId}`));
|
|
182
|
+
console.log(chalk.white(` App: ${appName} (${appId})`));
|
|
183
|
+
|
|
184
|
+
const proceed = await askYesNo(rl, chalk.white('\nProceed?'), true);
|
|
185
|
+
if (!proceed) {
|
|
186
|
+
console.log(chalk.gray('\nCancelled.\n'));
|
|
187
|
+
rl.close();
|
|
188
|
+
return {};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
console.log(chalk.gray('\nWriting .descix/workspace.json...\n'));
|
|
192
|
+
const config = new WorkspaceConfig({ version: '2.0', type: 'workspace', communities: {} }, projectPath);
|
|
193
|
+
config.registerApp(communityId, appId, { localPath: '.', kbId: 'General' });
|
|
194
|
+
await config.save(projectPath);
|
|
195
|
+
|
|
196
|
+
console.log(chalk.green('Created:'));
|
|
197
|
+
console.log(chalk.green(' ✓ .descix/workspace.json'));
|
|
198
|
+
|
|
199
|
+
// Generate agent instruction files with correct context
|
|
200
|
+
try {
|
|
201
|
+
const agentFilesWritten = await generateAgentFiles(projectPath);
|
|
202
|
+
for (const f of agentFilesWritten) {
|
|
203
|
+
console.log(chalk.green(` ✓ ${f}`));
|
|
204
|
+
}
|
|
205
|
+
} catch {
|
|
206
|
+
// Agent file generation is best-effort
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
console.log(chalk.cyan('\n─── Next Steps ───\n'));
|
|
210
|
+
console.log(chalk.white(` 1. descix app init -a ${appId} -c ${communityId} # register app on platform`));
|
|
211
|
+
console.log(chalk.white(` 2. descix update kb -c ${communityId} -a ${appId} # sync KB to Pinecone`));
|
|
212
|
+
console.log(chalk.white(` 3. descix chat -c ${communityId} -a ${appId} -q "test" # verify RAG\n`));
|
|
213
|
+
console.log(chalk.green('✅ Workspace initialized.\n'));
|
|
214
|
+
rl.close();
|
|
215
|
+
return { created: ['.descix/workspace.json'], skipped: [], warnings: [] };
|
|
216
|
+
|
|
217
|
+
} catch (error) {
|
|
218
|
+
rl.close();
|
|
219
|
+
throw error;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Non-interactive init: writes .descix/workspace.json only.
|
|
225
|
+
* @param {Object} options - { path?, communityId, appName? }
|
|
226
|
+
* @returns {Promise<Object>} { created, skipped, warnings }
|
|
227
|
+
*/
|
|
228
|
+
export async function initWorkspace(options) {
|
|
229
|
+
const projectPath = options.path ? path.resolve(options.path) : process.cwd();
|
|
230
|
+
const communityId = options.communityId;
|
|
231
|
+
const appName = options.appName || path.basename(projectPath).toLowerCase().replace(/[^a-z0-9]/g, '_');
|
|
232
|
+
const appId = appName.toLowerCase().replace(/[^a-z0-9_-]/g, '_');
|
|
233
|
+
if (!communityId) {
|
|
234
|
+
throw new Error('communityId is required');
|
|
235
|
+
}
|
|
236
|
+
const config = new WorkspaceConfig({ version: '2.0', type: 'workspace', communities: {} }, projectPath);
|
|
237
|
+
config.registerApp(communityId, appId, { localPath: '.', kbId: 'General' });
|
|
238
|
+
await config.save(projectPath);
|
|
239
|
+
|
|
240
|
+
// Generate agent instruction files with workspace context
|
|
241
|
+
const created = ['.descix/workspace.json'];
|
|
242
|
+
try {
|
|
243
|
+
const agentFilesWritten = await generateAgentFiles(projectPath);
|
|
244
|
+
created.push(...agentFilesWritten);
|
|
245
|
+
} catch {
|
|
246
|
+
// Best-effort
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return {
|
|
250
|
+
created,
|
|
251
|
+
skipped: [],
|
|
252
|
+
warnings: [],
|
|
253
|
+
nextSteps: [`descix app init -a ${appId} -c ${communityId}`, `descix update kb -c ${communityId} -a ${appId}`]
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export default { runInit, initWorkspace };
|