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.
- package/LICENSE +21 -0
- package/README.md +431 -0
- package/config/repos.example.json +128 -0
- package/config/settings.example.json +64 -0
- package/config/skills/code-review.md +76 -0
- package/config/skills/full-check.md +26 -0
- package/config/skills/lint-fix.md +23 -0
- package/dist/api/agent-routes.d.ts +2 -0
- package/dist/api/agent-routes.d.ts.map +1 -0
- package/dist/api/agent-routes.js +251 -0
- package/dist/api/agent-routes.js.map +1 -0
- package/dist/api/app-routes.d.ts +2 -0
- package/dist/api/app-routes.d.ts.map +1 -0
- package/dist/api/app-routes.js +150 -0
- package/dist/api/app-routes.js.map +1 -0
- package/dist/api/docker-routes.d.ts +2 -0
- package/dist/api/docker-routes.d.ts.map +1 -0
- package/dist/api/docker-routes.js +167 -0
- package/dist/api/docker-routes.js.map +1 -0
- package/dist/api/middleware.d.ts +6 -0
- package/dist/api/middleware.d.ts.map +1 -0
- package/dist/api/middleware.js +293 -0
- package/dist/api/middleware.js.map +1 -0
- package/dist/api/pin-auth.d.ts +65 -0
- package/dist/api/pin-auth.d.ts.map +1 -0
- package/dist/api/pin-auth.js +218 -0
- package/dist/api/pin-auth.js.map +1 -0
- package/dist/api/routes.d.ts +2 -0
- package/dist/api/routes.d.ts.map +1 -0
- package/dist/api/routes.js +473 -0
- package/dist/api/routes.js.map +1 -0
- package/dist/api/settings-routes.d.ts +2 -0
- package/dist/api/settings-routes.d.ts.map +1 -0
- package/dist/api/settings-routes.js +570 -0
- package/dist/api/settings-routes.js.map +1 -0
- package/dist/api/skill-routes.d.ts +2 -0
- package/dist/api/skill-routes.d.ts.map +1 -0
- package/dist/api/skill-routes.js +88 -0
- package/dist/api/skill-routes.js.map +1 -0
- package/dist/api/terminal-routes.d.ts +2 -0
- package/dist/api/terminal-routes.d.ts.map +1 -0
- package/dist/api/terminal-routes.js +3524 -0
- package/dist/api/terminal-routes.js.map +1 -0
- package/dist/api/tunnel-routes.d.ts +2 -0
- package/dist/api/tunnel-routes.d.ts.map +1 -0
- package/dist/api/tunnel-routes.js +196 -0
- package/dist/api/tunnel-routes.js.map +1 -0
- package/dist/api/workspace-routes.d.ts +3 -0
- package/dist/api/workspace-routes.d.ts.map +1 -0
- package/dist/api/workspace-routes.js +649 -0
- package/dist/api/workspace-routes.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +276 -0
- package/dist/cli.js.map +1 -0
- package/dist/client/assets/index-B4r0njGe.js +780 -0
- package/dist/client/assets/index-CY_9MyE0.css +1 -0
- package/dist/client/favicon.svg +5 -0
- package/dist/client/icons/icon-192.svg +5 -0
- package/dist/client/icons/icon-512.svg +5 -0
- package/dist/client/icons/logo-with-message.png +0 -0
- package/dist/client/icons/logo.png +0 -0
- package/dist/client/index.html +25 -0
- package/dist/client/manifest.json +62 -0
- package/dist/client/sw.js +243 -0
- package/dist/config/agent-usage.d.ts +34 -0
- package/dist/config/agent-usage.d.ts.map +1 -0
- package/dist/config/agent-usage.js +87 -0
- package/dist/config/agent-usage.js.map +1 -0
- package/dist/config/repos.d.ts +34 -0
- package/dist/config/repos.d.ts.map +1 -0
- package/dist/config/repos.js +412 -0
- package/dist/config/repos.js.map +1 -0
- package/dist/config/settings.d.ts +634 -0
- package/dist/config/settings.d.ts.map +1 -0
- package/dist/config/settings.js +459 -0
- package/dist/config/settings.js.map +1 -0
- package/dist/config/skills.d.ts +18 -0
- package/dist/config/skills.d.ts.map +1 -0
- package/dist/config/skills.js +174 -0
- package/dist/config/skills.js.map +1 -0
- package/dist/config/workspaces.d.ts +961 -0
- package/dist/config/workspaces.d.ts.map +1 -0
- package/dist/config/workspaces.js +482 -0
- package/dist/config/workspaces.js.map +1 -0
- package/dist/core/app-manager.d.ts +85 -0
- package/dist/core/app-manager.d.ts.map +1 -0
- package/dist/core/app-manager.js +447 -0
- package/dist/core/app-manager.js.map +1 -0
- package/dist/core/claude-invoker.d.ts +49 -0
- package/dist/core/claude-invoker.d.ts.map +1 -0
- package/dist/core/claude-invoker.js +583 -0
- package/dist/core/claude-invoker.js.map +1 -0
- package/dist/core/claude-session-reader.d.ts +25 -0
- package/dist/core/claude-session-reader.d.ts.map +1 -0
- package/dist/core/claude-session-reader.js +184 -0
- package/dist/core/claude-session-reader.js.map +1 -0
- package/dist/core/claude-usage-query.d.ts +78 -0
- package/dist/core/claude-usage-query.d.ts.map +1 -0
- package/dist/core/claude-usage-query.js +294 -0
- package/dist/core/claude-usage-query.js.map +1 -0
- package/dist/core/git-credential-helper.d.ts +57 -0
- package/dist/core/git-credential-helper.d.ts.map +1 -0
- package/dist/core/git-credential-helper.js +176 -0
- package/dist/core/git-credential-helper.js.map +1 -0
- package/dist/core/git-sandbox.d.ts +135 -0
- package/dist/core/git-sandbox.d.ts.map +1 -0
- package/dist/core/git-sandbox.js +907 -0
- package/dist/core/git-sandbox.js.map +1 -0
- package/dist/core/github-integration.d.ts +66 -0
- package/dist/core/github-integration.d.ts.map +1 -0
- package/dist/core/github-integration.js +350 -0
- package/dist/core/github-integration.js.map +1 -0
- package/dist/core/github-oauth.d.ts +88 -0
- package/dist/core/github-oauth.d.ts.map +1 -0
- package/dist/core/github-oauth.js +244 -0
- package/dist/core/github-oauth.js.map +1 -0
- package/dist/core/gitlab-integration.d.ts +66 -0
- package/dist/core/gitlab-integration.d.ts.map +1 -0
- package/dist/core/gitlab-integration.js +353 -0
- package/dist/core/gitlab-integration.js.map +1 -0
- package/dist/core/gitlab-oauth.d.ts +100 -0
- package/dist/core/gitlab-oauth.d.ts.map +1 -0
- package/dist/core/gitlab-oauth.js +366 -0
- package/dist/core/gitlab-oauth.js.map +1 -0
- package/dist/core/insights-extractor.d.ts +68 -0
- package/dist/core/insights-extractor.d.ts.map +1 -0
- package/dist/core/insights-extractor.js +402 -0
- package/dist/core/insights-extractor.js.map +1 -0
- package/dist/core/logger.d.ts +27 -0
- package/dist/core/logger.d.ts.map +1 -0
- package/dist/core/logger.js +70 -0
- package/dist/core/logger.js.map +1 -0
- package/dist/core/process-runner.d.ts +27 -0
- package/dist/core/process-runner.d.ts.map +1 -0
- package/dist/core/process-runner.js +147 -0
- package/dist/core/process-runner.js.map +1 -0
- package/dist/core/project-detector.d.ts +30 -0
- package/dist/core/project-detector.d.ts.map +1 -0
- package/dist/core/project-detector.js +482 -0
- package/dist/core/project-detector.js.map +1 -0
- package/dist/core/qr-generator.d.ts +18 -0
- package/dist/core/qr-generator.d.ts.map +1 -0
- package/dist/core/qr-generator.js +61 -0
- package/dist/core/qr-generator.js.map +1 -0
- package/dist/core/remote-tunnel-manager.d.ts +59 -0
- package/dist/core/remote-tunnel-manager.d.ts.map +1 -0
- package/dist/core/remote-tunnel-manager.js +235 -0
- package/dist/core/remote-tunnel-manager.js.map +1 -0
- package/dist/core/shared-docker-manager.d.ts +41 -0
- package/dist/core/shared-docker-manager.d.ts.map +1 -0
- package/dist/core/shared-docker-manager.js +409 -0
- package/dist/core/shared-docker-manager.js.map +1 -0
- package/dist/core/skill-executor.d.ts +25 -0
- package/dist/core/skill-executor.d.ts.map +1 -0
- package/dist/core/skill-executor.js +171 -0
- package/dist/core/skill-executor.js.map +1 -0
- package/dist/core/terminal-session.d.ts +149 -0
- package/dist/core/terminal-session.d.ts.map +1 -0
- package/dist/core/terminal-session.js +2340 -0
- package/dist/core/terminal-session.js.map +1 -0
- package/dist/core/tunnel-manager.d.ts +35 -0
- package/dist/core/tunnel-manager.d.ts.map +1 -0
- package/dist/core/tunnel-manager.js +137 -0
- package/dist/core/tunnel-manager.js.map +1 -0
- package/dist/core/usage-manager.d.ts +57 -0
- package/dist/core/usage-manager.d.ts.map +1 -0
- package/dist/core/usage-manager.js +363 -0
- package/dist/core/usage-manager.js.map +1 -0
- package/dist/core/ws-manager.d.ts +39 -0
- package/dist/core/ws-manager.d.ts.map +1 -0
- package/dist/core/ws-manager.js +190 -0
- package/dist/core/ws-manager.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +229 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +868 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +119 -0
- package/dist/types.js.map +1 -0
- 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
|