proteum 2.1.0-5 → 2.1.1

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.
Files changed (56) hide show
  1. package/AGENTS.md +37 -49
  2. package/README.md +52 -1
  3. package/agents/framework/AGENTS.md +104 -236
  4. package/agents/project/AGENTS.md +36 -70
  5. package/cli/commands/command.ts +243 -0
  6. package/cli/commands/commandLocalRunner.js +198 -0
  7. package/cli/commands/dev.ts +95 -1
  8. package/cli/commands/doctor.ts +8 -74
  9. package/cli/commands/explain.ts +8 -194
  10. package/cli/commands/trace.ts +8 -0
  11. package/cli/compiler/artifacts/commands.ts +217 -0
  12. package/cli/compiler/artifacts/manifest.ts +17 -2
  13. package/cli/compiler/artifacts/services.ts +291 -0
  14. package/cli/compiler/client/index.ts +13 -0
  15. package/cli/compiler/common/commands.ts +175 -0
  16. package/cli/compiler/common/proteumManifest.ts +15 -124
  17. package/cli/compiler/index.ts +25 -2
  18. package/cli/compiler/server/index.ts +3 -0
  19. package/cli/presentation/commands.ts +37 -5
  20. package/cli/runtime/commands.ts +29 -1
  21. package/cli/tsconfig.json +4 -1
  22. package/cli/utils/check.ts +1 -1
  23. package/client/app/component.tsx +11 -0
  24. package/client/dev/profiler/index.tsx +1511 -0
  25. package/client/dev/profiler/noop.tsx +5 -0
  26. package/client/dev/profiler/runtime.noop.ts +116 -0
  27. package/client/dev/profiler/runtime.ts +840 -0
  28. package/client/services/router/components/router.tsx +30 -2
  29. package/client/services/router/index.tsx +25 -0
  30. package/client/services/router/request/api.ts +133 -17
  31. package/commands/proteum/diagnostics.ts +11 -0
  32. package/common/dev/commands.ts +50 -0
  33. package/common/dev/diagnostics.ts +298 -0
  34. package/common/dev/profiler.ts +91 -0
  35. package/common/dev/proteumManifest.ts +135 -0
  36. package/common/dev/requestTrace.ts +28 -1
  37. package/docs/dev-commands.md +86 -0
  38. package/docs/request-tracing.md +2 -0
  39. package/package.json +1 -2
  40. package/server/app/commands.ts +35 -370
  41. package/server/app/commandsManager.ts +393 -0
  42. package/server/app/container/console/index.ts +0 -2
  43. package/server/app/container/trace/index.ts +88 -8
  44. package/server/app/devCommands.ts +192 -0
  45. package/server/app/devDiagnostics.ts +53 -0
  46. package/server/app/index.ts +27 -4
  47. package/server/services/cron/CronTask.ts +73 -5
  48. package/server/services/cron/index.ts +34 -11
  49. package/server/services/fetch/index.ts +3 -10
  50. package/server/services/prisma/index.ts +1 -1
  51. package/server/services/router/http/index.ts +132 -21
  52. package/server/services/router/index.ts +40 -4
  53. package/server/services/router/request/api.ts +30 -1
  54. package/skills/clean-project-code/SKILL.md +7 -2
  55. package/test-results/.last-run.json +4 -0
  56. package/types/aliases.d.ts +6 -0
@@ -0,0 +1,298 @@
1
+ import type {
2
+ TProteumManifest,
3
+ TProteumManifestCommand,
4
+ TProteumManifestController,
5
+ TProteumManifestDiagnostic,
6
+ TProteumManifestLayout,
7
+ TProteumManifestRoute,
8
+ TProteumManifestService,
9
+ } from './proteumManifest';
10
+
11
+ export const explainSectionNames = ['app', 'conventions', 'env', 'services', 'controllers', 'commands', 'routes', 'layouts', 'diagnostics'] as const;
12
+
13
+ export type TExplainSectionName = (typeof explainSectionNames)[number];
14
+ export type THumanTextBlock = {
15
+ title: string;
16
+ items: string[];
17
+ empty?: string;
18
+ };
19
+ export type TDoctorResponse = {
20
+ summary: {
21
+ errors: number;
22
+ warnings: number;
23
+ strictFailed: boolean;
24
+ };
25
+ diagnostics: TProteumManifestDiagnostic[];
26
+ };
27
+
28
+ const normalizePath = (value: string) => value.replace(/\\/g, '/');
29
+ const emptyBlock = 'none';
30
+
31
+ export const formatManifestLocation = (line?: number, column?: number) =>
32
+ line && column ? `:${line}:${column}` : line ? `:${line}` : '';
33
+
34
+ export const formatManifestFilepath = (manifest: TProteumManifest, filepath: string) => {
35
+ const normalizedFilepath = normalizePath(filepath);
36
+ const normalizedAppRoot = normalizePath(manifest.app.root);
37
+ const normalizedCoreRoot = normalizePath(manifest.app.coreRoot);
38
+
39
+ if (normalizedFilepath === normalizedAppRoot) return '.';
40
+ if (normalizedFilepath.startsWith(normalizedAppRoot + '/')) return normalizedFilepath.slice(normalizedAppRoot.length + 1) || '.';
41
+
42
+ if (normalizedFilepath === normalizedCoreRoot) return 'node_modules/proteum';
43
+ if (normalizedFilepath.startsWith(normalizedCoreRoot + '/'))
44
+ return `node_modules/proteum/${normalizedFilepath.slice(normalizedCoreRoot.length + 1)}`;
45
+
46
+ return normalizedFilepath;
47
+ };
48
+
49
+ const formatServiceItem = (manifest: TProteumManifest, service: TProteumManifestService) => {
50
+ if (service.kind === 'ref') {
51
+ return `${service.registeredName} -> ref ${service.refTo} [${service.parent}]`;
52
+ }
53
+
54
+ const source = service.metasFilepath ? formatManifestFilepath(manifest, service.metasFilepath) : 'unknown';
55
+ return `${service.registeredName} -> ${service.id} (${service.metaName}) [${service.scope}] priority=${service.priority} source=${source}`;
56
+ };
57
+
58
+ const formatControllerItem = (manifest: TProteumManifest, controller: TProteumManifestController) =>
59
+ `${controller.clientAccessor} -> POST ${controller.httpPath} [${controller.scope}] input=${controller.hasInput ? 'yes' : 'no'} source=${formatManifestFilepath(manifest, controller.filepath)}${formatManifestLocation(controller.sourceLocation.line, controller.sourceLocation.column)}#${controller.methodName}`;
60
+
61
+ const formatCommandItem = (manifest: TProteumManifest, command: TProteumManifestCommand) =>
62
+ `${command.path} -> ${command.className}.${command.methodName} [${command.scope}] source=${formatManifestFilepath(manifest, command.filepath)}${formatManifestLocation(command.sourceLocation.line, command.sourceLocation.column)}`;
63
+
64
+ const formatRouteTarget = (route: TProteumManifestRoute) => {
65
+ if (route.kind === 'client-error') return route.code !== undefined ? String(route.code) : route.codeRaw || '?';
66
+ return route.path || route.pathRaw || '?';
67
+ };
68
+
69
+ const formatRouteItem = (manifest: TProteumManifest, route: TProteumManifestRoute) => {
70
+ const chunk = route.chunkId ? ` chunk=${route.chunkId}` : '';
71
+ const setup = route.hasSetup ? ' setup=yes' : ' setup=no';
72
+ const options = route.normalizedOptionKeys.length > 0 ? ` options=${route.normalizedOptionKeys.join(',')}` : '';
73
+ const resolution = route.targetResolution !== 'literal' ? ` resolution=${route.targetResolution}` : '';
74
+
75
+ return `${route.kind} ${route.methodName} ${formatRouteTarget(route)} [${route.scope}]${chunk}${setup}${options}${resolution} source=${formatManifestFilepath(manifest, route.filepath)}${formatManifestLocation(route.sourceLocation.line, route.sourceLocation.column)}`;
76
+ };
77
+
78
+ const formatLayoutItem = (manifest: TProteumManifest, layout: TProteumManifestLayout) =>
79
+ `${layout.chunkId || 'root'} depth=${layout.depth} [${layout.scope}] source=${formatManifestFilepath(manifest, layout.filepath)}`;
80
+
81
+ const formatDiagnosticItem = (manifest: TProteumManifest, diagnostic: TProteumManifestDiagnostic) => {
82
+ const related =
83
+ diagnostic.relatedFilepaths && diagnostic.relatedFilepaths.length > 0
84
+ ? ` related=${diagnostic.relatedFilepaths.map((filepath) => formatManifestFilepath(manifest, filepath)).join(',')}`
85
+ : '';
86
+
87
+ return `[${diagnostic.level}] ${diagnostic.code} ${diagnostic.message} source=${formatManifestFilepath(manifest, diagnostic.filepath)}${formatManifestLocation(diagnostic.sourceLocation?.line, diagnostic.sourceLocation?.column)}${related}`;
88
+ };
89
+
90
+ export const pickExplainManifestSections = (manifest: TProteumManifest, sectionNames: TExplainSectionName[]) => {
91
+ if (sectionNames.length === 0) return manifest;
92
+
93
+ const selected: Partial<TProteumManifest> = {};
94
+
95
+ for (const sectionName of sectionNames) {
96
+ selected[sectionName] = manifest[sectionName];
97
+ }
98
+
99
+ return selected;
100
+ };
101
+
102
+ export const buildExplainSummaryItems = (manifest: TProteumManifest) => {
103
+ const errorsCount = manifest.diagnostics.filter((diagnostic) => diagnostic.level === 'error').length;
104
+ const warningsCount = manifest.diagnostics.filter((diagnostic) => diagnostic.level === 'warning').length;
105
+ const providedRequiredEnvVariables = manifest.env.requiredVariables.filter((variable) => variable.provided).length;
106
+
107
+ return [
108
+ `Proteum manifest: ${formatManifestFilepath(manifest, `${normalizePath(manifest.app.root)}/.proteum/manifest.json`)}`,
109
+ `App: ${manifest.app.identity.name} (${manifest.app.identity.identifier})`,
110
+ `Env vars: ${providedRequiredEnvVariables}/${manifest.env.requiredVariables.length} required provided`,
111
+ `Services: ${manifest.services.app.length} app, ${manifest.services.routerPlugins.length} router plugins`,
112
+ `Controllers: ${manifest.controllers.length}`,
113
+ `Commands: ${manifest.commands.length}`,
114
+ `Routes: ${manifest.routes.client.length} client, ${manifest.routes.server.length} server`,
115
+ `Layouts: ${manifest.layouts.length}`,
116
+ `Diagnostics: ${errorsCount} errors, ${warningsCount} warnings`,
117
+ 'Use `proteum explain --json` for the full machine-readable manifest or pass section flags like `routes` and `services`.',
118
+ ];
119
+ };
120
+
121
+ export const buildExplainBlocks = (manifest: TProteumManifest, sectionNames: TExplainSectionName[]): THumanTextBlock[] => {
122
+ const blocks: THumanTextBlock[] = [];
123
+
124
+ for (const sectionName of sectionNames) {
125
+ if (sectionName === 'app') {
126
+ blocks.push({
127
+ title: 'App',
128
+ items: [
129
+ `root=${formatManifestFilepath(manifest, manifest.app.root)}`,
130
+ `coreRoot=${formatManifestFilepath(manifest, manifest.app.coreRoot)}`,
131
+ `identity=${formatManifestFilepath(manifest, manifest.app.identityFilepath)}`,
132
+ `name=${manifest.app.identity.name}`,
133
+ `identifier=${manifest.app.identity.identifier}`,
134
+ `title=${manifest.app.identity.fullTitle || manifest.app.identity.title || manifest.app.identity.name}`,
135
+ ],
136
+ });
137
+ continue;
138
+ }
139
+
140
+ if (sectionName === 'conventions') {
141
+ blocks.push({
142
+ title: 'Conventions',
143
+ items: [
144
+ `routeSetupOptionKeys=${manifest.conventions.routeSetupOptionKeys.join(', ')}`,
145
+ `reservedRouteSetupKeys=${manifest.conventions.reservedRouteSetupKeys.join(', ')}`,
146
+ ],
147
+ });
148
+ continue;
149
+ }
150
+
151
+ if (sectionName === 'env') {
152
+ blocks.push({
153
+ title: 'Env',
154
+ items: [
155
+ `source=${manifest.env.source}`,
156
+ `loadedVariableKeys=${manifest.env.loadedVariableKeys.join(', ') || 'none'}`,
157
+ ...manifest.env.requiredVariables.map(
158
+ (variable) =>
159
+ `${variable.key} possibleValues=${variable.possibleValues.join(' | ')} provided=${variable.provided ? 'yes' : 'no'}`,
160
+ ),
161
+ `resolved.name=${manifest.env.resolved.name}`,
162
+ `resolved.profile=${manifest.env.resolved.profile}`,
163
+ `resolved.routerPort=${manifest.env.resolved.routerPort}`,
164
+ `resolved.routerCurrentDomain=${manifest.env.resolved.routerCurrentDomain}`,
165
+ ],
166
+ });
167
+ continue;
168
+ }
169
+
170
+ if (sectionName === 'services') {
171
+ blocks.push(
172
+ {
173
+ title: 'App Services',
174
+ items: manifest.services.app.map((service) => formatServiceItem(manifest, service)),
175
+ },
176
+ {
177
+ title: 'Router Plugins',
178
+ items: manifest.services.routerPlugins.map((service) => formatServiceItem(manifest, service)),
179
+ },
180
+ );
181
+ continue;
182
+ }
183
+
184
+ if (sectionName === 'controllers') {
185
+ blocks.push({
186
+ title: 'Controllers',
187
+ items: manifest.controllers.map((controller) => formatControllerItem(manifest, controller)),
188
+ });
189
+ continue;
190
+ }
191
+
192
+ if (sectionName === 'commands') {
193
+ blocks.push({
194
+ title: 'Commands',
195
+ items: manifest.commands.map((command) => formatCommandItem(manifest, command)),
196
+ });
197
+ continue;
198
+ }
199
+
200
+ if (sectionName === 'routes') {
201
+ blocks.push(
202
+ {
203
+ title: 'Client Routes',
204
+ items: manifest.routes.client.map((route) => formatRouteItem(manifest, route)),
205
+ },
206
+ {
207
+ title: 'Server Routes',
208
+ items: manifest.routes.server.map((route) => formatRouteItem(manifest, route)),
209
+ },
210
+ );
211
+ continue;
212
+ }
213
+
214
+ if (sectionName === 'layouts') {
215
+ blocks.push({
216
+ title: 'Layouts',
217
+ items: manifest.layouts.map((layout) => formatLayoutItem(manifest, layout)),
218
+ });
219
+ continue;
220
+ }
221
+
222
+ if (sectionName === 'diagnostics') {
223
+ blocks.push({
224
+ title: 'Diagnostics',
225
+ items: manifest.diagnostics.map((diagnostic) => formatDiagnosticItem(manifest, diagnostic)),
226
+ });
227
+ }
228
+ }
229
+
230
+ return blocks;
231
+ };
232
+
233
+ export const renderHumanBlock = (block: THumanTextBlock) => {
234
+ if (block.items.length === 0) return `${block.title}\n- ${block.empty || emptyBlock}`;
235
+ return `${block.title}\n${block.items.map((item) => `- ${item}`).join('\n')}`;
236
+ };
237
+
238
+ export const renderExplainHuman = (manifest: TProteumManifest, sectionNames: TExplainSectionName[]) => {
239
+ if (sectionNames.length === 0) return buildExplainSummaryItems(manifest).join('\n');
240
+ return buildExplainBlocks(manifest, sectionNames).map(renderHumanBlock).join('\n\n');
241
+ };
242
+
243
+ export const buildDoctorResponse = (manifest: TProteumManifest, strict = false): TDoctorResponse => {
244
+ const errors = manifest.diagnostics.filter((diagnostic) => diagnostic.level === 'error');
245
+ const warnings = manifest.diagnostics.filter((diagnostic) => diagnostic.level === 'warning');
246
+
247
+ return {
248
+ summary: {
249
+ errors: errors.length,
250
+ warnings: warnings.length,
251
+ strictFailed: strict === true && manifest.diagnostics.length > 0,
252
+ },
253
+ diagnostics: manifest.diagnostics,
254
+ };
255
+ };
256
+
257
+ export const buildDoctorBlocks = (manifest: TProteumManifest): THumanTextBlock[] => {
258
+ const errors = manifest.diagnostics.filter((diagnostic) => diagnostic.level === 'error');
259
+ const warnings = manifest.diagnostics.filter((diagnostic) => diagnostic.level === 'warning');
260
+
261
+ return [
262
+ {
263
+ title: 'Errors',
264
+ items: errors.map((diagnostic) => {
265
+ const related =
266
+ diagnostic.relatedFilepaths && diagnostic.relatedFilepaths.length > 0
267
+ ? ` related=${diagnostic.relatedFilepaths.map((filepath) => formatManifestFilepath(manifest, filepath)).join(',')}`
268
+ : '';
269
+
270
+ return `${diagnostic.code} ${diagnostic.message} source=${formatManifestFilepath(manifest, diagnostic.filepath)}${formatManifestLocation(diagnostic.sourceLocation?.line, diagnostic.sourceLocation?.column)}${related}`;
271
+ }),
272
+ },
273
+ {
274
+ title: 'Warnings',
275
+ items: warnings.map((diagnostic) => {
276
+ const related =
277
+ diagnostic.relatedFilepaths && diagnostic.relatedFilepaths.length > 0
278
+ ? ` related=${diagnostic.relatedFilepaths.map((filepath) => formatManifestFilepath(manifest, filepath)).join(',')}`
279
+ : '';
280
+
281
+ return `${diagnostic.code} ${diagnostic.message} source=${formatManifestFilepath(manifest, diagnostic.filepath)}${formatManifestLocation(diagnostic.sourceLocation?.line, diagnostic.sourceLocation?.column)}${related}`;
282
+ }),
283
+ },
284
+ ];
285
+ };
286
+
287
+ export const renderDoctorHuman = (manifest: TProteumManifest, strict = false) => {
288
+ const response = buildDoctorResponse(manifest, strict);
289
+ if (response.diagnostics.length === 0) return 'Proteum doctor\n- No manifest diagnostics were found.';
290
+
291
+ return [
292
+ 'Proteum doctor',
293
+ `- ${response.summary.errors} errors`,
294
+ `- ${response.summary.warnings} warnings`,
295
+ '',
296
+ ...buildDoctorBlocks(manifest).map(renderHumanBlock),
297
+ ].join('\n');
298
+ };
@@ -0,0 +1,91 @@
1
+ import type { TRequestTrace } from './requestTrace';
2
+
3
+ export const profilerTraceRequestIdHeader = 'x-proteum-trace-request-id';
4
+ export const profilerSessionIdHeader = 'x-proteum-profiler-session-id';
5
+ export const profilerOriginHeader = 'x-proteum-profiler-origin';
6
+ export const profilerParentRequestIdHeader = 'x-proteum-profiler-parent-request-id';
7
+
8
+ export type TProfilerUiState = 'expanded' | 'minimized' | 'pinned-handle';
9
+ export type TProfilerPanel =
10
+ | 'summary'
11
+ | 'timeline'
12
+ | 'routing'
13
+ | 'controller'
14
+ | 'ssr'
15
+ | 'api'
16
+ | 'explain'
17
+ | 'doctor'
18
+ | 'commands'
19
+ | 'cron'
20
+ | 'errors';
21
+ export type TProfilerNavigationSessionKind = 'initial-ssr' | 'client-navigation';
22
+ export type TProfilerSessionTraceKind = 'initial-root' | 'navigation-data' | 'async';
23
+ export type TProfilerNavigationStepStatus = 'pending' | 'completed' | 'error';
24
+ export type TProfilerCronTaskTrigger = 'scheduler' | 'manual' | 'autoexec';
25
+ export type TProfilerCronTaskRunStatus = 'completed' | 'error';
26
+
27
+ export type TProfilerCronTaskFrequency =
28
+ | { kind: 'cron'; value: string }
29
+ | { kind: 'date'; value: string };
30
+
31
+ export type TProfilerCronTask = {
32
+ name: string;
33
+ registeredAt: string;
34
+ frequency: TProfilerCronTaskFrequency;
35
+ autoexec: boolean;
36
+ automaticExecution: boolean;
37
+ nextInvocation?: string;
38
+ running: boolean;
39
+ lastTrigger?: TProfilerCronTaskTrigger;
40
+ lastRunStartedAt?: string;
41
+ lastRunFinishedAt?: string;
42
+ lastRunDurationMs?: number;
43
+ lastRunStatus?: TProfilerCronTaskRunStatus;
44
+ lastErrorMessage?: string;
45
+ runCount: number;
46
+ };
47
+
48
+ export type TProfilerNavigationStep = {
49
+ id: string;
50
+ label: string;
51
+ startedAt: string;
52
+ finishedAt?: string;
53
+ durationMs?: number;
54
+ status: TProfilerNavigationStepStatus;
55
+ details?: { [key: string]: string | number | boolean };
56
+ errorMessage?: string;
57
+ };
58
+
59
+ export type TProfilerSessionTrace = {
60
+ id: string;
61
+ kind: TProfilerSessionTraceKind;
62
+ label: string;
63
+ method: string;
64
+ path: string;
65
+ startedAt: string;
66
+ finishedAt?: string;
67
+ durationMs?: number;
68
+ status: 'pending' | 'completed' | 'error';
69
+ requestId?: string;
70
+ fetcherIds?: string[];
71
+ trace?: TRequestTrace;
72
+ errorMessage?: string;
73
+ };
74
+
75
+ export type TProfilerNavigationSession = {
76
+ id: string;
77
+ kind: TProfilerNavigationSessionKind;
78
+ label: string;
79
+ path: string;
80
+ url: string;
81
+ startedAt: string;
82
+ finishedAt?: string;
83
+ durationMs?: number;
84
+ status: 'active' | 'completed' | 'error';
85
+ requestId?: string;
86
+ routeLabel?: string;
87
+ routeChunkId?: string;
88
+ title?: string;
89
+ steps: TProfilerNavigationStep[];
90
+ traces: TProfilerSessionTrace[];
91
+ };
@@ -0,0 +1,135 @@
1
+ export type TProteumManifestScope = 'app' | 'framework';
2
+ export type TProteumManifestSourceLocation = { line: number; column: number };
3
+ export type TProteumManifestRouteTargetResolution = 'literal' | 'static-expression' | 'dynamic-expression';
4
+ export type TProteumManifestDiagnosticLevel = 'warning' | 'error';
5
+
6
+ export type TProteumManifestDiagnostic = {
7
+ level: TProteumManifestDiagnosticLevel;
8
+ code: string;
9
+ message: string;
10
+ filepath: string;
11
+ sourceLocation?: TProteumManifestSourceLocation;
12
+ relatedFilepaths?: string[];
13
+ };
14
+
15
+ export type TProteumManifestService = {
16
+ kind: 'service' | 'ref';
17
+ id?: string;
18
+ registeredName: string;
19
+ metaName?: string;
20
+ parent: string;
21
+ priority: number;
22
+ importPath?: string;
23
+ sourceDir?: string;
24
+ metasFilepath?: string;
25
+ refTo?: string;
26
+ scope: TProteumManifestScope;
27
+ };
28
+
29
+ export type TProteumManifestController = {
30
+ className: string;
31
+ importPath: string;
32
+ filepath: string;
33
+ sourceLocation: TProteumManifestSourceLocation;
34
+ routeBasePath: string;
35
+ methodName: string;
36
+ inputCallsCount: number;
37
+ hasInput: boolean;
38
+ routePath: string;
39
+ httpPath: string;
40
+ clientAccessor: string;
41
+ scope: TProteumManifestScope;
42
+ };
43
+
44
+ export type TProteumManifestCommand = {
45
+ className: string;
46
+ importPath: string;
47
+ filepath: string;
48
+ sourceLocation: TProteumManifestSourceLocation;
49
+ commandBasePath: string;
50
+ methodName: string;
51
+ path: string;
52
+ scope: TProteumManifestScope;
53
+ };
54
+
55
+ export type TProteumManifestRoute = {
56
+ kind: 'client-page' | 'client-error' | 'server-route';
57
+ methodName: string;
58
+ serviceLocalName: string;
59
+ filepath: string;
60
+ sourceLocation: TProteumManifestSourceLocation;
61
+ targetResolution: TProteumManifestRouteTargetResolution;
62
+ path?: string;
63
+ pathRaw?: string;
64
+ code?: number;
65
+ codeRaw?: string;
66
+ optionKeys: string[];
67
+ normalizedOptionKeys: string[];
68
+ invalidOptionKeys: string[];
69
+ reservedOptionKeys: string[];
70
+ optionsRaw?: string;
71
+ hasSetup: boolean;
72
+ chunkId?: string;
73
+ chunkFilepath?: string;
74
+ scope: TProteumManifestScope;
75
+ };
76
+
77
+ export type TProteumManifestLayout = {
78
+ chunkId: string;
79
+ filepath: string;
80
+ importPath: string;
81
+ depth: number;
82
+ scope: TProteumManifestScope;
83
+ };
84
+
85
+ export type TProteumManifest = {
86
+ version: 2;
87
+ app: {
88
+ root: string;
89
+ coreRoot: string;
90
+ identityFilepath: string;
91
+ identity: {
92
+ name: string;
93
+ identifier: string;
94
+ description: string;
95
+ language?: string;
96
+ locale?: string;
97
+ title?: string;
98
+ titleSuffix?: string;
99
+ fullTitle?: string;
100
+ webDescription?: string;
101
+ version?: string;
102
+ };
103
+ };
104
+ conventions: {
105
+ routeSetupOptionKeys: string[];
106
+ reservedRouteSetupKeys: string[];
107
+ };
108
+ env: {
109
+ source: string;
110
+ loadedVariableKeys: string[];
111
+ requiredVariables: {
112
+ key: string;
113
+ possibleValues: string[];
114
+ provided: boolean;
115
+ }[];
116
+ resolved: {
117
+ name: string;
118
+ profile: string;
119
+ routerPort: number;
120
+ routerCurrentDomain: string;
121
+ };
122
+ };
123
+ services: {
124
+ app: TProteumManifestService[];
125
+ routerPlugins: TProteumManifestService[];
126
+ };
127
+ controllers: TProteumManifestController[];
128
+ commands: TProteumManifestCommand[];
129
+ routes: {
130
+ client: TProteumManifestRoute[];
131
+ server: TProteumManifestRoute[];
132
+ };
133
+ layouts: TProteumManifestLayout[];
134
+ diagnostics: TProteumManifestDiagnostic[];
135
+ };
@@ -3,6 +3,10 @@ export const traceCaptureModes = ['summary', 'resolve', 'deep'] as const;
3
3
  export type TTraceCaptureMode = (typeof traceCaptureModes)[number];
4
4
  type TTracePrimitive = string | number | boolean;
5
5
 
6
+ export const traceCallOrigins = ['ssr-fetcher', 'api-batch-fetcher', 'client-async'] as const;
7
+
8
+ export type TTraceCallOrigin = (typeof traceCallOrigins)[number];
9
+
6
10
  export const traceEventTypes = [
7
11
  'request.start',
8
12
  'request.user',
@@ -57,12 +61,34 @@ export type TTraceEvent = {
57
61
  details: { [key: string]: TTraceSummaryValue };
58
62
  };
59
63
 
64
+ export type TTraceCall = {
65
+ id: string;
66
+ parentId?: string;
67
+ origin: TTraceCallOrigin;
68
+ label: string;
69
+ method: string;
70
+ path: string;
71
+ fetcherId?: string;
72
+ startedAt: string;
73
+ finishedAt?: string;
74
+ durationMs?: number;
75
+ statusCode?: number;
76
+ errorMessage?: string;
77
+ requestDataKeys: string[];
78
+ requestData?: TTraceSummaryValue;
79
+ resultKeys: string[];
80
+ result?: TTraceSummaryValue;
81
+ };
82
+
60
83
  export type TRequestTrace = {
61
84
  id: string;
62
85
  method: string;
63
86
  path: string;
64
87
  url: string;
65
88
  capture: TTraceCaptureMode;
89
+ profilerSessionId?: string;
90
+ profilerOrigin?: string;
91
+ profilerParentRequestId?: string;
66
92
  startedAt: string;
67
93
  finishedAt?: string;
68
94
  durationMs?: number;
@@ -71,10 +97,11 @@ export type TRequestTrace = {
71
97
  droppedEvents: number;
72
98
  persistedFilepath?: string;
73
99
  errorMessage?: string;
100
+ calls: TTraceCall[];
74
101
  events: TTraceEvent[];
75
102
  };
76
103
 
77
- export type TRequestTraceListItem = Omit<TRequestTrace, 'events'> & { eventCount: number };
104
+ export type TRequestTraceListItem = Omit<TRequestTrace, 'events' | 'calls'> & { eventCount: number; callCount: number };
78
105
 
79
106
  export type TRequestTraceListResponse = { requests: TRequestTraceListItem[] };
80
107
  export type TRequestTraceResponse = { request: TRequestTrace };
@@ -0,0 +1,86 @@
1
+ ## Dev Commands
2
+
3
+ Proteum supports a dev-only internal command surface for testing, debugging, and one-off server-side execution that should not be exposed as a normal controller or route.
4
+
5
+ ### Source Contract
6
+
7
+ - command files live under `./commands/**/*.ts`
8
+ - each file default-exports a class extending `Commands` from `@server/app/commands`
9
+ - every method with a body becomes a runnable command
10
+ - the command path is `file/path/methodName`
11
+ - `export const commandPath = 'Custom/path'` can override the file-derived base path
12
+
13
+ Example:
14
+
15
+ ```ts
16
+ import { Commands } from '@server/app/commands';
17
+
18
+ export default class DiagnosticsCommands extends Commands {
19
+ public async ping() {
20
+ return {
21
+ app: this.app.identity.identifier,
22
+ env: this.app.env.profile,
23
+ };
24
+ }
25
+ }
26
+ ```
27
+
28
+ The example above is available as `diagnostics/ping`.
29
+
30
+ When `./commands` exists, Proteum also creates `./commands/tsconfig.json` plus a generated command typing surface under `.proteum/server/commands.d.ts`.
31
+
32
+ - command files inherit the server alias project
33
+ - `extends Commands` works without importing your app class
34
+ - `@/server/index` remains available inside `/commands` as a generated command-only app type alias
35
+
36
+ ### CLI
37
+
38
+ Run a command locally:
39
+
40
+ ```bash
41
+ proteum command proteum/diagnostics/ping
42
+ ```
43
+
44
+ Local mode does this:
45
+
46
+ 1. refreshes `.proteum` artifacts
47
+ 2. picks a temporary local port
48
+ 3. builds the dev server output
49
+ 4. starts a temporary local dev server
50
+ 5. runs the command through the dev-only command endpoint
51
+ 6. prints the result and exits
52
+
53
+ Run a command against an existing dev instance:
54
+
55
+ ```bash
56
+ proteum command proteum/diagnostics/ping --port 3101
57
+ proteum command proteum/diagnostics/ping --url http://127.0.0.1:3101
58
+ ```
59
+
60
+ Use `--port` or `--url` when you want to reuse an existing `proteum dev` instance instead of building and starting a temporary local one.
61
+
62
+ ### Profiler
63
+
64
+ In `proteum dev`, the bottom profiler exposes a `Commands` tab.
65
+
66
+ - it lists every generated command path
67
+ - it shows the backing class, method, scope, and source location
68
+ - clicking `Run now` executes the command through the running dev server
69
+ - the last result or error stays attached to that command row in the panel
70
+
71
+ ### HTTP Endpoints
72
+
73
+ The CLI remote mode and the profiler use the same dev-only endpoints:
74
+
75
+ - `GET /__proteum/commands`
76
+ - `POST /__proteum/commands/run`
77
+
78
+ These endpoints exist only in dev mode and are not available in production.
79
+
80
+ ### Built-In Command
81
+
82
+ Proteum ships one framework command by default:
83
+
84
+ - `proteum/diagnostics/ping`
85
+
86
+ It returns the current app identifier, active env profile, and discovered root services so every dev app has a real command surface available immediately.
@@ -76,6 +76,8 @@ Notes:
76
76
  - `capture` defaults to `resolve`
77
77
  - `requestsLimit` defaults to `200`
78
78
  - `eventsLimit` defaults to `800`
79
+ - `proteum dev` removes auto-persisted crash traces from `var/traces/` when the dev session stops
80
+ - explicit `proteum trace export` files under `var/traces/exports/` are left in place
79
81
 
80
82
  ## Memory Model
81
83
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "proteum",
3
3
  "description": "LLM-first Opinionated Typescript Framework for web applications.",
4
- "version": "2.1.0-5",
4
+ "version": "2.1.1",
5
5
  "author": "Gaetan Le Gac (https://github.com/gaetanlegac)",
6
6
  "repository": "git://github.com/gaetanlegac/proteum.git",
7
7
  "license": "MIT",
@@ -70,7 +70,6 @@
70
70
  "preact-render-to-string": "^6.6.1",
71
71
  "prompts": "^2.4.2",
72
72
  "replace-once": "^1.0.0",
73
- "request": "^2.88.2",
74
73
  "responsive-loader": "^3.1.2",
75
74
  "serialize-javascript": "^6.0.2",
76
75
  "sharp": "^0.34.3",