github-issue-tower-defence-management 1.104.4 → 1.105.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.
Files changed (74) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +8 -7
  3. package/bin/adapter/entry-points/cli/index.js +21 -13
  4. package/bin/adapter/entry-points/cli/index.js.map +1 -1
  5. package/bin/adapter/entry-points/console/{consoleServer.js → webServer.js} +11 -11
  6. package/bin/adapter/entry-points/console/webServer.js.map +1 -0
  7. package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js +41 -2
  8. package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js.map +1 -1
  9. package/bin/adapter/entry-points/handlers/dashboardRowWriter.js +34 -0
  10. package/bin/adapter/entry-points/handlers/dashboardRowWriter.js.map +1 -0
  11. package/bin/adapter/entry-points/handlers/machineStatusWriter.js +60 -0
  12. package/bin/adapter/entry-points/handlers/machineStatusWriter.js.map +1 -0
  13. package/bin/adapter/entry-points/handlers/tokenStatusWriter.js +98 -0
  14. package/bin/adapter/entry-points/handlers/tokenStatusWriter.js.map +1 -0
  15. package/bin/adapter/proxy/RateLimitCache.js +3 -0
  16. package/bin/adapter/proxy/RateLimitCache.js.map +1 -1
  17. package/bin/adapter/repositories/ProcHostMetricsRepository.js +136 -0
  18. package/bin/adapter/repositories/ProcHostMetricsRepository.js.map +1 -0
  19. package/bin/adapter/repositories/ProcTakeOwnershipSpawnRepository.js +131 -0
  20. package/bin/adapter/repositories/ProcTakeOwnershipSpawnRepository.js.map +1 -0
  21. package/bin/domain/usecases/adapter-interfaces/TakeOwnershipSpawnRepository.js +3 -0
  22. package/bin/domain/usecases/adapter-interfaces/TakeOwnershipSpawnRepository.js.map +1 -0
  23. package/bin/domain/usecases/dashboard/GenerateDashboardRowUseCase.js +38 -0
  24. package/bin/domain/usecases/dashboard/GenerateDashboardRowUseCase.js.map +1 -0
  25. package/bin/domain/usecases/dashboard/GenerateTokenStatusUseCase.js +115 -0
  26. package/bin/domain/usecases/dashboard/GenerateTokenStatusUseCase.js.map +1 -0
  27. package/package.json +1 -1
  28. package/src/adapter/entry-points/cli/index.test.ts +74 -21
  29. package/src/adapter/entry-points/cli/index.ts +146 -136
  30. package/src/adapter/entry-points/console/ui/e2e/consoleTestHarness.ts +2 -2
  31. package/src/adapter/entry-points/console/{consoleServer.test.ts → webServer.test.ts} +32 -32
  32. package/src/adapter/entry-points/console/{consoleServer.ts → webServer.ts} +15 -17
  33. package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts +52 -0
  34. package/src/adapter/entry-points/handlers/dashboardRowWriter.test.ts +111 -0
  35. package/src/adapter/entry-points/handlers/dashboardRowWriter.ts +51 -0
  36. package/src/adapter/entry-points/handlers/machineStatusWriter.test.ts +100 -0
  37. package/src/adapter/entry-points/handlers/machineStatusWriter.ts +79 -0
  38. package/src/adapter/entry-points/handlers/tokenStatusWriter.test.ts +176 -0
  39. package/src/adapter/entry-points/handlers/tokenStatusWriter.ts +139 -0
  40. package/src/adapter/proxy/RateLimitCache.test.ts +32 -0
  41. package/src/adapter/proxy/RateLimitCache.ts +6 -0
  42. package/src/adapter/repositories/ProcHostMetricsRepository.test.ts +135 -0
  43. package/src/adapter/repositories/ProcHostMetricsRepository.ts +136 -0
  44. package/src/adapter/repositories/ProcTakeOwnershipSpawnRepository.test.ts +130 -0
  45. package/src/adapter/repositories/ProcTakeOwnershipSpawnRepository.ts +118 -0
  46. package/src/domain/usecases/adapter-interfaces/TakeOwnershipSpawnRepository.ts +8 -0
  47. package/src/domain/usecases/dashboard/GenerateDashboardRowUseCase.test.ts +159 -0
  48. package/src/domain/usecases/dashboard/GenerateDashboardRowUseCase.ts +72 -0
  49. package/src/domain/usecases/dashboard/GenerateTokenStatusUseCase.test.ts +209 -0
  50. package/src/domain/usecases/dashboard/GenerateTokenStatusUseCase.ts +195 -0
  51. package/types/adapter/entry-points/cli/index.d.ts.map +1 -1
  52. package/types/adapter/entry-points/console/{consoleServer.d.ts → webServer.d.ts} +7 -7
  53. package/types/adapter/entry-points/console/webServer.d.ts.map +1 -0
  54. package/types/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.d.ts.map +1 -1
  55. package/types/adapter/entry-points/handlers/dashboardRowWriter.d.ts +15 -0
  56. package/types/adapter/entry-points/handlers/dashboardRowWriter.d.ts.map +1 -0
  57. package/types/adapter/entry-points/handlers/machineStatusWriter.d.ts +16 -0
  58. package/types/adapter/entry-points/handlers/machineStatusWriter.d.ts.map +1 -0
  59. package/types/adapter/entry-points/handlers/tokenStatusWriter.d.ts +21 -0
  60. package/types/adapter/entry-points/handlers/tokenStatusWriter.d.ts.map +1 -0
  61. package/types/adapter/proxy/RateLimitCache.d.ts +2 -0
  62. package/types/adapter/proxy/RateLimitCache.d.ts.map +1 -1
  63. package/types/adapter/repositories/ProcHostMetricsRepository.d.ts +23 -0
  64. package/types/adapter/repositories/ProcHostMetricsRepository.d.ts.map +1 -0
  65. package/types/adapter/repositories/ProcTakeOwnershipSpawnRepository.d.ts +11 -0
  66. package/types/adapter/repositories/ProcTakeOwnershipSpawnRepository.d.ts.map +1 -0
  67. package/types/domain/usecases/adapter-interfaces/TakeOwnershipSpawnRepository.d.ts +8 -0
  68. package/types/domain/usecases/adapter-interfaces/TakeOwnershipSpawnRepository.d.ts.map +1 -0
  69. package/types/domain/usecases/dashboard/GenerateDashboardRowUseCase.d.ts +19 -0
  70. package/types/domain/usecases/dashboard/GenerateDashboardRowUseCase.d.ts.map +1 -0
  71. package/types/domain/usecases/dashboard/GenerateTokenStatusUseCase.d.ts +53 -0
  72. package/types/domain/usecases/dashboard/GenerateTokenStatusUseCase.d.ts.map +1 -0
  73. package/bin/adapter/entry-points/console/consoleServer.js.map +0 -1
  74. package/types/adapter/entry-points/console/consoleServer.d.ts.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "github-issue-tower-defence-management",
3
- "version": "1.104.4",
3
+ "version": "1.105.0",
4
4
  "description": "",
5
5
  "main": "bin/index.js",
6
6
  "scripts": {
@@ -73,23 +73,22 @@ jest.mock('../handlers/HandleScheduledEventUseCaseHandler', () => ({
73
73
  handle: jest.fn().mockResolvedValue(null),
74
74
  })),
75
75
  }));
76
- import type { StartConsoleServerOptions } from '../console/consoleServer';
76
+ import type { StartWebServerOptions } from '../console/webServer';
77
77
 
78
- const mockStartConsoleServer = jest
79
- .fn<Promise<unknown>, [StartConsoleServerOptions]>()
78
+ const mockStartWebServer = jest
79
+ .fn<Promise<unknown>, [StartWebServerOptions]>()
80
80
  .mockResolvedValue({
81
81
  close: jest.fn(),
82
82
  address: jest.fn().mockReturnValue({ port: 9981 }),
83
83
  });
84
- jest.mock('../console/consoleServer', () => {
84
+ jest.mock('../console/webServer', () => {
85
85
  const actual: Record<string, unknown> = jest.requireActual(
86
- '../console/consoleServer',
86
+ '../console/webServer',
87
87
  );
88
88
  return {
89
89
  ...actual,
90
- startConsoleServer: (
91
- options: StartConsoleServerOptions,
92
- ): Promise<unknown> => mockStartConsoleServer(options),
90
+ startWebServer: (options: StartWebServerOptions): Promise<unknown> =>
91
+ mockStartWebServer(options),
93
92
  };
94
93
  });
95
94
 
@@ -1677,10 +1676,10 @@ mysteryKey: 'value'
1677
1676
  });
1678
1677
  });
1679
1678
 
1680
- describe('serveConsole', () => {
1679
+ describe('serveWeb', () => {
1681
1680
  it('should appear in the CLI help output', () => {
1682
1681
  const helpText = program.helpInformation();
1683
- expect(helpText).toContain('serveConsole');
1682
+ expect(helpText).toContain('serveWeb');
1684
1683
  });
1685
1684
 
1686
1685
  it('should start the server on the default port 9981 when --port is omitted', async () => {
@@ -1690,13 +1689,13 @@ mysteryKey: 'value'
1690
1689
  await program.parseAsync([
1691
1690
  'node',
1692
1691
  'test',
1693
- 'serveConsole',
1692
+ 'serveWeb',
1694
1693
  '--configFilePath',
1695
1694
  configFilePath,
1696
1695
  ]);
1697
1696
 
1698
- expect(mockStartConsoleServer).toHaveBeenCalledTimes(1);
1699
- const callArg = mockStartConsoleServer.mock.calls[0][0];
1697
+ expect(mockStartWebServer).toHaveBeenCalledTimes(1);
1698
+ const callArg = mockStartWebServer.mock.calls[0][0];
1700
1699
  expect(callArg.port).toBe(9981);
1701
1700
  expect(callArg.accessToken).toBe('config-token');
1702
1701
  expect(callArg.consoleDataOutputDir).toBeNull();
@@ -1717,7 +1716,7 @@ mysteryKey: 'value'
1717
1716
  await program.parseAsync([
1718
1717
  'node',
1719
1718
  'test',
1720
- 'serveConsole',
1719
+ 'serveWeb',
1721
1720
  '--configFilePath',
1722
1721
  configFilePath,
1723
1722
  '--port',
@@ -1726,7 +1725,7 @@ mysteryKey: 'value'
1726
1725
  '/tmp/console-data',
1727
1726
  ]);
1728
1727
 
1729
- const callArg = mockStartConsoleServer.mock.calls[0][0];
1728
+ const callArg = mockStartWebServer.mock.calls[0][0];
1730
1729
  expect(callArg.port).toBe(12345);
1731
1730
  expect(callArg.consoleDataOutputDir).toBe('/tmp/console-data');
1732
1731
 
@@ -1746,7 +1745,7 @@ mysteryKey: 'value'
1746
1745
  program.parseAsync([
1747
1746
  'node',
1748
1747
  'test',
1749
- 'serveConsole',
1748
+ 'serveWeb',
1750
1749
  '--configFilePath',
1751
1750
  configFilePath,
1752
1751
  ]),
@@ -1774,7 +1773,7 @@ mysteryKey: 'value'
1774
1773
  program.parseAsync([
1775
1774
  'node',
1776
1775
  'test',
1777
- 'serveConsole',
1776
+ 'serveWeb',
1778
1777
  '--configFilePath',
1779
1778
  configFilePath,
1780
1779
  '--port',
@@ -1795,12 +1794,12 @@ mysteryKey: 'value'
1795
1794
  await program.parseAsync([
1796
1795
  'node',
1797
1796
  'test',
1798
- 'serveConsole',
1797
+ 'serveWeb',
1799
1798
  '--configFilePath',
1800
1799
  configFilePath,
1801
1800
  ]);
1802
1801
 
1803
- const callArg = mockStartConsoleServer.mock.calls[0][0];
1802
+ const callArg = mockStartWebServer.mock.calls[0][0];
1804
1803
  expect(callArg.issueRepository).not.toBeNull();
1805
1804
  expect(callArg.issueTitleStateCache).not.toBeNull();
1806
1805
 
@@ -1833,7 +1832,7 @@ mysteryKey: 'value'
1833
1832
  program.parseAsync([
1834
1833
  'node',
1835
1834
  'test',
1836
- 'serveConsole',
1835
+ 'serveWeb',
1837
1836
  '--configFilePath',
1838
1837
  configFilePath,
1839
1838
  ]),
@@ -1865,7 +1864,7 @@ mysteryKey: 'value'
1865
1864
  program.parseAsync([
1866
1865
  'node',
1867
1866
  'test',
1868
- 'serveConsole',
1867
+ 'serveWeb',
1869
1868
  '--configFilePath',
1870
1869
  configFilePath,
1871
1870
  ]),
@@ -1879,4 +1878,58 @@ mysteryKey: 'value'
1879
1878
  processExitSpy.mockRestore();
1880
1879
  });
1881
1880
  });
1881
+
1882
+ describe('serveConsole (deprecated alias)', () => {
1883
+ it('should appear in the CLI help output', () => {
1884
+ const helpText = program.helpInformation();
1885
+ expect(helpText).toContain('serveConsole');
1886
+ });
1887
+
1888
+ it('should route to the same handler as serveWeb on the default port 9981', async () => {
1889
+ writeConfig({ ...defaultConfig, consoleAccessToken: 'config-token' });
1890
+ const logSpy = jest.spyOn(console, 'log').mockImplementation();
1891
+
1892
+ await program.parseAsync([
1893
+ 'node',
1894
+ 'test',
1895
+ 'serveConsole',
1896
+ '--configFilePath',
1897
+ configFilePath,
1898
+ ]);
1899
+
1900
+ expect(mockStartWebServer).toHaveBeenCalledTimes(1);
1901
+ const callArg = mockStartWebServer.mock.calls[0][0];
1902
+ expect(callArg.port).toBe(9981);
1903
+ expect(callArg.accessToken).toBe('config-token');
1904
+ expect(callArg.consoleDataOutputDir).toBeNull();
1905
+ expect(callArg.uiDistDir).toBe(
1906
+ path.join(__dirname, '..', 'console', 'ui-dist'),
1907
+ );
1908
+
1909
+ logSpy.mockRestore();
1910
+ });
1911
+
1912
+ it('should use the provided --port and --consoleDataOutputDir', async () => {
1913
+ writeConfig({ ...defaultConfig, consoleAccessToken: 'config-token' });
1914
+ const logSpy = jest.spyOn(console, 'log').mockImplementation();
1915
+
1916
+ await program.parseAsync([
1917
+ 'node',
1918
+ 'test',
1919
+ 'serveConsole',
1920
+ '--configFilePath',
1921
+ configFilePath,
1922
+ '--port',
1923
+ '12345',
1924
+ '--consoleDataOutputDir',
1925
+ '/tmp/console-data',
1926
+ ]);
1927
+
1928
+ const callArg = mockStartWebServer.mock.calls[0][0];
1929
+ expect(callArg.port).toBe(12345);
1930
+ expect(callArg.consoleDataOutputDir).toBe('/tmp/console-data');
1931
+
1932
+ logSpy.mockRestore();
1933
+ });
1934
+ });
1882
1935
  });
@@ -32,10 +32,7 @@ import { GitHubIssueCommentRepository } from '../../repositories/GitHubIssueComm
32
32
  import { FetchWebhookRepository } from '../../repositories/FetchWebhookRepository';
33
33
  import { RevertOrphanedPreparationUseCase } from '../../../domain/usecases/RevertOrphanedPreparationUseCase';
34
34
  import * as path from 'path';
35
- import {
36
- DEFAULT_CONSOLE_PORT,
37
- startConsoleServer,
38
- } from '../console/consoleServer';
35
+ import { DEFAULT_WEB_PORT, startWebServer } from '../console/webServer';
39
36
  import { IssueTitleStateCache } from '../console/consoleReadApi';
40
37
  import {
41
38
  buildPjcodeToProjectUrl,
@@ -73,7 +70,7 @@ type CheckIssueReviewReadinessOptions = {
73
70
  configFilePath: string;
74
71
  };
75
72
 
76
- type ServeConsoleOptions = {
73
+ type ServeWebOptions = {
77
74
  configFilePath: string;
78
75
  port?: string;
79
76
  consoleDataOutputDir?: string;
@@ -596,146 +593,159 @@ program
596
593
  process.stdout.write(`${JSON.stringify(result)}\n`);
597
594
  });
598
595
 
599
- program
600
- .command('serveConsole')
601
- .description('Start the local TDPM Console HTTP server')
602
- .requiredOption(
603
- '--configFilePath <path>',
604
- 'Path to config file for tower defence management',
605
- )
606
- .option(
607
- '--port <number>',
608
- `Port for the console HTTP server (default: ${DEFAULT_CONSOLE_PORT})`,
609
- )
610
- .option(
611
- '--consoleDataOutputDir <path>',
612
- 'Directory where console data files are written and served from',
613
- )
614
- .option(
615
- '--inTmuxDataDir <path>',
616
- `Directory containing the flat in-tmux-by-human static JSON files served at /in-tmux-by-human/*.json (default: ${DEFAULT_IN_TMUX_DATA_DIR})`,
617
- )
618
- .option(
619
- '--dashboardDir <path>',
620
- `Directory containing the dashboard HTML fragment tdpm.txt served unauthenticated at /tdpm.txt (default: ${DEFAULT_DASHBOARD_DIR})`,
621
- )
622
- .action(async (options: ServeConsoleOptions) => {
623
- const config = loadConfigFile(options.configFilePath);
624
-
625
- const accessToken = config.consoleAccessToken;
626
- if (!accessToken) {
596
+ const runServeWeb = async (options: ServeWebOptions): Promise<void> => {
597
+ const config = loadConfigFile(options.configFilePath);
598
+
599
+ const accessToken = config.consoleAccessToken;
600
+ if (!accessToken) {
601
+ console.error(
602
+ 'consoleAccessToken is required. Provide it via the config file.',
603
+ );
604
+ process.exit(1);
605
+ }
606
+
607
+ let port = DEFAULT_WEB_PORT;
608
+ if (options.port !== undefined) {
609
+ const parsedPort = Number(options.port);
610
+ if (
611
+ !Number.isFinite(parsedPort) ||
612
+ !Number.isInteger(parsedPort) ||
613
+ parsedPort <= 0 ||
614
+ parsedPort > 65535
615
+ ) {
627
616
  console.error(
628
- 'consoleAccessToken is required. Provide it via the config file.',
617
+ 'Invalid value for --port. It must be a positive integer between 1 and 65535.',
629
618
  );
630
619
  process.exit(1);
631
620
  }
632
-
633
- let port = DEFAULT_CONSOLE_PORT;
634
- if (options.port !== undefined) {
635
- const parsedPort = Number(options.port);
636
- if (
637
- !Number.isFinite(parsedPort) ||
638
- !Number.isInteger(parsedPort) ||
639
- parsedPort <= 0 ||
640
- parsedPort > 65535
641
- ) {
621
+ port = parsedPort;
622
+ }
623
+
624
+ const token = process.env.GH_TOKEN;
625
+ if (!token) {
626
+ console.error('GH_TOKEN environment variable is required');
627
+ process.exit(1);
628
+ }
629
+
630
+ const projectUrl = config.projectUrl;
631
+ if (!projectUrl) {
632
+ console.error(
633
+ 'projectUrl is required. Provide it via the config file or project README.',
634
+ );
635
+ process.exit(1);
636
+ }
637
+
638
+ const projectName = config.projectName ?? 'default';
639
+ const localStorageRepository = new LocalStorageRepository();
640
+ const cachePath = `./tmp/cache/${projectName}`;
641
+ const localStorageCacheRepository = new LocalStorageCacheRepository(
642
+ localStorageRepository,
643
+ cachePath,
644
+ );
645
+ const githubRepositoryParams = buildGithubRepositoryParams(
646
+ localStorageRepository,
647
+ token,
648
+ );
649
+ const projectRepository = new GraphqlProjectRepository(
650
+ ...githubRepositoryParams,
651
+ );
652
+ const apiV3IssueRepository = new ApiV3IssueRepository(
653
+ ...githubRepositoryParams,
654
+ );
655
+ const restIssueRepository = new RestIssueRepository(
656
+ ...githubRepositoryParams,
657
+ );
658
+ const graphqlProjectItemRepository = new GraphqlProjectItemRepository(
659
+ ...githubRepositoryParams,
660
+ );
661
+ const issueRepository = new ApiV3CheerioRestIssueRepository(
662
+ apiV3IssueRepository,
663
+ restIssueRepository,
664
+ graphqlProjectItemRepository,
665
+ localStorageCacheRepository,
666
+ ...githubRepositoryParams,
667
+ );
668
+
669
+ const pjcodeToProjectUrl = buildPjcodeToProjectUrl(
670
+ projectName,
671
+ projectUrl,
672
+ config.consoleProjects ?? null,
673
+ );
674
+ const resolveProject = createConsoleProjectResolver(
675
+ pjcodeToProjectUrl,
676
+ async (targetProjectUrl: string) => {
677
+ const targetProjectId =
678
+ await projectRepository.findProjectIdByUrl(targetProjectUrl);
679
+ if (!targetProjectId) {
680
+ console.error(`No project found for projectUrl ${targetProjectUrl}`);
681
+ return null;
682
+ }
683
+ const loadedProject = await projectRepository.getProject(targetProjectId);
684
+ if (!loadedProject) {
642
685
  console.error(
643
- 'Invalid value for --port. It must be a positive integer between 1 and 65535.',
686
+ `Failed to load project for projectUrl ${targetProjectUrl}`,
644
687
  );
645
- process.exit(1);
688
+ return null;
646
689
  }
647
- port = parsedPort;
648
- }
649
-
650
- const token = process.env.GH_TOKEN;
651
- if (!token) {
652
- console.error('GH_TOKEN environment variable is required');
653
- process.exit(1);
654
- }
655
-
656
- const projectUrl = config.projectUrl;
657
- if (!projectUrl) {
658
- console.error(
659
- 'projectUrl is required. Provide it via the config file or project README.',
660
- );
661
- process.exit(1);
662
- }
690
+ return loadedProject;
691
+ },
692
+ );
693
+
694
+ const uiDistDir = path.join(__dirname, '..', 'console', 'ui-dist');
695
+ const consoleDataOutputDir = options.consoleDataOutputDir ?? null;
696
+ const inTmuxDataDir = options.inTmuxDataDir ?? DEFAULT_IN_TMUX_DATA_DIR;
697
+ const dashboardDir = options.dashboardDir ?? DEFAULT_DASHBOARD_DIR;
698
+
699
+ await startWebServer({
700
+ accessToken,
701
+ uiDistDir,
702
+ consoleDataOutputDir,
703
+ inTmuxDataDir,
704
+ dashboardDir,
705
+ githubToken: token,
706
+ issueRepository,
707
+ resolveProject,
708
+ issueTitleStateCache: new IssueTitleStateCache(),
709
+ port,
710
+ });
711
+ console.log(`TDPM web server listening on port ${port}`);
712
+ };
663
713
 
664
- const projectName = config.projectName ?? 'default';
665
- const localStorageRepository = new LocalStorageRepository();
666
- const cachePath = `./tmp/cache/${projectName}`;
667
- const localStorageCacheRepository = new LocalStorageCacheRepository(
668
- localStorageRepository,
669
- cachePath,
670
- );
671
- const githubRepositoryParams = buildGithubRepositoryParams(
672
- localStorageRepository,
673
- token,
674
- );
675
- const projectRepository = new GraphqlProjectRepository(
676
- ...githubRepositoryParams,
677
- );
678
- const apiV3IssueRepository = new ApiV3IssueRepository(
679
- ...githubRepositoryParams,
680
- );
681
- const restIssueRepository = new RestIssueRepository(
682
- ...githubRepositoryParams,
683
- );
684
- const graphqlProjectItemRepository = new GraphqlProjectItemRepository(
685
- ...githubRepositoryParams,
686
- );
687
- const issueRepository = new ApiV3CheerioRestIssueRepository(
688
- apiV3IssueRepository,
689
- restIssueRepository,
690
- graphqlProjectItemRepository,
691
- localStorageCacheRepository,
692
- ...githubRepositoryParams,
693
- );
714
+ const addServeWebOptions = (command: Command): Command =>
715
+ command
716
+ .requiredOption(
717
+ '--configFilePath <path>',
718
+ 'Path to config file for tower defence management',
719
+ )
720
+ .option(
721
+ '--port <number>',
722
+ `Port for the web HTTP server (default: ${DEFAULT_WEB_PORT})`,
723
+ )
724
+ .option(
725
+ '--consoleDataOutputDir <path>',
726
+ 'Directory where console data files are written and served from',
727
+ )
728
+ .option(
729
+ '--inTmuxDataDir <path>',
730
+ `Directory containing the flat in-tmux-by-human static JSON files served at /in-tmux-by-human/*.json (default: ${DEFAULT_IN_TMUX_DATA_DIR})`,
731
+ )
732
+ .option(
733
+ '--dashboardDir <path>',
734
+ `Directory containing the dashboard HTML fragment tdpm.txt served unauthenticated at /tdpm.txt (default: ${DEFAULT_DASHBOARD_DIR})`,
735
+ );
736
+
737
+ addServeWebOptions(program.command('serveWeb'))
738
+ .description(
739
+ 'Start the local TDPM web server (console tabs, dashboard, and in-tmux session list)',
740
+ )
741
+ .action(async (options: ServeWebOptions) => {
742
+ await runServeWeb(options);
743
+ });
694
744
 
695
- const pjcodeToProjectUrl = buildPjcodeToProjectUrl(
696
- projectName,
697
- projectUrl,
698
- config.consoleProjects ?? null,
699
- );
700
- const resolveProject = createConsoleProjectResolver(
701
- pjcodeToProjectUrl,
702
- async (targetProjectUrl: string) => {
703
- const targetProjectId =
704
- await projectRepository.findProjectIdByUrl(targetProjectUrl);
705
- if (!targetProjectId) {
706
- console.error(`No project found for projectUrl ${targetProjectUrl}`);
707
- return null;
708
- }
709
- const loadedProject =
710
- await projectRepository.getProject(targetProjectId);
711
- if (!loadedProject) {
712
- console.error(
713
- `Failed to load project for projectUrl ${targetProjectUrl}`,
714
- );
715
- return null;
716
- }
717
- return loadedProject;
718
- },
719
- );
720
-
721
- const uiDistDir = path.join(__dirname, '..', 'console', 'ui-dist');
722
- const consoleDataOutputDir = options.consoleDataOutputDir ?? null;
723
- const inTmuxDataDir = options.inTmuxDataDir ?? DEFAULT_IN_TMUX_DATA_DIR;
724
- const dashboardDir = options.dashboardDir ?? DEFAULT_DASHBOARD_DIR;
725
-
726
- await startConsoleServer({
727
- accessToken,
728
- uiDistDir,
729
- consoleDataOutputDir,
730
- inTmuxDataDir,
731
- dashboardDir,
732
- githubToken: token,
733
- issueRepository,
734
- resolveProject,
735
- issueTitleStateCache: new IssueTitleStateCache(),
736
- port,
737
- });
738
- console.log(`TDPM Console server listening on port ${port}`);
745
+ addServeWebOptions(program.command('serveConsole'))
746
+ .description('Deprecated alias for serveWeb. Use serveWeb instead.')
747
+ .action(async (options: ServeWebOptions) => {
748
+ await runServeWeb(options);
739
749
  });
740
750
 
741
751
  program
@@ -13,7 +13,7 @@ import type {
13
13
  } from '../../../../../domain/usecases/adapter-interfaces/IssueRepository';
14
14
  import type { ConsoleProjectBinding } from '../../consoleOperationApi';
15
15
  import { IssueTitleStateCache } from '../../consoleReadApi';
16
- import { startConsoleServer } from '../../consoleServer';
16
+ import { startWebServer } from '../../webServer';
17
17
 
18
18
  export const CONSOLE_E2E_PJCODE = 'umino';
19
19
  export const CONSOLE_E2E_TOKEN = 'console-e2e-fixture-token-3f9c1a';
@@ -362,7 +362,7 @@ export const startConsoleE2eHarness = async (): Promise<ConsoleE2eHarness> => {
362
362
  ): Promise<ConsoleProjectBinding | null> =>
363
363
  pjcode === CONSOLE_E2E_PJCODE ? { pjcode, project } : null;
364
364
 
365
- const server = await startConsoleServer({
365
+ const server = await startWebServer({
366
366
  accessToken: CONSOLE_E2E_TOKEN,
367
367
  uiDistDir,
368
368
  consoleDataOutputDir,