@hellix-ai/cubes 0.1.0 → 0.1.1
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/dist/cli.js +22 -3
- package/dist/sessions.js +5 -5
- package/dist/upgrade.js +105 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -14,7 +14,9 @@ import { readClipboardImage } from "./clipboard.js";
|
|
|
14
14
|
import { clearLogin, configPath, loadLogin, saveLogin } from "./config.js";
|
|
15
15
|
import { parseArgs } from "./args.js";
|
|
16
16
|
import { formatTable, isUuid, listSessions, LIVE, resolveSession, stateOf } from "./sessions.js";
|
|
17
|
-
|
|
17
|
+
import { upgrade } from "./upgrade.js";
|
|
18
|
+
const PKG = createRequire(import.meta.url)("../package.json");
|
|
19
|
+
const VERSION = PKG.version;
|
|
18
20
|
const HELP = `cubes ${VERSION} - attach to your Cubes sessions from your own terminal
|
|
19
21
|
|
|
20
22
|
Usage:
|
|
@@ -24,6 +26,7 @@ Usage:
|
|
|
24
26
|
cubes org [ALIAS] list your orgs, or switch the default
|
|
25
27
|
cubes ls [--all] [--json] list sessions (--all includes spawned ones)
|
|
26
28
|
cubes attach [SESSION] [--view] [--no-resume] attach to a session
|
|
29
|
+
cubes upgrade [--check] install the newest cubes (--check only reports)
|
|
27
30
|
|
|
28
31
|
SESSION is an id, the start of one (4+ characters), or part of its title. Left out, you pick from
|
|
29
32
|
the running sessions. A stopped or hibernated session is resumed first unless --no-resume.
|
|
@@ -281,10 +284,26 @@ async function main(argv) {
|
|
|
281
284
|
return 0;
|
|
282
285
|
case "attach":
|
|
283
286
|
case "a": return cmdAttach(a);
|
|
287
|
+
case "upgrade":
|
|
288
|
+
case "update":
|
|
289
|
+
return upgrade({
|
|
290
|
+
pkg: PKG.name, current: VERSION, scriptPath: process.argv[1] ?? "",
|
|
291
|
+
log: (line) => process.stderr.write(`${line}\n`),
|
|
292
|
+
}, { check: a.flags.has("check") });
|
|
284
293
|
default: throw new CliError(`unknown command "${a.cmd}" - see \`cubes help\``);
|
|
285
294
|
}
|
|
286
295
|
}
|
|
287
|
-
|
|
296
|
+
/** Exit only once everything written has reached its destination. A write to a PIPE is asynchronous
|
|
297
|
+
* in Node, so a bare process.exit() after `cubes ls --json | jq` cut the JSON off mid-string. An empty
|
|
298
|
+
* write's callback fires after every write queued before it. */
|
|
299
|
+
function exitAfterFlush(code) {
|
|
300
|
+
let pending = 2;
|
|
301
|
+
const done = () => { if (--pending === 0)
|
|
302
|
+
process.exit(code); };
|
|
303
|
+
process.stdout.write("", done);
|
|
304
|
+
process.stderr.write("", done);
|
|
305
|
+
}
|
|
306
|
+
main(process.argv.slice(2)).then((code) => exitAfterFlush(code), (e) => {
|
|
288
307
|
process.stderr.write(`cubes: ${e instanceof Error ? e.message : String(e)}\n`);
|
|
289
|
-
|
|
308
|
+
exitAfterFlush(e instanceof CliError ? e.exitCode : 1);
|
|
290
309
|
});
|
package/dist/sessions.js
CHANGED
|
@@ -65,19 +65,19 @@ function cut(s, w) {
|
|
|
65
65
|
return s.length <= w ? s : s.slice(0, w - 1) + "…";
|
|
66
66
|
}
|
|
67
67
|
export function formatTable(rows, width, now = Date.now()) {
|
|
68
|
-
const head = ["ID", "STATE", "AGE", "TITLE", "
|
|
68
|
+
const head = ["ID", "STATE", "AGE", "TITLE", "GROUP"];
|
|
69
69
|
const body = rows.map((s) => [
|
|
70
70
|
s.id.slice(0, 8),
|
|
71
71
|
stateOf(s),
|
|
72
72
|
age(s.last_attached_at ?? s.created_at, now),
|
|
73
73
|
s.title || "(untitled)",
|
|
74
|
-
s.
|
|
74
|
+
s.group_name ?? "",
|
|
75
75
|
]);
|
|
76
76
|
const w = (i) => Math.max(head[i].length, ...body.map((r) => r[i].length));
|
|
77
77
|
const fixed = [w(0), w(1), w(2)];
|
|
78
|
-
const
|
|
79
|
-
const titleW = Math.max(10, Math.min(w(3), width - fixed.reduce((a, b) => a + b, 0) -
|
|
78
|
+
const groupW = Math.min(w(4), 32);
|
|
79
|
+
const titleW = Math.max(10, Math.min(w(3), width - fixed.reduce((a, b) => a + b, 0) - groupW - 8));
|
|
80
80
|
const line = (r) => [r[0].padEnd(fixed[0]), r[1].padEnd(fixed[1]), r[2].padStart(fixed[2]),
|
|
81
|
-
cut(r[3], titleW).padEnd(titleW), cut(r[4],
|
|
81
|
+
cut(r[3], titleW).padEnd(titleW), cut(r[4], groupW)].join(" ").trimEnd();
|
|
82
82
|
return [line(head), ...body.map(line)].join("\n");
|
|
83
83
|
}
|
package/dist/upgrade.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// `cubes upgrade`: ask npm for the newest release and reinstall with whatever installed this copy.
|
|
2
|
+
//
|
|
3
|
+
// The install command has to match how the CLI got onto the machine: `npm install -g` over a pnpm or
|
|
4
|
+
// bun global install leaves the old copy first on PATH, and an npx run is a throwaway cache entry
|
|
5
|
+
// that `npx @hellix-ai/cubes@latest` already refreshes. The path of the running script says which.
|
|
6
|
+
import { spawn } from "node:child_process";
|
|
7
|
+
import { CliError } from "./auth.js";
|
|
8
|
+
export const REGISTRY = "https://registry.npmjs.org";
|
|
9
|
+
/** -1 / 0 / 1 for plain x.y.z versions. A prerelease sorts below its release (0.2.0-rc < 0.2.0). */
|
|
10
|
+
export function compareVersions(a, b) {
|
|
11
|
+
const parse = (v) => {
|
|
12
|
+
const [core = "", pre] = v.trim().replace(/^v/, "").split("-", 2);
|
|
13
|
+
return { nums: core.split(".").map((n) => Number.parseInt(n, 10) || 0), pre };
|
|
14
|
+
};
|
|
15
|
+
const x = parse(a);
|
|
16
|
+
const y = parse(b);
|
|
17
|
+
for (let i = 0; i < 3; i++) {
|
|
18
|
+
const d = (x.nums[i] ?? 0) - (y.nums[i] ?? 0);
|
|
19
|
+
if (d)
|
|
20
|
+
return d > 0 ? 1 : -1;
|
|
21
|
+
}
|
|
22
|
+
if (x.pre && !y.pre)
|
|
23
|
+
return -1;
|
|
24
|
+
if (!x.pre && y.pre)
|
|
25
|
+
return 1;
|
|
26
|
+
return 0;
|
|
27
|
+
}
|
|
28
|
+
export function installerFor(scriptPath) {
|
|
29
|
+
const p = scriptPath.replace(/\\/g, "/");
|
|
30
|
+
if (p.includes("/_npx/"))
|
|
31
|
+
return "npx";
|
|
32
|
+
if (p.includes("/.bun/"))
|
|
33
|
+
return "bun";
|
|
34
|
+
if (/\/pnpm\/|\/\.pnpm\//.test(p))
|
|
35
|
+
return "pnpm";
|
|
36
|
+
if (/\/yarn\/global\//.test(p))
|
|
37
|
+
return "yarn";
|
|
38
|
+
return "npm";
|
|
39
|
+
}
|
|
40
|
+
export function installCommand(installer, spec) {
|
|
41
|
+
switch (installer) {
|
|
42
|
+
case "pnpm": return ["pnpm", "add", "-g", spec];
|
|
43
|
+
case "bun": return ["bun", "add", "-g", spec];
|
|
44
|
+
case "yarn": return ["yarn", "global", "add", spec];
|
|
45
|
+
default: return ["npm", "install", "-g", spec];
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export async function latestVersion(pkg, fetchImpl = fetch) {
|
|
49
|
+
let res;
|
|
50
|
+
try {
|
|
51
|
+
res = await fetchImpl(`${REGISTRY}/${pkg.replace("/", "%2f")}/latest`, { signal: AbortSignal.timeout(10_000) });
|
|
52
|
+
}
|
|
53
|
+
catch (e) {
|
|
54
|
+
throw new CliError(`cannot reach the npm registry: ${e.message}`);
|
|
55
|
+
}
|
|
56
|
+
if (!res.ok)
|
|
57
|
+
throw new CliError(`the npm registry answered HTTP ${res.status} for ${pkg}`);
|
|
58
|
+
const body = (await res.json());
|
|
59
|
+
if (typeof body.version !== "string")
|
|
60
|
+
throw new CliError("the npm registry did not say what the latest version is");
|
|
61
|
+
return body.version;
|
|
62
|
+
}
|
|
63
|
+
function runInherited(argv) {
|
|
64
|
+
return new Promise((resolve) => {
|
|
65
|
+
// shell on Windows only, where the package managers are .cmd shims spawn cannot start directly.
|
|
66
|
+
const child = spawn(argv[0], argv.slice(1), { stdio: "inherit", shell: process.platform === "win32" });
|
|
67
|
+
child.on("error", () => resolve(127));
|
|
68
|
+
child.on("exit", (code) => resolve(code ?? 1));
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
/** Returns the process exit code. `check` only reports. */
|
|
72
|
+
export async function upgrade(d, opts = {}) {
|
|
73
|
+
const latest = await latestVersion(d.pkg, d.fetchImpl);
|
|
74
|
+
const cmp = compareVersions(d.current, latest);
|
|
75
|
+
if (cmp === 0) {
|
|
76
|
+
d.log(`cubes ${d.current} is the latest version.`);
|
|
77
|
+
return 0;
|
|
78
|
+
}
|
|
79
|
+
if (cmp > 0) {
|
|
80
|
+
d.log(`cubes ${d.current} is newer than the latest published (${latest}); nothing to do.`);
|
|
81
|
+
return 0;
|
|
82
|
+
}
|
|
83
|
+
const installer = installerFor(d.scriptPath);
|
|
84
|
+
const spec = `${d.pkg}@${latest}`;
|
|
85
|
+
if (installer === "npx") {
|
|
86
|
+
d.log(`cubes ${latest} is available (this is ${d.current}). You are running it through npx, so ask for the new one:`);
|
|
87
|
+
d.log(` npx ${d.pkg}@latest <command>`);
|
|
88
|
+
return 0;
|
|
89
|
+
}
|
|
90
|
+
const argv = installCommand(installer, spec);
|
|
91
|
+
if (opts.check) {
|
|
92
|
+
d.log(`cubes ${latest} is available (you have ${d.current}). Run \`cubes upgrade\`, or: ${argv.join(" ")}`);
|
|
93
|
+
return 0;
|
|
94
|
+
}
|
|
95
|
+
d.log(`Upgrading cubes ${d.current} -> ${latest}: ${argv.join(" ")}`);
|
|
96
|
+
const code = await (d.run ?? runInherited)(argv);
|
|
97
|
+
if (code === 127)
|
|
98
|
+
throw new CliError(`could not run \`${argv[0]}\` - upgrade by hand: ${argv.join(" ")}`);
|
|
99
|
+
if (code !== 0) {
|
|
100
|
+
throw new CliError(`the upgrade failed (exit ${code}). If it was a permissions error, your global ${argv[0]} ` +
|
|
101
|
+
`directory is not writable by you - run it with the rights it needs: ${argv.join(" ")}`);
|
|
102
|
+
}
|
|
103
|
+
d.log(`cubes is now ${latest}.`);
|
|
104
|
+
return 0;
|
|
105
|
+
}
|