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
@@ -10,7 +10,7 @@ class InitCommand extends ProteumCommand {
10
10
  public static usage = buildUsage('init');
11
11
 
12
12
  public name = Option.String('--name', { description: 'Human-readable app name.' });
13
- public description = Option.String('--description', { description: 'App description used in identity.yaml and package.json.' });
13
+ public description = Option.String('--description', { description: 'App description used in identity.config.ts and package.json.' });
14
14
  public identifier = Option.String('--identifier', { description: 'Application class and identity identifier.' });
15
15
  public port = Option.String('--port', { description: 'Default local router port used in .env.' });
16
16
  public url = Option.String('--url', { description: 'Default absolute URL used in .env.' });
@@ -179,19 +179,43 @@ class CheckCommand extends ProteumCommand {
179
179
  }
180
180
  }
181
181
 
182
+ class ConnectCommand extends ProteumCommand {
183
+ public static paths = [['connect']];
184
+
185
+ public static usage = buildUsage('connect');
186
+
187
+ public controllers = Option.Boolean('--controllers', false, {
188
+ description: 'Include imported connected controllers in the output.',
189
+ });
190
+ public json = Option.Boolean('--json', false, { description: 'Print JSON output.' });
191
+ public strict = Option.Boolean('--strict', false, { description: 'Exit with failure if any connect diagnostics exist.' });
192
+ public legacyArgs = Option.Rest();
193
+
194
+ public async execute() {
195
+ const args = { controllers: this.controllers, json: this.json, strict: this.strict } satisfies TArgsObject;
196
+
197
+ applyLegacyBooleanArgs('connect', this.legacyArgs, ['controllers', 'json', 'strict'], args);
198
+ this.setCliArgs(args);
199
+ await runCommandModule(() => import('../commands/connect'));
200
+ }
201
+ }
202
+
182
203
  class DoctorCommand extends ProteumCommand {
183
204
  public static paths = [['doctor']];
184
205
 
185
206
  public static usage = buildUsage('doctor');
186
207
 
208
+ public contracts = Option.Boolean('--contracts', false, {
209
+ description: 'Run contract-focused diagnostics for generated artifacts and manifest-owned source files.',
210
+ });
187
211
  public json = Option.Boolean('--json', false, { description: 'Print JSON output.' });
188
212
  public strict = Option.Boolean('--strict', false, { description: 'Exit with failure if any diagnostics exist.' });
189
213
  public legacyArgs = Option.Rest();
190
214
 
191
215
  public async execute() {
192
- const args = { json: this.json, strict: this.strict } satisfies TArgsObject;
216
+ const args = { contracts: this.contracts, json: this.json, strict: this.strict } satisfies TArgsObject;
193
217
 
194
- applyLegacyBooleanArgs('doctor', this.legacyArgs, ['json', 'strict'], args);
218
+ applyLegacyBooleanArgs('doctor', this.legacyArgs, ['contracts', 'json', 'strict'], args);
195
219
  this.setCliArgs(args);
196
220
  await runCommandModule(() => import('../commands/doctor'));
197
221
  }
@@ -207,6 +231,7 @@ class ExplainCommand extends ProteumCommand {
207
231
  public app = Option.Boolean('--app', false, { description: 'Include the app section.' });
208
232
  public conventions = Option.Boolean('--conventions', false, { description: 'Include the conventions section.' });
209
233
  public env = Option.Boolean('--env', false, { description: 'Include the env section.' });
234
+ public connected = Option.Boolean('--connected', false, { description: 'Include the connected-projects section.' });
210
235
  public services = Option.Boolean('--services', false, { description: 'Include the services section.' });
211
236
  public controllers = Option.Boolean('--controllers', false, { description: 'Include the controllers section.' });
212
237
  public commands = Option.Boolean('--commands', false, { description: 'Include the commands section.' });
@@ -215,14 +240,25 @@ class ExplainCommand extends ProteumCommand {
215
240
  public diagnostics = Option.Boolean('--diagnostics', false, {
216
241
  description: 'Include the diagnostics section.',
217
242
  });
218
- public legacyArgs = Option.Rest();
243
+ public args = Option.Rest();
219
244
 
220
245
  public async execute() {
246
+ const [mode = '', ...restArgs] = this.args;
247
+ if (mode === 'owner') {
248
+ this.setCliArgs({
249
+ json: this.json,
250
+ ownerQuery: restArgs.join(' ').trim(),
251
+ });
252
+ await runCommandModule(() => import('../commands/explain'));
253
+ return;
254
+ }
255
+
221
256
  const args = {
222
257
  json: this.json,
223
258
  all: this.all,
224
259
  app: this.app,
225
260
  conventions: this.conventions,
261
+ connected: this.connected,
226
262
  env: this.env,
227
263
  services: this.services,
228
264
  controllers: this.controllers,
@@ -234,8 +270,8 @@ class ExplainCommand extends ProteumCommand {
234
270
 
235
271
  applyLegacyBooleanArgs(
236
272
  'explain',
237
- this.legacyArgs,
238
- ['json', 'all', 'app', 'conventions', 'env', 'services', 'controllers', 'commands', 'routes', 'layouts', 'diagnostics'],
273
+ this.args,
274
+ ['json', 'all', 'app', 'conventions', 'env', 'connected', 'services', 'controllers', 'commands', 'routes', 'layouts', 'diagnostics'],
239
275
  args,
240
276
  );
241
277
  this.setCliArgs(args);
@@ -296,6 +332,147 @@ class CommandCommand extends ProteumCommand {
296
332
  }
297
333
  }
298
334
 
335
+ class SessionCommand extends ProteumCommand {
336
+ public static paths = [['session']];
337
+
338
+ public static usage = buildUsage('session');
339
+
340
+ public role = Option.String('--role', { description: 'Require the resolved user to have the given role.' });
341
+ public port = Option.String('--port', { description: 'Target an existing dev server on the given port.' });
342
+ public url = Option.String('--url', { description: 'Target an existing dev server at the given base URL.' });
343
+ public json = Option.Boolean('--json', false, { description: 'Print JSON output.' });
344
+ public args = Option.Rest();
345
+
346
+ public async execute() {
347
+ const [email = ''] = this.args;
348
+
349
+ this.setCliArgs({
350
+ email,
351
+ role: this.role ?? '',
352
+ port: this.port ?? '',
353
+ url: this.url ?? '',
354
+ json: this.json,
355
+ });
356
+
357
+ await runCommandModule(() => import('../commands/session'));
358
+ }
359
+ }
360
+
361
+ class DiagnoseCommand extends ProteumCommand {
362
+ public static paths = [['diagnose']];
363
+
364
+ public static usage = buildUsage('diagnose');
365
+
366
+ public port = Option.String('--port', { description: 'Target an existing dev server on the given port.' });
367
+ public url = Option.String('--url', { description: 'Target an existing dev server at the given base URL.' });
368
+ public json = Option.Boolean('--json', false, { description: 'Print JSON output.' });
369
+ public hit = Option.String('--hit', { description: 'Issue one HTTP request before diagnosing. Defaults to the target path when it starts with /.' });
370
+ public method = Option.String('--method', { description: 'HTTP method used with `--hit`.' });
371
+ public dataJson = Option.String('--data-json', { description: 'JSON request body used with `--hit`.' });
372
+ public sessionEmail = Option.String('--session-email', {
373
+ description: 'Mint a dev session before `--hit` and attach the returned cookie.',
374
+ });
375
+ public sessionRole = Option.String('--session-role', { description: 'Require the dev session user to have this role.' });
376
+ public capture = Option.String('--capture', { description: 'Trace capture mode armed before `--hit`.' });
377
+ public logsLevel = Option.String('--logs-level', { description: 'Minimum server log level included in the diagnose response.' });
378
+ public logsLimit = Option.String('--logs-limit', { description: 'Maximum number of server log lines included in the diagnose response.' });
379
+ public args = Option.Rest();
380
+
381
+ public async execute() {
382
+ const [target = ''] = this.args;
383
+
384
+ this.setCliArgs({
385
+ capture: this.capture ?? '',
386
+ dataJson: this.dataJson ?? '',
387
+ hit: this.hit ?? '',
388
+ json: this.json,
389
+ logsLevel: this.logsLevel ?? '',
390
+ logsLimit: this.logsLimit ?? '',
391
+ method: this.method ?? '',
392
+ port: this.port ?? '',
393
+ sessionEmail: this.sessionEmail ?? '',
394
+ sessionRole: this.sessionRole ?? '',
395
+ target,
396
+ url: this.url ?? '',
397
+ });
398
+
399
+ await runCommandModule(() => import('../commands/diagnose'));
400
+ }
401
+ }
402
+
403
+ class PerfCommand extends ProteumCommand {
404
+ public static paths = [['perf']];
405
+
406
+ public static usage = buildUsage('perf');
407
+
408
+ public port = Option.String('--port', { description: 'Target an existing dev server on the given port.' });
409
+ public url = Option.String('--url', { description: 'Target an existing dev server at the given base URL.' });
410
+ public json = Option.Boolean('--json', false, { description: 'Print JSON output.' });
411
+ public since = Option.String('--since', { description: 'Window used by `top` and `memory`, for example `today`, `yesterday`, or `1h`.' });
412
+ public baseline = Option.String('--baseline', { description: 'Baseline window used by `compare`.' });
413
+ public target = Option.String('--target', { description: 'Target window used by `compare`.' });
414
+ public groupBy = Option.String('--group-by', { description: 'Aggregate by `path`, `route`, or `controller`.' });
415
+ public limit = Option.String('--limit', { description: 'Maximum number of rows to print.' });
416
+ public args = Option.Rest();
417
+
418
+ public async execute() {
419
+ const [action = 'top', target = ''] = this.args;
420
+
421
+ this.setCliArgs({
422
+ action,
423
+ baseline: this.baseline ?? '',
424
+ groupBy: this.groupBy ?? '',
425
+ json: this.json,
426
+ limit: this.limit ?? '',
427
+ port: this.port ?? '',
428
+ since: this.since ?? '',
429
+ target,
430
+ targetWindow: this.target ?? '',
431
+ url: this.url ?? '',
432
+ });
433
+
434
+ await runCommandModule(() => import('../commands/perf'));
435
+ }
436
+ }
437
+
438
+ class VerifyCommand extends ProteumCommand {
439
+ public static paths = [['verify']];
440
+
441
+ public static usage = buildUsage('verify');
442
+
443
+ public json = Option.Boolean('--json', false, { description: 'Print JSON output.' });
444
+ public crosspath = Option.String('--crosspath', { description: 'Override the CrossPath reference app path.' });
445
+ public product = Option.String('--product', { description: 'Override the Unique Domains Product reference app path.' });
446
+ public website = Option.String('--website', { description: 'Override the Unique Domains Website reference app path.' });
447
+ public crosspathPort = Option.String('--crosspath-port', { description: 'Port used for the CrossPath validation server.' });
448
+ public productPort = Option.String('--product-port', {
449
+ description: 'Port used for the Unique Domains Product validation server.',
450
+ });
451
+ public websitePort = Option.String('--website-port', {
452
+ description: 'Port used for the Unique Domains Website validation server.',
453
+ });
454
+ public route = Option.String('--route', { description: 'Route loaded in both apps during validation.' });
455
+ public args = Option.Rest();
456
+
457
+ public async execute() {
458
+ const [action = 'framework-change'] = this.args;
459
+
460
+ this.setCliArgs({
461
+ action,
462
+ crosspath: this.crosspath ?? '',
463
+ crosspathPort: this.crosspathPort ?? '',
464
+ json: this.json,
465
+ product: this.product ?? '',
466
+ productPort: this.productPort ?? '',
467
+ route: this.route ?? '',
468
+ website: this.website ?? '',
469
+ websitePort: this.websitePort ?? '',
470
+ });
471
+
472
+ await runCommandModule(() => import('../commands/verify'));
473
+ }
474
+ }
475
+
299
476
  export const registeredCommands = {
300
477
  init: InitCommand,
301
478
  create: CreateCommand,
@@ -305,10 +482,15 @@ export const registeredCommands = {
305
482
  typecheck: TypecheckCommand,
306
483
  lint: LintCommand,
307
484
  check: CheckCommand,
485
+ connect: ConnectCommand,
308
486
  doctor: DoctorCommand,
309
487
  explain: ExplainCommand,
488
+ diagnose: DiagnoseCommand,
489
+ perf: PerfCommand,
310
490
  trace: TraceCommand,
311
491
  command: CommandCommand,
492
+ session: SessionCommand,
493
+ verify: VerifyCommand,
312
494
  } as const;
313
495
 
314
496
  export const createCli = (version: string) => {
@@ -329,10 +511,15 @@ export const createCli = (version: string) => {
329
511
  clipanion.register(TypecheckCommand);
330
512
  clipanion.register(LintCommand);
331
513
  clipanion.register(CheckCommand);
514
+ clipanion.register(ConnectCommand);
332
515
  clipanion.register(DoctorCommand);
333
516
  clipanion.register(ExplainCommand);
517
+ clipanion.register(DiagnoseCommand);
518
+ clipanion.register(PerfCommand);
334
519
  clipanion.register(TraceCommand);
335
520
  clipanion.register(CommandCommand);
521
+ clipanion.register(SessionCommand);
522
+ clipanion.register(VerifyCommand);
336
523
 
337
524
  return clipanion;
338
525
  };
@@ -1,10 +1,10 @@
1
1
  import fs from 'fs-extra';
2
2
  import path from 'path';
3
3
  import slugify from 'slugify';
4
- import yaml from 'yaml';
5
4
  import { UsageError } from 'clipanion';
6
5
 
7
6
  import cli from '..';
7
+ import { loadApplicationIdentityConfig } from '../../common/applicationConfigLoader';
8
8
  import { ensureProjectAgentSymlinks } from '../utils/agents';
9
9
  import { runProcess } from '../utils/runProcess';
10
10
  import {
@@ -18,6 +18,7 @@ import {
18
18
  createInitSummary,
19
19
  createPackageJsonTemplate,
20
20
  createPageTemplate,
21
+ createProteumConfigTemplate,
21
22
  createRouteTemplate,
22
23
  createRouterConfigTemplate,
23
24
  createServerIndexTemplate,
@@ -133,16 +134,16 @@ const defaultRouteFromSegments = (segments: string[]) => {
133
134
  const resolveRootServiceLeaf = (segments: string[]) => segments[segments.length - 1];
134
135
 
135
136
  const readIdentityConfig = (appRoot: string): TIdentityConfig => {
136
- const identityFilepath = path.join(appRoot, 'identity.yaml');
137
+ const identityFilepath = path.join(appRoot, 'identity.config.ts');
137
138
  if (!fs.existsSync(identityFilepath)) {
138
- throw new UsageError(`Missing identity.yaml in ${appRoot}. Run \`proteum init\` first or target a Proteum app root.`);
139
+ throw new UsageError(`Missing identity.config.ts in ${appRoot}. Run \`proteum init\` first or target a Proteum app root.`);
139
140
  }
140
141
 
141
- const parsed = yaml.parse(fs.readFileSync(identityFilepath, 'utf8')) as Partial<TIdentityConfig> | null;
142
- const identifier = typeof parsed?.identifier === 'string' ? parsed.identifier.trim() : '';
143
- const name = typeof parsed?.name === 'string' ? parsed.name.trim() : '';
142
+ const parsed = loadApplicationIdentityConfig(appRoot);
143
+ const identifier = typeof parsed.identifier === 'string' ? parsed.identifier.trim() : '';
144
+ const name = typeof parsed.name === 'string' ? parsed.name.trim() : '';
144
145
 
145
- if (!identifier) throw new UsageError(`identity.yaml in ${appRoot} is missing a valid "identifier" field.`);
146
+ if (!identifier) throw new UsageError(`identity.config.ts in ${appRoot} is missing a valid "identifier" field.`);
146
147
 
147
148
  return {
148
149
  identifier,
@@ -151,7 +152,7 @@ const readIdentityConfig = (appRoot: string): TIdentityConfig => {
151
152
  };
152
153
 
153
154
  const assertProteumAppRoot = (appRoot: string) => {
154
- const expectedEntries = ['package.json', 'identity.yaml', 'client', 'server'];
155
+ const expectedEntries = ['package.json', 'identity.config.ts', 'proteum.config.ts', 'client', 'server'];
155
156
  const missing = expectedEntries.filter((entry) => !fs.existsSync(path.join(appRoot, entry)));
156
157
  if (missing.length > 0) {
157
158
  throw new UsageError(
@@ -459,21 +460,9 @@ const createServicePlan = ({
459
460
  const configExportName = `${configFileBase}Config`;
460
461
  const relativeServiceDir = path.join('server', 'services', ...segments);
461
462
  const relativeServiceFilepath = path.join(relativeServiceDir, 'index.ts');
462
- const relativeServiceConfigFilepath = path.join(relativeServiceDir, 'service.json');
463
463
  const relativeConfigFilepath = path.join('server', 'config', `${configFileBase}.ts`);
464
464
  const propertyName = serviceImportName;
465
465
 
466
- const serviceJson = JSON.stringify(
467
- {
468
- id: `${appIdentifier}/${serviceImportName}`,
469
- name: `${appIdentifier}${serviceImportName}`,
470
- parent: 'app',
471
- dependences: [],
472
- },
473
- null,
474
- 4,
475
- ) + '\n';
476
-
477
466
  return {
478
467
  files: [
479
468
  {
@@ -483,10 +472,6 @@ const createServicePlan = ({
483
472
  className,
484
473
  }),
485
474
  },
486
- {
487
- relativePath: relativeServiceConfigFilepath,
488
- content: serviceJson,
489
- },
490
475
  {
491
476
  relativePath: relativeConfigFilepath,
492
477
  content: createServiceConfigTemplate({
@@ -628,13 +613,17 @@ const createInitFilePlans = (config: TScaffoldInitConfig): TScaffoldFilePlan[] =
628
613
  }),
629
614
  },
630
615
  {
631
- relativePath: 'identity.yaml',
616
+ relativePath: 'identity.config.ts',
632
617
  content: createIdentityTemplate({
633
618
  appName: config.name,
634
619
  appIdentifier: config.identifier,
635
620
  appDescription: config.description,
636
621
  }),
637
622
  },
623
+ {
624
+ relativePath: 'proteum.config.ts',
625
+ content: createProteumConfigTemplate(),
626
+ },
638
627
  {
639
628
  relativePath: '.env',
640
629
  content: createEnvTemplate({
@@ -227,6 +227,8 @@ export const createServerTsconfigTemplate = () => `{
227
227
  },
228
228
  "include": [
229
229
  ".",
230
+ "../identity.config.ts",
231
+ "../proteum.config.ts",
230
232
  "../var/typings",
231
233
  "../node_modules/proteum/types/global",
232
234
  "../.proteum/server/services.d.ts",
@@ -236,11 +238,12 @@ export const createServerTsconfigTemplate = () => `{
236
238
  `;
237
239
 
238
240
  export const createGitignoreTemplate = () => `node_modules
239
- .proteum
240
- .cache
241
- bin
242
- dev
243
- var
241
+ /.proteum
242
+ /.cache
243
+ /bin
244
+ /dev
245
+ /var
246
+ /proteum.connected.json
244
247
  .env
245
248
  `;
246
249
 
@@ -248,6 +251,7 @@ export const createEnvTemplate = ({ port, url }: { port: number; url: string })
248
251
  ENV_PROFILE=dev
249
252
  PORT=${port}
250
253
  URL=${url}
254
+ URL_INTERNAL=${url}
251
255
 
252
256
  # Optional trace settings
253
257
  # TRACE_ENABLE=true
@@ -306,28 +310,38 @@ export const createIdentityTemplate = ({
306
310
  appName: string;
307
311
  appIdentifier: string;
308
312
  appDescription: string;
309
- }) => `name: ${appName}
310
- identifier: ${appIdentifier}
311
- description: ${JSON.stringify(appDescription)}
312
-
313
- author:
314
- name: ${appName}
315
- url: localhost
316
- email: team@example.com
317
-
318
- social:
319
-
320
- language: en
321
- locale: en-US
322
- maincolor: white
323
- iconsPack: light
324
-
325
- web:
326
- title: ${JSON.stringify(appName)}
327
- titleSuffix: ${JSON.stringify(appName)}
328
- fullTitle: ${JSON.stringify(appName)}
329
- description: ${JSON.stringify(appDescription)}
330
- version: 0.0.1
313
+ }) => `import { Application } from 'proteum/config';
314
+
315
+ export default Application.identity({
316
+ name: ${JSON.stringify(appName)},
317
+ identifier: ${JSON.stringify(appIdentifier)},
318
+ description: ${JSON.stringify(appDescription)},
319
+ author: {
320
+ name: ${JSON.stringify(appName)},
321
+ url: 'localhost',
322
+ email: 'team@example.com',
323
+ },
324
+ social: {},
325
+ language: 'en',
326
+ locale: 'en-US',
327
+ maincolor: 'white',
328
+ iconsPack: 'light',
329
+ web: {
330
+ title: ${JSON.stringify(appName)},
331
+ titleSuffix: ${JSON.stringify(appName)},
332
+ fullTitle: ${JSON.stringify(appName)},
333
+ description: ${JSON.stringify(appDescription)},
334
+ version: '0.0.1',
335
+ },
336
+ });
337
+ `;
338
+
339
+ export const createProteumConfigTemplate = () => `import { Application } from 'proteum/config';
340
+
341
+ export default Application.setup({
342
+ transpile: [],
343
+ connect: {},
344
+ });
331
345
  `;
332
346
 
333
347
  export const createInitSummary = (result: TScaffoldResult, config: TScaffoldInitConfig) => ({
@@ -19,10 +19,12 @@ type TAgentLinkDefinition = { projectPath: string; sourcePath: string; ensurePar
19
19
  - CONSTANTS
20
20
  ----------------------------------*/
21
21
 
22
- // Project-local AGENTS entrypoints mapped to their framework-owned source files.
22
+ // Project-local instruction entrypoints mapped to their canonical shipped source files.
23
23
  const projectAgentLinkDefinitions: TAgentLinkDefinition[] = [
24
24
  { projectPath: 'AGENTS.md', sourcePath: 'AGENTS.md' },
25
25
  { projectPath: 'CODING_STYLE.md', sourcePath: 'CODING_STYLE.md' },
26
+ { projectPath: 'diagnostics.md', sourcePath: 'diagnostics.md' },
27
+ { projectPath: 'optimizations.md', sourcePath: 'optimizations.md' },
26
28
  { projectPath: path.join('client', 'AGENTS.md'), sourcePath: path.join('client', 'AGENTS.md') },
27
29
  { projectPath: path.join('client', 'pages', 'AGENTS.md'), sourcePath: path.join('client', 'pages', 'AGENTS.md') },
28
30
  {
@@ -81,7 +83,7 @@ function ensureSymlinks(appRoot: string, linkDefinitions: TAgentLinkDefinition[]
81
83
 
82
84
  const sourceFilepath = linkDefinition.sourcePath;
83
85
  if (!fs.existsSync(sourceFilepath)) {
84
- throw new Error(`Missing framework asset: ${sourceFilepath}`);
86
+ throw new Error(`Missing project instruction asset: ${sourceFilepath}`);
85
87
  }
86
88
 
87
89
  const symlinkTarget = path.relative(projectParentDir, sourceFilepath);
@@ -23,7 +23,15 @@ class KeyboardCommands {
23
23
  }
24
24
 
25
25
  private listen() {
26
+ if (!process.stdin) return;
27
+
26
28
  readline.emitKeypressEvents(process.stdin);
29
+
30
+ if (!process.stdin.isTTY || typeof process.stdin.setRawMode !== 'function') {
31
+ logVerbose('Keyboard shortcuts disabled because stdin is not an interactive TTY.');
32
+ return;
33
+ }
34
+
27
35
  process.stdin.setRawMode(true);
28
36
  process.stdin.on('keypress', async (chunk: string, key: Key) => {
29
37
  let str = key.name;
@@ -0,0 +1,66 @@
1
+ import React from 'react';
2
+ import type { ApexOptions } from 'apexcharts';
3
+
4
+ const readErrorMessage = (error: unknown) => (error instanceof Error ? error.message : String(error));
5
+
6
+ export default function ApexChart({
7
+ emptyLabel = 'No chart data available.',
8
+ options,
9
+ }: {
10
+ emptyLabel?: string;
11
+ options?: ApexOptions;
12
+ }) {
13
+ const mountRef = React.useRef<HTMLDivElement | null>(null);
14
+ const [errorMessage, setErrorMessage] = React.useState<string>();
15
+
16
+ React.useEffect(() => {
17
+ const target = mountRef.current;
18
+ if (!target || !options) return;
19
+
20
+ let disposed = false;
21
+ let chart: { destroy: () => void } | undefined;
22
+
23
+ target.innerHTML = '';
24
+ setErrorMessage(undefined);
25
+
26
+ void (async () => {
27
+ try {
28
+ const module = await import('apexcharts');
29
+ if (disposed || !mountRef.current) return;
30
+
31
+ const ApexCharts = module.default;
32
+ chart = new ApexCharts(mountRef.current, options);
33
+ await chart.render();
34
+ } catch (error) {
35
+ if (!disposed) setErrorMessage(readErrorMessage(error));
36
+ }
37
+ })();
38
+
39
+ return () => {
40
+ disposed = true;
41
+ chart?.destroy();
42
+ target.innerHTML = '';
43
+ };
44
+ }, [options]);
45
+
46
+ if (!options) return <div className="proteum-profiler__empty">{emptyLabel}</div>;
47
+
48
+ if (errorMessage) {
49
+ return (
50
+ <div className="proteum-profiler__chartMount">
51
+ <div className="proteum-profiler__row">
52
+ <div className="proteum-profiler__rowHeader">
53
+ <strong>Chart error</strong>
54
+ </div>
55
+ <div className="proteum-profiler__mono">{errorMessage}</div>
56
+ </div>
57
+ </div>
58
+ );
59
+ }
60
+
61
+ return (
62
+ <div className="proteum-profiler__chartMount">
63
+ <div ref={mountRef} />
64
+ </div>
65
+ );
66
+ }