@descix/cli 1.0.0 → 1.0.3
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 +9 -9
- 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 +209 -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 +29 -9
- 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 +30 -0
- 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/SERVICE_README_sdk.md +0 -399
- package/descix-cli-1.0.0.tgz +0 -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/test-hydration.js +0 -150
|
@@ -1,627 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Interactive App Creation Wizard
|
|
3
|
-
* Guides developers through creating/updating an app with all assets
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import chalk from 'chalk';
|
|
7
|
-
import readline from 'readline';
|
|
8
|
-
import * as fs from 'fs/promises';
|
|
9
|
-
import * as path from 'path';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Create readline interface for interactive prompts
|
|
13
|
-
*/
|
|
14
|
-
function createPrompt() {
|
|
15
|
-
return readline.createInterface({
|
|
16
|
-
input: process.stdin,
|
|
17
|
-
output: process.stdout
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Ask a yes/no question
|
|
23
|
-
*/
|
|
24
|
-
async function askYesNo(rl, question, defaultYes = true) {
|
|
25
|
-
return new Promise((resolve) => {
|
|
26
|
-
const suffix = defaultYes ? '(Y/n)' : '(y/N)';
|
|
27
|
-
rl.question(`${question} ${suffix}: `, (answer) => {
|
|
28
|
-
const trimmed = answer.trim().toLowerCase();
|
|
29
|
-
if (trimmed === '') {
|
|
30
|
-
resolve(defaultYes);
|
|
31
|
-
} else {
|
|
32
|
-
resolve(trimmed === 'y' || trimmed === 'yes');
|
|
33
|
-
}
|
|
34
|
-
});
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Ask for text input with optional default
|
|
40
|
-
*/
|
|
41
|
-
async function askInput(rl, question, defaultValue = '') {
|
|
42
|
-
return new Promise((resolve) => {
|
|
43
|
-
const prompt = defaultValue ? `${question} [${defaultValue}]: ` : `${question}: `;
|
|
44
|
-
rl.question(prompt, (answer) => {
|
|
45
|
-
resolve(answer.trim() || defaultValue);
|
|
46
|
-
});
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Ask for file/folder path with validation
|
|
52
|
-
*/
|
|
53
|
-
async function askPath(rl, question, mustExist = true, isDirectory = false) {
|
|
54
|
-
return new Promise((resolve) => {
|
|
55
|
-
const askPathRecursive = async () => {
|
|
56
|
-
const input = await askInput(rl, question);
|
|
57
|
-
|
|
58
|
-
if (!input) {
|
|
59
|
-
resolve(null); // Allow skip
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Expand ~ to home directory
|
|
64
|
-
const expandedPath = input.startsWith('~')
|
|
65
|
-
? path.join(process.env.HOME || '', input.slice(1))
|
|
66
|
-
: input;
|
|
67
|
-
|
|
68
|
-
const absolutePath = path.isAbsolute(expandedPath)
|
|
69
|
-
? expandedPath
|
|
70
|
-
: path.resolve(process.cwd(), expandedPath);
|
|
71
|
-
|
|
72
|
-
if (mustExist) {
|
|
73
|
-
try {
|
|
74
|
-
const stats = await fs.stat(absolutePath);
|
|
75
|
-
if (isDirectory && !stats.isDirectory()) {
|
|
76
|
-
console.log(chalk.yellow(` ⚠️ Path exists but is not a directory.`));
|
|
77
|
-
askPathRecursive();
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
if (!isDirectory && stats.isDirectory()) {
|
|
81
|
-
console.log(chalk.yellow(` ⚠️ Path is a directory, expected a file.`));
|
|
82
|
-
askPathRecursive();
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
resolve(absolutePath);
|
|
86
|
-
} catch (err) {
|
|
87
|
-
console.log(chalk.yellow(` ⚠️ Path does not exist: ${absolutePath}`));
|
|
88
|
-
askPathRecursive();
|
|
89
|
-
}
|
|
90
|
-
} else {
|
|
91
|
-
resolve(absolutePath);
|
|
92
|
-
}
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
askPathRecursive();
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Read file content as base64
|
|
101
|
-
*/
|
|
102
|
-
async function readFileAsBase64(filePath) {
|
|
103
|
-
const content = await fs.readFile(filePath);
|
|
104
|
-
return content.toString('base64');
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Read all files from a directory recursively
|
|
109
|
-
*/
|
|
110
|
-
async function readDirectoryFiles(dirPath, basePath = '') {
|
|
111
|
-
const files = [];
|
|
112
|
-
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
113
|
-
|
|
114
|
-
for (const entry of entries) {
|
|
115
|
-
const fullPath = path.join(dirPath, entry.name);
|
|
116
|
-
const relPath = path.join(basePath, entry.name);
|
|
117
|
-
|
|
118
|
-
if (entry.isDirectory()) {
|
|
119
|
-
const subFiles = await readDirectoryFiles(fullPath, relPath);
|
|
120
|
-
files.push(...subFiles);
|
|
121
|
-
} else if (entry.isFile()) {
|
|
122
|
-
// Skip hidden files and common non-content files
|
|
123
|
-
if (entry.name.startsWith('.') || entry.name === 'Thumbs.db') {
|
|
124
|
-
continue;
|
|
125
|
-
}
|
|
126
|
-
const content = await fs.readFile(fullPath);
|
|
127
|
-
files.push({
|
|
128
|
-
name: entry.name,
|
|
129
|
-
path: relPath,
|
|
130
|
-
content: content.toString('base64')
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
return files;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
import crypto from 'crypto';
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Generate a new service key pair
|
|
142
|
-
*/
|
|
143
|
-
function generateServiceKey() {
|
|
144
|
-
const { publicKey, privateKey } = crypto.generateKeyPairSync('ec', {
|
|
145
|
-
namedCurve: 'secp256k1',
|
|
146
|
-
publicKeyEncoding: {
|
|
147
|
-
type: 'spki',
|
|
148
|
-
format: 'pem'
|
|
149
|
-
},
|
|
150
|
-
privateKeyEncoding: {
|
|
151
|
-
type: 'pkcs8',
|
|
152
|
-
format: 'pem'
|
|
153
|
-
}
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
return { publicKey, privateKey };
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Run the interactive app creation wizard
|
|
161
|
-
*/
|
|
162
|
-
export async function runAppWizard(apiClient, options) {
|
|
163
|
-
const rl = createPrompt();
|
|
164
|
-
|
|
165
|
-
console.log(chalk.cyan('\n╔════════════════════════════════════════════╗'));
|
|
166
|
-
console.log(chalk.cyan('║ DeSciX App Creation Wizard ║'));
|
|
167
|
-
console.log(chalk.cyan('╚════════════════════════════════════════════╝\n'));
|
|
168
|
-
|
|
169
|
-
try {
|
|
170
|
-
// Phase 1: Entitlements Discovery
|
|
171
|
-
console.log(chalk.gray(' Fetching entitlements...'));
|
|
172
|
-
const entitlementsResponse = await apiClient.invoke('fetch_my_purchases', {});
|
|
173
|
-
const entitlements = entitlementsResponse.message || {};
|
|
174
|
-
const serviceSlots = entitlements.service_slots || [];
|
|
175
|
-
|
|
176
|
-
if (serviceSlots.length === 0) {
|
|
177
|
-
console.log(chalk.yellow('\n⚠️ No service slots available.'));
|
|
178
|
-
console.log(chalk.white(' You need a "Runner NFT" or Subscription to create an app.'));
|
|
179
|
-
console.log(chalk.gray(' Visit https://descix.net/store to purchase one.\n'));
|
|
180
|
-
rl.close();
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
console.log(chalk.cyan('\nSelect a Service Slot for this App:'));
|
|
185
|
-
serviceSlots.forEach((slot, index) => {
|
|
186
|
-
const statusColor = slot.status === 'active' ? chalk.green : chalk.gray;
|
|
187
|
-
console.log(`${index + 1}. ${chalk.bold(slot.name)} (${slot.type})`);
|
|
188
|
-
console.log(` Specs: ${slot.specs.ram}, ${slot.specs.cpu}`);
|
|
189
|
-
console.log(` Status: ${statusColor(slot.status)}`);
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
const slotIndex = await askInput(rl, chalk.white('\nSelect Slot #'), '1');
|
|
193
|
-
const selectedSlot = serviceSlots[parseInt(slotIndex) - 1];
|
|
194
|
-
|
|
195
|
-
if (!selectedSlot) {
|
|
196
|
-
console.log(chalk.red('\n❌ Invalid selection.\n'));
|
|
197
|
-
rl.close();
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
console.log(chalk.green(`\n ✓ Selected: ${selectedSlot.name}\n`));
|
|
202
|
-
|
|
203
|
-
// Step 1: Get community and app name if not provided
|
|
204
|
-
let communityId = options.community;
|
|
205
|
-
let appName = options.name;
|
|
206
|
-
|
|
207
|
-
if (!communityId) {
|
|
208
|
-
// Show available communities
|
|
209
|
-
const communities = entitlements.communities || [];
|
|
210
|
-
if (communities.length > 0) {
|
|
211
|
-
console.log(chalk.cyan('Select Community:'));
|
|
212
|
-
communities.forEach((c, i) => {
|
|
213
|
-
console.log(`${i + 1}. ${c.community_name} (${c.community_id})`);
|
|
214
|
-
});
|
|
215
|
-
const commIndex = await askInput(rl, chalk.white('\nSelect Community # (or type ID)'), '1');
|
|
216
|
-
const selectedComm = communities[parseInt(commIndex) - 1];
|
|
217
|
-
communityId = selectedComm ? selectedComm.community_id : commIndex;
|
|
218
|
-
} else {
|
|
219
|
-
communityId = await askInput(rl, chalk.white('Community ID'));
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
if (!communityId) {
|
|
223
|
-
console.log(chalk.red('\n❌ Community ID is required.\n'));
|
|
224
|
-
rl.close();
|
|
225
|
-
return;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
if (!appName) {
|
|
230
|
-
appName = await askInput(rl, chalk.white('App name'));
|
|
231
|
-
if (!appName) {
|
|
232
|
-
console.log(chalk.red('\n❌ App name is required.\n'));
|
|
233
|
-
rl.close();
|
|
234
|
-
return;
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
console.log(chalk.gray(`\n📋 Creating app '${appName}' in community '${communityId}'...\n`));
|
|
239
|
-
|
|
240
|
-
// Step 2: Check developer permission
|
|
241
|
-
console.log(chalk.gray(' Checking developer permissions...'));
|
|
242
|
-
const permCheck = await apiClient.invoke('check_developer_permission', {
|
|
243
|
-
community_id: communityId
|
|
244
|
-
});
|
|
245
|
-
const permResult = permCheck.message || permCheck;
|
|
246
|
-
|
|
247
|
-
if (!permResult.has_permission) {
|
|
248
|
-
console.log(chalk.red(`\n❌ ${permResult.message}`));
|
|
249
|
-
console.log(chalk.yellow(' Contact the community admin to request developer access.\n'));
|
|
250
|
-
rl.close();
|
|
251
|
-
return;
|
|
252
|
-
}
|
|
253
|
-
console.log(chalk.green(` ✓ Developer permission confirmed (${permResult.permission_type})\n`));
|
|
254
|
-
|
|
255
|
-
// Step 3: Check base folder registration
|
|
256
|
-
console.log(chalk.gray(' Checking Drive folder registration...'));
|
|
257
|
-
let folderCheck = await apiClient.invoke('get_user_base_folder', {});
|
|
258
|
-
let folderResult = folderCheck.message || folderCheck;
|
|
259
|
-
|
|
260
|
-
if (!folderResult.has_folder) {
|
|
261
|
-
console.log(chalk.yellow(`\n⚠️ No base folder registered yet.`));
|
|
262
|
-
console.log(chalk.gray('\n You need a Google Drive folder shared with dip@descix.net (Editor access).'));
|
|
263
|
-
console.log(chalk.gray(' This is where your app files will be stored.\n'));
|
|
264
|
-
|
|
265
|
-
const registerNow = await askYesNo(rl, chalk.white(' Do you have a folder ready to register?'), true);
|
|
266
|
-
|
|
267
|
-
if (!registerNow) {
|
|
268
|
-
console.log(chalk.gray('\n To set up a folder:'));
|
|
269
|
-
console.log(chalk.white(' 1. Create a folder in your Google Drive'));
|
|
270
|
-
console.log(chalk.white(' 2. Share it with dip@descix.net (Editor access)'));
|
|
271
|
-
console.log(chalk.white(' 3. Run this wizard again\n'));
|
|
272
|
-
rl.close();
|
|
273
|
-
return;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// Ask for folder URL/ID
|
|
277
|
-
const folderInput = await askInput(rl, chalk.white('\n Enter folder URL or ID'));
|
|
278
|
-
if (!folderInput) {
|
|
279
|
-
console.log(chalk.red('\n❌ Folder URL is required.\n'));
|
|
280
|
-
rl.close();
|
|
281
|
-
return;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
// Extract folder ID from URL if needed
|
|
285
|
-
let folderId = folderInput.trim();
|
|
286
|
-
if (folderId.includes('drive.google.com')) {
|
|
287
|
-
const match = folderId.match(/\/folders\/([a-zA-Z0-9_-]+)/);
|
|
288
|
-
if (match && match[1]) {
|
|
289
|
-
folderId = match[1];
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
// Also handle /d/ URLs
|
|
293
|
-
if (folderId.includes('/d/')) {
|
|
294
|
-
const match = folderId.match(/\/d\/([a-zA-Z0-9_-]+)/);
|
|
295
|
-
if (match && match[1]) {
|
|
296
|
-
folderId = match[1];
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
console.log(chalk.gray(`\n Registering folder: ${folderId}...`));
|
|
301
|
-
|
|
302
|
-
try {
|
|
303
|
-
const registerResult = await apiClient.invoke('register_base_folder', {
|
|
304
|
-
folder_url: folderId
|
|
305
|
-
});
|
|
306
|
-
const registerData = registerResult.message || registerResult;
|
|
307
|
-
console.log(chalk.green(` ✓ Folder registered: ${registerData.folder_name || folderId}`));
|
|
308
|
-
folderResult = { has_folder: true, folder_id: folderId };
|
|
309
|
-
} catch (err) {
|
|
310
|
-
console.log(chalk.red(`\n❌ ${err.message}`));
|
|
311
|
-
console.log(chalk.yellow('\n Make sure you\'ve shared the folder with dip@descix.net (Editor access)\n'));
|
|
312
|
-
rl.close();
|
|
313
|
-
return;
|
|
314
|
-
}
|
|
315
|
-
} else {
|
|
316
|
-
console.log(chalk.green(` ✓ Base folder registered: ${folderResult.folder_id}`));
|
|
317
|
-
}
|
|
318
|
-
console.log();
|
|
319
|
-
|
|
320
|
-
// Step 4: Check if app already exists
|
|
321
|
-
const appId = appName.toLowerCase().replace(/[^a-z0-9_-]/g, '');
|
|
322
|
-
console.log(chalk.gray(' Checking for existing app...'));
|
|
323
|
-
const existsCheck = await apiClient.invoke('check_app_exists', {
|
|
324
|
-
community_id: communityId,
|
|
325
|
-
app_id: appId
|
|
326
|
-
});
|
|
327
|
-
const existsResult = existsCheck.message || existsCheck;
|
|
328
|
-
|
|
329
|
-
let overwrite = false;
|
|
330
|
-
if (existsResult.exists) {
|
|
331
|
-
console.log(chalk.yellow(`\n⚠️ App '${appId}' already exists in community '${communityId}'.`));
|
|
332
|
-
overwrite = await askYesNo(rl, chalk.white(' Overwrite existing app?'), false);
|
|
333
|
-
if (!overwrite) {
|
|
334
|
-
console.log(chalk.gray('\n Operation cancelled.\n'));
|
|
335
|
-
rl.close();
|
|
336
|
-
return;
|
|
337
|
-
}
|
|
338
|
-
console.log(chalk.gray(' Will update existing app.\n'));
|
|
339
|
-
} else {
|
|
340
|
-
console.log(chalk.green(` ✓ App '${appId}' is available\n`));
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// Step 5: Collect optional assets
|
|
344
|
-
console.log(chalk.cyan('─── Optional Assets ───\n'));
|
|
345
|
-
console.log(chalk.gray(' Press Enter to skip any of these.\n'));
|
|
346
|
-
|
|
347
|
-
// Icon file
|
|
348
|
-
const iconPath = await askPath(rl, chalk.white(' Icon file path (png/jpg)'), true, false);
|
|
349
|
-
let iconBase64 = null;
|
|
350
|
-
if (iconPath) {
|
|
351
|
-
try {
|
|
352
|
-
iconBase64 = await readFileAsBase64(iconPath);
|
|
353
|
-
console.log(chalk.green(` ✓ Icon loaded: ${path.basename(iconPath)}`));
|
|
354
|
-
} catch (err) {
|
|
355
|
-
console.log(chalk.yellow(` ⚠️ Could not read icon: ${err.message}`));
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
// Description file
|
|
360
|
-
const descPath = await askPath(rl, chalk.white(' Description file path (txt/md)'), true, false);
|
|
361
|
-
let description = null;
|
|
362
|
-
if (descPath) {
|
|
363
|
-
try {
|
|
364
|
-
description = await fs.readFile(descPath, 'utf-8');
|
|
365
|
-
console.log(chalk.green(` ✓ Description loaded: ${path.basename(descPath)} (${description.length} chars)`));
|
|
366
|
-
} catch (err) {
|
|
367
|
-
console.log(chalk.yellow(` ⚠️ Could not read description: ${err.message}`));
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
// System instructions (text or file)
|
|
372
|
-
console.log(chalk.gray(' System instructions: Enter text directly or provide file path'));
|
|
373
|
-
const sysInstrInput = await askInput(rl, chalk.white(' System instructions (text or file path)'), true);
|
|
374
|
-
let systemInstructions = null;
|
|
375
|
-
let systemInstructionsFile = null;
|
|
376
|
-
if (sysInstrInput) {
|
|
377
|
-
// Check if it's a file path (contains path separators or ends with common extensions)
|
|
378
|
-
if (sysInstrInput.includes('/') || sysInstrInput.includes('\\') ||
|
|
379
|
-
sysInstrInput.endsWith('.txt') || sysInstrInput.endsWith('.md')) {
|
|
380
|
-
try {
|
|
381
|
-
systemInstructionsFile = await fs.readFile(sysInstrInput, 'utf-8');
|
|
382
|
-
systemInstructions = systemInstructionsFile;
|
|
383
|
-
console.log(chalk.green(` ✓ System instructions loaded from file: ${path.basename(sysInstrInput)} (${systemInstructions.length} chars)`));
|
|
384
|
-
} catch (err) {
|
|
385
|
-
console.log(chalk.yellow(` ⚠️ Could not read system instructions file: ${err.message}`));
|
|
386
|
-
// Treat as text if file read fails
|
|
387
|
-
systemInstructions = sysInstrInput;
|
|
388
|
-
console.log(chalk.gray(` Using as text input (${systemInstructions.length} chars)`));
|
|
389
|
-
}
|
|
390
|
-
} else {
|
|
391
|
-
// Treat as direct text input
|
|
392
|
-
systemInstructions = sysInstrInput;
|
|
393
|
-
console.log(chalk.green(` ✓ System instructions entered as text (${systemInstructions.length} chars)`));
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
// Knowledge base folder
|
|
398
|
-
const kbPath = await askPath(rl, chalk.white(' Knowledge base folder path'), true, true);
|
|
399
|
-
let kbFiles = null;
|
|
400
|
-
if (kbPath) {
|
|
401
|
-
try {
|
|
402
|
-
kbFiles = await readDirectoryFiles(kbPath);
|
|
403
|
-
console.log(chalk.green(` ✓ KB folder loaded: ${kbFiles.length} files`));
|
|
404
|
-
} catch (err) {
|
|
405
|
-
console.log(chalk.yellow(` ⚠️ Could not read KB folder: ${err.message}`));
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
// Step 6: Confirm and create
|
|
410
|
-
console.log(chalk.cyan('\n─── Summary ───\n'));
|
|
411
|
-
console.log(chalk.white(` Community: ${communityId}`));
|
|
412
|
-
console.log(chalk.white(` App Name: ${appName}`));
|
|
413
|
-
console.log(chalk.white(` App ID: ${appId}`));
|
|
414
|
-
console.log(chalk.white(` Slot: ${selectedSlot.name} (${selectedSlot.type})`));
|
|
415
|
-
console.log(chalk.white(` Icon: ${iconPath ? path.basename(iconPath) : 'Default'}`));
|
|
416
|
-
console.log(chalk.white(` Description: ${description ? `${description.length} chars` : 'Default'}`));
|
|
417
|
-
console.log(chalk.white(` Sys Instr: ${systemInstructions ? `${systemInstructions.length} chars` : 'Default'}`));
|
|
418
|
-
console.log(chalk.white(` KB Files: ${kbFiles ? `${kbFiles.length} files` : 'None'}`));
|
|
419
|
-
console.log(chalk.white(` Overwrite: ${overwrite ? 'Yes' : 'No'}`));
|
|
420
|
-
|
|
421
|
-
const proceed = await askYesNo(rl, chalk.white('\n Proceed with app creation?'), true);
|
|
422
|
-
if (!proceed) {
|
|
423
|
-
console.log(chalk.gray('\n Operation cancelled.\n'));
|
|
424
|
-
rl.close();
|
|
425
|
-
return;
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
// Step 7: Create skeleton app
|
|
429
|
-
console.log(chalk.gray('\n Creating app structure...'));
|
|
430
|
-
const createResult = await apiClient.invoke('create_skeleton_app', {
|
|
431
|
-
community_id: communityId,
|
|
432
|
-
app_name: appName,
|
|
433
|
-
overwrite: overwrite,
|
|
434
|
-
app_description: description,
|
|
435
|
-
// icon_url will be set after upload
|
|
436
|
-
});
|
|
437
|
-
const createData = createResult.message || createResult;
|
|
438
|
-
console.log(chalk.green(` ✓ App structure created`));
|
|
439
|
-
console.log(chalk.gray(` Drive folder: ${createData.folder_id}`));
|
|
440
|
-
|
|
441
|
-
// New Step: Generate and Register Service Key
|
|
442
|
-
console.log(chalk.gray(' Generating Service Delegate Key...'));
|
|
443
|
-
const { publicKey, privateKey } = generateServiceKey();
|
|
444
|
-
|
|
445
|
-
// Register delegate with Core (Virtual Registry)
|
|
446
|
-
try {
|
|
447
|
-
await apiClient.invoke('register_delegate', {
|
|
448
|
-
slot_id: selectedSlot.nft_id || selectedSlot.id,
|
|
449
|
-
slot_type: selectedSlot.type,
|
|
450
|
-
public_key: publicKey,
|
|
451
|
-
app_id: appId,
|
|
452
|
-
community_id: communityId
|
|
453
|
-
});
|
|
454
|
-
console.log(chalk.green(' ✓ Service Delegate Key registered'));
|
|
455
|
-
|
|
456
|
-
// Save key to dev-overrides.json (Standard Config Pattern)
|
|
457
|
-
const overridesPath = path.resolve(process.cwd(), 'dev-overrides.json');
|
|
458
|
-
let overrides = {};
|
|
459
|
-
|
|
460
|
-
try {
|
|
461
|
-
const content = await fs.readFile(overridesPath, 'utf-8');
|
|
462
|
-
overrides = JSON.parse(content);
|
|
463
|
-
} catch (e) {
|
|
464
|
-
// File doesn't exist or invalid, start fresh
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
// Update SERVICE_KEY in overrides
|
|
468
|
-
overrides.SERVICE_KEY = {
|
|
469
|
-
privateKey,
|
|
470
|
-
publicKey,
|
|
471
|
-
slotId: selectedSlot.nft_id || selectedSlot.id,
|
|
472
|
-
slotType: selectedSlot.type,
|
|
473
|
-
appId,
|
|
474
|
-
communityId,
|
|
475
|
-
createdAt: new Date().toISOString()
|
|
476
|
-
};
|
|
477
|
-
|
|
478
|
-
await fs.writeFile(overridesPath, JSON.stringify(overrides, null, 2));
|
|
479
|
-
console.log(chalk.green(` ✓ Key saved to dev-overrides.json`));
|
|
480
|
-
|
|
481
|
-
// Add to .gitignore if it exists
|
|
482
|
-
try {
|
|
483
|
-
const gitignorePath = path.resolve(process.cwd(), '.gitignore');
|
|
484
|
-
let gitignore = await fs.readFile(gitignorePath, 'utf-8').catch(() => '');
|
|
485
|
-
if (!gitignore.includes('dev-overrides.json')) {
|
|
486
|
-
await fs.appendFile(gitignorePath, '\ndev-overrides.json\n');
|
|
487
|
-
console.log(chalk.gray(' Added dev-overrides.json to .gitignore'));
|
|
488
|
-
}
|
|
489
|
-
} catch (err) {
|
|
490
|
-
// Ignore gitignore errors
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
} catch (err) {
|
|
494
|
-
console.log(chalk.yellow(` ⚠️ Failed to register delegate key: ${err.message}`));
|
|
495
|
-
console.log(chalk.gray(' You can register it later manually.'));
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
// Step 8: Upload assets (icon, description, system_instructions)
|
|
499
|
-
if (iconBase64) {
|
|
500
|
-
console.log(chalk.gray(' Uploading icon...'));
|
|
501
|
-
try {
|
|
502
|
-
await apiClient.invoke('upload_app_asset', {
|
|
503
|
-
community_id: communityId,
|
|
504
|
-
app_id: appId,
|
|
505
|
-
asset_type: 'icon',
|
|
506
|
-
file_name: `icon${path.extname(iconPath)}`,
|
|
507
|
-
file_content: iconBase64
|
|
508
|
-
});
|
|
509
|
-
console.log(chalk.green(` ✓ Icon uploaded`));
|
|
510
|
-
} catch (err) {
|
|
511
|
-
console.log(chalk.yellow(` ⚠️ Icon upload failed: ${err.message}`));
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
if (description) {
|
|
516
|
-
console.log(chalk.gray(' Uploading description...'));
|
|
517
|
-
try {
|
|
518
|
-
const descBase64 = Buffer.from(description, 'utf-8').toString('base64');
|
|
519
|
-
await apiClient.invoke('upload_app_asset', {
|
|
520
|
-
community_id: communityId,
|
|
521
|
-
app_id: appId,
|
|
522
|
-
asset_type: 'description',
|
|
523
|
-
file_name: 'app_description.md',
|
|
524
|
-
file_content: descBase64
|
|
525
|
-
});
|
|
526
|
-
console.log(chalk.green(` ✓ Description uploaded`));
|
|
527
|
-
} catch (err) {
|
|
528
|
-
console.log(chalk.yellow(` ⚠️ Description upload failed: ${err.message}`));
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
if (systemInstructions) {
|
|
533
|
-
console.log(chalk.gray(' Uploading system instructions...'));
|
|
534
|
-
try {
|
|
535
|
-
const sysInstrBase64 = Buffer.from(systemInstructions, 'utf-8').toString('base64');
|
|
536
|
-
await apiClient.invoke('upload_app_asset', {
|
|
537
|
-
community_id: communityId,
|
|
538
|
-
app_id: appId,
|
|
539
|
-
asset_type: 'system_instructions',
|
|
540
|
-
file_name: 'system_instructions.md',
|
|
541
|
-
file_content: sysInstrBase64
|
|
542
|
-
});
|
|
543
|
-
console.log(chalk.green(` ✓ System instructions uploaded`));
|
|
544
|
-
} catch (err) {
|
|
545
|
-
console.log(chalk.yellow(` ⚠️ System instructions upload failed: ${err.message}`));
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
// Step 9: Upload KB files if provided (in batches to avoid payload limits)
|
|
550
|
-
if (kbFiles && kbFiles.length > 0) {
|
|
551
|
-
console.log(chalk.gray(` Uploading ${kbFiles.length} KB files...`));
|
|
552
|
-
|
|
553
|
-
const BATCH_SIZE = 5; // Upload 5 files at a time (reduced for vectorization time)
|
|
554
|
-
let totalUploaded = 0;
|
|
555
|
-
let totalErrors = [];
|
|
556
|
-
|
|
557
|
-
try {
|
|
558
|
-
for (let i = 0; i < kbFiles.length; i += BATCH_SIZE) {
|
|
559
|
-
const batch = kbFiles.slice(i, i + BATCH_SIZE);
|
|
560
|
-
const batchNum = Math.floor(i / BATCH_SIZE) + 1;
|
|
561
|
-
const totalBatches = Math.ceil(kbFiles.length / BATCH_SIZE);
|
|
562
|
-
|
|
563
|
-
if (totalBatches > 1) {
|
|
564
|
-
process.stdout.write(chalk.gray(` Batch ${batchNum}/${totalBatches}...`));
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
try {
|
|
568
|
-
const uploadResult = await apiClient.invoke('upload_files_to_kb', {
|
|
569
|
-
community_id: communityId,
|
|
570
|
-
app_id: appId,
|
|
571
|
-
kb_id: 'General',
|
|
572
|
-
files: batch
|
|
573
|
-
});
|
|
574
|
-
const uploadData = uploadResult.message || uploadResult;
|
|
575
|
-
totalUploaded += uploadData.uploaded || batch.length;
|
|
576
|
-
if (uploadData.errors?.length) {
|
|
577
|
-
totalErrors.push(...uploadData.errors);
|
|
578
|
-
}
|
|
579
|
-
if (totalBatches > 1) {
|
|
580
|
-
console.log(chalk.green(` ✓`));
|
|
581
|
-
}
|
|
582
|
-
} catch (batchErr) {
|
|
583
|
-
if (totalBatches > 1) {
|
|
584
|
-
console.log(chalk.yellow(` failed: ${batchErr.message}`));
|
|
585
|
-
}
|
|
586
|
-
console.log(chalk.gray(` Files in batch: ${batch.map(f => f.name).join(', ')}`));
|
|
587
|
-
totalErrors.push({ batch: batchNum, error: batchErr.message });
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
console.log(chalk.green(` ✓ Uploaded ${totalUploaded} files`));
|
|
592
|
-
if (totalErrors.length > 0) {
|
|
593
|
-
console.log(chalk.yellow(` ⚠️ ${totalErrors.length} errors during upload`));
|
|
594
|
-
}
|
|
595
|
-
} catch (err) {
|
|
596
|
-
console.log(chalk.yellow(` ⚠️ KB upload failed: ${err.message}`));
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
// Done!
|
|
601
|
-
console.log(chalk.green('\n╔════════════════════════════════════════════╗'));
|
|
602
|
-
console.log(chalk.green('║ App Created Successfully! ║'));
|
|
603
|
-
console.log(chalk.green('╚════════════════════════════════════════════╝\n'));
|
|
604
|
-
console.log(chalk.white(` App ID: ${appId}`));
|
|
605
|
-
console.log(chalk.white(` Community: ${communityId}`));
|
|
606
|
-
console.log(chalk.white(` Folder: ${createData.folder_id}`));
|
|
607
|
-
|
|
608
|
-
console.log(chalk.gray(`\n To process your Knowledge Base:`));
|
|
609
|
-
console.log(chalk.cyan(` 1. descix kb pull -c ${communityId} -a ${appId}`));
|
|
610
|
-
console.log(chalk.cyan(` 2. descix kb build -c ${communityId} -a ${appId}`));
|
|
611
|
-
|
|
612
|
-
console.log(chalk.gray(`\n Query your app with:`));
|
|
613
|
-
console.log(chalk.cyan(` npx descix chat -c ${communityId} -a ${appId} "Your question here"`));
|
|
614
|
-
|
|
615
|
-
console.log(chalk.gray(`\n Add a CodeSite (static HTML/JS/CSS app):`));
|
|
616
|
-
console.log(chalk.cyan(` 1. Build your static site in a local directory`));
|
|
617
|
-
console.log(chalk.cyan(` 2. Deploy it: descix site deploy -c ${communityId} -a ${appId} -p <localPath>`));
|
|
618
|
-
console.log(chalk.cyan(` 3. The CodeSite will be available in the PWA's "My Apps" view\n`));
|
|
619
|
-
|
|
620
|
-
rl.close();
|
|
621
|
-
|
|
622
|
-
} catch (error) {
|
|
623
|
-
rl.close();
|
|
624
|
-
throw error;
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
|