glop.dev 0.5.0 → 0.7.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/dist/index.js +413 -168
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { Command as
|
|
4
|
+
import { Command as Command8 } from "commander";
|
|
5
5
|
|
|
6
6
|
// src/commands/auth.ts
|
|
7
7
|
import { Command } from "commander";
|
|
@@ -11,6 +11,70 @@ import fs from "fs";
|
|
|
11
11
|
import path from "path";
|
|
12
12
|
import os from "os";
|
|
13
13
|
import crypto from "crypto";
|
|
14
|
+
|
|
15
|
+
// src/lib/git.ts
|
|
16
|
+
import { execSync } from "child_process";
|
|
17
|
+
function getRepoRoot() {
|
|
18
|
+
try {
|
|
19
|
+
return execSync("git rev-parse --show-toplevel", {
|
|
20
|
+
encoding: "utf-8",
|
|
21
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
22
|
+
}).trim();
|
|
23
|
+
} catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function getRepoKey() {
|
|
28
|
+
try {
|
|
29
|
+
const remote = execSync("git remote get-url origin", {
|
|
30
|
+
encoding: "utf-8",
|
|
31
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
32
|
+
}).trim();
|
|
33
|
+
const match = remote.match(
|
|
34
|
+
/(?:github\.com|gitlab\.com|bitbucket\.org)[/:](.+?)(?:\.git)?$/
|
|
35
|
+
);
|
|
36
|
+
if (match) return match[1];
|
|
37
|
+
const parts = remote.split("/").filter(Boolean);
|
|
38
|
+
if (parts.length >= 2) {
|
|
39
|
+
return `${parts[parts.length - 2]}/${parts[parts.length - 1].replace(".git", "")}`;
|
|
40
|
+
}
|
|
41
|
+
return remote;
|
|
42
|
+
} catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function getBranch() {
|
|
47
|
+
try {
|
|
48
|
+
return execSync("git rev-parse --abbrev-ref HEAD", {
|
|
49
|
+
encoding: "utf-8",
|
|
50
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
51
|
+
}).trim();
|
|
52
|
+
} catch {
|
|
53
|
+
return "noname";
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function getGitUserName() {
|
|
57
|
+
try {
|
|
58
|
+
return execSync("git config user.name", {
|
|
59
|
+
encoding: "utf-8",
|
|
60
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
61
|
+
}).trim() || null;
|
|
62
|
+
} catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function getGitUserEmail() {
|
|
67
|
+
try {
|
|
68
|
+
return execSync("git config user.email", {
|
|
69
|
+
encoding: "utf-8",
|
|
70
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
71
|
+
}).trim() || null;
|
|
72
|
+
} catch {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// src/lib/config.ts
|
|
14
78
|
var CONFIG_DIR = path.join(os.homedir(), ".glop");
|
|
15
79
|
var CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
|
|
16
80
|
function ensureConfigDir() {
|
|
@@ -28,7 +92,7 @@ function getMachineId() {
|
|
|
28
92
|
fs.writeFileSync(machineIdFile, machineId);
|
|
29
93
|
return machineId;
|
|
30
94
|
}
|
|
31
|
-
function
|
|
95
|
+
function loadGlobalConfig() {
|
|
32
96
|
if (!fs.existsSync(CONFIG_FILE)) return null;
|
|
33
97
|
try {
|
|
34
98
|
const raw = fs.readFileSync(CONFIG_FILE, "utf-8");
|
|
@@ -37,15 +101,58 @@ function loadConfig() {
|
|
|
37
101
|
return null;
|
|
38
102
|
}
|
|
39
103
|
}
|
|
40
|
-
function
|
|
104
|
+
function saveGlobalConfig(config) {
|
|
41
105
|
ensureConfigDir();
|
|
42
106
|
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
43
107
|
}
|
|
108
|
+
function loadRepoConfig() {
|
|
109
|
+
const repoRoot = getRepoRoot();
|
|
110
|
+
if (!repoRoot) return null;
|
|
111
|
+
const repoConfigFile = path.join(repoRoot, ".glop", "config.json");
|
|
112
|
+
if (!fs.existsSync(repoConfigFile)) return null;
|
|
113
|
+
try {
|
|
114
|
+
const raw = fs.readFileSync(repoConfigFile, "utf-8");
|
|
115
|
+
return JSON.parse(raw);
|
|
116
|
+
} catch {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function saveRepoConfig(config) {
|
|
121
|
+
const repoRoot = getRepoRoot();
|
|
122
|
+
if (!repoRoot) throw new Error("Not in a git repository");
|
|
123
|
+
const repoConfigDir = path.join(repoRoot, ".glop");
|
|
124
|
+
if (!fs.existsSync(repoConfigDir)) {
|
|
125
|
+
fs.mkdirSync(repoConfigDir, { recursive: true });
|
|
126
|
+
}
|
|
127
|
+
fs.writeFileSync(
|
|
128
|
+
path.join(repoConfigDir, "config.json"),
|
|
129
|
+
JSON.stringify(config, null, 2)
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
function loadConfig() {
|
|
133
|
+
const global = loadGlobalConfig();
|
|
134
|
+
if (!global || Object.keys(global.workspaces).length === 0) return null;
|
|
135
|
+
const repoConfig = loadRepoConfig();
|
|
136
|
+
const workspaceId = repoConfig?.workspace_id || global.default_workspace || Object.keys(global.workspaces)[0];
|
|
137
|
+
if (!workspaceId) return null;
|
|
138
|
+
const ws = global.workspaces[workspaceId];
|
|
139
|
+
if (!ws) return null;
|
|
140
|
+
return {
|
|
141
|
+
server_url: global.server_url,
|
|
142
|
+
api_key: ws.api_key,
|
|
143
|
+
developer_id: ws.developer_id,
|
|
144
|
+
developer_name: global.developer_name,
|
|
145
|
+
machine_id: global.machine_id,
|
|
146
|
+
workspace_id: workspaceId,
|
|
147
|
+
workspace_name: ws.workspace_name,
|
|
148
|
+
workspace_slug: ws.workspace_slug
|
|
149
|
+
};
|
|
150
|
+
}
|
|
44
151
|
function getDefaultServerUrl() {
|
|
45
152
|
return process.env.GLOP_SERVER_URL || "https://www.glop.dev";
|
|
46
153
|
}
|
|
47
154
|
|
|
48
|
-
// src/
|
|
155
|
+
// src/lib/auth-flow.ts
|
|
49
156
|
import http from "http";
|
|
50
157
|
import { exec } from "child_process";
|
|
51
158
|
function openBrowser(url) {
|
|
@@ -67,39 +174,6 @@ function findOpenPort() {
|
|
|
67
174
|
});
|
|
68
175
|
});
|
|
69
176
|
}
|
|
70
|
-
var authCommand = new Command("auth").description("Authenticate with a glop server").option("-s, --server <url>", "Server URL").action(async (opts) => {
|
|
71
|
-
const serverUrl = (opts.server || getDefaultServerUrl()).replace(/\/+$/, "");
|
|
72
|
-
const port = await findOpenPort();
|
|
73
|
-
const machineId = getMachineId();
|
|
74
|
-
console.log("Opening browser for authentication...");
|
|
75
|
-
console.log(
|
|
76
|
-
"If the browser doesn't open, visit this URL manually:"
|
|
77
|
-
);
|
|
78
|
-
const authUrl = `${serverUrl}/cli-auth?port=${port}`;
|
|
79
|
-
console.log(` ${authUrl}
|
|
80
|
-
`);
|
|
81
|
-
console.log("Waiting for authorization...");
|
|
82
|
-
openBrowser(authUrl);
|
|
83
|
-
const result = await waitForCallback(port);
|
|
84
|
-
saveConfig({
|
|
85
|
-
server_url: serverUrl,
|
|
86
|
-
api_key: result.api_key,
|
|
87
|
-
developer_id: result.developer_id,
|
|
88
|
-
developer_name: result.developer_name,
|
|
89
|
-
machine_id: machineId
|
|
90
|
-
});
|
|
91
|
-
console.log("\nAuthenticated successfully!");
|
|
92
|
-
console.log(` Developer: ${result.developer_name}`);
|
|
93
|
-
console.log(` Server: ${serverUrl}`);
|
|
94
|
-
console.log(` Machine: ${machineId.slice(0, 8)}...`);
|
|
95
|
-
console.log(`
|
|
96
|
-
API key saved to ~/.glop/config.json`);
|
|
97
|
-
console.log(
|
|
98
|
-
`
|
|
99
|
-
\u2192 Run \`glop init\` in a repo to start streaming sessions.`
|
|
100
|
-
);
|
|
101
|
-
process.exit(0);
|
|
102
|
-
});
|
|
103
177
|
function waitForCallback(port) {
|
|
104
178
|
return new Promise((resolve, reject) => {
|
|
105
179
|
const timeout = setTimeout(() => {
|
|
@@ -129,7 +203,10 @@ function waitForCallback(port) {
|
|
|
129
203
|
resolve({
|
|
130
204
|
api_key: apiKey,
|
|
131
205
|
developer_id: developerId,
|
|
132
|
-
developer_name: developerName
|
|
206
|
+
developer_name: developerName,
|
|
207
|
+
workspace_id: url.searchParams.get("workspace_id") || void 0,
|
|
208
|
+
workspace_name: url.searchParams.get("workspace_name") || void 0,
|
|
209
|
+
workspace_slug: url.searchParams.get("workspace_slug") || void 0
|
|
133
210
|
});
|
|
134
211
|
return;
|
|
135
212
|
}
|
|
@@ -157,72 +234,59 @@ h1{margin:0 0 1rem;font-size:1.25rem}</style></head>
|
|
|
157
234
|
</html>`;
|
|
158
235
|
}
|
|
159
236
|
|
|
160
|
-
// src/commands/
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
}).trim();
|
|
200
|
-
} catch {
|
|
201
|
-
return "noname";
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
function getGitUserName() {
|
|
205
|
-
try {
|
|
206
|
-
return execSync("git config user.name", {
|
|
207
|
-
encoding: "utf-8",
|
|
208
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
209
|
-
}).trim() || null;
|
|
210
|
-
} catch {
|
|
211
|
-
return null;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
function getGitUserEmail() {
|
|
215
|
-
try {
|
|
216
|
-
return execSync("git config user.email", {
|
|
217
|
-
encoding: "utf-8",
|
|
218
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
219
|
-
}).trim() || null;
|
|
220
|
-
} catch {
|
|
221
|
-
return null;
|
|
237
|
+
// src/commands/auth.ts
|
|
238
|
+
var authCommand = new Command("auth").description("Authenticate with a glop server").option("-s, --server <url>", "Server URL").action(async (opts) => {
|
|
239
|
+
const serverUrl = (opts.server || getDefaultServerUrl()).replace(/\/+$/, "");
|
|
240
|
+
const port = await findOpenPort();
|
|
241
|
+
const machineId = getMachineId();
|
|
242
|
+
console.log("Opening browser for authentication...");
|
|
243
|
+
console.log(
|
|
244
|
+
"If the browser doesn't open, visit this URL manually:"
|
|
245
|
+
);
|
|
246
|
+
const authUrl = `${serverUrl}/cli-auth?port=${port}`;
|
|
247
|
+
console.log(` ${authUrl}
|
|
248
|
+
`);
|
|
249
|
+
console.log("Waiting for authorization...");
|
|
250
|
+
openBrowser(authUrl);
|
|
251
|
+
const result = await waitForCallback(port);
|
|
252
|
+
const existing = loadGlobalConfig();
|
|
253
|
+
const globalConfig = existing || {
|
|
254
|
+
server_url: serverUrl,
|
|
255
|
+
machine_id: machineId,
|
|
256
|
+
developer_name: result.developer_name,
|
|
257
|
+
workspaces: {}
|
|
258
|
+
};
|
|
259
|
+
globalConfig.server_url = serverUrl;
|
|
260
|
+
globalConfig.machine_id = machineId;
|
|
261
|
+
globalConfig.developer_name = result.developer_name;
|
|
262
|
+
if (result.workspace_id) {
|
|
263
|
+
globalConfig.workspaces[result.workspace_id] = {
|
|
264
|
+
api_key: result.api_key,
|
|
265
|
+
developer_id: result.developer_id,
|
|
266
|
+
workspace_name: result.workspace_name,
|
|
267
|
+
workspace_slug: result.workspace_slug
|
|
268
|
+
};
|
|
269
|
+
globalConfig.default_workspace = result.workspace_id;
|
|
270
|
+
}
|
|
271
|
+
saveGlobalConfig(globalConfig);
|
|
272
|
+
console.log("\nAuthenticated successfully!");
|
|
273
|
+
console.log(` Developer: ${result.developer_name}`);
|
|
274
|
+
if (result.workspace_name) {
|
|
275
|
+
console.log(` Workspace: ${result.workspace_name}`);
|
|
222
276
|
}
|
|
223
|
-
}
|
|
277
|
+
console.log(` Server: ${serverUrl}`);
|
|
278
|
+
console.log(` Machine: ${machineId.slice(0, 8)}...`);
|
|
279
|
+
console.log(`
|
|
280
|
+
API key saved to ~/.glop/config.json`);
|
|
281
|
+
console.log(
|
|
282
|
+
`
|
|
283
|
+
\u2192 Run \`glop init\` in a repo to start streaming sessions.`
|
|
284
|
+
);
|
|
285
|
+
process.exit(0);
|
|
286
|
+
});
|
|
224
287
|
|
|
225
288
|
// src/commands/deactivate.ts
|
|
289
|
+
import { Command as Command2 } from "commander";
|
|
226
290
|
import fs2 from "fs";
|
|
227
291
|
import path2 from "path";
|
|
228
292
|
var HOOK_EVENTS = [
|
|
@@ -306,7 +370,10 @@ var doctorCommand = new Command3("doctor").description("Check that glop is set u
|
|
|
306
370
|
console.log();
|
|
307
371
|
process.exit(1);
|
|
308
372
|
}
|
|
309
|
-
|
|
373
|
+
const repoBinding = loadRepoConfig();
|
|
374
|
+
const wsSource = repoBinding?.workspace_id ? "repo binding" : "default";
|
|
375
|
+
const authDetail = config.workspace_name ? `${config.developer_name} on ${config.server_url} (${config.workspace_name}, ${wsSource})` : `${config.developer_name} on ${config.server_url}`;
|
|
376
|
+
check("pass", "Authenticated", authDetail);
|
|
310
377
|
try {
|
|
311
378
|
const res = await fetch(`${config.server_url}/api/v1/health`, {
|
|
312
379
|
headers: {
|
|
@@ -529,89 +596,262 @@ var initCommand = new Command5("init").description("Install Claude Code hooks in
|
|
|
529
596
|
console.log(`${hadHooks ? "\u2713 glop updated" : "\u2713 glop connected"} \u2014 sessions will appear at ${config.server_url}/live`);
|
|
530
597
|
});
|
|
531
598
|
|
|
532
|
-
// src/commands/
|
|
599
|
+
// src/commands/update.ts
|
|
533
600
|
import { Command as Command6 } from "commander";
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
console.
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
process.exit(1);
|
|
601
|
+
import { execSync as execSync4 } from "child_process";
|
|
602
|
+
var updateCommand = new Command6("update").description("Update glop to the latest version").action(() => {
|
|
603
|
+
console.log("Updating glop\u2026");
|
|
604
|
+
try {
|
|
605
|
+
execSync4("npm install -g glop.dev@latest", { stdio: "inherit" });
|
|
606
|
+
console.log("\nglop has been updated successfully.");
|
|
607
|
+
} catch {
|
|
608
|
+
process.exitCode = 1;
|
|
543
609
|
}
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
// src/commands/workspace.ts
|
|
613
|
+
import { Command as Command7 } from "commander";
|
|
614
|
+
|
|
615
|
+
// src/lib/select.ts
|
|
616
|
+
function interactiveSelect(items, initialIndex = 0) {
|
|
617
|
+
return new Promise((resolve) => {
|
|
618
|
+
if (!process.stdin.isTTY) {
|
|
619
|
+
resolve(null);
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
let cursor = initialIndex;
|
|
623
|
+
function render() {
|
|
624
|
+
if (rendered) {
|
|
625
|
+
process.stdout.write(`\x1B[${items.length}A`);
|
|
626
|
+
}
|
|
627
|
+
for (let i = 0; i < items.length; i++) {
|
|
628
|
+
const isSelected = i === cursor;
|
|
629
|
+
const prefix = isSelected ? "\x1B[36m\u276F\x1B[0m " : " ";
|
|
630
|
+
process.stdout.write(`\x1B[2K${prefix}${items[i]}
|
|
631
|
+
`);
|
|
632
|
+
}
|
|
633
|
+
rendered = true;
|
|
634
|
+
}
|
|
635
|
+
let rendered = false;
|
|
636
|
+
process.stdin.setRawMode(true);
|
|
637
|
+
process.stdin.resume();
|
|
638
|
+
process.stdin.setEncoding("utf8");
|
|
639
|
+
function cleanup() {
|
|
640
|
+
process.stdin.setRawMode(false);
|
|
641
|
+
process.stdin.pause();
|
|
642
|
+
process.stdin.removeListener("data", onData);
|
|
643
|
+
}
|
|
644
|
+
function onData(key) {
|
|
645
|
+
if (key === "") {
|
|
646
|
+
cleanup();
|
|
647
|
+
resolve(null);
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
if (key === "\x1B" || key === "\x1B\x1B") {
|
|
651
|
+
cleanup();
|
|
652
|
+
resolve(null);
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
if (key === "\r" || key === "\n") {
|
|
656
|
+
cleanup();
|
|
657
|
+
resolve(cursor);
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
if (key === "\x1B[A" || key === "k") {
|
|
661
|
+
cursor = (cursor - 1 + items.length) % items.length;
|
|
662
|
+
render();
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
if (key === "\x1B[B" || key === "j") {
|
|
666
|
+
cursor = (cursor + 1) % items.length;
|
|
667
|
+
render();
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
process.stdin.on("data", onData);
|
|
672
|
+
render();
|
|
558
673
|
});
|
|
559
674
|
}
|
|
560
675
|
|
|
561
|
-
// src/commands/
|
|
562
|
-
|
|
563
|
-
const
|
|
564
|
-
if (
|
|
565
|
-
|
|
566
|
-
const minutes = Math.floor(seconds / 60);
|
|
567
|
-
if (minutes < 60) return `${minutes}m ago`;
|
|
568
|
-
const hours = Math.floor(minutes / 60);
|
|
569
|
-
if (hours < 24) return `${hours}h ago`;
|
|
570
|
-
const days = Math.floor(hours / 24);
|
|
571
|
-
return `${days}d ago`;
|
|
572
|
-
}
|
|
573
|
-
var statusCommand = new Command6("status").description("Show current Run status for this repo").action(async () => {
|
|
574
|
-
const repoKey = getRepoKey();
|
|
575
|
-
const branch = getBranch();
|
|
576
|
-
if (!repoKey) {
|
|
577
|
-
console.error("Not in a git repository with a remote.");
|
|
676
|
+
// src/commands/workspace.ts
|
|
677
|
+
var workspaceCommand = new Command7("workspace").description("View or switch workspaces").action(async () => {
|
|
678
|
+
const config = loadConfig();
|
|
679
|
+
if (!config) {
|
|
680
|
+
console.error("Not authenticated. Run `glop auth` first.");
|
|
578
681
|
process.exit(1);
|
|
579
682
|
}
|
|
683
|
+
let data;
|
|
580
684
|
try {
|
|
581
|
-
const res = await
|
|
582
|
-
|
|
583
|
-
|
|
685
|
+
const res = await fetch(`${config.server_url}/api/v1/cli/workspaces`, {
|
|
686
|
+
headers: {
|
|
687
|
+
Authorization: `Bearer ${config.api_key}`,
|
|
688
|
+
"X-Machine-Id": config.machine_id
|
|
689
|
+
},
|
|
690
|
+
signal: AbortSignal.timeout(1e4)
|
|
691
|
+
});
|
|
692
|
+
if (res.status === 401) {
|
|
693
|
+
console.error("API key is invalid. Run `glop auth` to re-authenticate.");
|
|
584
694
|
process.exit(1);
|
|
585
695
|
}
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
);
|
|
590
|
-
if (matchingRuns.length === 0) {
|
|
591
|
-
console.log(`No active runs for ${repoKey} (${branch})`);
|
|
592
|
-
return;
|
|
593
|
-
}
|
|
594
|
-
for (const run of matchingRuns) {
|
|
595
|
-
console.log(`Run: ${run.id.slice(0, 8)}`);
|
|
596
|
-
console.log(` Status: ${run.status}`);
|
|
597
|
-
console.log(` Phase: ${run.phase}`);
|
|
598
|
-
console.log(` Title: ${run.title || "-"}`);
|
|
599
|
-
console.log(` Last: ${run.last_action_label || "-"}`);
|
|
600
|
-
console.log(` Updated: ${timeAgo(run.last_event_at)}`);
|
|
696
|
+
if (!res.ok) {
|
|
697
|
+
console.error(`Failed to fetch workspaces (HTTP ${res.status}).`);
|
|
698
|
+
process.exit(1);
|
|
601
699
|
}
|
|
700
|
+
data = await res.json();
|
|
602
701
|
} catch (err) {
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
702
|
+
if (err instanceof Error && err.name === "TimeoutError") {
|
|
703
|
+
console.error(`Cannot connect to ${config.server_url}`);
|
|
704
|
+
} else {
|
|
705
|
+
console.error("Failed to fetch workspaces.");
|
|
706
|
+
}
|
|
607
707
|
process.exit(1);
|
|
608
708
|
}
|
|
709
|
+
if (data.workspaces.length === 0) {
|
|
710
|
+
console.log("No workspaces found.");
|
|
711
|
+
process.exit(0);
|
|
712
|
+
}
|
|
713
|
+
const repoConfig = loadRepoConfig();
|
|
714
|
+
const currentId = repoConfig?.workspace_id || config.workspace_id || data.current_workspace_id;
|
|
715
|
+
if (!process.stdin.isTTY) {
|
|
716
|
+
const current = data.workspaces.find((w) => w.id === currentId);
|
|
717
|
+
console.log(current ? current.name : currentId);
|
|
718
|
+
process.exit(0);
|
|
719
|
+
}
|
|
720
|
+
if (data.workspaces.length === 1) {
|
|
721
|
+
console.log(` Workspace: ${data.workspaces[0].name}`);
|
|
722
|
+
console.log(" (only workspace)");
|
|
723
|
+
process.exit(0);
|
|
724
|
+
}
|
|
725
|
+
const items = data.workspaces.map((w) => {
|
|
726
|
+
const marker = w.id === currentId ? "\u25CF" : "\u25CB";
|
|
727
|
+
return `${marker} ${w.name}`;
|
|
728
|
+
});
|
|
729
|
+
const currentIndex = data.workspaces.findIndex((w) => w.id === currentId);
|
|
730
|
+
console.log("\n Workspaces:\n");
|
|
731
|
+
console.log(" \x1B[2m\u2191/\u2193 navigate \xB7 Enter select \xB7 Esc cancel\x1B[0m\n");
|
|
732
|
+
const selected = await interactiveSelect(items, Math.max(currentIndex, 0));
|
|
733
|
+
if (selected === null) {
|
|
734
|
+
console.log("\n Cancelled.");
|
|
735
|
+
process.exit(0);
|
|
736
|
+
}
|
|
737
|
+
const selectedWorkspace = data.workspaces[selected];
|
|
738
|
+
if (selectedWorkspace.id === currentId) {
|
|
739
|
+
console.log(`
|
|
740
|
+
Already on ${selectedWorkspace.name}.`);
|
|
741
|
+
process.exit(0);
|
|
742
|
+
}
|
|
743
|
+
const globalConfig = loadGlobalConfig();
|
|
744
|
+
const existingCreds = globalConfig.workspaces[selectedWorkspace.id];
|
|
745
|
+
const repoRoot = getRepoRoot();
|
|
746
|
+
if (existingCreds) {
|
|
747
|
+
if (repoRoot) {
|
|
748
|
+
saveRepoConfig({ workspace_id: selectedWorkspace.id });
|
|
749
|
+
} else {
|
|
750
|
+
globalConfig.default_workspace = selectedWorkspace.id;
|
|
751
|
+
saveGlobalConfig(globalConfig);
|
|
752
|
+
}
|
|
753
|
+
console.log(`
|
|
754
|
+
Switched to ${selectedWorkspace.name}!`);
|
|
755
|
+
process.exit(0);
|
|
756
|
+
}
|
|
757
|
+
console.log(`
|
|
758
|
+
Switching to ${selectedWorkspace.name}...`);
|
|
759
|
+
console.log(" Opening browser for authorization...\n");
|
|
760
|
+
const port = await findOpenPort();
|
|
761
|
+
const machineId = getMachineId();
|
|
762
|
+
const authUrl = `${config.server_url}/cli-auth?port=${port}&workspace_id=${selectedWorkspace.id}`;
|
|
763
|
+
console.log(" If the browser doesn't open, visit this URL manually:");
|
|
764
|
+
console.log(` ${authUrl}
|
|
765
|
+
`);
|
|
766
|
+
console.log(" Waiting for authorization...");
|
|
767
|
+
openBrowser(authUrl);
|
|
768
|
+
const result = await waitForCallback(port);
|
|
769
|
+
const wsId = result.workspace_id || selectedWorkspace.id;
|
|
770
|
+
globalConfig.workspaces[wsId] = {
|
|
771
|
+
api_key: result.api_key,
|
|
772
|
+
developer_id: result.developer_id,
|
|
773
|
+
workspace_name: result.workspace_name,
|
|
774
|
+
workspace_slug: result.workspace_slug
|
|
775
|
+
};
|
|
776
|
+
globalConfig.developer_name = result.developer_name;
|
|
777
|
+
if (repoRoot) {
|
|
778
|
+
saveRepoConfig({ workspace_id: wsId });
|
|
779
|
+
} else {
|
|
780
|
+
globalConfig.default_workspace = wsId;
|
|
781
|
+
}
|
|
782
|
+
saveGlobalConfig(globalConfig);
|
|
783
|
+
console.log(`
|
|
784
|
+
Switched to ${result.workspace_name || selectedWorkspace.name}!`);
|
|
785
|
+
process.exit(0);
|
|
609
786
|
});
|
|
610
787
|
|
|
788
|
+
// src/lib/update-check.ts
|
|
789
|
+
import fs5 from "fs";
|
|
790
|
+
import path5 from "path";
|
|
791
|
+
import os2 from "os";
|
|
792
|
+
var CONFIG_DIR2 = path5.join(os2.homedir(), ".glop");
|
|
793
|
+
var CACHE_FILE = path5.join(CONFIG_DIR2, "update-check.json");
|
|
794
|
+
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
795
|
+
function ensureConfigDir2() {
|
|
796
|
+
if (!fs5.existsSync(CONFIG_DIR2)) {
|
|
797
|
+
fs5.mkdirSync(CONFIG_DIR2, { recursive: true });
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
function isNewerVersion(current, latest) {
|
|
801
|
+
const currentParts = current.split(".").map(Number);
|
|
802
|
+
const latestParts = latest.split(".").map(Number);
|
|
803
|
+
for (let i = 0; i < 3; i++) {
|
|
804
|
+
const c = currentParts[i] || 0;
|
|
805
|
+
const l = latestParts[i] || 0;
|
|
806
|
+
if (l > c) return true;
|
|
807
|
+
if (l < c) return false;
|
|
808
|
+
}
|
|
809
|
+
return false;
|
|
810
|
+
}
|
|
811
|
+
async function checkForUpdate(currentVersion) {
|
|
812
|
+
try {
|
|
813
|
+
if (process.env.CI) return;
|
|
814
|
+
if (!process.stderr.isTTY) return;
|
|
815
|
+
let latestVersion = null;
|
|
816
|
+
if (fs5.existsSync(CACHE_FILE)) {
|
|
817
|
+
try {
|
|
818
|
+
const raw = fs5.readFileSync(CACHE_FILE, "utf-8");
|
|
819
|
+
const cache = JSON.parse(raw);
|
|
820
|
+
if (Date.now() - cache.last_check < CHECK_INTERVAL_MS) {
|
|
821
|
+
latestVersion = cache.latest_version;
|
|
822
|
+
}
|
|
823
|
+
} catch {
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
if (!latestVersion) {
|
|
827
|
+
const response = await fetch(
|
|
828
|
+
"https://registry.npmjs.org/glop.dev/latest",
|
|
829
|
+
{ signal: AbortSignal.timeout(3e3) }
|
|
830
|
+
);
|
|
831
|
+
const data = await response.json();
|
|
832
|
+
latestVersion = data.version;
|
|
833
|
+
ensureConfigDir2();
|
|
834
|
+
const cache = {
|
|
835
|
+
last_check: Date.now(),
|
|
836
|
+
latest_version: latestVersion
|
|
837
|
+
};
|
|
838
|
+
fs5.writeFileSync(CACHE_FILE, JSON.stringify(cache));
|
|
839
|
+
}
|
|
840
|
+
if (isNewerVersion(currentVersion, latestVersion)) {
|
|
841
|
+
console.error(
|
|
842
|
+
`
|
|
843
|
+
Update available: ${currentVersion} \u2192 ${latestVersion}. Run \`glop update\` to update.
|
|
844
|
+
`
|
|
845
|
+
);
|
|
846
|
+
}
|
|
847
|
+
} catch {
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
|
|
611
851
|
// package.json
|
|
612
852
|
var package_default = {
|
|
613
853
|
name: "glop.dev",
|
|
614
|
-
version: "0.
|
|
854
|
+
version: "0.7.0",
|
|
615
855
|
type: "module",
|
|
616
856
|
bin: {
|
|
617
857
|
glop: "./dist/index.js"
|
|
@@ -638,11 +878,16 @@ var package_default = {
|
|
|
638
878
|
};
|
|
639
879
|
|
|
640
880
|
// src/index.ts
|
|
641
|
-
var program = new
|
|
881
|
+
var program = new Command8().name("glop").description("Passive control plane for local Claude-driven development").version(package_default.version);
|
|
642
882
|
program.addCommand(authCommand);
|
|
643
883
|
program.addCommand(deactivateCommand);
|
|
644
884
|
program.addCommand(doctorCommand);
|
|
645
885
|
program.addCommand(hookCommand, { hidden: true });
|
|
646
886
|
program.addCommand(initCommand);
|
|
647
|
-
program.addCommand(
|
|
887
|
+
program.addCommand(updateCommand);
|
|
888
|
+
program.addCommand(workspaceCommand);
|
|
889
|
+
program.hook("postAction", async (_thisCommand, actionCommand) => {
|
|
890
|
+
if (actionCommand.name() === "__hook") return;
|
|
891
|
+
await checkForUpdate(package_default.version);
|
|
892
|
+
});
|
|
648
893
|
program.parse();
|