@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/README.md +229 -0
- package/dist/embed.js +17 -15
- package/dist/index.d.ts +44 -3
- package/dist/previewBootstrap.d.ts +27 -0
- package/dist/previewBootstrap.js +203 -0
- package/dist/rolls-cli.mjs +312 -0
- package/dist/rover.js +17 -15
- package/dist/worker/rover-worker.js +1 -1
- package/package.json +11 -1
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, '&')
|
|
8
|
+
.replace(/"/g, '"')
|
|
9
|
+
.replace(/</g, '<')
|
|
10
|
+
.replace(/>/g, '>');
|
|
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
|
+
};
|