@rtrvr-ai/rover 2.3.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,10 @@
1
1
  import type { RoverPageCaptureConfig } from '@rover/shared/lib/types/index.js';
2
- import { type RoverShortcut, type RoverVoiceConfig } from '@rover/ui';
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';
7
+ import { type FollowupChatEntry } from './followupChatHeuristics.js';
5
8
  export type RoverWebToolsConfig = {
6
9
  enableExternalWebContext?: boolean;
7
10
  allowDomains?: string[];
@@ -198,8 +201,26 @@ export type ClientToolDefinition = {
198
201
  schema?: any;
199
202
  llmCallable?: boolean;
200
203
  };
201
- export type RoverEventName = 'ready' | 'updated' | 'status' | 'tool_start' | 'tool_result' | 'error' | 'auth_required' | 'navigation_guardrail' | 'mode_change' | 'task_started' | 'task_ended' | 'task_suggested_reset' | 'context_restored' | 'checkpoint_state' | 'checkpoint_error' | 'tab_event_conflict_retry' | 'tab_event_conflict_exhausted' | 'checkpoint_token_missing' | 'open' | 'close';
204
+ export type RoverEventName = 'ready' | 'updated' | 'status' | 'run_started' | 'run_state_transition' | 'run_completed' | 'tool_start' | 'tool_result' | 'error' | 'auth_required' | 'navigation_guardrail' | 'mode_change' | 'task_started' | 'task_ended' | 'task_suggested_reset' | 'context_restored' | 'checkpoint_state' | 'checkpoint_error' | 'tab_event_conflict_retry' | 'tab_event_conflict_exhausted' | 'checkpoint_token_missing' | 'open' | 'close';
202
205
  export type RoverEventHandler = (payload?: any) => void;
206
+ export type RoverPromptContextEntry = {
207
+ role?: 'model';
208
+ message: string;
209
+ source?: string;
210
+ };
211
+ export type RoverPromptContextInput = {
212
+ userText: string;
213
+ isFreshTask: boolean;
214
+ pageUrl: string;
215
+ taskId?: string;
216
+ taskBoundaryId?: string;
217
+ visitorId?: string;
218
+ visitor?: {
219
+ name?: string;
220
+ email?: string;
221
+ };
222
+ };
223
+ export type RoverPromptContextProvider = (input: RoverPromptContextInput) => string | RoverPromptContextEntry | Array<string | RoverPromptContextEntry> | null | undefined | Promise<string | RoverPromptContextEntry | Array<string | RoverPromptContextEntry> | null | undefined>;
203
224
  type RoverVoiceTelemetryEventName = 'voice_started' | 'voice_stopped' | 'voice_transcript_ready' | 'voice_error' | 'voice_permission_denied' | 'voice_provider_selected';
204
225
  export type RoverInstance = {
205
226
  boot: (cfg: RoverInit) => RoverInstance;
@@ -219,6 +240,9 @@ export type RoverInstance = {
219
240
  reason?: string;
220
241
  }) => void;
221
242
  getState: () => any;
243
+ requestSigned: (input: string | URL, init?: RequestInit) => Promise<Response>;
244
+ attachLaunch: (params: RoverPreviewAttachLaunch) => Promise<RoverLaunchAttachResponse | null>;
245
+ registerPromptContextProvider: (provider: RoverPromptContextProvider) => () => void;
222
246
  registerTool: (nameOrDef: string | ClientToolDefinition, handler: (args: any) => any | Promise<any>) => void;
223
247
  identify: (visitor: {
224
248
  name?: string;
@@ -227,6 +251,17 @@ export type RoverInstance = {
227
251
  on: (event: RoverEventName, handler: RoverEventHandler) => () => void;
228
252
  };
229
253
  type TelemetryEventName = RoverEventName | RoverVoiceTelemetryEventName;
254
+ declare function normalizePromptContextEntry(input: string | RoverPromptContextEntry): FollowupChatEntry | null;
255
+ declare function buildPublicRunStartedPayload(msg: any): Record<string, unknown>;
256
+ declare function buildPublicRunLifecyclePayload(msg: any, completionState: ReturnType<typeof normalizeRunCompletionState>): Record<string, unknown>;
257
+ declare function normalizeRunCompletionState(msg: any): {
258
+ taskComplete: boolean;
259
+ needsUserInput: boolean;
260
+ terminalState: 'waiting_input' | 'in_progress' | 'completed' | 'failed';
261
+ contextResetRecommended: boolean;
262
+ continuationReason?: 'loop_continue' | 'same_tab_navigation_handoff' | 'awaiting_user';
263
+ questions?: RoverAskUserQuestion[];
264
+ };
230
265
  declare function sanitizeWorkerState(input: any): PersistedWorkerState | undefined;
231
266
  declare function cloneRuntimeStateForCheckpoint(state: PersistedRuntimeState): PersistedRuntimeState;
232
267
  export declare function identify(visitor: {
@@ -242,6 +277,9 @@ export declare function close(): void;
242
277
  export declare function show(): void;
243
278
  export declare function hide(): void;
244
279
  export declare function send(text: string): void;
280
+ export declare function attachLaunch(params: RoverPreviewAttachLaunch): Promise<RoverLaunchAttachResponse | null>;
281
+ export declare function requestSigned(input: string | URL, init?: RequestInit): Promise<Response>;
282
+ export declare function registerPromptContextProvider(provider: RoverPromptContextProvider): () => void;
245
283
  export declare function newTask(options?: {
246
284
  reason?: string;
247
285
  clearUi?: boolean;
@@ -259,6 +297,9 @@ export declare const __roverInternalsForTests: {
259
297
  maxCoalesceDelayMs: number;
260
298
  };
261
299
  getTelemetryFastLaneEvents: () => TelemetryEventName[];
300
+ normalizePromptContextEntry: typeof normalizePromptContextEntry;
301
+ buildPublicRunStartedPayload: typeof buildPublicRunStartedPayload;
302
+ buildPublicRunLifecyclePayload: typeof buildPublicRunLifecyclePayload;
262
303
  };
304
+ export { createRoverBookmarklet, createRoverConsoleSnippet, createRoverScriptTagSnippet, readRoverScriptDataAttributes, };
263
305
  export declare function installGlobal(): void;
264
- 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
+ };