@xfey/tutti 0.1.23 → 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.
@@ -6,8 +6,6 @@ export declare function renderCompletionPage(options: {
6
6
  joinTokenReusable?: boolean;
7
7
  projectId: string;
8
8
  workspaceRoot: string;
9
- relayUrl: string;
10
- branch?: string;
11
9
  tty?: boolean;
12
10
  hyperlinks?: boolean;
13
11
  }): string;
@@ -4,7 +4,7 @@ import { formatLaunchLifecycleResult, startLaunchProject, waitForForegroundHostS
4
4
  import { prepareLaunchProject } from "./launch.js";
5
5
  import { spawnDetachedHost, waitForManagedHostReady } from "./managed-host.js";
6
6
  import { resolveLaunchWorkspacePath } from "./project-resolver.js";
7
- import { formatJoinLinkValidity, formatTerminalLink, renderTerminalQr } from "./terminal-qr.js";
7
+ import { formatJoinLinkValidity, formatTerminalLink } from "./terminal-qr.js";
8
8
  import { renderTuttiTerminalLogo } from "./terminal-logo.js";
9
9
  import { runProviderSetupTui } from "./provider-tui.js";
10
10
  import { LaunchError } from "./errors.js";
@@ -25,69 +25,69 @@ export async function confirmFromTty(request) {
25
25
  readline.close();
26
26
  }
27
27
  }
28
- async function waitForEnter() {
29
- if (!process.stdin.isTTY || !process.stdout.isTTY) {
30
- return;
31
- }
32
- const readline = createInterface({
33
- input: process.stdin,
34
- output: process.stdout,
35
- });
36
- try {
37
- await readline.question("");
38
- }
39
- finally {
40
- readline.close();
41
- }
42
- }
43
28
  function formatProjectLabel(workspaceRoot, projectId) {
44
29
  return basename(workspaceRoot) || projectId;
45
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)}`;
43
+ }
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)}`;
59
+ }
60
+ return stripped.length;
61
+ }
46
62
  function borderedBlock(lines) {
47
- const width = Math.max(...lines.map((line) => line.length));
63
+ const width = Math.max(...lines.map(visibleTerminalLength));
48
64
  const top = `╭${"─".repeat(width + 2)}╮`;
49
65
  const bottom = `╰${"─".repeat(width + 2)}╯`;
50
- const body = lines.map((line) => `│ ${line.padEnd(width, " ")} │`);
66
+ const body = lines.map((line) => `│ ${line}${" ".repeat(width - visibleTerminalLength(line))} │`);
51
67
  return [top, ...body, bottom].join("\n");
52
68
  }
53
69
  export function renderCompletionPage(options) {
54
70
  const isTty = options.tty ?? process.stdout.isTTY === true;
55
71
  const projectLabel = formatProjectLabel(options.workspaceRoot, options.projectId);
56
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
+ });
57
82
  const summaryLines = [
58
83
  "Tutti is hosting this project.",
59
84
  `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.",
85
+ `Join URL: ${joinUrl}`,
86
+ joinValidity,
70
87
  ];
71
88
  return [
72
89
  ...(isTty ? [CLEAR, renderTuttiTerminalLogo({ tty: true }), ""] : []),
73
90
  isTty ? borderedBlock(summaryLines) : summaryLines.join("\n"),
74
- "",
75
- `Join URL: ${joinUrl}`,
76
- formatJoinLinkValidity({
77
- ...(options.joinTokenExpiresAt === undefined
78
- ? {}
79
- : { expiresAt: options.joinTokenExpiresAt }),
80
- ...(options.joinTokenReusable === undefined
81
- ? {}
82
- : { reusable: options.joinTokenReusable }),
83
- }),
84
- "",
85
- "QR code:",
86
- renderTerminalQr(options.joinUrl),
87
- "",
88
- ...nextSteps,
89
- "",
90
- isTty ? "[Enter] close this view. The host keeps running." : "The host keeps running.",
91
91
  ].join("\n");
92
92
  }
93
93
  async function ensureProviderConfigured(preparation) {
@@ -150,8 +150,6 @@ export async function runBackgroundLaunchCommand(options) {
150
150
  joinUrl: ready.join_url,
151
151
  projectId: preparation.project_id,
152
152
  workspaceRoot: preparation.workspace_root,
153
- relayUrl: preparation.relay_url,
154
- branch: preparation.branch,
155
153
  tty: process.stdout.isTTY === true,
156
154
  hyperlinks: process.stdout.isTTY === true,
157
155
  ...(ready.join_token_expires_at === undefined
@@ -161,6 +159,5 @@ export async function runBackgroundLaunchCommand(options) {
161
159
  ? {}
162
160
  : { joinTokenReusable: ready.join_token_reusable }),
163
161
  })}\n`);
164
- await waitForEnter();
165
162
  }
166
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
  }),
@@ -6,5 +6,6 @@ export declare function formatJoinLinkValidity(options: {
6
6
  expiresAt?: string;
7
7
  reusable?: boolean;
8
8
  now?: Date;
9
+ includeRemaining?: boolean;
9
10
  }): string;
10
11
  //# sourceMappingURL=terminal-qr.d.ts.map
@@ -29,6 +29,9 @@ export function formatJoinLinkValidity(options) {
29
29
  if (options.expiresAt !== undefined) {
30
30
  const expiresAt = new Date(options.expiresAt);
31
31
  if (!Number.isNaN(expiresAt.getTime())) {
32
+ if (options.includeRemaining === false) {
33
+ return `Join link valid until: ${options.expiresAt}`;
34
+ }
32
35
  const now = options.now ?? new Date();
33
36
  const remainingMs = expiresAt.getTime() - now.getTime();
34
37
  const suffix = remainingMs <= 0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xfey/tutti",
3
- "version": "0.1.23",
3
+ "version": "0.1.24",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",