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.
Files changed (2) hide show
  1. package/dist/index.js +413 -168
  2. 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 Command7 } from "commander";
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 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: {
@@ -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/status.ts
599
+ // src/commands/update.ts
533
600
  import { Command as Command6 } from "commander";
534
-
535
- // src/lib/api-client.ts
536
- function getConfig() {
537
- const config = loadConfig();
538
- if (!config) {
539
- console.error(
540
- "Not authenticated. Run `glop auth` first."
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
- return config;
545
- }
546
- async function apiRequest(path5, options = {}) {
547
- const config = getConfig();
548
- const url = `${config.server_url}${path5}`;
549
- const headers = {
550
- "Content-Type": "application/json",
551
- Authorization: `Bearer ${config.api_key}`,
552
- "X-Machine-Id": config.machine_id,
553
- ...options.headers
554
- };
555
- return fetch(url, {
556
- ...options,
557
- headers
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/status.ts
562
- function timeAgo(iso) {
563
- const seconds = Math.floor((Date.now() - new Date(iso).getTime()) / 1e3);
564
- if (seconds < 5) return "just now";
565
- if (seconds < 60) return `${seconds}s ago`;
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 apiRequest("/api/v1/live");
582
- if (!res.ok) {
583
- console.error("Failed to fetch status:", res.statusText);
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
- const data = await res.json();
587
- const matchingRuns = data.runs.filter(
588
- (r) => r.repo_key.includes(repoKey.split("/").pop() || "") && r.branch_name === branch
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
- console.error(
604
- "Failed to connect:",
605
- err instanceof Error ? err.message : err
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.5.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 Command7().name("glop").description("Passive control plane for local Claude-driven development").version(package_default.version);
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(statusCommand);
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();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "glop.dev",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "glop": "./dist/index.js"