@xfey/tutti 0.1.22 → 0.1.24

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,14 @@
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
+ tty?: boolean;
10
+ hyperlinks?: boolean;
11
+ }): string;
3
12
  export declare function runForegroundLaunchCommand(options: {
4
13
  target?: string;
5
14
  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 } 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";
@@ -24,40 +25,69 @@ export async function confirmFromTty(request) {
24
25
  readline.close();
25
26
  }
26
27
  }
27
- async function waitForEnter() {
28
- if (!process.stdin.isTTY || !process.stdout.isTTY) {
29
- return;
28
+ function formatProjectLabel(workspaceRoot, projectId) {
29
+ return basename(workspaceRoot) || projectId;
30
+ }
31
+ function visibleTerminalLength(line) {
32
+ let stripped = line;
33
+ while (true) {
34
+ const start = stripped.indexOf("\u001B]8;;");
35
+ if (start < 0) {
36
+ break;
37
+ }
38
+ const end = stripped.indexOf("\u001B\\", start);
39
+ if (end < 0) {
40
+ break;
41
+ }
42
+ stripped = `${stripped.slice(0, start)}${stripped.slice(end + 2)}`;
30
43
  }
31
- const readline = createInterface({
32
- input: process.stdin,
33
- output: process.stdout,
34
- });
35
- try {
36
- await readline.question("");
37
- }
38
- finally {
39
- readline.close();
44
+ while (true) {
45
+ const start = stripped.indexOf("\u001B[");
46
+ if (start < 0) {
47
+ break;
48
+ }
49
+ const end = Array.from(stripped.slice(start + 2)).findIndex((character) => {
50
+ const codePoint = character.codePointAt(0);
51
+ return (codePoint !== undefined &&
52
+ ((codePoint >= 0x41 && codePoint <= 0x5a) ||
53
+ (codePoint >= 0x61 && codePoint <= 0x7a)));
54
+ });
55
+ if (end < 0) {
56
+ break;
57
+ }
58
+ stripped = `${stripped.slice(0, start)}${stripped.slice(start + 3 + end)}`;
40
59
  }
60
+ return stripped.length;
41
61
  }
42
- function renderCompletionPage(options) {
43
- const isTty = process.stdout.isTTY === true;
62
+ function borderedBlock(lines) {
63
+ const width = Math.max(...lines.map(visibleTerminalLength));
64
+ const top = `╭${"─".repeat(width + 2)}╮`;
65
+ const bottom = `╰${"─".repeat(width + 2)}╯`;
66
+ const body = lines.map((line) => `│ ${line}${" ".repeat(width - visibleTerminalLength(line))} │`);
67
+ return [top, ...body, bottom].join("\n");
68
+ }
69
+ export function renderCompletionPage(options) {
70
+ const isTty = options.tty ?? process.stdout.isTTY === true;
71
+ const projectLabel = formatProjectLabel(options.workspaceRoot, options.projectId);
72
+ const joinUrl = formatTerminalLink(options.joinUrl, { hyperlinks: options.hyperlinks === true });
73
+ const joinValidity = formatJoinLinkValidity({
74
+ ...(options.joinTokenExpiresAt === undefined
75
+ ? {}
76
+ : { expiresAt: options.joinTokenExpiresAt }),
77
+ ...(options.joinTokenReusable === undefined
78
+ ? {}
79
+ : { reusable: options.joinTokenReusable }),
80
+ includeRemaining: false,
81
+ });
82
+ const summaryLines = [
83
+ "Tutti is hosting this project.",
84
+ `Project: ${projectLabel}`,
85
+ `Join URL: ${joinUrl}`,
86
+ joinValidity,
87
+ ];
44
88
  return [
45
89
  ...(isTty ? [CLEAR, renderTuttiTerminalLogo({ tty: true }), ""] : []),
46
- "Tutti is running in the background.",
47
- "",
48
- `Join URL: ${options.joinUrl}`,
49
- formatJoinLinkValidity({
50
- ...(options.joinTokenExpiresAt === undefined
51
- ? {}
52
- : { expiresAt: options.joinTokenExpiresAt }),
53
- ...(options.joinTokenReusable === undefined
54
- ? {}
55
- : { reusable: options.joinTokenReusable }),
56
- }),
57
- "",
58
- renderTerminalQr(options.joinUrl),
59
- "",
60
- "[Enter] to continue",
90
+ isTty ? borderedBlock(summaryLines) : summaryLines.join("\n"),
61
91
  ].join("\n");
62
92
  }
63
93
  async function ensureProviderConfigured(preparation) {
@@ -118,6 +148,10 @@ 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
+ tty: process.stdout.isTTY === true,
154
+ hyperlinks: process.stdout.isTTY === true,
121
155
  ...(ready.join_token_expires_at === undefined
122
156
  ? {}
123
157
  : { joinTokenExpiresAt: ready.join_token_expires_at }),
@@ -125,6 +159,5 @@ export async function runBackgroundLaunchCommand(options) {
125
159
  ? {}
126
160
  : { joinTokenReusable: ready.join_token_reusable }),
127
161
  })}\n`);
128
- await waitForEnter();
129
162
  }
130
163
  //# sourceMappingURL=launch-command.js.map
@@ -9,13 +9,21 @@ const SHOW_CURSOR = "\u001B[?25h";
9
9
  const BLINK_ON = "\u001B[5m";
10
10
  const BLINK_OFF = "\u001B[25m";
11
11
  const CLEAR = "\u001B[2J\u001B[H";
12
- const FIELD_UNDERLINE = "------------------------------------------------------------";
12
+ const FIELD_BOX_WIDTH = 60;
13
13
  function providerSetupCancelled() {
14
14
  return new LaunchError("provider_setup_cancelled", "Provider setup cancelled.", "Run the command again when you are ready.");
15
15
  }
16
16
  function renderFieldInput(options) {
17
17
  const value = options.secret && options.value.length > 0 ? "*".repeat(options.value.length) : options.value;
18
- return [options.label, `${value}${BLINK_ON}_${BLINK_OFF}`, FIELD_UNDERLINE];
18
+ const visibleValue = value.length >= FIELD_BOX_WIDTH ? `<${value.slice(-(FIELD_BOX_WIDTH - 2))}` : value;
19
+ const cursor = `${BLINK_ON}_${BLINK_OFF}`;
20
+ const plainLength = visibleValue.length + 1;
21
+ const padding = Math.max(0, FIELD_BOX_WIDTH - plainLength);
22
+ return [
23
+ `+${"-".repeat(FIELD_BOX_WIDTH + 2)}+`,
24
+ `| ${visibleValue}${cursor}${" ".repeat(padding)} |`,
25
+ `+${"-".repeat(FIELD_BOX_WIDTH + 2)}+`,
26
+ ];
19
27
  }
20
28
  function renderProviderField(state) {
21
29
  if (state.step === "base-url") {
@@ -24,7 +32,6 @@ function renderProviderField(state) {
24
32
  "OpenAI-compatible Responses API base URL; must expose POST /responses.",
25
33
  "",
26
34
  ...renderFieldInput({
27
- label: "Base URL",
28
35
  value: state.baseUrl,
29
36
  }),
30
37
  "",
@@ -36,7 +43,6 @@ function renderProviderField(state) {
36
43
  "Stored only in the machine-local Tutti credential store.",
37
44
  "",
38
45
  ...renderFieldInput({
39
- label: "API Key",
40
46
  value: state.apiKey,
41
47
  secret: true,
42
48
  }),
@@ -1,7 +1,11 @@
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;
4
7
  reusable?: boolean;
5
8
  now?: Date;
9
+ includeRemaining?: boolean;
6
10
  }): string;
7
11
  //# sourceMappingURL=terminal-qr.d.ts.map
@@ -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 = "";
@@ -12,6 +29,9 @@ export function formatJoinLinkValidity(options) {
12
29
  if (options.expiresAt !== undefined) {
13
30
  const expiresAt = new Date(options.expiresAt);
14
31
  if (!Number.isNaN(expiresAt.getTime())) {
32
+ if (options.includeRemaining === false) {
33
+ return `Join link valid until: ${options.expiresAt}`;
34
+ }
15
35
  const now = options.now ?? new Date();
16
36
  const remainingMs = expiresAt.getTime() - now.getTime();
17
37
  const suffix = remainingMs <= 0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xfey/tutti",
3
- "version": "0.1.22",
3
+ "version": "0.1.24",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",