@xfey/tutti 0.1.6 → 0.1.8

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.
@@ -0,0 +1,308 @@
1
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
2
+ import { join, resolve } from "node:path";
3
+ import { emitKeypressEvents } from "node:readline";
4
+ import { ID_PREFIXES, isPrefixedId } from "@tutti/shared/ids";
5
+ import { redactText } from "@tutti/shared/utils";
6
+ import { createRuntimeEndpointProbe } from "./host-runtime-endpoint.js";
7
+ import { getHostLogFilePath, getMachineRuntimeEndpointPath, getProjectLocalStoreRoot, readMachineProjectBinding, readMachineRuntimeEndpoint, } from "./machine-local.js";
8
+ import { readHostLocalLaunchStatus, readHostLocalProject, readHostLocalProviderConfig, requestHostLocalShutdown, rotateHostLocalInvite, } from "./local-control-client.js";
9
+ import { resolveExistingProjectContext } from "./project-resolver.js";
10
+ import { renderTerminalQr } from "./terminal-qr.js";
11
+ import { resolveTuttiHome } from "../../providers/openai/index.js";
12
+ function resolveProject(workspacePath) {
13
+ return resolveExistingProjectContext({
14
+ ...(workspacePath === undefined ? {} : { workspacePath }),
15
+ });
16
+ }
17
+ function projectIdsFromTuttiHome(tuttiHome) {
18
+ const projectsRoot = join(tuttiHome, "projects");
19
+ if (!existsSync(projectsRoot)) {
20
+ return [];
21
+ }
22
+ return readdirSync(projectsRoot, { withFileTypes: true })
23
+ .filter((entry) => entry.isDirectory() && isPrefixedId(entry.name, ID_PREFIXES.project))
24
+ .map((entry) => entry.name)
25
+ .sort();
26
+ }
27
+ function readEndpointFile(tuttiHome, projectId) {
28
+ try {
29
+ return readMachineRuntimeEndpoint(tuttiHome, projectId);
30
+ }
31
+ catch {
32
+ return null;
33
+ }
34
+ }
35
+ export async function listRuntimeProjects(options = {}) {
36
+ const cwd = options.cwd ?? process.cwd();
37
+ const tuttiHome = resolveTuttiHome(options.env?.TUTTI_HOME, cwd);
38
+ const probe = createRuntimeEndpointProbe(options.fetchImpl ?? fetch);
39
+ const rows = [];
40
+ for (const projectId of projectIdsFromTuttiHome(tuttiHome)) {
41
+ const endpoint = readEndpointFile(tuttiHome, projectId);
42
+ if (endpoint === null) {
43
+ continue;
44
+ }
45
+ const binding = readMachineProjectBinding(tuttiHome, projectId);
46
+ const health = await probe(endpoint);
47
+ if (health.kind === "stale") {
48
+ rows.push({
49
+ project_id: projectId,
50
+ status: "stale",
51
+ display_name: binding?.workspace_root === undefined ? projectId : binding.workspace_root.split(/[\\/]/u).pop() ?? projectId,
52
+ workspace_root: binding?.workspace_root ?? endpoint.workspace_root,
53
+ endpoint: endpoint.base_url,
54
+ });
55
+ continue;
56
+ }
57
+ const fetchOption = options.fetchImpl === undefined ? {} : { fetchImpl: options.fetchImpl };
58
+ const [project, provider, launchStatus] = await Promise.allSettled([
59
+ readHostLocalProject({ endpoint, ...fetchOption }),
60
+ readHostLocalProviderConfig({ endpoint, ...fetchOption }),
61
+ readHostLocalLaunchStatus({ endpoint, ...fetchOption }),
62
+ ]);
63
+ const row = {
64
+ project_id: projectId,
65
+ status: "online",
66
+ display_name: project.status === "fulfilled" ? project.value.display_name : binding?.workspace_root.split(/[\\/]/u).pop() ?? projectId,
67
+ workspace_root: binding?.workspace_root ?? endpoint.workspace_root,
68
+ endpoint: endpoint.base_url,
69
+ };
70
+ if (provider.status === "fulfilled") {
71
+ row.provider_status = provider.value.status;
72
+ }
73
+ if (launchStatus.status === "fulfilled" && launchStatus.value.relay?.relay_project_ref !== undefined) {
74
+ row.relay_project_ref = launchStatus.value.relay.relay_project_ref;
75
+ }
76
+ if (launchStatus.status === "fulfilled" && launchStatus.value.relay?.join_url !== undefined) {
77
+ row.join_url = launchStatus.value.relay.join_url;
78
+ }
79
+ rows.push(row);
80
+ }
81
+ return rows;
82
+ }
83
+ function formatTable(rows) {
84
+ if (rows.length === 0) {
85
+ return "No Tutti host processes found.";
86
+ }
87
+ const header = ["STATUS", "PROJECT", "PROVIDER", "WORKSPACE"];
88
+ const data = rows.map((row) => [
89
+ row.status,
90
+ row.display_name,
91
+ row.provider_status ?? "-",
92
+ redactText(row.workspace_root),
93
+ ]);
94
+ const widths = header.map((label, index) => Math.max(label.length, ...data.map((row) => row[index]?.length ?? 0)));
95
+ const line = (columns) => columns.map((column, index) => column.padEnd(widths[index] ?? column.length)).join(" ");
96
+ return [line(header), line(widths.map((width) => "-".repeat(width))), ...data.map(line)].join("\n");
97
+ }
98
+ const CLEAR = "\u001B[2J\u001B[H";
99
+ const HIDE_CURSOR = "\u001B[?25l";
100
+ const SHOW_CURSOR = "\u001B[?25h";
101
+ function keypress(stdin) {
102
+ return new Promise((resolveKeypress) => {
103
+ stdin.once("keypress", (character, key) => {
104
+ resolveKeypress({
105
+ ...(character === undefined ? {} : { character }),
106
+ key,
107
+ });
108
+ });
109
+ });
110
+ }
111
+ function selectedProject(rows, selectedIndex) {
112
+ return rows[selectedIndex];
113
+ }
114
+ function renderManager(options) {
115
+ const lines = [
116
+ CLEAR,
117
+ HIDE_CURSOR,
118
+ "Tutti Projects",
119
+ "",
120
+ options.rows.length === 0
121
+ ? "No Tutti host processes found."
122
+ : "Up/Down select, Enter/i invite, l logs, s stop, r refresh, q quit.",
123
+ "",
124
+ ];
125
+ if (options.rows.length > 0) {
126
+ const header = ["", "STATUS", "PROJECT", "PROVIDER", "WORKSPACE"];
127
+ const data = options.rows.map((row, index) => [
128
+ index === options.selectedIndex ? ">" : " ",
129
+ row.status,
130
+ row.display_name,
131
+ row.provider_status ?? "-",
132
+ redactText(row.workspace_root),
133
+ ]);
134
+ const widths = header.map((label, index) => Math.max(label.length, ...data.map((row) => row[index]?.length ?? 0)));
135
+ const line = (columns) => columns.map((column, index) => column.padEnd(widths[index] ?? column.length)).join(" ");
136
+ lines.push(line(header), line(widths.map((width) => "-".repeat(width))), ...data.map(line), "");
137
+ }
138
+ if (options.message !== undefined) {
139
+ lines.push(options.message, "");
140
+ }
141
+ if (options.detail !== undefined) {
142
+ lines.push(options.detail, "");
143
+ }
144
+ return lines.join("\n");
145
+ }
146
+ export async function runPsCommand() {
147
+ return formatTable(await listRuntimeProjects());
148
+ }
149
+ export async function runPsManageCommand(options = {}) {
150
+ const stdin = options.stdin ?? process.stdin;
151
+ const stdout = options.stdout ?? process.stdout;
152
+ if (!stdin.isTTY || !stdout.isTTY) {
153
+ stdout.write(`${await runPsCommand()}\n`);
154
+ return;
155
+ }
156
+ emitKeypressEvents(stdin);
157
+ stdin.setRawMode(true);
158
+ let rows = await listRuntimeProjects();
159
+ let selectedIndex = 0;
160
+ let message;
161
+ let detail;
162
+ const render = () => {
163
+ stdout.write(renderManager({
164
+ rows,
165
+ selectedIndex,
166
+ ...(message === undefined ? {} : { message }),
167
+ ...(detail === undefined ? {} : { detail }),
168
+ }));
169
+ };
170
+ try {
171
+ render();
172
+ while (true) {
173
+ const input = await keypress(stdin);
174
+ if (input.key.ctrl === true && input.key.name === "c") {
175
+ return;
176
+ }
177
+ if (input.key.name === "escape" || input.character === "q") {
178
+ return;
179
+ }
180
+ if (input.key.name === "up" && rows.length > 0) {
181
+ selectedIndex = selectedIndex === 0 ? rows.length - 1 : selectedIndex - 1;
182
+ message = undefined;
183
+ detail = undefined;
184
+ render();
185
+ continue;
186
+ }
187
+ if (input.key.name === "down" && rows.length > 0) {
188
+ selectedIndex = selectedIndex === rows.length - 1 ? 0 : selectedIndex + 1;
189
+ message = undefined;
190
+ detail = undefined;
191
+ render();
192
+ continue;
193
+ }
194
+ if (input.character === "r") {
195
+ rows = await listRuntimeProjects();
196
+ selectedIndex = Math.min(selectedIndex, Math.max(rows.length - 1, 0));
197
+ message = "Refreshed.";
198
+ detail = undefined;
199
+ render();
200
+ continue;
201
+ }
202
+ const project = selectedProject(rows, selectedIndex);
203
+ if (project === undefined) {
204
+ message = "No project selected.";
205
+ detail = undefined;
206
+ render();
207
+ continue;
208
+ }
209
+ if (input.key.name === "return" || input.character === "i") {
210
+ try {
211
+ detail = await runInviteCommand(project.workspace_root);
212
+ message = `Invite refreshed for ${project.display_name}.`;
213
+ }
214
+ catch (error) {
215
+ message = error instanceof Error ? error.message : "Invite refresh failed.";
216
+ detail = undefined;
217
+ }
218
+ render();
219
+ continue;
220
+ }
221
+ if (input.character === "l") {
222
+ detail = runLogsCommand(project.workspace_root, 40);
223
+ message = `Showing last 40 host log lines for ${project.display_name}.`;
224
+ render();
225
+ continue;
226
+ }
227
+ if (input.character === "s") {
228
+ try {
229
+ message = await runStopCommand(project.workspace_root);
230
+ rows = await listRuntimeProjects();
231
+ selectedIndex = Math.min(selectedIndex, Math.max(rows.length - 1, 0));
232
+ detail = undefined;
233
+ }
234
+ catch (error) {
235
+ message = error instanceof Error ? error.message : "Stop failed.";
236
+ detail = undefined;
237
+ }
238
+ render();
239
+ continue;
240
+ }
241
+ message = "Unsupported key.";
242
+ detail = undefined;
243
+ render();
244
+ }
245
+ }
246
+ finally {
247
+ stdin.setRawMode(false);
248
+ stdout.write(SHOW_CURSOR);
249
+ }
250
+ }
251
+ export async function runStopCommand(workspacePath) {
252
+ const project = resolveProject(workspacePath);
253
+ const endpoint = project.runtime_endpoint;
254
+ if (endpoint === null) {
255
+ return "Tutti host is not running for this project.";
256
+ }
257
+ await requestHostLocalShutdown({ endpoint });
258
+ return `Stopped ${project.display_name}.`;
259
+ }
260
+ export async function runInviteCommand(workspacePath) {
261
+ const project = resolveProject(workspacePath);
262
+ const endpoint = project.runtime_endpoint;
263
+ if (endpoint === null) {
264
+ throw new Error("Tutti host is not running for this project.");
265
+ }
266
+ const status = await rotateHostLocalInvite({ endpoint });
267
+ const joinUrl = status.relay?.join_url;
268
+ if (joinUrl === undefined) {
269
+ throw new Error("Relay did not return a visible join URL.");
270
+ }
271
+ return [`Join URL: ${joinUrl}`, "", renderTerminalQr(joinUrl)].join("\n");
272
+ }
273
+ export function runProviderStatusCommand(workspacePath) {
274
+ const project = resolveProject(workspacePath);
275
+ return [
276
+ `Project: ${project.display_name}`,
277
+ `Provider: ${project.provider_config.status}`,
278
+ ...(project.provider_config.status === "configured"
279
+ ? [
280
+ `Model: ${project.provider_config.default_model}`,
281
+ `Key: ${project.provider_config.redacted_key}`,
282
+ `Validated: ${project.provider_config.validated_at}`,
283
+ ]
284
+ : []),
285
+ ].join("\n");
286
+ }
287
+ export function runLogsCommand(workspacePath, tailLines = 120) {
288
+ const project = resolveProject(workspacePath);
289
+ const logPath = getHostLogFilePath(project.tutti_home);
290
+ if (!existsSync(logPath)) {
291
+ return `No host log exists yet at ${redactText(logPath)}.`;
292
+ }
293
+ const lines = readFileSync(logPath, "utf8").split(/\r?\n/u).filter(Boolean).slice(-tailLines);
294
+ return lines.length === 0 ? "Host log is empty." : lines.join("\n");
295
+ }
296
+ export function readRuntimeEndpointForProject(tuttiHome, projectId) {
297
+ return readEndpointFile(tuttiHome, projectId);
298
+ }
299
+ export function runtimeEndpointPathForProject(tuttiHome, projectId) {
300
+ return getMachineRuntimeEndpointPath(tuttiHome, projectId);
301
+ }
302
+ export function projectLocalStoreRoot(tuttiHome, projectId) {
303
+ return getProjectLocalStoreRoot(tuttiHome, projectId);
304
+ }
305
+ export function resolveWorkspacePath(value) {
306
+ return resolve(process.cwd(), value ?? ".");
307
+ }
308
+ //# sourceMappingURL=runtime-commands.js.map
@@ -0,0 +1,5 @@
1
+ export declare function renderTuttiTerminalLogo(options?: {
2
+ tty?: boolean;
3
+ color?: boolean;
4
+ }): string;
5
+ //# sourceMappingURL=terminal-logo.d.ts.map
@@ -0,0 +1,27 @@
1
+ const RESET = "\u001B[0m";
2
+ const ACTION_STRONG = "\u001B[38;2;8;121;95m";
3
+ // Static Braille rendering of docs/design/Tutti.png, generated from the alpha mask.
4
+ const TUTTI_BRAILLE_LOGO = [
5
+ " ⣀⣤⣶⣶⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⡄ ⣠⣶⣶⣦ ⢀⣤⣶⣶⣄",
6
+ " ⢀⣾⣿⣿⠿⠛⠛⠛⢉⣿⣿⣿⠏⠉⠉⠛⠛⠛⠛⠛⠁ ⢀⣾⣿⠏⣿⣿⡇⣠⣿⡿⠙⣿⣿",
7
+ " ⢸⣿⣿⡇ ⢸⣿⣿⣿ ⣾⣿⡏⢀⣿⣿⢡⣿⣿⠃⣸⣿⡟ ⢀⣴⣶⣤",
8
+ " ⠈⠿⣿⠟ ⣿⣿⣿⡇ ⢸⣿⣿⣡⣾⡿⠃⣾⣿⣟⣰⣿⠟⠁ ⠘⠿⠿⠛",
9
+ " ⢰⣿⣿⣿⠁ ⣤⣶⡦ ⣴⣶⡆⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠆⣤⣶⣦",
10
+ " ⣸⣿⣿⡿ ⢰⣿⣿⠇ ⢰⣿⣿⠇⠈⠉⣿⣿⡏⠉⠉⠉⢹⣿⣿⡏⠉⠉⠁ ⢠⣿⣿⡟",
11
+ " ⣿⣿⣿⡇ ⣾⣿⣿ ⢠⣿⣿⡿ ⢀⣿⣿⣇ ⢸⣿⣿⡇ ⣸⣿⣿⠇",
12
+ " ⢰⣿⣿⣿⠃ ⣿⣿⣿⣴⣿⣿⣿⣇⣀⣴⣿⣿⣿⣿⣄⣀⣠⣾⣿⣿⣧⣀⣀⣀⣴⣿⣿⣿",
13
+ " ⠸⣿⣿⡿ ⠹⣿⣿⡿⠃⠻⣿⣿⣿⠟⠋⠈⠻⣿⣿⣿⡿⠛⠙⠿⣿⣿⣿⣿⠟⣿⣿⡿",
14
+ ].join("\n");
15
+ function supportsColor(options) {
16
+ return options.color !== false && process.env.NO_COLOR === undefined;
17
+ }
18
+ export function renderTuttiTerminalLogo(options = {}) {
19
+ if (options.tty !== true) {
20
+ return "Tutti";
21
+ }
22
+ if (!supportsColor(options)) {
23
+ return TUTTI_BRAILLE_LOGO;
24
+ }
25
+ return `${ACTION_STRONG}${TUTTI_BRAILLE_LOGO}${RESET}`;
26
+ }
27
+ //# sourceMappingURL=terminal-logo.js.map
@@ -0,0 +1,2 @@
1
+ export declare function renderTerminalQr(input: string): string;
2
+ //# sourceMappingURL=terminal-qr.d.ts.map
@@ -0,0 +1,11 @@
1
+ import { createRequire } from "node:module";
2
+ const require = createRequire(import.meta.url);
3
+ export function renderTerminalQr(input) {
4
+ const qrcode = require("qrcode-terminal");
5
+ let output = "";
6
+ qrcode.generate(input, { small: true }, (rendered) => {
7
+ output = rendered;
8
+ });
9
+ return output.trimEnd();
10
+ }
11
+ //# sourceMappingURL=terminal-qr.js.map
@@ -0,0 +1,2 @@
1
+ export declare function readCliVersion(): string;
2
+ //# sourceMappingURL=version.d.ts.map
@@ -0,0 +1,7 @@
1
+ import { readFileSync } from "node:fs";
2
+ export function readCliVersion() {
3
+ const packageUrl = new URL("../../../package.json", import.meta.url);
4
+ const parsed = JSON.parse(readFileSync(packageUrl, "utf8"));
5
+ return typeof parsed.version === "string" ? parsed.version : "0.0.0";
6
+ }
7
+ //# sourceMappingURL=version.js.map
@@ -3,6 +3,7 @@ import type { ProviderConfigProjection } from "@tutti/shared/schemas/api";
3
3
  export type HostLocalControlOptions = {
4
4
  token: string;
5
5
  getLaunchStatus?: () => HostLocalLaunchStatus;
6
+ refreshInvite?: () => Promise<HostLocalLaunchStatus>;
6
7
  project?: {
7
8
  get: () => HostLocalProjectProjection;
8
9
  update: (input: HostLocalProjectBody) => HostLocalProjectProjection;
@@ -190,6 +190,19 @@ export function registerHostLocalControlRoutes(app, options) {
190
190
  requireLocalToken(request.headers.authorization);
191
191
  return options.getLaunchStatus?.() ?? {};
192
192
  });
193
+ app.post("/host-local/v1/invite/rotate", {
194
+ schema: {
195
+ response: {
196
+ 200: HostLocalLaunchStatusResponseSchema,
197
+ },
198
+ },
199
+ }, async (request) => {
200
+ requireLocalToken(request.headers.authorization);
201
+ if (options.refreshInvite === undefined) {
202
+ return options.getLaunchStatus?.() ?? {};
203
+ }
204
+ return await options.refreshInvite();
205
+ });
193
206
  app.get("/host-local/v1/provider/config", {
194
207
  schema: {
195
208
  response: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xfey/tutti",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -35,6 +35,7 @@
35
35
  "better-sqlite3": "^12.9.0",
36
36
  "fastify": "^5.8.5",
37
37
  "openai": "^6.34.0",
38
+ "qrcode-terminal": "^0.12.0",
38
39
  "sharp": "^0.35.3",
39
40
  "ulid": "^3.0.2",
40
41
  "ws": "^8.20.0",