@gobius/t3ctl 0.3.0 → 0.5.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/README.md +39 -8
- package/dist/t3ctl.js +557 -0
- package/dist/t3ctl.js.map +1 -0
- package/package.json +15 -3
- package/t3ctl.mjs +0 -443
package/README.md
CHANGED
|
@@ -156,7 +156,7 @@ t3ctl project create t3ctl ~/Code/t3ctl
|
|
|
156
156
|
### `t3ctl thread create <project> <title>`
|
|
157
157
|
|
|
158
158
|
Create a thread. This produces an **idle thread with no messages** — it does not
|
|
159
|
-
start the agent. Use `thread
|
|
159
|
+
start the agent. Use `thread send` for that.
|
|
160
160
|
|
|
161
161
|
```sh
|
|
162
162
|
t3ctl thread create t3ctl "rewrite the readme for users" --branch docs/readme
|
|
@@ -176,13 +176,15 @@ Flags:
|
|
|
176
176
|
`--model` splits on the **first** slash, so slashed model names work as-is:
|
|
177
177
|
`--model opencode/github-copilot/gpt-5.4`.
|
|
178
178
|
|
|
179
|
-
### `t3ctl thread
|
|
179
|
+
### `t3ctl thread send <thread> <message...>`
|
|
180
180
|
|
|
181
|
-
Send a message and run the agent.
|
|
181
|
+
Send a message to a thread and run the agent. This is how you continue a
|
|
182
|
+
conversation — it is the same thing the app does for every message you type,
|
|
183
|
+
first or fiftieth. Aliased as `thread start`, the name it shipped under in 0.2.0. Everything after the thread reference is the
|
|
182
184
|
message — no quoting needed.
|
|
183
185
|
|
|
184
186
|
```sh
|
|
185
|
-
t3ctl thread
|
|
187
|
+
t3ctl thread send "rewrite the readme" move the endpoint tables into CONTRIBUTING.md
|
|
186
188
|
```
|
|
187
189
|
|
|
188
190
|
```
|
|
@@ -195,13 +197,42 @@ started rewrite the readme for users
|
|
|
195
197
|
|
|
196
198
|
Accepts `--model`, `--runtime-mode`, `--interaction-mode`, and `--host`. Unlike
|
|
197
199
|
`thread create`, `--model` has no default here: the thread's existing model is
|
|
198
|
-
reused unless you override it.
|
|
199
|
-
|
|
200
|
+
reused unless you override it.
|
|
201
|
+
|
|
202
|
+
### `t3ctl thread rename <thread> <new title...>`
|
|
203
|
+
|
|
204
|
+
Rename a thread. Useful when a thread was created from a script and ended up with
|
|
205
|
+
a title like a filename.
|
|
206
|
+
|
|
207
|
+
```sh
|
|
208
|
+
t3ctl thread rename "probe well-known" Verify hosts with the environment descriptor
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### `t3ctl thread retitle <thread> [--timeout <seconds>]`
|
|
212
|
+
|
|
213
|
+
Ask the server to derive a title from the thread's own content, instead of
|
|
214
|
+
supplying one.
|
|
200
215
|
|
|
201
216
|
```sh
|
|
202
|
-
t3ctl thread
|
|
217
|
+
t3ctl thread retitle "probe well-known"
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
This is a **request, not a guarantee**. The server records the intent and expects
|
|
221
|
+
something downstream to generate the title; in practice it sometimes clears the
|
|
222
|
+
request a few seconds later without producing one, and reports no error when it
|
|
223
|
+
does. So `retitle` watches the thread afterwards and tells you which happened:
|
|
224
|
+
|
|
225
|
+
```
|
|
226
|
+
asked the server to retitle Build t3ctl: a CLI for controlling T3 Code hosts (seq 677390)
|
|
227
|
+
no title was generated — the server cleared the request without producing one.
|
|
228
|
+
The title is still "Build t3ctl: a CLI for controlling T3 Code hosts". Set one directly:
|
|
229
|
+
t3ctl thread rename 861645a3-... <title...>
|
|
203
230
|
```
|
|
204
231
|
|
|
232
|
+
It exits non-zero when no title appears, so a script can tell. Waits 30s by
|
|
233
|
+
default; `--timeout` changes that. If you need the rename to be certain, use
|
|
234
|
+
`thread rename`, which sets the title directly.
|
|
235
|
+
|
|
205
236
|
### `t3ctl thread interrupt <thread>`
|
|
206
237
|
|
|
207
238
|
Stop the turn that's currently running.
|
|
@@ -249,7 +280,7 @@ Write commands act on one host. With a single host registered, that one is
|
|
|
249
280
|
implied. With more than one, pass `--host`:
|
|
250
281
|
|
|
251
282
|
```sh
|
|
252
|
-
t3ctl thread
|
|
283
|
+
t3ctl thread send --host desktop "api rate limits" pick this back up
|
|
253
284
|
```
|
|
254
285
|
|
|
255
286
|
Otherwise you get `multiple hosts; pass --host <laptop|desktop>`.
|
package/dist/t3ctl.js
ADDED
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// t3ctl — a controller CLI for T3 Code hosts.
|
|
3
|
+
// Peer of the mobile app: pairs once per host, then reads/controls remotely.
|
|
4
|
+
// Spike scope: host registry + read-only listing.
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import { Command } from 'commander';
|
|
7
|
+
import os from 'node:os';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
const CONFIG_DIR = path.join(os.homedir(), '.config', 't3ctl');
|
|
10
|
+
const HOSTS_FILE = path.join(CONFIG_DIR, 'hosts.json');
|
|
11
|
+
const readHosts = () => {
|
|
12
|
+
if (!fs.existsSync(HOSTS_FILE))
|
|
13
|
+
return [];
|
|
14
|
+
return JSON.parse(fs.readFileSync(HOSTS_FILE, 'utf8')).hosts ?? [];
|
|
15
|
+
};
|
|
16
|
+
const writeHosts = (hosts) => {
|
|
17
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
|
|
18
|
+
fs.writeFileSync(HOSTS_FILE, JSON.stringify({ hosts }, null, 2), { mode: 0o600 });
|
|
19
|
+
};
|
|
20
|
+
const snapshot = async (host) => {
|
|
21
|
+
const res = await fetch(`${host.origin}/api/orchestration/snapshot`, {
|
|
22
|
+
headers: host.token ? { authorization: `Bearer ${host.token}` } : {},
|
|
23
|
+
signal: AbortSignal.timeout(host.timeoutMs ?? 15000),
|
|
24
|
+
});
|
|
25
|
+
if (!res.ok)
|
|
26
|
+
throw new Error(`${host.name}: HTTP ${res.status} ${await res.text().catch(() => '')}`.trim());
|
|
27
|
+
return (await res.json());
|
|
28
|
+
};
|
|
29
|
+
// Derived status. Order matters: most urgent wins.
|
|
30
|
+
const threadStatus = (t) => {
|
|
31
|
+
if (t.deletedAt)
|
|
32
|
+
return 'deleted';
|
|
33
|
+
if (t.archivedAt)
|
|
34
|
+
return 'archived';
|
|
35
|
+
if (t.session?.activeTurnId || t.session?.status === 'running')
|
|
36
|
+
return 'running';
|
|
37
|
+
if (t.session?.status === 'error' || t.latestTurn?.state === 'error')
|
|
38
|
+
return 'error';
|
|
39
|
+
if (t.snoozedUntil && new Date(t.snoozedUntil) > new Date())
|
|
40
|
+
return 'snoozed';
|
|
41
|
+
if (t.proposedPlans?.length)
|
|
42
|
+
return 'needs-review';
|
|
43
|
+
if (t.settledAt && !t.unsettledAt)
|
|
44
|
+
return 'settled';
|
|
45
|
+
return 'idle';
|
|
46
|
+
};
|
|
47
|
+
const ICON = {
|
|
48
|
+
running: '\x1b[32m●\x1b[0m', error: '\x1b[31m✕\x1b[0m', 'needs-review': '\x1b[33m◆\x1b[0m',
|
|
49
|
+
snoozed: '\x1b[90m☾\x1b[0m', settled: '\x1b[90m✓\x1b[0m', idle: '\x1b[90m·\x1b[0m',
|
|
50
|
+
archived: '\x1b[90m▪\x1b[0m', deleted: '\x1b[90m✗\x1b[0m',
|
|
51
|
+
};
|
|
52
|
+
/** `catch` binds `unknown`; every call site wants the same string out of it. */
|
|
53
|
+
const errorMessage = (e) => (e instanceof Error ? e.message : String(e));
|
|
54
|
+
const dim = (s) => `\x1b[90m${s}\x1b[0m`;
|
|
55
|
+
// Usage errors are user errors: print to stderr and exit non-zero so scripts
|
|
56
|
+
// can tell them apart from success.
|
|
57
|
+
const usage = (message) => {
|
|
58
|
+
console.error(message);
|
|
59
|
+
process.exitCode = 1;
|
|
60
|
+
};
|
|
61
|
+
const bold = (s) => `\x1b[1m${s}\x1b[0m`;
|
|
62
|
+
const collect = async (hosts) => {
|
|
63
|
+
const results = await Promise.allSettled(hosts.map(async (h) => ({ host: h, snap: await snapshot(h) })));
|
|
64
|
+
const ok = [];
|
|
65
|
+
const failed = [];
|
|
66
|
+
results.forEach((r, i) => {
|
|
67
|
+
const host = hosts[i];
|
|
68
|
+
if (!host)
|
|
69
|
+
return;
|
|
70
|
+
if (r.status === 'fulfilled')
|
|
71
|
+
ok.push(r.value);
|
|
72
|
+
else
|
|
73
|
+
failed.push({ host, error: errorMessage(r.reason) });
|
|
74
|
+
});
|
|
75
|
+
return { ok, failed };
|
|
76
|
+
};
|
|
77
|
+
const cmdLs = async ({ threads: showThreads, all: showAll, json: asJson }) => {
|
|
78
|
+
const hosts = readHosts();
|
|
79
|
+
if (!hosts.length)
|
|
80
|
+
return console.error('No hosts registered. Run: t3ctl host add <origin> <token>');
|
|
81
|
+
const { ok, failed } = await collect(hosts);
|
|
82
|
+
if (asJson) {
|
|
83
|
+
const out = ok.flatMap(({ host, snap }) => snap.projects
|
|
84
|
+
.filter((p) => showAll || !p.deletedAt)
|
|
85
|
+
.map((p) => ({
|
|
86
|
+
host: host.name, id: p.id, title: p.title, workspaceRoot: p.workspaceRoot,
|
|
87
|
+
threads: snap.threads.filter((t) => t.projectId === p.id)
|
|
88
|
+
.filter((t) => showAll || (!t.deletedAt && !t.archivedAt))
|
|
89
|
+
.map((t) => ({ id: t.id, title: t.title, branch: t.branch, status: threadStatus(t), provider: t.session?.providerName ?? null, updatedAt: t.updatedAt })),
|
|
90
|
+
})));
|
|
91
|
+
console.log(JSON.stringify({ projects: out, unreachable: failed.map((f) => ({ host: f.host.name, error: f.error })) }, null, 2));
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
for (const { host, snap } of ok) {
|
|
95
|
+
console.log(`\n${bold(host.name)} ${dim(host.origin)}`);
|
|
96
|
+
const projects = snap.projects.filter((p) => showAll || !p.deletedAt)
|
|
97
|
+
.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
98
|
+
for (const p of projects) {
|
|
99
|
+
const threads = snap.threads.filter((t) => t.projectId === p.id)
|
|
100
|
+
.filter((t) => showAll || (!t.deletedAt && !t.archivedAt))
|
|
101
|
+
.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
102
|
+
if (!threads.length && !showAll)
|
|
103
|
+
continue;
|
|
104
|
+
const counts = {};
|
|
105
|
+
for (const t of threads)
|
|
106
|
+
counts[threadStatus(t)] = (counts[threadStatus(t)] ?? 0) + 1;
|
|
107
|
+
const badge = Object.entries(counts).map(([k, v]) => `${ICON[k] ?? '?'}${v}`).join(' ');
|
|
108
|
+
console.log(` ${bold(p.title)} ${badge} ${dim(p.workspaceRoot.replace(os.homedir(), '~'))}`);
|
|
109
|
+
if (!showThreads)
|
|
110
|
+
continue;
|
|
111
|
+
for (const t of threads) {
|
|
112
|
+
const s = threadStatus(t);
|
|
113
|
+
const meta = [t.branch, t.session?.providerName].filter(Boolean).join(' ');
|
|
114
|
+
console.log(` ${ICON[s] ?? '?'} ${(t.title || '(untitled)').slice(0, 62).padEnd(62)} ${dim(meta)}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
for (const f of failed)
|
|
119
|
+
console.error(`\n\x1b[31munreachable\x1b[0m ${f.host.name}: ${f.error}`);
|
|
120
|
+
};
|
|
121
|
+
// ---- host registry ------------------------------------------------------
|
|
122
|
+
// The descriptor at /.well-known/t3/environment is UNAUTHENTICATED, so probing
|
|
123
|
+
// it answers "is anyone home, and is it T3 Code?" without a token — a wrong
|
|
124
|
+
// origin fails here instead of as a baffling 401 on the first real call.
|
|
125
|
+
// Schema: ExecutionEnvironmentDescriptor in packages/contracts/src/environment.ts.
|
|
126
|
+
const DESCRIPTOR_PATH = '/.well-known/t3/environment';
|
|
127
|
+
const probe = async (origin, timeoutMs = 5000) => {
|
|
128
|
+
let res;
|
|
129
|
+
try {
|
|
130
|
+
res = await fetch(`${origin}${DESCRIPTOR_PATH}`, { signal: AbortSignal.timeout(timeoutMs) });
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
// Node's fetch reports a bare "fetch failed"; the cause carries the real reason.
|
|
134
|
+
const e = error;
|
|
135
|
+
const why = e.name === 'TimeoutError' ? `no response in ${timeoutMs}ms`
|
|
136
|
+
: (e.cause?.message ?? e.message ?? String(error));
|
|
137
|
+
throw new Error(`cannot reach ${origin}: ${why}`);
|
|
138
|
+
}
|
|
139
|
+
const notT3 = (why) => new Error(`not a T3 Code server (${DESCRIPTOR_PATH} ${why})`);
|
|
140
|
+
if (!res.ok)
|
|
141
|
+
throw notT3(`returned HTTP ${res.status}`);
|
|
142
|
+
let d;
|
|
143
|
+
try {
|
|
144
|
+
d = (await res.json());
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
throw notT3('is not JSON');
|
|
148
|
+
}
|
|
149
|
+
const required = ['environmentId', 'label', 'serverVersion'];
|
|
150
|
+
const missing = required.filter((k) => typeof d[k] !== 'string' || !d[k]);
|
|
151
|
+
if (missing.length)
|
|
152
|
+
throw notT3(`is missing ${missing.join(', ')}`);
|
|
153
|
+
return {
|
|
154
|
+
environmentId: d['environmentId'],
|
|
155
|
+
label: d['label'],
|
|
156
|
+
serverVersion: d['serverVersion'],
|
|
157
|
+
};
|
|
158
|
+
};
|
|
159
|
+
const shortId = (id) => (id ? id.slice(0, 8) : '-');
|
|
160
|
+
const warn = (message) => console.error(`\x1b[33mwarning\x1b[0m ${message}`);
|
|
161
|
+
// The name is what you type in --host, so derive a typeable slug from the label
|
|
162
|
+
// rather than using the label verbatim.
|
|
163
|
+
const slugify = (label) => label.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '') || 'host';
|
|
164
|
+
const uniqueName = (base, hosts) => {
|
|
165
|
+
if (!hosts.some((h) => h.name === base))
|
|
166
|
+
return base;
|
|
167
|
+
for (let n = 2;; n++)
|
|
168
|
+
if (!hosts.some((h) => h.name === `${base}-${n}`))
|
|
169
|
+
return `${base}-${n}`;
|
|
170
|
+
};
|
|
171
|
+
const isOrigin = (value) => /^https?:\/\//i.test(value ?? '');
|
|
172
|
+
const parseHostAdd = (pos) => {
|
|
173
|
+
const [origin, token] = pos;
|
|
174
|
+
return origin && isOrigin(origin) ? { origin, token: token ?? null } : null;
|
|
175
|
+
};
|
|
176
|
+
const HOST_ADD_USAGE = 'usage: t3ctl host add <origin> [token] [--name <name>]\n' +
|
|
177
|
+
' an origin needs a scheme, e.g. http://localhost:3773';
|
|
178
|
+
const cmdHostAdd = async (pos, flags, hosts) => {
|
|
179
|
+
// Validate before any network or registry work so a bare `host add` prints
|
|
180
|
+
// usage instead of stalling on a probe.
|
|
181
|
+
const parsed = parseHostAdd(pos);
|
|
182
|
+
if (!parsed)
|
|
183
|
+
return usage(HOST_ADD_USAGE);
|
|
184
|
+
const origin = parsed.origin.replace(/\/$/, '');
|
|
185
|
+
const descriptor = await probe(origin);
|
|
186
|
+
const existing = hosts.find((h) => h.origin === origin);
|
|
187
|
+
// A changed environmentId on a known origin means the origin now points at a
|
|
188
|
+
// different machine — the stored token almost certainly belongs to the old one.
|
|
189
|
+
if (existing?.environmentId && existing.environmentId !== descriptor.environmentId) {
|
|
190
|
+
warn(`${origin} is now a DIFFERENT environment\n` +
|
|
191
|
+
` was ${existing.environmentId} (${existing.label ?? 'unknown'})\n` +
|
|
192
|
+
` now ${descriptor.environmentId} (${descriptor.label})\n` +
|
|
193
|
+
` the token stored for "${existing.name}" was issued by the old one and will likely fail`);
|
|
194
|
+
}
|
|
195
|
+
const name = flags.name ?? existing?.name ??
|
|
196
|
+
uniqueName(slugify(descriptor.label), hosts);
|
|
197
|
+
const token = parsed.token ?? existing?.token ?? null;
|
|
198
|
+
const others = hosts.filter((h) => h.name !== name && h.origin !== origin && h.serverVersion);
|
|
199
|
+
const skewed = [...new Set(others.flatMap((h) => (h.serverVersion ? [h.serverVersion] : [])))]
|
|
200
|
+
.filter((v) => v !== descriptor.serverVersion);
|
|
201
|
+
if (skewed.length)
|
|
202
|
+
warn(`serverVersion ${descriptor.serverVersion} differs from other hosts: ${skewed.join(', ')}`);
|
|
203
|
+
writeHosts(hosts.filter((h) => h.name !== name && h.origin !== origin).concat({
|
|
204
|
+
name, origin, token,
|
|
205
|
+
environmentId: descriptor.environmentId,
|
|
206
|
+
label: descriptor.label,
|
|
207
|
+
serverVersion: descriptor.serverVersion,
|
|
208
|
+
}));
|
|
209
|
+
console.log(`added ${bold(name)} -> ${origin}\n label ${descriptor.label}\n` +
|
|
210
|
+
` env ${descriptor.environmentId}\n version ${descriptor.serverVersion}`);
|
|
211
|
+
if (!token)
|
|
212
|
+
warn(`no token stored for ${name} — reads will fail until you run: t3ctl host add ${origin} <token>`);
|
|
213
|
+
};
|
|
214
|
+
const cmdHostsList = async (hosts) => {
|
|
215
|
+
if (!hosts.length)
|
|
216
|
+
return console.log('(no hosts)');
|
|
217
|
+
const probes = await Promise.allSettled(hosts.map((h) => probe(h.origin)));
|
|
218
|
+
const drifted = [];
|
|
219
|
+
hosts.forEach((h, i) => {
|
|
220
|
+
const p = probes[i];
|
|
221
|
+
const live = p?.status === 'fulfilled' ? p.value : null;
|
|
222
|
+
if (live && h.environmentId && h.environmentId !== live.environmentId)
|
|
223
|
+
drifted.push({ h, live });
|
|
224
|
+
const icon = live ? ICON.running : ICON.error;
|
|
225
|
+
const label = live?.label ?? h.label ?? '-';
|
|
226
|
+
const env = shortId(live?.environmentId ?? h.environmentId);
|
|
227
|
+
const version = live?.serverVersion ?? h.serverVersion ?? '-';
|
|
228
|
+
// Values for an unreachable host are whatever was last stored, so dim the
|
|
229
|
+
// whole row to keep remembered data visually distinct from probed data.
|
|
230
|
+
const cell = (text, width) => (live ? text.padEnd(width) : dim(text.padEnd(width)));
|
|
231
|
+
console.log(`${icon} ${live ? bold(h.name.padEnd(14)) : dim(h.name.padEnd(14))} ${cell(label, 18)} ${dim(env.padEnd(9))} ${cell(version, 28)} ${dim(h.origin)}` +
|
|
232
|
+
(live || !p || p.status !== 'rejected' ? '' : ` ${dim(errorMessage(p.reason))}`));
|
|
233
|
+
});
|
|
234
|
+
for (const { h, live } of drifted) {
|
|
235
|
+
warn(`${h.name} (${h.origin}) is now a DIFFERENT environment\n` +
|
|
236
|
+
` was ${h.environmentId} (${h.label ?? 'unknown'})\n now ${live.environmentId} (${live.label})`);
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
// ---- writes -------------------------------------------------------------
|
|
240
|
+
// Commands are dispatched directly (no envelope). The CLIENT mints every id;
|
|
241
|
+
// commandId is the idempotency key, so retries are safe.
|
|
242
|
+
// Schemas: packages/contracts/src/orchestration.ts in pingdotgg/t3code.
|
|
243
|
+
const pickHost = (flags) => {
|
|
244
|
+
const hosts = readHosts();
|
|
245
|
+
if (flags.host) {
|
|
246
|
+
const h = hosts.find((x) => x.name === flags.host);
|
|
247
|
+
if (!h)
|
|
248
|
+
throw new Error(`no such host: ${flags.host}`);
|
|
249
|
+
return h;
|
|
250
|
+
}
|
|
251
|
+
if (!hosts.length)
|
|
252
|
+
throw new Error('no hosts registered — run: t3ctl host add <origin> <token>');
|
|
253
|
+
if (hosts.length > 1)
|
|
254
|
+
throw new Error(`multiple hosts; pass --host <${hosts.map((h) => h.name).join('|')}>`);
|
|
255
|
+
const only = hosts[0];
|
|
256
|
+
if (!only)
|
|
257
|
+
throw new Error('no hosts registered — run: t3ctl host add <origin> <token>');
|
|
258
|
+
return only;
|
|
259
|
+
};
|
|
260
|
+
const dispatch = async (host, command) => {
|
|
261
|
+
const res = await fetch(`${host.origin}/api/orchestration/dispatch`, {
|
|
262
|
+
method: 'POST',
|
|
263
|
+
headers: { ...(host.token ? { authorization: `Bearer ${host.token}` } : {}), 'content-type': 'application/json' },
|
|
264
|
+
body: JSON.stringify(command),
|
|
265
|
+
signal: AbortSignal.timeout(host.timeoutMs ?? 15000),
|
|
266
|
+
});
|
|
267
|
+
const body = await res.text();
|
|
268
|
+
if (!res.ok)
|
|
269
|
+
throw new Error(`${command.type} failed: HTTP ${res.status} ${body}`);
|
|
270
|
+
return body ? JSON.parse(body) : { sequence: 0 };
|
|
271
|
+
};
|
|
272
|
+
const resolveProject = (snap, ref) => snap.projects.find((p) => p.id === ref) ??
|
|
273
|
+
snap.projects.find((p) => !p.deletedAt && p.title === ref) ??
|
|
274
|
+
snap.projects.find((p) => !p.deletedAt && p.workspaceRoot === path.resolve(ref.replace(/^~/, os.homedir())));
|
|
275
|
+
// Commands whose entire payload is {commandId, threadId}. Verified against
|
|
276
|
+
// packages/contracts/src/orchestration.ts — note `unsettle` is NOT one of
|
|
277
|
+
// these (it carries extra fields), so it is deliberately absent.
|
|
278
|
+
const SIMPLE_THREAD_COMMANDS = ['settle', 'archive', 'unarchive', 'unpin', 'delete'];
|
|
279
|
+
const resolveThread = (snap, ref) => {
|
|
280
|
+
const live = snap.threads.filter((t) => !t.deletedAt);
|
|
281
|
+
const byId = live.find((t) => t.id === ref);
|
|
282
|
+
if (byId)
|
|
283
|
+
return byId;
|
|
284
|
+
const exact = live.filter((t) => t.title === ref);
|
|
285
|
+
if (exact[0] && exact.length === 1)
|
|
286
|
+
return exact[0];
|
|
287
|
+
const fuzzy = live.filter((t) => (t.title ?? '').toLowerCase().includes(ref.toLowerCase()));
|
|
288
|
+
if (fuzzy[0] && fuzzy.length === 1)
|
|
289
|
+
return fuzzy[0];
|
|
290
|
+
if (fuzzy.length > 1) {
|
|
291
|
+
throw new Error(`"${ref}" matches ${fuzzy.length} threads:\n` +
|
|
292
|
+
fuzzy.slice(0, 8).map((t) => ` ${t.id} ${t.title}`).join('\n'));
|
|
293
|
+
}
|
|
294
|
+
throw new Error(`no thread matching "${ref}"`);
|
|
295
|
+
};
|
|
296
|
+
// thread.turn.start is ONE command carrying the first message inline — this is
|
|
297
|
+
// what the UI fires immediately after thread.create, which is why a thread with
|
|
298
|
+
// no messages is a state the UI never produces. The client-side schema requires
|
|
299
|
+
// runtimeMode/interactionMode explicitly (the server-side one defaults them).
|
|
300
|
+
const cmdThreadStart = async (thread, host, text, flags) => {
|
|
301
|
+
const command = {
|
|
302
|
+
type: 'thread.turn.start',
|
|
303
|
+
commandId: crypto.randomUUID(),
|
|
304
|
+
threadId: thread.id,
|
|
305
|
+
message: { messageId: crypto.randomUUID(), role: 'user', text, attachments: [] },
|
|
306
|
+
runtimeMode: flags['runtime-mode'] ?? thread.runtimeMode ?? 'full-access',
|
|
307
|
+
interactionMode: flags['interaction-mode'] ?? 'default',
|
|
308
|
+
createdAt: new Date().toISOString(),
|
|
309
|
+
};
|
|
310
|
+
if (flags.model) {
|
|
311
|
+
const slash = flags.model.indexOf('/');
|
|
312
|
+
if (slash < 1)
|
|
313
|
+
throw new Error(`--model must be <instance>/<model>, got "${flags.model}"`);
|
|
314
|
+
command.modelSelection = { instanceId: flags.model.slice(0, slash), model: flags.model.slice(slash + 1) };
|
|
315
|
+
}
|
|
316
|
+
else if (thread.modelSelection) {
|
|
317
|
+
command.modelSelection = thread.modelSelection;
|
|
318
|
+
}
|
|
319
|
+
const { sequence } = await dispatch(host, command);
|
|
320
|
+
const m = command['modelSelection'];
|
|
321
|
+
console.log(`started ${bold(thread.title || thread.id)}\n id ${thread.id}` +
|
|
322
|
+
(m ? `\n model ${m.instanceId}/${m.model}` : '') +
|
|
323
|
+
`\n mode ${command.runtimeMode} / ${command.interactionMode}\n seq ${sequence}`);
|
|
324
|
+
};
|
|
325
|
+
const cmdThreadInterrupt = async (thread, host) => {
|
|
326
|
+
const { sequence } = await dispatch(host, {
|
|
327
|
+
type: 'thread.turn.interrupt', commandId: crypto.randomUUID(), threadId: thread.id,
|
|
328
|
+
});
|
|
329
|
+
console.log(`interrupted ${bold(thread.title || thread.id)}\n seq ${sequence}`);
|
|
330
|
+
};
|
|
331
|
+
// thread.meta.update also accepts regenerateTitle:true, which asks the server to
|
|
332
|
+
// derive a title from the thread's own content instead of taking one from us.
|
|
333
|
+
const cmdThreadRename = async (thread, host, title) => {
|
|
334
|
+
const { sequence } = await dispatch(host, {
|
|
335
|
+
type: 'thread.meta.update', commandId: crypto.randomUUID(), threadId: thread.id, title,
|
|
336
|
+
});
|
|
337
|
+
console.log(`renamed ${dim(thread.title || thread.id)} -> ${bold(title)}\n seq ${sequence}`);
|
|
338
|
+
};
|
|
339
|
+
// Lighter than the full snapshot; retitle polls this so it does not refetch every
|
|
340
|
+
// thread's history once a second.
|
|
341
|
+
const threadDetail = async (host, threadId) => {
|
|
342
|
+
const res = await fetch(`${host.origin}/api/orchestration/threads/${threadId}?turnLimit=1`, {
|
|
343
|
+
headers: { authorization: `Bearer ${host.token}` },
|
|
344
|
+
signal: AbortSignal.timeout(host.timeoutMs ?? 15000),
|
|
345
|
+
});
|
|
346
|
+
if (!res.ok)
|
|
347
|
+
throw new Error(`${host.name}: HTTP ${res.status}`);
|
|
348
|
+
return (await res.json()).thread;
|
|
349
|
+
};
|
|
350
|
+
// `regenerateTitle` does NOT rename anything by itself. The server records an
|
|
351
|
+
// intent marker (`titleRegeneration: {requestId, startedAt}`) and expects something
|
|
352
|
+
// downstream to generate a title and write it back. Observed on a live server: the
|
|
353
|
+
// marker is sometimes cleared a few seconds later with the title untouched, and no
|
|
354
|
+
// error is reported anywhere — not in the event, the command receipt, or the server
|
|
355
|
+
// trace. So we dispatch, then watch, and say what actually happened.
|
|
356
|
+
const cmdThreadRetitle = async (thread, host, timeoutSeconds) => {
|
|
357
|
+
const before = thread.title;
|
|
358
|
+
const { sequence } = await dispatch(host, {
|
|
359
|
+
type: 'thread.meta.update', commandId: crypto.randomUUID(), threadId: thread.id,
|
|
360
|
+
regenerateTitle: true,
|
|
361
|
+
});
|
|
362
|
+
console.log(`asked the server to retitle ${bold(before || thread.id)}${dim(` (seq ${sequence})`)}`);
|
|
363
|
+
const deadline = Date.now() + timeoutSeconds * 1000;
|
|
364
|
+
let sawMarker = false;
|
|
365
|
+
while (Date.now() < deadline) {
|
|
366
|
+
await new Promise((r) => setTimeout(r, 1500));
|
|
367
|
+
const current = await threadDetail(host, thread.id);
|
|
368
|
+
if (current.title && current.title !== before) {
|
|
369
|
+
console.log(`retitled -> ${bold(current.title)}`);
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
if (current.titleRegeneration) {
|
|
373
|
+
sawMarker = true;
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
if (sawMarker)
|
|
377
|
+
break; // marker appeared and was cleared, title unchanged
|
|
378
|
+
}
|
|
379
|
+
console.error(`\x1b[33mno title was generated\x1b[0m — the server ${sawMarker ? 'cleared the request without producing one' : `did not act on it within ${timeoutSeconds}s`}.\n` +
|
|
380
|
+
` The title is still "${before}". Set one directly:\n` +
|
|
381
|
+
` t3ctl thread rename ${thread.id} <title...>`);
|
|
382
|
+
process.exitCode = 1;
|
|
383
|
+
};
|
|
384
|
+
const cmdProjectCreate = async (title, root, flags) => {
|
|
385
|
+
const host = pickHost(flags);
|
|
386
|
+
const workspaceRoot = path.resolve(root.replace(/^~/, os.homedir()));
|
|
387
|
+
if (!fs.existsSync(workspaceRoot))
|
|
388
|
+
throw new Error(`workspace root does not exist: ${workspaceRoot}`);
|
|
389
|
+
const projectId = crypto.randomUUID();
|
|
390
|
+
const { sequence } = await dispatch(host, {
|
|
391
|
+
type: 'project.create', commandId: crypto.randomUUID(),
|
|
392
|
+
projectId, title, workspaceRoot, createdAt: new Date().toISOString(),
|
|
393
|
+
});
|
|
394
|
+
console.log(`created project ${bold(title)} on ${host.name}\n id ${projectId}\n root ${workspaceRoot}\n seq ${sequence}`);
|
|
395
|
+
};
|
|
396
|
+
const cmdThreadCreate = async (projectRef, title, flags) => {
|
|
397
|
+
const host = pickHost(flags);
|
|
398
|
+
const project = resolveProject(await snapshot(host), projectRef);
|
|
399
|
+
if (!project)
|
|
400
|
+
throw new Error(`no project matching "${projectRef}" on ${host.name}`);
|
|
401
|
+
// instanceId is the segment before the FIRST slash; the model keeps the rest,
|
|
402
|
+
// because opencode model ids are themselves slashed ("github-copilot/gpt-5.4").
|
|
403
|
+
const raw = flags.model ?? 'claudeAgent/claude-opus-5';
|
|
404
|
+
const slash = raw.indexOf('/');
|
|
405
|
+
if (slash < 1)
|
|
406
|
+
throw new Error(`--model must be <instance>/<model>, got "${raw}"`);
|
|
407
|
+
const modelSelection = { instanceId: raw.slice(0, slash), model: raw.slice(slash + 1) };
|
|
408
|
+
const threadId = crypto.randomUUID();
|
|
409
|
+
const { sequence } = await dispatch(host, {
|
|
410
|
+
type: 'thread.create', commandId: crypto.randomUUID(),
|
|
411
|
+
threadId, projectId: project.id, title, modelSelection,
|
|
412
|
+
runtimeMode: flags['runtime-mode'] ?? 'full-access',
|
|
413
|
+
interactionMode: flags['interaction-mode'] ?? 'default',
|
|
414
|
+
branch: flags.branch ?? null,
|
|
415
|
+
worktreePath: flags.worktree ?? null,
|
|
416
|
+
createdAt: new Date().toISOString(),
|
|
417
|
+
});
|
|
418
|
+
console.log(`created thread ${bold(title)} in ${project.title} on ${host.name}\n id ${threadId}\n model ${modelSelection.instanceId}/${modelSelection.model}\n seq ${sequence}`);
|
|
419
|
+
};
|
|
420
|
+
const cmdThreadSimple = async (verb, ref, flags) => {
|
|
421
|
+
const host = pickHost(flags);
|
|
422
|
+
const thread = resolveThread(await snapshot(host), ref);
|
|
423
|
+
const { sequence } = await dispatch(host, {
|
|
424
|
+
type: `thread.${verb}`, commandId: crypto.randomUUID(), threadId: thread.id,
|
|
425
|
+
});
|
|
426
|
+
console.log(`${verb}d ${bold(thread.title || thread.id)}\n id ${thread.id}\n seq ${sequence}`);
|
|
427
|
+
};
|
|
428
|
+
// ---- cli ----------------------------------------------------------------
|
|
429
|
+
// commander's option names arrive camelCased; the command implementations were
|
|
430
|
+
// written against the kebab-case spellings, so translate once here rather than
|
|
431
|
+
// touching every call site.
|
|
432
|
+
const FLAG_NAMES = {
|
|
433
|
+
host: 'host', model: 'model', branch: 'branch', worktree: 'worktree',
|
|
434
|
+
name: 'name', timeout: 'timeout',
|
|
435
|
+
runtimeMode: 'runtime-mode', interactionMode: 'interaction-mode',
|
|
436
|
+
};
|
|
437
|
+
// Only carry options that were actually supplied. Emitting every key
|
|
438
|
+
// unconditionally made `'name' in flags` always true, which made host add bail
|
|
439
|
+
// on every invocation.
|
|
440
|
+
const toFlags = (o) => Object.fromEntries(Object.entries(FLAG_NAMES)
|
|
441
|
+
.filter(([from]) => o[from] !== undefined)
|
|
442
|
+
.map(([from, to]) => [to, o[from]]));
|
|
443
|
+
const resolve = async (ref, o) => {
|
|
444
|
+
const host = pickHost(toFlags(o));
|
|
445
|
+
return { host, thread: resolveThread(await snapshot(host), ref) };
|
|
446
|
+
};
|
|
447
|
+
// Compiled to dist/t3ctl.js, so package.json is one level up — true in the repo
|
|
448
|
+
// and in the published tarball, which ships dist/ alongside package.json.
|
|
449
|
+
const pkg = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
|
|
450
|
+
const program = new Command();
|
|
451
|
+
program
|
|
452
|
+
.name('t3ctl')
|
|
453
|
+
.description('Control T3 Code hosts — list and drive coding-agent threads across every machine you run T3 Code on.')
|
|
454
|
+
.version(pkg.version, '-v, --version', 'print the t3ctl version')
|
|
455
|
+
.showHelpAfterError('(run `t3ctl --help` or `t3ctl <command> --help`)')
|
|
456
|
+
.configureHelp({ showGlobalOptions: true });
|
|
457
|
+
const hostOption = (cmd) => cmd.option('--host <name>', 'which registered host to talk to (required when several are registered)');
|
|
458
|
+
program.command('ls')
|
|
459
|
+
.description('list projects and threads across all registered hosts')
|
|
460
|
+
.option('-t, --threads', 'expand the threads under each project')
|
|
461
|
+
.option('-a, --all', 'include archived and deleted items')
|
|
462
|
+
.option('--json', 'emit JSON instead of a table')
|
|
463
|
+
.action((o) => cmdLs(o));
|
|
464
|
+
const host = program.command('host').description('manage the host registry');
|
|
465
|
+
host.command('add')
|
|
466
|
+
.argument('<origin>', 'base URL of the host, e.g. https://box.tailnet.ts.net:3773')
|
|
467
|
+
.argument('[token]', 'bearer token from `t3 auth session issue` on that host')
|
|
468
|
+
.description('register a host, probing /.well-known/t3/environment first')
|
|
469
|
+
.option('--name <name>', 'override the label detected from the host')
|
|
470
|
+
.action((origin, token, o) => cmdHostAdd([origin, token].filter(Boolean), toFlags(o), readHosts()));
|
|
471
|
+
host.command('rm')
|
|
472
|
+
.argument('<name>', 'registered host name')
|
|
473
|
+
.description('remove a host from the registry')
|
|
474
|
+
.action((name) => {
|
|
475
|
+
const hosts = readHosts();
|
|
476
|
+
if (!hosts.some((h) => h.name === name))
|
|
477
|
+
throw new Error(`no such host: ${name}`);
|
|
478
|
+
writeHosts(hosts.filter((h) => h.name !== name));
|
|
479
|
+
console.log(`removed ${name}`);
|
|
480
|
+
});
|
|
481
|
+
host.command('ls').description('list registered hosts and probe each one')
|
|
482
|
+
.action(() => cmdHostsList(readHosts()));
|
|
483
|
+
program.command('hosts').description('list registered hosts and probe each one (alias of `host ls`)')
|
|
484
|
+
.action(() => cmdHostsList(readHosts()));
|
|
485
|
+
const project = program.command('project').description('manage projects');
|
|
486
|
+
hostOption(project.command('create')
|
|
487
|
+
.argument('<title>', 'name for the project as it appears in T3 Code')
|
|
488
|
+
.argument('<workspace-root>', 'existing directory the project maps to')
|
|
489
|
+
.description('create a project for an existing directory'))
|
|
490
|
+
.action((title, root, o) => cmdProjectCreate(title, root, toFlags(o)));
|
|
491
|
+
const thread = program.command('thread').description('create and drive threads');
|
|
492
|
+
hostOption(thread.command('create')
|
|
493
|
+
.argument('<project>', 'project id, title, or workspace root')
|
|
494
|
+
.argument('<title...>', 'thread title; everything after the project is used')
|
|
495
|
+
.description('create a thread (idle — use `thread send` to run it)')
|
|
496
|
+
.option('--model <instance/model>', 'e.g. claudeAgent/claude-opus-5', 'claudeAgent/claude-opus-5')
|
|
497
|
+
.option('--branch <branch>', 'git branch to associate with the thread')
|
|
498
|
+
.option('--worktree <path>', 'git worktree the thread should run in')
|
|
499
|
+
.option('--runtime-mode <mode>', 'approval-required | auto-accept-edits | auto | full-access', 'full-access')
|
|
500
|
+
.option('--interaction-mode <mode>', 'default | plan', 'default'))
|
|
501
|
+
.action((ref, title, o) => cmdThreadCreate(ref, title.join(' '), toFlags(o)));
|
|
502
|
+
hostOption(thread.command('send')
|
|
503
|
+
.alias('start')
|
|
504
|
+
.argument('<thread>', 'thread id, exact title, or unique substring')
|
|
505
|
+
.argument('<message...>', 'the message; everything after the thread is sent')
|
|
506
|
+
.description('send a message to a thread and run the agent')
|
|
507
|
+
.option('--model <instance/model>', "override the thread's model for this turn")
|
|
508
|
+
.option('--runtime-mode <mode>', 'approval-required | auto-accept-edits | auto | full-access')
|
|
509
|
+
.option('--interaction-mode <mode>', 'default | plan'))
|
|
510
|
+
.action(async (ref, message, o) => {
|
|
511
|
+
const { host: h, thread: t } = await resolve(ref, o);
|
|
512
|
+
return cmdThreadStart(t, h, message.join(' '), toFlags(o));
|
|
513
|
+
});
|
|
514
|
+
hostOption(thread.command('rename')
|
|
515
|
+
.argument('<thread>', 'thread id, exact title, or unique substring')
|
|
516
|
+
.argument('<title...>', 'the new title')
|
|
517
|
+
.description('set a thread title directly'))
|
|
518
|
+
.action(async (ref, title, o) => {
|
|
519
|
+
const { host: h, thread: t } = await resolve(ref, o);
|
|
520
|
+
return cmdThreadRename(t, h, title.join(' '));
|
|
521
|
+
});
|
|
522
|
+
hostOption(thread.command('retitle')
|
|
523
|
+
.argument('<thread>', 'thread id, exact title, or unique substring')
|
|
524
|
+
.description('ask the server to derive a title; warns if it produces none')
|
|
525
|
+
.option('--timeout <seconds>', 'how long to wait for a title', '30'))
|
|
526
|
+
.action(async (ref, o) => {
|
|
527
|
+
const { host: h, thread: t } = await resolve(ref, o);
|
|
528
|
+
return cmdThreadRetitle(t, h, Number(o.timeout) > 0 ? Number(o.timeout) : 30);
|
|
529
|
+
});
|
|
530
|
+
hostOption(thread.command('interrupt')
|
|
531
|
+
.argument('<thread>', 'thread id, exact title, or unique substring')
|
|
532
|
+
.description('stop the turn currently running in a thread'))
|
|
533
|
+
.action(async (ref, o) => {
|
|
534
|
+
const { host: h, thread: t } = await resolve(ref, o);
|
|
535
|
+
return cmdThreadInterrupt(t, h);
|
|
536
|
+
});
|
|
537
|
+
const VERB_HELP = {
|
|
538
|
+
settle: 'mark a thread done so it drops out of the active list',
|
|
539
|
+
archive: 'hide a thread from the default listing (reversible)',
|
|
540
|
+
unarchive: 'bring an archived thread back into the default listing',
|
|
541
|
+
unpin: 'remove a thread from the pinned section',
|
|
542
|
+
delete: 'delete a thread',
|
|
543
|
+
};
|
|
544
|
+
for (const verb of SIMPLE_THREAD_COMMANDS) {
|
|
545
|
+
hostOption(thread.command(verb)
|
|
546
|
+
.argument('<thread>', 'thread id, exact title, or unique substring')
|
|
547
|
+
.description(VERB_HELP[verb] ?? `${verb} a thread`))
|
|
548
|
+
.action((ref, o) => cmdThreadSimple(verb, ref, toFlags(o)));
|
|
549
|
+
}
|
|
550
|
+
try {
|
|
551
|
+
await program.parseAsync(process.argv);
|
|
552
|
+
}
|
|
553
|
+
catch (error) {
|
|
554
|
+
console.error(`\x1b[31merror\x1b[0m ${error instanceof Error ? error.message : String(error)}`);
|
|
555
|
+
process.exit(1);
|
|
556
|
+
}
|
|
557
|
+
//# sourceMappingURL=t3ctl.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"t3ctl.js","sourceRoot":"","sources":["../src/t3ctl.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gobius/t3ctl",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Controller CLI for T3 Code hosts — list and drive threads across machines",
|
|
6
6
|
"keywords": [
|
|
@@ -21,10 +21,10 @@
|
|
|
21
21
|
"url": "https://github.com/Goobles/t3ctl/issues"
|
|
22
22
|
},
|
|
23
23
|
"bin": {
|
|
24
|
-
"t3ctl": "t3ctl.
|
|
24
|
+
"t3ctl": "./dist/t3ctl.js"
|
|
25
25
|
},
|
|
26
26
|
"files": [
|
|
27
|
-
"
|
|
27
|
+
"dist",
|
|
28
28
|
"README.md",
|
|
29
29
|
"LICENSE"
|
|
30
30
|
],
|
|
@@ -33,5 +33,17 @@
|
|
|
33
33
|
},
|
|
34
34
|
"publishConfig": {
|
|
35
35
|
"access": "public"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"commander": "^15.0.0"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@types/node": "^26.4.1",
|
|
42
|
+
"typescript": "^7.0.2"
|
|
43
|
+
},
|
|
44
|
+
"scripts": {
|
|
45
|
+
"build": "tsc",
|
|
46
|
+
"typecheck": "tsc --noEmit",
|
|
47
|
+
"test": "node --test test/*.test.mjs"
|
|
36
48
|
}
|
|
37
49
|
}
|
package/t3ctl.mjs
DELETED
|
@@ -1,443 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// t3ctl — a controller CLI for T3 Code hosts.
|
|
3
|
-
// Peer of the mobile app: pairs once per host, then reads/controls remotely.
|
|
4
|
-
// Spike scope: host registry + read-only listing.
|
|
5
|
-
|
|
6
|
-
import fs from 'node:fs';
|
|
7
|
-
import os from 'node:os';
|
|
8
|
-
import path from 'node:path';
|
|
9
|
-
|
|
10
|
-
const CONFIG_DIR = path.join(os.homedir(), '.config', 't3ctl');
|
|
11
|
-
const HOSTS_FILE = path.join(CONFIG_DIR, 'hosts.json');
|
|
12
|
-
|
|
13
|
-
const readHosts = () => {
|
|
14
|
-
if (!fs.existsSync(HOSTS_FILE)) return [];
|
|
15
|
-
return JSON.parse(fs.readFileSync(HOSTS_FILE, 'utf8')).hosts ?? [];
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
const writeHosts = (hosts) => {
|
|
19
|
-
fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
|
|
20
|
-
fs.writeFileSync(HOSTS_FILE, JSON.stringify({ hosts }, null, 2), { mode: 0o600 });
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
const snapshot = async (host) => {
|
|
24
|
-
const res = await fetch(`${host.origin}/api/orchestration/snapshot`, {
|
|
25
|
-
headers: host.token ? { authorization: `Bearer ${host.token}` } : {},
|
|
26
|
-
signal: AbortSignal.timeout(host.timeoutMs ?? 15000),
|
|
27
|
-
});
|
|
28
|
-
if (!res.ok) throw new Error(`${host.name}: HTTP ${res.status} ${await res.text().catch(() => '')}`.trim());
|
|
29
|
-
return res.json();
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
// Derived status. Order matters: most urgent wins.
|
|
33
|
-
const threadStatus = (t) => {
|
|
34
|
-
if (t.deletedAt) return 'deleted';
|
|
35
|
-
if (t.archivedAt) return 'archived';
|
|
36
|
-
if (t.session?.activeTurnId || t.session?.status === 'running') return 'running';
|
|
37
|
-
if (t.session?.status === 'error' || t.latestTurn?.state === 'error') return 'error';
|
|
38
|
-
if (t.snoozedUntil && new Date(t.snoozedUntil) > new Date()) return 'snoozed';
|
|
39
|
-
if (t.proposedPlans?.length) return 'needs-review';
|
|
40
|
-
if (t.settledAt && !t.unsettledAt) return 'settled';
|
|
41
|
-
return 'idle';
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
const ICON = {
|
|
45
|
-
running: '\x1b[32m●\x1b[0m', error: '\x1b[31m✕\x1b[0m', 'needs-review': '\x1b[33m◆\x1b[0m',
|
|
46
|
-
snoozed: '\x1b[90m☾\x1b[0m', settled: '\x1b[90m✓\x1b[0m', idle: '\x1b[90m·\x1b[0m',
|
|
47
|
-
archived: '\x1b[90m▪\x1b[0m', deleted: '\x1b[90m✗\x1b[0m',
|
|
48
|
-
};
|
|
49
|
-
const dim = (s) => `\x1b[90m${s}\x1b[0m`;
|
|
50
|
-
// Usage errors are user errors: print to stderr and exit non-zero so scripts
|
|
51
|
-
// can tell them apart from success.
|
|
52
|
-
const usage = (message) => {
|
|
53
|
-
console.error(message);
|
|
54
|
-
process.exitCode = 1;
|
|
55
|
-
};
|
|
56
|
-
const bold = (s) => `\x1b[1m${s}\x1b[0m`;
|
|
57
|
-
|
|
58
|
-
const collect = async (hosts) => {
|
|
59
|
-
const results = await Promise.allSettled(hosts.map(async (h) => ({ host: h, snap: await snapshot(h) })));
|
|
60
|
-
const ok = [], failed = [];
|
|
61
|
-
results.forEach((r, i) => r.status === 'fulfilled' ? ok.push(r.value) : failed.push({ host: hosts[i], error: r.reason.message }));
|
|
62
|
-
return { ok, failed };
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
const cmdLs = async (args) => {
|
|
66
|
-
const showThreads = args.includes('--threads') || args.includes('-t');
|
|
67
|
-
const showAll = args.includes('--all') || args.includes('-a');
|
|
68
|
-
const asJson = args.includes('--json');
|
|
69
|
-
const hosts = readHosts();
|
|
70
|
-
if (!hosts.length) return console.error('No hosts registered. Run: t3ctl host add <origin> <token>');
|
|
71
|
-
|
|
72
|
-
const { ok, failed } = await collect(hosts);
|
|
73
|
-
|
|
74
|
-
if (asJson) {
|
|
75
|
-
const out = ok.flatMap(({ host, snap }) => snap.projects
|
|
76
|
-
.filter((p) => showAll || !p.deletedAt)
|
|
77
|
-
.map((p) => ({
|
|
78
|
-
host: host.name, id: p.id, title: p.title, workspaceRoot: p.workspaceRoot,
|
|
79
|
-
threads: snap.threads.filter((t) => t.projectId === p.id)
|
|
80
|
-
.filter((t) => showAll || (!t.deletedAt && !t.archivedAt))
|
|
81
|
-
.map((t) => ({ id: t.id, title: t.title, branch: t.branch, status: threadStatus(t), provider: t.session?.providerName ?? null, updatedAt: t.updatedAt })),
|
|
82
|
-
})));
|
|
83
|
-
console.log(JSON.stringify({ projects: out, unreachable: failed.map((f) => ({ host: f.host.name, error: f.error })) }, null, 2));
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
for (const { host, snap } of ok) {
|
|
88
|
-
console.log(`\n${bold(host.name)} ${dim(host.origin)}`);
|
|
89
|
-
const projects = snap.projects.filter((p) => showAll || !p.deletedAt)
|
|
90
|
-
.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
91
|
-
|
|
92
|
-
for (const p of projects) {
|
|
93
|
-
const threads = snap.threads.filter((t) => t.projectId === p.id)
|
|
94
|
-
.filter((t) => showAll || (!t.deletedAt && !t.archivedAt))
|
|
95
|
-
.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
96
|
-
if (!threads.length && !showAll) continue;
|
|
97
|
-
|
|
98
|
-
const counts = {};
|
|
99
|
-
for (const t of threads) counts[threadStatus(t)] = (counts[threadStatus(t)] ?? 0) + 1;
|
|
100
|
-
const badge = Object.entries(counts).map(([k, v]) => `${ICON[k] ?? '?'}${v}`).join(' ');
|
|
101
|
-
|
|
102
|
-
console.log(` ${bold(p.title)} ${badge} ${dim(p.workspaceRoot.replace(os.homedir(), '~'))}`);
|
|
103
|
-
if (!showThreads) continue;
|
|
104
|
-
for (const t of threads) {
|
|
105
|
-
const s = threadStatus(t);
|
|
106
|
-
const meta = [t.branch, t.session?.providerName].filter(Boolean).join(' ');
|
|
107
|
-
console.log(` ${ICON[s] ?? '?'} ${(t.title || '(untitled)').slice(0, 62).padEnd(62)} ${dim(meta)}`);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
for (const f of failed) console.error(`\n\x1b[31munreachable\x1b[0m ${f.host.name}: ${f.error}`);
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
// ---- host registry ------------------------------------------------------
|
|
115
|
-
// The descriptor at /.well-known/t3/environment is UNAUTHENTICATED, so probing
|
|
116
|
-
// it answers "is anyone home, and is it T3 Code?" without a token — a wrong
|
|
117
|
-
// origin fails here instead of as a baffling 401 on the first real call.
|
|
118
|
-
// Schema: ExecutionEnvironmentDescriptor in packages/contracts/src/environment.ts.
|
|
119
|
-
|
|
120
|
-
const DESCRIPTOR_PATH = '/.well-known/t3/environment';
|
|
121
|
-
|
|
122
|
-
const probe = async (origin, timeoutMs = 5000) => {
|
|
123
|
-
let res;
|
|
124
|
-
try {
|
|
125
|
-
res = await fetch(`${origin}${DESCRIPTOR_PATH}`, { signal: AbortSignal.timeout(timeoutMs) });
|
|
126
|
-
} catch (error) {
|
|
127
|
-
// Node's fetch reports a bare "fetch failed"; the cause carries the real reason.
|
|
128
|
-
const why = error.name === 'TimeoutError' ? `no response in ${timeoutMs}ms`
|
|
129
|
-
: (error.cause?.message ?? error.message);
|
|
130
|
-
throw new Error(`cannot reach ${origin}: ${why}`);
|
|
131
|
-
}
|
|
132
|
-
const notT3 = (why) => new Error(`not a T3 Code server (${DESCRIPTOR_PATH} ${why})`);
|
|
133
|
-
if (!res.ok) throw notT3(`returned HTTP ${res.status}`);
|
|
134
|
-
let d;
|
|
135
|
-
try { d = await res.json(); } catch { throw notT3('is not JSON'); }
|
|
136
|
-
const missing = ['environmentId', 'label', 'serverVersion'].filter((k) => typeof d?.[k] !== 'string' || !d[k]);
|
|
137
|
-
if (missing.length) throw notT3(`is missing ${missing.join(', ')}`);
|
|
138
|
-
return { environmentId: d.environmentId, label: d.label, serverVersion: d.serverVersion };
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
const shortId = (id) => (id ? id.slice(0, 8) : '-');
|
|
142
|
-
const warn = (message) => console.error(`\x1b[33mwarning\x1b[0m ${message}`);
|
|
143
|
-
|
|
144
|
-
// The name is what you type in --host, so derive a typeable slug from the label
|
|
145
|
-
// rather than using the label verbatim.
|
|
146
|
-
const slugify = (label) => label.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '') || 'host';
|
|
147
|
-
|
|
148
|
-
const uniqueName = (base, hosts) => {
|
|
149
|
-
if (!hosts.some((h) => h.name === base)) return base;
|
|
150
|
-
for (let n = 2; ; n++) if (!hosts.some((h) => h.name === `${base}-${n}`)) return `${base}-${n}`;
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
const isOrigin = (value) => /^https?:\/\//i.test(value ?? '');
|
|
154
|
-
|
|
155
|
-
// Two accepted shapes, told apart by the scheme:
|
|
156
|
-
// host add <origin> [token] [--name <n>] current
|
|
157
|
-
// host add <name> <origin> <token> legacy, still works
|
|
158
|
-
const parseHostAdd = (pos) => {
|
|
159
|
-
if (isOrigin(pos[0])) return { origin: pos[0], token: pos[1] ?? null, legacy: false };
|
|
160
|
-
if (isOrigin(pos[1])) return { name: pos[0], origin: pos[1], token: pos[2] ?? null, legacy: true };
|
|
161
|
-
return null;
|
|
162
|
-
};
|
|
163
|
-
|
|
164
|
-
const HOST_ADD_USAGE = 'usage: t3ctl host add <origin> [token] [--name <name>]\n' +
|
|
165
|
-
' an origin needs a scheme, e.g. http://localhost:3773';
|
|
166
|
-
|
|
167
|
-
const cmdHostAdd = async (pos, flags, hosts) => {
|
|
168
|
-
// Validate before any network or registry work so a bare `host add` prints
|
|
169
|
-
// usage instead of stalling on a probe.
|
|
170
|
-
const parsed = parseHostAdd(pos);
|
|
171
|
-
if (!parsed) return usage(HOST_ADD_USAGE);
|
|
172
|
-
if ('name' in flags && !flags.name) return usage(HOST_ADD_USAGE);
|
|
173
|
-
if (parsed.legacy) warn('"host add <name> <origin> <token>" is deprecated — use: t3ctl host add <origin> [token] [--name <name>]');
|
|
174
|
-
|
|
175
|
-
const origin = parsed.origin.replace(/\/$/, '');
|
|
176
|
-
const descriptor = await probe(origin);
|
|
177
|
-
const existing = hosts.find((h) => h.origin === origin);
|
|
178
|
-
|
|
179
|
-
// A changed environmentId on a known origin means the origin now points at a
|
|
180
|
-
// different machine — the stored token almost certainly belongs to the old one.
|
|
181
|
-
if (existing?.environmentId && existing.environmentId !== descriptor.environmentId) {
|
|
182
|
-
warn(`${origin} is now a DIFFERENT environment\n` +
|
|
183
|
-
` was ${existing.environmentId} (${existing.label ?? 'unknown'})\n` +
|
|
184
|
-
` now ${descriptor.environmentId} (${descriptor.label})\n` +
|
|
185
|
-
` the token stored for "${existing.name}" was issued by the old one and will likely fail`);
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
const name = flags.name ?? parsed.name ?? existing?.name ??
|
|
189
|
-
uniqueName(slugify(descriptor.label), hosts);
|
|
190
|
-
const token = parsed.token ?? existing?.token ?? null;
|
|
191
|
-
|
|
192
|
-
const others = hosts.filter((h) => h.name !== name && h.origin !== origin && h.serverVersion);
|
|
193
|
-
const skewed = [...new Set(others.map((h) => h.serverVersion))].filter((v) => v !== descriptor.serverVersion);
|
|
194
|
-
if (skewed.length) warn(`serverVersion ${descriptor.serverVersion} differs from other hosts: ${skewed.join(', ')}`);
|
|
195
|
-
|
|
196
|
-
writeHosts(hosts.filter((h) => h.name !== name && h.origin !== origin).concat({
|
|
197
|
-
name, origin, token,
|
|
198
|
-
environmentId: descriptor.environmentId,
|
|
199
|
-
label: descriptor.label,
|
|
200
|
-
serverVersion: descriptor.serverVersion,
|
|
201
|
-
}));
|
|
202
|
-
console.log(`added ${bold(name)} -> ${origin}\n label ${descriptor.label}\n` +
|
|
203
|
-
` env ${descriptor.environmentId}\n version ${descriptor.serverVersion}`);
|
|
204
|
-
if (!token) warn(`no token stored for ${name} — reads will fail until you run: t3ctl host add ${origin} <token>`);
|
|
205
|
-
};
|
|
206
|
-
|
|
207
|
-
const cmdHostsList = async (hosts) => {
|
|
208
|
-
if (!hosts.length) return console.log('(no hosts)');
|
|
209
|
-
const probes = await Promise.allSettled(hosts.map((h) => probe(h.origin)));
|
|
210
|
-
const drifted = [];
|
|
211
|
-
hosts.forEach((h, i) => {
|
|
212
|
-
const p = probes[i];
|
|
213
|
-
const live = p.status === 'fulfilled' ? p.value : null;
|
|
214
|
-
if (live && h.environmentId && h.environmentId !== live.environmentId) drifted.push({ h, live });
|
|
215
|
-
const icon = live ? ICON.running : ICON.error;
|
|
216
|
-
const label = live?.label ?? h.label ?? '-';
|
|
217
|
-
const env = shortId(live?.environmentId ?? h.environmentId);
|
|
218
|
-
const version = live?.serverVersion ?? h.serverVersion ?? '-';
|
|
219
|
-
// Values for an unreachable host are whatever was last stored, so dim the
|
|
220
|
-
// whole row to keep remembered data visually distinct from probed data.
|
|
221
|
-
const cell = (text, width) => (live ? text.padEnd(width) : dim(text.padEnd(width)));
|
|
222
|
-
console.log(`${icon} ${live ? bold(h.name.padEnd(14)) : dim(h.name.padEnd(14))} ${cell(label, 18)} ${dim(env.padEnd(9))} ${cell(version, 28)} ${dim(h.origin)}` +
|
|
223
|
-
(live ? '' : ` ${dim(p.reason.message)}`));
|
|
224
|
-
});
|
|
225
|
-
for (const { h, live } of drifted) {
|
|
226
|
-
warn(`${h.name} (${h.origin}) is now a DIFFERENT environment\n` +
|
|
227
|
-
` was ${h.environmentId} (${h.label ?? 'unknown'})\n now ${live.environmentId} (${live.label})`);
|
|
228
|
-
}
|
|
229
|
-
};
|
|
230
|
-
|
|
231
|
-
const cmdHost = async (args) => {
|
|
232
|
-
const [sub, ...rest] = args;
|
|
233
|
-
const { flags, pos } = parseArgs(rest);
|
|
234
|
-
const hosts = readHosts();
|
|
235
|
-
if (sub === 'add') return cmdHostAdd(pos, flags, hosts);
|
|
236
|
-
if (sub === 'rm') {
|
|
237
|
-
if (!pos[0]) return usage('usage: t3ctl host rm <name>');
|
|
238
|
-
writeHosts(hosts.filter((h) => h.name !== pos[0]));
|
|
239
|
-
console.log(`removed ${pos[0]}`);
|
|
240
|
-
return;
|
|
241
|
-
}
|
|
242
|
-
return cmdHostsList(hosts);
|
|
243
|
-
};
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
// ---- writes -------------------------------------------------------------
|
|
247
|
-
// Commands are dispatched directly (no envelope). The CLIENT mints every id;
|
|
248
|
-
// commandId is the idempotency key, so retries are safe.
|
|
249
|
-
// Schemas: packages/contracts/src/orchestration.ts in pingdotgg/t3code.
|
|
250
|
-
|
|
251
|
-
const VALUE_FLAGS = ['host', 'model', 'branch', 'runtime-mode', 'interaction-mode', 'worktree', 'name'];
|
|
252
|
-
|
|
253
|
-
const parseArgs = (argv) => {
|
|
254
|
-
const flags = {}, pos = [];
|
|
255
|
-
for (let i = 0; i < argv.length; i++) {
|
|
256
|
-
const a = argv[i];
|
|
257
|
-
if (!a.startsWith('--')) { pos.push(a); continue; }
|
|
258
|
-
const k = a.slice(2);
|
|
259
|
-
flags[k] = VALUE_FLAGS.includes(k) ? argv[++i] : true;
|
|
260
|
-
}
|
|
261
|
-
return { flags, pos };
|
|
262
|
-
};
|
|
263
|
-
|
|
264
|
-
const pickHost = (flags) => {
|
|
265
|
-
const hosts = readHosts();
|
|
266
|
-
if (flags.host) {
|
|
267
|
-
const h = hosts.find((x) => x.name === flags.host);
|
|
268
|
-
if (!h) throw new Error(`no such host: ${flags.host}`);
|
|
269
|
-
return h;
|
|
270
|
-
}
|
|
271
|
-
if (!hosts.length) throw new Error('no hosts registered — run: t3ctl host add <origin> <token>');
|
|
272
|
-
if (hosts.length > 1) throw new Error(`multiple hosts; pass --host <${hosts.map((h) => h.name).join('|')}>`);
|
|
273
|
-
return hosts[0];
|
|
274
|
-
};
|
|
275
|
-
|
|
276
|
-
const dispatch = async (host, command) => {
|
|
277
|
-
const res = await fetch(`${host.origin}/api/orchestration/dispatch`, {
|
|
278
|
-
method: 'POST',
|
|
279
|
-
headers: { ...(host.token ? { authorization: `Bearer ${host.token}` } : {}), 'content-type': 'application/json' },
|
|
280
|
-
body: JSON.stringify(command),
|
|
281
|
-
signal: AbortSignal.timeout(host.timeoutMs ?? 15000),
|
|
282
|
-
});
|
|
283
|
-
const body = await res.text();
|
|
284
|
-
if (!res.ok) throw new Error(`${command.type} failed: HTTP ${res.status} ${body}`);
|
|
285
|
-
return body ? JSON.parse(body) : {};
|
|
286
|
-
};
|
|
287
|
-
|
|
288
|
-
const resolveProject = (snap, ref) =>
|
|
289
|
-
snap.projects.find((p) => p.id === ref) ??
|
|
290
|
-
snap.projects.find((p) => !p.deletedAt && p.title === ref) ??
|
|
291
|
-
snap.projects.find((p) => !p.deletedAt && p.workspaceRoot === path.resolve(ref.replace(/^~/, os.homedir())));
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
// Commands whose entire payload is {commandId, threadId}. Verified against
|
|
295
|
-
// packages/contracts/src/orchestration.ts — note `unsettle` is NOT one of
|
|
296
|
-
// these (it carries extra fields), so it is deliberately absent.
|
|
297
|
-
const SIMPLE_THREAD_COMMANDS = ['settle', 'archive', 'unarchive', 'unpin', 'delete'];
|
|
298
|
-
|
|
299
|
-
const resolveThread = (snap, ref) => {
|
|
300
|
-
const live = snap.threads.filter((t) => !t.deletedAt);
|
|
301
|
-
const byId = live.find((t) => t.id === ref);
|
|
302
|
-
if (byId) return byId;
|
|
303
|
-
const exact = live.filter((t) => t.title === ref);
|
|
304
|
-
if (exact.length === 1) return exact[0];
|
|
305
|
-
const fuzzy = live.filter((t) => (t.title ?? '').toLowerCase().includes(ref.toLowerCase()));
|
|
306
|
-
if (fuzzy.length === 1) return fuzzy[0];
|
|
307
|
-
if (fuzzy.length > 1) {
|
|
308
|
-
throw new Error(`"${ref}" matches ${fuzzy.length} threads:\n` +
|
|
309
|
-
fuzzy.slice(0, 8).map((t) => ` ${t.id} ${t.title}`).join('\n'));
|
|
310
|
-
}
|
|
311
|
-
throw new Error(`no thread matching "${ref}"`);
|
|
312
|
-
};
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
// thread.turn.start is ONE command carrying the first message inline — this is
|
|
316
|
-
// what the UI fires immediately after thread.create, which is why a thread with
|
|
317
|
-
// no messages is a state the UI never produces. The client-side schema requires
|
|
318
|
-
// runtimeMode/interactionMode explicitly (the server-side one defaults them).
|
|
319
|
-
const cmdThreadStart = async (thread, host, text, flags) => {
|
|
320
|
-
const command = {
|
|
321
|
-
type: 'thread.turn.start',
|
|
322
|
-
commandId: crypto.randomUUID(),
|
|
323
|
-
threadId: thread.id,
|
|
324
|
-
message: { messageId: crypto.randomUUID(), role: 'user', text, attachments: [] },
|
|
325
|
-
runtimeMode: flags['runtime-mode'] ?? thread.runtimeMode ?? 'full-access',
|
|
326
|
-
interactionMode: flags['interaction-mode'] ?? 'default',
|
|
327
|
-
createdAt: new Date().toISOString(),
|
|
328
|
-
};
|
|
329
|
-
if (flags.model) {
|
|
330
|
-
const slash = flags.model.indexOf('/');
|
|
331
|
-
if (slash < 1) throw new Error(`--model must be <instance>/<model>, got "${flags.model}"`);
|
|
332
|
-
command.modelSelection = { instanceId: flags.model.slice(0, slash), model: flags.model.slice(slash + 1) };
|
|
333
|
-
} else if (thread.modelSelection) {
|
|
334
|
-
command.modelSelection = thread.modelSelection;
|
|
335
|
-
}
|
|
336
|
-
const { sequence } = await dispatch(host, command);
|
|
337
|
-
const m = command.modelSelection;
|
|
338
|
-
console.log(`started ${bold(thread.title || thread.id)}\n id ${thread.id}` +
|
|
339
|
-
(m ? `\n model ${m.instanceId}/${m.model}` : '') +
|
|
340
|
-
`\n mode ${command.runtimeMode} / ${command.interactionMode}\n seq ${sequence}`);
|
|
341
|
-
};
|
|
342
|
-
|
|
343
|
-
const cmdThreadInterrupt = async (thread, host) => {
|
|
344
|
-
const { sequence } = await dispatch(host, {
|
|
345
|
-
type: 'thread.turn.interrupt', commandId: crypto.randomUUID(), threadId: thread.id,
|
|
346
|
-
});
|
|
347
|
-
console.log(`interrupted ${bold(thread.title || thread.id)}\n seq ${sequence}`);
|
|
348
|
-
};
|
|
349
|
-
|
|
350
|
-
const cmdProject = async (args) => {
|
|
351
|
-
const [sub, ...rest] = args;
|
|
352
|
-
const { flags, pos } = parseArgs(rest);
|
|
353
|
-
if (sub !== 'create') return usage('usage: t3ctl project create <title> <workspace-root> [--host <name>]');
|
|
354
|
-
const [title, root] = pos;
|
|
355
|
-
if (!title || !root) return usage('usage: t3ctl project create <title> <workspace-root> [--host <name>]');
|
|
356
|
-
const host = pickHost(flags);
|
|
357
|
-
const workspaceRoot = path.resolve(root.replace(/^~/, os.homedir()));
|
|
358
|
-
if (!fs.existsSync(workspaceRoot)) return console.error(`workspace root does not exist: ${workspaceRoot}`);
|
|
359
|
-
const projectId = crypto.randomUUID();
|
|
360
|
-
const { sequence } = await dispatch(host, {
|
|
361
|
-
type: 'project.create', commandId: crypto.randomUUID(),
|
|
362
|
-
projectId, title, workspaceRoot, createdAt: new Date().toISOString(),
|
|
363
|
-
});
|
|
364
|
-
console.log(`created project ${bold(title)} on ${host.name}\n id ${projectId}\n root ${workspaceRoot}\n seq ${sequence}`);
|
|
365
|
-
};
|
|
366
|
-
|
|
367
|
-
const cmdThread = async (args) => {
|
|
368
|
-
const [sub, ...rest] = args;
|
|
369
|
-
const { flags, pos } = parseArgs(rest);
|
|
370
|
-
if (sub === 'start' || sub === 'interrupt') {
|
|
371
|
-
// Validate arguments before touching the host registry, so `t3ctl thread
|
|
372
|
-
// start` with no args prints usage instead of "no hosts registered".
|
|
373
|
-
const [ref, ...rest2] = pos;
|
|
374
|
-
if (!ref) return usage(`usage: t3ctl thread ${sub} <thread>` + (sub === 'start' ? ' <message...>' : ''));
|
|
375
|
-
if (sub === 'start' && rest2.length === 0) return usage('usage: t3ctl thread start <thread> <message...>');
|
|
376
|
-
const host = pickHost(flags);
|
|
377
|
-
const thread = resolveThread(await snapshot(host), ref);
|
|
378
|
-
if (sub === 'interrupt') return cmdThreadInterrupt(thread, host);
|
|
379
|
-
return cmdThreadStart(thread, host, rest2.join(' '), flags);
|
|
380
|
-
}
|
|
381
|
-
if (SIMPLE_THREAD_COMMANDS.includes(sub)) {
|
|
382
|
-
if (!pos.length) return usage(`usage: t3ctl thread ${sub} <thread>`);
|
|
383
|
-
const host = pickHost(flags);
|
|
384
|
-
const thread = resolveThread(await snapshot(host), pos.join(' '));
|
|
385
|
-
const { sequence } = await dispatch(host, {
|
|
386
|
-
type: `thread.${sub}`, commandId: crypto.randomUUID(), threadId: thread.id,
|
|
387
|
-
});
|
|
388
|
-
console.log(`${sub}d ${bold(thread.title || thread.id)}\n id ${thread.id}\n seq ${sequence}`);
|
|
389
|
-
return;
|
|
390
|
-
}
|
|
391
|
-
if (sub !== 'create') return usage('usage: t3ctl thread create <project> <title> [--model <instance>/<model>] [--branch <b>] [--host <name>]\n t3ctl thread <settle|archive|unarchive|unpin|delete> <thread>');
|
|
392
|
-
const [projectRef, ...titleParts] = pos;
|
|
393
|
-
const title = titleParts.join(' ');
|
|
394
|
-
if (!projectRef || !title) return usage('usage: t3ctl thread create <project> <title> [--model <instance>/<model>] [--branch <b>] [--host <name>]');
|
|
395
|
-
const host = pickHost(flags);
|
|
396
|
-
const project = resolveProject(await snapshot(host), projectRef);
|
|
397
|
-
if (!project) throw new Error(`no project matching "${projectRef}" on ${host.name}`);
|
|
398
|
-
|
|
399
|
-
// instanceId is the segment before the first slash; model keeps the rest
|
|
400
|
-
// (opencode models look like "github-copilot/gpt-5.4").
|
|
401
|
-
const raw = flags.model ?? 'claudeAgent/claude-opus-5';
|
|
402
|
-
const slash = raw.indexOf('/');
|
|
403
|
-
if (slash < 1) throw new Error(`--model must be <instance>/<model>, got "${raw}"`);
|
|
404
|
-
const modelSelection = { instanceId: raw.slice(0, slash), model: raw.slice(slash + 1) };
|
|
405
|
-
|
|
406
|
-
const threadId = crypto.randomUUID();
|
|
407
|
-
const { sequence } = await dispatch(host, {
|
|
408
|
-
type: 'thread.create', commandId: crypto.randomUUID(),
|
|
409
|
-
threadId, projectId: project.id, title, modelSelection,
|
|
410
|
-
runtimeMode: flags['runtime-mode'] ?? 'full-access',
|
|
411
|
-
interactionMode: flags['interaction-mode'] ?? 'default',
|
|
412
|
-
branch: flags.branch ?? null,
|
|
413
|
-
worktreePath: flags.worktree ?? null,
|
|
414
|
-
createdAt: new Date().toISOString(),
|
|
415
|
-
});
|
|
416
|
-
console.log(`created thread ${bold(title)} in ${project.title} on ${host.name}\n id ${threadId}\n model ${modelSelection.instanceId}/${modelSelection.model}\n seq ${sequence}`);
|
|
417
|
-
};
|
|
418
|
-
|
|
419
|
-
const [cmd, ...rest] = process.argv.slice(2);
|
|
420
|
-
const commands = { ls: cmdLs, host: cmdHost, hosts: () => cmdHost([]), project: cmdProject, thread: cmdThread };
|
|
421
|
-
if (!commands[cmd]) {
|
|
422
|
-
console.log(`t3ctl — control T3 Code hosts
|
|
423
|
-
|
|
424
|
-
t3ctl ls [-t|--threads] [-a|--all] [--json] list projects and threads across hosts
|
|
425
|
-
t3ctl host add <origin> [token] [--name <n>] register a host (probes it first)
|
|
426
|
-
t3ctl host rm <name> remove a host
|
|
427
|
-
t3ctl hosts list hosts and probe each one
|
|
428
|
-
|
|
429
|
-
t3ctl project create <title> <root> create a project for an existing dir
|
|
430
|
-
t3ctl thread create <project> <title> start a thread (--model inst/model,
|
|
431
|
-
--branch, --host)
|
|
432
|
-
t3ctl thread start <thread> <message...> send a message and run the agent
|
|
433
|
-
t3ctl thread interrupt <thread> stop the running turn
|
|
434
|
-
t3ctl thread settle <thread> settle a thread (also: archive,
|
|
435
|
-
unarchive, unpin, delete)`);
|
|
436
|
-
process.exit(cmd ? 1 : 0);
|
|
437
|
-
}
|
|
438
|
-
try {
|
|
439
|
-
await commands[cmd](rest);
|
|
440
|
-
} catch (error) {
|
|
441
|
-
console.error(`\x1b[31merror\x1b[0m ${error instanceof Error ? error.message : String(error)}`);
|
|
442
|
-
process.exit(1);
|
|
443
|
-
}
|