@tryclean/create 0.1.2 → 0.1.3
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 +219 -38
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -662,6 +662,9 @@ async function installTailscale() {
|
|
|
662
662
|
case "linux":
|
|
663
663
|
await exec("curl -fsSL https://tailscale.com/install.sh | sh");
|
|
664
664
|
break;
|
|
665
|
+
case "win32":
|
|
666
|
+
await exec("winget install Tailscale.Tailscale", { timeout: 12e4 });
|
|
667
|
+
break;
|
|
665
668
|
default:
|
|
666
669
|
throw new Error("Unsupported platform for auto-install");
|
|
667
670
|
}
|
|
@@ -719,6 +722,18 @@ async function startDaemon() {
|
|
|
719
722
|
}
|
|
720
723
|
if (await waitForDaemon(5e3)) return tried;
|
|
721
724
|
}
|
|
725
|
+
} else if (process.platform === "win32") {
|
|
726
|
+
tried.push("net start Tailscale");
|
|
727
|
+
try {
|
|
728
|
+
await exec("net start Tailscale", { timeout: 1e4 });
|
|
729
|
+
} catch {
|
|
730
|
+
}
|
|
731
|
+
if (await waitForDaemon(5e3)) return tried;
|
|
732
|
+
tried.push("start Tailscale");
|
|
733
|
+
try {
|
|
734
|
+
await exec(`start "" "C:\\Program Files\\Tailscale\\tailscale-ipn.exe"`, { timeout: 5e3 });
|
|
735
|
+
} catch {
|
|
736
|
+
}
|
|
722
737
|
} else if (process.platform === "linux") {
|
|
723
738
|
tried.push("sudo systemctl enable --now tailscaled");
|
|
724
739
|
try {
|
|
@@ -775,6 +790,9 @@ async function installCloudflared() {
|
|
|
775
790
|
"curl -fsSL https://pkg.cloudflare.com/cloudflared-linux-amd64.deb -o /tmp/cloudflared.deb && sudo dpkg -i /tmp/cloudflared.deb"
|
|
776
791
|
);
|
|
777
792
|
break;
|
|
793
|
+
case "win32":
|
|
794
|
+
await exec("winget install Cloudflare.cloudflared", { timeout: 12e4 });
|
|
795
|
+
break;
|
|
778
796
|
default:
|
|
779
797
|
throw new Error("Unsupported platform for auto-install");
|
|
780
798
|
}
|
|
@@ -1729,31 +1747,190 @@ async function connect() {
|
|
|
1729
1747
|
dashboardUrl = authUrl;
|
|
1730
1748
|
p15.log.info(`Found local installation \u2014 using ${pc15.bold(engineUrl)}`);
|
|
1731
1749
|
} else {
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1750
|
+
const method = await p15.select({
|
|
1751
|
+
message: "How do you connect to your Clean server?",
|
|
1752
|
+
options: [
|
|
1753
|
+
{ value: "tailscale", label: "Tailscale", hint: "server is on a Tailscale network" },
|
|
1754
|
+
{ value: "url", label: "I have a URL", hint: "direct URL or Cloudflare tunnel" }
|
|
1755
|
+
]
|
|
1756
|
+
});
|
|
1757
|
+
if (isCancel(method)) cancelled();
|
|
1758
|
+
if (method === "tailscale") {
|
|
1759
|
+
const result = await handleTailscaleConnect();
|
|
1760
|
+
engineUrl = result.engineUrl;
|
|
1761
|
+
apiKey = result.apiKey;
|
|
1762
|
+
} else {
|
|
1763
|
+
const url = await p15.text({
|
|
1764
|
+
message: "Clean engine URL:",
|
|
1765
|
+
placeholder: "https://clean.yourcompany.com:8000",
|
|
1766
|
+
validate(value) {
|
|
1767
|
+
if (!value.trim()) return "URL is required";
|
|
1768
|
+
try {
|
|
1769
|
+
new URL(value);
|
|
1770
|
+
} catch {
|
|
1771
|
+
return "Invalid URL";
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
});
|
|
1775
|
+
if (isCancel(url)) cancelled();
|
|
1776
|
+
engineUrl = url.replace(/\/$/, "");
|
|
1777
|
+
const key = await p15.text({
|
|
1778
|
+
message: "API key:",
|
|
1779
|
+
placeholder: "your-api-key or clean_sk_...",
|
|
1780
|
+
validate(value) {
|
|
1781
|
+
if (!value.trim()) return "API key is required";
|
|
1742
1782
|
}
|
|
1783
|
+
});
|
|
1784
|
+
if (isCancel(key)) cancelled();
|
|
1785
|
+
apiKey = key;
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
showMcpConfig(engineUrl, apiKey, dashboardUrl);
|
|
1789
|
+
}
|
|
1790
|
+
async function handleTailscaleConnect() {
|
|
1791
|
+
let installed = await isTailscaleInstalled();
|
|
1792
|
+
if (!installed) {
|
|
1793
|
+
const s2 = p15.spinner();
|
|
1794
|
+
s2.start("Installing Tailscale");
|
|
1795
|
+
try {
|
|
1796
|
+
await installTailscale();
|
|
1797
|
+
installed = await isTailscaleInstalled();
|
|
1798
|
+
if (installed) {
|
|
1799
|
+
s2.stop("Tailscale installed");
|
|
1800
|
+
} else {
|
|
1801
|
+
s2.stop("Installation may have failed", 2);
|
|
1743
1802
|
}
|
|
1744
|
-
})
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1803
|
+
} catch (err) {
|
|
1804
|
+
s2.stop("Installation failed", 1);
|
|
1805
|
+
p15.log.error(`Could not install Tailscale: ${err.message}`);
|
|
1806
|
+
p15.log.info(
|
|
1807
|
+
`Install manually:
|
|
1808
|
+
macOS: ${pc15.cyan("brew install tailscale")}
|
|
1809
|
+
Linux: ${pc15.cyan("curl -fsSL https://tailscale.com/install.sh | sh")}
|
|
1810
|
+
Windows: ${pc15.cyan("winget install Tailscale.Tailscale")}`
|
|
1811
|
+
);
|
|
1812
|
+
process.exit(1);
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
let daemonUp = await isDaemonRunning();
|
|
1816
|
+
if (!daemonUp) {
|
|
1817
|
+
const s2 = p15.spinner();
|
|
1818
|
+
s2.start("Starting Tailscale daemon");
|
|
1819
|
+
const tried = await startDaemon();
|
|
1820
|
+
daemonUp = await waitForDaemon(1e4);
|
|
1821
|
+
if (daemonUp) {
|
|
1822
|
+
s2.stop("Tailscale daemon running");
|
|
1823
|
+
} else {
|
|
1824
|
+
s2.stop("Automatic start methods exhausted", 2);
|
|
1825
|
+
p15.log.warn(
|
|
1826
|
+
`Tried: ${tried.map((t) => pc15.dim(t)).join(", ")}
|
|
1827
|
+
tailscaled needs to be running before we can continue.`
|
|
1828
|
+
);
|
|
1829
|
+
while (!daemonUp) {
|
|
1830
|
+
const openTerminal = process.platform === "darwin";
|
|
1831
|
+
const action = await p15.select({
|
|
1832
|
+
message: "tailscaled isn't running yet. What to do?",
|
|
1833
|
+
options: [
|
|
1834
|
+
...openTerminal ? [{ value: "open", label: "Open new terminal with sudo tailscaled", hint: "recommended" }] : [],
|
|
1835
|
+
{ value: "manual", label: "I'll start it myself", hint: "run in another tab/window" },
|
|
1836
|
+
{ value: "cancel", label: "Cancel" }
|
|
1837
|
+
]
|
|
1838
|
+
});
|
|
1839
|
+
if (isCancel(action)) cancelled();
|
|
1840
|
+
if (action === "open") {
|
|
1841
|
+
try {
|
|
1842
|
+
const { exec: exec2 } = await import("./exec-OIKPD6DO.js");
|
|
1843
|
+
await exec2(`osascript -e 'tell app "Terminal" to do script "sudo tailscaled"'`);
|
|
1844
|
+
p15.log.info("Opened a new Terminal window \u2014 enter your password there.");
|
|
1845
|
+
} catch {
|
|
1846
|
+
p15.log.warn(`Run in another tab: ${pc15.cyan("sudo tailscaled")}`);
|
|
1847
|
+
}
|
|
1848
|
+
const s22 = p15.spinner();
|
|
1849
|
+
s22.start("Waiting for tailscaled (30s)");
|
|
1850
|
+
daemonUp = await waitForDaemon(3e4);
|
|
1851
|
+
s22.stop(daemonUp ? "Tailscale daemon running" : "Still not responding");
|
|
1852
|
+
} else if (action === "manual") {
|
|
1853
|
+
p15.log.info(`In another terminal, run: ${pc15.cyan("sudo tailscaled")}`);
|
|
1854
|
+
const s22 = p15.spinner();
|
|
1855
|
+
s22.start("Waiting for tailscaled (30s)");
|
|
1856
|
+
daemonUp = await waitForDaemon(3e4);
|
|
1857
|
+
s22.stop(daemonUp ? "Tailscale daemon detected" : "Still not detected");
|
|
1858
|
+
} else {
|
|
1859
|
+
cancelled();
|
|
1860
|
+
}
|
|
1752
1861
|
}
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1864
|
+
const connected = await isTailscaleUp();
|
|
1865
|
+
if (!connected) {
|
|
1866
|
+
p15.log.info("Tailscale needs to authenticate. This will open your browser.");
|
|
1867
|
+
const proceed = await p15.confirm({
|
|
1868
|
+
message: "Run tailscale up now?",
|
|
1869
|
+
initialValue: true
|
|
1753
1870
|
});
|
|
1754
|
-
if (isCancel(
|
|
1755
|
-
|
|
1871
|
+
if (isCancel(proceed)) cancelled();
|
|
1872
|
+
if (proceed) {
|
|
1873
|
+
p15.log.info(pc15.dim("Running tailscale up... (complete auth in browser)"));
|
|
1874
|
+
try {
|
|
1875
|
+
connectTailscale();
|
|
1876
|
+
} catch (err) {
|
|
1877
|
+
p15.log.error(`tailscale up failed: ${err.message}`);
|
|
1878
|
+
process.exit(1);
|
|
1879
|
+
}
|
|
1880
|
+
if (!await isTailscaleUp()) {
|
|
1881
|
+
p15.log.error("Tailscale doesn't appear connected. Try running `tailscale up` manually.");
|
|
1882
|
+
process.exit(1);
|
|
1883
|
+
}
|
|
1884
|
+
} else {
|
|
1885
|
+
cancelled();
|
|
1886
|
+
}
|
|
1756
1887
|
}
|
|
1888
|
+
const myIp = await getTailscaleIp();
|
|
1889
|
+
if (myIp) {
|
|
1890
|
+
p15.log.success(`Connected to Tailscale \u2014 your IP: ${pc15.bold(myIp)}`);
|
|
1891
|
+
} else {
|
|
1892
|
+
p15.log.success("Connected to Tailscale");
|
|
1893
|
+
}
|
|
1894
|
+
const serverIp = await p15.text({
|
|
1895
|
+
message: "Clean server's Tailscale IP (or hostname):",
|
|
1896
|
+
placeholder: "100.x.y.z",
|
|
1897
|
+
validate(value) {
|
|
1898
|
+
if (!value.trim()) return "Server IP is required";
|
|
1899
|
+
}
|
|
1900
|
+
});
|
|
1901
|
+
if (isCancel(serverIp)) cancelled();
|
|
1902
|
+
const port = await p15.text({
|
|
1903
|
+
message: "Engine port:",
|
|
1904
|
+
placeholder: "8000",
|
|
1905
|
+
initialValue: "8000"
|
|
1906
|
+
});
|
|
1907
|
+
if (isCancel(port)) cancelled();
|
|
1908
|
+
const engineUrl = `http://${serverIp.trim()}:${port.trim()}`;
|
|
1909
|
+
const key = await p15.text({
|
|
1910
|
+
message: "API key:",
|
|
1911
|
+
placeholder: "your-api-key or clean_sk_...",
|
|
1912
|
+
validate(value) {
|
|
1913
|
+
if (!value.trim()) return "API key is required";
|
|
1914
|
+
}
|
|
1915
|
+
});
|
|
1916
|
+
if (isCancel(key)) cancelled();
|
|
1917
|
+
const s = p15.spinner();
|
|
1918
|
+
s.start(`Testing connection to ${engineUrl}`);
|
|
1919
|
+
try {
|
|
1920
|
+
const resp = await fetch(`${engineUrl}/health`, { signal: AbortSignal.timeout(5e3) });
|
|
1921
|
+
if (resp.ok) {
|
|
1922
|
+
s.stop("Server reachable");
|
|
1923
|
+
} else {
|
|
1924
|
+
s.stop(`Server responded with ${resp.status}`, 2);
|
|
1925
|
+
p15.log.warn("Server may not be healthy, but continuing with config generation.");
|
|
1926
|
+
}
|
|
1927
|
+
} catch {
|
|
1928
|
+
s.stop("Could not reach server", 2);
|
|
1929
|
+
p15.log.warn("Make sure the server is running. Continuing with config generation.");
|
|
1930
|
+
}
|
|
1931
|
+
return { engineUrl, apiKey: key };
|
|
1932
|
+
}
|
|
1933
|
+
function showMcpConfig(engineUrl, apiKey, dashboardUrl) {
|
|
1757
1934
|
const sseUrl = `${engineUrl}/mcp/sse`;
|
|
1758
1935
|
const claudeConfig = JSON.stringify(
|
|
1759
1936
|
{
|
|
@@ -1783,7 +1960,7 @@ ${pc15.cyan(claudeConfig)}`,
|
|
|
1783
1960
|
);
|
|
1784
1961
|
p15.log.info(
|
|
1785
1962
|
`${pc15.bold("Cursor / VS Code")}
|
|
1786
|
-
Add the same config to your MCP settings
|
|
1963
|
+
Add the same config to your MCP settings.
|
|
1787
1964
|
Cursor: ${pc15.dim("Settings \u2192 MCP Servers \u2192 Add")}
|
|
1788
1965
|
VS Code: ${pc15.dim(".vscode/mcp.json or settings.json")}`
|
|
1789
1966
|
);
|
|
@@ -1794,25 +1971,29 @@ ${pc15.cyan(claudeConfig)}`,
|
|
|
1794
1971
|
${pc15.cyan(dashboardUrl)}`
|
|
1795
1972
|
);
|
|
1796
1973
|
}
|
|
1797
|
-
const
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1974
|
+
const copySync = async () => {
|
|
1975
|
+
const copy = await p15.confirm({
|
|
1976
|
+
message: "Copy MCP config to clipboard?",
|
|
1977
|
+
initialValue: true
|
|
1978
|
+
});
|
|
1979
|
+
if (!p15.isCancel(copy) && copy) {
|
|
1980
|
+
try {
|
|
1981
|
+
const { exec: exec2 } = await import("./exec-OIKPD6DO.js");
|
|
1982
|
+
const escaped = JSON.stringify(claudeConfig);
|
|
1983
|
+
if (process.platform === "darwin") {
|
|
1984
|
+
await exec2(`echo ${escaped} | pbcopy`);
|
|
1985
|
+
} else if (process.platform === "win32") {
|
|
1986
|
+
await exec2(`echo ${escaped} | clip`);
|
|
1987
|
+
} else {
|
|
1988
|
+
await exec2(`echo ${escaped} | xclip -selection clipboard`);
|
|
1989
|
+
}
|
|
1990
|
+
p15.log.success("Config copied to clipboard");
|
|
1991
|
+
} catch {
|
|
1992
|
+
p15.log.warn("Could not copy to clipboard \u2014 copy the config above manually");
|
|
1810
1993
|
}
|
|
1811
|
-
p15.log.success("Config copied to clipboard");
|
|
1812
|
-
} catch {
|
|
1813
|
-
p15.log.warn("Could not copy to clipboard \u2014 copy the config above manually");
|
|
1814
1994
|
}
|
|
1815
|
-
}
|
|
1995
|
+
};
|
|
1996
|
+
copySync();
|
|
1816
1997
|
p15.outro(pc15.green("Connect your AI tools and start searching!"));
|
|
1817
1998
|
}
|
|
1818
1999
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tryclean/create",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Set up Clean — semantic code search for AI agents",
|
|
5
5
|
"bin": {
|
|
6
6
|
"create-clean": "./dist/index.js",
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
15
|
"@clack/prompts": "^0.9",
|
|
16
|
+
"@tryclean/create": "^0.1.2",
|
|
16
17
|
"picocolors": "^1.1"
|
|
17
18
|
},
|
|
18
19
|
"devDependencies": {
|