@bramblex/codex-workbench 0.1.16 → 0.1.17

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bramblex/codex-workbench",
3
- "version": "0.1.16",
3
+ "version": "0.1.17",
4
4
  "description": "Terminal workbench for browsing and managing local and SSH Codex sessions.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -33,7 +33,7 @@
33
33
  "LICENSE"
34
34
  ],
35
35
  "scripts": {
36
- "test": "node -e \"const fs=require('fs'),{spawnSync}=require('child_process');function files(d){return fs.readdirSync(d,{withFileTypes:true}).flatMap(e=>e.isDirectory()?files(d+'/'+e.name):e.name.endsWith('.js')?[d+'/'+e.name]:[])}for(const f of files('src')){const r=spawnSync(process.execPath,['--check',f],{stdio:'inherit'});if(r.status)process.exit(r.status)}\" && node --check bin/codex-workbench && node --check scripts/pty-codex.js && node --check scripts/tui-pty-codex.js && node --check scripts/blessed-xterm-codex.js && node test/codex-bin.test.js && node test/blessed-compat.test.js && node test/config-paths.test.js && node test/session-sources.test.js && node test/workbench-config.test.js && node test/smoke.js",
36
+ "test": "node -e \"const fs=require('fs'),{spawnSync}=require('child_process');function files(d){return fs.readdirSync(d,{withFileTypes:true}).flatMap(e=>e.isDirectory()?files(d+'/'+e.name):e.name.endsWith('.js')?[d+'/'+e.name]:[])}for(const f of files('src')){const r=spawnSync(process.execPath,['--check',f],{stdio:'inherit'});if(r.status)process.exit(r.status)}\" && node --check bin/codex-workbench && node --check scripts/pty-codex.js && node --check scripts/tui-pty-codex.js && node --check scripts/blessed-xterm-codex.js && node test/codex-bin.test.js && node test/blessed-compat.test.js && node test/config-paths.test.js && node test/session-sources.test.js && node test/update-checker.test.js && node test/workbench-config.test.js && node test/smoke.js",
37
37
  "pty:codex": "node scripts/pty-codex.js",
38
38
  "tui:codex": "node scripts/tui-pty-codex.js",
39
39
  "xterm:codex": "node scripts/blessed-xterm-codex.js"
@@ -0,0 +1,72 @@
1
+ 'use strict';
2
+
3
+ const https = require('https');
4
+ const pkg = require('../../package.json');
5
+
6
+ const REGISTRY_URL = `https://registry.npmjs.org/${encodeURIComponent(pkg.name).replace(/^%40/, '@')}/latest`;
7
+
8
+ function parseVersion(version) {
9
+ return String(version || '')
10
+ .replace(/^v/, '')
11
+ .split('.')
12
+ .map((part) => Number.parseInt(part, 10))
13
+ .map((part) => (Number.isFinite(part) ? part : 0));
14
+ }
15
+
16
+ function compareVersions(a, b) {
17
+ const left = parseVersion(a);
18
+ const right = parseVersion(b);
19
+ const length = Math.max(left.length, right.length);
20
+ for (let i = 0; i < length; i += 1) {
21
+ const delta = (left[i] || 0) - (right[i] || 0);
22
+ if (delta !== 0) return delta;
23
+ }
24
+ return 0;
25
+ }
26
+
27
+ function fetchLatestVersion(timeoutMs = 1500) {
28
+ return new Promise((resolve) => {
29
+ const req = https.get(REGISTRY_URL, {
30
+ headers: { accept: 'application/json', 'user-agent': `${pkg.name}/${pkg.version}` },
31
+ timeout: timeoutMs,
32
+ }, (res) => {
33
+ if (res.statusCode !== 200) {
34
+ res.resume();
35
+ resolve(null);
36
+ return;
37
+ }
38
+ let body = '';
39
+ res.setEncoding('utf8');
40
+ res.on('data', (chunk) => { body += chunk; });
41
+ res.on('end', () => {
42
+ try {
43
+ const payload = JSON.parse(body);
44
+ resolve(payload && payload.version ? String(payload.version) : null);
45
+ } catch {
46
+ resolve(null);
47
+ }
48
+ });
49
+ });
50
+
51
+ req.on('timeout', () => {
52
+ req.destroy();
53
+ resolve(null);
54
+ });
55
+ req.on('error', () => resolve(null));
56
+ });
57
+ }
58
+
59
+ async function checkForUpdate(currentVersion = pkg.version) {
60
+ const latestVersion = await fetchLatestVersion();
61
+ if (!latestVersion || compareVersions(latestVersion, currentVersion) <= 0) return null;
62
+ return {
63
+ currentVersion,
64
+ latestVersion,
65
+ };
66
+ }
67
+
68
+ module.exports = {
69
+ checkForUpdate,
70
+ compareVersions,
71
+ fetchLatestVersion,
72
+ };
@@ -21,6 +21,7 @@ const {
21
21
  updateSourceMetadata,
22
22
  } = require('../services/session-sources');
23
23
  const { usableCwd } = require('../services/codex-runner');
24
+ const { checkForUpdate } = require('../services/update-checker');
24
25
  const { createDirectoryPicker } = require('./directory-picker');
25
26
 
26
27
  async function runWorkbench() {
@@ -42,6 +43,7 @@ async function runWorkbench() {
42
43
  let activePanel = 'projects';
43
44
  let remoteLoadId = 0;
44
45
  let remoteLoading = false;
46
+ let updateInfo = null;
45
47
  let closed = false;
46
48
 
47
49
  const screen = blessed.screen({
@@ -466,7 +468,8 @@ async function runWorkbench() {
466
468
  const render = () => {
467
469
  applyLayout();
468
470
  const visible = currentSessions();
469
- header.setContent(` ${appTitle}\n ${visible.length}/${sessions.length} visible ${groupDisplayName(currentGroup())}`);
471
+ const updateText = updateInfo ? ` Update available: v${updateInfo.latestVersion}` : '';
472
+ header.setContent(` ${appTitle}${updateText}\n ${visible.length}/${sessions.length} visible ${groupDisplayName(currentGroup())}`);
470
473
  detailBox.setContent(detailContent(selectedSession()));
471
474
  updateFocusStyles();
472
475
  screen.render();
@@ -891,6 +894,11 @@ async function runWorkbench() {
891
894
  projectsList.focus();
892
895
  render();
893
896
  startRemoteReload(true);
897
+ checkForUpdate(pkg.version).then((nextUpdateInfo) => {
898
+ if (closed || !nextUpdateInfo) return;
899
+ updateInfo = nextUpdateInfo;
900
+ render();
901
+ });
894
902
 
895
903
  return new Promise(() => {});
896
904
  }