@tryclean/create 0.1.0 → 0.1.1
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/dist/index.js +147 -66
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -42,6 +42,12 @@ async function isDockerRunning() {
|
|
|
42
42
|
async function startDocker() {
|
|
43
43
|
if (process.platform === "darwin") {
|
|
44
44
|
await exec("open -a Docker");
|
|
45
|
+
} else if (process.platform === "win32") {
|
|
46
|
+
try {
|
|
47
|
+
await exec(`start "" "C:\\Program Files\\Docker\\Docker\\Docker Desktop.exe"`);
|
|
48
|
+
} catch {
|
|
49
|
+
await exec(`powershell -Command "Start-Process 'Docker Desktop'"`);
|
|
50
|
+
}
|
|
45
51
|
} else if (process.platform === "linux") {
|
|
46
52
|
await exec("systemctl start docker");
|
|
47
53
|
} else {
|
|
@@ -112,6 +118,7 @@ async function composeCp(cwd, src, dest) {
|
|
|
112
118
|
// src/lib/network.ts
|
|
113
119
|
import { createServer } from "net";
|
|
114
120
|
import { hostname } from "os";
|
|
121
|
+
var isWindows = process.platform === "win32";
|
|
115
122
|
function checkPort(port) {
|
|
116
123
|
return new Promise((resolve) => {
|
|
117
124
|
const server = createServer();
|
|
@@ -124,11 +131,23 @@ function checkPort(port) {
|
|
|
124
131
|
}
|
|
125
132
|
async function getProcessOnPort(port) {
|
|
126
133
|
try {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
134
|
+
if (isWindows) {
|
|
135
|
+
const { stdout } = await exec(`netstat -ano | findstr ":${port}" | findstr LISTENING`);
|
|
136
|
+
const line = stdout.trim().split("\n")[0];
|
|
137
|
+
if (!line) return null;
|
|
138
|
+
const parts = line.trim().split(/\s+/);
|
|
139
|
+
const pid = parts[parts.length - 1];
|
|
140
|
+
if (!pid || pid === "0") return null;
|
|
141
|
+
const { stdout: taskOut } = await exec(`tasklist /FI "PID eq ${pid}" /FO CSV /NH`);
|
|
142
|
+
const match = taskOut.match(/"([^"]+)"/);
|
|
143
|
+
return { pid, command: match?.[1] ?? "unknown" };
|
|
144
|
+
} else {
|
|
145
|
+
const { stdout } = await exec(`lsof -ti :${port}`);
|
|
146
|
+
const pid = stdout.trim().split("\n")[0];
|
|
147
|
+
if (!pid) return null;
|
|
148
|
+
const { stdout: psOut } = await exec(`ps -p ${pid} -o comm=`);
|
|
149
|
+
return { pid, command: psOut.trim() };
|
|
150
|
+
}
|
|
132
151
|
} catch {
|
|
133
152
|
return null;
|
|
134
153
|
}
|
|
@@ -142,7 +161,8 @@ async function getDockerContainerOnPort(port) {
|
|
|
142
161
|
}
|
|
143
162
|
}
|
|
144
163
|
function isDockerProcess(command2) {
|
|
145
|
-
|
|
164
|
+
const lower = command2.toLowerCase();
|
|
165
|
+
return lower.includes("docker") || lower.includes("com.docker");
|
|
146
166
|
}
|
|
147
167
|
async function killProcessOnPort(port) {
|
|
148
168
|
try {
|
|
@@ -157,14 +177,30 @@ async function killProcessOnPort(port) {
|
|
|
157
177
|
return true;
|
|
158
178
|
}
|
|
159
179
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
180
|
+
if (isWindows) {
|
|
181
|
+
const { stdout } = await exec(`netstat -ano | findstr ":${port}" | findstr LISTENING`);
|
|
182
|
+
const pids = /* @__PURE__ */ new Set();
|
|
183
|
+
for (const line of stdout.trim().split("\n")) {
|
|
184
|
+
const parts = line.trim().split(/\s+/);
|
|
185
|
+
const pid = parts[parts.length - 1];
|
|
186
|
+
if (pid && pid !== "0") pids.add(pid);
|
|
187
|
+
}
|
|
188
|
+
if (pids.size === 0) return false;
|
|
189
|
+
for (const pid of pids) {
|
|
190
|
+
await exec(`taskkill /F /PID ${pid}`);
|
|
191
|
+
}
|
|
192
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
193
|
+
return true;
|
|
194
|
+
} else {
|
|
195
|
+
const { stdout } = await exec(`lsof -ti :${port}`);
|
|
196
|
+
const pids = stdout.trim().split("\n").filter(Boolean);
|
|
197
|
+
if (pids.length === 0) return false;
|
|
198
|
+
for (const pid of pids) {
|
|
199
|
+
await exec(`kill -9 ${pid}`);
|
|
200
|
+
}
|
|
201
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
202
|
+
return true;
|
|
165
203
|
}
|
|
166
|
-
await new Promise((r) => setTimeout(r, 500));
|
|
167
|
-
return true;
|
|
168
204
|
} catch {
|
|
169
205
|
return false;
|
|
170
206
|
}
|
|
@@ -187,7 +223,7 @@ async function preflight() {
|
|
|
187
223
|
const dockerInstalled = await isDockerInstalled();
|
|
188
224
|
if (!dockerInstalled) {
|
|
189
225
|
s.stop("Docker not found", 2);
|
|
190
|
-
const installCmd = process.platform === "darwin" ? "brew install --cask docker" : process.platform === "linux" ? "curl -fsSL https://get.docker.com | sh" : null;
|
|
226
|
+
const installCmd = process.platform === "darwin" ? "brew install --cask docker" : process.platform === "linux" ? "curl -fsSL https://get.docker.com | sh" : process.platform === "win32" ? "winget install Docker.DockerDesktop" : null;
|
|
191
227
|
if (installCmd) {
|
|
192
228
|
const install = await p2.confirm({
|
|
193
229
|
message: `Docker is required. Install it now? (${pc.cyan(installCmd)})`,
|
|
@@ -198,8 +234,11 @@ async function preflight() {
|
|
|
198
234
|
const { exec: exec2 } = await import("./exec-OIKPD6DO.js");
|
|
199
235
|
s.start("Installing Docker");
|
|
200
236
|
try {
|
|
201
|
-
await exec2(installCmd);
|
|
237
|
+
await exec2(installCmd, { timeout: 3e5 });
|
|
202
238
|
s.stop("Docker installed");
|
|
239
|
+
if (process.platform === "win32") {
|
|
240
|
+
p2.log.info("You may need to restart your terminal or log out/in for Docker to work.");
|
|
241
|
+
}
|
|
203
242
|
} catch (err) {
|
|
204
243
|
s.stop("Installation failed", 1);
|
|
205
244
|
p2.log.error(
|
|
@@ -397,8 +436,8 @@ async function activateLicense(code) {
|
|
|
397
436
|
body: JSON.stringify({ code })
|
|
398
437
|
});
|
|
399
438
|
if (!resp.ok) {
|
|
400
|
-
const
|
|
401
|
-
throw new Error(`Activation failed (${resp.status}): ${
|
|
439
|
+
const text8 = await resp.text().catch(() => "");
|
|
440
|
+
throw new Error(`Activation failed (${resp.status}): ${text8 || resp.statusText}`);
|
|
402
441
|
}
|
|
403
442
|
const data = await resp.json();
|
|
404
443
|
return data.license_key;
|
|
@@ -1662,28 +1701,58 @@ function readEnvVar(envPath, key) {
|
|
|
1662
1701
|
return null;
|
|
1663
1702
|
}
|
|
1664
1703
|
}
|
|
1704
|
+
function findLocalEnv() {
|
|
1705
|
+
const cwd = process.cwd();
|
|
1706
|
+
if (existsSync2(join3(cwd, ".env"))) return join3(cwd, ".env");
|
|
1707
|
+
if (existsSync2(join3(cwd, "clean", ".env"))) return join3(cwd, "clean", ".env");
|
|
1708
|
+
return null;
|
|
1709
|
+
}
|
|
1665
1710
|
async function connect() {
|
|
1666
1711
|
banner();
|
|
1667
1712
|
p15.intro(pc15.bgCyan(pc15.black(" Connect AI Tools ")));
|
|
1668
|
-
const cwd = process.cwd();
|
|
1669
|
-
const envPath = existsSync2(join3(cwd, ".env")) ? join3(cwd, ".env") : join3(cwd, "clean", ".env");
|
|
1670
|
-
if (!existsSync2(envPath)) {
|
|
1671
|
-
p15.log.error(
|
|
1672
|
-
`No Clean installation found.
|
|
1673
|
-
Run ${pc15.cyan("npx create-clean")} first, or cd into your installation directory.`
|
|
1674
|
-
);
|
|
1675
|
-
process.exit(1);
|
|
1676
|
-
}
|
|
1677
|
-
const apiKey = readEnvVar(envPath, "CLEAN_API_KEY") ?? "<your-api-key>";
|
|
1678
|
-
const authUrl = readEnvVar(envPath, "AUTH_URL") ?? "http://localhost:3000";
|
|
1679
|
-
const cleanPort = readEnvVar(envPath, "CLEAN_PORT") ?? "8000";
|
|
1680
1713
|
let engineUrl;
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1714
|
+
let apiKey;
|
|
1715
|
+
let dashboardUrl = null;
|
|
1716
|
+
const envPath = findLocalEnv();
|
|
1717
|
+
if (envPath) {
|
|
1718
|
+
const localApiKey = readEnvVar(envPath, "CLEAN_API_KEY");
|
|
1719
|
+
const authUrl = readEnvVar(envPath, "AUTH_URL") ?? "http://localhost:3000";
|
|
1720
|
+
const cleanPort = readEnvVar(envPath, "CLEAN_PORT") ?? "8000";
|
|
1721
|
+
try {
|
|
1722
|
+
const parsed = new URL(authUrl);
|
|
1723
|
+
parsed.port = cleanPort;
|
|
1724
|
+
engineUrl = parsed.toString().replace(/\/$/, "");
|
|
1725
|
+
} catch {
|
|
1726
|
+
engineUrl = `http://localhost:${cleanPort}`;
|
|
1727
|
+
}
|
|
1728
|
+
apiKey = localApiKey ?? "<your-api-key>";
|
|
1729
|
+
dashboardUrl = authUrl;
|
|
1730
|
+
p15.log.info(`Found local installation \u2014 using ${pc15.bold(engineUrl)}`);
|
|
1731
|
+
} else {
|
|
1732
|
+
p15.log.info("No local installation found. Enter your Clean server details.");
|
|
1733
|
+
const url = await p15.text({
|
|
1734
|
+
message: "Clean engine URL:",
|
|
1735
|
+
placeholder: "https://clean.yourcompany.com:8000",
|
|
1736
|
+
validate(value) {
|
|
1737
|
+
if (!value.trim()) return "URL is required";
|
|
1738
|
+
try {
|
|
1739
|
+
new URL(value);
|
|
1740
|
+
} catch {
|
|
1741
|
+
return "Invalid URL";
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
});
|
|
1745
|
+
if (isCancel(url)) cancelled();
|
|
1746
|
+
engineUrl = url.replace(/\/$/, "");
|
|
1747
|
+
const key = await p15.text({
|
|
1748
|
+
message: "API key:",
|
|
1749
|
+
placeholder: "your-api-key or clean_sk_...",
|
|
1750
|
+
validate(value) {
|
|
1751
|
+
if (!value.trim()) return "API key is required";
|
|
1752
|
+
}
|
|
1753
|
+
});
|
|
1754
|
+
if (isCancel(key)) cancelled();
|
|
1755
|
+
apiKey = key;
|
|
1687
1756
|
}
|
|
1688
1757
|
const sseUrl = `${engineUrl}/mcp/sse`;
|
|
1689
1758
|
const claudeConfig = JSON.stringify(
|
|
@@ -1703,7 +1772,7 @@ async function connect() {
|
|
|
1703
1772
|
p15.log.step(pc15.bold("MCP Server Connection"));
|
|
1704
1773
|
p15.log.info(
|
|
1705
1774
|
`${pc15.bold("Endpoint:")} ${pc15.cyan(sseUrl)}
|
|
1706
|
-
${pc15.bold("API Key:")} ${pc15.dim(apiKey.slice(0, 8) + "..." + apiKey.slice(-4))}`
|
|
1775
|
+
${pc15.bold("API Key:")} ${pc15.dim(apiKey.length > 12 ? apiKey.slice(0, 8) + "..." + apiKey.slice(-4) : apiKey)}`
|
|
1707
1776
|
);
|
|
1708
1777
|
p15.note(
|
|
1709
1778
|
`${pc15.bold("Claude Desktop / Claude Code")}
|
|
@@ -1718,11 +1787,13 @@ ${pc15.cyan(claudeConfig)}`,
|
|
|
1718
1787
|
Cursor: ${pc15.dim("Settings \u2192 MCP Servers \u2192 Add")}
|
|
1719
1788
|
VS Code: ${pc15.dim(".vscode/mcp.json or settings.json")}`
|
|
1720
1789
|
);
|
|
1721
|
-
|
|
1722
|
-
|
|
1790
|
+
if (dashboardUrl) {
|
|
1791
|
+
p15.log.info(
|
|
1792
|
+
`${pc15.bold("Dashboard API Keys")}
|
|
1723
1793
|
Create scoped API keys with repo restrictions at:
|
|
1724
|
-
${pc15.cyan(
|
|
1725
|
-
|
|
1794
|
+
${pc15.cyan(dashboardUrl)}`
|
|
1795
|
+
);
|
|
1796
|
+
}
|
|
1726
1797
|
const copy = await p15.confirm({
|
|
1727
1798
|
message: "Copy MCP config to clipboard?",
|
|
1728
1799
|
initialValue: true
|
|
@@ -1732,6 +1803,8 @@ ${pc15.cyan(claudeConfig)}`,
|
|
|
1732
1803
|
const { exec: exec2 } = await import("./exec-OIKPD6DO.js");
|
|
1733
1804
|
if (process.platform === "darwin") {
|
|
1734
1805
|
await exec2(`echo ${JSON.stringify(claudeConfig)} | pbcopy`);
|
|
1806
|
+
} else if (process.platform === "win32") {
|
|
1807
|
+
await exec2(`echo ${JSON.stringify(claudeConfig)} | clip`);
|
|
1735
1808
|
} else {
|
|
1736
1809
|
await exec2(`echo ${JSON.stringify(claudeConfig)} | xclip -selection clipboard`);
|
|
1737
1810
|
}
|
|
@@ -1763,38 +1836,46 @@ function findInstallation() {
|
|
|
1763
1836
|
return null;
|
|
1764
1837
|
}
|
|
1765
1838
|
async function smartStart() {
|
|
1766
|
-
const installDir = findInstallation();
|
|
1767
|
-
if (!installDir) {
|
|
1768
|
-
await setup();
|
|
1769
|
-
return;
|
|
1770
|
-
}
|
|
1771
1839
|
const p16 = await import("@clack/prompts");
|
|
1772
1840
|
const pc16 = (await import("picocolors")).default;
|
|
1773
1841
|
const { banner: banner2 } = await import("./ui-6FJJNJ7F.js");
|
|
1842
|
+
const installDir = findInstallation();
|
|
1774
1843
|
banner2();
|
|
1775
1844
|
p16.intro(pc16.bgCyan(pc16.black(" Clean ")));
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
p16.
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
await setup();
|
|
1794
|
-
|
|
1795
|
-
await
|
|
1845
|
+
if (installDir) {
|
|
1846
|
+
p16.log.info(`Existing installation found in ${pc16.bold(installDir)}`);
|
|
1847
|
+
const action = await p16.select({
|
|
1848
|
+
message: "What would you like to do?",
|
|
1849
|
+
options: [
|
|
1850
|
+
{ value: "status", label: "Check status", hint: "service health" },
|
|
1851
|
+
{ value: "update", label: "Update", hint: "pull latest images + restart" },
|
|
1852
|
+
{ value: "connect", label: "Connect AI tools", hint: "MCP setup for Claude, Cursor, etc." },
|
|
1853
|
+
{ value: "logs", label: "View logs", hint: "tail docker compose logs" },
|
|
1854
|
+
{ value: "backup", label: "Backup data", hint: "postgres + vector data" },
|
|
1855
|
+
{ value: "setup", label: "Re-run setup wizard", hint: "fresh install" }
|
|
1856
|
+
]
|
|
1857
|
+
});
|
|
1858
|
+
if (p16.isCancel(action)) {
|
|
1859
|
+
p16.outro("Bye!");
|
|
1860
|
+
return;
|
|
1861
|
+
}
|
|
1862
|
+
if (action === "setup") await setup();
|
|
1863
|
+
else if (action === "connect") await connect();
|
|
1864
|
+
else await commands[action]();
|
|
1796
1865
|
} else {
|
|
1797
|
-
await
|
|
1866
|
+
const action = await p16.select({
|
|
1867
|
+
message: "What would you like to do?",
|
|
1868
|
+
options: [
|
|
1869
|
+
{ value: "connect", label: "Connect to Clean server", hint: "set up MCP for your AI tools" },
|
|
1870
|
+
{ value: "setup", label: "Host Clean server", hint: "self-hosted setup wizard (needs Docker)" }
|
|
1871
|
+
]
|
|
1872
|
+
});
|
|
1873
|
+
if (p16.isCancel(action)) {
|
|
1874
|
+
p16.outro("Bye!");
|
|
1875
|
+
return;
|
|
1876
|
+
}
|
|
1877
|
+
if (action === "connect") await connect();
|
|
1878
|
+
else await setup();
|
|
1798
1879
|
}
|
|
1799
1880
|
}
|
|
1800
1881
|
async function main() {
|