glop.dev 0.4.0 → 0.6.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 +404 -170
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -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: {
|
|
@@ -378,16 +445,19 @@ var doctorCommand = new Command3("doctor").description("Check that glop is set u
|
|
|
378
445
|
|
|
379
446
|
// src/commands/hook.ts
|
|
380
447
|
import { Command as Command4 } from "commander";
|
|
381
|
-
import { openSync, readSync, closeSync } from "fs";
|
|
448
|
+
import { openSync, readSync, closeSync, readFileSync } from "fs";
|
|
382
449
|
function extractSlugFromTranscript(transcriptPath) {
|
|
383
450
|
try {
|
|
384
451
|
const fd = openSync(transcriptPath, "r");
|
|
385
|
-
const buf = Buffer.alloc(
|
|
386
|
-
const bytesRead = readSync(fd, buf, 0,
|
|
452
|
+
const buf = Buffer.alloc(262144);
|
|
453
|
+
const bytesRead = readSync(fd, buf, 0, 262144, 0);
|
|
387
454
|
closeSync(fd);
|
|
388
455
|
const head = buf.toString("utf-8", 0, bytesRead);
|
|
389
456
|
const match = head.match(/"slug":"([^"]+)"/);
|
|
390
|
-
|
|
457
|
+
if (match) return match[1];
|
|
458
|
+
if (bytesRead < 262144) return null;
|
|
459
|
+
const full = readFileSync(transcriptPath, "utf-8");
|
|
460
|
+
return full.match(/"slug":"([^"]+)"/)?.[1] ?? null;
|
|
391
461
|
} catch {
|
|
392
462
|
return null;
|
|
393
463
|
}
|
|
@@ -526,89 +596,249 @@ var initCommand = new Command5("init").description("Install Claude Code hooks in
|
|
|
526
596
|
console.log(`${hadHooks ? "\u2713 glop updated" : "\u2713 glop connected"} \u2014 sessions will appear at ${config.server_url}/live`);
|
|
527
597
|
});
|
|
528
598
|
|
|
529
|
-
// src/commands/
|
|
599
|
+
// src/commands/workspace.ts
|
|
530
600
|
import { Command as Command6 } from "commander";
|
|
531
601
|
|
|
532
|
-
// src/lib/
|
|
533
|
-
function
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
602
|
+
// src/lib/select.ts
|
|
603
|
+
function interactiveSelect(items, initialIndex = 0) {
|
|
604
|
+
return new Promise((resolve) => {
|
|
605
|
+
if (!process.stdin.isTTY) {
|
|
606
|
+
resolve(null);
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
let cursor = initialIndex;
|
|
610
|
+
function render() {
|
|
611
|
+
if (rendered) {
|
|
612
|
+
process.stdout.write(`\x1B[${items.length}A`);
|
|
613
|
+
}
|
|
614
|
+
for (let i = 0; i < items.length; i++) {
|
|
615
|
+
const isSelected = i === cursor;
|
|
616
|
+
const prefix = isSelected ? "\x1B[36m\u276F\x1B[0m " : " ";
|
|
617
|
+
process.stdout.write(`\x1B[2K${prefix}${items[i]}
|
|
618
|
+
`);
|
|
619
|
+
}
|
|
620
|
+
rendered = true;
|
|
621
|
+
}
|
|
622
|
+
let rendered = false;
|
|
623
|
+
process.stdin.setRawMode(true);
|
|
624
|
+
process.stdin.resume();
|
|
625
|
+
process.stdin.setEncoding("utf8");
|
|
626
|
+
function cleanup() {
|
|
627
|
+
process.stdin.setRawMode(false);
|
|
628
|
+
process.stdin.pause();
|
|
629
|
+
process.stdin.removeListener("data", onData);
|
|
630
|
+
}
|
|
631
|
+
function onData(key) {
|
|
632
|
+
if (key === "") {
|
|
633
|
+
cleanup();
|
|
634
|
+
resolve(null);
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
if (key === "\x1B" || key === "\x1B\x1B") {
|
|
638
|
+
cleanup();
|
|
639
|
+
resolve(null);
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
if (key === "\r" || key === "\n") {
|
|
643
|
+
cleanup();
|
|
644
|
+
resolve(cursor);
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
if (key === "\x1B[A" || key === "k") {
|
|
648
|
+
cursor = (cursor - 1 + items.length) % items.length;
|
|
649
|
+
render();
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
if (key === "\x1B[B" || key === "j") {
|
|
653
|
+
cursor = (cursor + 1) % items.length;
|
|
654
|
+
render();
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
process.stdin.on("data", onData);
|
|
659
|
+
render();
|
|
555
660
|
});
|
|
556
661
|
}
|
|
557
662
|
|
|
558
|
-
// src/commands/
|
|
559
|
-
|
|
560
|
-
const
|
|
561
|
-
if (
|
|
562
|
-
|
|
563
|
-
const minutes = Math.floor(seconds / 60);
|
|
564
|
-
if (minutes < 60) return `${minutes}m ago`;
|
|
565
|
-
const hours = Math.floor(minutes / 60);
|
|
566
|
-
if (hours < 24) return `${hours}h ago`;
|
|
567
|
-
const days = Math.floor(hours / 24);
|
|
568
|
-
return `${days}d ago`;
|
|
569
|
-
}
|
|
570
|
-
var statusCommand = new Command6("status").description("Show current Run status for this repo").action(async () => {
|
|
571
|
-
const repoKey = getRepoKey();
|
|
572
|
-
const branch = getBranch();
|
|
573
|
-
if (!repoKey) {
|
|
574
|
-
console.error("Not in a git repository with a remote.");
|
|
663
|
+
// src/commands/workspace.ts
|
|
664
|
+
var workspaceCommand = new Command6("workspace").description("View or switch workspaces").action(async () => {
|
|
665
|
+
const config = loadConfig();
|
|
666
|
+
if (!config) {
|
|
667
|
+
console.error("Not authenticated. Run `glop auth` first.");
|
|
575
668
|
process.exit(1);
|
|
576
669
|
}
|
|
670
|
+
let data;
|
|
577
671
|
try {
|
|
578
|
-
const res = await
|
|
579
|
-
|
|
580
|
-
|
|
672
|
+
const res = await fetch(`${config.server_url}/api/v1/cli/workspaces`, {
|
|
673
|
+
headers: {
|
|
674
|
+
Authorization: `Bearer ${config.api_key}`,
|
|
675
|
+
"X-Machine-Id": config.machine_id
|
|
676
|
+
},
|
|
677
|
+
signal: AbortSignal.timeout(1e4)
|
|
678
|
+
});
|
|
679
|
+
if (res.status === 401) {
|
|
680
|
+
console.error("API key is invalid. Run `glop auth` to re-authenticate.");
|
|
581
681
|
process.exit(1);
|
|
582
682
|
}
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
);
|
|
587
|
-
if (matchingRuns.length === 0) {
|
|
588
|
-
console.log(`No active runs for ${repoKey} (${branch})`);
|
|
589
|
-
return;
|
|
590
|
-
}
|
|
591
|
-
for (const run of matchingRuns) {
|
|
592
|
-
console.log(`Run: ${run.id.slice(0, 8)}`);
|
|
593
|
-
console.log(` Status: ${run.status}`);
|
|
594
|
-
console.log(` Phase: ${run.phase}`);
|
|
595
|
-
console.log(` Title: ${run.title || "-"}`);
|
|
596
|
-
console.log(` Last: ${run.last_action_label || "-"}`);
|
|
597
|
-
console.log(` Updated: ${timeAgo(run.last_event_at)}`);
|
|
683
|
+
if (!res.ok) {
|
|
684
|
+
console.error(`Failed to fetch workspaces (HTTP ${res.status}).`);
|
|
685
|
+
process.exit(1);
|
|
598
686
|
}
|
|
687
|
+
data = await res.json();
|
|
599
688
|
} catch (err) {
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
689
|
+
if (err instanceof Error && err.name === "TimeoutError") {
|
|
690
|
+
console.error(`Cannot connect to ${config.server_url}`);
|
|
691
|
+
} else {
|
|
692
|
+
console.error("Failed to fetch workspaces.");
|
|
693
|
+
}
|
|
604
694
|
process.exit(1);
|
|
605
695
|
}
|
|
696
|
+
if (data.workspaces.length === 0) {
|
|
697
|
+
console.log("No workspaces found.");
|
|
698
|
+
process.exit(0);
|
|
699
|
+
}
|
|
700
|
+
const repoConfig = loadRepoConfig();
|
|
701
|
+
const currentId = repoConfig?.workspace_id || config.workspace_id || data.current_workspace_id;
|
|
702
|
+
if (!process.stdin.isTTY) {
|
|
703
|
+
const current = data.workspaces.find((w) => w.id === currentId);
|
|
704
|
+
console.log(current ? current.name : currentId);
|
|
705
|
+
process.exit(0);
|
|
706
|
+
}
|
|
707
|
+
if (data.workspaces.length === 1) {
|
|
708
|
+
console.log(` Workspace: ${data.workspaces[0].name}`);
|
|
709
|
+
console.log(" (only workspace)");
|
|
710
|
+
process.exit(0);
|
|
711
|
+
}
|
|
712
|
+
const items = data.workspaces.map((w) => {
|
|
713
|
+
const marker = w.id === currentId ? "\u25CF" : "\u25CB";
|
|
714
|
+
return `${marker} ${w.name}`;
|
|
715
|
+
});
|
|
716
|
+
const currentIndex = data.workspaces.findIndex((w) => w.id === currentId);
|
|
717
|
+
console.log("\n Workspaces:\n");
|
|
718
|
+
console.log(" \x1B[2m\u2191/\u2193 navigate \xB7 Enter select \xB7 Esc cancel\x1B[0m\n");
|
|
719
|
+
const selected = await interactiveSelect(items, Math.max(currentIndex, 0));
|
|
720
|
+
if (selected === null) {
|
|
721
|
+
console.log("\n Cancelled.");
|
|
722
|
+
process.exit(0);
|
|
723
|
+
}
|
|
724
|
+
const selectedWorkspace = data.workspaces[selected];
|
|
725
|
+
if (selectedWorkspace.id === currentId) {
|
|
726
|
+
console.log(`
|
|
727
|
+
Already on ${selectedWorkspace.name}.`);
|
|
728
|
+
process.exit(0);
|
|
729
|
+
}
|
|
730
|
+
const globalConfig = loadGlobalConfig();
|
|
731
|
+
const existingCreds = globalConfig.workspaces[selectedWorkspace.id];
|
|
732
|
+
const repoRoot = getRepoRoot();
|
|
733
|
+
if (existingCreds) {
|
|
734
|
+
if (repoRoot) {
|
|
735
|
+
saveRepoConfig({ workspace_id: selectedWorkspace.id });
|
|
736
|
+
} else {
|
|
737
|
+
globalConfig.default_workspace = selectedWorkspace.id;
|
|
738
|
+
saveGlobalConfig(globalConfig);
|
|
739
|
+
}
|
|
740
|
+
console.log(`
|
|
741
|
+
Switched to ${selectedWorkspace.name}!`);
|
|
742
|
+
process.exit(0);
|
|
743
|
+
}
|
|
744
|
+
console.log(`
|
|
745
|
+
Switching to ${selectedWorkspace.name}...`);
|
|
746
|
+
console.log(" Opening browser for authorization...\n");
|
|
747
|
+
const port = await findOpenPort();
|
|
748
|
+
const machineId = getMachineId();
|
|
749
|
+
const authUrl = `${config.server_url}/cli-auth?port=${port}&workspace_id=${selectedWorkspace.id}`;
|
|
750
|
+
console.log(" If the browser doesn't open, visit this URL manually:");
|
|
751
|
+
console.log(` ${authUrl}
|
|
752
|
+
`);
|
|
753
|
+
console.log(" Waiting for authorization...");
|
|
754
|
+
openBrowser(authUrl);
|
|
755
|
+
const result = await waitForCallback(port);
|
|
756
|
+
const wsId = result.workspace_id || selectedWorkspace.id;
|
|
757
|
+
globalConfig.workspaces[wsId] = {
|
|
758
|
+
api_key: result.api_key,
|
|
759
|
+
developer_id: result.developer_id,
|
|
760
|
+
workspace_name: result.workspace_name,
|
|
761
|
+
workspace_slug: result.workspace_slug
|
|
762
|
+
};
|
|
763
|
+
globalConfig.developer_name = result.developer_name;
|
|
764
|
+
if (repoRoot) {
|
|
765
|
+
saveRepoConfig({ workspace_id: wsId });
|
|
766
|
+
} else {
|
|
767
|
+
globalConfig.default_workspace = wsId;
|
|
768
|
+
}
|
|
769
|
+
saveGlobalConfig(globalConfig);
|
|
770
|
+
console.log(`
|
|
771
|
+
Switched to ${result.workspace_name || selectedWorkspace.name}!`);
|
|
772
|
+
process.exit(0);
|
|
606
773
|
});
|
|
607
774
|
|
|
775
|
+
// src/lib/update-check.ts
|
|
776
|
+
import fs5 from "fs";
|
|
777
|
+
import path5 from "path";
|
|
778
|
+
import os2 from "os";
|
|
779
|
+
var CONFIG_DIR2 = path5.join(os2.homedir(), ".glop");
|
|
780
|
+
var CACHE_FILE = path5.join(CONFIG_DIR2, "update-check.json");
|
|
781
|
+
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
782
|
+
function ensureConfigDir2() {
|
|
783
|
+
if (!fs5.existsSync(CONFIG_DIR2)) {
|
|
784
|
+
fs5.mkdirSync(CONFIG_DIR2, { recursive: true });
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
function isNewerVersion(current, latest) {
|
|
788
|
+
const currentParts = current.split(".").map(Number);
|
|
789
|
+
const latestParts = latest.split(".").map(Number);
|
|
790
|
+
for (let i = 0; i < 3; i++) {
|
|
791
|
+
const c = currentParts[i] || 0;
|
|
792
|
+
const l = latestParts[i] || 0;
|
|
793
|
+
if (l > c) return true;
|
|
794
|
+
if (l < c) return false;
|
|
795
|
+
}
|
|
796
|
+
return false;
|
|
797
|
+
}
|
|
798
|
+
async function checkForUpdate(currentVersion) {
|
|
799
|
+
try {
|
|
800
|
+
if (process.env.CI) return;
|
|
801
|
+
if (!process.stderr.isTTY) return;
|
|
802
|
+
let latestVersion = null;
|
|
803
|
+
if (fs5.existsSync(CACHE_FILE)) {
|
|
804
|
+
try {
|
|
805
|
+
const raw = fs5.readFileSync(CACHE_FILE, "utf-8");
|
|
806
|
+
const cache = JSON.parse(raw);
|
|
807
|
+
if (Date.now() - cache.last_check < CHECK_INTERVAL_MS) {
|
|
808
|
+
latestVersion = cache.latest_version;
|
|
809
|
+
}
|
|
810
|
+
} catch {
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
if (!latestVersion) {
|
|
814
|
+
const response = await fetch(
|
|
815
|
+
"https://registry.npmjs.org/glop.dev/latest",
|
|
816
|
+
{ signal: AbortSignal.timeout(3e3) }
|
|
817
|
+
);
|
|
818
|
+
const data = await response.json();
|
|
819
|
+
latestVersion = data.version;
|
|
820
|
+
ensureConfigDir2();
|
|
821
|
+
const cache = {
|
|
822
|
+
last_check: Date.now(),
|
|
823
|
+
latest_version: latestVersion
|
|
824
|
+
};
|
|
825
|
+
fs5.writeFileSync(CACHE_FILE, JSON.stringify(cache));
|
|
826
|
+
}
|
|
827
|
+
if (isNewerVersion(currentVersion, latestVersion)) {
|
|
828
|
+
console.error(
|
|
829
|
+
`
|
|
830
|
+
Update available: ${currentVersion} \u2192 ${latestVersion}. Run \`npm i -g glop.dev\` to update.
|
|
831
|
+
`
|
|
832
|
+
);
|
|
833
|
+
}
|
|
834
|
+
} catch {
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
|
|
608
838
|
// package.json
|
|
609
839
|
var package_default = {
|
|
610
840
|
name: "glop.dev",
|
|
611
|
-
version: "0.
|
|
841
|
+
version: "0.6.0",
|
|
612
842
|
type: "module",
|
|
613
843
|
bin: {
|
|
614
844
|
glop: "./dist/index.js"
|
|
@@ -641,5 +871,9 @@ program.addCommand(deactivateCommand);
|
|
|
641
871
|
program.addCommand(doctorCommand);
|
|
642
872
|
program.addCommand(hookCommand, { hidden: true });
|
|
643
873
|
program.addCommand(initCommand);
|
|
644
|
-
program.addCommand(
|
|
874
|
+
program.addCommand(workspaceCommand);
|
|
875
|
+
program.hook("postAction", async (_thisCommand, actionCommand) => {
|
|
876
|
+
if (actionCommand.name() === "__hook") return;
|
|
877
|
+
await checkForUpdate(package_default.version);
|
|
878
|
+
});
|
|
645
879
|
program.parse();
|