@bramblex/codex-workbench 0.1.15 → 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.15",
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
+ };
@@ -3,6 +3,7 @@
3
3
  const path = require('path');
4
4
  require('./blessed-compat');
5
5
  const blessed = require('blessed');
6
+ const pkg = require('../../package.json');
6
7
  const { printList, printShow } = require('../cli-output');
7
8
  const { deleteSessionFile } = require('../model/session-store');
8
9
  const { localTime, shortId, truncate } = require('../model/format');
@@ -20,6 +21,7 @@ const {
20
21
  updateSourceMetadata,
21
22
  } = require('../services/session-sources');
22
23
  const { usableCwd } = require('../services/codex-runner');
24
+ const { checkForUpdate } = require('../services/update-checker');
23
25
  const { createDirectoryPicker } = require('./directory-picker');
24
26
 
25
27
  async function runWorkbench() {
@@ -27,6 +29,7 @@ async function runWorkbench() {
27
29
  return printList(loadWorkbenchSessions().sessions);
28
30
  }
29
31
 
32
+ const appTitle = `Codex Workbench v${pkg.version}`;
30
33
  let sessions = [];
31
34
  let sources = [];
32
35
  let sourceErrors = [];
@@ -40,12 +43,13 @@ async function runWorkbench() {
40
43
  let activePanel = 'projects';
41
44
  let remoteLoadId = 0;
42
45
  let remoteLoading = false;
46
+ let updateInfo = null;
43
47
  let closed = false;
44
48
 
45
49
  const screen = blessed.screen({
46
50
  smartCSR: true,
47
51
  fullUnicode: true,
48
- title: 'Codex Workbench',
52
+ title: appTitle,
49
53
  });
50
54
 
51
55
  const header = blessed.box({
@@ -56,7 +60,7 @@ async function runWorkbench() {
56
60
  height: 3,
57
61
  padding: { left: 1, right: 1 },
58
62
  style: { fg: 'white', bg: 'blue' },
59
- content: 'Codex Workbench',
63
+ content: appTitle,
60
64
  });
61
65
 
62
66
  const projectsList = blessed.list({
@@ -464,7 +468,8 @@ async function runWorkbench() {
464
468
  const render = () => {
465
469
  applyLayout();
466
470
  const visible = currentSessions();
467
- header.setContent(` Codex Workbench\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())}`);
468
473
  detailBox.setContent(detailContent(selectedSession()));
469
474
  updateFocusStyles();
470
475
  screen.render();
@@ -889,6 +894,11 @@ async function runWorkbench() {
889
894
  projectsList.focus();
890
895
  render();
891
896
  startRemoteReload(true);
897
+ checkForUpdate(pkg.version).then((nextUpdateInfo) => {
898
+ if (closed || !nextUpdateInfo) return;
899
+ updateInfo = nextUpdateInfo;
900
+ render();
901
+ });
892
902
 
893
903
  return new Promise(() => {});
894
904
  }