@shardflux/cli 0.16.0 → 0.18.0-next.4

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.
Files changed (74) hide show
  1. package/CHANGELOG.md +67 -2
  2. package/README.md +47 -8
  3. package/dist/agent-commands.d.ts +9 -1
  4. package/dist/agent-commands.js +65 -16
  5. package/dist/agent-models.d.ts +61 -0
  6. package/dist/agent-models.js +79 -0
  7. package/dist/agent-state.d.ts +10 -0
  8. package/dist/agent-state.js +13 -0
  9. package/dist/agent-target-commands.d.ts +6 -1
  10. package/dist/agent-target-commands.js +113 -16
  11. package/dist/agent-targets.d.ts +17 -5
  12. package/dist/agent-targets.js +27 -11
  13. package/dist/args.d.ts +5 -1
  14. package/dist/args.js +49 -9
  15. package/dist/cli-proxy-forward.d.ts +45 -0
  16. package/dist/cli-proxy-forward.js +432 -0
  17. package/dist/cli-proxy-hub.d.ts +273 -0
  18. package/dist/cli-proxy-hub.js +749 -0
  19. package/dist/cli-proxy.d.ts +325 -0
  20. package/dist/cli-proxy.js +1016 -0
  21. package/dist/codex-cloud.d.ts +2 -1
  22. package/dist/codex-cloud.js +16 -6
  23. package/dist/command-center-actions.js +16 -1
  24. package/dist/conversation-local.js +2 -2
  25. package/dist/conversation-stash.js +4 -3
  26. package/dist/conversation-terminal.d.ts +5 -0
  27. package/dist/conversation-terminal.js +45 -6
  28. package/dist/conversations.d.ts +18 -2
  29. package/dist/conversations.js +12 -5
  30. package/dist/desktop-linux.d.ts +40 -0
  31. package/dist/desktop-linux.js +186 -0
  32. package/dist/device-approvals.d.ts +12 -0
  33. package/dist/device-approvals.js +75 -0
  34. package/dist/device-helper.d.ts +3 -1
  35. package/dist/device-helper.js +5 -2
  36. package/dist/errors.d.ts +2 -0
  37. package/dist/errors.js +1 -1
  38. package/dist/git-remote-bin.js +0 -0
  39. package/dist/handlers.js +3 -1
  40. package/dist/index.d.ts +4 -0
  41. package/dist/index.js +3 -0
  42. package/dist/linux-helper.d.ts +12 -0
  43. package/dist/linux-helper.js +88 -0
  44. package/dist/linux-terminal.d.ts +11 -0
  45. package/dist/linux-terminal.js +42 -0
  46. package/dist/main.js +30 -6
  47. package/dist/menubar.js +44 -4
  48. package/dist/onboarding.d.ts +2 -0
  49. package/dist/onboarding.js +59 -9
  50. package/dist/t3-asar.d.ts +10 -0
  51. package/dist/t3-asar.js +91 -0
  52. package/dist/t3-launch.d.ts +37 -0
  53. package/dist/t3-launch.js +133 -0
  54. package/dist/t3-linux.d.ts +68 -0
  55. package/dist/t3-linux.js +343 -0
  56. package/dist/t3-mode.d.ts +129 -0
  57. package/dist/t3-mode.js +676 -0
  58. package/dist/t3-phone.d.ts +17 -0
  59. package/dist/t3-phone.js +15 -0
  60. package/dist/t3-provider.d.ts +5 -3
  61. package/dist/t3-provider.js +12 -49
  62. package/dist/t3-thread-script.d.ts +18 -0
  63. package/dist/t3-thread-script.js +245 -0
  64. package/dist/terminal-onboarding.d.ts +11 -0
  65. package/dist/terminal-onboarding.js +75 -0
  66. package/dist/thread-on-computer.d.ts +2 -2
  67. package/dist/thread-on-computer.js +13 -7
  68. package/dist/update.d.ts +1 -1
  69. package/dist/update.js +28 -1
  70. package/dist/worktree-twin-engine.d.ts +31 -1
  71. package/dist/worktree-twin-engine.js +253 -40
  72. package/dist/worktree-twin.d.ts +17 -0
  73. package/dist/worktree-twin.js +147 -22
  74. package/package.json +7 -19
@@ -0,0 +1,91 @@
1
+ /**
2
+ * T3 Code's app.asar, read in place (nothing is extracted or run): the app's release version (its package.json) and
3
+ * whether its server has the ACP Registry provider driver. The same archive ships in the macOS bundle
4
+ * (Contents/Resources/app.asar) and in the Linux packages (`/opt/T3 Code (Alpha)/resources/app.asar` from the .deb).
5
+ * Answers are kept for the process per archive, keyed by its size and modification time.
6
+ */
7
+ import { closeSync, openSync, readFileSync, readSync, statSync } from 'node:fs';
8
+ import { join } from 'node:path';
9
+ /** The server CLI inside the archive: what Electron runs as Node to issue T3 sessions. */
10
+ export const T3_SERVER_ENTRY = 'apps/server/dist/bin.mjs';
11
+ const RELEASE = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
12
+ const DRIVER = ['ProviderDriverKind.make("acpRegistry")', "ProviderDriverKind.make('acpRegistry')"].map(s => Buffer.from(s));
13
+ const MAX_FILE = 64 << 20;
14
+ const known = new Map();
15
+ /** One fact about an archive, computed from its header once per size and modification time; `fallback` when unreadable. */
16
+ function fact(archive, key, compute, fallback) {
17
+ let fd;
18
+ try {
19
+ const stat = statSync(archive), stamp = `${stat.size}:${stat.mtimeMs}`;
20
+ const seen = known.get(archive)?.stamp === stamp ? known.get(archive) : { stamp };
21
+ if (seen[key] !== undefined)
22
+ return seen[key];
23
+ fd = openSync(archive, 'r');
24
+ const sizes = Buffer.alloc(16);
25
+ if (readSync(fd, sizes, 0, 16, 0) !== 16)
26
+ return fallback;
27
+ const headerSize = sizes.readUInt32LE(12);
28
+ if (headerSize < 1 || headerSize > 16 << 20)
29
+ return fallback;
30
+ const header = Buffer.alloc(headerSize);
31
+ if (readSync(fd, header, 0, headerSize, 16) !== headerSize)
32
+ return fallback;
33
+ const value = compute({ path: archive, fd, size: stat.size, base: 8 + sizes.readUInt32LE(4), header: JSON.parse(header.toString()) });
34
+ known.set(archive, { ...seen, [key]: value });
35
+ return value;
36
+ }
37
+ catch {
38
+ return fallback;
39
+ }
40
+ finally {
41
+ if (fd !== undefined)
42
+ closeSync(fd);
43
+ }
44
+ }
45
+ function entry(a, path) {
46
+ let e = a.header;
47
+ for (const part of path.split('/'))
48
+ e = e?.files?.[part];
49
+ return e && !e.files ? e : undefined;
50
+ }
51
+ /** One file's bytes, from the archive or its `.unpacked` folder; undefined when it is missing or out of bounds. */
52
+ function read(a, path) {
53
+ const e = entry(a, path), size = e?.size;
54
+ if (!e || !Number.isSafeInteger(size) || !size || size < 1 || size > MAX_FILE)
55
+ return undefined;
56
+ if (e.unpacked)
57
+ return readFileSync(join(`${a.path}.unpacked`, path));
58
+ const offset = Number(e.offset) + a.base;
59
+ if (!Number.isSafeInteger(offset) || offset < 16 || offset + size > a.size)
60
+ return undefined;
61
+ const bytes = Buffer.alloc(size);
62
+ return readSync(a.fd, bytes, 0, size, offset) === size ? bytes : undefined;
63
+ }
64
+ /** The app's release version (package.json `version`, e.g. `0.0.46-nightly.20261010.2908`), or null. */
65
+ export function asarVersion(archive) {
66
+ return fact(archive, 'version', a => {
67
+ const version = JSON.parse(read(a, 'package.json')?.toString() ?? '{}').version;
68
+ return typeof version === 'string' && RELEASE.test(version) ? version : null;
69
+ }, null) ?? null;
70
+ }
71
+ /**
72
+ * Whether the server registers the ACP Registry driver. A version number alone is insufficient (0.0.45 lacks it while
73
+ * upstream main had it). Builds put the driver in `bin.mjs` itself, or (nightlies since October 2026) in a chunk the
74
+ * thin `bin.mjs` entry imports (`binCli-<hash>.mjs`): the entry and the chunks it names are searched.
75
+ */
76
+ export function t3ArchiveSupportsAcp(archive) {
77
+ return fact(archive, 'acp', a => {
78
+ const has = (bytes) => !!bytes && DRIVER.some(marker => bytes.includes(marker));
79
+ const bin = read(a, T3_SERVER_ENTRY);
80
+ if (!bin)
81
+ return false;
82
+ if (has(bin))
83
+ return true;
84
+ // A whole server in bin.mjs (0.0.45: 9 MB) without the driver has none; only a thin entry defers to chunks.
85
+ if (bin.length > 256 << 10)
86
+ return false;
87
+ const dir = T3_SERVER_ENTRY.slice(0, T3_SERVER_ENTRY.lastIndexOf('/') + 1);
88
+ const chunks = [...bin.toString().matchAll(/(?:\bimport\s*\(\s*|\bfrom\s*)["']\.\/([\w.-]+\.m?js)["']/g)].map(m => m[1]);
89
+ return [...new Set(chunks)].slice(0, 16).some(chunk => has(read(a, dir + chunk)));
90
+ }, false) === true;
91
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * The CLI side of T3 Code mode (0.17.0+): `shard agents new --in t3 [--prompt…] [--on <ref>]`, `shard ./` and
3
+ * `--in auto` when T3 is where new conversations open, and `shard agents open t3 <conversation|workspace>`.
4
+ * --json results (contract shared with the menu bar):
5
+ * new: { target: "t3", mode, project, t3_thread_id, conversation_id, workspace: { workspace_key, id } | null,
6
+ * opened: true, focused: false, model, effort, ... }
7
+ * open: { opened: true, t3_thread_id, focused: false, ... }
8
+ * `focused` is false: T3 has no way to show a given thread from outside, so T3 is brought to the front instead.
9
+ */
10
+ import type { Workspace } from '@shardflux/sdk';
11
+ import type { Ctx } from './commands.js';
12
+ import type { Values } from './args.js';
13
+ import type { AgentApp, T3Install, T3ThreadRequest, T3ThreadResult } from './t3-mode.js';
14
+ /**
15
+ * A `t3_*` refusal raised once the workspace, conversation or thread exists names them (details.workspace_key,
16
+ * conversation_id, t3_thread_id), so a client opens what exists instead of starting it again.
17
+ */
18
+ export declare function withT3Context(error: unknown, ref: {
19
+ workspace_key?: string | null;
20
+ conversation_id?: string | null;
21
+ t3_thread_id?: string | null;
22
+ }): unknown;
23
+ /** The thread to start, when the command carries a first message (`--prompt`, `--prompt-file`, `--image`, `--model`, `--effort`). */
24
+ export declare function t3ThreadRequest(ctx: Ctx, values: Values, app: AgentApp): Promise<T3ThreadRequest | undefined>;
25
+ /**
26
+ * `agents new --in t3`: with a first message in ACP mode, the thread starts on T3's Shardflux provider (its own VM);
27
+ * otherwise the project VM is opened as T3's SSH environment (with its phone link), and a first message becomes a
28
+ * thread on the T3 server in that VM. `--on <ref>` uses that existing workspace instead of the project VM.
29
+ */
30
+ export declare function launchT3(ctx: Ctx, values: Values, path: string, explicitApp: string | undefined): Promise<number>;
31
+ /** The T3 fields of an SSH-mode result (the thread, when one was started in the VM). */
32
+ export declare function t3Fields(ws: Workspace, project: string, install: T3Install | undefined, started: T3ThreadResult | undefined, conversation?: string | null): Record<string, unknown>;
33
+ /**
34
+ * `agents open t3 <conversation>` for a conversation that runs on T3's Shardflux provider (ACP mode): T3 is brought to
35
+ * the front with that thread's id. A conversation of another route opens its VM as T3's SSH environment.
36
+ */
37
+ export declare function openT3Conversation(ctx: Ctx, conversationId: string, values?: Values): Promise<number>;
@@ -0,0 +1,133 @@
1
+ import { out, resolveWorkspace, str } from "./commands.js";
2
+ import { CodedError, NotFoundError, UsageError } from "./errors.js";
3
+ import { projectIdentity } from "./project-identity.js";
4
+ import { AgentStateStore } from "./agent-state.js";
5
+ import { readInitialImages, readInitialPrompt } from "./initial-prompt.js";
6
+ import { findAcpThread, openT3, startAcpThread, t3Install, t3NotInstalled, t3OpenFailed } from "./t3-mode.js";
7
+ import { agentReachability } from "./agent-reachability.js";
8
+ import { conversationWorkspace, getConversation } from "./conversations.js";
9
+ import { resolveOn } from "./thread-on-computer.js";
10
+ import { ensureCloudClaude } from "./claude-cloud-login.js";
11
+ import { modelChoice } from "./agent-models.js";
12
+ /**
13
+ * A `t3_*` refusal raised once the workspace, conversation or thread exists names them (details.workspace_key,
14
+ * conversation_id, t3_thread_id), so a client opens what exists instead of starting it again.
15
+ */
16
+ export function withT3Context(error, ref) {
17
+ if (!(error instanceof CodedError) || !error.code.startsWith('t3_'))
18
+ return error;
19
+ const details = { ...error.details, conversation_id: ref.conversation_id ?? error.details?.conversation_id ?? null, workspace_key: ref.workspace_key ?? error.details?.workspace_key ?? null,
20
+ t3_thread_id: ref.t3_thread_id ?? error.details?.t3_thread_id ?? null };
21
+ const next = new CodedError(error.code, error.message, error.hint, details);
22
+ next.stack = error.stack;
23
+ return next;
24
+ }
25
+ /** The thread to start, when the command carries a first message (`--prompt`, `--prompt-file`, `--image`, `--model`, `--effort`). */
26
+ export async function t3ThreadRequest(ctx, values, app) {
27
+ const prompt = await readInitialPrompt(ctx, values);
28
+ const images = await readInitialImages(ctx, values, prompt);
29
+ if (prompt === undefined) {
30
+ if (values.model !== undefined || values.effort !== undefined)
31
+ throw new UsageError('--model and --effort choose how the first message runs: add --prompt or --prompt-file.');
32
+ return undefined;
33
+ }
34
+ // The same choices as every other new thread (agent-models.ts); "default" leaves the agent's own.
35
+ const choice = modelChoice(values, app);
36
+ return { app, prompt, ...(choice.model ? { model: choice.model } : {}), ...(choice.effort ? { effort: choice.effort } : {}), ...(images.length ? { images } : {}) };
37
+ }
38
+ /**
39
+ * `agents new --in t3`: with a first message in ACP mode, the thread starts on T3's Shardflux provider (its own VM);
40
+ * otherwise the project VM is opened as T3's SSH environment (with its phone link), and a first message becomes a
41
+ * thread on the T3 server in that VM. `--on <ref>` uses that existing workspace instead of the project VM.
42
+ */
43
+ export async function launchT3(ctx, values, path, explicitApp) {
44
+ const install = t3Install(ctx.io.env);
45
+ if (!install)
46
+ throw t3NotInstalled();
47
+ if (explicitApp !== undefined && explicitApp !== 'claude' && explicitApp !== 'codex')
48
+ throw new UsageError('agent app must be claude or codex');
49
+ const project = projectIdentity(path);
50
+ const app = explicitApp ?? new AgentStateStore(ctx.store.dir).app(project.remote ?? project.folder);
51
+ const request = await t3ThreadRequest(ctx, values, app);
52
+ const on = str(values.on);
53
+ if (values.review === true)
54
+ throw new UsageError('--review starts a Claude reviewer in its terminal; leave out --in t3.');
55
+ if (on !== undefined) {
56
+ if (request && install.mode === 'acp') {
57
+ throw new CodedError('t3_on_not_supported', 'With the Shardflux provider in T3 Code, every thread runs in its own new VM, so it cannot start on an existing workspace.', 'Leave out --on to start the thread in its own VM, or open that workspace in T3 Code with shard agents open t3 <workspace>.', { workspace_key: on, conversation_id: null });
58
+ }
59
+ const { ws, conversation } = await onComputer(ctx, on, project.folder);
60
+ // The thread's agent runs in that VM with the account's logins, as `agents open t3` prepares them.
61
+ await ensureCloudClaude(ctx, ws);
62
+ const { openDesktopWorkspace } = await import("./agent-target-commands.js");
63
+ return openDesktopWorkspace(ctx, ws, 't3', install.version ?? undefined, false, request, install, conversation);
64
+ }
65
+ if (request && install.mode === 'acp')
66
+ return acpThreadOutput(ctx, install, project, request);
67
+ const { launchProjectWith } = await import("./agent-commands.js");
68
+ return launchProjectWith(ctx, { ...values, in: 't3' }, [path, app], request, install);
69
+ }
70
+ /**
71
+ * The computer `--on` names, as for any new thread (thread-on-computer.ts): a workspace key or id, else a conversation
72
+ * id, key or name (its VM). The T3 server in that VM runs the thread.
73
+ */
74
+ async function onComputer(ctx, ref, folder) {
75
+ if (!/^conv_[a-z0-9]{26}$/.test(ref)) {
76
+ try {
77
+ const ws = await resolveWorkspace(ctx, ref);
78
+ return { ws, conversation: ws.labels.shard_conversation || null };
79
+ }
80
+ catch (error) {
81
+ if (!(error instanceof NotFoundError))
82
+ throw error;
83
+ }
84
+ }
85
+ const target = await resolveOn(ctx, ref, folder);
86
+ return { ws: await conversationWorkspace(ctx, target), conversation: target.id };
87
+ }
88
+ async function acpThreadOutput(ctx, install, project, request) {
89
+ const started = await startAcpThread(ctx, install, project.folder, request);
90
+ new AgentStateStore(ctx.store.dir).saveTarget(project.remote ?? project.folder, 't3');
91
+ out(ctx, `Started in T3 Code: ${request.prompt.split('\n')[0].slice(0, 80)}${started.conversation_id ? ` (${started.conversation_id})` : ''}\n`, { target: 't3', mode: started.mode, project: project.slug, t3_thread_id: started.t3_thread_id, conversation_id: started.conversation_id, workspace: started.workspace,
92
+ opened: true, focused: false, model: started.model, effort: started.effort, ...(started.effort_reason ? { effort_reason: started.effort_reason } : {}), app_bundle_id: 'com.t3tools.t3code' });
93
+ return 0;
94
+ }
95
+ /** The T3 fields of an SSH-mode result (the thread, when one was started in the VM). */
96
+ export function t3Fields(ws, project, install, started, conversation) {
97
+ return { mode: install?.mode ?? 'ssh', project, t3_thread_id: started?.t3_thread_id ?? null, conversation_id: started?.conversation_id ?? conversation ?? (ws.labels.shard_conversation || null),
98
+ opened: true, focused: false, model: started?.model ?? null, effort: started?.effort ?? null, ...(started?.effort_reason ? { effort_reason: started.effort_reason } : {}) };
99
+ }
100
+ /**
101
+ * `agents open t3 <conversation>` for a conversation that runs on T3's Shardflux provider (ACP mode): T3 is brought to
102
+ * the front with that thread's id. A conversation of another route opens its VM as T3's SSH environment.
103
+ */
104
+ export async function openT3Conversation(ctx, conversationId, values = {}) {
105
+ const install = t3Install(ctx.io.env);
106
+ if (!install)
107
+ throw t3NotInstalled();
108
+ const conversation = await getConversation(ctx, conversationId);
109
+ if (install.mode === 'acp' && conversation.route === 'acp') {
110
+ let threadId;
111
+ try {
112
+ threadId = await findAcpThread(ctx, install, conversationId);
113
+ }
114
+ catch (error) {
115
+ throw withT3Context(error, { conversation_id: conversationId });
116
+ }
117
+ try {
118
+ await openT3(ctx.io.env, install);
119
+ }
120
+ catch (error) {
121
+ throw threadId ? t3OpenFailed(error, { t3_thread_id: threadId, conversation_id: conversationId }) : error;
122
+ }
123
+ out(ctx, threadId ? `Opened T3 Code (thread ${threadId}).\n` : 'Opened T3 Code.\n', { target: 't3', mode: 'acp', conversation_id: conversationId, t3_thread_id: threadId, opened: true, focused: false, app_bundle_id: 'com.t3tools.t3code' });
124
+ return 0;
125
+ }
126
+ // Its VM as T3's SSH environment, with the checks every `agents open t3` makes: --reachable and the cloud Claude login.
127
+ let ws = await conversationWorkspace(ctx, conversation);
128
+ if (values.reachable !== undefined)
129
+ ws = await ctx.cloud().workspaces.open({ key: ws.key, ...agentReachability(values), wait: false });
130
+ await ensureCloudClaude(ctx, ws);
131
+ const { openDesktopWorkspace } = await import("./agent-target-commands.js");
132
+ return openDesktopWorkspace(ctx, ws, 't3', install.version ?? undefined, false, undefined, install, conversationId);
133
+ }
@@ -0,0 +1,68 @@
1
+ type Env = Record<string, string | undefined>;
2
+ export type T3LinuxFormat = 'deb' | 'directory' | 'appimage' | 'flatpak' | 'snap';
3
+ export interface T3LinuxApp {
4
+ format: T3LinuxFormat;
5
+ /** The desktop entry's file name (the id `gtk-launch` takes); null for an AppImage found by its file name. */
6
+ desktop_id: string | null;
7
+ /** What starts it: the entry's Exec without field codes, or the AppImage. */
8
+ exec: string[];
9
+ /** The app's resources/app.asar on this computer (deb, other unpacked installs, flatpak); null for an AppImage or snap. */
10
+ asar: string | null;
11
+ /** Flatpak: the app id and the app's files folder on this computer (mounted at /app in its sandbox). */
12
+ flatpak?: {
13
+ id: string;
14
+ files: string;
15
+ };
16
+ }
17
+ /** A Linux T3 Code: `path` is the program it runs (the executable, or the AppImage). */
18
+ export interface T3LinuxInstall {
19
+ path: string;
20
+ version: string | null;
21
+ linux: T3LinuxApp;
22
+ }
23
+ /** The launcher T3 (nightlies since October 2026) writes to `<T3 home>/bin/t3` for its bundled CLI; for an AppImage it mounts the image itself. */
24
+ export declare const T3_LAUNCHER_MARKER = "Written by T3 Code: runs the desktop app's bundled t3 CLI.";
25
+ /**
26
+ * The folders scanned on Linux, in XDG precedence: the data home's and each data dir's `applications`, snapd's and
27
+ * flatpak's exported entries, then the usual AppImage folders. SHARDFLUX_APPLICATIONS_DIRS replaces all of them.
28
+ */
29
+ export declare function linuxApplicationDirs(env: Env): string[];
30
+ /** The `[Desktop Entry]` group's unlocalized keys (string escapes undone), first value of each key. */
31
+ export declare function desktopEntry(text: string): Record<string, string>;
32
+ /** An entry's Exec as argv (the Desktop Entry quoting rules), field codes and flatpak's @@ markers removed; undefined when malformed. */
33
+ export declare function desktopExec(value: string): string[] | undefined;
34
+ /** The installed T3 Code apps on Linux, from the desktop entries and AppImages in `dirs` (linuxApplicationDirs). */
35
+ export declare function linuxT3Apps(dirs: string[], env: Env): T3LinuxInstall[];
36
+ /**
37
+ * Whether this install runs the Shardflux provider (ACP mode): the server in its app.asar has the ACP Registry driver
38
+ * (deb, unpacked installs, flatpak). An AppImage is never mounted to look: its version decides (0.0.46+ have the
39
+ * driver), and T3's own launcher issues its sessions. A snap starts threads over SSH.
40
+ */
41
+ export declare function linuxSupportsAcp(app: T3LinuxInstall): boolean;
42
+ /**
43
+ * The bundled T3 server CLI (Electron as Node, ELECTRON_RUN_AS_NODE=1 set by the caller): the app's executable with
44
+ * the entry inside its app.asar; a flatpak's through `flatpak run`; an AppImage's through T3's own launcher
45
+ * (`<T3 home>/bin/t3`, which mounts the image). Undefined when there is none yet.
46
+ */
47
+ export declare function linuxServerCli(app: T3LinuxInstall, t3Home: string): {
48
+ command: string;
49
+ args: string[];
50
+ } | undefined;
51
+ /**
52
+ * How T3 is opened (or brought to the front, as a second launch of T3 hands over to the running one): `gtk-launch`
53
+ * with its desktop entry when there is one, else its own program, started detached.
54
+ */
55
+ export declare function linuxOpenCommands(app: T3LinuxInstall, env: Env): Array<{
56
+ command: string;
57
+ args: string[];
58
+ detached: boolean;
59
+ }>;
60
+ /** Opens T3 Code on Linux. Nothing here ever stops a T3 process: a launcher that hangs is left alone. */
61
+ export declare function openLinuxT3(env: Env, app: T3LinuxInstall): Promise<void>;
62
+ /**
63
+ * When T3 last started, as the filesystem records it: the program's access time once it is later than its modification
64
+ * time (dpkg installs both at the build time; relatime updates the access time on the first start, then daily). Null
65
+ * before the first start and on noatime mounts.
66
+ */
67
+ export declare function linuxLastUsed(path: string): string | null;
68
+ export {};