@rtrvr-ai/rover 3.0.0 → 3.0.1

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.
package/dist/index.d.ts CHANGED
@@ -1,7 +1,9 @@
1
1
  import type { RoverPageCaptureConfig } from '@rover/shared/lib/types/index.js';
2
2
  import { type RoverAskUserQuestion, type RoverShortcut, type RoverVoiceConfig } from '@rover/ui';
3
+ import { createRoverBookmarklet, createRoverConsoleSnippet, createRoverScriptTagSnippet, readRoverScriptDataAttributes, type RoverPreviewAttachLaunch } from './previewBootstrap.js';
3
4
  import { type RoverCloudCheckpointState } from './cloudCheckpoint.js';
4
5
  import type { PersistedRuntimeState, PersistedWorkerState } from './runtimeTypes.js';
6
+ import { type RoverLaunchAttachResponse } from './serverRuntime.js';
5
7
  import { type FollowupChatEntry } from './followupChatHeuristics.js';
6
8
  export type RoverWebToolsConfig = {
7
9
  enableExternalWebContext?: boolean;
@@ -239,6 +241,7 @@ export type RoverInstance = {
239
241
  }) => void;
240
242
  getState: () => any;
241
243
  requestSigned: (input: string | URL, init?: RequestInit) => Promise<Response>;
244
+ attachLaunch: (params: RoverPreviewAttachLaunch) => Promise<RoverLaunchAttachResponse | null>;
242
245
  registerPromptContextProvider: (provider: RoverPromptContextProvider) => () => void;
243
246
  registerTool: (nameOrDef: string | ClientToolDefinition, handler: (args: any) => any | Promise<any>) => void;
244
247
  identify: (visitor: {
@@ -274,6 +277,7 @@ export declare function close(): void;
274
277
  export declare function show(): void;
275
278
  export declare function hide(): void;
276
279
  export declare function send(text: string): void;
280
+ export declare function attachLaunch(params: RoverPreviewAttachLaunch): Promise<RoverLaunchAttachResponse | null>;
277
281
  export declare function requestSigned(input: string | URL, init?: RequestInit): Promise<Response>;
278
282
  export declare function registerPromptContextProvider(provider: RoverPromptContextProvider): () => void;
279
283
  export declare function newTask(options?: {
@@ -297,5 +301,5 @@ export declare const __roverInternalsForTests: {
297
301
  buildPublicRunStartedPayload: typeof buildPublicRunStartedPayload;
298
302
  buildPublicRunLifecyclePayload: typeof buildPublicRunLifecyclePayload;
299
303
  };
304
+ export { createRoverBookmarklet, createRoverConsoleSnippet, createRoverScriptTagSnippet, readRoverScriptDataAttributes, };
300
305
  export declare function installGlobal(): void;
301
- export {};
@@ -0,0 +1,27 @@
1
+ export type RoverPreviewAttachLaunch = {
2
+ requestId: string;
3
+ attachToken: string;
4
+ };
5
+ export type RoverPreviewBootstrapConfig = {
6
+ scriptUrl?: string;
7
+ siteId: string;
8
+ publicKey?: string;
9
+ sessionToken?: string;
10
+ sessionId?: string;
11
+ siteKeyId?: string;
12
+ apiBase?: string;
13
+ workerUrl?: string;
14
+ allowedDomains?: string[];
15
+ domainScopeMode?: 'host_only' | 'registrable_domain';
16
+ externalNavigationPolicy?: 'open_new_tab_notice' | 'block' | 'allow';
17
+ sessionScope?: 'shared_site' | 'tab';
18
+ openOnInit?: boolean;
19
+ mode?: 'safe' | 'full';
20
+ allowActions?: boolean;
21
+ attachLaunch?: RoverPreviewAttachLaunch;
22
+ };
23
+ export type RoverScriptAttributeSource = Pick<HTMLScriptElement, 'getAttribute'>;
24
+ export declare function createRoverConsoleSnippet(config: RoverPreviewBootstrapConfig): string;
25
+ export declare function createRoverBookmarklet(config: RoverPreviewBootstrapConfig): string;
26
+ export declare function createRoverScriptTagSnippet(config: RoverPreviewBootstrapConfig): string;
27
+ export declare function readRoverScriptDataAttributes(scriptEl: RoverScriptAttributeSource): RoverPreviewBootstrapConfig | null;
@@ -0,0 +1,203 @@
1
+ const DEFAULT_EMBED_SCRIPT_URL = 'https://rover.rtrvr.ai/embed.js';
2
+ function toStringValue(value) {
3
+ return String(value || '').trim();
4
+ }
5
+ function escapeHtmlAttr(value) {
6
+ return String(value || '')
7
+ .replace(/&/g, '&amp;')
8
+ .replace(/"/g, '&quot;')
9
+ .replace(/</g, '&lt;')
10
+ .replace(/>/g, '&gt;');
11
+ }
12
+ function parseBooleanAttr(value) {
13
+ const normalized = toStringValue(value).toLowerCase();
14
+ if (!normalized)
15
+ return undefined;
16
+ if (['1', 'true', 'yes', 'on'].includes(normalized))
17
+ return true;
18
+ if (['0', 'false', 'no', 'off'].includes(normalized))
19
+ return false;
20
+ return undefined;
21
+ }
22
+ function parseCsvList(value) {
23
+ const items = toStringValue(value)
24
+ .split(',')
25
+ .map(item => item.trim())
26
+ .filter(Boolean);
27
+ if (!items.length)
28
+ return undefined;
29
+ return Array.from(new Set(items));
30
+ }
31
+ function normalizeBootstrapConfig(config) {
32
+ return {
33
+ ...config,
34
+ scriptUrl: toStringValue(config.scriptUrl) || DEFAULT_EMBED_SCRIPT_URL,
35
+ };
36
+ }
37
+ function buildBootstrapPayload(config) {
38
+ const normalized = normalizeBootstrapConfig(config);
39
+ const payload = {
40
+ siteId: normalized.siteId,
41
+ };
42
+ if (normalized.publicKey)
43
+ payload.publicKey = normalized.publicKey;
44
+ if (normalized.sessionToken)
45
+ payload.sessionToken = normalized.sessionToken;
46
+ if (normalized.sessionId)
47
+ payload.sessionId = normalized.sessionId;
48
+ if (normalized.siteKeyId)
49
+ payload.siteKeyId = normalized.siteKeyId;
50
+ if (normalized.apiBase)
51
+ payload.apiBase = normalized.apiBase;
52
+ if (normalized.workerUrl)
53
+ payload.workerUrl = normalized.workerUrl;
54
+ if (normalized.allowedDomains?.length)
55
+ payload.allowedDomains = normalized.allowedDomains;
56
+ if (normalized.domainScopeMode)
57
+ payload.domainScopeMode = normalized.domainScopeMode;
58
+ if (normalized.externalNavigationPolicy)
59
+ payload.externalNavigationPolicy = normalized.externalNavigationPolicy;
60
+ if (normalized.sessionScope)
61
+ payload.sessionScope = normalized.sessionScope;
62
+ if (typeof normalized.openOnInit === 'boolean')
63
+ payload.openOnInit = normalized.openOnInit;
64
+ if (normalized.mode)
65
+ payload.mode = normalized.mode;
66
+ if (typeof normalized.allowActions === 'boolean')
67
+ payload.allowActions = normalized.allowActions;
68
+ return payload;
69
+ }
70
+ function buildQueueStub() {
71
+ return [
72
+ '(function(){',
73
+ ' var r = window.rover = window.rover || function(){',
74
+ ' (r.q = r.q || []).push(arguments);',
75
+ ' };',
76
+ ' r.l = +new Date();',
77
+ '})();',
78
+ ].join('\n');
79
+ }
80
+ function buildCompactQueueStub() {
81
+ return '(function(){var r=window.rover=window.rover||function(){(r.q=r.q||[]).push(arguments)};r.l=+new Date()})();';
82
+ }
83
+ function buildConsoleScript(config, compact = false) {
84
+ const normalized = normalizeBootstrapConfig(config);
85
+ const payloadJson = compact
86
+ ? JSON.stringify(buildBootstrapPayload(normalized))
87
+ : JSON.stringify(buildBootstrapPayload(normalized), null, 2);
88
+ const attachJson = normalized.attachLaunch
89
+ ? (compact ? JSON.stringify(normalized.attachLaunch) : JSON.stringify(normalized.attachLaunch, null, 2))
90
+ : '';
91
+ const scriptUrl = JSON.stringify(normalized.scriptUrl);
92
+ if (compact) {
93
+ const parts = [
94
+ buildCompactQueueStub(),
95
+ `rover('boot', ${payloadJson});`,
96
+ normalized.attachLaunch ? `rover('attachLaunch', ${attachJson});` : '',
97
+ `(function(){var s=document.createElement('script');s.src=${scriptUrl};s.async=true;(document.head||document.documentElement).appendChild(s)})();`,
98
+ ];
99
+ return parts.filter(Boolean).join('');
100
+ }
101
+ const lines = [
102
+ buildQueueStub(),
103
+ '',
104
+ `rover('boot', ${payloadJson});`,
105
+ ];
106
+ if (normalized.attachLaunch) {
107
+ lines.push(`rover('attachLaunch', ${attachJson});`);
108
+ }
109
+ lines.push('', '(function(){', ' var s = document.createElement("script");', ` s.src = ${scriptUrl};`, ' s.async = true;', ' (document.head || document.documentElement).appendChild(s);', '})();');
110
+ return lines.join('\n');
111
+ }
112
+ export function createRoverConsoleSnippet(config) {
113
+ return buildConsoleScript(config, false);
114
+ }
115
+ export function createRoverBookmarklet(config) {
116
+ return `javascript:${buildConsoleScript(config, true)}`;
117
+ }
118
+ export function createRoverScriptTagSnippet(config) {
119
+ const normalized = normalizeBootstrapConfig(config);
120
+ const attrs = [
121
+ `src="${escapeHtmlAttr(normalized.scriptUrl)}"`,
122
+ `data-site-id="${escapeHtmlAttr(normalized.siteId)}"`,
123
+ ];
124
+ if (normalized.publicKey)
125
+ attrs.push(`data-public-key="${escapeHtmlAttr(normalized.publicKey)}"`);
126
+ if (normalized.sessionToken)
127
+ attrs.push(`data-session-token="${escapeHtmlAttr(normalized.sessionToken)}"`);
128
+ if (normalized.sessionId)
129
+ attrs.push(`data-session-id="${escapeHtmlAttr(normalized.sessionId)}"`);
130
+ if (normalized.siteKeyId)
131
+ attrs.push(`data-site-key-id="${escapeHtmlAttr(normalized.siteKeyId)}"`);
132
+ if (normalized.apiBase)
133
+ attrs.push(`data-api-base="${escapeHtmlAttr(normalized.apiBase)}"`);
134
+ if (normalized.workerUrl)
135
+ attrs.push(`data-worker-url="${escapeHtmlAttr(normalized.workerUrl)}"`);
136
+ if (normalized.allowedDomains?.length)
137
+ attrs.push(`data-allowed-domains="${escapeHtmlAttr(normalized.allowedDomains.join(','))}"`);
138
+ if (normalized.domainScopeMode)
139
+ attrs.push(`data-domain-scope-mode="${escapeHtmlAttr(normalized.domainScopeMode)}"`);
140
+ if (normalized.externalNavigationPolicy)
141
+ attrs.push(`data-external-navigation-policy="${escapeHtmlAttr(normalized.externalNavigationPolicy)}"`);
142
+ if (normalized.sessionScope)
143
+ attrs.push(`data-session-scope="${escapeHtmlAttr(normalized.sessionScope)}"`);
144
+ if (typeof normalized.openOnInit === 'boolean')
145
+ attrs.push(`data-open-on-init="${escapeHtmlAttr(String(normalized.openOnInit))}"`);
146
+ if (normalized.mode)
147
+ attrs.push(`data-mode="${escapeHtmlAttr(normalized.mode)}"`);
148
+ if (typeof normalized.allowActions === 'boolean')
149
+ attrs.push(`data-allow-actions="${escapeHtmlAttr(String(normalized.allowActions))}"`);
150
+ return `<script ${attrs.join(' ')}></script>`;
151
+ }
152
+ export function readRoverScriptDataAttributes(scriptEl) {
153
+ const siteId = toStringValue(scriptEl.getAttribute('data-site-id'));
154
+ const publicKey = toStringValue(scriptEl.getAttribute('data-public-key'));
155
+ const sessionToken = toStringValue(scriptEl.getAttribute('data-session-token'));
156
+ if (!siteId || (!publicKey && !sessionToken))
157
+ return null;
158
+ const config = {
159
+ siteId,
160
+ };
161
+ if (publicKey)
162
+ config.publicKey = publicKey;
163
+ if (sessionToken)
164
+ config.sessionToken = sessionToken;
165
+ const sessionId = toStringValue(scriptEl.getAttribute('data-session-id'));
166
+ if (sessionId)
167
+ config.sessionId = sessionId;
168
+ const siteKeyId = toStringValue(scriptEl.getAttribute('data-site-key-id'));
169
+ if (siteKeyId)
170
+ config.siteKeyId = siteKeyId;
171
+ const apiBase = toStringValue(scriptEl.getAttribute('data-api-base'));
172
+ if (apiBase)
173
+ config.apiBase = apiBase;
174
+ const workerUrl = toStringValue(scriptEl.getAttribute('data-worker-url'));
175
+ if (workerUrl)
176
+ config.workerUrl = workerUrl;
177
+ const allowedDomains = parseCsvList(scriptEl.getAttribute('data-allowed-domains'));
178
+ if (allowedDomains)
179
+ config.allowedDomains = allowedDomains;
180
+ const domainScopeMode = toStringValue(scriptEl.getAttribute('data-domain-scope-mode'));
181
+ if (domainScopeMode === 'host_only' || domainScopeMode === 'registrable_domain') {
182
+ config.domainScopeMode = domainScopeMode;
183
+ }
184
+ const externalNavigationPolicy = toStringValue(scriptEl.getAttribute('data-external-navigation-policy'));
185
+ if (externalNavigationPolicy === 'open_new_tab_notice' || externalNavigationPolicy === 'block' || externalNavigationPolicy === 'allow') {
186
+ config.externalNavigationPolicy = externalNavigationPolicy;
187
+ }
188
+ const sessionScope = toStringValue(scriptEl.getAttribute('data-session-scope'));
189
+ if (sessionScope === 'shared_site' || sessionScope === 'tab') {
190
+ config.sessionScope = sessionScope;
191
+ }
192
+ const openOnInit = parseBooleanAttr(scriptEl.getAttribute('data-open-on-init'));
193
+ if (typeof openOnInit === 'boolean')
194
+ config.openOnInit = openOnInit;
195
+ const mode = toStringValue(scriptEl.getAttribute('data-mode'));
196
+ if (mode === 'safe' || mode === 'full') {
197
+ config.mode = mode;
198
+ }
199
+ const allowActions = parseBooleanAttr(scriptEl.getAttribute('data-allow-actions'));
200
+ if (typeof allowActions === 'boolean')
201
+ config.allowActions = allowActions;
202
+ return config;
203
+ }
@@ -0,0 +1,312 @@
1
+ #!/usr/bin/env node
2
+
3
+ // ../../apps/rolls/src/terminal.mjs
4
+ var ESC = "\x1B[";
5
+ var orange = (s) => `\x1B[38;2;255;76;0m${s}\x1B[0m`;
6
+ var amber = (s) => `\x1B[38;2;255;184;0m${s}\x1B[0m`;
7
+ var green = (s) => `\x1B[38;2;74;222;128m${s}\x1B[0m`;
8
+ var dim = (s) => `\x1B[2m${s}\x1B[0m`;
9
+ var bold = (s) => `\x1B[1m${s}\x1B[0m`;
10
+ var red = (s) => `\x1B[31m${s}\x1B[0m`;
11
+ var cyan = (s) => `\x1B[36m${s}\x1B[0m`;
12
+ var clearScreen = () => process.stdout.write(`${ESC}2J${ESC}H`);
13
+ var hideCursor = () => process.stdout.write(`${ESC}?25l`);
14
+ var showCursor = () => process.stdout.write(`${ESC}?25h`);
15
+ function typewrite(text, delay = 40) {
16
+ return new Promise((resolve) => {
17
+ let i = 0;
18
+ const tick = () => {
19
+ if (i < text.length) {
20
+ process.stdout.write(text[i]);
21
+ i++;
22
+ setTimeout(tick, delay);
23
+ } else {
24
+ resolve();
25
+ }
26
+ };
27
+ tick();
28
+ });
29
+ }
30
+ var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
31
+ function waitForEnter() {
32
+ return new Promise((resolve) => {
33
+ if (process.stdin.isTTY) {
34
+ process.stdin.setRawMode(true);
35
+ }
36
+ process.stdin.resume();
37
+ process.stdin.once("data", (key) => {
38
+ if (process.stdin.isTTY) {
39
+ process.stdin.setRawMode(false);
40
+ }
41
+ if (key[0] === 3) {
42
+ showCursor();
43
+ process.exit(0);
44
+ }
45
+ resolve();
46
+ });
47
+ });
48
+ }
49
+ function selectMenu(items, renderFn) {
50
+ return new Promise((resolve) => {
51
+ let selected = 0;
52
+ const render = () => {
53
+ renderFn(items, selected);
54
+ };
55
+ render();
56
+ if (process.stdin.isTTY) {
57
+ process.stdin.setRawMode(true);
58
+ }
59
+ process.stdin.resume();
60
+ process.stdin.setEncoding("utf8");
61
+ const onData = (key) => {
62
+ if (key === "") {
63
+ process.stdin.removeListener("data", onData);
64
+ if (process.stdin.isTTY) {
65
+ process.stdin.setRawMode(false);
66
+ }
67
+ showCursor();
68
+ process.exit(0);
69
+ }
70
+ if (key === "\x1B[A") {
71
+ selected = (selected - 1 + items.length) % items.length;
72
+ render();
73
+ }
74
+ if (key === "\x1B[B") {
75
+ selected = (selected + 1) % items.length;
76
+ render();
77
+ }
78
+ if (key === "\r" || key === "\n") {
79
+ process.stdin.removeListener("data", onData);
80
+ if (process.stdin.isTTY) {
81
+ process.stdin.setRawMode(false);
82
+ }
83
+ resolve(items[selected]);
84
+ }
85
+ };
86
+ process.stdin.on("data", onData);
87
+ });
88
+ }
89
+ function stripAnsi(s) {
90
+ return s.replace(/\x1b\[[0-9;]*m/g, "");
91
+ }
92
+ function box(lines, { width = 56, padding = 2, double = true } = {}) {
93
+ const [tl, tr, bl, br, h, v] = double ? ["\u2554", "\u2557", "\u255A", "\u255D", "\u2550", "\u2551"] : ["\u250C", "\u2510", "\u2514", "\u2518", "\u2500", "\u2502"];
94
+ const inner = width - 2;
95
+ const out = [];
96
+ out.push(` ${tl}${h.repeat(inner)}${tr}`);
97
+ for (const line of lines) {
98
+ const stripped = stripAnsi(line);
99
+ const rightPad = Math.max(0, inner - padding - stripped.length);
100
+ out.push(` ${v}${" ".repeat(padding)}${line}${" ".repeat(rightPad)}${v}`);
101
+ }
102
+ out.push(` ${bl}${h.repeat(inner)}${br}`);
103
+ return out.join("\n");
104
+ }
105
+
106
+ // ../../apps/rolls/src/art.mjs
107
+ var LOGO_LINES = [
108
+ ` ___ _____ ___ _ _ ___ ___ ___ _ _ `,
109
+ `| _ \\|_ _|| _ \\| | | || _ \\ | _ \\ / _ \\| | | | `,
110
+ `| / | | | /| |_| || / | /| (_) | |__ | |__ `,
111
+ `|_|_\\ |_| |_|_\\ \\___/ |_|_\\ |_|_\\ \\___/|____|____| `
112
+ ];
113
+ var LOGO_COLORED = LOGO_LINES.map((l) => orange(l));
114
+ var CHICKEN = [
115
+ ` _ _`,
116
+ ` (o >`,
117
+ ` //\\`,
118
+ ` V_/_`
119
+ ];
120
+ var CHICKEN_COLORED = CHICKEN.map((l) => amber(l));
121
+
122
+ // ../../apps/rolls/src/menu.mjs
123
+ var MENU_ITEMS = [
124
+ {
125
+ id: "series-a-seekh",
126
+ emoji: "\u{1F959}",
127
+ name: "The Series A Seekh Kebab Roll",
128
+ price: "$4.20M (pre-revenue)",
129
+ subtitle: "Pre-money valuation: delicious",
130
+ spice: 3
131
+ },
132
+ {
133
+ id: "pivot-paneer",
134
+ emoji: "\u{1FAD3}",
135
+ name: "Pivot Paneer Tikka Roll",
136
+ price: "2 SAFE notes",
137
+ subtitle: "We were a SaaS company once",
138
+ spice: 2
139
+ },
140
+ {
141
+ id: "burn-rate-bhurji",
142
+ emoji: "\u{1F373}",
143
+ name: "The Burn Rate Bhurji Roll",
144
+ price: "$0 (bootstrapped)",
145
+ subtitle: "Consuming capital, one bite at a time",
146
+ spice: 4
147
+ },
148
+ {
149
+ id: "yc-chicken-tikka",
150
+ emoji: "\u{1F357}",
151
+ name: "YC Chicken Tikka Roll",
152
+ price: "$500K (standard deal)",
153
+ subtitle: "Backed by garlic chutney",
154
+ spice: 3
155
+ },
156
+ {
157
+ id: "cap-table-kathi",
158
+ emoji: "\u{1F32F}",
159
+ name: "Cap Table Kathi Roll",
160
+ price: "409A pending",
161
+ subtitle: "Ownership is complicated",
162
+ spice: 2
163
+ },
164
+ {
165
+ id: "runway-raita",
166
+ emoji: "\u{1F963}",
167
+ name: "Runway Extension Raita",
168
+ price: "Free (angel round)",
169
+ subtitle: "It's a side. Like your consulting gig.",
170
+ spice: 0
171
+ }
172
+ ];
173
+
174
+ // ../../apps/rolls/src/order-flow.mjs
175
+ async function playOrderFlow(item) {
176
+ clearScreen();
177
+ const cmd = ` $ rover order --item "${item.name}" --extra-spicy`;
178
+ await typewrite(green(cmd), 30);
179
+ process.stdout.write("\n\n");
180
+ const lines = [
181
+ { text: "Booting agent runtime...", delay: 600 },
182
+ { text: "Authenticating with rtrvr-rolls-HQ...", delay: 800 },
183
+ { text: "Agent connected. Model: gpt-4-turbo-tandoori", delay: 500 },
184
+ { text: "Navigating to kitchen API...", delay: 900 },
185
+ { text: `Locating: "${item.name}"`, delay: 700 },
186
+ { text: "Adding to cart... done", delay: 500 },
187
+ { text: "Applying coupon: DEMO-DAY-DISCOUNT", delay: 600 },
188
+ { text: "Coupon rejected: this is not a real restaurant", delay: 400, color: "red" },
189
+ { text: "Processing payment via npm credits...", delay: 800 },
190
+ { text: "Contacting kitchen microservice...", delay: 1e3 },
191
+ { text: "Kitchen API returned: 418 I'm a teapot", delay: 500, color: "red" },
192
+ { text: "Retrying with exponential backoff and extra masala...", delay: 1200 },
193
+ { text: "Hmm...", delay: 800 },
194
+ { text: "Wait a second...", delay: 1e3 },
195
+ { text: "...", delay: 1500 }
196
+ ];
197
+ for (const line of lines) {
198
+ const prefix = dim(" [rover] ");
199
+ let text = line.text;
200
+ if (line.color === "red") {
201
+ text = red(text);
202
+ } else {
203
+ text = cyan(text);
204
+ }
205
+ process.stdout.write(prefix);
206
+ await typewrite(text, 25);
207
+ process.stdout.write("\n");
208
+ await sleep(line.delay);
209
+ }
210
+ await sleep(500);
211
+ }
212
+
213
+ // ../../apps/rolls/src/reveal.mjs
214
+ async function showReveal() {
215
+ clearScreen();
216
+ const lines = [
217
+ "",
218
+ bold(orange(" \u{1F389} APRIL FOOLS! \u{1F389}")),
219
+ "",
220
+ ` rtrvr rolls isn't real (yet).`,
221
+ ` But ${bold("Rover")} is.`,
222
+ "",
223
+ " Rover is an AI agent that actually browses the web",
224
+ " for your users. It clicks, types, navigates, and",
225
+ ` extracts ${dim("\u2014 so your users don't have to.")}`,
226
+ "",
227
+ ` No tandoori required.`,
228
+ "",
229
+ green(" npx -p @rtrvr-ai/rover rtrvr-rolls"),
230
+ amber(" https://rtrvr.ai/rover"),
231
+ "",
232
+ dim(" \u2500\u2500 No chickens were harmed."),
233
+ dim(" Some VCs were mildly offended. \u2500\u2500"),
234
+ ""
235
+ ];
236
+ console.log(box(lines, { width: 58 }));
237
+ await sleep(500);
238
+ showCursor();
239
+ process.stdout.write("\n");
240
+ }
241
+
242
+ // ../../apps/rolls/src/index.mjs
243
+ function cleanup() {
244
+ showCursor();
245
+ process.stdout.write("\n");
246
+ process.exit(0);
247
+ }
248
+ process.on("SIGINT", cleanup);
249
+ process.on("SIGTERM", cleanup);
250
+ async function showWelcome() {
251
+ clearScreen();
252
+ hideCursor();
253
+ const lines = [
254
+ "",
255
+ ...LOGO_COLORED,
256
+ "",
257
+ bold(" Protein-packed rolls for founders who forgot to eat"),
258
+ orange(" The world's first agentic restaurant."),
259
+ "",
260
+ dim(" Press ENTER to view the menu..."),
261
+ ""
262
+ ];
263
+ console.log(box(lines, { width: 58 }));
264
+ await waitForEnter();
265
+ }
266
+ async function showMenu() {
267
+ const renderMenu = (items, selectedIndex) => {
268
+ clearScreen();
269
+ hideCursor();
270
+ const W = 47;
271
+ const h = "\u2500";
272
+ const header = [
273
+ ` \u250C${h.repeat(W)}\u2510`,
274
+ ` \u2502 ${bold("THE MENU")}${" ".repeat(W - 38)}${dim("rtrvr rolls est. 2026")} \u2502`,
275
+ ` \u251C${h.repeat(W)}\u2524`
276
+ ];
277
+ const body = [];
278
+ body.push(` \u2502${" ".repeat(W)}\u2502`);
279
+ for (let i = 0; i < items.length; i++) {
280
+ const item = items[i];
281
+ const pointer = i === selectedIndex ? orange("\u276F") : " ";
282
+ const nameColor = i === selectedIndex ? orange : (s) => s;
283
+ const nameLine = ` ${pointer} ${item.emoji} ${nameColor(bold(item.name))}`;
284
+ const priceLine = ` ${amber(item.price)}`;
285
+ const subLine = ` ${dim(`"${item.subtitle}"`)}`;
286
+ const padLine = (line) => {
287
+ const stripped = stripAnsi(line);
288
+ const pad = Math.max(0, W - stripped.length);
289
+ return ` \u2502${line}${" ".repeat(pad)}\u2502`;
290
+ };
291
+ body.push(padLine(nameLine));
292
+ body.push(padLine(priceLine));
293
+ body.push(padLine(subLine));
294
+ body.push(` \u2502${" ".repeat(W)}\u2502`);
295
+ }
296
+ const footer = [
297
+ ` \u2502 ${dim("\u2191/\u2193 to browse ENTER to order")}${" ".repeat(W - 33)}\u2502`,
298
+ ` \u2514${h.repeat(W)}\u2524`
299
+ ];
300
+ process.stdout.write([...header, ...body, ...footer].join("\n") + "\n");
301
+ };
302
+ return selectMenu(MENU_ITEMS, renderMenu);
303
+ }
304
+ async function run() {
305
+ await showWelcome();
306
+ const selected = await showMenu();
307
+ await playOrderFlow(selected);
308
+ await showReveal();
309
+ }
310
+ export {
311
+ run
312
+ };