@treeport/treeport 0.5.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/treeport.mjs +26 -1
- package/dist/dist-Crk_Xr82.js +735 -0
- package/dist/node/cli/index.js +139 -9
- package/dist/node/server/core/launcher.js +42 -13
- package/dist/node/server/index.js +2823 -269
- package/dist/{update-BW-a6Bd-.js → update-qVp7yL5D.js} +1 -392
- package/dist/web/assets/index-2LLiNn3-.js +146 -0
- package/dist/web/assets/index-DmDs47YU.css +2 -0
- package/dist/web/index.html +2 -2
- package/drizzle/0010_browser_panels_and_permissions.sql +21 -0
- package/drizzle/0011_free_magdalene.sql +1 -0
- package/drizzle/meta/0010_snapshot.json +923 -0
- package/drizzle/meta/0011_snapshot.json +931 -0
- package/drizzle/meta/_journal.json +14 -0
- package/package.json +5 -2
- package/skills/treeport/SKILL.md +58 -2
- package/dist/web/assets/index-Cr4UkmRD.js +0 -146
- package/dist/web/assets/index-he-SubzL.css +0 -2
package/dist/node/cli/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { B as parseEventsSnapshot, H as SOCKET_IO_PATH, R as webPanelInputSchema, V as parseProductEvent, t as TERMINAL_CAPTURE_MAX_LINES } from "../../dist-Crk_Xr82.js";
|
|
3
|
+
import { A as treeportVersion, C as disableTailscaleRemote, D as resolvePackagePath, E as resolveLocalApiUrl, M as parseDurationMs, O as runDoctor, S as daemonUp, T as readDaemonLogs, _ as serviceStatus, b as daemonHealth, d as serviceDisable, f as serviceDoctorCheck, g as serviceStart, h as serviceRun, k as tailscaleRemoteStatus, l as readServiceLogs, m as serviceInstalled, p as serviceEnable, s as runLocalUpdate, t as LocalUpdateError, u as serviceApply, v as serviceStop, w as enableTailscaleRemote, x as daemonStatus, y as daemonDown } from "../../update-qVp7yL5D.js";
|
|
3
4
|
import fs from "node:fs/promises";
|
|
4
5
|
import path from "node:path";
|
|
5
6
|
import { Command, CommanderError } from "commander";
|
|
@@ -285,7 +286,37 @@ async function webPanelLaunchCwd(worktree) {
|
|
|
285
286
|
});
|
|
286
287
|
return path.relative(worktreeRoot, cwd) || ".";
|
|
287
288
|
}
|
|
288
|
-
function
|
|
289
|
+
async function resolveBrowserPanel(panelId) {
|
|
290
|
+
const candidates = (await projects()).flatMap((project) => project.worktrees.flatMap((worktree) => worktree.panels.filter((panel) => panel.kind === "browser").map((panel) => ({
|
|
291
|
+
panel,
|
|
292
|
+
worktree
|
|
293
|
+
}))));
|
|
294
|
+
if (panelId) {
|
|
295
|
+
const match = candidates.find((candidate) => candidate.panel.id === panelId);
|
|
296
|
+
if (!match) throw new CliError(`Browser ${panelId} was not found`, 5);
|
|
297
|
+
return match;
|
|
298
|
+
}
|
|
299
|
+
const worktree = await resolveWorktree(".");
|
|
300
|
+
const matches = candidates.filter((candidate) => candidate.worktree.id === worktree.id);
|
|
301
|
+
if (matches.length === 1) return matches[0];
|
|
302
|
+
if (matches.length === 0) throw new CliError(`No Browser is open in worktree ${worktree.name}`, 5, "BROWSER_PANEL_NOT_FOUND");
|
|
303
|
+
throw new CliError(`More than one Browser is open in worktree ${worktree.name}; specify --panel`, 5, "BROWSER_PANEL_AMBIGUOUS", { panelIds: matches.map((candidate) => candidate.panel.id) });
|
|
304
|
+
}
|
|
305
|
+
async function runBrowserAgentCommand(command, args, panelId) {
|
|
306
|
+
const { panel } = await resolveBrowserPanel(panelId);
|
|
307
|
+
const result = await request(`/api/panels/${encodeURIComponent(panel.id)}/browser-agent`, {
|
|
308
|
+
method: "POST",
|
|
309
|
+
body: JSON.stringify({
|
|
310
|
+
command,
|
|
311
|
+
args
|
|
312
|
+
})
|
|
313
|
+
});
|
|
314
|
+
return {
|
|
315
|
+
panelId: panel.id,
|
|
316
|
+
output: result.output
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
function panelUrl(worktree, panelId) {
|
|
289
320
|
const target = new URL(apiUrl);
|
|
290
321
|
target.pathname = `/projects/${encodeURIComponent(worktree.projectId)}/worktrees/${encodeURIComponent(worktree.id)}/panels/${encodeURIComponent(panelId)}`;
|
|
291
322
|
target.search = "";
|
|
@@ -441,13 +472,10 @@ async function waitForTerminal(terminalId, condition, timeoutMs) {
|
|
|
441
472
|
function print(value, human) {
|
|
442
473
|
writeStdout(`${jsonOutput ? JSON.stringify(value) : human ? human() : JSON.stringify(value, null, 2)}\n`);
|
|
443
474
|
}
|
|
444
|
-
const agentGuidance = `AI agents:
|
|
445
|
-
If you're an AI agent, use \`treeport skills\` to see the usage guide.
|
|
446
|
-
`;
|
|
447
475
|
async function main(args) {
|
|
448
476
|
const argv = args[0] === "spawn" || args[0] === "terminal" && args[1] === "create" ? commandArgv(args) : void 0;
|
|
449
477
|
let parserError = "";
|
|
450
|
-
const program = new Command().name("treeport").usage("[options] [folder] [command]").description("Manage Treeport projects, trees, and terminals.").argument("[folder]", "folder or folder inside a Git repository to open").option("--json", "emit machine-readable JSON").
|
|
478
|
+
const program = new Command().name("treeport").usage("[options] [folder] [command]").description("Manage Treeport projects, trees, and terminals.").argument("[folder]", "folder or folder inside a Git repository to open").option("--json", "emit machine-readable JSON").configureOutput({
|
|
451
479
|
writeOut: writeStdout,
|
|
452
480
|
writeErr: (value) => {
|
|
453
481
|
parserError += value;
|
|
@@ -673,6 +701,7 @@ async function main(args) {
|
|
|
673
701
|
worktreeId,
|
|
674
702
|
terminalId
|
|
675
703
|
});
|
|
704
|
+
const treeContext = (await request(`/api/worktrees/${encodeURIComponent(worktree.id)}/context`)).context;
|
|
676
705
|
const context = {
|
|
677
706
|
managed: true,
|
|
678
707
|
apiUrl,
|
|
@@ -695,7 +724,8 @@ async function main(args) {
|
|
|
695
724
|
head: worktree.head,
|
|
696
725
|
branch: worktree.branch,
|
|
697
726
|
detached: worktree.detached,
|
|
698
|
-
kind: worktree.kind
|
|
727
|
+
kind: worktree.kind,
|
|
728
|
+
context: treeContext
|
|
699
729
|
},
|
|
700
730
|
terminal: {
|
|
701
731
|
id: terminal.id,
|
|
@@ -705,8 +735,108 @@ async function main(args) {
|
|
|
705
735
|
exitCode: terminal.exitCode
|
|
706
736
|
}
|
|
707
737
|
};
|
|
708
|
-
print(context, () =>
|
|
738
|
+
print(context, () => {
|
|
739
|
+
const entries = Object.entries(context.worktree.context);
|
|
740
|
+
const treeContextText = entries.length === 0 ? "—" : entries.map(([key, value]) => {
|
|
741
|
+
const [first = "", ...rest] = [...value].map((character) => {
|
|
742
|
+
const code = character.charCodeAt(0);
|
|
743
|
+
return code !== 10 && (code <= 31 || code >= 127 && code <= 159) ? `\\u${code.toString(16).padStart(4, "0")}` : character;
|
|
744
|
+
}).join("").split("\n");
|
|
745
|
+
return ` ${key}: ${first}${rest.length > 0 ? `\n${rest.map((line) => ` ${line}`).join("\n")}` : ""}`;
|
|
746
|
+
}).join("\n");
|
|
747
|
+
return `Treeport context\n\nProject: ${context.project.name} (${context.project.id})\nTree: ${context.worktree.name} (${context.worktree.id})\nPath: ${context.worktree.path}\nContext:\n${treeContextText}\nTerminal: ${context.terminal.name} (${context.terminal.id}) — ${context.terminal.status}\nAPI: ${context.apiUrl}\nLifecycle: ${context.daemonLifecycle === "external" ? "externally managed" : context.daemonLifecycle === "service" ? "managed by the OS service" : "managed by Treeport"}`;
|
|
748
|
+
});
|
|
749
|
+
});
|
|
750
|
+
const browserCommand = program.command("browser").description("Manage Browser and its hosted Chromium");
|
|
751
|
+
browserCommand.action(() => {
|
|
752
|
+
writeStdout(browserCommand.helpInformation());
|
|
753
|
+
});
|
|
754
|
+
const browserOpenCommand = browserCommand.command("open").description("Open Browser and request client navigation").argument("[url]", "absolute HTTP or HTTPS URL").requiredOption("--worktree <id-or-path-or-dot>", "owning tree").option("--json", "emit machine-readable JSON");
|
|
755
|
+
browserOpenCommand.action(async (url) => {
|
|
756
|
+
const worktree = await resolveWorktree(browserOpenCommand.opts().worktree);
|
|
757
|
+
const body = {
|
|
758
|
+
url,
|
|
759
|
+
sourceTerminalId: contextWorktreeId === worktree.id ? contextTerminalId ?? null : null
|
|
760
|
+
};
|
|
761
|
+
const result = await request(`/api/worktrees/${encodeURIComponent(worktree.id)}/browser-panels`, {
|
|
762
|
+
method: "POST",
|
|
763
|
+
body: JSON.stringify(body)
|
|
764
|
+
});
|
|
765
|
+
const output = {
|
|
766
|
+
...result,
|
|
767
|
+
url: panelUrl(worktree, result.panel.id)
|
|
768
|
+
};
|
|
769
|
+
print(output, () => `Opened ${result.panel.title} (${result.panel.id})\n${output.url}`);
|
|
709
770
|
});
|
|
771
|
+
browserCommand.command("install").description("Install the Chromium build used by Browser").option("--json", "emit machine-readable JSON").action(async () => {
|
|
772
|
+
const result = await request("/api/browser/install", { method: "POST" });
|
|
773
|
+
print(result, () => result.message);
|
|
774
|
+
});
|
|
775
|
+
browserCommand.command("status").description("Show hosted browser installation status").option("--json", "emit machine-readable JSON").action(async () => {
|
|
776
|
+
const result = await request("/api/browser/status");
|
|
777
|
+
print(result, () => `${result.installed ? "Chromium is installed" : "Chromium is not installed"}\nLaunch ready: ${result.launchReady ? "yes" : "no"}\nPlaywright: ${result.playwrightVersion}\nBrowser: ${result.channel} ${result.browserRevision}\nExecutable: ${result.executablePath}${result.launchError ? `\nLaunch error: ${result.launchError}` : ""}`);
|
|
778
|
+
});
|
|
779
|
+
browserCommand.command("remove").description("Remove Treeport's hosted Chromium build").option("--json", "emit machine-readable JSON").action(async () => {
|
|
780
|
+
await request("/api/browser/install", { method: "DELETE" });
|
|
781
|
+
print({ removed: true }, () => "Removed Treeport hosted Chromium");
|
|
782
|
+
});
|
|
783
|
+
browserCommand.command("list").description("List open Browser sessions").option("--json", "emit machine-readable JSON").action(async () => {
|
|
784
|
+
const panels = (await projects()).flatMap((project) => project.worktrees.flatMap((worktree) => worktree.panels.filter((panel) => panel.kind === "browser").map((panel) => ({
|
|
785
|
+
panelId: panel.id,
|
|
786
|
+
title: panel.title,
|
|
787
|
+
worktreeId: worktree.id,
|
|
788
|
+
worktree: worktree.name,
|
|
789
|
+
projectId: project.id,
|
|
790
|
+
project: project.name
|
|
791
|
+
}))));
|
|
792
|
+
print(panels, () => panels.length ? panels.map((panel) => `${panel.panelId}\t${panel.project} / ${panel.worktree}\t${panel.title}`).join("\n") : "Browser is not open.");
|
|
793
|
+
});
|
|
794
|
+
const printAgentResult = async (command, args, panelId) => {
|
|
795
|
+
const result = await runBrowserAgentCommand(command, args, panelId);
|
|
796
|
+
print(result, () => result.output);
|
|
797
|
+
};
|
|
798
|
+
const browserSnapshotCommand = browserCommand.command("snapshot").description("Capture an accessibility snapshot of the hosted page").option("--panel <panel-id>", "Browser ID").option("--json", "emit machine-readable JSON");
|
|
799
|
+
browserSnapshotCommand.action(async () => printAgentResult("snapshot", [], browserSnapshotCommand.opts().panel));
|
|
800
|
+
const browserClickCommand = browserCommand.command("click").description("Click an element from the latest browser snapshot").argument("<target>", "Playwright element reference or selector").option("--panel <panel-id>", "Browser ID").option("--json", "emit machine-readable JSON");
|
|
801
|
+
browserClickCommand.action(async (target) => printAgentResult("click", [target], browserClickCommand.opts().panel));
|
|
802
|
+
const browserFillCommand = browserCommand.command("fill").description("Fill an editable element from the latest browser snapshot").argument("<target>", "Playwright element reference or selector").argument("<text>", "text to enter").option("--panel <panel-id>", "Browser ID").option("--json", "emit machine-readable JSON");
|
|
803
|
+
browserFillCommand.action(async (target, text) => printAgentResult("fill", [target, text], browserFillCommand.opts().panel));
|
|
804
|
+
const browserPressCommand = browserCommand.command("press").description("Press a key in the hosted page").argument("<key>", "Playwright key name").option("--panel <panel-id>", "Browser ID").option("--json", "emit machine-readable JSON");
|
|
805
|
+
browserPressCommand.action(async (key) => printAgentResult("press", [key], browserPressCommand.opts().panel));
|
|
806
|
+
const browserGotoCommand = browserCommand.command("goto").description("Navigate the hosted page").argument("<url>", "absolute HTTP or HTTPS URL").option("--panel <panel-id>", "Browser ID").option("--json", "emit machine-readable JSON");
|
|
807
|
+
browserGotoCommand.action(async (url) => printAgentResult("goto", [url], browserGotoCommand.opts().panel));
|
|
808
|
+
const browserConsoleCommand = browserCommand.command("console").description("List page console messages").argument("[level]", "minimum console level").option("--panel <panel-id>", "Browser ID").option("--json", "emit machine-readable JSON");
|
|
809
|
+
browserConsoleCommand.action(async (level) => printAgentResult("console", level ? [level] : [], browserConsoleCommand.opts().panel));
|
|
810
|
+
for (const [name, description, agentName] of [
|
|
811
|
+
[
|
|
812
|
+
"back",
|
|
813
|
+
"Go back in the hosted page",
|
|
814
|
+
"go-back"
|
|
815
|
+
],
|
|
816
|
+
[
|
|
817
|
+
"forward",
|
|
818
|
+
"Go forward in the hosted page",
|
|
819
|
+
"go-forward"
|
|
820
|
+
],
|
|
821
|
+
[
|
|
822
|
+
"reload",
|
|
823
|
+
"Reload the hosted page",
|
|
824
|
+
"reload"
|
|
825
|
+
],
|
|
826
|
+
[
|
|
827
|
+
"network",
|
|
828
|
+
"List page network requests",
|
|
829
|
+
"requests"
|
|
830
|
+
],
|
|
831
|
+
[
|
|
832
|
+
"screenshot",
|
|
833
|
+
"Capture a screenshot of the hosted page",
|
|
834
|
+
"screenshot"
|
|
835
|
+
]
|
|
836
|
+
]) {
|
|
837
|
+
const command = browserCommand.command(name).description(description).option("--panel <panel-id>", "Browser ID").option("--json", "emit machine-readable JSON");
|
|
838
|
+
command.action(async () => printAgentResult(agentName, [], command.opts().panel));
|
|
839
|
+
}
|
|
710
840
|
const installCommand = program.command("install").description("Install and configure a Treeport package").argument("<source>", "npm: source or local directory").option("-l, --local", "configure the registered project containing the current directory").option("--json", "emit machine-readable JSON");
|
|
711
841
|
installCommand.action(async (source) => {
|
|
712
842
|
const options = installCommand.opts();
|
|
@@ -858,7 +988,7 @@ async function main(args) {
|
|
|
858
988
|
});
|
|
859
989
|
const output = {
|
|
860
990
|
...result,
|
|
861
|
-
url:
|
|
991
|
+
url: panelUrl(worktree, result.panel.id)
|
|
862
992
|
};
|
|
863
993
|
print(output, () => `${result.reused ? "Reused" : "Opened"} ${result.panel.title} (${result.panel.id})\n${output.url}`);
|
|
864
994
|
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import "../../../dist-Crk_Xr82.js";
|
|
2
3
|
import { t as integrateShellLaunch } from "../../../shell-integration-Be_c91lw.js";
|
|
3
4
|
import fs from "node:fs/promises";
|
|
4
5
|
import path from "node:path";
|
|
@@ -13,6 +14,7 @@ const FORWARDED_SIGNALS = [
|
|
|
13
14
|
];
|
|
14
15
|
const launchSpecSchema = z.object({
|
|
15
16
|
argv: z.array(z.string()),
|
|
17
|
+
initialTitle: z.string().trim().min(1).max(120).optional(),
|
|
16
18
|
fallbackArgv: z.array(z.string()).optional(),
|
|
17
19
|
cwd: z.string(),
|
|
18
20
|
env: z.record(z.string(), z.string()),
|
|
@@ -159,30 +161,57 @@ async function runLaunchSpec(spec, dependencies = {}) {
|
|
|
159
161
|
...spec.env
|
|
160
162
|
};
|
|
161
163
|
const command = integrateShellLaunch(spec.argv, commandEnvironment, spec.shellIntegrationDir, spec.tmuxExecutable);
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
spawnProcess,
|
|
166
|
-
signalSource
|
|
167
|
-
});
|
|
168
|
-
if (result.spawnError) stderr.write(`Treeport launcher: ${safeDiagnostic(result.spawnError.message) || "spawn error"}\n`);
|
|
169
|
-
if (result.forwardedSignal && (!spec.fallbackArgv || result.forwardedSignal !== "SIGINT")) return 1;
|
|
170
|
-
if (spec.fallbackArgv) {
|
|
171
|
-
if (spec.tmuxExecutable && tmuxPane && spec.fallbackArgv[0]) await runChild([
|
|
164
|
+
const initialTitle = spec.initialTitle ? safeDiagnostic(spec.initialTitle) : "";
|
|
165
|
+
if (initialTitle && spec.tmuxExecutable && tmuxPane) {
|
|
166
|
+
if ((await runChild([
|
|
172
167
|
spec.tmuxExecutable,
|
|
173
168
|
"set-option",
|
|
174
169
|
"-p",
|
|
175
170
|
"-t",
|
|
176
171
|
tmuxPane,
|
|
177
172
|
"--",
|
|
178
|
-
"@treeport-
|
|
179
|
-
|
|
173
|
+
"@treeport-command",
|
|
174
|
+
initialTitle
|
|
180
175
|
], {
|
|
181
176
|
cwd: spec.cwd,
|
|
182
177
|
env: process.env,
|
|
183
178
|
spawnProcess,
|
|
184
179
|
signalSource
|
|
185
|
-
});
|
|
180
|
+
})).forwardedSignal) return 1;
|
|
181
|
+
}
|
|
182
|
+
const result = await runChild(command.argv, {
|
|
183
|
+
cwd: spec.cwd,
|
|
184
|
+
env: command.env,
|
|
185
|
+
spawnProcess,
|
|
186
|
+
signalSource
|
|
187
|
+
});
|
|
188
|
+
if (result.spawnError) stderr.write(`Treeport launcher: ${safeDiagnostic(result.spawnError.message) || "spawn error"}\n`);
|
|
189
|
+
if (result.forwardedSignal && (!spec.fallbackArgv || result.forwardedSignal !== "SIGINT")) return 1;
|
|
190
|
+
if (spec.fallbackArgv) {
|
|
191
|
+
if (spec.tmuxExecutable && tmuxPane && spec.fallbackArgv[0]) {
|
|
192
|
+
if ((await runChild([
|
|
193
|
+
spec.tmuxExecutable,
|
|
194
|
+
"set-option",
|
|
195
|
+
"-p",
|
|
196
|
+
"-u",
|
|
197
|
+
"-t",
|
|
198
|
+
tmuxPane,
|
|
199
|
+
"@treeport-command",
|
|
200
|
+
";",
|
|
201
|
+
"set-option",
|
|
202
|
+
"-p",
|
|
203
|
+
"-t",
|
|
204
|
+
tmuxPane,
|
|
205
|
+
"--",
|
|
206
|
+
"@treeport-fallback-shell",
|
|
207
|
+
Buffer.from(JSON.stringify(spec.fallbackArgv[0]), "utf8").toString("base64url")
|
|
208
|
+
], {
|
|
209
|
+
cwd: spec.cwd,
|
|
210
|
+
env: process.env,
|
|
211
|
+
spawnProcess,
|
|
212
|
+
signalSource
|
|
213
|
+
})).forwardedSignal) return 1;
|
|
214
|
+
}
|
|
186
215
|
const fallback = integrateShellLaunch(spec.fallbackArgv, commandEnvironment, spec.shellIntegrationDir, spec.tmuxExecutable);
|
|
187
216
|
const fallbackResult = await runChild(fallback.argv, {
|
|
188
217
|
cwd: spec.cwd,
|