claudedesk 1.0.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 (182) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +431 -0
  3. package/config/repos.example.json +128 -0
  4. package/config/settings.example.json +64 -0
  5. package/config/skills/code-review.md +76 -0
  6. package/config/skills/full-check.md +26 -0
  7. package/config/skills/lint-fix.md +23 -0
  8. package/dist/api/agent-routes.d.ts +2 -0
  9. package/dist/api/agent-routes.d.ts.map +1 -0
  10. package/dist/api/agent-routes.js +251 -0
  11. package/dist/api/agent-routes.js.map +1 -0
  12. package/dist/api/app-routes.d.ts +2 -0
  13. package/dist/api/app-routes.d.ts.map +1 -0
  14. package/dist/api/app-routes.js +150 -0
  15. package/dist/api/app-routes.js.map +1 -0
  16. package/dist/api/docker-routes.d.ts +2 -0
  17. package/dist/api/docker-routes.d.ts.map +1 -0
  18. package/dist/api/docker-routes.js +167 -0
  19. package/dist/api/docker-routes.js.map +1 -0
  20. package/dist/api/middleware.d.ts +6 -0
  21. package/dist/api/middleware.d.ts.map +1 -0
  22. package/dist/api/middleware.js +293 -0
  23. package/dist/api/middleware.js.map +1 -0
  24. package/dist/api/pin-auth.d.ts +65 -0
  25. package/dist/api/pin-auth.d.ts.map +1 -0
  26. package/dist/api/pin-auth.js +218 -0
  27. package/dist/api/pin-auth.js.map +1 -0
  28. package/dist/api/routes.d.ts +2 -0
  29. package/dist/api/routes.d.ts.map +1 -0
  30. package/dist/api/routes.js +473 -0
  31. package/dist/api/routes.js.map +1 -0
  32. package/dist/api/settings-routes.d.ts +2 -0
  33. package/dist/api/settings-routes.d.ts.map +1 -0
  34. package/dist/api/settings-routes.js +570 -0
  35. package/dist/api/settings-routes.js.map +1 -0
  36. package/dist/api/skill-routes.d.ts +2 -0
  37. package/dist/api/skill-routes.d.ts.map +1 -0
  38. package/dist/api/skill-routes.js +88 -0
  39. package/dist/api/skill-routes.js.map +1 -0
  40. package/dist/api/terminal-routes.d.ts +2 -0
  41. package/dist/api/terminal-routes.d.ts.map +1 -0
  42. package/dist/api/terminal-routes.js +3524 -0
  43. package/dist/api/terminal-routes.js.map +1 -0
  44. package/dist/api/tunnel-routes.d.ts +2 -0
  45. package/dist/api/tunnel-routes.d.ts.map +1 -0
  46. package/dist/api/tunnel-routes.js +196 -0
  47. package/dist/api/tunnel-routes.js.map +1 -0
  48. package/dist/api/workspace-routes.d.ts +3 -0
  49. package/dist/api/workspace-routes.d.ts.map +1 -0
  50. package/dist/api/workspace-routes.js +649 -0
  51. package/dist/api/workspace-routes.js.map +1 -0
  52. package/dist/cli.d.ts +3 -0
  53. package/dist/cli.d.ts.map +1 -0
  54. package/dist/cli.js +276 -0
  55. package/dist/cli.js.map +1 -0
  56. package/dist/client/assets/index-B4r0njGe.js +780 -0
  57. package/dist/client/assets/index-CY_9MyE0.css +1 -0
  58. package/dist/client/favicon.svg +5 -0
  59. package/dist/client/icons/icon-192.svg +5 -0
  60. package/dist/client/icons/icon-512.svg +5 -0
  61. package/dist/client/icons/logo-with-message.png +0 -0
  62. package/dist/client/icons/logo.png +0 -0
  63. package/dist/client/index.html +25 -0
  64. package/dist/client/manifest.json +62 -0
  65. package/dist/client/sw.js +243 -0
  66. package/dist/config/agent-usage.d.ts +34 -0
  67. package/dist/config/agent-usage.d.ts.map +1 -0
  68. package/dist/config/agent-usage.js +87 -0
  69. package/dist/config/agent-usage.js.map +1 -0
  70. package/dist/config/repos.d.ts +34 -0
  71. package/dist/config/repos.d.ts.map +1 -0
  72. package/dist/config/repos.js +412 -0
  73. package/dist/config/repos.js.map +1 -0
  74. package/dist/config/settings.d.ts +634 -0
  75. package/dist/config/settings.d.ts.map +1 -0
  76. package/dist/config/settings.js +459 -0
  77. package/dist/config/settings.js.map +1 -0
  78. package/dist/config/skills.d.ts +18 -0
  79. package/dist/config/skills.d.ts.map +1 -0
  80. package/dist/config/skills.js +174 -0
  81. package/dist/config/skills.js.map +1 -0
  82. package/dist/config/workspaces.d.ts +961 -0
  83. package/dist/config/workspaces.d.ts.map +1 -0
  84. package/dist/config/workspaces.js +482 -0
  85. package/dist/config/workspaces.js.map +1 -0
  86. package/dist/core/app-manager.d.ts +85 -0
  87. package/dist/core/app-manager.d.ts.map +1 -0
  88. package/dist/core/app-manager.js +447 -0
  89. package/dist/core/app-manager.js.map +1 -0
  90. package/dist/core/claude-invoker.d.ts +49 -0
  91. package/dist/core/claude-invoker.d.ts.map +1 -0
  92. package/dist/core/claude-invoker.js +583 -0
  93. package/dist/core/claude-invoker.js.map +1 -0
  94. package/dist/core/claude-session-reader.d.ts +25 -0
  95. package/dist/core/claude-session-reader.d.ts.map +1 -0
  96. package/dist/core/claude-session-reader.js +184 -0
  97. package/dist/core/claude-session-reader.js.map +1 -0
  98. package/dist/core/claude-usage-query.d.ts +78 -0
  99. package/dist/core/claude-usage-query.d.ts.map +1 -0
  100. package/dist/core/claude-usage-query.js +294 -0
  101. package/dist/core/claude-usage-query.js.map +1 -0
  102. package/dist/core/git-credential-helper.d.ts +57 -0
  103. package/dist/core/git-credential-helper.d.ts.map +1 -0
  104. package/dist/core/git-credential-helper.js +176 -0
  105. package/dist/core/git-credential-helper.js.map +1 -0
  106. package/dist/core/git-sandbox.d.ts +135 -0
  107. package/dist/core/git-sandbox.d.ts.map +1 -0
  108. package/dist/core/git-sandbox.js +907 -0
  109. package/dist/core/git-sandbox.js.map +1 -0
  110. package/dist/core/github-integration.d.ts +66 -0
  111. package/dist/core/github-integration.d.ts.map +1 -0
  112. package/dist/core/github-integration.js +350 -0
  113. package/dist/core/github-integration.js.map +1 -0
  114. package/dist/core/github-oauth.d.ts +88 -0
  115. package/dist/core/github-oauth.d.ts.map +1 -0
  116. package/dist/core/github-oauth.js +244 -0
  117. package/dist/core/github-oauth.js.map +1 -0
  118. package/dist/core/gitlab-integration.d.ts +66 -0
  119. package/dist/core/gitlab-integration.d.ts.map +1 -0
  120. package/dist/core/gitlab-integration.js +353 -0
  121. package/dist/core/gitlab-integration.js.map +1 -0
  122. package/dist/core/gitlab-oauth.d.ts +100 -0
  123. package/dist/core/gitlab-oauth.d.ts.map +1 -0
  124. package/dist/core/gitlab-oauth.js +366 -0
  125. package/dist/core/gitlab-oauth.js.map +1 -0
  126. package/dist/core/insights-extractor.d.ts +68 -0
  127. package/dist/core/insights-extractor.d.ts.map +1 -0
  128. package/dist/core/insights-extractor.js +402 -0
  129. package/dist/core/insights-extractor.js.map +1 -0
  130. package/dist/core/logger.d.ts +27 -0
  131. package/dist/core/logger.d.ts.map +1 -0
  132. package/dist/core/logger.js +70 -0
  133. package/dist/core/logger.js.map +1 -0
  134. package/dist/core/process-runner.d.ts +27 -0
  135. package/dist/core/process-runner.d.ts.map +1 -0
  136. package/dist/core/process-runner.js +147 -0
  137. package/dist/core/process-runner.js.map +1 -0
  138. package/dist/core/project-detector.d.ts +30 -0
  139. package/dist/core/project-detector.d.ts.map +1 -0
  140. package/dist/core/project-detector.js +482 -0
  141. package/dist/core/project-detector.js.map +1 -0
  142. package/dist/core/qr-generator.d.ts +18 -0
  143. package/dist/core/qr-generator.d.ts.map +1 -0
  144. package/dist/core/qr-generator.js +61 -0
  145. package/dist/core/qr-generator.js.map +1 -0
  146. package/dist/core/remote-tunnel-manager.d.ts +59 -0
  147. package/dist/core/remote-tunnel-manager.d.ts.map +1 -0
  148. package/dist/core/remote-tunnel-manager.js +235 -0
  149. package/dist/core/remote-tunnel-manager.js.map +1 -0
  150. package/dist/core/shared-docker-manager.d.ts +41 -0
  151. package/dist/core/shared-docker-manager.d.ts.map +1 -0
  152. package/dist/core/shared-docker-manager.js +409 -0
  153. package/dist/core/shared-docker-manager.js.map +1 -0
  154. package/dist/core/skill-executor.d.ts +25 -0
  155. package/dist/core/skill-executor.d.ts.map +1 -0
  156. package/dist/core/skill-executor.js +171 -0
  157. package/dist/core/skill-executor.js.map +1 -0
  158. package/dist/core/terminal-session.d.ts +149 -0
  159. package/dist/core/terminal-session.d.ts.map +1 -0
  160. package/dist/core/terminal-session.js +2340 -0
  161. package/dist/core/terminal-session.js.map +1 -0
  162. package/dist/core/tunnel-manager.d.ts +35 -0
  163. package/dist/core/tunnel-manager.d.ts.map +1 -0
  164. package/dist/core/tunnel-manager.js +137 -0
  165. package/dist/core/tunnel-manager.js.map +1 -0
  166. package/dist/core/usage-manager.d.ts +57 -0
  167. package/dist/core/usage-manager.d.ts.map +1 -0
  168. package/dist/core/usage-manager.js +363 -0
  169. package/dist/core/usage-manager.js.map +1 -0
  170. package/dist/core/ws-manager.d.ts +39 -0
  171. package/dist/core/ws-manager.d.ts.map +1 -0
  172. package/dist/core/ws-manager.js +190 -0
  173. package/dist/core/ws-manager.js.map +1 -0
  174. package/dist/index.d.ts +7 -0
  175. package/dist/index.d.ts.map +1 -0
  176. package/dist/index.js +229 -0
  177. package/dist/index.js.map +1 -0
  178. package/dist/types.d.ts +868 -0
  179. package/dist/types.d.ts.map +1 -0
  180. package/dist/types.js +119 -0
  181. package/dist/types.js.map +1 -0
  182. package/package.json +96 -0
@@ -0,0 +1,147 @@
1
+ import { spawn } from 'child_process';
2
+ import { createWriteStream, mkdirSync, existsSync } from 'fs';
3
+ import { join } from 'path';
4
+ import treeKill from 'tree-kill';
5
+ export class ProcessRunner {
6
+ runningProcesses = new Map();
7
+ async run(command, options) {
8
+ const { cwd, timeout = 120000, logFile, env } = options;
9
+ console.log(`[ProcessRunner] Starting: "${command}" in ${cwd}`);
10
+ return new Promise((resolve) => {
11
+ let stdout = '';
12
+ let stderr = '';
13
+ let killed = false;
14
+ // On Windows, use cmd.exe to run commands
15
+ const isWindows = process.platform === 'win32';
16
+ const shell = isWindows ? 'cmd.exe' : '/bin/sh';
17
+ const shellArgs = isWindows ? ['/c', command] : ['-c', command];
18
+ console.log(`[ProcessRunner] Shell: ${shell}, args: ${JSON.stringify(shellArgs)}`);
19
+ const proc = spawn(shell, shellArgs, {
20
+ cwd,
21
+ env: { ...process.env, ...env },
22
+ stdio: ['ignore', 'pipe', 'pipe'],
23
+ });
24
+ const processId = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
25
+ this.runningProcesses.set(processId, proc);
26
+ console.log(`[ProcessRunner] Process spawned, PID: ${proc.pid}, processId: ${processId}`);
27
+ // Set up log file if specified
28
+ let logStream = null;
29
+ if (logFile) {
30
+ const logDir = join(cwd, 'logs');
31
+ if (!existsSync(logDir)) {
32
+ mkdirSync(logDir, { recursive: true });
33
+ }
34
+ logStream = createWriteStream(join(logDir, logFile));
35
+ logStream.write(`$ ${command}\n\n`);
36
+ }
37
+ proc.stdout?.on('data', (data) => {
38
+ const text = data.toString();
39
+ stdout += text;
40
+ logStream?.write(text);
41
+ });
42
+ proc.stderr?.on('data', (data) => {
43
+ const text = data.toString();
44
+ stderr += text;
45
+ logStream?.write(`[stderr] ${text}`);
46
+ });
47
+ const timer = setTimeout(() => {
48
+ killed = true;
49
+ this.killProcess(processId);
50
+ }, timeout);
51
+ proc.on('close', (code) => {
52
+ console.log(`[ProcessRunner] Process ${processId} closed with code ${code}`);
53
+ clearTimeout(timer);
54
+ this.runningProcesses.delete(processId);
55
+ logStream?.end();
56
+ resolve({
57
+ exitCode: code,
58
+ stdout,
59
+ stderr,
60
+ killed,
61
+ error: killed ? `Process timed out after ${timeout}ms` : undefined,
62
+ });
63
+ });
64
+ proc.on('error', (err) => {
65
+ console.log(`[ProcessRunner] Process ${processId} error: ${err.message}`);
66
+ clearTimeout(timer);
67
+ this.runningProcesses.delete(processId);
68
+ logStream?.end();
69
+ resolve({
70
+ exitCode: null,
71
+ stdout,
72
+ stderr,
73
+ killed: false,
74
+ error: err.message,
75
+ });
76
+ });
77
+ });
78
+ }
79
+ // Start a long-running process (like a dev server)
80
+ startServer(command, options) {
81
+ const { cwd, env, logFile } = options;
82
+ const isWindows = process.platform === 'win32';
83
+ const shell = isWindows ? 'cmd.exe' : '/bin/sh';
84
+ const shellArgs = isWindows ? ['/c', command] : ['-c', command];
85
+ const proc = spawn(shell, shellArgs, {
86
+ cwd,
87
+ env: { ...process.env, ...env },
88
+ stdio: ['ignore', 'pipe', 'pipe'],
89
+ detached: !isWindows, // On Unix, create process group
90
+ });
91
+ const processId = `server-${Date.now()}-${Math.random().toString(36).slice(2)}`;
92
+ this.runningProcesses.set(processId, proc);
93
+ // Set up log file if specified
94
+ if (logFile) {
95
+ const logDir = join(cwd, 'logs');
96
+ if (!existsSync(logDir)) {
97
+ mkdirSync(logDir, { recursive: true });
98
+ }
99
+ const logStream = createWriteStream(join(logDir, logFile));
100
+ logStream.write(`$ ${command}\n\n`);
101
+ proc.stdout?.on('data', (data) => {
102
+ logStream.write(data.toString());
103
+ });
104
+ proc.stderr?.on('data', (data) => {
105
+ logStream.write(`[stderr] ${data.toString()}`);
106
+ });
107
+ proc.on('close', () => {
108
+ logStream.end();
109
+ });
110
+ }
111
+ return { processId, process: proc };
112
+ }
113
+ killProcess(processId) {
114
+ return new Promise((resolve) => {
115
+ const proc = this.runningProcesses.get(processId);
116
+ if (!proc || !proc.pid) {
117
+ this.runningProcesses.delete(processId);
118
+ resolve();
119
+ return;
120
+ }
121
+ // Use tree-kill to kill the entire process tree on Windows
122
+ treeKill(proc.pid, 'SIGTERM', (err) => {
123
+ if (err) {
124
+ console.error(`Failed to kill process ${processId}:`, err);
125
+ // Force kill
126
+ treeKill(proc.pid, 'SIGKILL', () => {
127
+ this.runningProcesses.delete(processId);
128
+ resolve();
129
+ });
130
+ }
131
+ else {
132
+ this.runningProcesses.delete(processId);
133
+ resolve();
134
+ }
135
+ });
136
+ });
137
+ }
138
+ killAllProcesses() {
139
+ const killPromises = Array.from(this.runningProcesses.keys()).map(id => this.killProcess(id));
140
+ return Promise.all(killPromises);
141
+ }
142
+ getRunningProcessIds() {
143
+ return Array.from(this.runningProcesses.keys());
144
+ }
145
+ }
146
+ export const processRunner = new ProcessRunner();
147
+ //# sourceMappingURL=process-runner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"process-runner.js","sourceRoot":"","sources":["../../src/core/process-runner.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ import type { RepoConfig, DetectedService } from '../types.js';
2
+ export interface ProjectDetection {
3
+ exists: boolean;
4
+ isDirectory: boolean;
5
+ projectType: ProjectType | null;
6
+ framework: string | null;
7
+ suggestedId: string;
8
+ suggestedCommands: RepoConfig['commands'];
9
+ suggestedProof: RepoConfig['proof'];
10
+ suggestedPort: number | null;
11
+ detectedFiles: string[];
12
+ }
13
+ export type ProjectType = 'nodejs' | 'rust' | 'python' | 'go' | 'unknown';
14
+ export declare function detectProject(path: string): Promise<ProjectDetection>;
15
+ /**
16
+ * Detect workspace services in a monorepo.
17
+ * Reads workspace configuration from package.json (npm/yarn) or pnpm-workspace.yaml
18
+ * and returns an array of detected services that have dev or start scripts.
19
+ */
20
+ export declare function detectWorkspaceServices(repoPath: string): Promise<DetectedService[]>;
21
+ /**
22
+ * Check if a repo is a monorepo (has workspace configuration)
23
+ */
24
+ export declare function isMonorepo(repoPath: string): Promise<boolean>;
25
+ /**
26
+ * Get the primary service from a list of services.
27
+ * Priority: First frontend service, or first service overall.
28
+ */
29
+ export declare function getPrimaryService(services: DetectedService[]): DetectedService | null;
30
+ //# sourceMappingURL=project-detector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-detector.d.ts","sourceRoot":"","sources":["../../src/core/project-detector.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAO/D,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,OAAO,CAAC;IAChB,WAAW,EAAE,OAAO,CAAC;IACrB,WAAW,EAAE,WAAW,GAAG,IAAI,CAAC;IAChC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,EAAE,UAAU,CAAC,UAAU,CAAC,CAAC;IAC1C,cAAc,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC;IACpC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,aAAa,EAAE,MAAM,EAAE,CAAC;CACzB;AAED,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,MAAM,GAAG,QAAQ,GAAG,IAAI,GAAG,SAAS,CAAC;AAgC1E,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CA2E3E;AAwND;;;;GAIG;AACH,wBAAsB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC,CAwF1F;AA0ED;;GAEG;AACH,wBAAsB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAGnE;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,eAAe,EAAE,GAAG,eAAe,GAAG,IAAI,CAarF"}
@@ -0,0 +1,482 @@
1
+ import { existsSync, readFileSync, statSync } from 'fs';
2
+ import { join, basename, normalize } from 'path';
3
+ import { glob } from 'glob';
4
+ // Helper to normalize paths to forward slashes for cross-platform compatibility
5
+ function normalizePath(p) {
6
+ return normalize(p).replace(/\\/g, '/').replace(/\/$/, '');
7
+ }
8
+ const FRAMEWORK_DETECTION = {
9
+ // Frontend frameworks
10
+ 'vite': { name: 'Vite', port: 5173, isFrontend: true },
11
+ 'next': { name: 'Next.js', port: 3000, isFrontend: true },
12
+ 'react-scripts': { name: 'Create React App', port: 3000, isFrontend: true },
13
+ '@angular/core': { name: 'Angular', port: 4200, isFrontend: true },
14
+ 'vue': { name: 'Vue.js', port: 5173, isFrontend: true },
15
+ 'svelte': { name: 'Svelte', port: 5173, isFrontend: true },
16
+ 'nuxt': { name: 'Nuxt', port: 3000, isFrontend: true },
17
+ 'gatsby': { name: 'Gatsby', port: 8000, isFrontend: true },
18
+ 'astro': { name: 'Astro', port: 4321, isFrontend: true },
19
+ // Backend frameworks - health check uses root URL by default
20
+ // NestJS apps typically use /api global prefix + /health endpoint
21
+ 'express': { name: 'Express', port: 3000, isFrontend: false },
22
+ 'fastify': { name: 'Fastify', port: 3000, isFrontend: false },
23
+ '@nestjs/core': { name: 'NestJS', port: 3000, isFrontend: false, healthPath: '/api/health' },
24
+ 'koa': { name: 'Koa', port: 3000, isFrontend: false },
25
+ 'hapi': { name: 'Hapi', port: 3000, isFrontend: false },
26
+ '@hapi/hapi': { name: 'Hapi', port: 3000, isFrontend: false },
27
+ 'restify': { name: 'Restify', port: 3000, isFrontend: false },
28
+ };
29
+ export async function detectProject(path) {
30
+ // Normalize path for cross-platform compatibility
31
+ const normalizedPath = normalizePath(path);
32
+ const result = {
33
+ exists: false,
34
+ isDirectory: false,
35
+ projectType: null,
36
+ framework: null,
37
+ suggestedId: '',
38
+ suggestedCommands: {},
39
+ suggestedProof: { mode: 'cli', cli: { command: 'echo "No proof configured"' } },
40
+ suggestedPort: null,
41
+ detectedFiles: [],
42
+ };
43
+ // Check if path exists
44
+ if (!existsSync(normalizedPath)) {
45
+ return result;
46
+ }
47
+ result.exists = true;
48
+ // Check if it's a directory
49
+ const stat = statSync(normalizedPath);
50
+ if (!stat.isDirectory()) {
51
+ return result;
52
+ }
53
+ result.isDirectory = true;
54
+ // Generate suggested ID from directory name
55
+ const dirName = basename(normalizedPath);
56
+ result.suggestedId = dirName.toLowerCase().replace(/[^a-z0-9-]/g, '-');
57
+ // Detect project files
58
+ const projectFiles = [
59
+ 'package.json',
60
+ 'Cargo.toml',
61
+ 'pyproject.toml',
62
+ 'setup.py',
63
+ 'requirements.txt',
64
+ 'go.mod',
65
+ '.git',
66
+ 'tsconfig.json',
67
+ 'vite.config.ts',
68
+ 'vite.config.js',
69
+ 'next.config.js',
70
+ 'next.config.mjs',
71
+ ];
72
+ for (const file of projectFiles) {
73
+ if (existsSync(join(normalizedPath, file))) {
74
+ result.detectedFiles.push(file);
75
+ }
76
+ }
77
+ // Detect project type and framework
78
+ if (existsSync(join(normalizedPath, 'package.json'))) {
79
+ result.projectType = 'nodejs';
80
+ detectNodeProject(normalizedPath, result);
81
+ }
82
+ else if (existsSync(join(normalizedPath, 'Cargo.toml'))) {
83
+ result.projectType = 'rust';
84
+ detectRustProject(normalizedPath, result);
85
+ }
86
+ else if (existsSync(join(normalizedPath, 'pyproject.toml')) ||
87
+ existsSync(join(normalizedPath, 'setup.py')) ||
88
+ existsSync(join(normalizedPath, 'requirements.txt'))) {
89
+ result.projectType = 'python';
90
+ detectPythonProject(normalizedPath, result);
91
+ }
92
+ else if (existsSync(join(normalizedPath, 'go.mod'))) {
93
+ result.projectType = 'go';
94
+ detectGoProject(normalizedPath, result);
95
+ }
96
+ else {
97
+ result.projectType = 'unknown';
98
+ }
99
+ return result;
100
+ }
101
+ function detectNodeProject(path, result) {
102
+ try {
103
+ const pkgPath = join(path, 'package.json');
104
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
105
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
106
+ // Detect framework
107
+ for (const [dep, info] of Object.entries(FRAMEWORK_DETECTION)) {
108
+ if (allDeps[dep]) {
109
+ result.framework = info.name;
110
+ result.suggestedPort = info.port;
111
+ // Set proof mode based on frontend vs backend
112
+ if (info.isFrontend) {
113
+ result.suggestedProof = {
114
+ mode: 'web',
115
+ web: {
116
+ url: `http://localhost:${info.port}`,
117
+ waitForSelector: 'body',
118
+ },
119
+ };
120
+ }
121
+ else {
122
+ result.suggestedProof = {
123
+ mode: 'api',
124
+ api: {
125
+ // Use {port} placeholder so proof uses actual detected port at runtime
126
+ healthUrl: `http://localhost:{port}${info.healthPath || ''}`,
127
+ timeout: 30000,
128
+ },
129
+ };
130
+ }
131
+ break;
132
+ }
133
+ }
134
+ // Suggest commands based on scripts
135
+ result.suggestedCommands = {
136
+ install: 'npm install',
137
+ };
138
+ if (pkg.scripts?.build) {
139
+ result.suggestedCommands.build = 'npm run build';
140
+ }
141
+ if (pkg.scripts?.test && pkg.scripts.test !== 'echo "Error: no test specified" && exit 1') {
142
+ result.suggestedCommands.test = 'npm test';
143
+ }
144
+ if (pkg.scripts?.dev) {
145
+ result.suggestedCommands.run = 'npm run dev';
146
+ }
147
+ else if (pkg.scripts?.start) {
148
+ result.suggestedCommands.run = 'npm start';
149
+ }
150
+ // Use TypeScript type check if tsconfig exists
151
+ if (existsSync(join(path, 'tsconfig.json')) && !result.suggestedCommands.build) {
152
+ result.suggestedCommands.build = 'npx tsc --noEmit';
153
+ }
154
+ // Default port if none detected but has run command
155
+ if (!result.suggestedPort && result.suggestedCommands.run) {
156
+ result.suggestedPort = 3000;
157
+ result.suggestedProof = {
158
+ mode: 'api',
159
+ api: {
160
+ // Use {port} placeholder so proof uses actual detected port at runtime
161
+ healthUrl: 'http://localhost:{port}',
162
+ timeout: 30000,
163
+ },
164
+ };
165
+ }
166
+ }
167
+ catch (error) {
168
+ // Failed to parse package.json, use minimal defaults
169
+ result.suggestedCommands = { install: 'npm install' };
170
+ }
171
+ }
172
+ function detectRustProject(path, result) {
173
+ result.framework = 'Cargo';
174
+ result.suggestedCommands = {
175
+ build: 'cargo build',
176
+ test: 'cargo test',
177
+ run: 'cargo run',
178
+ };
179
+ // Check if it's a web project (has actix, axum, rocket, etc.)
180
+ try {
181
+ const cargoPath = join(path, 'Cargo.toml');
182
+ const cargo = readFileSync(cargoPath, 'utf-8');
183
+ if (cargo.includes('actix-web') || cargo.includes('axum') ||
184
+ cargo.includes('rocket') || cargo.includes('warp')) {
185
+ result.suggestedPort = 8080;
186
+ result.suggestedProof = {
187
+ mode: 'api',
188
+ api: {
189
+ healthUrl: 'http://localhost:8080',
190
+ timeout: 30000,
191
+ },
192
+ };
193
+ }
194
+ }
195
+ catch {
196
+ // Use defaults
197
+ }
198
+ }
199
+ function detectPythonProject(path, result) {
200
+ result.framework = 'Python';
201
+ // Check for different package managers
202
+ if (existsSync(join(path, 'pyproject.toml'))) {
203
+ // Check if using poetry
204
+ try {
205
+ const pyproject = readFileSync(join(path, 'pyproject.toml'), 'utf-8');
206
+ if (pyproject.includes('[tool.poetry]')) {
207
+ result.suggestedCommands = {
208
+ install: 'poetry install',
209
+ test: 'poetry run pytest',
210
+ };
211
+ }
212
+ else {
213
+ result.suggestedCommands = {
214
+ install: 'pip install -e .',
215
+ test: 'pytest',
216
+ };
217
+ }
218
+ }
219
+ catch {
220
+ result.suggestedCommands = {
221
+ install: 'pip install -e .',
222
+ test: 'pytest',
223
+ };
224
+ }
225
+ }
226
+ else if (existsSync(join(path, 'requirements.txt'))) {
227
+ result.suggestedCommands = {
228
+ install: 'pip install -r requirements.txt',
229
+ test: 'pytest',
230
+ };
231
+ }
232
+ else {
233
+ result.suggestedCommands = {
234
+ install: 'pip install -e .',
235
+ test: 'pytest',
236
+ };
237
+ }
238
+ // Check for web frameworks
239
+ try {
240
+ const files = [
241
+ join(path, 'requirements.txt'),
242
+ join(path, 'pyproject.toml'),
243
+ ];
244
+ for (const file of files) {
245
+ if (existsSync(file)) {
246
+ const content = readFileSync(file, 'utf-8').toLowerCase();
247
+ if (content.includes('fastapi') || content.includes('flask') ||
248
+ content.includes('django') || content.includes('starlette')) {
249
+ result.suggestedPort = 8000;
250
+ result.suggestedProof = {
251
+ mode: 'api',
252
+ api: {
253
+ healthUrl: 'http://localhost:8000',
254
+ timeout: 30000,
255
+ },
256
+ };
257
+ if (content.includes('fastapi')) {
258
+ result.framework = 'FastAPI';
259
+ result.suggestedCommands.run = 'uvicorn main:app --reload';
260
+ }
261
+ else if (content.includes('flask')) {
262
+ result.framework = 'Flask';
263
+ result.suggestedCommands.run = 'flask run';
264
+ }
265
+ else if (content.includes('django')) {
266
+ result.framework = 'Django';
267
+ result.suggestedCommands.run = 'python manage.py runserver';
268
+ }
269
+ break;
270
+ }
271
+ }
272
+ }
273
+ }
274
+ catch {
275
+ // Use defaults
276
+ }
277
+ }
278
+ function detectGoProject(path, result) {
279
+ result.framework = 'Go';
280
+ result.suggestedCommands = {
281
+ build: 'go build',
282
+ test: 'go test ./...',
283
+ run: 'go run .',
284
+ };
285
+ // Check for web frameworks in go.mod
286
+ try {
287
+ const goModPath = join(path, 'go.mod');
288
+ const goMod = readFileSync(goModPath, 'utf-8');
289
+ if (goMod.includes('gin-gonic') || goMod.includes('echo') ||
290
+ goMod.includes('fiber') || goMod.includes('chi')) {
291
+ result.suggestedPort = 8080;
292
+ result.suggestedProof = {
293
+ mode: 'api',
294
+ api: {
295
+ healthUrl: 'http://localhost:8080',
296
+ timeout: 30000,
297
+ },
298
+ };
299
+ }
300
+ }
301
+ catch {
302
+ // Use defaults
303
+ }
304
+ }
305
+ /**
306
+ * Detect workspace services in a monorepo.
307
+ * Reads workspace configuration from package.json (npm/yarn) or pnpm-workspace.yaml
308
+ * and returns an array of detected services that have dev or start scripts.
309
+ */
310
+ export async function detectWorkspaceServices(repoPath) {
311
+ const services = [];
312
+ const workspacePatterns = await getWorkspacePatterns(repoPath);
313
+ if (workspacePatterns.length === 0) {
314
+ return services;
315
+ }
316
+ // Expand glob patterns to find all workspace directories
317
+ const workspaceDirs = [];
318
+ for (const pattern of workspacePatterns) {
319
+ // Handle patterns like "packages/*" - glob returns paths
320
+ const matches = await glob(pattern.replace(/\\/g, '/'), {
321
+ cwd: repoPath,
322
+ nodir: false,
323
+ });
324
+ // Filter to only include directories (those with package.json)
325
+ for (const match of matches) {
326
+ const fullPath = join(repoPath, match);
327
+ if (existsSync(join(fullPath, 'package.json'))) {
328
+ workspaceDirs.push(match);
329
+ }
330
+ }
331
+ }
332
+ // Filter and detect each workspace
333
+ for (const dir of workspaceDirs) {
334
+ const fullPath = join(repoPath, dir);
335
+ const packageJsonPath = join(fullPath, 'package.json');
336
+ if (!existsSync(packageJsonPath)) {
337
+ continue;
338
+ }
339
+ try {
340
+ const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
341
+ const scripts = pkg.scripts || {};
342
+ // Only include workspaces that have dev or start script
343
+ const hasDevScript = 'dev' in scripts;
344
+ const hasStartScript = 'start' in scripts;
345
+ if (!hasDevScript && !hasStartScript) {
346
+ continue;
347
+ }
348
+ // Detect framework for this workspace
349
+ const detection = await detectProject(fullPath);
350
+ const service = {
351
+ id: pkg.name || basename(dir),
352
+ name: getDisplayName(pkg.name || basename(dir)),
353
+ path: dir.replace(/\\/g, '/'), // Normalize to forward slashes
354
+ framework: detection.framework || undefined,
355
+ runScript: hasDevScript ? 'dev' : 'start',
356
+ suggestedPort: detection.suggestedPort || 3000,
357
+ };
358
+ // Set proof based on framework type
359
+ if (detection.suggestedProof) {
360
+ const frameworkInfo = getFrameworkInfo(detection.framework);
361
+ if (frameworkInfo?.isFrontend) {
362
+ service.proof = {
363
+ mode: 'web',
364
+ web: {
365
+ url: `http://localhost:{port}`,
366
+ waitForSelector: 'body',
367
+ },
368
+ };
369
+ }
370
+ else {
371
+ service.proof = {
372
+ mode: 'api',
373
+ api: {
374
+ healthUrl: `http://localhost:{port}${frameworkInfo?.healthPath || ''}`,
375
+ timeout: 30000,
376
+ },
377
+ };
378
+ }
379
+ }
380
+ services.push(service);
381
+ }
382
+ catch (error) {
383
+ // Skip workspaces with invalid package.json
384
+ console.warn(`Failed to parse package.json in ${dir}:`, error);
385
+ }
386
+ }
387
+ return services;
388
+ }
389
+ /**
390
+ * Get workspace patterns from package.json or pnpm-workspace.yaml
391
+ */
392
+ async function getWorkspacePatterns(repoPath) {
393
+ // Try npm/yarn workspaces from package.json
394
+ const packageJsonPath = join(repoPath, 'package.json');
395
+ if (existsSync(packageJsonPath)) {
396
+ try {
397
+ const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
398
+ if (pkg.workspaces) {
399
+ // Handle both array format and object format (yarn workspaces)
400
+ if (Array.isArray(pkg.workspaces)) {
401
+ return pkg.workspaces;
402
+ }
403
+ if (pkg.workspaces.packages) {
404
+ return pkg.workspaces.packages;
405
+ }
406
+ }
407
+ }
408
+ catch {
409
+ // Failed to parse package.json
410
+ }
411
+ }
412
+ // Try pnpm-workspace.yaml
413
+ const pnpmWorkspacePath = join(repoPath, 'pnpm-workspace.yaml');
414
+ if (existsSync(pnpmWorkspacePath)) {
415
+ try {
416
+ const content = readFileSync(pnpmWorkspacePath, 'utf-8');
417
+ // Simple YAML parsing for packages array
418
+ const packagesMatch = content.match(/packages:\s*\n((?:\s+-\s+.+\n?)+)/);
419
+ if (packagesMatch) {
420
+ const packages = packagesMatch[1]
421
+ .split('\n')
422
+ .map(line => line.match(/^\s+-\s+['"]?([^'"]+)['"]?/)?.[1])
423
+ .filter((p) => !!p);
424
+ return packages;
425
+ }
426
+ }
427
+ catch {
428
+ // Failed to parse pnpm-workspace.yaml
429
+ }
430
+ }
431
+ return [];
432
+ }
433
+ /**
434
+ * Get display name from package name (strip scope prefix)
435
+ */
436
+ function getDisplayName(packageName) {
437
+ // Remove @scope/ prefix if present
438
+ const withoutScope = packageName.replace(/^@[^/]+\//, '');
439
+ // Convert kebab-case to Title Case
440
+ return withoutScope
441
+ .split('-')
442
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1))
443
+ .join(' ');
444
+ }
445
+ /**
446
+ * Get framework info by name
447
+ */
448
+ function getFrameworkInfo(frameworkName) {
449
+ if (!frameworkName)
450
+ return null;
451
+ for (const info of Object.values(FRAMEWORK_DETECTION)) {
452
+ if (info.name === frameworkName) {
453
+ return info;
454
+ }
455
+ }
456
+ return null;
457
+ }
458
+ /**
459
+ * Check if a repo is a monorepo (has workspace configuration)
460
+ */
461
+ export async function isMonorepo(repoPath) {
462
+ const patterns = await getWorkspacePatterns(repoPath);
463
+ return patterns.length > 0;
464
+ }
465
+ /**
466
+ * Get the primary service from a list of services.
467
+ * Priority: First frontend service, or first service overall.
468
+ */
469
+ export function getPrimaryService(services) {
470
+ if (services.length === 0)
471
+ return null;
472
+ // Find first frontend service
473
+ for (const service of services) {
474
+ const frameworkInfo = getFrameworkInfo(service.framework || null);
475
+ if (frameworkInfo?.isFrontend) {
476
+ return service;
477
+ }
478
+ }
479
+ // Default to first service
480
+ return services[0];
481
+ }
482
+ //# sourceMappingURL=project-detector.js.map