sprouts-cli 0.1.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/README.md +22 -0
- package/dist/index.js +105 -0
- package/package.json +28 -0
package/README.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# sprouts-cli
|
|
2
|
+
|
|
3
|
+
Terminal helper for **Sprouts IDE pairing**: starts a device session, opens the browser to `/ide/connect`, and saves the IDE access token to `~/.sprouts/ide-token.json`.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx sprouts-cli login
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Environment
|
|
12
|
+
|
|
13
|
+
- `SPROUTS_API_URL` — API base URL (default `https://api.getsprouts.io`).
|
|
14
|
+
|
|
15
|
+
## Publish
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm run build
|
|
19
|
+
npm publish --access public
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
(Published as unscoped `sprouts-cli` because the `@sprouts` npm org was not available to the publishing account.)
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import * as fs from "node:fs";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
const DEFAULT_API = "https://api.getsprouts.io";
|
|
6
|
+
function getApiBase() {
|
|
7
|
+
const u = process.env.SPROUTS_API_URL?.replace(/\/$/, "");
|
|
8
|
+
return u || DEFAULT_API;
|
|
9
|
+
}
|
|
10
|
+
function openUrl(url) {
|
|
11
|
+
const platform = process.platform;
|
|
12
|
+
const cmd = platform === "darwin"
|
|
13
|
+
? "open"
|
|
14
|
+
: platform === "win32"
|
|
15
|
+
? "start"
|
|
16
|
+
: "xdg-open";
|
|
17
|
+
if (platform === "win32") {
|
|
18
|
+
spawn("cmd", ["/c", "start", "", url], { stdio: "ignore", detached: true });
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
spawn(cmd, [url], { stdio: "ignore", detached: true });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function tokenPath() {
|
|
25
|
+
const home = process.env.HOME || process.env.USERPROFILE || ".";
|
|
26
|
+
const dir = path.join(home, ".sprouts");
|
|
27
|
+
return path.join(dir, "ide-token.json");
|
|
28
|
+
}
|
|
29
|
+
async function pollToken(api, deviceCode, intervalSec) {
|
|
30
|
+
const maxMs = 15 * 60 * 1000;
|
|
31
|
+
const start = Date.now();
|
|
32
|
+
while (Date.now() - start < maxMs) {
|
|
33
|
+
await new Promise((r) => setTimeout(r, intervalSec * 1000));
|
|
34
|
+
const res = await fetch(`${api}/api/ide/device/token`, {
|
|
35
|
+
method: "POST",
|
|
36
|
+
headers: { "Content-Type": "application/json" },
|
|
37
|
+
body: JSON.stringify({ device_code: deviceCode }),
|
|
38
|
+
});
|
|
39
|
+
const data = (await res.json());
|
|
40
|
+
if (res.status === 410) {
|
|
41
|
+
throw new Error(data.error || "Pairing expired. Run login again.");
|
|
42
|
+
}
|
|
43
|
+
if (!res.ok && res.status !== 200) {
|
|
44
|
+
throw new Error(data.error || `Token poll failed (${res.status})`);
|
|
45
|
+
}
|
|
46
|
+
if (data.status === "complete" && data.access_token) {
|
|
47
|
+
return { access_token: data.access_token };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
throw new Error("Timed out waiting for browser sign-in.");
|
|
51
|
+
}
|
|
52
|
+
async function cmdLogin() {
|
|
53
|
+
const api = getApiBase();
|
|
54
|
+
console.log(`Using API: ${api}`);
|
|
55
|
+
const startRes = await fetch(`${api}/api/ide/device/start`, {
|
|
56
|
+
method: "POST",
|
|
57
|
+
headers: { "Content-Type": "application/json" },
|
|
58
|
+
});
|
|
59
|
+
if (!startRes.ok) {
|
|
60
|
+
const err = await startRes.text();
|
|
61
|
+
throw new Error(`device/start failed: ${startRes.status} ${err}`);
|
|
62
|
+
}
|
|
63
|
+
const start = (await startRes.json());
|
|
64
|
+
console.log("\nOpening browser to sign in with Google or Apple…\n");
|
|
65
|
+
console.log(start.verification_uri);
|
|
66
|
+
openUrl(start.verification_uri);
|
|
67
|
+
const interval = start.interval && start.interval >= 2 ? start.interval : 5;
|
|
68
|
+
console.log("\nWaiting for you to finish in the browser (Ctrl+C to cancel)…\n");
|
|
69
|
+
const { access_token } = await pollToken(api, start.device_code, interval);
|
|
70
|
+
const dir = path.dirname(tokenPath());
|
|
71
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
72
|
+
fs.writeFileSync(tokenPath(), JSON.stringify({ access_token: access_token, savedAt: new Date().toISOString() }, null, 2), "utf8");
|
|
73
|
+
console.log("\nDone. Token saved to:", tokenPath());
|
|
74
|
+
console.log("Paste this token into the Sprouts extension settings if prompted, or sign in from the extension UI.\n");
|
|
75
|
+
}
|
|
76
|
+
function printHelp() {
|
|
77
|
+
console.log(`
|
|
78
|
+
Sprouts CLI — pair your editor session
|
|
79
|
+
|
|
80
|
+
Usage:
|
|
81
|
+
npx sprouts-cli login Start browser sign-in and save IDE token to ~/.sprouts/ide-token.json
|
|
82
|
+
|
|
83
|
+
Environment:
|
|
84
|
+
SPROUTS_API_URL Backend base URL (default: ${DEFAULT_API})
|
|
85
|
+
`);
|
|
86
|
+
}
|
|
87
|
+
async function main() {
|
|
88
|
+
const argv = process.argv.slice(2);
|
|
89
|
+
const cmd = argv[0] || "login";
|
|
90
|
+
if (cmd === "help" || cmd === "-h" || cmd === "--help") {
|
|
91
|
+
printHelp();
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (cmd === "login") {
|
|
95
|
+
await cmdLogin();
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
console.error("Unknown command:", cmd);
|
|
99
|
+
printHelp();
|
|
100
|
+
process.exitCode = 1;
|
|
101
|
+
}
|
|
102
|
+
main().catch((e) => {
|
|
103
|
+
console.error(e instanceof Error ? e.message : e);
|
|
104
|
+
process.exitCode = 1;
|
|
105
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "sprouts-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Sprouts CLI — IDE pairing (opens browser to sign in)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"sprouts": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsc",
|
|
14
|
+
"prepublishOnly": "npm run build"
|
|
15
|
+
},
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=18"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"sprouts",
|
|
21
|
+
"cursor",
|
|
22
|
+
"vscode"
|
|
23
|
+
],
|
|
24
|
+
"license": "UNLICENSED",
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"typescript": "^5.8.3"
|
|
27
|
+
}
|
|
28
|
+
}
|