@rtrvr-ai/rover 3.0.0 → 4.0.0
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 +291 -12
- package/dist/agentDiscovery.d.ts +304 -0
- package/dist/agentDiscovery.js +1040 -0
- package/dist/embed.js +3706 -1562
- package/dist/index.d.ts +18 -1
- package/dist/ownerInstall.d.ts +150 -0
- package/dist/ownerInstall.js +340 -0
- package/dist/previewBootstrap.d.ts +36 -0
- package/dist/previewBootstrap.js +273 -0
- package/dist/rolls-cli.mjs +312 -0
- package/dist/rover.js +3706 -1562
- package/dist/worker/rover-worker.js +11 -11
- package/package.json +23 -1
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
const DEFAULT_EMBED_SCRIPT_URL = 'https://rover.rtrvr.ai/embed.js';
|
|
2
|
+
const DEFAULT_AGENT_BASE = 'https://agent.rtrvr.ai';
|
|
3
|
+
const VOICE_AUTO_STOP_MIN_MS = 800;
|
|
4
|
+
const VOICE_AUTO_STOP_MAX_MS = 5000;
|
|
5
|
+
function toStringValue(value) {
|
|
6
|
+
return String(value || '').trim();
|
|
7
|
+
}
|
|
8
|
+
function escapeHtmlAttr(value) {
|
|
9
|
+
return String(value || '')
|
|
10
|
+
.replace(/&/g, '&')
|
|
11
|
+
.replace(/"/g, '"')
|
|
12
|
+
.replace(/</g, '<')
|
|
13
|
+
.replace(/>/g, '>');
|
|
14
|
+
}
|
|
15
|
+
function escapeScriptJson(value) {
|
|
16
|
+
return value
|
|
17
|
+
.replace(/</g, '\\u003c')
|
|
18
|
+
.replace(/>/g, '\\u003e')
|
|
19
|
+
.replace(/&/g, '\\u0026');
|
|
20
|
+
}
|
|
21
|
+
function parseBooleanAttr(value) {
|
|
22
|
+
const normalized = toStringValue(value).toLowerCase();
|
|
23
|
+
if (!normalized)
|
|
24
|
+
return undefined;
|
|
25
|
+
if (['1', 'true', 'yes', 'on'].includes(normalized))
|
|
26
|
+
return true;
|
|
27
|
+
if (['0', 'false', 'no', 'off'].includes(normalized))
|
|
28
|
+
return false;
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
function parseCsvList(value) {
|
|
32
|
+
const items = toStringValue(value)
|
|
33
|
+
.split(',')
|
|
34
|
+
.map(item => item.trim())
|
|
35
|
+
.filter(Boolean);
|
|
36
|
+
if (!items.length)
|
|
37
|
+
return undefined;
|
|
38
|
+
return Array.from(new Set(items));
|
|
39
|
+
}
|
|
40
|
+
function parseIntegerAttr(value) {
|
|
41
|
+
const parsed = Number(toStringValue(value));
|
|
42
|
+
return Number.isFinite(parsed) ? Math.trunc(parsed) : undefined;
|
|
43
|
+
}
|
|
44
|
+
function normalizeVoiceConfig(value) {
|
|
45
|
+
if (!value || typeof value !== 'object')
|
|
46
|
+
return undefined;
|
|
47
|
+
const voice = {};
|
|
48
|
+
if (typeof value.enabled === 'boolean')
|
|
49
|
+
voice.enabled = value.enabled;
|
|
50
|
+
const language = toStringValue(value.language).replace(/[^a-zA-Z0-9-]/g, '').slice(0, 48);
|
|
51
|
+
if (language)
|
|
52
|
+
voice.language = language;
|
|
53
|
+
const autoStopMs = Number(value.autoStopMs);
|
|
54
|
+
if (Number.isFinite(autoStopMs)) {
|
|
55
|
+
voice.autoStopMs = Math.max(VOICE_AUTO_STOP_MIN_MS, Math.min(VOICE_AUTO_STOP_MAX_MS, Math.trunc(autoStopMs)));
|
|
56
|
+
}
|
|
57
|
+
return Object.keys(voice).length ? voice : undefined;
|
|
58
|
+
}
|
|
59
|
+
function normalizeUiConfig(value) {
|
|
60
|
+
if (!value || typeof value !== 'object')
|
|
61
|
+
return undefined;
|
|
62
|
+
const ui = {};
|
|
63
|
+
const voice = normalizeVoiceConfig(value.voice);
|
|
64
|
+
if (voice)
|
|
65
|
+
ui.voice = voice;
|
|
66
|
+
return Object.keys(ui).length ? ui : undefined;
|
|
67
|
+
}
|
|
68
|
+
function normalizeBootstrapConfig(config) {
|
|
69
|
+
const next = {
|
|
70
|
+
...config,
|
|
71
|
+
scriptUrl: toStringValue(config.scriptUrl) || DEFAULT_EMBED_SCRIPT_URL,
|
|
72
|
+
};
|
|
73
|
+
const ui = normalizeUiConfig(config.ui);
|
|
74
|
+
if (ui)
|
|
75
|
+
next.ui = ui;
|
|
76
|
+
else
|
|
77
|
+
delete next.ui;
|
|
78
|
+
return next;
|
|
79
|
+
}
|
|
80
|
+
function buildBootstrapPayload(config) {
|
|
81
|
+
const normalized = normalizeBootstrapConfig(config);
|
|
82
|
+
const payload = {
|
|
83
|
+
siteId: normalized.siteId,
|
|
84
|
+
};
|
|
85
|
+
if (normalized.publicKey)
|
|
86
|
+
payload.publicKey = normalized.publicKey;
|
|
87
|
+
if (normalized.sessionToken)
|
|
88
|
+
payload.sessionToken = normalized.sessionToken;
|
|
89
|
+
if (normalized.sessionId)
|
|
90
|
+
payload.sessionId = normalized.sessionId;
|
|
91
|
+
if (normalized.siteKeyId)
|
|
92
|
+
payload.siteKeyId = normalized.siteKeyId;
|
|
93
|
+
if (normalized.apiBase)
|
|
94
|
+
payload.apiBase = normalized.apiBase;
|
|
95
|
+
if (normalized.workerUrl)
|
|
96
|
+
payload.workerUrl = normalized.workerUrl;
|
|
97
|
+
if (normalized.allowedDomains?.length)
|
|
98
|
+
payload.allowedDomains = normalized.allowedDomains;
|
|
99
|
+
if (normalized.domainScopeMode)
|
|
100
|
+
payload.domainScopeMode = normalized.domainScopeMode;
|
|
101
|
+
if (normalized.externalNavigationPolicy)
|
|
102
|
+
payload.externalNavigationPolicy = normalized.externalNavigationPolicy;
|
|
103
|
+
if (normalized.sessionScope)
|
|
104
|
+
payload.sessionScope = normalized.sessionScope;
|
|
105
|
+
if (typeof normalized.openOnInit === 'boolean')
|
|
106
|
+
payload.openOnInit = normalized.openOnInit;
|
|
107
|
+
if (normalized.mode)
|
|
108
|
+
payload.mode = normalized.mode;
|
|
109
|
+
if (typeof normalized.allowActions === 'boolean')
|
|
110
|
+
payload.allowActions = normalized.allowActions;
|
|
111
|
+
if (normalized.ui?.voice)
|
|
112
|
+
payload.ui = { voice: normalized.ui.voice };
|
|
113
|
+
return payload;
|
|
114
|
+
}
|
|
115
|
+
function buildQueueStub() {
|
|
116
|
+
return [
|
|
117
|
+
'(function(){',
|
|
118
|
+
' var r = window.rover = window.rover || function(){',
|
|
119
|
+
' (r.q = r.q || []).push(arguments);',
|
|
120
|
+
' };',
|
|
121
|
+
' r.l = +new Date();',
|
|
122
|
+
'})();',
|
|
123
|
+
].join('\n');
|
|
124
|
+
}
|
|
125
|
+
function buildCompactQueueStub() {
|
|
126
|
+
return '(function(){var r=window.rover=window.rover||function(){(r.q=r.q||[]).push(arguments)};r.l=+new Date()})();';
|
|
127
|
+
}
|
|
128
|
+
function buildConsoleScript(config, compact = false) {
|
|
129
|
+
const normalized = normalizeBootstrapConfig(config);
|
|
130
|
+
const payloadJson = compact
|
|
131
|
+
? JSON.stringify(buildBootstrapPayload(normalized))
|
|
132
|
+
: JSON.stringify(buildBootstrapPayload(normalized), null, 2);
|
|
133
|
+
const attachJson = normalized.attachLaunch
|
|
134
|
+
? (compact ? JSON.stringify(normalized.attachLaunch) : JSON.stringify(normalized.attachLaunch, null, 2))
|
|
135
|
+
: '';
|
|
136
|
+
const scriptUrl = JSON.stringify(normalized.scriptUrl);
|
|
137
|
+
if (compact) {
|
|
138
|
+
const parts = [
|
|
139
|
+
buildCompactQueueStub(),
|
|
140
|
+
`rover('boot', ${payloadJson});`,
|
|
141
|
+
normalized.attachLaunch ? `rover('attachLaunch', ${attachJson});` : '',
|
|
142
|
+
`(function(){var s=document.createElement('script');s.src=${scriptUrl};s.async=true;(document.head||document.documentElement).appendChild(s)})();`,
|
|
143
|
+
];
|
|
144
|
+
return parts.filter(Boolean).join('');
|
|
145
|
+
}
|
|
146
|
+
const lines = [
|
|
147
|
+
buildQueueStub(),
|
|
148
|
+
'',
|
|
149
|
+
`rover('boot', ${payloadJson});`,
|
|
150
|
+
];
|
|
151
|
+
if (normalized.attachLaunch) {
|
|
152
|
+
lines.push(`rover('attachLaunch', ${attachJson});`);
|
|
153
|
+
}
|
|
154
|
+
lines.push('', '(function(){', ' var s = document.createElement("script");', ` s.src = ${scriptUrl};`, ' s.async = true;', ' (document.head || document.documentElement).appendChild(s);', '})();');
|
|
155
|
+
return lines.join('\n');
|
|
156
|
+
}
|
|
157
|
+
export function createRoverConsoleSnippet(config) {
|
|
158
|
+
return buildConsoleScript(config, false);
|
|
159
|
+
}
|
|
160
|
+
export function createRoverBookmarklet(config) {
|
|
161
|
+
return `javascript:${buildConsoleScript(config, true)}`;
|
|
162
|
+
}
|
|
163
|
+
export function createRoverScriptTagSnippet(config) {
|
|
164
|
+
const normalized = normalizeBootstrapConfig(config);
|
|
165
|
+
const attrs = [
|
|
166
|
+
`src="${escapeHtmlAttr(normalized.scriptUrl)}"`,
|
|
167
|
+
`data-site-id="${escapeHtmlAttr(normalized.siteId)}"`,
|
|
168
|
+
];
|
|
169
|
+
if (normalized.publicKey)
|
|
170
|
+
attrs.push(`data-public-key="${escapeHtmlAttr(normalized.publicKey)}"`);
|
|
171
|
+
if (normalized.sessionToken)
|
|
172
|
+
attrs.push(`data-session-token="${escapeHtmlAttr(normalized.sessionToken)}"`);
|
|
173
|
+
if (normalized.sessionId)
|
|
174
|
+
attrs.push(`data-session-id="${escapeHtmlAttr(normalized.sessionId)}"`);
|
|
175
|
+
if (normalized.siteKeyId)
|
|
176
|
+
attrs.push(`data-site-key-id="${escapeHtmlAttr(normalized.siteKeyId)}"`);
|
|
177
|
+
if (normalized.apiBase)
|
|
178
|
+
attrs.push(`data-api-base="${escapeHtmlAttr(normalized.apiBase)}"`);
|
|
179
|
+
if (normalized.workerUrl)
|
|
180
|
+
attrs.push(`data-worker-url="${escapeHtmlAttr(normalized.workerUrl)}"`);
|
|
181
|
+
if (normalized.allowedDomains?.length)
|
|
182
|
+
attrs.push(`data-allowed-domains="${escapeHtmlAttr(normalized.allowedDomains.join(','))}"`);
|
|
183
|
+
if (normalized.domainScopeMode)
|
|
184
|
+
attrs.push(`data-domain-scope-mode="${escapeHtmlAttr(normalized.domainScopeMode)}"`);
|
|
185
|
+
if (normalized.externalNavigationPolicy)
|
|
186
|
+
attrs.push(`data-external-navigation-policy="${escapeHtmlAttr(normalized.externalNavigationPolicy)}"`);
|
|
187
|
+
if (normalized.sessionScope)
|
|
188
|
+
attrs.push(`data-session-scope="${escapeHtmlAttr(normalized.sessionScope)}"`);
|
|
189
|
+
if (typeof normalized.openOnInit === 'boolean')
|
|
190
|
+
attrs.push(`data-open-on-init="${escapeHtmlAttr(String(normalized.openOnInit))}"`);
|
|
191
|
+
if (normalized.mode)
|
|
192
|
+
attrs.push(`data-mode="${escapeHtmlAttr(normalized.mode)}"`);
|
|
193
|
+
if (typeof normalized.allowActions === 'boolean')
|
|
194
|
+
attrs.push(`data-allow-actions="${escapeHtmlAttr(String(normalized.allowActions))}"`);
|
|
195
|
+
if (typeof normalized.ui?.voice?.enabled === 'boolean')
|
|
196
|
+
attrs.push(`data-voice-enabled="${escapeHtmlAttr(String(normalized.ui.voice.enabled))}"`);
|
|
197
|
+
if (normalized.ui?.voice?.language)
|
|
198
|
+
attrs.push(`data-voice-language="${escapeHtmlAttr(normalized.ui.voice.language)}"`);
|
|
199
|
+
if (typeof normalized.ui?.voice?.autoStopMs === 'number')
|
|
200
|
+
attrs.push(`data-voice-auto-stop-ms="${escapeHtmlAttr(String(normalized.ui.voice.autoStopMs))}"`);
|
|
201
|
+
const taskEndpoint = `${toStringValue(normalized.apiBase) || DEFAULT_AGENT_BASE}/v1/tasks`;
|
|
202
|
+
const markerJson = escapeScriptJson(JSON.stringify({ task: taskEndpoint }));
|
|
203
|
+
return [
|
|
204
|
+
`<script type="application/agent+json" data-rover-agent-discovery="marker">${markerJson}</script>`,
|
|
205
|
+
'<link rel="service-desc" href="/.well-known/agent-card.json" type="application/json" data-rover-agent-discovery="service-desc" />',
|
|
206
|
+
'<link rel="service-doc" href="/llms.txt" type="text/markdown" data-rover-agent-discovery="service-doc" />',
|
|
207
|
+
`<script ${attrs.join(' ')}></script>`,
|
|
208
|
+
].join('\n');
|
|
209
|
+
}
|
|
210
|
+
export function readRoverScriptDataAttributes(scriptEl) {
|
|
211
|
+
const siteId = toStringValue(scriptEl.getAttribute('data-site-id'));
|
|
212
|
+
const publicKey = toStringValue(scriptEl.getAttribute('data-public-key'));
|
|
213
|
+
const sessionToken = toStringValue(scriptEl.getAttribute('data-session-token'));
|
|
214
|
+
if (!siteId || (!publicKey && !sessionToken))
|
|
215
|
+
return null;
|
|
216
|
+
const config = {
|
|
217
|
+
siteId,
|
|
218
|
+
};
|
|
219
|
+
if (publicKey)
|
|
220
|
+
config.publicKey = publicKey;
|
|
221
|
+
if (sessionToken)
|
|
222
|
+
config.sessionToken = sessionToken;
|
|
223
|
+
const sessionId = toStringValue(scriptEl.getAttribute('data-session-id'));
|
|
224
|
+
if (sessionId)
|
|
225
|
+
config.sessionId = sessionId;
|
|
226
|
+
const siteKeyId = toStringValue(scriptEl.getAttribute('data-site-key-id'));
|
|
227
|
+
if (siteKeyId)
|
|
228
|
+
config.siteKeyId = siteKeyId;
|
|
229
|
+
const apiBase = toStringValue(scriptEl.getAttribute('data-api-base'));
|
|
230
|
+
if (apiBase)
|
|
231
|
+
config.apiBase = apiBase;
|
|
232
|
+
const workerUrl = toStringValue(scriptEl.getAttribute('data-worker-url'));
|
|
233
|
+
if (workerUrl)
|
|
234
|
+
config.workerUrl = workerUrl;
|
|
235
|
+
const allowedDomains = parseCsvList(scriptEl.getAttribute('data-allowed-domains'));
|
|
236
|
+
if (allowedDomains)
|
|
237
|
+
config.allowedDomains = allowedDomains;
|
|
238
|
+
const domainScopeMode = toStringValue(scriptEl.getAttribute('data-domain-scope-mode'));
|
|
239
|
+
if (domainScopeMode === 'host_only' || domainScopeMode === 'registrable_domain') {
|
|
240
|
+
config.domainScopeMode = domainScopeMode;
|
|
241
|
+
}
|
|
242
|
+
const externalNavigationPolicy = toStringValue(scriptEl.getAttribute('data-external-navigation-policy'));
|
|
243
|
+
if (externalNavigationPolicy === 'open_new_tab_notice' || externalNavigationPolicy === 'block' || externalNavigationPolicy === 'allow') {
|
|
244
|
+
config.externalNavigationPolicy = externalNavigationPolicy;
|
|
245
|
+
}
|
|
246
|
+
const sessionScope = toStringValue(scriptEl.getAttribute('data-session-scope'));
|
|
247
|
+
if (sessionScope === 'shared_site' || sessionScope === 'tab') {
|
|
248
|
+
config.sessionScope = sessionScope;
|
|
249
|
+
}
|
|
250
|
+
const openOnInit = parseBooleanAttr(scriptEl.getAttribute('data-open-on-init'));
|
|
251
|
+
if (typeof openOnInit === 'boolean')
|
|
252
|
+
config.openOnInit = openOnInit;
|
|
253
|
+
const mode = toStringValue(scriptEl.getAttribute('data-mode'));
|
|
254
|
+
if (mode === 'safe' || mode === 'full') {
|
|
255
|
+
config.mode = mode;
|
|
256
|
+
}
|
|
257
|
+
const allowActions = parseBooleanAttr(scriptEl.getAttribute('data-allow-actions'));
|
|
258
|
+
if (typeof allowActions === 'boolean')
|
|
259
|
+
config.allowActions = allowActions;
|
|
260
|
+
const voiceEnabled = parseBooleanAttr(scriptEl.getAttribute('data-voice-enabled'));
|
|
261
|
+
const voiceLanguage = toStringValue(scriptEl.getAttribute('data-voice-language'));
|
|
262
|
+
const voiceAutoStopMs = parseIntegerAttr(scriptEl.getAttribute('data-voice-auto-stop-ms'));
|
|
263
|
+
if (typeof voiceEnabled === 'boolean' || voiceLanguage || typeof voiceAutoStopMs === 'number') {
|
|
264
|
+
config.ui = {
|
|
265
|
+
voice: {
|
|
266
|
+
...(typeof voiceEnabled === 'boolean' ? { enabled: voiceEnabled } : {}),
|
|
267
|
+
...(voiceLanguage ? { language: voiceLanguage } : {}),
|
|
268
|
+
...(typeof voiceAutoStopMs === 'number' ? { autoStopMs: voiceAutoStopMs } : {}),
|
|
269
|
+
},
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
return config;
|
|
273
|
+
}
|
|
@@ -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
|
+
};
|