@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,382 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* DeSciX MCP Server
|
|
5
|
+
*
|
|
6
|
+
* Thin HTTP relay: curated app-dev tools inline, all calls dispatch
|
|
7
|
+
* to backend via DeSciXApiClient (POST /apifront/).
|
|
8
|
+
*
|
|
9
|
+
* descix_doctor is handled locally (no backend call needed).
|
|
10
|
+
*
|
|
11
|
+
* Uses official @modelcontextprotocol/sdk with stdio transport.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
15
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
16
|
+
import {
|
|
17
|
+
ListToolsRequestSchema,
|
|
18
|
+
CallToolRequestSchema,
|
|
19
|
+
} from '@modelcontextprotocol/sdk/types.js';
|
|
20
|
+
import { WorkspaceConfig } from '../lib/workspace-config.js';
|
|
21
|
+
import { DeSciXApiClient } from '../lib/api-client.js';
|
|
22
|
+
import { WalletFileManager } from '../lib/wallet-file.js';
|
|
23
|
+
import * as fs from 'fs/promises';
|
|
24
|
+
import * as path from 'path';
|
|
25
|
+
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
// Curated app-dev tool definitions (inline — no external tools.js dependency)
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
|
|
30
|
+
const TOOLS = [
|
|
31
|
+
{
|
|
32
|
+
name: 'descix_doctor',
|
|
33
|
+
description:
|
|
34
|
+
'Startup diagnostic: returns auth state, workspace context, active app/community, ' +
|
|
35
|
+
'available MCP tools by category, and any mismatches between local config and platform. ' +
|
|
36
|
+
'Call this FIRST when starting a new session to understand the environment.',
|
|
37
|
+
inputSchema: {
|
|
38
|
+
type: 'object',
|
|
39
|
+
properties: {
|
|
40
|
+
verify_remote: {
|
|
41
|
+
type: 'boolean',
|
|
42
|
+
description: 'Also verify app exists on the platform (requires network). Default: true',
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: 'ask_question_to_app',
|
|
49
|
+
description: 'Ask an AI-powered question to an app\'s knowledge base using RAG',
|
|
50
|
+
inputSchema: {
|
|
51
|
+
type: 'object',
|
|
52
|
+
properties: {
|
|
53
|
+
app_id: { type: 'string', description: 'App ID to query' },
|
|
54
|
+
knowledgebase_name: { type: 'string', description: 'KB name (default: General)' },
|
|
55
|
+
user_input: { type: 'string', description: 'The question to ask' },
|
|
56
|
+
},
|
|
57
|
+
required: ['app_id', 'user_input'],
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: 'query_knowledge_base',
|
|
62
|
+
description: 'Search a knowledge base using vector similarity (returns raw chunks)',
|
|
63
|
+
inputSchema: {
|
|
64
|
+
type: 'object',
|
|
65
|
+
properties: {
|
|
66
|
+
app_id: { type: 'string', description: 'App ID' },
|
|
67
|
+
kb_id: { type: 'string', description: 'KB name (default: General)' },
|
|
68
|
+
query: { type: 'string', description: 'Search query' },
|
|
69
|
+
limit: { type: 'number', description: 'Max results (default: 5)' },
|
|
70
|
+
},
|
|
71
|
+
required: ['app_id', 'query'],
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
name: 'find_communities',
|
|
76
|
+
description: 'List available communities on the platform',
|
|
77
|
+
inputSchema: {
|
|
78
|
+
type: 'object',
|
|
79
|
+
properties: {
|
|
80
|
+
filter: { type: 'string', description: 'Optional filter string' },
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
name: 'list_apps_for_community',
|
|
86
|
+
description: 'List apps in a community',
|
|
87
|
+
inputSchema: {
|
|
88
|
+
type: 'object',
|
|
89
|
+
properties: {
|
|
90
|
+
community_id: { type: 'string', description: 'Community ID' },
|
|
91
|
+
},
|
|
92
|
+
required: ['community_id'],
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: 'tell_me_how',
|
|
97
|
+
description:
|
|
98
|
+
'Discover platform tools and services by asking a natural language question. ' +
|
|
99
|
+
'Searches the entire service mesh for relevant capabilities. ' +
|
|
100
|
+
'If results are empty, try scope "discovery" or rephrase your question.',
|
|
101
|
+
inputSchema: {
|
|
102
|
+
type: 'object',
|
|
103
|
+
properties: {
|
|
104
|
+
question: { type: 'string', description: 'What do you want to do?' },
|
|
105
|
+
scope: {
|
|
106
|
+
type: 'string',
|
|
107
|
+
enum: ['project', 'entitlements', 'discovery'],
|
|
108
|
+
description: 'Search scope. "entitlements" = your purchased tools, "discovery" = all platform capabilities. Default: entitlements.',
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
required: ['question'],
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
name: 'resolve_invite',
|
|
116
|
+
description:
|
|
117
|
+
'Resolve a DeSciX invite token into app configuration. Call this when .descix/app.json ' +
|
|
118
|
+
'contains an invite_token field. Returns app context, community info, and the agent_hint ' +
|
|
119
|
+
'authored by the app creator. The agent_hint tells you about the user\'s skill level and ' +
|
|
120
|
+
'goals — use it to adapt your approach.',
|
|
121
|
+
inputSchema: {
|
|
122
|
+
type: 'object',
|
|
123
|
+
properties: {
|
|
124
|
+
invite_token: {
|
|
125
|
+
type: 'string',
|
|
126
|
+
description: 'The invite token from .descix/app.json',
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
required: ['invite_token'],
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
];
|
|
133
|
+
|
|
134
|
+
// ---------------------------------------------------------------------------
|
|
135
|
+
// Server bootstrap
|
|
136
|
+
// ---------------------------------------------------------------------------
|
|
137
|
+
|
|
138
|
+
console.error('[MCP] Starting DeSciX MCP server...');
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
const workspaceRoot = process.cwd();
|
|
142
|
+
console.error(`[MCP] Workspace root: ${workspaceRoot}`);
|
|
143
|
+
|
|
144
|
+
// Load workspace config (optional — server works without it)
|
|
145
|
+
let workspaceConfig = null;
|
|
146
|
+
try {
|
|
147
|
+
workspaceConfig = await WorkspaceConfig.load(workspaceRoot);
|
|
148
|
+
console.error(`[MCP] Loaded config for community: ${workspaceConfig.primaryCommunity}`);
|
|
149
|
+
} catch {
|
|
150
|
+
console.error('[MCP] No workspace config found — using defaults');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Create HTTP-only API client
|
|
154
|
+
const apiClient = new DeSciXApiClient({
|
|
155
|
+
projectRoot: workspaceRoot,
|
|
156
|
+
baseUrl: workspaceConfig?.apiUrl || null,
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// Load wallet credentials
|
|
160
|
+
let walletInfo = null;
|
|
161
|
+
const walletPath = await WalletFileManager.findWalletFile(workspaceRoot);
|
|
162
|
+
if (walletPath) {
|
|
163
|
+
walletInfo = await WalletFileManager.loadWalletFile(walletPath);
|
|
164
|
+
if (walletInfo) {
|
|
165
|
+
console.error(`[MCP] Authenticated as: ${walletInfo.userId || 'unknown'}`);
|
|
166
|
+
apiClient.setCredentials({
|
|
167
|
+
userId: walletInfo.userId || null,
|
|
168
|
+
accessToken: walletInfo.sessionToken || null,
|
|
169
|
+
walletAddress: walletInfo.walletAddress || null,
|
|
170
|
+
signature: walletInfo.signature || null,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
} else {
|
|
174
|
+
console.error('[MCP] No wallet file — some tools may require authentication');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Default context from workspace config — NO hardcoded fallbacks.
|
|
178
|
+
// When workspace isn't configured, defaultContext stays null so tools
|
|
179
|
+
// don't silently target the wrong app.
|
|
180
|
+
const defaultContext = workspaceConfig?.defaultContext || null;
|
|
181
|
+
|
|
182
|
+
// -------------------------------------------------------------------------
|
|
183
|
+
// descix_doctor — local diagnostic (no backend call)
|
|
184
|
+
// -------------------------------------------------------------------------
|
|
185
|
+
|
|
186
|
+
async function runDoctor(args) {
|
|
187
|
+
const verifyRemote = args?.verify_remote !== false;
|
|
188
|
+
const report = {
|
|
189
|
+
auth: { connected: false },
|
|
190
|
+
workspace: { configured: false },
|
|
191
|
+
app_context: null,
|
|
192
|
+
tools: {
|
|
193
|
+
diagnostic: ['descix_doctor'],
|
|
194
|
+
knowledge: ['ask_question_to_app', 'query_knowledge_base'],
|
|
195
|
+
discovery: ['find_communities', 'list_apps_for_community', 'tell_me_how'],
|
|
196
|
+
},
|
|
197
|
+
warnings: [],
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
// Auth state
|
|
201
|
+
if (walletInfo) {
|
|
202
|
+
report.auth = {
|
|
203
|
+
connected: true,
|
|
204
|
+
user_id: walletInfo.userId || null,
|
|
205
|
+
session_valid: WalletFileManager.hasValidSession?.(walletInfo) ?? true,
|
|
206
|
+
};
|
|
207
|
+
} else {
|
|
208
|
+
report.warnings.push('Not authenticated. Run "DeSciX: Connect" or "descix login" to authenticate.');
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Workspace config — reload fresh so we pick up workspace.json created mid-session
|
|
212
|
+
let freshConfig = null;
|
|
213
|
+
let freshContext = null;
|
|
214
|
+
try {
|
|
215
|
+
freshConfig = await WorkspaceConfig.load(workspaceRoot || process.cwd());
|
|
216
|
+
freshContext = freshConfig.defaultContext || null;
|
|
217
|
+
} catch { /* no workspace.json present */ }
|
|
218
|
+
|
|
219
|
+
if (freshConfig) {
|
|
220
|
+
const platform = freshConfig.env?.platform || {};
|
|
221
|
+
const products = freshConfig.env?.products || [];
|
|
222
|
+
report.workspace = {
|
|
223
|
+
configured: true,
|
|
224
|
+
version: freshConfig.version || 'unknown',
|
|
225
|
+
environment: freshConfig.env?.environment || 'unknown',
|
|
226
|
+
};
|
|
227
|
+
if (freshContext) {
|
|
228
|
+
report.app_context = {
|
|
229
|
+
app_id: freshContext.appId,
|
|
230
|
+
community_id: freshContext.communityId,
|
|
231
|
+
kb_id: freshContext.kbId || 'General',
|
|
232
|
+
api_url: freshConfig.apiUrl || apiClient.baseUrl,
|
|
233
|
+
local_apps: [
|
|
234
|
+
platform.appId,
|
|
235
|
+
...products.map(p => p.appId),
|
|
236
|
+
].filter(Boolean),
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
} else {
|
|
240
|
+
report.workspace = { configured: false };
|
|
241
|
+
report.setup_needed = true;
|
|
242
|
+
report.next_steps = [
|
|
243
|
+
'This project is not configured yet. Guide the user through setup:',
|
|
244
|
+
'',
|
|
245
|
+
'## Before asking questions, scan the repo',
|
|
246
|
+
'List the working directory to detect existing content (docs/, src/, package.json, kb/, site/).',
|
|
247
|
+
'Use findings to tailor your checkpoint questions.',
|
|
248
|
+
'',
|
|
249
|
+
'## Checkpoints (ask the user at each step)',
|
|
250
|
+
'1. **Objective:** "Do you want to explore existing apps, or build something new?"',
|
|
251
|
+
'2. **Community/App:** Use find_communities + list_apps_for_community, then ask: "Which community and app should this project target?"',
|
|
252
|
+
'3. **Environment:** "Should this run against local dev backend (localhost:4000) or hosted API (descix.net)?"',
|
|
253
|
+
'',
|
|
254
|
+
'## Canonical setup commands (run in terminal)',
|
|
255
|
+
'```',
|
|
256
|
+
'descix whoami # confirm identity',
|
|
257
|
+
'descix app list # see available apps',
|
|
258
|
+
'descix init -c <community> -a <app> -p . # create workspace.json',
|
|
259
|
+
'descix app init -a <app> -c <community> # register app on platform',
|
|
260
|
+
'```',
|
|
261
|
+
'',
|
|
262
|
+
'## Important: use explicit flags',
|
|
263
|
+
'Always use -c <community> -a <app> flags for all commands until workspace context is verified working.',
|
|
264
|
+
'',
|
|
265
|
+
'## Integrating existing content',
|
|
266
|
+
'If the repo has existing content (HTML app, React app, SDK with docs):',
|
|
267
|
+
'- Do NOT copy files into site/ or kb/. Integrate in place.',
|
|
268
|
+
'- Existing HTML/JS: serve as-is, add DeSciXAppSDK.js for DeSciX integration.',
|
|
269
|
+
'- React/Vite: wrap root in <AppShell appId="app_id"> from @descix/app-sdk/AppShell.',
|
|
270
|
+
'- Existing docs: copy *.md to kb/General/ for sync pipeline. Keep originals as source of truth.',
|
|
271
|
+
'',
|
|
272
|
+
'DeSciX is like a virtual university: communities are departments, apps are courses/textbooks/services.',
|
|
273
|
+
];
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Check agent instruction files
|
|
277
|
+
const checkRoot = freshConfig?.getWorkspaceRoot?.() || workspaceRoot || process.cwd();
|
|
278
|
+
const agentFiles = ['CLAUDE.md', '.github/copilot-instructions.md', '.cursorrules', '.clinerules'];
|
|
279
|
+
const foundFiles = [];
|
|
280
|
+
for (const f of agentFiles) {
|
|
281
|
+
try {
|
|
282
|
+
await fs.access(path.join(checkRoot, f));
|
|
283
|
+
foundFiles.push(f);
|
|
284
|
+
} catch { /* not present */ }
|
|
285
|
+
}
|
|
286
|
+
report.agent_files = foundFiles.length > 0 ? foundFiles : null;
|
|
287
|
+
if (!report.agent_files) {
|
|
288
|
+
report.warnings.push('No agent instruction files found. Run "descix quickstart" to generate them.');
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Remote verification (if requested, authenticated, and workspace configured)
|
|
292
|
+
if (verifyRemote && walletInfo && freshContext?.appId) {
|
|
293
|
+
try {
|
|
294
|
+
const apps = await apiClient.invoke('list_apps_for_community', {
|
|
295
|
+
community_id: freshContext.communityId,
|
|
296
|
+
});
|
|
297
|
+
const appList = Array.isArray(apps) ? apps : (apps?.apps || apps?.products || []);
|
|
298
|
+
const appIds = appList.map(a => a.app_id || a.appId || a.id).filter(Boolean);
|
|
299
|
+
report.remote_apps = appIds;
|
|
300
|
+
|
|
301
|
+
if (!appIds.includes(freshContext.appId)) {
|
|
302
|
+
report.warnings.push(
|
|
303
|
+
`Local app_id "${freshContext.appId}" not found on platform for community "${freshContext.communityId}". ` +
|
|
304
|
+
`Available: ${appIds.join(', ') || 'none'}. Run "descix app init -a ${freshContext.appId}" to register it.`
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
} catch (err) {
|
|
308
|
+
report.warnings.push(`Could not verify remote state: ${err.message}`);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (report.warnings.length === 0) {
|
|
313
|
+
report.status = 'healthy';
|
|
314
|
+
} else {
|
|
315
|
+
report.status = 'warnings';
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return report;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Create MCP server
|
|
322
|
+
const server = new Server(
|
|
323
|
+
{ name: 'DeSciX MCP Server', version: '1.0.0' },
|
|
324
|
+
{ capabilities: { tools: {} } },
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
// tools/list — return curated tool definitions
|
|
328
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
329
|
+
console.error(`[MCP] tools/list — returning ${TOOLS.length} tools`);
|
|
330
|
+
return { tools: TOOLS };
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
// tools/call — dispatch to backend via HTTP (except doctor which is local)
|
|
334
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
335
|
+
const { name, arguments: args } = request.params;
|
|
336
|
+
console.error(`[MCP] tools/call — ${name}`);
|
|
337
|
+
|
|
338
|
+
try {
|
|
339
|
+
// descix_doctor is handled locally
|
|
340
|
+
if (name === 'descix_doctor') {
|
|
341
|
+
const report = await runDoctor(args);
|
|
342
|
+
return {
|
|
343
|
+
content: [{ type: 'text', text: JSON.stringify(report, null, 2) }],
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Merge default context for convenience (caller can override).
|
|
348
|
+
// Only auto-fill when workspace is configured — prevents silently
|
|
349
|
+
// targeting wrong app/community.
|
|
350
|
+
const params = { ...args };
|
|
351
|
+
if (defaultContext) {
|
|
352
|
+
if (!params.app_id && defaultContext.appId) params.app_id = defaultContext.appId;
|
|
353
|
+
if (!params.kb_id && (defaultContext.kbId || 'General')) params.kb_id = params.kb_id || defaultContext.kbId || 'General';
|
|
354
|
+
if (!params.community_id && defaultContext.communityId) params.community_id = defaultContext.communityId;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const result = await apiClient.invoke(name, params);
|
|
358
|
+
return {
|
|
359
|
+
content: [{
|
|
360
|
+
type: 'text',
|
|
361
|
+
text: typeof result === 'string' ? result : JSON.stringify(result, null, 2),
|
|
362
|
+
}],
|
|
363
|
+
};
|
|
364
|
+
} catch (error) {
|
|
365
|
+
console.error(`[MCP] Tool error: ${error.message}`);
|
|
366
|
+
return {
|
|
367
|
+
content: [{ type: 'text', text: `Error: ${error.message}` }],
|
|
368
|
+
isError: true,
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
// Connect stdio transport
|
|
374
|
+
const transport = new StdioServerTransport();
|
|
375
|
+
console.error('[MCP] Connecting stdio transport...');
|
|
376
|
+
await server.connect(transport);
|
|
377
|
+
console.error('[MCP] Server connected and running');
|
|
378
|
+
} catch (error) {
|
|
379
|
+
console.error(`[MCP] Fatal error: ${error.message}`);
|
|
380
|
+
console.error(error.stack);
|
|
381
|
+
process.exit(1);
|
|
382
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Files — Multi-format instruction file generator
|
|
3
|
+
*
|
|
4
|
+
* Reads workspace.json and generates agent instruction files for
|
|
5
|
+
* Claude Code, GitHub Copilot, Cursor, and Cline from shared templates.
|
|
6
|
+
*
|
|
7
|
+
* Shared between CLI (descix quickstart) and VS Code extension.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import * as fs from 'fs/promises';
|
|
11
|
+
import * as path from 'path';
|
|
12
|
+
import { fileURLToPath } from 'url';
|
|
13
|
+
|
|
14
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
15
|
+
const __dirname = path.dirname(__filename);
|
|
16
|
+
const TEMPLATES_DIR = path.join(__dirname, '../templates');
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Template → output file mapping
|
|
20
|
+
*/
|
|
21
|
+
const AGENT_FILES = [
|
|
22
|
+
{ template: 'agent-claude.md', output: 'CLAUDE.md' },
|
|
23
|
+
{ template: 'agent-copilot.md', output: '.github/copilot-instructions.md' },
|
|
24
|
+
{ template: 'agent-cursor.md', output: '.cursorrules' },
|
|
25
|
+
{ template: 'agent-cline.md', output: '.clinerules' },
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Read workspace.json and extract template variables
|
|
30
|
+
*/
|
|
31
|
+
async function loadContext(workspaceRoot) {
|
|
32
|
+
const wsPath = path.join(workspaceRoot, '.descix', 'workspace.json');
|
|
33
|
+
try {
|
|
34
|
+
const raw = await fs.readFile(wsPath, 'utf-8');
|
|
35
|
+
const config = JSON.parse(raw);
|
|
36
|
+
|
|
37
|
+
// Derive from v2.1 format or legacy
|
|
38
|
+
const platform = config.env?.platform || {};
|
|
39
|
+
const appId = platform.appId || config.defaultContext?.appId || 'my-app';
|
|
40
|
+
const communityId = platform.communityId || config.defaultContext?.communityId || config.primaryCommunity || 'descix';
|
|
41
|
+
|
|
42
|
+
// Derive API URL
|
|
43
|
+
let apiUrl = config.apiUrl || null;
|
|
44
|
+
if (!apiUrl && platform.microservice?.port) {
|
|
45
|
+
apiUrl = `https://localhost:${platform.microservice.port}`;
|
|
46
|
+
}
|
|
47
|
+
apiUrl = apiUrl || 'https://descix.net';
|
|
48
|
+
|
|
49
|
+
// Friendly name: capitalize app_id or use as-is
|
|
50
|
+
const appName = appId.charAt(0).toUpperCase() + appId.slice(1);
|
|
51
|
+
|
|
52
|
+
return { appId, communityId, apiUrl, appName };
|
|
53
|
+
} catch {
|
|
54
|
+
// No workspace.json — use defaults
|
|
55
|
+
return {
|
|
56
|
+
appId: path.basename(workspaceRoot),
|
|
57
|
+
communityId: 'descix',
|
|
58
|
+
apiUrl: 'https://descix.net',
|
|
59
|
+
appName: path.basename(workspaceRoot),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Substitute {{placeholder}} tokens in a template string
|
|
66
|
+
*/
|
|
67
|
+
function render(template, vars) {
|
|
68
|
+
return template
|
|
69
|
+
.replace(/\{\{appId\}\}/g, vars.appId)
|
|
70
|
+
.replace(/\{\{communityId\}\}/g, vars.communityId)
|
|
71
|
+
.replace(/\{\{apiUrl\}\}/g, vars.apiUrl)
|
|
72
|
+
.replace(/\{\{appName\}\}/g, vars.appName);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Generate all agent instruction files in the workspace root.
|
|
77
|
+
*
|
|
78
|
+
* @param {string} workspaceRoot - Project root directory
|
|
79
|
+
* @returns {Promise<string[]>} List of files written
|
|
80
|
+
*/
|
|
81
|
+
export async function generateAgentFiles(workspaceRoot) {
|
|
82
|
+
const vars = await loadContext(workspaceRoot);
|
|
83
|
+
const written = [];
|
|
84
|
+
|
|
85
|
+
for (const { template, output } of AGENT_FILES) {
|
|
86
|
+
const templatePath = path.join(TEMPLATES_DIR, template);
|
|
87
|
+
const outputPath = path.join(workspaceRoot, output);
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
// Check if file exists and has been customized
|
|
91
|
+
try {
|
|
92
|
+
const existing = await fs.readFile(outputPath, 'utf-8');
|
|
93
|
+
if (!existing.includes('<!-- BOOTSTRAP')) {
|
|
94
|
+
// File has been customized — don't overwrite
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
} catch {
|
|
98
|
+
// File doesn't exist — proceed with writing
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const raw = await fs.readFile(templatePath, 'utf-8');
|
|
102
|
+
const content = render(raw, vars);
|
|
103
|
+
|
|
104
|
+
// Ensure parent directory exists (e.g., .github/)
|
|
105
|
+
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
|
106
|
+
await fs.writeFile(outputPath, content, 'utf-8');
|
|
107
|
+
written.push(output);
|
|
108
|
+
} catch (err) {
|
|
109
|
+
// Template missing — skip silently
|
|
110
|
+
console.error(`[agent-files] Skipped ${template}: ${err.message}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return written;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Generate .vscode/mcp.json for non-extension users.
|
|
119
|
+
* Skips if the DeSciX VS Code extension is installed (it registers MCP natively).
|
|
120
|
+
*
|
|
121
|
+
* @param {string} workspaceRoot - Project root directory
|
|
122
|
+
* @returns {Promise<boolean>} True if file was written, false if skipped
|
|
123
|
+
*/
|
|
124
|
+
export async function generateMcpConfig(workspaceRoot) {
|
|
125
|
+
// Skip if the DeSciX extension is installed — it registers the MCP server
|
|
126
|
+
// natively via registerMcpServerDefinitionProvider, so mcp.json would create
|
|
127
|
+
// a duplicate namespace.
|
|
128
|
+
try {
|
|
129
|
+
const extensionsDir = path.join(
|
|
130
|
+
process.env.HOME || process.env.USERPROFILE || '',
|
|
131
|
+
'.vscode', 'extensions'
|
|
132
|
+
);
|
|
133
|
+
const entries = await fs.readdir(extensionsDir);
|
|
134
|
+
if (entries.some(e => e.startsWith('descix.'))) {
|
|
135
|
+
return false; // Extension installed — skip mcp.json
|
|
136
|
+
}
|
|
137
|
+
} catch {
|
|
138
|
+
// Can't read extensions dir — proceed with mcp.json
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const outputPath = path.join(workspaceRoot, '.vscode', 'mcp.json');
|
|
142
|
+
|
|
143
|
+
// Preserve existing servers if file already exists
|
|
144
|
+
let existing = { servers: {} };
|
|
145
|
+
try {
|
|
146
|
+
const raw = await fs.readFile(outputPath, 'utf-8');
|
|
147
|
+
existing = JSON.parse(raw);
|
|
148
|
+
if (!existing.servers) existing.servers = {};
|
|
149
|
+
} catch {
|
|
150
|
+
// No existing file — start fresh
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Add/update DeSciX MCP server
|
|
154
|
+
existing.servers['descix'] = {
|
|
155
|
+
command: 'descix',
|
|
156
|
+
args: ['mcp-serve'],
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
|
160
|
+
await fs.writeFile(outputPath, JSON.stringify(existing, null, 2), 'utf-8');
|
|
161
|
+
return true;
|
|
162
|
+
}
|