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.
Files changed (2) hide show
  1. package/dist/index.js +404 -170
  2. 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 loadConfig() {
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 saveConfig(config) {
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/commands/auth.ts
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/deactivate.ts
161
- import { Command as Command2 } from "commander";
162
-
163
- // src/lib/git.ts
164
- import { execSync } from "child_process";
165
- function getRepoRoot() {
166
- try {
167
- return execSync("git rev-parse --show-toplevel", {
168
- encoding: "utf-8",
169
- stdio: ["pipe", "pipe", "pipe"]
170
- }).trim();
171
- } catch {
172
- return null;
173
- }
174
- }
175
- function getRepoKey() {
176
- try {
177
- const remote = execSync("git remote get-url origin", {
178
- encoding: "utf-8",
179
- stdio: ["pipe", "pipe", "pipe"]
180
- }).trim();
181
- const match = remote.match(
182
- /(?:github\.com|gitlab\.com|bitbucket\.org)[/:](.+?)(?:\.git)?$/
183
- );
184
- if (match) return match[1];
185
- const parts = remote.split("/").filter(Boolean);
186
- if (parts.length >= 2) {
187
- return `${parts[parts.length - 2]}/${parts[parts.length - 1].replace(".git", "")}`;
188
- }
189
- return remote;
190
- } catch {
191
- return null;
192
- }
193
- }
194
- function getBranch() {
195
- try {
196
- return execSync("git rev-parse --abbrev-ref HEAD", {
197
- encoding: "utf-8",
198
- stdio: ["pipe", "pipe", "pipe"]
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
- check("pass", "Authenticated", `${config.developer_name} on ${config.server_url}`);
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(65536);
386
- const bytesRead = readSync(fd, buf, 0, 65536, 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
- return match ? match[1] : null;
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/status.ts
599
+ // src/commands/workspace.ts
530
600
  import { Command as Command6 } from "commander";
531
601
 
532
- // src/lib/api-client.ts
533
- function getConfig() {
534
- const config = loadConfig();
535
- if (!config) {
536
- console.error(
537
- "Not authenticated. Run `glop auth` first."
538
- );
539
- process.exit(1);
540
- }
541
- return config;
542
- }
543
- async function apiRequest(path5, options = {}) {
544
- const config = getConfig();
545
- const url = `${config.server_url}${path5}`;
546
- const headers = {
547
- "Content-Type": "application/json",
548
- Authorization: `Bearer ${config.api_key}`,
549
- "X-Machine-Id": config.machine_id,
550
- ...options.headers
551
- };
552
- return fetch(url, {
553
- ...options,
554
- headers
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/status.ts
559
- function timeAgo(iso) {
560
- const seconds = Math.floor((Date.now() - new Date(iso).getTime()) / 1e3);
561
- if (seconds < 5) return "just now";
562
- if (seconds < 60) return `${seconds}s ago`;
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 apiRequest("/api/v1/live");
579
- if (!res.ok) {
580
- console.error("Failed to fetch status:", res.statusText);
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
- const data = await res.json();
584
- const matchingRuns = data.runs.filter(
585
- (r) => r.repo_key.includes(repoKey.split("/").pop() || "") && r.branch_name === branch
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
- console.error(
601
- "Failed to connect:",
602
- err instanceof Error ? err.message : err
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.4.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(statusCommand);
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();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "glop.dev",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "glop": "./dist/index.js"