@xfey/tutti 0.1.22 → 0.1.23
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.
|
@@ -10,28 +10,11 @@ import { HOST_SERVER_VERSION, startForegroundHostServer, } from "./host-server-r
|
|
|
10
10
|
import { deleteMachineRuntimeEndpoint, readHostRegistrationSecret, readMachineProjectBinding, readMachineRuntimeEndpoint, } from "./machine-local.js";
|
|
11
11
|
import { prepareLaunchProject, resolveLaunchLocalContext, } from "./launch.js";
|
|
12
12
|
import { registerHostWithRelay, relayErrorToLaunchError, } from "./relay-registration.js";
|
|
13
|
-
import { formatJoinLinkValidity } from "./terminal-qr.js";
|
|
13
|
+
import { formatJoinLinkValidity, formatTerminalLink } from "./terminal-qr.js";
|
|
14
14
|
export { createRuntimeEndpointProbe } from "./host-runtime-endpoint.js";
|
|
15
15
|
const DEFAULT_SHUTDOWN_WAIT_MS = 2_000;
|
|
16
|
-
const OSC8_START = "\u001B]8;;";
|
|
17
|
-
const OSC8_END = "\u001B]8;;\u001B\\";
|
|
18
|
-
function isTerminalSafeHttpUrl(value) {
|
|
19
|
-
if (!value.startsWith("http://") && !value.startsWith("https://")) {
|
|
20
|
-
return false;
|
|
21
|
-
}
|
|
22
|
-
return Array.from(value).every((character) => {
|
|
23
|
-
const codePoint = character.codePointAt(0);
|
|
24
|
-
return codePoint !== undefined && codePoint > 0x1f && codePoint !== 0x7f;
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
function terminalLink(value, options) {
|
|
28
|
-
if (options.hyperlinks !== true || !isTerminalSafeHttpUrl(value)) {
|
|
29
|
-
return value;
|
|
30
|
-
}
|
|
31
|
-
return `${OSC8_START}${value}\u001B\\${value}${OSC8_END}`;
|
|
32
|
-
}
|
|
33
16
|
function urlLine(label, value, options) {
|
|
34
|
-
return `${label}: ${
|
|
17
|
+
return `${label}: ${formatTerminalLink(value, options)}`;
|
|
35
18
|
}
|
|
36
19
|
function endpointWithoutToken(endpoint) {
|
|
37
20
|
return {
|
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
import type { LaunchConfirmationRequest } from "./git-bootstrap.js";
|
|
2
2
|
export declare function confirmFromTty(request: LaunchConfirmationRequest): Promise<boolean>;
|
|
3
|
+
export declare function renderCompletionPage(options: {
|
|
4
|
+
joinUrl: string;
|
|
5
|
+
joinTokenExpiresAt?: string;
|
|
6
|
+
joinTokenReusable?: boolean;
|
|
7
|
+
projectId: string;
|
|
8
|
+
workspaceRoot: string;
|
|
9
|
+
relayUrl: string;
|
|
10
|
+
branch?: string;
|
|
11
|
+
tty?: boolean;
|
|
12
|
+
hyperlinks?: boolean;
|
|
13
|
+
}): string;
|
|
3
14
|
export declare function runForegroundLaunchCommand(options: {
|
|
4
15
|
target?: string;
|
|
5
16
|
yes: boolean;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { createInterface } from "node:readline/promises";
|
|
2
|
+
import { basename } from "node:path";
|
|
2
3
|
import { formatLaunchLifecycleResult, startLaunchProject, waitForForegroundHostShutdown } from "./host-lifecycle.js";
|
|
3
4
|
import { prepareLaunchProject } from "./launch.js";
|
|
4
5
|
import { spawnDetachedHost, waitForManagedHostReady } from "./managed-host.js";
|
|
5
6
|
import { resolveLaunchWorkspacePath } from "./project-resolver.js";
|
|
6
|
-
import { formatJoinLinkValidity, renderTerminalQr } from "./terminal-qr.js";
|
|
7
|
+
import { formatJoinLinkValidity, formatTerminalLink, renderTerminalQr } from "./terminal-qr.js";
|
|
7
8
|
import { renderTuttiTerminalLogo } from "./terminal-logo.js";
|
|
8
9
|
import { runProviderSetupTui } from "./provider-tui.js";
|
|
9
10
|
import { LaunchError } from "./errors.js";
|
|
@@ -39,13 +40,39 @@ async function waitForEnter() {
|
|
|
39
40
|
readline.close();
|
|
40
41
|
}
|
|
41
42
|
}
|
|
42
|
-
function
|
|
43
|
-
|
|
43
|
+
function formatProjectLabel(workspaceRoot, projectId) {
|
|
44
|
+
return basename(workspaceRoot) || projectId;
|
|
45
|
+
}
|
|
46
|
+
function borderedBlock(lines) {
|
|
47
|
+
const width = Math.max(...lines.map((line) => line.length));
|
|
48
|
+
const top = `╭${"─".repeat(width + 2)}╮`;
|
|
49
|
+
const bottom = `╰${"─".repeat(width + 2)}╯`;
|
|
50
|
+
const body = lines.map((line) => `│ ${line.padEnd(width, " ")} │`);
|
|
51
|
+
return [top, ...body, bottom].join("\n");
|
|
52
|
+
}
|
|
53
|
+
export function renderCompletionPage(options) {
|
|
54
|
+
const isTty = options.tty ?? process.stdout.isTTY === true;
|
|
55
|
+
const projectLabel = formatProjectLabel(options.workspaceRoot, options.projectId);
|
|
56
|
+
const joinUrl = formatTerminalLink(options.joinUrl, { hyperlinks: options.hyperlinks === true });
|
|
57
|
+
const summaryLines = [
|
|
58
|
+
"Tutti is hosting this project.",
|
|
59
|
+
`Project: ${projectLabel}`,
|
|
60
|
+
"Status: background host ready",
|
|
61
|
+
`Branch: ${options.branch ?? "tutti/mainline"}`,
|
|
62
|
+
`Relay: ${options.relayUrl}`,
|
|
63
|
+
];
|
|
64
|
+
const nextSteps = [
|
|
65
|
+
"Next:",
|
|
66
|
+
" Open the join URL or scan the QR code.",
|
|
67
|
+
" `tutti invite` rotates a fresh link.",
|
|
68
|
+
" `tutti ps` shows running hosts.",
|
|
69
|
+
" `tutti stop` stops this host.",
|
|
70
|
+
];
|
|
44
71
|
return [
|
|
45
72
|
...(isTty ? [CLEAR, renderTuttiTerminalLogo({ tty: true }), ""] : []),
|
|
46
|
-
|
|
73
|
+
isTty ? borderedBlock(summaryLines) : summaryLines.join("\n"),
|
|
47
74
|
"",
|
|
48
|
-
`Join URL: ${
|
|
75
|
+
`Join URL: ${joinUrl}`,
|
|
49
76
|
formatJoinLinkValidity({
|
|
50
77
|
...(options.joinTokenExpiresAt === undefined
|
|
51
78
|
? {}
|
|
@@ -55,9 +82,12 @@ function renderCompletionPage(options) {
|
|
|
55
82
|
: { reusable: options.joinTokenReusable }),
|
|
56
83
|
}),
|
|
57
84
|
"",
|
|
85
|
+
"QR code:",
|
|
58
86
|
renderTerminalQr(options.joinUrl),
|
|
59
87
|
"",
|
|
60
|
-
|
|
88
|
+
...nextSteps,
|
|
89
|
+
"",
|
|
90
|
+
isTty ? "[Enter] close this view. The host keeps running." : "The host keeps running.",
|
|
61
91
|
].join("\n");
|
|
62
92
|
}
|
|
63
93
|
async function ensureProviderConfigured(preparation) {
|
|
@@ -118,6 +148,12 @@ export async function runBackgroundLaunchCommand(options) {
|
|
|
118
148
|
}
|
|
119
149
|
process.stdout.write(`${renderCompletionPage({
|
|
120
150
|
joinUrl: ready.join_url,
|
|
151
|
+
projectId: preparation.project_id,
|
|
152
|
+
workspaceRoot: preparation.workspace_root,
|
|
153
|
+
relayUrl: preparation.relay_url,
|
|
154
|
+
branch: preparation.branch,
|
|
155
|
+
tty: process.stdout.isTTY === true,
|
|
156
|
+
hyperlinks: process.stdout.isTTY === true,
|
|
121
157
|
...(ready.join_token_expires_at === undefined
|
|
122
158
|
? {}
|
|
123
159
|
: { joinTokenExpiresAt: ready.join_token_expires_at }),
|
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
const require = createRequire(import.meta.url);
|
|
3
|
+
const OSC8_START = "\u001B]8;;";
|
|
4
|
+
const OSC8_END = "\u001B]8;;\u001B\\";
|
|
5
|
+
function isTerminalSafeHttpUrl(value) {
|
|
6
|
+
if (!value.startsWith("http://") && !value.startsWith("https://")) {
|
|
7
|
+
return false;
|
|
8
|
+
}
|
|
9
|
+
return Array.from(value).every((character) => {
|
|
10
|
+
const codePoint = character.codePointAt(0);
|
|
11
|
+
return codePoint !== undefined && codePoint > 0x1f && codePoint !== 0x7f;
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
export function formatTerminalLink(value, options = {}) {
|
|
15
|
+
if (options.hyperlinks !== true || !isTerminalSafeHttpUrl(value)) {
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
18
|
+
return `${OSC8_START}${value}\u001B\\${value}${OSC8_END}`;
|
|
19
|
+
}
|
|
3
20
|
export function renderTerminalQr(input) {
|
|
4
21
|
const qrcode = require("qrcode-terminal");
|
|
5
22
|
let output = "";
|