proteum 2.2.9 → 2.3.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/AGENTS.md +3 -2
- package/README.md +49 -11
- package/agents/project/AGENTS.md +43 -5
- package/agents/project/diagnostics.md +6 -2
- package/agents/project/optimizations.md +1 -0
- package/agents/project/root/AGENTS.md +14 -5
- package/agents/project/tests/AGENTS.md +6 -0
- package/agents/project/tests/e2e/AGENTS.md +13 -0
- package/agents/project/tests/e2e/REAL_WORLD_JOURNEY_TESTS.md +192 -0
- package/cli/commands/connect.ts +40 -4
- package/cli/commands/diagnose.ts +136 -5
- package/cli/commands/doctor.ts +24 -4
- package/cli/commands/explain.ts +105 -6
- package/cli/commands/mcp.ts +16 -0
- package/cli/commands/orient.ts +66 -3
- package/cli/commands/perf.ts +118 -13
- package/cli/commands/runtime.ts +151 -0
- package/cli/commands/trace.ts +116 -21
- package/cli/mcp/provider.ts +365 -0
- package/cli/mcp/stdio.ts +16 -0
- package/cli/presentation/commands.ts +77 -20
- package/cli/presentation/devSession.ts +2 -0
- package/cli/runtime/commands.ts +95 -12
- package/cli/utils/agentOutput.ts +46 -0
- package/cli/utils/agents.ts +116 -49
- package/common/dev/inspection.ts +14 -6
- package/common/dev/mcpPayloads.ts +736 -0
- package/common/dev/mcpServer.ts +254 -0
- package/docs/agent-routing.md +126 -0
- package/docs/dev-commands.md +2 -0
- package/docs/dev-sessions.md +2 -1
- package/docs/diagnostics.md +68 -23
- package/docs/mcp.md +149 -0
- package/docs/migrate-from-2.1.3.md +15 -5
- package/docs/request-tracing.md +12 -6
- package/package.json +2 -1
- package/server/app/devMcp.ts +159 -0
- package/server/services/router/http/cache.ts +116 -0
- package/server/services/router/http/index.ts +94 -35
- package/server/services/router/index.ts +8 -11
- package/tests/agents-utils.test.cjs +36 -13
- package/tests/dev-transpile-watch.test.cjs +117 -8
- package/tests/inspection.test.cjs +67 -0
- package/tests/mcp.test.cjs +127 -0
- package/tests/router-cache-config.test.cjs +74 -0
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import got from 'got';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
import { buildContractsDoctorResponse } from '../../common/dev/contractsDoctor';
|
|
6
|
+
import { buildDoctorResponse, type TDoctorResponse } from '../../common/dev/diagnostics';
|
|
7
|
+
import { buildOrientationResponse, explainOwner } from '../../common/dev/inspection';
|
|
8
|
+
import {
|
|
9
|
+
buildRuntimeStatusPayload,
|
|
10
|
+
compactDiagnoseResponse,
|
|
11
|
+
compactDoctorResponse,
|
|
12
|
+
compactExplainSummary,
|
|
13
|
+
compactLogsResponse,
|
|
14
|
+
compactOrientationResponse,
|
|
15
|
+
compactPerfRequestResponse,
|
|
16
|
+
compactPerfTopResponse,
|
|
17
|
+
compactTraceResponse,
|
|
18
|
+
resolveInstructionRouting,
|
|
19
|
+
} from '../../common/dev/mcpPayloads';
|
|
20
|
+
import type { TProteumMcpProvider } from '../../common/dev/mcpServer';
|
|
21
|
+
import type { TDevConsoleLogLevel, TDevConsoleLogsResponse } from '../../common/dev/console';
|
|
22
|
+
import type { TDiagnoseResponse } from '../../common/dev/inspection';
|
|
23
|
+
import type { TPerfRequestResponse, TPerfTopResponse } from '../../common/dev/performance';
|
|
24
|
+
import type { TProteumManifest } from '../../common/dev/proteumManifest';
|
|
25
|
+
import type { TRequestTraceResponse } from '../../common/dev/requestTrace';
|
|
26
|
+
import { readProteumManifest } from '../compiler/common/proteumManifest';
|
|
27
|
+
import { listDevSessionInspections, type TDevSessionInspection } from '../runtime/devSessions';
|
|
28
|
+
|
|
29
|
+
type TCliProteumMcpProviderArgs = {
|
|
30
|
+
appRoot: string;
|
|
31
|
+
sessionFilePath?: string;
|
|
32
|
+
url?: string;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
type TRequestOptions = {
|
|
36
|
+
method?: 'GET' | 'POST';
|
|
37
|
+
searchParams?: Record<string, string>;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const normalizeBaseUrl = (value: string) => value.replace(/\/+$/, '');
|
|
41
|
+
const dedupe = <TValue>(values: TValue[]) => [...new Set(values)];
|
|
42
|
+
|
|
43
|
+
const buildBaseUrlCandidates = (value: string) => {
|
|
44
|
+
const normalized = normalizeBaseUrl(value);
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
const parsed = new URL(normalized);
|
|
48
|
+
const port = parsed.port;
|
|
49
|
+
const pathname = parsed.pathname === '/' ? '' : parsed.pathname;
|
|
50
|
+
const search = parsed.search;
|
|
51
|
+
const hash = parsed.hash;
|
|
52
|
+
const buildUrl = (hostname: string) => `${parsed.protocol}//${hostname}${port ? `:${port}` : ''}${pathname}${search}${hash}`;
|
|
53
|
+
|
|
54
|
+
if (parsed.hostname === '127.0.0.1') return dedupe([normalized, buildUrl('localhost'), buildUrl('[::1]')]);
|
|
55
|
+
if (parsed.hostname === 'localhost') return dedupe([normalized, buildUrl('127.0.0.1'), buildUrl('[::1]')]);
|
|
56
|
+
if (parsed.hostname === '[::1]' || parsed.hostname === '::1') return dedupe([normalized, buildUrl('localhost'), buildUrl('127.0.0.1')]);
|
|
57
|
+
} catch (_error) {}
|
|
58
|
+
|
|
59
|
+
return [normalized];
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const compactSession = (inspection: TDevSessionInspection) => ({
|
|
63
|
+
sessionFilePath: inspection.sessionFilePath,
|
|
64
|
+
live: inspection.live,
|
|
65
|
+
stale: inspection.stale,
|
|
66
|
+
invalid: inspection.invalid,
|
|
67
|
+
parseError: inspection.parseError,
|
|
68
|
+
pid: inspection.record?.pid,
|
|
69
|
+
routerPort: inspection.record?.routerPort,
|
|
70
|
+
publicUrl: inspection.record?.publicUrl,
|
|
71
|
+
state: inspection.record?.state,
|
|
72
|
+
startedAt: inspection.record?.startedAt,
|
|
73
|
+
updatedAt: inspection.record?.updatedAt,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const getSessionUrl = (inspection: TDevSessionInspection) => {
|
|
77
|
+
if (!inspection.record) return '';
|
|
78
|
+
if (inspection.record.publicUrl) return inspection.record.publicUrl.replace(/\/+$/, '');
|
|
79
|
+
return `http://localhost:${inspection.record.routerPort}`;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export class CliProteumMcpProvider implements TProteumMcpProvider {
|
|
83
|
+
private sessionsPromise?: Promise<TDevSessionInspection[]>;
|
|
84
|
+
|
|
85
|
+
public constructor(private args: TCliProteumMcpProviderArgs) {}
|
|
86
|
+
|
|
87
|
+
private readManifestIfAvailable() {
|
|
88
|
+
const manifestFilepath = path.join(this.args.appRoot, '.proteum', 'manifest.json');
|
|
89
|
+
if (!fs.existsSync(manifestFilepath)) return undefined;
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
return readProteumManifest(this.args.appRoot);
|
|
93
|
+
} catch (_error) {
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
private readLocalManifest() {
|
|
99
|
+
const manifest = this.readManifestIfAvailable();
|
|
100
|
+
if (!manifest) {
|
|
101
|
+
throw new Error(
|
|
102
|
+
`Proteum manifest was not found in ${this.args.appRoot}. Run \`proteum refresh\`, \`proteum dev\`, or pass --url for a running dev server.`,
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return manifest;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private async readSessions() {
|
|
110
|
+
this.sessionsPromise ??= listDevSessionInspections({
|
|
111
|
+
appRoot: this.args.appRoot,
|
|
112
|
+
sessionFilePath: this.args.sessionFilePath,
|
|
113
|
+
});
|
|
114
|
+
return await this.sessionsPromise;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
private async selectSession() {
|
|
118
|
+
const sessions = await this.readSessions();
|
|
119
|
+
const liveSessions = sessions.filter((inspection) => inspection.live && inspection.record);
|
|
120
|
+
|
|
121
|
+
return (
|
|
122
|
+
liveSessions.find((inspection) => inspection.record?.state === 'ready') ||
|
|
123
|
+
liveSessions[0] ||
|
|
124
|
+
sessions.find((inspection) => inspection.record)
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private async getBaseUrlCandidates() {
|
|
129
|
+
if (this.args.url?.trim()) return buildBaseUrlCandidates(this.args.url.trim());
|
|
130
|
+
|
|
131
|
+
const selectedSession = await this.selectSession();
|
|
132
|
+
const selectedBaseUrl = selectedSession ? getSessionUrl(selectedSession) : '';
|
|
133
|
+
if (selectedBaseUrl) return buildBaseUrlCandidates(selectedBaseUrl);
|
|
134
|
+
|
|
135
|
+
const manifest = this.readManifestIfAvailable();
|
|
136
|
+
const routerPort = manifest?.env.resolved.routerPort;
|
|
137
|
+
if (typeof routerPort === 'number' && routerPort > 0) {
|
|
138
|
+
return dedupe([`http://localhost:${routerPort}`, `http://127.0.0.1:${routerPort}`, `http://[::1]:${routerPort}`]);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return [];
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private async requestJson<TResponse>(pathname: string, options: TRequestOptions = {}) {
|
|
145
|
+
const attempts: string[] = [];
|
|
146
|
+
const baseUrls = await this.getBaseUrlCandidates();
|
|
147
|
+
|
|
148
|
+
for (const baseUrl of baseUrls) {
|
|
149
|
+
const url = `${baseUrl}${pathname}${options.searchParams ? `?${new URLSearchParams(options.searchParams).toString()}` : ''}`;
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
const response = await got(url, {
|
|
153
|
+
method: options.method || 'GET',
|
|
154
|
+
responseType: 'json',
|
|
155
|
+
retry: { limit: 0 },
|
|
156
|
+
throwHttpErrors: false,
|
|
157
|
+
timeout: { request: 2_500 },
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
if (response.statusCode >= 400) {
|
|
161
|
+
const body = response.body as { error?: string } | undefined;
|
|
162
|
+
throw new Error(body?.error || `Proteum dev endpoint returned HTTP ${response.statusCode}.`);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return { baseUrl, body: response.body as TResponse };
|
|
166
|
+
} catch (error) {
|
|
167
|
+
attempts.push(`${url}: ${error instanceof Error ? error.message : String(error)}`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
throw new Error(
|
|
172
|
+
[
|
|
173
|
+
'Could not reach a Proteum dev MCP data source.',
|
|
174
|
+
...attempts.map((attempt) => `- ${attempt}`),
|
|
175
|
+
'Start `proteum dev`, pass --url, or use tools that can read the local manifest from disk.',
|
|
176
|
+
].join('\n'),
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
private async readManifestPreferRuntime() {
|
|
181
|
+
if (this.args.url?.trim()) {
|
|
182
|
+
return (await this.requestJson<TProteumManifest>('/__proteum/explain')).body;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const localManifest = this.readManifestIfAvailable();
|
|
186
|
+
if (localManifest) return localManifest;
|
|
187
|
+
|
|
188
|
+
return (await this.requestJson<TProteumManifest>('/__proteum/explain')).body;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private async probeRuntimeHealth() {
|
|
192
|
+
try {
|
|
193
|
+
const response = await this.requestJson<TDoctorResponse>('/__proteum/doctor');
|
|
194
|
+
return {
|
|
195
|
+
reachable: true,
|
|
196
|
+
baseUrl: response.baseUrl,
|
|
197
|
+
doctor: response.body.summary,
|
|
198
|
+
};
|
|
199
|
+
} catch (error) {
|
|
200
|
+
return {
|
|
201
|
+
reachable: false,
|
|
202
|
+
error: error instanceof Error ? error.message : String(error),
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
public async runtimeStatus(_input: Record<string, never> = {}) {
|
|
208
|
+
const manifest = this.readManifestIfAvailable();
|
|
209
|
+
const sessions = await this.readSessions();
|
|
210
|
+
const selectedSession = await this.selectSession();
|
|
211
|
+
const health = await this.probeRuntimeHealth();
|
|
212
|
+
const runtime =
|
|
213
|
+
health.reachable && 'baseUrl' in health
|
|
214
|
+
? {
|
|
215
|
+
publicUrl: health.baseUrl,
|
|
216
|
+
routerPort: selectedSession?.record?.routerPort || manifest?.env.resolved.routerPort,
|
|
217
|
+
source: this.args.url?.trim() ? 'explicit-url' : selectedSession?.record ? 'tracked-session' : 'manifest-port',
|
|
218
|
+
session: selectedSession ? compactSession(selectedSession) : undefined,
|
|
219
|
+
mcpUrl: `${health.baseUrl}/__proteum/mcp`,
|
|
220
|
+
}
|
|
221
|
+
: selectedSession
|
|
222
|
+
? {
|
|
223
|
+
routerPort: selectedSession.record?.routerPort,
|
|
224
|
+
publicUrl: selectedSession.record?.publicUrl,
|
|
225
|
+
source: 'tracked-session',
|
|
226
|
+
session: compactSession(selectedSession),
|
|
227
|
+
}
|
|
228
|
+
: undefined;
|
|
229
|
+
|
|
230
|
+
return buildRuntimeStatusPayload({
|
|
231
|
+
appRoot: this.args.appRoot,
|
|
232
|
+
health,
|
|
233
|
+
manifest,
|
|
234
|
+
runtime,
|
|
235
|
+
sessions: sessions.map(compactSession),
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
public async orient({ query }: { query: string }) {
|
|
240
|
+
return compactOrientationResponse(buildOrientationResponse(await this.readManifestPreferRuntime(), query));
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
public async instructionsResolve({ query }: { query?: string }) {
|
|
244
|
+
return resolveInstructionRouting({ appRoot: this.args.appRoot, query });
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
public async explainSummary({ query }: { query?: string }) {
|
|
248
|
+
const manifest = await this.readManifestPreferRuntime();
|
|
249
|
+
const normalizedQuery = query?.trim();
|
|
250
|
+
|
|
251
|
+
return compactExplainSummary({
|
|
252
|
+
manifest,
|
|
253
|
+
owner: normalizedQuery ? explainOwner(manifest, normalizedQuery) : undefined,
|
|
254
|
+
query: normalizedQuery,
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
public async doctor({ contracts = true }: { contracts?: boolean }) {
|
|
259
|
+
if (this.args.url?.trim()) {
|
|
260
|
+
const doctor = (await this.requestJson<TDoctorResponse>('/__proteum/doctor')).body;
|
|
261
|
+
const contractDoctor = contracts
|
|
262
|
+
? (await this.requestJson<TDoctorResponse>('/__proteum/doctor/contracts')).body
|
|
263
|
+
: undefined;
|
|
264
|
+
return compactDoctorResponse({ contracts: contractDoctor, doctor });
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const manifest = this.readManifestIfAvailable();
|
|
268
|
+
if (manifest) {
|
|
269
|
+
return compactDoctorResponse({
|
|
270
|
+
contracts: contracts ? buildContractsDoctorResponse(manifest) : undefined,
|
|
271
|
+
doctor: buildDoctorResponse(manifest),
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const doctor = (await this.requestJson<TDoctorResponse>('/__proteum/doctor')).body;
|
|
276
|
+
const contractDoctor = contracts
|
|
277
|
+
? (await this.requestJson<TDoctorResponse>('/__proteum/doctor/contracts')).body
|
|
278
|
+
: undefined;
|
|
279
|
+
return compactDoctorResponse({ contracts: contractDoctor, doctor });
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
public async diagnose(input: {
|
|
283
|
+
logsLevel?: TDevConsoleLogLevel;
|
|
284
|
+
logsLimit?: number;
|
|
285
|
+
path?: string;
|
|
286
|
+
query?: string;
|
|
287
|
+
requestId?: string;
|
|
288
|
+
}) {
|
|
289
|
+
const searchParams: Record<string, string> = {};
|
|
290
|
+
if (input.logsLevel) searchParams.logsLevel = input.logsLevel;
|
|
291
|
+
if (typeof input.logsLimit === 'number') searchParams.logsLimit = String(input.logsLimit);
|
|
292
|
+
if (input.path) searchParams.path = input.path;
|
|
293
|
+
if (input.query) searchParams.query = input.query;
|
|
294
|
+
if (input.requestId) searchParams.requestId = input.requestId;
|
|
295
|
+
|
|
296
|
+
return compactDiagnoseResponse((await this.requestJson<TDiagnoseResponse>('/__proteum/diagnose', { searchParams })).body);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
public async traceLatest(input: { detail?: 'compact' | 'full'; limit?: number; offset?: number }) {
|
|
300
|
+
const response = (await this.requestJson<TRequestTraceResponse>('/__proteum/trace/latest')).body;
|
|
301
|
+
return compactTraceResponse({
|
|
302
|
+
detail: input.detail,
|
|
303
|
+
limit: input.limit,
|
|
304
|
+
offset: input.offset,
|
|
305
|
+
request: response.request,
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
public async traceShow(input: { detail?: 'compact' | 'full'; limit?: number; offset?: number; requestId: string }) {
|
|
310
|
+
const response = (await this.requestJson<TRequestTraceResponse>(`/__proteum/trace/requests/${encodeURIComponent(input.requestId)}`))
|
|
311
|
+
.body;
|
|
312
|
+
return compactTraceResponse({
|
|
313
|
+
detail: input.detail,
|
|
314
|
+
limit: input.limit,
|
|
315
|
+
offset: input.offset,
|
|
316
|
+
request: response.request,
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
public async perfTop(input: { groupBy?: 'path' | 'route' | 'controller'; limit?: number; since?: string }) {
|
|
321
|
+
return compactPerfTopResponse(
|
|
322
|
+
(
|
|
323
|
+
await this.requestJson<TPerfTopResponse>('/__proteum/perf/top', {
|
|
324
|
+
searchParams: {
|
|
325
|
+
groupBy: input.groupBy || 'path',
|
|
326
|
+
limit: String(input.limit || 12),
|
|
327
|
+
since: input.since || 'today',
|
|
328
|
+
},
|
|
329
|
+
})
|
|
330
|
+
).body,
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
public async perfRequest({ query }: { query: string }) {
|
|
335
|
+
return compactPerfRequestResponse(
|
|
336
|
+
(
|
|
337
|
+
await this.requestJson<TPerfRequestResponse>('/__proteum/perf/request', {
|
|
338
|
+
searchParams: { query },
|
|
339
|
+
})
|
|
340
|
+
).body,
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
public async logsTail({ level = 'warn', limit = 40 }: { level?: TDevConsoleLogLevel; limit?: number }) {
|
|
345
|
+
return compactLogsResponse({
|
|
346
|
+
level,
|
|
347
|
+
limit,
|
|
348
|
+
response: (
|
|
349
|
+
await this.requestJson<TDevConsoleLogsResponse>('/__proteum/logs', {
|
|
350
|
+
searchParams: { level, limit: String(limit) },
|
|
351
|
+
})
|
|
352
|
+
).body,
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
public async readResource(uri: string) {
|
|
357
|
+
if (uri === 'proteum://runtime/status') return await this.runtimeStatus({});
|
|
358
|
+
if (uri === 'proteum://instructions/router') return await this.instructionsResolve({});
|
|
359
|
+
if (uri === 'proteum://manifest/summary') return await this.explainSummary({});
|
|
360
|
+
if (uri === 'proteum://trace/latest/summary') return await this.traceLatest({});
|
|
361
|
+
if (uri === 'proteum://perf/top') return await this.perfTop({});
|
|
362
|
+
|
|
363
|
+
throw new Error(`Unknown Proteum MCP resource: ${uri}`);
|
|
364
|
+
}
|
|
365
|
+
}
|
package/cli/mcp/stdio.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
2
|
+
|
|
3
|
+
import { createProteumMcpServer, type TProteumMcpProvider } from '../../common/dev/mcpServer';
|
|
4
|
+
|
|
5
|
+
export const startProteumMcpStdioServer = async ({
|
|
6
|
+
provider,
|
|
7
|
+
version,
|
|
8
|
+
}: {
|
|
9
|
+
provider: TProteumMcpProvider;
|
|
10
|
+
version: string;
|
|
11
|
+
}) => {
|
|
12
|
+
const server = createProteumMcpServer({ provider, version });
|
|
13
|
+
const transport = new StdioServerTransport();
|
|
14
|
+
|
|
15
|
+
await server.connect(transport);
|
|
16
|
+
};
|
|
@@ -21,6 +21,8 @@ export const proteumCommandNames = [
|
|
|
21
21
|
'orient',
|
|
22
22
|
'diagnose',
|
|
23
23
|
'perf',
|
|
24
|
+
'runtime',
|
|
25
|
+
'mcp',
|
|
24
26
|
'trace',
|
|
25
27
|
'command',
|
|
26
28
|
'session',
|
|
@@ -49,7 +51,7 @@ export type TProteumCommandDoc = {
|
|
|
49
51
|
|
|
50
52
|
export const proteumRecommendedFlow: TRow[] = [
|
|
51
53
|
{ label: '1. proteum orient <query>', value: 'Start here for multi-repo, generated, or connected work before reading code.' },
|
|
52
|
-
{ label: '2. proteum
|
|
54
|
+
{ label: '2. proteum runtime status', value: 'Reuse an existing tracked dev session before starting a new one.' },
|
|
53
55
|
{ label: '3. proteum diagnose <path> --hit <path>', value: 'Validate the smallest trustworthy request surface before broader checks.' },
|
|
54
56
|
{ label: '4. proteum check', value: 'Refresh, typecheck, and lint before you commit or push.' },
|
|
55
57
|
];
|
|
@@ -57,7 +59,7 @@ export const proteumRecommendedFlow: TRow[] = [
|
|
|
57
59
|
export const proteumCommandGroups: Array<{ title: string; names: TProteumCommandName[] }> = [
|
|
58
60
|
{ title: 'Daily workflow', names: ['dev', 'refresh', 'build'] },
|
|
59
61
|
{ title: 'Quality gates', names: ['typecheck', 'lint', 'check', 'e2e'] },
|
|
60
|
-
{ title: 'Manifest and contracts', names: ['connect', 'doctor', 'explain', 'orient', 'diagnose', 'perf', 'trace', 'command', 'session', 'verify'] },
|
|
62
|
+
{ title: 'Manifest and contracts', names: ['connect', 'doctor', 'explain', 'orient', 'diagnose', 'perf', 'runtime', 'mcp', 'trace', 'command', 'session', 'verify'] },
|
|
61
63
|
{ title: 'Project scaffolding', names: ['init', 'configure', 'create', 'migrate'] },
|
|
62
64
|
];
|
|
63
65
|
|
|
@@ -310,16 +312,17 @@ export const proteumCommands: Record<TProteumCommandName, TProteumCommandDoc> =
|
|
|
310
312
|
name: 'connect',
|
|
311
313
|
category: 'Manifest and contracts',
|
|
312
314
|
summary: 'Inspect connected-project config, cached contracts, and imported controllers.',
|
|
313
|
-
usage: 'proteum connect [--controllers] [--
|
|
315
|
+
usage: 'proteum connect [--controllers] [--full|--human] [--strict]',
|
|
314
316
|
bestFor:
|
|
315
317
|
'Auditing the current app connect setup without manually stitching together refresh, explain, env inspection, and contract checks.',
|
|
316
318
|
examples: [
|
|
317
|
-
{ description: 'Print a
|
|
319
|
+
{ description: 'Print a compact connected-project summary', command: 'proteum connect' },
|
|
318
320
|
{ description: 'Include imported connected controllers', command: 'proteum connect --controllers' },
|
|
319
|
-
{ description: 'Emit
|
|
321
|
+
{ description: 'Emit the full connect payload', command: 'proteum connect --full' },
|
|
320
322
|
{ description: 'Fail when connect diagnostics exist', command: 'proteum connect --strict' },
|
|
321
323
|
],
|
|
322
324
|
notes: [
|
|
325
|
+
'Default output is compact `proteum-agent-v1` JSON.',
|
|
323
326
|
'Proteum refreshes generated typings before reading the connect manifest state.',
|
|
324
327
|
'This command inspects explicit `proteum.config.ts` connected sources and URLs, cached `.proteum/connected/*.json` files, and imported connected controllers.',
|
|
325
328
|
'`--strict` is intended for CI or framework validation when connected contracts must be present and usable.',
|
|
@@ -330,27 +333,31 @@ export const proteumCommands: Record<TProteumCommandName, TProteumCommandDoc> =
|
|
|
330
333
|
name: 'doctor',
|
|
331
334
|
category: 'Manifest and contracts',
|
|
332
335
|
summary: 'Inspect the generated Proteum manifest diagnostics.',
|
|
333
|
-
usage: 'proteum doctor [--contracts] [--
|
|
336
|
+
usage: 'proteum doctor [--contracts] [--full|--human] [--strict]',
|
|
334
337
|
bestFor:
|
|
335
338
|
'Auditing manifest warnings and errors, especially in CI or when route/controller generation behaves unexpectedly.',
|
|
336
339
|
examples: [
|
|
337
|
-
{ description: 'Print a
|
|
340
|
+
{ description: 'Print a compact diagnostic summary', command: 'proteum doctor' },
|
|
338
341
|
{ description: 'Inspect missing generated contracts and source files', command: 'proteum doctor --contracts' },
|
|
339
342
|
{ description: 'Fail if any diagnostics exist', command: 'proteum doctor --strict' },
|
|
340
|
-
{ description: 'Emit
|
|
343
|
+
{ description: 'Emit the full diagnostic payload', command: 'proteum doctor --full' },
|
|
344
|
+
],
|
|
345
|
+
notes: [
|
|
346
|
+
'Default output is compact `proteum-agent-v1` JSON.',
|
|
347
|
+
'`--strict` is intended for CI and pre-release verification.',
|
|
348
|
+
'`--contracts` checks manifest-owned source files and expected generated artifacts on disk.',
|
|
341
349
|
],
|
|
342
|
-
notes: ['`--strict` is intended for CI and pre-release verification.', '`--contracts` checks manifest-owned source files and expected generated artifacts on disk.'],
|
|
343
350
|
status: 'stable',
|
|
344
351
|
},
|
|
345
352
|
explain: {
|
|
346
353
|
name: 'explain',
|
|
347
354
|
category: 'Manifest and contracts',
|
|
348
355
|
summary: 'Explain the generated Proteum manifest.',
|
|
349
|
-
usage: 'proteum explain [owner <query>] [--all|--app|--conventions|--env|--connected|--services|--controllers|--commands|--routes|--layouts|--diagnostics]
|
|
356
|
+
usage: 'proteum explain [owner <query>] [--manifest|--full|--human|--all|--app|--conventions|--env|--connected|--services|--controllers|--commands|--routes|--layouts|--diagnostics]',
|
|
350
357
|
bestFor:
|
|
351
|
-
'Inspecting
|
|
358
|
+
'Inspecting the compact generated-app summary first, then opening selected manifest sections only when needed.',
|
|
352
359
|
examples: [
|
|
353
|
-
{ description: 'Show the default
|
|
360
|
+
{ description: 'Show the default compact agent summary', command: 'proteum explain' },
|
|
354
361
|
{
|
|
355
362
|
description: 'Inspect generated routes, controllers, and commands together',
|
|
356
363
|
command: 'proteum explain --routes --controllers --commands',
|
|
@@ -360,9 +367,11 @@ export const proteumCommands: Record<TProteumCommandName, TProteumCommandDoc> =
|
|
|
360
367
|
command: 'proteum explain --connected --controllers',
|
|
361
368
|
},
|
|
362
369
|
{ description: 'Resolve the most likely manifest owner for a path or file', command: 'proteum explain owner /api/Auth/CurrentUser' },
|
|
363
|
-
{ description: 'Emit the
|
|
370
|
+
{ description: 'Emit the full manifest only when needed', command: 'proteum explain --manifest' },
|
|
364
371
|
],
|
|
365
372
|
notes: [
|
|
373
|
+
'Default output is compact `proteum-agent-v1` JSON because the CLI is optimized for agents.',
|
|
374
|
+
'`--full`, `--manifest`, or explicit section flags are the escape hatch for large details.',
|
|
366
375
|
'Legacy positional section selection remains supported, for example `proteum explain routes services`.',
|
|
367
376
|
'`proteum explain owner <query>` ranks matching routes, controllers, services, commands, layouts, and diagnostics from the manifest.',
|
|
368
377
|
'Connected projects are emitted from explicit `proteum.config.ts` `connect.<Namespace>.*` values plus the resolved connected contract.',
|
|
@@ -373,17 +382,17 @@ export const proteumCommands: Record<TProteumCommandName, TProteumCommandDoc> =
|
|
|
373
382
|
name: 'orient',
|
|
374
383
|
category: 'Manifest and contracts',
|
|
375
384
|
summary: 'Resolve owners, guidance files, connected boundaries, and next steps before opening code.',
|
|
376
|
-
usage: 'proteum orient <query> [--port <port>|--url <baseUrl>] [--
|
|
385
|
+
usage: 'proteum orient <query> [--port <port>|--url <baseUrl>] [--full|--human]',
|
|
377
386
|
bestFor:
|
|
378
387
|
'Starting multi-repo, generated-artifact, or connected-project work with one explicit orientation step instead of guessing the first files to read.',
|
|
379
388
|
examples: [
|
|
380
389
|
{ description: 'Orient around a generated controller path', command: 'proteum orient /api/Auth/CurrentUser' },
|
|
381
390
|
{ description: 'Orient around a connected namespace or route', command: 'proteum orient Product.Stats.general' },
|
|
382
|
-
{ description: 'Use a running dev server when the local manifest is unavailable', command: 'proteum orient /domains --port 3101
|
|
391
|
+
{ description: 'Use a running dev server when the local manifest is unavailable', command: 'proteum orient /domains --port 3101' },
|
|
383
392
|
],
|
|
384
393
|
notes: [
|
|
385
|
-
'
|
|
386
|
-
'Use it before reading source when the query might map to generated code, connected imports,
|
|
394
|
+
'Default output is compact `proteum-agent-v1` JSON with `mustRead`, conditional docs, owner matches, and next commands.',
|
|
395
|
+
'Use it before reading source when the query might map to generated code, connected imports, framework-owned files, or area instructions.',
|
|
387
396
|
'When `--port` or `--url` is provided, Proteum can read the manifest from a running dev server instead of only from disk.',
|
|
388
397
|
],
|
|
389
398
|
status: 'experimental',
|
|
@@ -392,7 +401,7 @@ export const proteumCommands: Record<TProteumCommandName, TProteumCommandDoc> =
|
|
|
392
401
|
name: 'diagnose',
|
|
393
402
|
category: 'Manifest and contracts',
|
|
394
403
|
summary: 'Combine owner lookup, doctor output, contract checks, traces, and server logs into one report.',
|
|
395
|
-
usage: 'proteum diagnose [<query>] [--hit <path>] [--method <verb>] [--data-json <json>] [--session-email <email>] [--session-role <role>] [--port <port>|--url <baseUrl>] [--
|
|
404
|
+
usage: 'proteum diagnose [<query>] [--hit <path>] [--method <verb>] [--data-json <json>] [--session-email <email>] [--session-role <role>] [--port <port>|--url <baseUrl>] [--full|--human]',
|
|
396
405
|
bestFor:
|
|
397
406
|
'Collapsing the usual explain + doctor + trace + session + server log loop into one structured debugging pass.',
|
|
398
407
|
examples: [
|
|
@@ -401,6 +410,8 @@ export const proteumCommands: Record<TProteumCommandName, TProteumCommandDoc> =
|
|
|
401
410
|
{ description: 'Diagnose an API call with a JSON payload', command: 'proteum diagnose /api/Auth/CurrentUser --hit /api/Auth/CurrentUser --method POST --data-json "{}"' },
|
|
402
411
|
],
|
|
403
412
|
notes: [
|
|
413
|
+
'Default output is compact `proteum-agent-v1` JSON and omits raw request events, payloads, and SQL text.',
|
|
414
|
+
'Use `--full` only when the compact response says lower-level detail is required.',
|
|
404
415
|
'This command talks to the running app over the dev-only diagnostics, trace, and session endpoints.',
|
|
405
416
|
'When `--hit` is omitted, Proteum diagnoses the latest matching request trace if one already exists.',
|
|
406
417
|
],
|
|
@@ -410,7 +421,7 @@ export const proteumCommands: Record<TProteumCommandName, TProteumCommandDoc> =
|
|
|
410
421
|
name: 'perf',
|
|
411
422
|
category: 'Manifest and contracts',
|
|
412
423
|
summary: 'Inspect shared performance rollups built from live request traces on a running Proteum dev server.',
|
|
413
|
-
usage: 'proteum perf [top|request <requestId|path>|compare|memory] [--since <window>] [--baseline <window>] [--target <window>] [--group-by <path|route|controller>] [--limit <n>] [--port <port>|--url <baseUrl>] [--
|
|
424
|
+
usage: 'proteum perf [top|request <requestId|path>|compare|memory] [--since <window>] [--baseline <window>] [--target <window>] [--group-by <path|route|controller>] [--limit <n>] [--port <port>|--url <baseUrl>] [--full|--human]',
|
|
414
425
|
bestFor:
|
|
415
426
|
'Finding the routes or controllers with the biggest response-time, CPU, SQL, render, and memory impact without manually stitching traces together.',
|
|
416
427
|
examples: [
|
|
@@ -426,17 +437,61 @@ export const proteumCommands: Record<TProteumCommandName, TProteumCommandDoc> =
|
|
|
426
437
|
},
|
|
427
438
|
],
|
|
428
439
|
notes: [
|
|
440
|
+
'Default output is compact `proteum-agent-v1` JSON with capped rows and next commands.',
|
|
429
441
|
'Perf data is derived from the same dev-only request trace buffer used by `proteum trace` and the profiler.',
|
|
430
442
|
'Window values accept `1h`, `6h`, `24h`, `today`, `yesterday`, or an ISO timestamp.',
|
|
431
443
|
'Older traces captured before the perf runtime metrics were added may not include CPU or memory deltas.',
|
|
432
444
|
],
|
|
433
445
|
status: 'experimental',
|
|
434
446
|
},
|
|
447
|
+
runtime: {
|
|
448
|
+
name: 'runtime',
|
|
449
|
+
category: 'Manifest and contracts',
|
|
450
|
+
summary: 'Inspect the current app manifest, tracked dev sessions, and runtime health in one compact response.',
|
|
451
|
+
usage: 'proteum runtime status [--session-file <path>] [--full]',
|
|
452
|
+
bestFor:
|
|
453
|
+
'Reusing a live dev session and avoiding repeated dev-list, manifest, and health-check commands before request diagnostics.',
|
|
454
|
+
examples: [
|
|
455
|
+
{ description: 'Resolve the current runtime status', command: 'proteum runtime status' },
|
|
456
|
+
{ description: 'Inspect one explicit session file', command: 'proteum runtime status --session-file var/run/proteum/dev/agents/task.json' },
|
|
457
|
+
],
|
|
458
|
+
notes: [
|
|
459
|
+
'Default output is compact `proteum-agent-v1` JSON with the selected session, health, and next command.',
|
|
460
|
+
'Use `--full` to include every tracked session field.',
|
|
461
|
+
],
|
|
462
|
+
status: 'experimental',
|
|
463
|
+
},
|
|
464
|
+
mcp: {
|
|
465
|
+
name: 'mcp',
|
|
466
|
+
category: 'Manifest and contracts',
|
|
467
|
+
summary: 'Start a read-only Proteum MCP server for compact agent diagnostics and runtime data.',
|
|
468
|
+
usage: 'proteum mcp [--cwd <path>] [--url <baseUrl>] [--session-file <path>]',
|
|
469
|
+
bestFor:
|
|
470
|
+
'Agent integrations that need repeated low-token access to Proteum manifest, instruction routing, runtime status, trace, perf, diagnose, and log summaries.',
|
|
471
|
+
examples: [
|
|
472
|
+
{ description: 'Start the MCP server for the current app over stdio', command: 'proteum mcp' },
|
|
473
|
+
{
|
|
474
|
+
description: 'Point the MCP server at a running dev server',
|
|
475
|
+
command: 'proteum mcp --url http://localhost:3101',
|
|
476
|
+
},
|
|
477
|
+
{
|
|
478
|
+
description: 'Resolve runtime data from an explicit tracked session file',
|
|
479
|
+
command: 'proteum mcp --session-file var/run/proteum/dev/agents/task.json',
|
|
480
|
+
},
|
|
481
|
+
],
|
|
482
|
+
notes: [
|
|
483
|
+
'`proteum mcp` is read-only in v1 and does not start/stop dev servers, refresh generated code, write files, or mutate traces.',
|
|
484
|
+
'Tool and resource payloads are compact single-line `proteum-mcp-v1` JSON for low-token agent reads.',
|
|
485
|
+
'Use the CLI for reproducible build/dev/check workflows; use MCP for repeated agent reads and progressive detail loading.',
|
|
486
|
+
'A running `proteum dev` server also exposes the same tool contract at `/__proteum/mcp` for runtime-adjacent access.',
|
|
487
|
+
],
|
|
488
|
+
status: 'experimental',
|
|
489
|
+
},
|
|
435
490
|
trace: {
|
|
436
491
|
name: 'trace',
|
|
437
492
|
category: 'Manifest and contracts',
|
|
438
493
|
summary: 'Inspect live in-memory request traces from a running Proteum dev server.',
|
|
439
|
-
usage: 'proteum trace [latest|show <requestId>|requests|arm|export <requestId>] [--port <port>|--url <baseUrl>] [--
|
|
494
|
+
usage: 'proteum trace [latest|show <requestId>|requests|arm|export <requestId>] [--port <port>|--url <baseUrl>] [--events|--full|--human]',
|
|
440
495
|
bestFor:
|
|
441
496
|
'Debugging route resolution, context creation, SSR payloads, renders, and runtime errors without attaching a debugger.',
|
|
442
497
|
examples: [
|
|
@@ -447,6 +502,8 @@ export const proteumCommands: Record<TProteumCommandName, TProteumCommandDoc> =
|
|
|
447
502
|
{ description: 'Target a custom dev base URL directly', command: 'proteum trace latest --url http://127.0.0.1:3010' },
|
|
448
503
|
],
|
|
449
504
|
notes: [
|
|
505
|
+
'Default output is compact `proteum-agent-v1` JSON with counts, errors, hot calls, and hot SQL only.',
|
|
506
|
+
'Use `--events` or `--full` to print the raw event stream, payload summaries, and SQL text.',
|
|
450
507
|
'This command talks to the running app over the dev-only `__proteum/trace` HTTP endpoints.',
|
|
451
508
|
'Traces are stored in a bounded in-memory buffer with payload summarization and sensitive-field redaction.',
|
|
452
509
|
'Use `--port` when the app is not running on the router port declared in `PORT`, or `--url` when the host itself is non-standard.',
|
|
@@ -35,6 +35,7 @@ export const renderDevSession = async ({
|
|
|
35
35
|
{ label: 'root', value: appRoot },
|
|
36
36
|
{ label: 'router', value: `http://localhost:${routerPort}` },
|
|
37
37
|
{ label: 'hmr', value: `http://localhost:${devEventPort}/__proteum_hmr` },
|
|
38
|
+
{ label: 'mcp', value: `http://localhost:${routerPort}/__proteum/mcp` },
|
|
38
39
|
...(connectedProjects && connectedProjects.length > 0
|
|
39
40
|
? connectedProjects.map((connectedProject) => ({
|
|
40
41
|
label: `connect ${connectedProject.namespace}`,
|
|
@@ -91,6 +92,7 @@ export const renderServerReadyBanner = async ({
|
|
|
91
92
|
createElement(Text, { dimColor: true }, `Diagnose /: proteum diagnose / --port ${routerPort}`),
|
|
92
93
|
createElement(Text, { dimColor: true }, `Perf top: proteum perf top --port ${routerPort}`),
|
|
93
94
|
createElement(Text, { dimColor: true }, `Trace latest: proteum trace latest --port ${routerPort}`),
|
|
95
|
+
createElement(Text, { dimColor: true }, `MCP: ${publicUrl}/__proteum/mcp`),
|
|
94
96
|
);
|
|
95
97
|
});
|
|
96
98
|
|