@alexkroman1/aai-cli 5.1.1 → 5.2.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/dist/_agent.d.ts +39 -0
- package/dist/_api-client.d.ts +37 -0
- package/dist/_bundler.d.ts +46 -0
- package/dist/_config.d.ts +57 -0
- package/dist/_default-html.d.ts +12 -0
- package/dist/_deploy.d.ts +28 -0
- package/dist/_dev-server.d.ts +74 -0
- package/dist/{_init-B-LEVTHx.mjs → _init-DU-sXH6S.mjs} +44 -65
- package/dist/_init.d.ts +7 -0
- package/dist/_mock-api.d.ts +22 -0
- package/dist/_mock-registry.d.ts +17 -0
- package/dist/_output.d.ts +50 -0
- package/dist/_server-common.d.ts +17 -0
- package/dist/_slug-api.d.ts +15 -0
- package/dist/_templates.d.ts +21 -0
- package/dist/_typecheck-gate.d.ts +7 -0
- package/dist/_ui.d.ts +22 -0
- package/dist/_utils.d.ts +48 -0
- package/dist/_vite-env.d.ts +8 -0
- package/dist/build.d.ts +16 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.mjs +1 -1
- package/dist/client-bundler.d.ts +21 -0
- package/dist/delete.d.ts +18 -0
- package/dist/deploy.d.ts +15 -0
- package/dist/dev.d.ts +13 -0
- package/dist/{init-tc3Rbgrf.mjs → init-BjeK8crW.mjs} +1 -1
- package/dist/init.d.ts +24 -0
- package/dist/scaffold/.env.example +11 -0
- package/dist/scaffold/CLAUDE.md +919 -0
- package/dist/scaffold/global.d.ts +1 -0
- package/dist/scaffold/package.json +32 -0
- package/dist/scaffold/pnpm-workspace.yaml +13 -0
- package/dist/scaffold/tsconfig.json +19 -0
- package/dist/scaffold/vite.config.ts +14 -0
- package/dist/scaffold/vitest.config.ts +24 -0
- package/dist/secret.d.ts +25 -0
- package/dist/storage.d.ts +22 -0
- package/dist/templates/code-interpreter/agent.ts +11 -0
- package/dist/templates/code-interpreter/system-prompt.md +18 -0
- package/dist/templates/dispatch-center/agent.test.ts +153 -0
- package/dist/templates/dispatch-center/agent.ts +50 -0
- package/dist/templates/dispatch-center/client.tsx +378 -0
- package/dist/templates/dispatch-center/shared.ts +631 -0
- package/dist/templates/dispatch-center/system-prompt.md +39 -0
- package/dist/templates/dispatch-center/tools/incident_add_note.ts +27 -0
- package/dist/templates/dispatch-center/tools/incident_create.ts +79 -0
- package/dist/templates/dispatch-center/tools/incident_escalate.ts +88 -0
- package/dist/templates/dispatch-center/tools/incident_get.ts +36 -0
- package/dist/templates/dispatch-center/tools/incident_triage.ts +78 -0
- package/dist/templates/dispatch-center/tools/incident_update_status.ts +68 -0
- package/dist/templates/dispatch-center/tools/ops_dashboard.ts +52 -0
- package/dist/templates/dispatch-center/tools/ops_protocols.ts +27 -0
- package/dist/templates/dispatch-center/tools/ops_run_scenario.ts +149 -0
- package/dist/templates/dispatch-center/tools/resources_dispatch.ts +97 -0
- package/dist/templates/dispatch-center/tools/resources_get_available.ts +36 -0
- package/dist/templates/dispatch-center/tools/resources_update_status.ts +62 -0
- package/dist/templates/embedded-assets/agent.ts +55 -0
- package/dist/templates/embedded-assets/knowledge.json +20 -0
- package/dist/templates/health-assistant/agent.ts +174 -0
- package/dist/templates/health-assistant/system-prompt.md +17 -0
- package/dist/templates/infocom-adventure/agent.ts +119 -0
- package/dist/templates/infocom-adventure/client.tsx +279 -0
- package/dist/templates/infocom-adventure/shared.ts +38 -0
- package/dist/templates/infocom-adventure/system-prompt.md +43 -0
- package/dist/templates/math-buddy/agent.ts +16 -0
- package/dist/templates/math-buddy/system-prompt.md +12 -0
- package/dist/templates/night-owl/agent.ts +76 -0
- package/dist/templates/night-owl/client.tsx +130 -0
- package/dist/templates/night-owl/shared.ts +5 -0
- package/dist/templates/personal-finance/agent.ts +11 -0
- package/dist/templates/personal-finance/system-prompt.md +17 -0
- package/dist/templates/pipeline-simple/agent.test.ts +32 -0
- package/dist/templates/pipeline-simple/agent.ts +13 -0
- package/dist/templates/pizza-ordering/agent.test.ts +235 -0
- package/dist/templates/pizza-ordering/agent.ts +174 -0
- package/dist/templates/pizza-ordering/client.tsx +120 -0
- package/dist/templates/pizza-ordering/shared.ts +142 -0
- package/dist/templates/pizza-ordering/system-prompt.md +17 -0
- package/dist/templates/simple/agent.ts +5 -0
- package/dist/templates/solo-rpg/agent.test.ts +477 -0
- package/dist/templates/solo-rpg/agent.ts +37 -0
- package/dist/templates/solo-rpg/client.tsx +809 -0
- package/dist/templates/solo-rpg/shared.ts +747 -0
- package/dist/templates/solo-rpg/system-prompt.md +115 -0
- package/dist/templates/solo-rpg/tools/action_roll.ts +98 -0
- package/dist/templates/solo-rpg/tools/burn_momentum.ts +69 -0
- package/dist/templates/solo-rpg/tools/check_state.ts +11 -0
- package/dist/templates/solo-rpg/tools/load_game.ts +25 -0
- package/dist/templates/solo-rpg/tools/oracle.ts +248 -0
- package/dist/templates/solo-rpg/tools/save_game.ts +20 -0
- package/dist/templates/solo-rpg/tools/setup_character.ts +208 -0
- package/dist/templates/solo-rpg/tools/update_state.ts +170 -0
- package/dist/templates/web-researcher/agent.ts +11 -0
- package/dist/test.d.ts +39 -0
- package/dist/tsdown.config.d.ts +2 -0
- package/dist/typecheck.d.ts +26 -0
- package/dist/worker-bundler.d.ts +29 -0
- package/package.json +9 -6
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import "@alexkroman1/aai-ui/styles.css";
|
|
2
|
+
import type { ChatMessage } from "@alexkroman1/aai-ui";
|
|
3
|
+
import { client, useSession } from "@alexkroman1/aai-ui";
|
|
4
|
+
import { useEffect, useRef } from "react";
|
|
5
|
+
|
|
6
|
+
const CSS = `
|
|
7
|
+
@keyframes ic-flicker {
|
|
8
|
+
0% { opacity: 0.97; } 5% { opacity: 0.95; } 10% { opacity: 0.98; }
|
|
9
|
+
15% { opacity: 0.96; } 20% { opacity: 0.99; } 50% { opacity: 0.96; }
|
|
10
|
+
80% { opacity: 0.98; } 100% { opacity: 0.97; }
|
|
11
|
+
}
|
|
12
|
+
@keyframes ic-scanline {
|
|
13
|
+
0% { transform: translateY(-100%); }
|
|
14
|
+
100% { transform: translateY(100vh); }
|
|
15
|
+
}
|
|
16
|
+
@keyframes ic-boot {
|
|
17
|
+
0% { opacity: 0; transform: scaleY(0.01); }
|
|
18
|
+
30% { opacity: 1; transform: scaleY(0.01); }
|
|
19
|
+
60% { transform: scaleY(1); }
|
|
20
|
+
100% { transform: scaleY(1); opacity: 1; }
|
|
21
|
+
}
|
|
22
|
+
@keyframes ic-pulse {
|
|
23
|
+
0%, 100% { box-shadow: 0 0 8px rgba(0, 255, 65, 0.3); }
|
|
24
|
+
50% { box-shadow: 0 0 20px rgba(0, 255, 65, 0.6); }
|
|
25
|
+
}
|
|
26
|
+
.ic-crt::before {
|
|
27
|
+
content: ""; position: absolute; inset: 0; pointer-events: none; z-index: 10;
|
|
28
|
+
background: repeating-linear-gradient(0deg, rgba(0,0,0,0.15) 0px, rgba(0,0,0,0.15) 1px, transparent 1px, transparent 3px);
|
|
29
|
+
}
|
|
30
|
+
.ic-crt::after {
|
|
31
|
+
content: ""; position: absolute; top: 0; left: 0; right: 0; height: 4px;
|
|
32
|
+
background: rgba(0,255,65,0.08); animation: ic-scanline 8s linear infinite;
|
|
33
|
+
pointer-events: none; z-index: 11;
|
|
34
|
+
}
|
|
35
|
+
.ic-messages::-webkit-scrollbar { width: 6px; }
|
|
36
|
+
.ic-messages::-webkit-scrollbar-track { background: #001a00; }
|
|
37
|
+
.ic-messages::-webkit-scrollbar-thumb { background: #00ff41; }
|
|
38
|
+
.ic-user-msg::before { content: "> "; color: #00ccff; }
|
|
39
|
+
.ic-transcript::before { content: "> "; color: #007a1e; }
|
|
40
|
+
`;
|
|
41
|
+
|
|
42
|
+
const ASCII_LOGO = `
|
|
43
|
+
____ _ __ __ _____ ____ _ _
|
|
44
|
+
/ ___| / \\ \\ \\ / /| ____|| _ \\ | \\ | |
|
|
45
|
+
| | / _ \\ \\ \\ / / | _| | |_) || \\| |
|
|
46
|
+
| |___ / ___ \\ \\ V / | |___ | _ < | |\\ |
|
|
47
|
+
\\____/_/ \\_\\ \\_/ |_____||_| \\_\\|_| \\_|
|
|
48
|
+
`;
|
|
49
|
+
|
|
50
|
+
const CRT_BG = "#000800";
|
|
51
|
+
const GREEN = "#00ff41";
|
|
52
|
+
const GREEN_DIM = "#00aa2a";
|
|
53
|
+
const GREEN_DARK = "#003300";
|
|
54
|
+
const CYAN = "#00ccff";
|
|
55
|
+
|
|
56
|
+
function InfocomAdventure() {
|
|
57
|
+
const session = useSession();
|
|
58
|
+
const bottom = useRef<HTMLDivElement>(null);
|
|
59
|
+
|
|
60
|
+
// Bumps whenever a message lands or the live transcript changes, so the
|
|
61
|
+
// view follows the conversation instead of scrolling once on mount.
|
|
62
|
+
const contentVersion = session.messages.length + (session.userTranscript?.length ?? 0);
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
// Never true — contentVersion only drives the dependency array, and this
|
|
65
|
+
// reference keeps the exhaustive-deps lint satisfied.
|
|
66
|
+
if (contentVersion < 0) return;
|
|
67
|
+
bottom.current?.scrollIntoView({ behavior: "smooth" });
|
|
68
|
+
}, [contentVersion]);
|
|
69
|
+
|
|
70
|
+
const stateLabel =
|
|
71
|
+
session.state === "listening"
|
|
72
|
+
? "Listening"
|
|
73
|
+
: session.state === "speaking"
|
|
74
|
+
? "Narrating"
|
|
75
|
+
: session.state === "thinking"
|
|
76
|
+
? "Thinking"
|
|
77
|
+
: session.state === "connecting"
|
|
78
|
+
? "Connecting"
|
|
79
|
+
: session.state === "ready"
|
|
80
|
+
? "Ready"
|
|
81
|
+
: "Idle";
|
|
82
|
+
|
|
83
|
+
const msgCount = session.messages.filter((m: ChatMessage) => m.role === "user").length;
|
|
84
|
+
|
|
85
|
+
const dotColor =
|
|
86
|
+
session.state === "listening"
|
|
87
|
+
? GREEN
|
|
88
|
+
: session.state === "speaking"
|
|
89
|
+
? "#ffaa00"
|
|
90
|
+
: session.state === "thinking"
|
|
91
|
+
? CYAN
|
|
92
|
+
: GREEN_DARK;
|
|
93
|
+
|
|
94
|
+
if (!session.started) {
|
|
95
|
+
return (
|
|
96
|
+
<>
|
|
97
|
+
<style>{CSS}</style>
|
|
98
|
+
<div
|
|
99
|
+
className="ic-crt fixed inset-0 overflow-hidden"
|
|
100
|
+
style={{
|
|
101
|
+
background: CRT_BG,
|
|
102
|
+
color: GREEN,
|
|
103
|
+
fontFamily: "monospace",
|
|
104
|
+
fontSize: "15px",
|
|
105
|
+
lineHeight: 1.6,
|
|
106
|
+
animation: "ic-flicker 4s infinite",
|
|
107
|
+
}}
|
|
108
|
+
>
|
|
109
|
+
<div
|
|
110
|
+
className="flex flex-col items-center justify-center h-full text-center p-10"
|
|
111
|
+
style={{ animation: "ic-boot 1.5s ease-out" }}
|
|
112
|
+
>
|
|
113
|
+
<div
|
|
114
|
+
className="text-[11px] whitespace-pre mb-8"
|
|
115
|
+
style={{ textShadow: "0 0 10px rgba(0,255,65,0.5)" }}
|
|
116
|
+
>
|
|
117
|
+
{ASCII_LOGO}
|
|
118
|
+
</div>
|
|
119
|
+
<div className="text-[13px] mb-2" style={{ color: GREEN_DIM }}>
|
|
120
|
+
AN INTERACTIVE FICTION
|
|
121
|
+
</div>
|
|
122
|
+
<div className="text-[13px] mb-2" style={{ color: GREEN_DIM }}>
|
|
123
|
+
In the style of the classic text adventures.
|
|
124
|
+
</div>
|
|
125
|
+
<div className="text-[13px] mt-4" style={{ color: GREEN }}>
|
|
126
|
+
VOICE-ENABLED EDITION
|
|
127
|
+
</div>
|
|
128
|
+
<button
|
|
129
|
+
type="button"
|
|
130
|
+
className="mt-10 px-12 py-3.5 bg-transparent cursor-pointer uppercase tracking-[3px] font-mono text-base"
|
|
131
|
+
style={{
|
|
132
|
+
color: GREEN,
|
|
133
|
+
border: `1px solid ${GREEN}`,
|
|
134
|
+
animation: "ic-pulse 2s ease-in-out infinite",
|
|
135
|
+
}}
|
|
136
|
+
onClick={session.start}
|
|
137
|
+
>
|
|
138
|
+
Begin Adventure
|
|
139
|
+
</button>
|
|
140
|
+
</div>
|
|
141
|
+
<div
|
|
142
|
+
className="fixed inset-0 pointer-events-none z-12"
|
|
143
|
+
style={{
|
|
144
|
+
background:
|
|
145
|
+
"radial-gradient(ellipse at center, transparent 60%, rgba(0,0,0,0.4) 100%)",
|
|
146
|
+
}}
|
|
147
|
+
/>
|
|
148
|
+
</div>
|
|
149
|
+
</>
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return (
|
|
154
|
+
<>
|
|
155
|
+
<style>{CSS}</style>
|
|
156
|
+
<div
|
|
157
|
+
className="ic-crt fixed inset-0 overflow-hidden"
|
|
158
|
+
style={{
|
|
159
|
+
background: CRT_BG,
|
|
160
|
+
color: GREEN,
|
|
161
|
+
fontFamily: "monospace",
|
|
162
|
+
fontSize: "15px",
|
|
163
|
+
lineHeight: 1.6,
|
|
164
|
+
animation: "ic-flicker 4s infinite",
|
|
165
|
+
}}
|
|
166
|
+
>
|
|
167
|
+
<div className="flex flex-col h-full">
|
|
168
|
+
{/* Status bar */}
|
|
169
|
+
<div
|
|
170
|
+
className="flex items-center justify-between px-5 py-2 text-[13px] font-bold tracking-wider shrink-0"
|
|
171
|
+
style={{ background: GREEN, color: CRT_BG }}
|
|
172
|
+
>
|
|
173
|
+
<div className="flex gap-6">
|
|
174
|
+
<span>CAVERN ADVENTURE</span>
|
|
175
|
+
<span>Turns: {msgCount}</span>
|
|
176
|
+
</div>
|
|
177
|
+
<span>Voice Adventure</span>
|
|
178
|
+
</div>
|
|
179
|
+
|
|
180
|
+
{session.error && (
|
|
181
|
+
<div className="px-5 py-2 text-xs" style={{ background: "#3a0000", color: "#ff4141" }}>
|
|
182
|
+
ERROR: {session.error.message}
|
|
183
|
+
</div>
|
|
184
|
+
)}
|
|
185
|
+
|
|
186
|
+
{/* Messages */}
|
|
187
|
+
<div
|
|
188
|
+
className="ic-messages flex-1 overflow-y-auto p-5"
|
|
189
|
+
style={{ scrollbarWidth: "thin", scrollbarColor: `${GREEN} #001a00` }}
|
|
190
|
+
>
|
|
191
|
+
{session.messages.map((msg: ChatMessage, i: number) => (
|
|
192
|
+
<div
|
|
193
|
+
key={i}
|
|
194
|
+
className={`mb-4 ${msg.role === "user" ? "ic-user-msg" : ""}`}
|
|
195
|
+
style={{
|
|
196
|
+
textShadow:
|
|
197
|
+
msg.role === "user"
|
|
198
|
+
? "0 0 5px rgba(0,204,255,0.3)"
|
|
199
|
+
: "0 0 5px rgba(0,255,65,0.3)",
|
|
200
|
+
color: msg.role === "user" ? CYAN : GREEN,
|
|
201
|
+
}}
|
|
202
|
+
>
|
|
203
|
+
{msg.content}
|
|
204
|
+
</div>
|
|
205
|
+
))}
|
|
206
|
+
{session.userTranscript !== null && (
|
|
207
|
+
<div
|
|
208
|
+
className="ic-transcript italic"
|
|
209
|
+
style={{ color: "#007a1e", textShadow: "0 0 5px rgba(0,255,65,0.15)" }}
|
|
210
|
+
>
|
|
211
|
+
{session.userTranscript === "" ? "..." : session.userTranscript}
|
|
212
|
+
</div>
|
|
213
|
+
)}
|
|
214
|
+
<div ref={bottom} />
|
|
215
|
+
</div>
|
|
216
|
+
|
|
217
|
+
{/* Footer controls */}
|
|
218
|
+
<div
|
|
219
|
+
className="flex items-center justify-between px-5 py-2 shrink-0 gap-3"
|
|
220
|
+
style={{ borderTop: `1px solid ${GREEN_DARK}`, background: "#001100" }}
|
|
221
|
+
>
|
|
222
|
+
<div
|
|
223
|
+
className="flex items-center gap-2.5 text-xs uppercase tracking-wider"
|
|
224
|
+
style={{ color: GREEN_DIM }}
|
|
225
|
+
>
|
|
226
|
+
<div
|
|
227
|
+
className="w-2 h-2 rounded-full"
|
|
228
|
+
style={{
|
|
229
|
+
background: dotColor,
|
|
230
|
+
boxShadow: dotColor !== GREEN_DARK ? `0 0 6px ${dotColor}` : "none",
|
|
231
|
+
}}
|
|
232
|
+
/>
|
|
233
|
+
<span>{stateLabel}</span>
|
|
234
|
+
</div>
|
|
235
|
+
<div className="flex gap-2">
|
|
236
|
+
<button
|
|
237
|
+
type="button"
|
|
238
|
+
className="px-4 py-1 bg-transparent cursor-pointer uppercase tracking-wider font-mono text-[11px]"
|
|
239
|
+
style={{ color: GREEN_DIM, border: `1px solid ${GREEN_DARK}` }}
|
|
240
|
+
onClick={session.toggle}
|
|
241
|
+
>
|
|
242
|
+
{session.running ? "[P]ause" : "[R]esume"}
|
|
243
|
+
</button>
|
|
244
|
+
{/* Full reload = new WebSocket = fresh sessionId, so the
|
|
245
|
+
session-scoped game state starts over too.
|
|
246
|
+
(session.reset() keeps the same sessionId and would
|
|
247
|
+
resume the old game.) */}
|
|
248
|
+
<button
|
|
249
|
+
type="button"
|
|
250
|
+
className="px-4 py-1 bg-transparent cursor-pointer uppercase tracking-wider font-mono text-[11px]"
|
|
251
|
+
style={{ color: GREEN_DIM, border: `1px solid ${GREEN_DARK}` }}
|
|
252
|
+
onClick={() => window.location.reload()}
|
|
253
|
+
>
|
|
254
|
+
[N]ew Game
|
|
255
|
+
</button>
|
|
256
|
+
</div>
|
|
257
|
+
</div>
|
|
258
|
+
</div>
|
|
259
|
+
<div
|
|
260
|
+
className="fixed inset-0 pointer-events-none z-12"
|
|
261
|
+
style={{
|
|
262
|
+
background: "radial-gradient(ellipse at center, transparent 60%, rgba(0,0,0,0.4) 100%)",
|
|
263
|
+
}}
|
|
264
|
+
/>
|
|
265
|
+
</div>
|
|
266
|
+
</>
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
client({
|
|
271
|
+
component: InfocomAdventure,
|
|
272
|
+
theme: {
|
|
273
|
+
bg: CRT_BG,
|
|
274
|
+
primary: GREEN,
|
|
275
|
+
text: GREEN,
|
|
276
|
+
surface: "#001a00",
|
|
277
|
+
border: GREEN_DARK,
|
|
278
|
+
},
|
|
279
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { ToolContext } from "@alexkroman1/aai";
|
|
2
|
+
|
|
3
|
+
export type GameState = {
|
|
4
|
+
inventory: string[];
|
|
5
|
+
currentRoom: string;
|
|
6
|
+
score: number;
|
|
7
|
+
moves: number;
|
|
8
|
+
flags: Record<string, boolean>;
|
|
9
|
+
history: string[];
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const DEFAULT_GAME_STATE: GameState = {
|
|
13
|
+
inventory: [],
|
|
14
|
+
currentRoom: "Cave Mouth",
|
|
15
|
+
score: 0,
|
|
16
|
+
moves: 0,
|
|
17
|
+
flags: {},
|
|
18
|
+
history: [],
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// The game lives in `ctx.state`, the agent's per-session mutable state — each
|
|
22
|
+
// session is its own playthrough, so concurrent players never see each
|
|
23
|
+
// other's game and a fresh session starts a fresh adventure.
|
|
24
|
+
type StateSlot = { game?: GameState };
|
|
25
|
+
|
|
26
|
+
/** The session's live game. Mutations to the returned object stick — it is
|
|
27
|
+
* the object stored in `ctx.state`. */
|
|
28
|
+
export function getGameState(ctx: ToolContext): GameState {
|
|
29
|
+
const slot = ctx.state as StateSlot;
|
|
30
|
+
slot.game ??= structuredClone(DEFAULT_GAME_STATE);
|
|
31
|
+
return slot.game;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function resetGameState(ctx: ToolContext): GameState {
|
|
35
|
+
const slot = ctx.state as StateSlot;
|
|
36
|
+
slot.game = structuredClone(DEFAULT_GAME_STATE);
|
|
37
|
+
return slot.game;
|
|
38
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
You are a classic text adventure game engine running an original game called CAVERN ADVENTURE, in the dry, witty style of early-1980s interactive fiction.
|
|
2
|
+
|
|
3
|
+
You ARE the game. You maintain the world state, describe rooms, handle puzzles, manage inventory, track score, and respond to player commands.
|
|
4
|
+
|
|
5
|
+
GAME WORLD RULES:
|
|
6
|
+
- The game is an original underground adventure. Invent the world once, then keep it consistent for the whole session
|
|
7
|
+
- The map includes: Cave Mouth, the Pine Forest around it, a Narrow Ledge, the Echoing Hall, an Underground River, the Flooded Gallery, the Crystal Grotto, the Old Miners' Camp, a maze of twisting tunnels, and a sealed Vault deep below
|
|
8
|
+
- Key items: the rusted lantern (at the entrance), a coil of rope, a miner's pick, a silver key, a jeweled scarab, a golden chalice, an ancient crown, a carved jade idol
|
|
9
|
+
- Key encounters: a hulking cave troll guarding a bridge, a hooded scavenger who prowls the tunnels and steals unattended treasure, a colony of bats, something large asleep in the Vault
|
|
10
|
+
- Puzzles gate progress: crossing the underground river, draining the Flooded Gallery, unlocking the Vault with the silver key, finding your way through the tunnel maze
|
|
11
|
+
- Score increases when the player finds treasures and carries them back to the stone pedestal at the Cave Mouth
|
|
12
|
+
- The lantern has limited fuel. Underground rooms are pitch dark without it — wandering in darkness is dangerous
|
|
13
|
+
|
|
14
|
+
VOICE-FIRST RESPONSE RULES:
|
|
15
|
+
- Describe rooms vividly but concisely — two to four sentences max
|
|
16
|
+
- For movement, describe the new room immediately
|
|
17
|
+
- For failed actions, give brief, witty responses in classic adventure style ("There is a wall in the way." or "You can't eat that.")
|
|
18
|
+
- Read inventory as a spoken list
|
|
19
|
+
- Announce score changes
|
|
20
|
+
- Keep a dry, understated sense of humor
|
|
21
|
+
- Never use visual formatting — no bullets, no bold, no lists with dashes
|
|
22
|
+
- Use "First... Then... Finally..." for sequences
|
|
23
|
+
- Use directional words naturally: "To the north you see..." not "N: forest"
|
|
24
|
+
|
|
25
|
+
COMMAND INTERPRETATION:
|
|
26
|
+
- Players speak naturally. Translate their voice into classic adventure commands
|
|
27
|
+
- "go north" / "head north" / "walk north" = north
|
|
28
|
+
- "pick up the rope" / "grab the rope" / "take rope" = take rope
|
|
29
|
+
- "what do I have" / "check my stuff" / "inventory" = inventory
|
|
30
|
+
- "where am I" / "look around" / "describe the room" = look
|
|
31
|
+
- "hit the troll" / "fight the troll" / "attack troll" = attack troll with pick
|
|
32
|
+
- "what's my score" = score
|
|
33
|
+
- "start over" / "new game" / "restart" = restart
|
|
34
|
+
- Accept natural conversational commands and map them to game actions
|
|
35
|
+
|
|
36
|
+
Use the game state tools to track inventory, location, score, and flags. Use game_state_get to read the current state, game_state_move to change rooms, game_state_take to pick up items, game_state_drop to drop items, game_state_score to add points, game_state_flag to set game flags, and game_state_history to log commands. When the player asks to restart, quit, or start a new game, call game_state_restart, then narrate the opening scene again. Always update state when the player takes an item, moves rooms, or triggers an event. Check state before responding to ensure consistency.
|
|
37
|
+
|
|
38
|
+
ATMOSPHERE:
|
|
39
|
+
- Underground areas should feel dark and foreboding with the lantern lit, and terrifying in pitch blackness
|
|
40
|
+
- The hooded scavenger should appear unpredictably and make off with dropped treasure
|
|
41
|
+
- The troll blocks the bridge until dealt with — by wits or by force
|
|
42
|
+
- Convey a sense of mystery and danger
|
|
43
|
+
- Keep the wry, understated humor of the classic text adventures
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { agent, assemblyAIPipeline } from "@alexkroman1/aai";
|
|
2
|
+
import { assemblyAI as assemblyAILlm } from "@alexkroman1/aai/llm";
|
|
3
|
+
import systemPrompt from "./system-prompt.md?raw";
|
|
4
|
+
|
|
5
|
+
export default agent({
|
|
6
|
+
name: "Math Buddy",
|
|
7
|
+
...assemblyAIPipeline(),
|
|
8
|
+
// Overriding the LLM stage: this tutor delegates every calculation to
|
|
9
|
+
// run_code, so it needs turn-taking speed more than reasoning depth —
|
|
10
|
+
// and Flash-Lite is both quicker and cheaper than the preset's default.
|
|
11
|
+
llm: assemblyAILlm({ model: "gemini-2.5-flash-lite" }),
|
|
12
|
+
systemPrompt,
|
|
13
|
+
greeting:
|
|
14
|
+
"Hey, I'm Math Buddy. Try asking me something like, what's 127 times 849, convert 5 miles to kilometers, or roll 3 twenty-sided dice.",
|
|
15
|
+
builtinTools: ["run_code"],
|
|
16
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
You are Math Buddy, a friendly math assistant. You help with calculations,
|
|
2
|
+
unit conversions, dice rolls, and random number generation. Keep answers short and clear.
|
|
3
|
+
When doing multi-step math, show your work briefly.
|
|
4
|
+
|
|
5
|
+
Use run_code for ALL calculations. Write JavaScript using console.log() for output.
|
|
6
|
+
|
|
7
|
+
Examples:
|
|
8
|
+
- Math expressions: console.log((12 + 8) * 3) or console.log(Math.sqrt(144))
|
|
9
|
+
- Unit conversions: convert using known factors (1 km = 0.621371 mi, 1 lb = 0.453592 kg, etc.)
|
|
10
|
+
- Temperature: C to F = (c * 9/5) + 32, F to C = (f - 32) * 5/9, C to K = c + 273.15
|
|
11
|
+
- Dice rolls: console.log(Array.from({length: N}, () => Math.floor(Math.random() * sides) + 1))
|
|
12
|
+
- Random numbers: console.log(Math.floor(Math.random() * (max - min + 1)) + min)
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { agent, assemblyAIPipeline, tool } from "@alexkroman1/aai";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { CATEGORIES, type Category, MOODS, type Mood } from "./shared.ts";
|
|
4
|
+
|
|
5
|
+
const PICKS: Record<Category, Record<Mood, string[]>> = {
|
|
6
|
+
movie: {
|
|
7
|
+
chill: ["Lost in Translation", "The Grand Budapest Hotel", "Amelie"],
|
|
8
|
+
intense: ["Inception", "Interstellar", "The Dark Knight"],
|
|
9
|
+
cozy: ["When Harry Met Sally", "The Holiday", "Paddington 2"],
|
|
10
|
+
spooky: ["The Shining", "Get Out", "Hereditary"],
|
|
11
|
+
funny: ["The Big Lebowski", "Airplane!", "Superbad"],
|
|
12
|
+
},
|
|
13
|
+
music: {
|
|
14
|
+
chill: ["Khruangbin — Con Todo El Mundo", "Tycho — Dive", "Bonobo — Migration"],
|
|
15
|
+
intense: ["Radiohead — OK Computer", "Tool — Lateralus", "Deftones — White Pony"],
|
|
16
|
+
cozy: [
|
|
17
|
+
"Norah Jones — Come Away with Me",
|
|
18
|
+
"Iron & Wine — Our Endless Numbered Days",
|
|
19
|
+
"Bon Iver — For Emma, Forever Ago",
|
|
20
|
+
],
|
|
21
|
+
spooky: [
|
|
22
|
+
"Portishead — Dummy",
|
|
23
|
+
"Massive Attack — Mezzanine",
|
|
24
|
+
"Boards of Canada — Music Has the Right to Children",
|
|
25
|
+
],
|
|
26
|
+
funny: [
|
|
27
|
+
"Weird Al — Running with Scissors",
|
|
28
|
+
"Flight of the Conchords — S/T",
|
|
29
|
+
"Tenacious D — S/T",
|
|
30
|
+
],
|
|
31
|
+
},
|
|
32
|
+
book: {
|
|
33
|
+
chill: ["Norwegian Wood — Murakami", "The Alchemist — Coelho", "Siddhartha — Hesse"],
|
|
34
|
+
intense: ["Blood Meridian — McCarthy", "House of Leaves — Danielewski", "Neuromancer — Gibson"],
|
|
35
|
+
cozy: [
|
|
36
|
+
"The House in the Cerulean Sea — Klune",
|
|
37
|
+
"A Man Called Ove — Backman",
|
|
38
|
+
"Anxious People — Backman",
|
|
39
|
+
],
|
|
40
|
+
spooky: [
|
|
41
|
+
"The Haunting of Hill House — Jackson",
|
|
42
|
+
"Mexican Gothic — Moreno-Garcia",
|
|
43
|
+
"The Turn of the Screw — James",
|
|
44
|
+
],
|
|
45
|
+
funny: ["Good Omens — Pratchett & Gaiman", "Hitchhiker's Guide — Adams", "Catch-22 — Heller"],
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export default agent({
|
|
50
|
+
name: "Night Owl",
|
|
51
|
+
...assemblyAIPipeline(),
|
|
52
|
+
systemPrompt:
|
|
53
|
+
"You are Night Owl, a cozy evening companion. You help people wind down, recommend entertainment, and share interesting facts about the night sky. Keep your tone warm and relaxed. Use short, conversational responses.\n\nUse run_code for sleep calculations:\n- Each sleep cycle is 90 minutes, plus 15 minutes to fall asleep\n- Bedtime = wake_time - (cycles * 90 + 15) minutes\n- If result is negative, add 1440 (24 hours in minutes)\n- Format as HH:MM",
|
|
54
|
+
greeting:
|
|
55
|
+
"Hey there, night owl. Try asking me for a cozy movie recommendation, or tell me what time you need to wake up and I'll calculate the best time to fall asleep.",
|
|
56
|
+
builtinTools: ["run_code"],
|
|
57
|
+
|
|
58
|
+
tools: {
|
|
59
|
+
recommend: tool({
|
|
60
|
+
description: "Get recommendations for movies, music, or books based on mood.",
|
|
61
|
+
parameters: z.object({
|
|
62
|
+
category: z.enum(CATEGORIES),
|
|
63
|
+
mood: z.enum(MOODS),
|
|
64
|
+
}),
|
|
65
|
+
async execute(args, ctx) {
|
|
66
|
+
const result = {
|
|
67
|
+
category: args.category,
|
|
68
|
+
mood: args.mood,
|
|
69
|
+
picks: PICKS[args.category][args.mood],
|
|
70
|
+
};
|
|
71
|
+
ctx.send("recommendations", result);
|
|
72
|
+
return result;
|
|
73
|
+
},
|
|
74
|
+
}),
|
|
75
|
+
},
|
|
76
|
+
});
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import "@alexkroman1/aai-ui/styles.css";
|
|
2
|
+
import { Button, client, useEvent, useTheme, useToolCallStart } from "@alexkroman1/aai-ui";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { MOODS } from "./shared.ts";
|
|
5
|
+
|
|
6
|
+
type Rec = { category: string; mood: string; picks: string[] };
|
|
7
|
+
|
|
8
|
+
const MOOD_EMOJI: Record<string, string> = {
|
|
9
|
+
chill: "\u{1F60C}",
|
|
10
|
+
intense: "\u{1F525}",
|
|
11
|
+
cozy: "\u{1F9E3}",
|
|
12
|
+
spooky: "\u{1F47B}",
|
|
13
|
+
funny: "\u{1F602}",
|
|
14
|
+
};
|
|
15
|
+
const CAT_EMOJI: Record<string, string> = {
|
|
16
|
+
movie: "\u{1F3AC}",
|
|
17
|
+
music: "\u{1F3B5}",
|
|
18
|
+
book: "\u{1F4DA}",
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
function RecSidebar() {
|
|
22
|
+
const theme = useTheme();
|
|
23
|
+
const [recs, setRecs] = useState<Rec[]>([]);
|
|
24
|
+
const [activeMood, setActiveMood] = useState<string | null>(null);
|
|
25
|
+
|
|
26
|
+
useToolCallStart("recommend", (tc) => {
|
|
27
|
+
setActiveMood(tc.args.mood as string);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
useEvent("recommendations", (result) => {
|
|
31
|
+
setRecs((prev) => [result as Rec, ...prev]);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const filtered = activeMood ? recs.filter((r) => r.mood === activeMood) : recs;
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<div
|
|
38
|
+
className="flex flex-col h-full text-sm"
|
|
39
|
+
style={{ background: theme.bg, color: theme.text }}
|
|
40
|
+
>
|
|
41
|
+
<div className="px-4 py-3 border-b shrink-0" style={{ borderColor: theme.border }}>
|
|
42
|
+
<h2
|
|
43
|
+
className="text-xs font-bold uppercase tracking-wide opacity-60"
|
|
44
|
+
style={{ color: theme.text }}
|
|
45
|
+
>
|
|
46
|
+
Recommendations
|
|
47
|
+
</h2>
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
<div
|
|
51
|
+
className="flex flex-wrap gap-1.5 px-3 py-2.5 border-b shrink-0"
|
|
52
|
+
style={{ borderColor: theme.border }}
|
|
53
|
+
>
|
|
54
|
+
{MOODS.map((mood) => (
|
|
55
|
+
<Button
|
|
56
|
+
key={mood}
|
|
57
|
+
variant={activeMood === mood ? "default" : "ghost"}
|
|
58
|
+
onClick={() => setActiveMood(activeMood === mood ? null : mood)}
|
|
59
|
+
>
|
|
60
|
+
{MOOD_EMOJI[mood]} {mood}
|
|
61
|
+
</Button>
|
|
62
|
+
))}
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
<div className="flex-1 overflow-y-auto px-3 py-2">
|
|
66
|
+
{filtered.length === 0 && (
|
|
67
|
+
<p className="text-xs text-center py-8 opacity-40" style={{ color: theme.text }}>
|
|
68
|
+
Ask me to recommend a movie, album, or book
|
|
69
|
+
</p>
|
|
70
|
+
)}
|
|
71
|
+
{filtered.map((rec, i) => (
|
|
72
|
+
<div
|
|
73
|
+
key={`${rec.category}-${rec.mood}-${i}`}
|
|
74
|
+
className="mb-3 p-2.5 rounded-lg border"
|
|
75
|
+
style={{ background: theme.surface, borderColor: theme.border }}
|
|
76
|
+
>
|
|
77
|
+
<div className="flex items-center gap-2 mb-1.5">
|
|
78
|
+
<span className="text-xs">{CAT_EMOJI[rec.category]}</span>
|
|
79
|
+
<span className="text-xs font-semibold capitalize" style={{ color: theme.primary }}>
|
|
80
|
+
{rec.category}s
|
|
81
|
+
</span>
|
|
82
|
+
<span className="text-xs capitalize opacity-50" style={{ color: theme.text }}>
|
|
83
|
+
{rec.mood}
|
|
84
|
+
</span>
|
|
85
|
+
</div>
|
|
86
|
+
{rec.picks.map((pick) => (
|
|
87
|
+
<p
|
|
88
|
+
key={pick}
|
|
89
|
+
className="text-xs pl-5 py-0.5 opacity-80"
|
|
90
|
+
style={{ color: theme.text }}
|
|
91
|
+
>
|
|
92
|
+
{pick}
|
|
93
|
+
</p>
|
|
94
|
+
))}
|
|
95
|
+
</div>
|
|
96
|
+
))}
|
|
97
|
+
</div>
|
|
98
|
+
|
|
99
|
+
{recs.length > 0 && (
|
|
100
|
+
<div className="px-3 py-2 border-t shrink-0" style={{ borderColor: theme.border }}>
|
|
101
|
+
<Button
|
|
102
|
+
variant="ghost"
|
|
103
|
+
className="w-full"
|
|
104
|
+
onClick={() => {
|
|
105
|
+
setRecs([]);
|
|
106
|
+
setActiveMood(null);
|
|
107
|
+
}}
|
|
108
|
+
>
|
|
109
|
+
Clear
|
|
110
|
+
</Button>
|
|
111
|
+
</div>
|
|
112
|
+
)}
|
|
113
|
+
</div>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
client({
|
|
118
|
+
name: "Night Owl",
|
|
119
|
+
sidebar: RecSidebar,
|
|
120
|
+
theme: {
|
|
121
|
+
bg: "#0c0e1a",
|
|
122
|
+
primary: "#a78bfa",
|
|
123
|
+
text: "#e2e0f0",
|
|
124
|
+
surface: "#131627",
|
|
125
|
+
border: "#1e2340",
|
|
126
|
+
},
|
|
127
|
+
tools: {
|
|
128
|
+
recommend: { icon: "\u{1F989}", label: "Recommending" },
|
|
129
|
+
},
|
|
130
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { agent, assemblyAIPipeline } from "@alexkroman1/aai";
|
|
2
|
+
import systemPrompt from "./system-prompt.md?raw";
|
|
3
|
+
|
|
4
|
+
export default agent({
|
|
5
|
+
name: "Penny",
|
|
6
|
+
...assemblyAIPipeline(),
|
|
7
|
+
systemPrompt,
|
|
8
|
+
greeting:
|
|
9
|
+
"Hey, I'm Penny, your personal finance helper. Try asking me something like, what's 100 dollars in euros, what's the price of bitcoin, or help me split a 120 dollar bill four ways with 20 percent tip.",
|
|
10
|
+
builtinTools: ["run_code", "fetch_json"],
|
|
11
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
You are Penny, a friendly personal finance assistant. You help people with currency conversions, cryptocurrency prices, loan calculations, savings projections, and splitting bills.
|
|
2
|
+
|
|
3
|
+
Rules:
|
|
4
|
+
- Always show your math clearly when explaining calculations
|
|
5
|
+
- When discussing investments or crypto, remind users that prices fluctuate and this is not financial advice
|
|
6
|
+
- Be encouraging about savings goals
|
|
7
|
+
- Keep responses concise — this is a voice conversation
|
|
8
|
+
- Round dollar amounts to two decimal places for clarity
|
|
9
|
+
|
|
10
|
+
API endpoints (use fetch_json):
|
|
11
|
+
- Currency rates: https://open.er-api.com/v6/latest/{CODE} — returns { rates: { USD: 1.0, EUR: 0.85, ... } }
|
|
12
|
+
- Crypto prices: https://api.coingecko.com/api/v3/simple/price?ids={coin}&vs_currencies={cur}&include_24hr_change=true&include_market_cap=true
|
|
13
|
+
|
|
14
|
+
Math calculations (use run_code):
|
|
15
|
+
- Compound interest: FV = principal * (1 + rate/n)^(n*years) + monthly * ((1 + rate/n)^(n*years) - 1) / (rate/n)
|
|
16
|
+
- Loan payment: M = P * (r(1+r)^n) / ((1+r)^n - 1) where r = annual_rate/12, n = years*12
|
|
17
|
+
- Tip calculator: tip = bill * percent/100, per_person = (bill + tip) / people
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { toAgentConfig } from "@alexkroman1/aai/manifest";
|
|
2
|
+
import { describe, expect, test } from "vitest";
|
|
3
|
+
import agentDef from "./agent.ts";
|
|
4
|
+
|
|
5
|
+
describe("pipeline-simple template", () => {
|
|
6
|
+
test("config passes manifest validation", () => {
|
|
7
|
+
// Same conversion `aai build`/`aai deploy` run — catches a partial
|
|
8
|
+
// provider triple or invalid tuning, not just descriptor presence.
|
|
9
|
+
expect(() => toAgentConfig(agentDef)).not.toThrow();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test("exports an agent with all three pipeline providers", () => {
|
|
13
|
+
// Presence of stt + llm + tts is what flips the runtime into
|
|
14
|
+
// pipeline mode (see parseManifest). Smoke-test each is wired up.
|
|
15
|
+
expect(agentDef.name).toBe("pipeline-simple");
|
|
16
|
+
expect(agentDef.stt).toBeDefined();
|
|
17
|
+
expect(agentDef.llm).toBeDefined();
|
|
18
|
+
expect(agentDef.tts).toBeDefined();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("STT descriptor is AssemblyAI", () => {
|
|
22
|
+
expect(agentDef.stt?.kind).toBe("assemblyai");
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("LLM descriptor is Anthropic", () => {
|
|
26
|
+
expect(agentDef.llm?.kind).toBe("anthropic");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("TTS descriptor is AssemblyAI", () => {
|
|
30
|
+
expect(agentDef.tts?.kind).toBe("assemblyai");
|
|
31
|
+
});
|
|
32
|
+
});
|