@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}: ${terminalLink(value, options)}`;
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 renderCompletionPage(options) {
43
- const isTty = process.stdout.isTTY === true;
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
- "Tutti is running in the background.",
73
+ isTty ? borderedBlock(summaryLines) : summaryLines.join("\n"),
47
74
  "",
48
- `Join URL: ${options.joinUrl}`,
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
- "[Enter] to continue",
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,3 +1,6 @@
1
+ export declare function formatTerminalLink(value: string, options?: {
2
+ hyperlinks?: boolean;
3
+ }): string;
1
4
  export declare function renderTerminalQr(input: string): string;
2
5
  export declare function formatJoinLinkValidity(options: {
3
6
  expiresAt?: string;
@@ -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 = "";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xfey/tutti",
3
- "version": "0.1.22",
3
+ "version": "0.1.23",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",