@shardflux/cli 0.18.0-next.5 → 0.18.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.
- package/CHANGELOG.md +61 -8
- package/README.md +92 -33
- package/dist/account-args.js +5 -3
- package/dist/account-commands.d.ts +4 -1
- package/dist/account-commands.js +104 -14
- package/dist/account-pool.d.ts +31 -0
- package/dist/account-pool.js +313 -0
- package/dist/agent-setup.d.ts +38 -0
- package/dist/agent-setup.js +116 -0
- package/dist/agent-target-commands.js +10 -1
- package/dist/agent-targets.d.ts +11 -0
- package/dist/agent-targets.js +22 -4
- package/dist/args.d.ts +3 -1
- package/dist/args.js +50 -5
- package/dist/claude-cloud-login.d.ts +11 -0
- package/dist/claude-cloud-login.js +49 -39
- package/dist/cli-proxy-hub.d.ts +26 -1
- package/dist/cli-proxy-hub.js +60 -4
- package/dist/commands.d.ts +3 -0
- package/dist/commands.js +7 -3
- package/dist/conversation-commands.d.ts +5 -0
- package/dist/conversation-commands.js +8 -2
- package/dist/conversations.d.ts +9 -0
- package/dist/desktop-linux.js +6 -5
- package/dist/filesystem-commands.d.ts +12 -2
- package/dist/filesystem-commands.js +46 -18
- package/dist/git-remote-bin.js +0 -0
- package/dist/handlers.js +2 -1
- package/dist/input.d.ts +4 -2
- package/dist/input.js +6 -3
- package/dist/main.js +19 -8
- package/dist/menubar.js +1 -1
- package/dist/t3-asar.d.ts +12 -2
- package/dist/t3-asar.js +14 -5
- package/dist/t3-launch.js +4 -1
- package/dist/t3-linux.d.ts +12 -13
- package/dist/t3-linux.js +81 -87
- package/dist/t3-mode.d.ts +4 -3
- package/dist/t3-mode.js +15 -14
- package/dist/t3-version.d.ts +8 -0
- package/dist/t3-version.js +42 -0
- package/package.json +21 -7
package/dist/t3-linux.js
CHANGED
|
@@ -3,29 +3,38 @@
|
|
|
3
3
|
*
|
|
4
4
|
* T3 ships a .deb and an AppImage (electron-builder, 0.0.45 and the nightlies, verified from the release assets):
|
|
5
5
|
* - .deb (package `t3code`): the app in `/opt/T3 Code (Alpha)/` (`/opt/T3 Code (Nightly)/` for nightlies) with the
|
|
6
|
-
* executable `t3code`, `resources/app.asar` and `resources/package-type` ("deb");
|
|
7
|
-
* `/usr/share/applications/t3code.desktop` (`Name=T3 Code (Alpha)`, `Exec="/opt/T3 Code (Alpha)/t3code"
|
|
8
|
-
* `StartupWMClass=t3code`, `MimeType=x-scheme-handler/t3code;…`); `/usr/bin/t3code` is an update-alternatives
|
|
9
|
-
* made by its postinst. dpkg records nightlies as `0.0.46~nightly.<date>.<run>`.
|
|
10
|
-
* - AppImage: `T3-Code-<version>-<x86_64|arm64>.AppImage`, a
|
|
11
|
-
* and Gear Lever integrate it with a copy of the same entry (`
|
|
6
|
+
* executable `t3code`, `resources/app.asar` (package.json `name: "t3code"`) and `resources/package-type` ("deb");
|
|
7
|
+
* the entry `/usr/share/applications/t3code.desktop` (`Name=T3 Code (Alpha)`, `Exec="/opt/T3 Code (Alpha)/t3code"
|
|
8
|
+
* %U`, `StartupWMClass=t3code`, `MimeType=x-scheme-handler/t3code;…`); `/usr/bin/t3code` is an update-alternatives
|
|
9
|
+
* link made by its postinst. dpkg records nightlies as `0.0.46~nightly.<date>.<run>`.
|
|
10
|
+
* - AppImage: `T3-Code-<version>-<x86_64|arm64>.AppImage`, a type 2 AppImage (ELF, `AI\x02` at byte 8) that is never
|
|
11
|
+
* mounted here; AppImageLauncher and Gear Lever integrate it with a copy of the same entry (`Exec=<the AppImage> %U`).
|
|
12
|
+
* T3 publishes no flatpak or snap: the flatpak handling below follows flatpak's export layout with T3's app id
|
|
13
|
+
* (`com.t3tools.t3code`, its AppStream id), an unverified layout; a snap entry is accepted only when it runs an unpacked
|
|
14
|
+
* T3 directly.
|
|
12
15
|
*
|
|
13
|
-
* Entries are found in the XDG application folders (
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
+
* Entries are found in the XDG application folders (and snapd's and flatpak's exports) and keyed on what the entry says
|
|
17
|
+
* (its Name, and StartupWMClass, the t3code URL scheme or the flatpak id), never on its file name; only files whose name
|
|
18
|
+
* could be one are read. An entry is accepted only when it runs T3's own program with options: no shell, env prefix or
|
|
19
|
+
* other program, and an unpacked app's app.asar must be T3 Code's. Detection never reads the program of an unpacked app,
|
|
20
|
+
* and a computer without T3 costs a few directory listings and no subprocess.
|
|
16
21
|
*/
|
|
17
22
|
import { spawn, spawnSync } from 'node:child_process';
|
|
18
23
|
import { accessSync, constants, openSync, readFileSync, readSync, closeSync, readdirSync, realpathSync, statSync } from 'node:fs';
|
|
19
24
|
import { homedir } from 'node:os';
|
|
20
25
|
import { basename, delimiter, dirname, isAbsolute, join, relative } from 'node:path';
|
|
21
|
-
import { T3_SERVER_ENTRY,
|
|
26
|
+
import { T3_SERVER_ENTRY, asarPackage, t3ArchiveSupportsAcp } from "./t3-asar.js";
|
|
27
|
+
import { t3WritesLauncher } from "./t3-version.js";
|
|
22
28
|
const RELEASE = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
|
|
23
29
|
const MAYBE_ENTRY = /t3|^appimagekit_|^gearlever_/i;
|
|
24
30
|
const APPIMAGE = /^t3[-_ ]?code.*\.appimage$/i;
|
|
25
31
|
/** `T3-Code-<version>-<arch>.AppImage`, also as AppImageLauncher renames it (`…-x86_64_<hash>.AppImage`). */
|
|
26
32
|
const APPIMAGE_VERSION = /^T3[-_ ]?Code-(\d+\.\d+\.\d+(?:-[0-9A-Za-z.]+)?)-(?:x86_64|x64|amd64|arm64|aarch64|armv7l)(?![0-9A-Za-z])/i;
|
|
33
|
+
/** T3's app id (its AppStream id); a T3 flatpak with it is an assumption, not a published package. */
|
|
27
34
|
const FLATPAK_ID = 'com.t3tools.t3code';
|
|
28
|
-
/**
|
|
35
|
+
/** Programs that would run something other than T3 itself. */
|
|
36
|
+
const WRAPPERS = /^(?:env|sh|bash|dash|zsh|ksh|fish|busybox|sudo|pkexec|nohup)$/;
|
|
37
|
+
/** The launcher T3 (from v0.0.46-preview.20261008.2800) writes to `<T3 home>/bin/t3` for its bundled CLI; for an AppImage it mounts the image itself. */
|
|
29
38
|
export const T3_LAUNCHER_MARKER = "Written by T3 Code: runs the desktop app's bundled t3 CLI.";
|
|
30
39
|
/**
|
|
31
40
|
* The folders scanned on Linux, in XDG precedence: the data home's and each data dir's `applications`, snapd's and
|
|
@@ -106,6 +115,12 @@ const executable = (path) => { try {
|
|
|
106
115
|
catch {
|
|
107
116
|
return false;
|
|
108
117
|
} };
|
|
118
|
+
const isFile = (path) => { try {
|
|
119
|
+
return statSync(path).isFile();
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
return false;
|
|
123
|
+
} };
|
|
109
124
|
const real = (path) => { try {
|
|
110
125
|
return realpathSync(path);
|
|
111
126
|
}
|
|
@@ -114,10 +129,8 @@ catch {
|
|
|
114
129
|
} };
|
|
115
130
|
const onPath = (name, env) => isAbsolute(name) ? (executable(name) ? name : undefined) : (env.PATH ?? '').split(delimiter).filter(Boolean).map(dir => join(dir, name)).find(executable);
|
|
116
131
|
const release = (value) => value && RELEASE.test(value) ? value : null;
|
|
117
|
-
/** A type 2 AppImage: an ELF file with `AI\x02` at byte 8. */
|
|
132
|
+
/** A type 2 AppImage: an ELF file with `AI\x02` at byte 8 (as T3-Code-0.0.45-x86_64.AppImage and the nightlies start). */
|
|
118
133
|
function isAppImage(path) {
|
|
119
|
-
if (/\.appimage$/i.test(path))
|
|
120
|
-
return true;
|
|
121
134
|
let fd;
|
|
122
135
|
try {
|
|
123
136
|
fd = openSync(path, 'r');
|
|
@@ -139,12 +152,7 @@ function dpkgVersion(env) {
|
|
|
139
152
|
}
|
|
140
153
|
/** The resources/app.asar of an unpacked Electron app, one or two folders below `root` (a flatpak's files). */
|
|
141
154
|
function findAsar(root) {
|
|
142
|
-
const at = (dir) => { const asar = join(dir, 'resources', 'app.asar');
|
|
143
|
-
return statSync(asar).isFile() ? asar : undefined;
|
|
144
|
-
}
|
|
145
|
-
catch {
|
|
146
|
-
return undefined;
|
|
147
|
-
} };
|
|
155
|
+
const at = (dir) => { const asar = join(dir, 'resources', 'app.asar'); return isFile(asar) ? asar : undefined; };
|
|
148
156
|
const children = (dir) => { try {
|
|
149
157
|
return readdirSync(dir, { withFileTypes: true }).filter(d => d.isDirectory()).map(d => join(dir, d.name));
|
|
150
158
|
}
|
|
@@ -153,59 +161,53 @@ function findAsar(root) {
|
|
|
153
161
|
} };
|
|
154
162
|
return at(root) ?? children(root).map(at).find(Boolean) ?? children(root).flatMap(children).slice(0, 64).map(at).find(Boolean);
|
|
155
163
|
}
|
|
156
|
-
/** The
|
|
164
|
+
/** The flatpak an exported entry runs (unverified layout: `flatpak run [options] <id>`, files under the installation). */
|
|
165
|
+
function fromFlatpak(e, exec, id, dir) {
|
|
166
|
+
const appId = e['X-Flatpak'];
|
|
167
|
+
if (basename(exec[0]) !== 'flatpak' || !exec.slice(1).every(a => a.startsWith('-') || a === 'run' || a === appId))
|
|
168
|
+
return undefined;
|
|
169
|
+
const root = dir.endsWith(join('exports', 'share', 'applications')) ? dirname(dirname(dirname(dir))) : '/var/lib/flatpak';
|
|
170
|
+
const files = join(root, 'app', appId, 'current', 'active', 'files');
|
|
171
|
+
const asar = findAsar(files);
|
|
172
|
+
const pkg = asar ? asarPackage(asar) : null;
|
|
173
|
+
if (!asar || !pkg?.t3)
|
|
174
|
+
return undefined;
|
|
175
|
+
const command = exec.find(a => a.startsWith('--command='))?.slice('--command='.length);
|
|
176
|
+
const exe = join(dirname(dirname(asar)), basename(command || 't3code'));
|
|
177
|
+
return { path: exe, version: pkg.version, linux: { format: 'flatpak', desktop_id: id, exec, asar: executable(exe) ? asar : null, flatpak: { id: appId, files } } };
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* The install a T3 entry describes: an unpacked app (its program sits next to `resources/app.asar`, which must be T3
|
|
181
|
+
* Code's; the program itself is never read) or an AppImage (checked by its magic bytes). Refused: a shell, env prefix
|
|
182
|
+
* or other wrapper, an argument that is not an option, and a program that resolves anywhere else.
|
|
183
|
+
*/
|
|
157
184
|
function fromEntry(e, id, dir, env) {
|
|
158
185
|
const exec = desktopExec(e.Exec ?? '');
|
|
159
186
|
if (!exec?.length)
|
|
160
187
|
return undefined;
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
i++;
|
|
166
|
-
}
|
|
167
|
-
const program = exec[i];
|
|
168
|
-
if (!program)
|
|
188
|
+
if (e['X-Flatpak'])
|
|
189
|
+
return fromFlatpak(e, exec, id, dir);
|
|
190
|
+
const [program, ...args] = exec;
|
|
191
|
+
if (WRAPPERS.test(basename(program)) || args.some(a => !a.startsWith('-')))
|
|
169
192
|
return undefined;
|
|
170
|
-
const entryVersion = release(e['X-AppImage-Version']);
|
|
171
|
-
if (e['X-Flatpak']) {
|
|
172
|
-
const appId = e['X-Flatpak'];
|
|
173
|
-
const root = dir.endsWith(join('exports', 'share', 'applications')) ? dirname(dirname(dirname(dir))) : '/var/lib/flatpak';
|
|
174
|
-
const files = join(root, 'app', appId, 'current', 'active', 'files');
|
|
175
|
-
const asar = findAsar(files) ?? null;
|
|
176
|
-
const command = exec.find(a => a.startsWith('--command='))?.slice('--command='.length);
|
|
177
|
-
const exe = asar ? join(dirname(dirname(asar)), basename(command || 't3code')) : files;
|
|
178
|
-
return { path: exe, version: (asar ? asarVersion(asar) : null) ?? entryVersion, linux: { format: 'flatpak', desktop_id: id, exec, asar: executable(exe) ? asar : null, flatpak: { id: appId, files } } };
|
|
179
|
-
}
|
|
180
193
|
const resolved = onPath(program, env);
|
|
181
194
|
if (!resolved)
|
|
182
195
|
return undefined;
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
196
|
+
const target = real(resolved), resources = join(dirname(target), 'resources');
|
|
197
|
+
if (isFile(join(resources, 'app.asar')) || isFile(join(resources, 'package-type'))) {
|
|
198
|
+
const asar = join(resources, 'app.asar'), pkg = asarPackage(asar);
|
|
199
|
+
if (!pkg?.t3)
|
|
200
|
+
return undefined;
|
|
201
|
+
let deb = false;
|
|
186
202
|
try {
|
|
187
|
-
|
|
203
|
+
deb = readFileSync(join(resources, 'package-type'), 'utf8').trim() === 'deb';
|
|
188
204
|
}
|
|
189
|
-
catch { /*
|
|
190
|
-
return { path:
|
|
205
|
+
catch { /* not from the .deb */ }
|
|
206
|
+
return { path: target, version: pkg.version ?? (deb ? dpkgVersion(env) : null), linux: { format: deb ? 'deb' : 'directory', desktop_id: id, exec, asar } };
|
|
191
207
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
}
|
|
196
|
-
const asar = join(dirname(target), 'resources', 'app.asar');
|
|
197
|
-
let found = false;
|
|
198
|
-
try {
|
|
199
|
-
found = statSync(asar).isFile();
|
|
200
|
-
}
|
|
201
|
-
catch { /* not an unpacked Electron app */ }
|
|
202
|
-
let deb = false;
|
|
203
|
-
try {
|
|
204
|
-
deb = readFileSync(join(dirname(target), 'resources', 'package-type'), 'utf8').trim() === 'deb';
|
|
205
|
-
}
|
|
206
|
-
catch { /* not from the .deb */ }
|
|
207
|
-
const version = (found ? asarVersion(asar) : null) ?? (deb ? dpkgVersion(env) : null) ?? entryVersion;
|
|
208
|
-
return { path: target, version, linux: { format: deb ? 'deb' : 'directory', desktop_id: id, exec, asar: found ? asar : null } };
|
|
208
|
+
if (!isAppImage(target))
|
|
209
|
+
return undefined;
|
|
210
|
+
return { path: target, version: release(e['X-AppImage-Version']) ?? release(APPIMAGE_VERSION.exec(basename(target))?.[1]), linux: { format: 'appimage', desktop_id: id, exec, asar: null } };
|
|
209
211
|
}
|
|
210
212
|
/** The installed T3 Code apps on Linux, from the desktop entries and AppImages in `dirs` (linuxApplicationDirs). */
|
|
211
213
|
export function linuxT3Apps(dirs, env) {
|
|
@@ -244,26 +246,21 @@ export function linuxT3Apps(dirs, env) {
|
|
|
244
246
|
}
|
|
245
247
|
for (const file of loose) {
|
|
246
248
|
const path = real(file);
|
|
247
|
-
if (
|
|
249
|
+
if (found.some(f => f.path === path) || !executable(path) || !isAppImage(path))
|
|
248
250
|
continue;
|
|
249
251
|
found.push({ path, version: release(APPIMAGE_VERSION.exec(basename(path))?.[1]), linux: { format: 'appimage', desktop_id: null, exec: [path], asar: null } });
|
|
250
252
|
}
|
|
251
253
|
return found;
|
|
252
254
|
}
|
|
253
|
-
/** `0.0.46` and later (nightlies of 0.0.46 included). */
|
|
254
|
-
const atLeast046 = (version) => {
|
|
255
|
-
const [major = 0, minor = 0, patch = 0] = /^(\d+)\.(\d+)\.(\d+)/.exec(version ?? '')?.slice(1).map(Number) ?? [];
|
|
256
|
-
return major > 0 || minor > 0 || patch >= 46;
|
|
257
|
-
};
|
|
258
255
|
/**
|
|
259
256
|
* Whether this install runs the Shardflux provider (ACP mode): the server in its app.asar has the ACP Registry driver
|
|
260
|
-
* (deb, unpacked installs, flatpak). An AppImage is never mounted to look: its
|
|
261
|
-
*
|
|
257
|
+
* (deb, unpacked installs, flatpak). An AppImage is never mounted to look: its sessions come from T3's own launcher, so
|
|
258
|
+
* it is ACP from the first build that writes one (t3WritesLauncher); earlier builds start threads over SSH.
|
|
262
259
|
*/
|
|
263
260
|
export function linuxSupportsAcp(app) {
|
|
264
261
|
if (app.linux.format === 'appimage')
|
|
265
|
-
return
|
|
266
|
-
return
|
|
262
|
+
return t3WritesLauncher(app.version);
|
|
263
|
+
return !!app.linux.asar && t3ArchiveSupportsAcp(app.linux.asar);
|
|
267
264
|
}
|
|
268
265
|
/**
|
|
269
266
|
* The bundled T3 server CLI (Electron as Node, ELECTRON_RUN_AS_NODE=1 set by the caller): the app's executable with
|
|
@@ -288,6 +285,16 @@ export function linuxServerCli(app, t3Home) {
|
|
|
288
285
|
}
|
|
289
286
|
return undefined;
|
|
290
287
|
}
|
|
288
|
+
/**
|
|
289
|
+
* The environment T3 is opened with: the caller's, without what would make its Electron run as plain Node
|
|
290
|
+
* (ELECTRON_RUN_AS_NODE: `t3code` would exit at once while the open still looked fine) or detach its console.
|
|
291
|
+
*/
|
|
292
|
+
export function t3OpenEnv(env) {
|
|
293
|
+
const clean = { ...env };
|
|
294
|
+
delete clean.ELECTRON_RUN_AS_NODE;
|
|
295
|
+
delete clean.ELECTRON_NO_ATTACH_CONSOLE;
|
|
296
|
+
return clean;
|
|
297
|
+
}
|
|
291
298
|
/**
|
|
292
299
|
* How T3 is opened (or brought to the front, as a second launch of T3 hands over to the running one): `gtk-launch`
|
|
293
300
|
* with its desktop entry when there is one, else its own program, started detached.
|
|
@@ -301,7 +308,8 @@ export function linuxOpenCommands(app, env) {
|
|
|
301
308
|
];
|
|
302
309
|
}
|
|
303
310
|
/** Opens T3 Code on Linux. Nothing here ever stops a T3 process: a launcher that hangs is left alone. */
|
|
304
|
-
export async function openLinuxT3(
|
|
311
|
+
export async function openLinuxT3(callerEnv, app) {
|
|
312
|
+
const env = t3OpenEnv(callerEnv);
|
|
305
313
|
let last;
|
|
306
314
|
for (const { command, args, detached } of linuxOpenCommands(app, env)) {
|
|
307
315
|
try {
|
|
@@ -327,17 +335,3 @@ export async function openLinuxT3(env, app) {
|
|
|
327
335
|
}
|
|
328
336
|
throw new Error(`could not open T3 Code (${last instanceof Error ? last.message : String(last)})`);
|
|
329
337
|
}
|
|
330
|
-
/**
|
|
331
|
-
* When T3 last started, as the filesystem records it: the program's access time once it is later than its modification
|
|
332
|
-
* time (dpkg installs both at the build time; relatime updates the access time on the first start, then daily). Null
|
|
333
|
-
* before the first start and on noatime mounts.
|
|
334
|
-
*/
|
|
335
|
-
export function linuxLastUsed(path) {
|
|
336
|
-
try {
|
|
337
|
-
const s = statSync(path);
|
|
338
|
-
return s.atimeMs > s.mtimeMs + 1000 ? new Date(s.atimeMs).toISOString() : null;
|
|
339
|
-
}
|
|
340
|
-
catch {
|
|
341
|
-
return null;
|
|
342
|
-
}
|
|
343
|
-
}
|
package/dist/t3-mode.d.ts
CHANGED
|
@@ -47,8 +47,9 @@ export interface T3ThreadResult {
|
|
|
47
47
|
effort_reason?: EffortReason;
|
|
48
48
|
}
|
|
49
49
|
/**
|
|
50
|
-
* Native lookups that tests replace: Spotlight's last-used dates of app bundles, one `mdls` for all of them (macOS)
|
|
51
|
-
*
|
|
50
|
+
* Native lookups that tests replace: Spotlight's last-used dates of app bundles, one `mdls` for all of them (macOS).
|
|
51
|
+
* Linux has no reliable signal, so every date is null there: dpkg sets the program's access time to the install time,
|
|
52
|
+
* and any read (a scan, `dpkg --verify`, a backup) moves it.
|
|
52
53
|
*/
|
|
53
54
|
export declare const t3Native: {
|
|
54
55
|
lastUsedMany(paths: readonly string[], env: Record<string, string | undefined>, platform?: NodeJS.Platform): Record<string, string | null>;
|
|
@@ -56,7 +57,7 @@ export declare const t3Native: {
|
|
|
56
57
|
/** Spotlight takes about 40 ms per call, so the dates are kept for a few minutes (app-usage.json in the config dir). */
|
|
57
58
|
export declare const LAST_USED_TTL_MS: number;
|
|
58
59
|
export declare function lastUsedDates(ctx: Ctx, paths: readonly string[], now?: number): Record<string, string | null>;
|
|
59
|
-
/** The installed T3 Code
|
|
60
|
+
/** The installed T3 Code: the newest build with ACP providers when several are installed (preferredT3). */
|
|
60
61
|
export declare function t3Install(env: Record<string, string | undefined>, platform?: NodeJS.Platform, dirs?: string[]): T3Install | undefined;
|
|
61
62
|
export { t3HomeDir };
|
|
62
63
|
/** The Shardflux provider instance in T3's server settings (read only; every `shard` run writes it, see t3-provider.ts). */
|
package/dist/t3-mode.js
CHANGED
|
@@ -24,9 +24,9 @@ import { basename, join } from 'node:path';
|
|
|
24
24
|
import { setTimeout as delay } from 'node:timers/promises';
|
|
25
25
|
import { CodedError, UsageError, describeError } from "./errors.js";
|
|
26
26
|
import { AgentStateStore } from "./agent-state.js";
|
|
27
|
-
import { applicationDirs, desktopAppPath, t3Apps } from "./agent-targets.js";
|
|
28
|
-
import { configureT3Provider,
|
|
29
|
-
import {
|
|
27
|
+
import { applicationDirs, desktopAppPath, preferredT3, t3Apps } from "./agent-targets.js";
|
|
28
|
+
import { configureT3Provider, t3HomeDir } from "./t3-provider.js";
|
|
29
|
+
import { linuxServerCli, openLinuxT3, t3OpenEnv } from "./t3-linux.js";
|
|
30
30
|
import { acpSignedIn } from "./acp-login.js";
|
|
31
31
|
import { AcpModelCache } from "./acp-models.js";
|
|
32
32
|
import { nativeModelOptions } from "./acp-catalog.js";
|
|
@@ -49,15 +49,16 @@ const isoDate = (raw) => {
|
|
|
49
49
|
return parsed && !Number.isNaN(parsed.getTime()) ? parsed.toISOString() : null;
|
|
50
50
|
};
|
|
51
51
|
/**
|
|
52
|
-
* Native lookups that tests replace: Spotlight's last-used dates of app bundles, one `mdls` for all of them (macOS)
|
|
53
|
-
*
|
|
52
|
+
* Native lookups that tests replace: Spotlight's last-used dates of app bundles, one `mdls` for all of them (macOS).
|
|
53
|
+
* Linux has no reliable signal, so every date is null there: dpkg sets the program's access time to the install time,
|
|
54
|
+
* and any read (a scan, `dpkg --verify`, a backup) moves it.
|
|
54
55
|
*/
|
|
55
56
|
export const t3Native = {
|
|
56
57
|
lastUsedMany(paths, env, platform = process.platform) {
|
|
57
58
|
if (!paths.length)
|
|
58
59
|
return {};
|
|
59
60
|
if (platform !== 'darwin')
|
|
60
|
-
return Object.fromEntries(paths.map(path => [path,
|
|
61
|
+
return Object.fromEntries(paths.map(path => [path, null]));
|
|
61
62
|
const r = spawnSync('mdls', ['-name', 'kMDItemLastUsedDate', '-raw', ...paths], { env, encoding: 'utf8', input: '', timeout: 3000 });
|
|
62
63
|
// -raw separates the values with NUL; a bundle Spotlight has no date for prints "(null)".
|
|
63
64
|
const values = r.status === 0 ? r.stdout.split('\0').map(v => v.trim()) : [];
|
|
@@ -83,14 +84,13 @@ export function lastUsedDates(ctx, paths, now = Date.now()) {
|
|
|
83
84
|
catch { /* a cache only */ }
|
|
84
85
|
return dates;
|
|
85
86
|
}
|
|
86
|
-
/**
|
|
87
|
-
const supportsAcp = (app) => app.linux ? linuxSupportsAcp({ ...app, linux: app.linux }) : t3BundleSupportsAcp(app.path);
|
|
88
|
-
/** The installed T3 Code, preferring a build with ACP providers when several channels are installed. */
|
|
87
|
+
/** The installed T3 Code: the newest build with ACP providers when several are installed (preferredT3). */
|
|
89
88
|
export function t3Install(env, platform = process.platform, dirs = applicationDirs(env, platform)) {
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
89
|
+
const picked = preferredT3(t3Apps(dirs, platform, env));
|
|
90
|
+
if (!picked)
|
|
91
|
+
return undefined;
|
|
92
|
+
const { app, acp } = picked;
|
|
93
|
+
return { path: app.path, version: app.version, mode: acp ? 'acp' : 'ssh', ...(app.linux ? { linux: app.linux } : {}) };
|
|
94
94
|
}
|
|
95
95
|
export { t3HomeDir };
|
|
96
96
|
/** The Shardflux provider instance in T3's server settings (read only; every `shard` run writes it, see t3-provider.ts). */
|
|
@@ -219,7 +219,8 @@ export async function openT3(env, install) {
|
|
|
219
219
|
throw t3NotInstalled();
|
|
220
220
|
if (install.linux)
|
|
221
221
|
return openLinuxT3(env, { path: install.path, version: install.version, linux: install.linux });
|
|
222
|
-
|
|
222
|
+
// As on Linux: an inherited ELECTRON_RUN_AS_NODE would start T3 as plain Node (t3OpenEnv).
|
|
223
|
+
const child = spawn('open', ['-a', install.path], { env: t3OpenEnv(env), stdio: 'ignore' });
|
|
223
224
|
await new Promise((resolve, reject) => {
|
|
224
225
|
const timer = setTimeout(() => { child.kill('SIGKILL'); reject(new Error('T3 Code did not open in time')); }, 10_000);
|
|
225
226
|
child.once('error', e => { clearTimeout(timer); reject(e); });
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Orders T3 versions (newest last): by release, a release after its own prereleases, builds of one release by run. */
|
|
2
|
+
export declare function compareT3Versions(a: string | null, b: string | null): number;
|
|
3
|
+
/**
|
|
4
|
+
* Whether this T3 writes its own CLI launcher (`<T3 home>/bin/t3`, DesktopCliShim, upstream 73e87f683ae1 of
|
|
5
|
+
* 2026-10-07): 0.0.46 builds from run 2800 on (v0.0.46-preview.20261008.2800 is the first; nightly 2787 predates it),
|
|
6
|
+
* 0.0.46 itself and later releases.
|
|
7
|
+
*/
|
|
8
|
+
export declare function t3WritesLauncher(version: string | null): boolean;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* T3 Code release versions: `0.0.45`, and builds of the next release `0.0.46-<nightly|preview>.<YYYYMMDD>.<run>`, where
|
|
3
|
+
* `<run>` is one CI run counter shared by both channels (nightly 2787 < preview 2800 < nightly 2801).
|
|
4
|
+
*/
|
|
5
|
+
const PARTS = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/;
|
|
6
|
+
function parse(version) {
|
|
7
|
+
const m = PARTS.exec(version);
|
|
8
|
+
if (!m)
|
|
9
|
+
return undefined;
|
|
10
|
+
const pre = m[4] ?? null;
|
|
11
|
+
return { core: [Number(m[1]), Number(m[2]), Number(m[3])], pre, run: pre ? Number(/\.(\d+)$/.exec(pre)?.[1] ?? NaN) : null };
|
|
12
|
+
}
|
|
13
|
+
/** Orders T3 versions (newest last): by release, a release after its own prereleases, builds of one release by run. */
|
|
14
|
+
export function compareT3Versions(a, b) {
|
|
15
|
+
const x = a ? parse(a) : undefined, y = b ? parse(b) : undefined;
|
|
16
|
+
if (!x || !y)
|
|
17
|
+
return x ? 1 : y ? -1 : 0;
|
|
18
|
+
for (let i = 0; i < 3; i++)
|
|
19
|
+
if (x.core[i] !== y.core[i])
|
|
20
|
+
return x.core[i] - y.core[i];
|
|
21
|
+
if (!x.pre || !y.pre)
|
|
22
|
+
return x.pre ? -1 : y.pre ? 1 : 0;
|
|
23
|
+
if (Number.isFinite(x.run) && Number.isFinite(y.run) && x.run !== y.run)
|
|
24
|
+
return x.run - y.run;
|
|
25
|
+
return x.pre < y.pre ? -1 : x.pre > y.pre ? 1 : 0;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Whether this T3 writes its own CLI launcher (`<T3 home>/bin/t3`, DesktopCliShim, upstream 73e87f683ae1 of
|
|
29
|
+
* 2026-10-07): 0.0.46 builds from run 2800 on (v0.0.46-preview.20261008.2800 is the first; nightly 2787 predates it),
|
|
30
|
+
* 0.0.46 itself and later releases.
|
|
31
|
+
*/
|
|
32
|
+
export function t3WritesLauncher(version) {
|
|
33
|
+
const v = version ? parse(version) : undefined;
|
|
34
|
+
if (!v)
|
|
35
|
+
return false;
|
|
36
|
+
const [major, minor, patch] = v.core;
|
|
37
|
+
if (major > 0 || minor > 0 || patch > 46)
|
|
38
|
+
return true;
|
|
39
|
+
if (patch < 46)
|
|
40
|
+
return false;
|
|
41
|
+
return !v.pre || (Number.isFinite(v.run) && v.run >= 2800);
|
|
42
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shardflux/cli",
|
|
3
|
-
"version": "0.18.0
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "shard: the Shardflux command line. Create an account, sign in, set up keys and billing, then open, run commands in, move files to, suspend, resume and fork serverless agent workspaces.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -43,17 +43,31 @@
|
|
|
43
43
|
"access": "public"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@shardflux/sdk": "0.24.0-next.5",
|
|
47
46
|
"classic-level": "3.0.0",
|
|
48
47
|
"smol-toml": "1.9.0",
|
|
49
48
|
"snappyjs": "0.7.0",
|
|
50
|
-
"yaml": "2.9.1"
|
|
49
|
+
"yaml": "2.9.1",
|
|
50
|
+
"@shardflux/sdk": "^0.24.0"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@eslint/js": "10.0.1",
|
|
54
|
+
"@types/node": "24.13.6",
|
|
55
|
+
"eslint": "10.11.0",
|
|
56
|
+
"playwright": "1.62.1",
|
|
57
|
+
"typescript": "5.9.3",
|
|
58
|
+
"typescript-eslint": "8.70.1"
|
|
51
59
|
},
|
|
52
60
|
"optionalDependencies": {
|
|
53
|
-
"@shardflux/
|
|
54
|
-
"@shardflux/desktop-linux-
|
|
55
|
-
"@shardflux/
|
|
61
|
+
"@shardflux/desktop-linux-x64": "0.1.0",
|
|
62
|
+
"@shardflux/desktop-linux-arm64": "0.1.0",
|
|
63
|
+
"@shardflux/menubar-darwin": "^0.6.0"
|
|
64
|
+
},
|
|
65
|
+
"scripts": {
|
|
66
|
+
"build": "node scripts/build.mjs",
|
|
67
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
68
|
+
"lint": "eslint .",
|
|
69
|
+
"test": "node --test test/*.test.ts"
|
|
56
70
|
},
|
|
57
71
|
"main": "./dist/index.js",
|
|
58
72
|
"types": "./dist/index.d.ts"
|
|
59
|
-
}
|
|
73
|
+
}
|