proteum 2.1.2 → 2.1.6

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 (99) hide show
  1. package/AGENTS.md +22 -14
  2. package/README.md +112 -17
  3. package/agents/project/AGENTS.md +188 -25
  4. package/agents/project/CODING_STYLE.md +1 -0
  5. package/agents/project/client/AGENTS.md +13 -8
  6. package/agents/project/client/pages/AGENTS.md +17 -9
  7. package/agents/project/diagnostics.md +52 -0
  8. package/agents/project/optimizations.md +48 -0
  9. package/agents/project/server/routes/AGENTS.md +9 -6
  10. package/agents/project/server/services/AGENTS.md +10 -6
  11. package/agents/project/tests/AGENTS.md +11 -5
  12. package/cli/app/config.ts +13 -14
  13. package/cli/app/index.ts +58 -0
  14. package/cli/commands/command.ts +8 -0
  15. package/cli/commands/connect.ts +45 -0
  16. package/cli/commands/dev.ts +26 -11
  17. package/cli/commands/diagnose.ts +286 -0
  18. package/cli/commands/doctor.ts +18 -5
  19. package/cli/commands/explain.ts +25 -0
  20. package/cli/commands/perf.ts +243 -0
  21. package/cli/commands/session.ts +254 -0
  22. package/cli/commands/sessionLocalRunner.js +188 -0
  23. package/cli/commands/trace.ts +17 -1
  24. package/cli/commands/verify.ts +281 -0
  25. package/cli/compiler/artifacts/connectedProjects.ts +453 -0
  26. package/cli/compiler/artifacts/controllers.ts +198 -49
  27. package/cli/compiler/artifacts/discovery.ts +0 -34
  28. package/cli/compiler/artifacts/manifest.ts +90 -6
  29. package/cli/compiler/artifacts/routing.ts +2 -2
  30. package/cli/compiler/artifacts/services.ts +277 -130
  31. package/cli/compiler/client/index.ts +3 -0
  32. package/cli/compiler/common/files/style.ts +52 -0
  33. package/cli/compiler/common/generatedRouteModules.ts +34 -5
  34. package/cli/compiler/common/scripts.ts +11 -5
  35. package/cli/compiler/index.ts +2 -1
  36. package/cli/compiler/server/index.ts +3 -0
  37. package/cli/presentation/commands.ts +136 -7
  38. package/cli/presentation/devSession.ts +32 -7
  39. package/cli/runtime/commands.ts +193 -6
  40. package/cli/scaffold/index.ts +14 -25
  41. package/cli/scaffold/templates.ts +41 -27
  42. package/cli/utils/agents.ts +4 -2
  43. package/cli/utils/keyboard.ts +8 -0
  44. package/client/dev/profiler/ApexChart.tsx +66 -0
  45. package/client/dev/profiler/index.tsx +2798 -417
  46. package/client/dev/profiler/runtime.noop.ts +12 -0
  47. package/client/dev/profiler/runtime.ts +195 -4
  48. package/client/services/router/request/api.ts +6 -1
  49. package/common/applicationConfig.ts +173 -0
  50. package/common/applicationConfigLoader.ts +102 -0
  51. package/common/connectedProjects.ts +113 -0
  52. package/common/dev/connect.ts +267 -0
  53. package/common/dev/console.ts +31 -0
  54. package/common/dev/contractsDoctor.ts +128 -0
  55. package/common/dev/diagnostics.ts +59 -15
  56. package/common/dev/inspection.ts +491 -0
  57. package/common/dev/performance.ts +809 -0
  58. package/common/dev/profiler.ts +3 -0
  59. package/common/dev/proteumManifest.ts +31 -6
  60. package/common/dev/requestTrace.ts +56 -1
  61. package/common/dev/session.ts +24 -0
  62. package/common/env/proteumEnv.ts +176 -50
  63. package/common/router/index.ts +1 -0
  64. package/common/router/request/api.ts +2 -0
  65. package/config.ts +5 -0
  66. package/docs/dev-commands.md +5 -1
  67. package/docs/dev-sessions.md +90 -0
  68. package/docs/diagnostics.md +74 -11
  69. package/docs/request-tracing.md +50 -3
  70. package/package.json +1 -1
  71. package/server/app/container/config.ts +16 -87
  72. package/server/app/container/console/index.ts +42 -8
  73. package/server/app/container/index.ts +3 -1
  74. package/server/app/container/trace/index.ts +153 -0
  75. package/server/app/devDiagnostics.ts +138 -0
  76. package/server/app/index.ts +18 -8
  77. package/server/app/service/container.ts +0 -12
  78. package/server/app/service/index.ts +0 -2
  79. package/server/services/prisma/index.ts +121 -4
  80. package/server/services/router/http/index.ts +352 -0
  81. package/server/services/router/index.ts +50 -47
  82. package/server/services/router/request/api.ts +160 -19
  83. package/server/services/router/request/index.ts +8 -0
  84. package/server/services/router/response/index.ts +24 -1
  85. package/server/services/router/response/page/document.tsx +5 -0
  86. package/server/services/router/response/page/index.tsx +10 -0
  87. package/agents/framework/AGENTS.md +0 -177
  88. package/server/services/auth/router/service.json +0 -6
  89. package/server/services/auth/service.json +0 -6
  90. package/server/services/cron/service.json +0 -6
  91. package/server/services/disks/drivers/local/service.json +0 -6
  92. package/server/services/disks/drivers/s3/service.json +0 -6
  93. package/server/services/disks/service.json +0 -6
  94. package/server/services/fetch/service.json +0 -7
  95. package/server/services/prisma/service.json +0 -6
  96. package/server/services/router/service.json +0 -6
  97. package/server/services/schema/router/service.json +0 -6
  98. package/server/services/schema/service.json +0 -6
  99. package/server/services/security/encrypt/aes/service.json +0 -6
@@ -36,7 +36,7 @@ export type TIndexedRouteDefinition = {
36
36
  hasSetup: boolean;
37
37
  };
38
38
 
39
- type TGeneratedClientRouteModuleOptions = { chunkId: string; filepath: string };
39
+ type TGeneratedClientRouteModuleOptions = { chunkId: string };
40
40
 
41
41
  type TWriteGeneratedRouteModuleOptions = {
42
42
  outputFilepath: string;
@@ -222,6 +222,9 @@ const getRouteOptionMetadata = (node: ts.ObjectLiteralExpression | undefined) =>
222
222
  return { optionKeys, normalizedOptionKeys, invalidOptionKeys, reservedOptionKeys };
223
223
  };
224
224
 
225
+ const buildInjectedRouteMetadata = (sourceFilepath: string, sourceLocation: TIndexedSourceLocation, extra: string[] = []) =>
226
+ `{ filepath: ${JSON.stringify(normalizeFilepath(sourceFilepath))}, sourceLocation: { line: ${sourceLocation.line}, column: ${sourceLocation.column} }${extra.length > 0 ? `, ${extra.join(', ')}` : ''} }`;
227
+
225
228
  const routeModuleExtensions = ['.ts', '.tsx', '.js', '.jsx'];
226
229
 
227
230
  const resolveRouteImport = (sourceFilepath: string, moduleSpecifier: string, routeSourceFilepaths?: Set<string>) => {
@@ -467,7 +470,10 @@ const buildClientRegisterArgs = (
467
470
  clientRoute: TGeneratedClientRouteModuleOptions,
468
471
  ) => {
469
472
  const { optionsArg, setupArg, renderArg } = getClientRouteSignature(sourceFile, definition);
470
- const injectedOptions = `{ id: ${JSON.stringify(clientRoute.chunkId)}, filepath: ${JSON.stringify(clientRoute.filepath)} }`;
473
+ const sourceLocation = getNodeLocation(sourceFile, definition.callExpression);
474
+ const injectedOptions = buildInjectedRouteMetadata(sourceFile.fileName, sourceLocation, [
475
+ `id: ${JSON.stringify(clientRoute.chunkId)}`,
476
+ ]);
471
477
 
472
478
  if (!optionsArg && !setupArg) {
473
479
  return [injectedOptions, getNodeText(sourceFile, renderArg)];
@@ -475,7 +481,7 @@ const buildClientRegisterArgs = (
475
481
 
476
482
  if (optionsArg && !setupArg) {
477
483
  return [
478
- `{ ...(${getNodeText(sourceFile, optionsArg)}), id: ${JSON.stringify(clientRoute.chunkId)}, filepath: ${JSON.stringify(clientRoute.filepath)} }`,
484
+ `{ ...(${getNodeText(sourceFile, optionsArg)}), ...${injectedOptions} }`,
479
485
  getNodeText(sourceFile, renderArg),
480
486
  ];
481
487
  }
@@ -485,12 +491,35 @@ const buildClientRegisterArgs = (
485
491
  }
486
492
 
487
493
  return [
488
- `{ ...(${getNodeText(sourceFile, optionsArg!)}), id: ${JSON.stringify(clientRoute.chunkId)}, filepath: ${JSON.stringify(clientRoute.filepath)} }`,
494
+ `{ ...(${getNodeText(sourceFile, optionsArg!)}), ...${injectedOptions} }`,
489
495
  getNodeText(sourceFile, setupArg!),
490
496
  getNodeText(sourceFile, renderArg),
491
497
  ];
492
498
  };
493
499
 
500
+ const buildServerRegisterArgs = (sourceFile: ts.SourceFile, definition: TRouteDefinition) => {
501
+ const sourceLocation = getNodeLocation(sourceFile, definition.callExpression);
502
+ const [targetArg, ...routeArgs] = [...definition.args];
503
+ const controllerArg = routeArgs[routeArgs.length - 1];
504
+ const optionsArg =
505
+ routeArgs.length >= 2 && ts.isObjectLiteralExpression(routeArgs[0]) ? routeArgs[0] : undefined;
506
+ const injectedOptions = buildInjectedRouteMetadata(sourceFile.fileName, sourceLocation);
507
+
508
+ if (routeArgs.length === 1) {
509
+ return [getNodeText(sourceFile, targetArg), injectedOptions, getNodeText(sourceFile, controllerArg)];
510
+ }
511
+
512
+ if (optionsArg) {
513
+ return [
514
+ getNodeText(sourceFile, targetArg),
515
+ `{ ...(${getNodeText(sourceFile, optionsArg)}), ...${injectedOptions} }`,
516
+ getNodeText(sourceFile, controllerArg),
517
+ ];
518
+ }
519
+
520
+ return [getNodeText(sourceFile, targetArg), ...routeArgs.map((arg) => getNodeText(sourceFile, arg))];
521
+ };
522
+
494
523
  const buildRegisterStatements = (
495
524
  sourceFile: ts.SourceFile,
496
525
  side: TRouteSide,
@@ -519,7 +548,7 @@ const buildRegisterStatements = (
519
548
  }
520
549
 
521
550
  return definitions.map((definition) => {
522
- const args = [...definition.args].map((arg) => getNodeText(sourceFile, arg));
551
+ const args = buildServerRegisterArgs(sourceFile, definition);
523
552
 
524
553
  return `${definition.serviceLocalName}.${definition.methodName}(${args.join(', ')});`;
525
554
  });
@@ -3,24 +3,30 @@ import type { RuleSetRule } from '@rspack/core';
3
3
 
4
4
  type TScriptRuleOptions = { app: App; side: TAppSide; dev: boolean };
5
5
 
6
- const shouldExcludeNodeModule = (filePath: string) => {
7
- if (!filePath.includes('node_modules')) return false;
6
+ const shouldExcludeNodeModule = (app: App, filePath: string) => {
7
+ const normalizedFilePath = filePath.replace(/\\/g, '/');
8
+ if (!normalizedFilePath.includes('/node_modules/')) return false;
8
9
 
9
- if (filePath.includes('node_modules/proteum') && !filePath.includes('node_modules/proteum/node_modules')) {
10
+ if (
11
+ normalizedFilePath.includes('/node_modules/proteum/') &&
12
+ !normalizedFilePath.includes('/node_modules/proteum/node_modules/')
13
+ ) {
10
14
  return false;
11
15
  }
12
16
 
17
+ if (app.isTranspileModuleFile(normalizedFilePath)) return false;
18
+
13
19
  return true;
14
20
  };
15
21
 
16
22
  const getSwcTarget = (side: TAppSide) => (side === 'client' ? 'es2022' : 'es2021');
17
23
 
18
- module.exports = ({ side, dev }: TScriptRuleOptions): RuleSetRule[] => {
24
+ module.exports = ({ app, side, dev }: TScriptRuleOptions): RuleSetRule[] => {
19
25
  return [
20
26
  {
21
27
  loader: 'builtin:swc-loader',
22
28
  type: 'javascript/auto',
23
- exclude: shouldExcludeNodeModule,
29
+ exclude: (filePath: string) => shouldExcludeNodeModule(app, filePath),
24
30
  options: {
25
31
  sourceMaps: true,
26
32
  jsc: {
@@ -141,12 +141,13 @@ export default class Compiler {
141
141
  if (!this.refreshingGeneratedArtifacts) {
142
142
  this.refreshingGeneratedArtifacts = (async () => {
143
143
  const services = generateServiceArtifacts();
144
- const controllers = generateControllerArtifacts();
144
+ const { connectedProjects, controllers } = await generateControllerArtifacts();
145
145
  const commands = generateCommandArtifacts();
146
146
  const { clientRoutes, serverRoutes, layouts } = generateRoutingArtifacts();
147
147
 
148
148
  writeCurrentProteumManifest({
149
149
  services,
150
+ connectedProjects,
150
151
  controllers,
151
152
  commands,
152
153
  routes: { client: clientRoutes, server: serverRoutes },
@@ -82,6 +82,7 @@ export default function createCompiler(
82
82
  const outputPath = app.outputPath(outputTarget);
83
83
  const installedCoreRoot = app.paths.root + '/node_modules/proteum';
84
84
  const frameworkRoots = [cli.paths.core.root, installedCoreRoot];
85
+ const transpileModuleDirectories = app.transpileModuleDirectories;
85
86
 
86
87
  const commonConfig = createCommonConfig(app, 'server', mode, outputTarget);
87
88
  const { aliases } = app.aliases.server.forWebpack({ modulesPath: app.paths.root + '/node_modules' });
@@ -140,6 +141,7 @@ export default function createCompiler(
140
141
  // Aliased modules
141
142
  app.aliases.server.containsAlias(request) ||
142
143
  // TODO: proteum.conf: compile: include
144
+ app.isTranspileModuleRequest(request) ||
143
145
  // Compile proteum modules
144
146
  request.startsWith('proteum') ||
145
147
  // React-based UI packages must pass through the alias layer on the server,
@@ -193,6 +195,7 @@ export default function createCompiler(
193
195
  ...frameworkRoots.map((rootPath) => rootPath + '/client'),
194
196
  ...frameworkRoots.map((rootPath) => rootPath + '/common'),
195
197
  ...frameworkRoots.map((rootPath) => rootPath + '/server'),
198
+ ...transpileModuleDirectories,
196
199
 
197
200
  // Complle 5HTP modules so they can refer to the framework instance and aliases
198
201
  // Temp disabled because compile issue on vercel
@@ -12,10 +12,15 @@ export const proteumCommandNames = [
12
12
  'typecheck',
13
13
  'lint',
14
14
  'check',
15
+ 'connect',
15
16
  'doctor',
16
17
  'explain',
18
+ 'diagnose',
19
+ 'perf',
17
20
  'trace',
18
21
  'command',
22
+ 'session',
23
+ 'verify',
19
24
  ] as const;
20
25
 
21
26
  export type TProteumCommandName = (typeof proteumCommandNames)[number];
@@ -48,7 +53,7 @@ export const proteumRecommendedFlow: TRow[] = [
48
53
  export const proteumCommandGroups: Array<{ title: string; names: TProteumCommandName[] }> = [
49
54
  { title: 'Daily workflow', names: ['dev', 'refresh', 'build'] },
50
55
  { title: 'Quality gates', names: ['typecheck', 'lint', 'check'] },
51
- { title: 'Manifest and contracts', names: ['doctor', 'explain', 'trace', 'command'] },
56
+ { title: 'Manifest and contracts', names: ['connect', 'doctor', 'explain', 'diagnose', 'perf', 'trace', 'command', 'session', 'verify'] },
52
57
  { title: 'Project scaffolding', names: ['init', 'create'] },
53
58
  ];
54
59
 
@@ -96,7 +101,7 @@ export const proteumCommands: Record<TProteumCommandName, TProteumCommandDoc> =
96
101
  ],
97
102
  notes: [
98
103
  'Page scaffolds write `client/pages/**/index.tsx` and default the route path from the logical target path unless `--route` is provided.',
99
- 'Service scaffolds create `server/services/**/index.ts`, `service.json`, a config export under `server/config/*.ts`, and then try to register the new root service in `server/index.ts`.',
104
+ 'Service scaffolds create `server/services/**/index.ts`, a typed config export under `server/config/*.ts`, and then try to register the new root service in `server/index.ts`.',
100
105
  'Use `--dry-run --json` when an agent needs a machine-readable plan before writing files.',
101
106
  ],
102
107
  status: 'experimental',
@@ -189,26 +194,47 @@ export const proteumCommands: Record<TProteumCommandName, TProteumCommandDoc> =
189
194
  notes: ['This command executes refresh, typecheck, then lint in that order.'],
190
195
  status: 'stable',
191
196
  },
197
+ connect: {
198
+ name: 'connect',
199
+ category: 'Manifest and contracts',
200
+ summary: 'Inspect connected-project config, cached contracts, and imported controllers.',
201
+ usage: 'proteum connect [--controllers] [--json] [--strict]',
202
+ bestFor:
203
+ 'Auditing the current app connect setup without manually stitching together refresh, explain, env inspection, and contract checks.',
204
+ examples: [
205
+ { description: 'Print a human-readable connected-project summary', command: 'proteum connect' },
206
+ { description: 'Include imported connected controllers', command: 'proteum connect --controllers' },
207
+ { description: 'Emit machine-readable connect output', command: 'proteum connect --json' },
208
+ { description: 'Fail when connect diagnostics exist', command: 'proteum connect --strict' },
209
+ ],
210
+ notes: [
211
+ 'Proteum refreshes generated typings before reading the connect manifest state.',
212
+ 'This command inspects explicit `proteum.config.ts` connected sources and URLs, cached `.proteum/connected/*.json` files, and imported connected controllers.',
213
+ '`--strict` is intended for CI or framework validation when connected contracts must be present and usable.',
214
+ ],
215
+ status: 'stable',
216
+ },
192
217
  doctor: {
193
218
  name: 'doctor',
194
219
  category: 'Manifest and contracts',
195
220
  summary: 'Inspect the generated Proteum manifest diagnostics.',
196
- usage: 'proteum doctor [--json] [--strict]',
221
+ usage: 'proteum doctor [--contracts] [--json] [--strict]',
197
222
  bestFor:
198
223
  'Auditing manifest warnings and errors, especially in CI or when route/controller generation behaves unexpectedly.',
199
224
  examples: [
200
225
  { description: 'Print a human-readable diagnostic summary', command: 'proteum doctor' },
226
+ { description: 'Inspect missing generated contracts and source files', command: 'proteum doctor --contracts' },
201
227
  { description: 'Fail if any diagnostics exist', command: 'proteum doctor --strict' },
202
228
  { description: 'Emit machine-readable diagnostics', command: 'proteum doctor --json' },
203
229
  ],
204
- notes: ['`--strict` is intended for CI and pre-release verification.'],
230
+ notes: ['`--strict` is intended for CI and pre-release verification.', '`--contracts` checks manifest-owned source files and expected generated artifacts on disk.'],
205
231
  status: 'stable',
206
232
  },
207
233
  explain: {
208
234
  name: 'explain',
209
235
  category: 'Manifest and contracts',
210
236
  summary: 'Explain the generated Proteum manifest.',
211
- usage: 'proteum explain [--all|--app|--conventions|--env|--services|--controllers|--commands|--routes|--layouts|--diagnostics] [--json]',
237
+ usage: 'proteum explain [owner <query>] [--all|--app|--conventions|--env|--connected|--services|--controllers|--commands|--routes|--layouts|--diagnostics] [--json]',
212
238
  bestFor:
213
239
  'Inspecting how source files became generated routes, controllers, commands, layouts, services, and diagnostics without reading compiler internals.',
214
240
  examples: [
@@ -217,11 +243,64 @@ export const proteumCommands: Record<TProteumCommandName, TProteumCommandDoc> =
217
243
  description: 'Inspect generated routes, controllers, and commands together',
218
244
  command: 'proteum explain --routes --controllers --commands',
219
245
  },
246
+ {
247
+ description: 'Inspect configured connected projects and imported controllers',
248
+ command: 'proteum explain --connected --controllers',
249
+ },
250
+ { description: 'Resolve the most likely manifest owner for a path or file', command: 'proteum explain owner /api/Auth/CurrentUser' },
220
251
  { description: 'Emit the selected manifest sections as JSON', command: 'proteum explain --routes --json' },
221
252
  ],
222
- notes: ['Legacy positional section selection remains supported, for example `proteum explain routes services`.'],
253
+ notes: [
254
+ 'Legacy positional section selection remains supported, for example `proteum explain routes services`.',
255
+ '`proteum explain owner <query>` ranks matching routes, controllers, services, commands, layouts, and diagnostics from the manifest.',
256
+ 'Connected projects are emitted from explicit `proteum.config.ts` `connect.<Namespace>.*` values plus the resolved connected contract.',
257
+ ],
223
258
  status: 'stable',
224
259
  },
260
+ diagnose: {
261
+ name: 'diagnose',
262
+ category: 'Manifest and contracts',
263
+ summary: 'Combine owner lookup, doctor output, contract checks, traces, and server logs into one report.',
264
+ usage: 'proteum diagnose [<query>] [--hit <path>] [--method <verb>] [--data-json <json>] [--session-email <email>] [--session-role <role>] [--port <port>|--url <baseUrl>] [--json]',
265
+ bestFor:
266
+ 'Collapsing the usual explain + doctor + trace + session + server log loop into one structured debugging pass.',
267
+ examples: [
268
+ { description: 'Diagnose the latest matching route trace on the running dev server', command: 'proteum diagnose /domains' },
269
+ { description: 'Arm a deep trace, mint an admin session, hit a protected page once, then diagnose it', command: 'proteum diagnose /godmode/users --hit /godmode/users --session-email god@example.com --session-role GOD' },
270
+ { description: 'Diagnose an API call with a JSON payload', command: 'proteum diagnose /api/Auth/CurrentUser --hit /api/Auth/CurrentUser --method POST --data-json "{}"' },
271
+ ],
272
+ notes: [
273
+ 'This command talks to the running app over the dev-only diagnostics, trace, and session endpoints.',
274
+ 'When `--hit` is omitted, Proteum diagnoses the latest matching request trace if one already exists.',
275
+ ],
276
+ status: 'experimental',
277
+ },
278
+ perf: {
279
+ name: 'perf',
280
+ category: 'Manifest and contracts',
281
+ summary: 'Inspect shared performance rollups built from live request traces on a running Proteum dev server.',
282
+ 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>] [--json]',
283
+ bestFor:
284
+ 'Finding the routes or controllers with the biggest response-time, CPU, SQL, render, and memory impact without manually stitching traces together.',
285
+ examples: [
286
+ { description: 'Show the hottest paths for the current day', command: 'proteum perf top --since today' },
287
+ { description: 'Inspect one traced request or the latest request for a path', command: 'proteum perf request /domains' },
288
+ {
289
+ description: 'Compare today against yesterday by route',
290
+ command: 'proteum perf compare --baseline yesterday --target today --group-by route',
291
+ },
292
+ {
293
+ description: 'Rank memory growth by controller over the last hour',
294
+ command: 'proteum perf memory --since 1h --group-by controller',
295
+ },
296
+ ],
297
+ notes: [
298
+ 'Perf data is derived from the same dev-only request trace buffer used by `proteum trace` and the profiler.',
299
+ 'Window values accept `1h`, `6h`, `24h`, `today`, `yesterday`, or an ISO timestamp.',
300
+ 'Older traces captured before the perf runtime metrics were added may not include CPU or memory deltas.',
301
+ ],
302
+ status: 'experimental',
303
+ },
225
304
  trace: {
226
305
  name: 'trace',
227
306
  category: 'Manifest and contracts',
@@ -274,11 +353,61 @@ export const proteumCommands: Record<TProteumCommandName, TProteumCommandDoc> =
274
353
  ],
275
354
  status: 'experimental',
276
355
  },
356
+ session: {
357
+ name: 'session',
358
+ category: 'Manifest and contracts',
359
+ summary: 'Mint a dev-only auth session token and cookie payload for a known user.',
360
+ usage: 'proteum session <email> [--role <role>] [--port <port>|--url <baseUrl>] [--json]',
361
+ bestFor:
362
+ 'Starting browser or API automation from an authenticated state without driving the login UI, while still using the app-configured auth service.',
363
+ examples: [
364
+ {
365
+ description: 'Mint an admin session for a running dev server',
366
+ command: 'proteum session admin@example.com --role ADMIN --port 3101',
367
+ },
368
+ {
369
+ description: 'Mint a GOD session for unique.domains and print machine-readable cookie data',
370
+ command: 'proteum session god@example.com --role GOD --json',
371
+ },
372
+ ],
373
+ notes: [
374
+ 'Sessions are available only in dev mode and use the auth service registered on the current app router.',
375
+ 'You must provide the target user email explicitly; Proteum does not guess your admin account universally across apps.',
376
+ 'The command returns a token plus Playwright-ready cookie JSON so agents can inject the session into a browser context directly.',
377
+ 'Without `--port` or `--url`, Proteum refreshes generated artifacts, builds the dev output, starts a temporary local dev server, creates the session, prints the payload, and exits.',
378
+ ],
379
+ status: 'experimental',
380
+ },
381
+ verify: {
382
+ name: 'verify',
383
+ category: 'Manifest and contracts',
384
+ summary: 'Validate framework changes against CrossPath, Unique Domains Product, and Unique Domains Website.',
385
+ usage: 'proteum verify [framework-change] [--crosspath <path>] [--product <path>] [--website <path>] [--crosspath-port <port>] [--product-port <port>] [--website-port <port>] [--route <path>] [--json]',
386
+ bestFor:
387
+ 'Framework-repo smoke validation when Proteum changes must be exercised against CrossPath and the website -> product connected-project flow before review.',
388
+ examples: [
389
+ {
390
+ description: 'Run the default framework smoke verification against the reference apps',
391
+ command: 'proteum verify framework-change',
392
+ },
393
+ {
394
+ description: 'Load a specific route in the website validation pass',
395
+ command: 'proteum verify framework-change --route /domains',
396
+ },
397
+ ],
398
+ notes: [
399
+ 'When a reference app is already running on the requested port, Proteum reuses it instead of spawning a new `proteum dev` process.',
400
+ 'When Proteum spawns the website reference app, it sets the env values consumed by the website `proteum.config.ts` for the product internal and public URLs.',
401
+ 'This command is intended for the framework repo and will be most useful where the reference app paths exist locally.',
402
+ ],
403
+ status: 'experimental',
404
+ },
277
405
  };
278
406
 
279
407
  export const isLikelyProteumAppRoot = (workdir: string) =>
280
408
  fs.existsSync(path.join(workdir, 'package.json')) &&
281
- fs.existsSync(path.join(workdir, 'identity.yaml')) &&
409
+ fs.existsSync(path.join(workdir, 'identity.config.ts')) &&
410
+ fs.existsSync(path.join(workdir, 'proteum.config.ts')) &&
282
411
  fs.existsSync(path.join(workdir, 'client')) &&
283
412
  fs.existsSync(path.join(workdir, 'server'));
284
413
 
@@ -18,24 +18,30 @@ export const renderDevSession = async ({
18
18
  appRoot,
19
19
  routerPort,
20
20
  devEventPort,
21
+ connectedProjects,
22
+ proteumVersion,
21
23
  }: {
22
24
  appName: string;
23
25
  appRoot: string;
24
26
  routerPort: number;
25
27
  devEventPort: number;
28
+ connectedProjects?: Array<{ namespace: string; urlInternal: string }>;
29
+ proteumVersion: string;
26
30
  }) =>
27
31
  [
28
32
  await renderInk(({ Box, Text }) => {
29
33
  const createElement = React.createElement;
30
34
  const wordmark = ProteumWordmark.map((line) =>
31
- createElement(Text, { key: line, bold: true, color: 'cyan' }, line),
35
+ createElement(Text, { key: line, bold: true, color: 'blue' }, line),
32
36
  );
37
+ const versionLabel = proteumVersion ? `v${proteumVersion}` : '';
33
38
 
34
39
  return createElement(
35
40
  Box,
36
- { borderStyle: 'round', borderColor: 'cyan', paddingX: 2, paddingY: 0, flexDirection: 'column' },
37
- createElement(Text, { bold: true, backgroundColor: 'cyan', color: 'black' }, ' WELCOME TO '),
41
+ { borderStyle: 'round', borderColor: 'blue', paddingX: 2, paddingY: 0, flexDirection: 'column' },
42
+ createElement(Text, { bold: true, backgroundColor: 'blue', color: 'white' }, ' WELCOME TO '),
38
43
  createElement(Box, { flexDirection: 'column' }, ...wordmark),
44
+ versionLabel ? createElement(Text, { bold: true, color: 'blue' }, versionLabel) : null,
39
45
  createElement(Text, { dimColor: true }, ProteumTagline),
40
46
  );
41
47
  }),
@@ -45,6 +51,14 @@ export const renderDevSession = async ({
45
51
  { label: 'root', value: appRoot },
46
52
  { label: 'router', value: `http://localhost:${routerPort}` },
47
53
  { label: 'hmr', value: `http://localhost:${devEventPort}/__proteum_hmr` },
54
+ ...(connectedProjects && connectedProjects.length > 0
55
+ ? connectedProjects.map((connectedProject) => ({
56
+ label: `connect ${connectedProject.namespace}`,
57
+ value: connectedProject.urlInternal,
58
+ }))
59
+ : []),
60
+ { label: 'diagnose', value: `proteum diagnose / --port ${routerPort}` },
61
+ { label: 'perf', value: `proteum perf top --port ${routerPort}` },
48
62
  { label: 'trace', value: `proteum trace latest --port ${routerPort}` },
49
63
  { label: 'trace deep', value: `proteum trace arm --capture deep --port ${routerPort}` },
50
64
  { label: 'hotkeys', value: 'Ctrl+R reload, Ctrl+C stop' },
@@ -57,10 +71,12 @@ export const renderServerReadyBanner = async ({
57
71
  appName,
58
72
  publicUrl,
59
73
  routerPort,
74
+ connectedProjectsCount,
60
75
  }: {
61
76
  appName: string;
62
77
  publicUrl: string;
63
78
  routerPort: number;
79
+ connectedProjectsCount?: number;
64
80
  }) =>
65
81
  renderInk(({ Box, Text }) => {
66
82
  const createElement = React.createElement;
@@ -68,10 +84,19 @@ export const renderServerReadyBanner = async ({
68
84
  return createElement(
69
85
  Box,
70
86
  { borderStyle: 'round', borderColor: 'green', paddingX: 2, paddingY: 0, flexDirection: 'column' },
71
- createElement(Text, { bold: true, backgroundColor: 'green', color: 'black' }, ' SERVER READY '),
87
+ createElement(Text, { bold: true, backgroundColor: 'green', color: 'white' }, ' SERVER READY '),
72
88
  createElement(Text, { bold: true, color: 'green' }, appName),
73
89
  createElement(Text, { bold: true }, publicUrl),
74
90
  createElement(Text, { dimColor: true }, 'SSR server is listening for requests and hot reloads.'),
91
+ connectedProjectsCount
92
+ ? createElement(
93
+ Text,
94
+ { dimColor: true },
95
+ `Connected projects: ${connectedProjectsCount}`,
96
+ )
97
+ : null,
98
+ createElement(Text, { dimColor: true }, `Diagnose /: proteum diagnose / --port ${routerPort}`),
99
+ createElement(Text, { dimColor: true }, `Perf top: proteum perf top --port ${routerPort}`),
75
100
  createElement(Text, { dimColor: true }, `Trace latest: proteum trace latest --port ${routerPort}`),
76
101
  );
77
102
  });
@@ -82,8 +107,8 @@ export const renderDevShutdownBanner = async () =>
82
107
 
83
108
  return createElement(
84
109
  Box,
85
- { borderStyle: 'round', borderColor: 'yellow', paddingX: 2, paddingY: 0, flexDirection: 'column' },
86
- createElement(Text, { bold: true, backgroundColor: 'yellow', color: 'black' }, ' SHUTTING DOWN '),
87
- createElement(Text, { bold: true, color: 'yellow' }, 'Thank you for developping with Proteum'),
110
+ { borderStyle: 'round', borderColor: 'blue', paddingX: 2, paddingY: 0, flexDirection: 'column' },
111
+ createElement(Text, { bold: true, backgroundColor: 'blue', color: 'white' }, ' SHUTTING DOWN '),
112
+ createElement(Text, { bold: true, color: 'blue' }, 'Thank you for developping with Proteum'),
88
113
  );
89
114
  });