@treeport/treeport 0.1.0 → 0.2.2

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 CHANGED
@@ -4,9 +4,12 @@ Treeport is a worktree-first terminal driver for persistent development workspac
4
4
 
5
5
  ```sh
6
6
  npm install --global @treeport/treeport
7
- treeport up
7
+ cd /path/to/repository
8
+ treeport .
8
9
  ```
9
10
 
11
+ Treeport starts its backend if needed, registers the repository and its worktrees, and opens the current worktree in the desktop app or browser. Run `treeport up` to start only the backend.
12
+
10
13
  Treeport supports macOS and Linux and requires Node.js 24 or newer, Git, and tmux 3.2 or newer.
11
14
 
12
15
  Documentation: <https://treeport.app>
@@ -0,0 +1,407 @@
1
+ import { z } from "zod";
2
+ //#region ../../packages/shared/dist/terminal-protocol.js
3
+ const SOCKET_IO_PATH = "/api/socket.io/";
4
+ const TERMINAL_CONTROLLER_GRACE_MS = 1e4;
5
+ const TERMINAL_OUTPUT_HIGH_WATERMARK = 256 * 1024;
6
+ const TERMINAL_OUTPUT_LOW_WATERMARK = 64 * 1024;
7
+ const TERMINAL_OUTPUT_STALL_TIMEOUT_MS = 3e4;
8
+ const TERMINAL_MAX_CLIENT_MESSAGE_BYTES = 128 * 1024;
9
+ const TERMINAL_MAX_INPUT_BYTES = 64 * 1024;
10
+ const TERMINAL_SCROLL_EXIT_SEQUENCE = "\x1B[9000~";
11
+ const TERMINAL_SELECTION_START_SEQUENCE = "\x1B[9001~";
12
+ const TERMINAL_SELECTION_STOP_SEQUENCE = "\x1B[9002~";
13
+ const TERMINAL_SELECTION_CLEAR_SEQUENCE = "\x1B[9003~";
14
+ const TERMINAL_SELECTION_RESTORE_SEQUENCE = "\x1B[9004~";
15
+ const terminalId = z.string().min(1).max(128);
16
+ const clientId = z.string().min(1).max(128);
17
+ const streamId = z.string().min(1).max(128);
18
+ const generation = z.number().int().nonnegative();
19
+ const dimensions = {
20
+ cols: z.number().int().min(2).max(1e3),
21
+ rows: z.number().int().min(2).max(500)
22
+ };
23
+ const terminalSizeSchema = z.strictObject(dimensions);
24
+ const terminalProgressSchema = z.strictObject({
25
+ state: z.enum([
26
+ "normal",
27
+ "error",
28
+ "indeterminate",
29
+ "paused"
30
+ ]),
31
+ value: z.number().int().min(0).max(100).nullable()
32
+ });
33
+ const terminalProgramSchema = z.enum([
34
+ "pi",
35
+ "claude",
36
+ "codex"
37
+ ]);
38
+ const terminalRuntimeMetadataSchema = z.strictObject({
39
+ terminalId: z.string().min(1),
40
+ title: z.string().max(256).nullable(),
41
+ program: terminalProgramSchema.nullable().default(null),
42
+ hasForegroundProcess: z.boolean().nullable().optional(),
43
+ progress: terminalProgressSchema.nullable(),
44
+ progressStartedAt: z.string().datetime().nullable().default(null),
45
+ progressClearedAt: z.string().datetime().nullable().default(null),
46
+ bell: z.strictObject({
47
+ sequence: z.number().int().positive(),
48
+ at: z.string().datetime(),
49
+ unread: z.boolean()
50
+ }).nullable().default(null)
51
+ });
52
+ const terminalBellAcknowledgementSchema = z.strictObject({ sequence: z.number().int().positive() });
53
+ function parseTerminalProgress(data) {
54
+ const [command, rawState, rawValue, ...extra] = data.split(";");
55
+ if (command !== "4" || extra.length > 0 || !/^[0-4]$/.test(rawState ?? "")) return;
56
+ const state = Number(rawState);
57
+ if (state === 0) return null;
58
+ if (rawValue !== void 0 && rawValue !== "" && !/^\d{1,3}$/.test(rawValue)) return;
59
+ const value = rawValue === void 0 || rawValue === "" ? null : Number(rawValue);
60
+ if (value !== null && value > 100) return;
61
+ return {
62
+ state: [
63
+ void 0,
64
+ "normal",
65
+ "error",
66
+ "indeterminate",
67
+ "paused"
68
+ ][state],
69
+ value
70
+ };
71
+ }
72
+ const terminalAuthSchema = z.strictObject({
73
+ terminalId,
74
+ clientId,
75
+ ...dimensions
76
+ });
77
+ const terminalInputSchema = z.strictObject({
78
+ generation,
79
+ data: z.string().max(TERMINAL_MAX_INPUT_BYTES)
80
+ });
81
+ const terminalBinarySchema = z.strictObject({
82
+ generation,
83
+ data: z.string().max(TERMINAL_MAX_INPUT_BYTES)
84
+ });
85
+ const terminalResizeSchema = z.strictObject({
86
+ generation,
87
+ ...dimensions
88
+ });
89
+ const terminalTakeControlSchema = z.strictObject({
90
+ generation,
91
+ ...dimensions
92
+ });
93
+ const terminalLegacyTakeControlSchema = z.strictObject({ generation });
94
+ const terminalOutputAckSchema = z.strictObject({
95
+ streamId,
96
+ sequence: z.number().int().nonnegative()
97
+ });
98
+ const terminalReadyBase = {
99
+ connectionId: z.string().min(1).max(128),
100
+ streamId,
101
+ generation,
102
+ controller: z.boolean(),
103
+ reset: z.literal("full")
104
+ };
105
+ const terminalLegacyReadySchema = z.strictObject(terminalReadyBase);
106
+ const terminalReadyV2Schema = z.strictObject({
107
+ ...terminalReadyBase,
108
+ ...dimensions,
109
+ revision: z.number().int().positive()
110
+ });
111
+ z.union([terminalLegacyReadySchema, terminalReadyV2Schema]);
112
+ z.strictObject({
113
+ ...dimensions,
114
+ revision: z.number().int().positive()
115
+ });
116
+ z.strictObject({
117
+ streamId,
118
+ sequence: z.number().int().positive(),
119
+ data: z.string()
120
+ });
121
+ z.strictObject({ title: z.string().max(256) });
122
+ z.strictObject({ progress: terminalProgressSchema.nullable() });
123
+ z.strictObject({ viewing: z.boolean() });
124
+ z.strictObject({
125
+ generation,
126
+ controller: z.boolean()
127
+ });
128
+ z.strictObject({ exitCode: z.number().int().nullable() });
129
+ z.strictObject({
130
+ code: z.string().min(1).max(80),
131
+ message: z.string().min(1).max(1e3),
132
+ retryable: z.boolean()
133
+ });
134
+ function parseTerminalAuth(value) {
135
+ const parsed = terminalAuthSchema.safeParse(value);
136
+ return parsed.success ? parsed.data : null;
137
+ }
138
+ //#endregion
139
+ //#region ../../packages/shared/dist/socket-protocol.js
140
+ const identifierSchema = z.string().min(1).max(128);
141
+ const eventEnvelope = (type, data) => z.strictObject({
142
+ id: identifierSchema,
143
+ type: z.literal(type),
144
+ at: z.string().datetime(),
145
+ data
146
+ });
147
+ const projectEventDataSchema = z.strictObject({
148
+ projectId: identifierSchema,
149
+ worktreeId: z.null()
150
+ });
151
+ const worktreeEventDataSchema = z.strictObject({ worktreeId: identifierSchema });
152
+ const projectWorktreeEventDataSchema = z.strictObject({
153
+ projectId: identifierSchema,
154
+ worktreeId: identifierSchema
155
+ });
156
+ const operationEventDataSchema = z.strictObject({
157
+ operationId: identifierSchema,
158
+ worktreeId: identifierSchema
159
+ });
160
+ const productEventSchema = z.discriminatedUnion("type", [
161
+ eventEnvelope("project.created", projectEventDataSchema),
162
+ eventEnvelope("project.updated", projectEventDataSchema),
163
+ eventEnvelope("project.removed", projectEventDataSchema),
164
+ eventEnvelope("worktree.created", projectWorktreeEventDataSchema),
165
+ eventEnvelope("worktree.updated", worktreeEventDataSchema),
166
+ eventEnvelope("worktree.removed", projectWorktreeEventDataSchema),
167
+ eventEnvelope("create.started", z.strictObject({
168
+ projectId: identifierSchema,
169
+ operationId: identifierSchema,
170
+ worktreeId: z.null()
171
+ })),
172
+ eventEnvelope("create.completed", z.strictObject({
173
+ projectId: identifierSchema,
174
+ operationId: identifierSchema,
175
+ worktreeId: identifierSchema
176
+ })),
177
+ eventEnvelope("create.failed", z.strictObject({
178
+ projectId: identifierSchema,
179
+ operationId: identifierSchema,
180
+ worktreeId: z.null()
181
+ })),
182
+ eventEnvelope("terminal.created", z.strictObject({
183
+ projectId: identifierSchema.optional(),
184
+ worktreeId: identifierSchema,
185
+ terminalId: identifierSchema
186
+ })),
187
+ eventEnvelope("terminal.updated", z.strictObject({
188
+ worktreeId: identifierSchema,
189
+ terminalId: identifierSchema
190
+ })),
191
+ eventEnvelope("terminal.removed", z.strictObject({
192
+ worktreeId: identifierSchema,
193
+ terminalId: identifierSchema
194
+ })),
195
+ eventEnvelope("terminal.metadata", terminalRuntimeMetadataSchema.extend({ worktreeId: z.null() })),
196
+ eventEnvelope("terminal.controller_changed", z.strictObject({
197
+ terminalId: identifierSchema,
198
+ controlled: z.boolean(),
199
+ worktreeId: z.null()
200
+ })),
201
+ eventEnvelope("panel.created", z.strictObject({
202
+ worktreeId: identifierSchema,
203
+ panelId: identifierSchema
204
+ })),
205
+ eventEnvelope("panel.updated", z.strictObject({
206
+ worktreeId: identifierSchema,
207
+ panelId: identifierSchema
208
+ })),
209
+ eventEnvelope("panel.open_requested", z.strictObject({
210
+ worktreeId: identifierSchema,
211
+ panelId: identifierSchema,
212
+ sourceTerminalId: identifierSchema.nullable()
213
+ })),
214
+ eventEnvelope("panel.removed", z.strictObject({
215
+ worktreeId: identifierSchema,
216
+ panelId: identifierSchema
217
+ })),
218
+ eventEnvelope("remove.started", operationEventDataSchema.extend({ kind: z.literal("remove") })),
219
+ eventEnvelope("remove.completed", operationEventDataSchema),
220
+ eventEnvelope("remove.failed", operationEventDataSchema.extend({ error: z.string() }))
221
+ ]);
222
+ const webPanelSnapshotSchema = z.strictObject({
223
+ id: z.string().min(1),
224
+ kind: z.literal("web"),
225
+ worktreeId: z.string().min(1),
226
+ definitionId: z.string().min(1),
227
+ title: z.string().min(1),
228
+ launch: z.strictObject({
229
+ input: z.record(z.string(), z.json()).nullable(),
230
+ cwd: z.string().nullable()
231
+ }),
232
+ sandbox: z.strictObject({ allowSameOrigin: z.boolean() }),
233
+ createdAt: z.string(),
234
+ updatedAt: z.string()
235
+ });
236
+ const eventsSnapshotSchema = z.strictObject({
237
+ at: z.string().datetime(),
238
+ terminalMetadata: z.array(terminalRuntimeMetadataSchema),
239
+ webPanels: z.array(webPanelSnapshotSchema)
240
+ });
241
+ function parseEventsSnapshot(value) {
242
+ const parsed = eventsSnapshotSchema.safeParse(value);
243
+ return parsed.success ? parsed.data : null;
244
+ }
245
+ function parseProductEvent(value) {
246
+ const parsed = productEventSchema.safeParse(value);
247
+ return parsed.success ? parsed.data : null;
248
+ }
249
+ //#endregion
250
+ //#region ../../packages/shared/dist/index.js
251
+ const TERMINAL_MAX_UPLOAD_BYTES = 50 * 1024 * 1024;
252
+ const TERMINAL_EXECUTABLE_MAX_LENGTH = 4096;
253
+ const TERMINAL_ARGUMENT_MAX_LENGTH = 4096;
254
+ const TERMINAL_CAPTURE_MAX_LINES = 5e3;
255
+ const WEB_PANEL_INPUT_MAX_BYTES = 64 * 1024;
256
+ const PROJECT_COLORS = [
257
+ "rose",
258
+ "orange",
259
+ "amber",
260
+ "emerald",
261
+ "cyan",
262
+ "blue",
263
+ "violet",
264
+ "pink"
265
+ ];
266
+ const browseDirectoryQuerySchema = z.object({
267
+ input: z.string().trim().min(1).max(4096),
268
+ hidden: z.enum(["true", "false"]).optional().default("false").transform((value) => value === "true")
269
+ });
270
+ const terminalCaptureQuerySchema = z.object({ lines: z.coerce.number().int().min(1).max(TERMINAL_CAPTURE_MAX_LINES).optional().default(200) });
271
+ const registerProjectSchema = z.object({
272
+ path: z.string().trim().min(1),
273
+ name: z.string().trim().min(1).max(120).optional()
274
+ });
275
+ const updateProjectSchema = z.object({ color: z.enum(PROJECT_COLORS).nullable() });
276
+ const terminalNameSchema = z.string().trim().min(1).max(120);
277
+ const terminalArgvSchema = z.array(z.string()).min(1).max(128);
278
+ const terminalPresetArgumentSchema = z.string().max(TERMINAL_ARGUMENT_MAX_LENGTH);
279
+ const terminalPresetFields = {
280
+ name: terminalNameSchema,
281
+ executable: z.string().min(1).max(TERMINAL_EXECUTABLE_MAX_LENGTH).refine((value) => value.trim().length > 0, { message: "Executable cannot be blank" }),
282
+ args: z.array(terminalPresetArgumentSchema).max(127),
283
+ closeOnSuccess: z.boolean().default(false)
284
+ };
285
+ const repositoryTerminalPresetSchema = z.strictObject(terminalPresetFields);
286
+ const repositoryTerminalPresetIdSchema = z.string().regex(/^[a-z0-9][a-z0-9._-]{0,119}$/, { message: "Preset IDs must contain only lowercase letters, numbers, dots, underscores, and hyphens" });
287
+ const repositoryTerminalPresetsFileSchema = z.strictObject({
288
+ version: z.literal(1),
289
+ presets: z.record(repositoryTerminalPresetIdSchema, z.unknown())
290
+ });
291
+ const terminalPresetRevisionSchema = z.string().min(1).max(64);
292
+ const initialTerminalSchema = z.object({
293
+ name: terminalNameSchema,
294
+ argv: terminalArgvSchema.optional(),
295
+ returnToShell: z.boolean().optional(),
296
+ initialSize: terminalSizeSchema.optional()
297
+ });
298
+ const createWorktreeSchema = z.object({
299
+ name: z.string().trim().min(1).max(120),
300
+ base: z.enum(["default", "current"]).default("default"),
301
+ sourceWorktreeId: z.string().min(1).optional(),
302
+ initialTerminal: initialTerminalSchema.optional()
303
+ }).superRefine((value, context) => {
304
+ if (value.base === "current" && !value.sourceWorktreeId) context.addIssue({
305
+ code: "custom",
306
+ path: ["sourceWorktreeId"],
307
+ message: "A source worktree is required when starting from current"
308
+ });
309
+ });
310
+ const terminalCwdSchema = z.string().min(1).max(4096).refine((value) => value.trim().length > 0 && !value.includes("\0"), { message: "Working directory cannot be blank or contain NUL" });
311
+ const terminalEnvironmentKeySchema = z.string().min(1).max(256).refine((value) => !value.includes("=") && !value.includes("\0"), { message: "Environment keys cannot contain equals or NUL" });
312
+ const terminalEnvironmentSchema = z.record(terminalEnvironmentKeySchema, z.string().max(TERMINAL_ARGUMENT_MAX_LENGTH).refine((value) => !value.includes("\0"), { message: "Environment values cannot contain NUL" })).refine((value) => Object.keys(value).length <= 128, { message: "Environment cannot contain more than 128 variables" });
313
+ const createTerminalSchema = z.object({
314
+ name: terminalNameSchema,
315
+ argv: terminalArgvSchema.optional(),
316
+ cwd: terminalCwdSchema.optional(),
317
+ env: terminalEnvironmentSchema.optional(),
318
+ returnToShell: z.boolean().optional(),
319
+ closeOnSuccess: z.boolean().optional(),
320
+ initialSize: terminalSizeSchema.optional()
321
+ }).refine((value) => !(value.returnToShell && value.closeOnSuccess), { message: "A terminal cannot return to a shell and close on success" });
322
+ const updateTerminalSchema = z.object({ name: terminalNameSchema });
323
+ const webPanelInputSchema = z.record(z.string(), z.json());
324
+ const createWebPanelSchema = z.object({
325
+ definitionId: z.string().min(1).max(256),
326
+ input: webPanelInputSchema.nullable().optional(),
327
+ launchCwd: z.string().max(4096).nullable().optional()
328
+ });
329
+ const openWebPanelSchema = createWebPanelSchema.extend({
330
+ newInstance: z.boolean().optional(),
331
+ sourceTerminalId: z.string().min(1).max(128).nullable().optional()
332
+ });
333
+ const webPanelStorageKeySchema = z.string().min(1).max(128);
334
+ const getWebPanelStorageSchema = z.object({ key: webPanelStorageKeySchema });
335
+ const setWebPanelStorageSchema = z.object({
336
+ key: webPanelStorageKeySchema,
337
+ value: z.json()
338
+ });
339
+ const deleteWebPanelStorageSchema = z.object({ key: webPanelStorageKeySchema });
340
+ const createTerminalPresetSchema = z.object(terminalPresetFields);
341
+ const updateTerminalPresetSchema = z.object({
342
+ ...terminalPresetFields,
343
+ closeOnSuccess: z.boolean().optional(),
344
+ expectedUpdatedAt: terminalPresetRevisionSchema
345
+ });
346
+ const deleteTerminalPresetSchema = z.object({ expectedUpdatedAt: terminalPresetRevisionSchema });
347
+ const packageProjectQuerySchema = z.object({ path: z.string().trim().min(1).max(4096) });
348
+ const packageInstallSchema = z.object({
349
+ source: z.string().trim().min(1).max(4096),
350
+ projectId: z.string().min(1).optional()
351
+ });
352
+ const packageRemoveSchema = z.object({
353
+ source: z.string().trim().min(1).max(4096),
354
+ projectId: z.string().min(1).optional()
355
+ });
356
+ const packageUpdateSchema = z.object({ source: z.string().trim().min(1).max(4096).optional() });
357
+ const packageReloadSchema = z.object({ projectId: z.string().min(1).optional() });
358
+ const removeWorktreeSchema = z.object({
359
+ confirmationToken: z.string().length(64),
360
+ confirmDestructive: z.boolean()
361
+ });
362
+ z.object({
363
+ project: z.string().min(1),
364
+ worktreeName: z.string().trim().min(1).max(120),
365
+ name: terminalNameSchema,
366
+ argv: terminalArgvSchema.optional(),
367
+ base: z.enum(["default", "current"]).default("default"),
368
+ sourceWorktreeId: z.string().min(1).optional()
369
+ }).superRefine((value, context) => {
370
+ if (value.base === "current" && !value.sourceWorktreeId) context.addIssue({
371
+ code: "custom",
372
+ path: ["sourceWorktreeId"],
373
+ message: "A source worktree is required when starting from current"
374
+ });
375
+ });
376
+ //#endregion
377
+ //#region src/duration.ts
378
+ const DURATION_UNITS = {
379
+ ms: 1,
380
+ s: 1e3,
381
+ m: 6e4,
382
+ h: 36e5
383
+ };
384
+ const MAX_DURATION_MS = 2147483647;
385
+ function parseDurationMs(value) {
386
+ const match = /^(\d+)(ms|s|m|h)$/.exec(value);
387
+ if (!match) throw new Error("Timeout must be a positive duration such as 500ms, 30s, 5m, or 1h");
388
+ const timeoutMs = Number(match[1]) * DURATION_UNITS[match[2]];
389
+ if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs > MAX_DURATION_MS) throw new Error("Timeout must be between 1ms and 2147483647ms");
390
+ return timeoutMs;
391
+ }
392
+ //#endregion
393
+ //#region src/server/core/loopback.ts
394
+ const LOOPBACK_HOSTS = /* @__PURE__ */ new Set([
395
+ "127.0.0.1",
396
+ "::1",
397
+ "localhost"
398
+ ]);
399
+ function isLoopbackHost(host) {
400
+ return LOOPBACK_HOSTS.has(host.trim().toLowerCase());
401
+ }
402
+ function assertLoopbackHost(host) {
403
+ if (isLoopbackHost(host)) return;
404
+ throw new Error("Treeport supports only loopback listeners. Run `treeport up --host 127.0.0.1`, then use `treeport remote enable` for private remote access.");
405
+ }
406
+ //#endregion
407
+ export { parseProductEvent as A, TERMINAL_SELECTION_RESTORE_SEQUENCE as B, repositoryTerminalPresetsFileSchema as C, updateTerminalPresetSchema as D, updateProjectSchema as E, TERMINAL_OUTPUT_HIGH_WATERMARK as F, terminalBellAcknowledgementSchema as G, TERMINAL_SELECTION_STOP_SEQUENCE as H, TERMINAL_OUTPUT_LOW_WATERMARK as I, terminalLegacyTakeControlSchema as J, terminalBinarySchema as K, TERMINAL_OUTPUT_STALL_TIMEOUT_MS as L, TERMINAL_CONTROLLER_GRACE_MS as M, TERMINAL_MAX_CLIENT_MESSAGE_BYTES as N, updateTerminalSchema as O, TERMINAL_MAX_INPUT_BYTES as P, terminalTakeControlSchema as Q, TERMINAL_SCROLL_EXIT_SEQUENCE as R, repositoryTerminalPresetSchema as S, terminalCaptureQuerySchema as T, parseTerminalAuth as U, TERMINAL_SELECTION_START_SEQUENCE as V, parseTerminalProgress as W, terminalResizeSchema as X, terminalOutputAckSchema as Y, terminalSizeSchema as Z, packageReloadSchema as _, WEB_PANEL_INPUT_MAX_BYTES as a, registerProjectSchema as b, createTerminalSchema as c, deleteTerminalPresetSchema as d, deleteWebPanelStorageSchema as f, packageProjectQuerySchema as g, packageInstallSchema as h, TERMINAL_MAX_UPLOAD_BYTES as i, SOCKET_IO_PATH as j, parseEventsSnapshot as k, createWebPanelSchema as l, openWebPanelSchema as m, parseDurationMs as n, browseDirectoryQuerySchema as o, getWebPanelStorageSchema as p, terminalInputSchema as q, TERMINAL_CAPTURE_MAX_LINES as r, createTerminalPresetSchema as s, assertLoopbackHost as t, createWorktreeSchema as u, packageRemoveSchema as v, setWebPanelStorageSchema as w, removeWorktreeSchema as x, packageUpdateSchema as y, TERMINAL_SELECTION_CLEAR_SEQUENCE as z };