@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 +2 -2
- package/src/services/update-checker.js +72 -0
- package/src/ui/workbench.js +13 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bramblex/codex-workbench",
|
|
3
|
-
"version": "0.1.
|
|
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
|
+
};
|
package/src/ui/workbench.js
CHANGED
|
@@ -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:
|
|
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:
|
|
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
|
-
|
|
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
|
}
|