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.
Files changed (3) hide show
  1. package/README.md +22 -0
  2. package/dist/index.js +105 -0
  3. 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
+ }