create-spatius-app 0.1.0-beta.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.
Files changed (96) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +57 -0
  3. package/dist/cli.js +9046 -0
  4. package/dist/cli.js.map +1 -0
  5. package/dist/templates.js +2856 -0
  6. package/dist/templates.js.map +1 -0
  7. package/package.json +79 -0
  8. package/schemas/result-v1.schema.json +129 -0
  9. package/schemas/result-v2.schema.json +146 -0
  10. package/templates/cloudflare-livekit/.editorconfig +15 -0
  11. package/templates/cloudflare-livekit/.prettierignore +6 -0
  12. package/templates/cloudflare-livekit/.prettierrc.json +4 -0
  13. package/templates/cloudflare-livekit/AGENTS.md +41 -0
  14. package/templates/cloudflare-livekit/README.md +37 -0
  15. package/templates/cloudflare-livekit/agent/Dockerfile.pip +31 -0
  16. package/templates/cloudflare-livekit/agent/Dockerfile.uv +29 -0
  17. package/templates/cloudflare-livekit/agent/README.md +84 -0
  18. package/templates/cloudflare-livekit/agent/dockerignore +11 -0
  19. package/templates/cloudflare-livekit/agent/env.example +8 -0
  20. package/templates/cloudflare-livekit/agent/pyproject.toml +21 -0
  21. package/templates/cloudflare-livekit/agent/python-version +1 -0
  22. package/templates/cloudflare-livekit/agent/src/__init__.py +1 -0
  23. package/templates/cloudflare-livekit/agent/src/agent.py +97 -0
  24. package/templates/cloudflare-livekit/agent/src/dispatch_metadata.py +64 -0
  25. package/templates/cloudflare-livekit/agent/tests/test_agent_startup.py +211 -0
  26. package/templates/cloudflare-livekit/agent/tests/test_dispatch_metadata.py +97 -0
  27. package/templates/cloudflare-livekit/agent/uv.lock +2272 -0
  28. package/templates/cloudflare-livekit/components.json +23 -0
  29. package/templates/cloudflare-livekit/contracts/agent-dispatch-metadata.schema.json +40 -0
  30. package/templates/cloudflare-livekit/dev.vars.example +12 -0
  31. package/templates/cloudflare-livekit/eslint.config.mjs +41 -0
  32. package/templates/cloudflare-livekit/gitignore +30 -0
  33. package/templates/cloudflare-livekit/index.html +17 -0
  34. package/templates/cloudflare-livekit/npmrc +4 -0
  35. package/templates/cloudflare-livekit/package-lock.json +6514 -0
  36. package/templates/cloudflare-livekit/package.json +66 -0
  37. package/templates/cloudflare-livekit/playwright.config.ts +29 -0
  38. package/templates/cloudflare-livekit/pnpm-lock.yaml +4012 -0
  39. package/templates/cloudflare-livekit/pnpm-workspace.yaml +8 -0
  40. package/templates/cloudflare-livekit/scripts/agent-runtime.mjs +129 -0
  41. package/templates/cloudflare-livekit/scripts/command.mjs +52 -0
  42. package/templates/cloudflare-livekit/scripts/dev-agent.mjs +77 -0
  43. package/templates/cloudflare-livekit/scripts/dev-web.mjs +18 -0
  44. package/templates/cloudflare-livekit/scripts/managed-process.mjs +144 -0
  45. package/templates/cloudflare-livekit/scripts/process-tree.mjs +111 -0
  46. package/templates/cloudflare-livekit/scripts/run-agent-python.mjs +25 -0
  47. package/templates/cloudflare-livekit/tests/dev-agent.test.mjs +327 -0
  48. package/templates/cloudflare-livekit/tests/dev-fixtures.mjs +235 -0
  49. package/templates/cloudflare-livekit/tests/dev-supervision.test.mjs +203 -0
  50. package/templates/cloudflare-livekit/tests/e2e/conversation.spec.ts +376 -0
  51. package/templates/cloudflare-livekit/tests/e2e/fake-avatar.ts +24 -0
  52. package/templates/cloudflare-livekit/tests/e2e/fake-components.tsx +173 -0
  53. package/templates/cloudflare-livekit/tests/e2e/fake-livekit.ts +123 -0
  54. package/templates/cloudflare-livekit/tests/e2e/harness.html +12 -0
  55. package/templates/cloudflare-livekit/tests/e2e/harness.tsx +1 -0
  56. package/templates/cloudflare-livekit/tests/e2e/vite.config.ts +48 -0
  57. package/templates/cloudflare-livekit/tests/web/api.test.ts +64 -0
  58. package/templates/cloudflare-livekit/tests/web/app.test.tsx +125 -0
  59. package/templates/cloudflare-livekit/tests/web/audio-visualizer.test.tsx +53 -0
  60. package/templates/cloudflare-livekit/tests/web/avatar-session.test.ts +119 -0
  61. package/templates/cloudflare-livekit/tests/web/components.test.tsx +152 -0
  62. package/templates/cloudflare-livekit/tests/web/conversation.test.tsx +76 -0
  63. package/templates/cloudflare-livekit/tests/web/session-attempt.test.ts +178 -0
  64. package/templates/cloudflare-livekit/tsconfig.json +9 -0
  65. package/templates/cloudflare-livekit/tsconfig.node.json +23 -0
  66. package/templates/cloudflare-livekit/tsconfig.web.json +26 -0
  67. package/templates/cloudflare-livekit/tsconfig.worker.json +22 -0
  68. package/templates/cloudflare-livekit/vite.config.ts +49 -0
  69. package/templates/cloudflare-livekit/vitest.config.ts +11 -0
  70. package/templates/cloudflare-livekit/web/App.tsx +176 -0
  71. package/templates/cloudflare-livekit/web/api.ts +69 -0
  72. package/templates/cloudflare-livekit/web/assets/FONT-PROVENANCE.md +8 -0
  73. package/templates/cloudflare-livekit/web/assets/OFL.txt +92 -0
  74. package/templates/cloudflare-livekit/web/assets/inter-latin.woff2 +0 -0
  75. package/templates/cloudflare-livekit/web/avatar-session.ts +67 -0
  76. package/templates/cloudflare-livekit/web/components/agents-ui/LICENSE +201 -0
  77. package/templates/cloudflare-livekit/web/components/agents-ui/PROVENANCE.md +32 -0
  78. package/templates/cloudflare-livekit/web/components/agents-ui/agent-audio-visualizer-bar.tsx +43 -0
  79. package/templates/cloudflare-livekit/web/components/agents-ui/agent-chat-input.tsx +78 -0
  80. package/templates/cloudflare-livekit/web/components/agents-ui/agent-chat-transcript.tsx +145 -0
  81. package/templates/cloudflare-livekit/web/components/agents-ui/agent-disconnect-button.tsx +27 -0
  82. package/templates/cloudflare-livekit/web/components/agents-ui/agent-session-provider.tsx +29 -0
  83. package/templates/cloudflare-livekit/web/components/agents-ui/agent-track-toggle.tsx +51 -0
  84. package/templates/cloudflare-livekit/web/components/agents-ui/start-audio-button.tsx +13 -0
  85. package/templates/cloudflare-livekit/web/components/conversation.tsx +352 -0
  86. package/templates/cloudflare-livekit/web/components/icons.tsx +39 -0
  87. package/templates/cloudflare-livekit/web/components/transcript-panel.tsx +12 -0
  88. package/templates/cloudflare-livekit/web/main.tsx +17 -0
  89. package/templates/cloudflare-livekit/web/session-attempt.ts +110 -0
  90. package/templates/cloudflare-livekit/web/styles.css +571 -0
  91. package/templates/cloudflare-livekit/worker/dispatch-metadata.ts +22 -0
  92. package/templates/cloudflare-livekit/worker/index.test.ts +152 -0
  93. package/templates/cloudflare-livekit/worker/index.ts +61 -0
  94. package/templates/cloudflare-livekit/worker/session.ts +103 -0
  95. package/templates/cloudflare-livekit/worker-configuration.d.ts +17184 -0
  96. package/templates/cloudflare-livekit/wrangler.jsonc +27 -0
@@ -0,0 +1,43 @@
1
+ // Copyright LiveKit. Apache-2.0. Adapted from Agents UI; see ./PROVENANCE.md.
2
+ import {
3
+ useMultibandTrackVolume,
4
+ type TrackReferenceOrPlaceholder,
5
+ } from '@livekit/components-react';
6
+ import type { LocalAudioTrack } from 'livekit-client';
7
+
8
+ /** Microphone-only Agents UI bar visualizer. No simulated agent-state animation. */
9
+ export function AgentAudioVisualizerBar({
10
+ audioTrack,
11
+ enabled,
12
+ }: {
13
+ audioTrack?: LocalAudioTrack | TrackReferenceOrPlaceholder;
14
+ enabled: boolean;
15
+ }) {
16
+ const bands = useMultibandTrackVolume(enabled ? audioTrack : undefined, {
17
+ bands: 5,
18
+ loPass: 100,
19
+ hiPass: 200,
20
+ });
21
+ return (
22
+ <div
23
+ className="user-audio"
24
+ role="group"
25
+ aria-label="Your microphone activity"
26
+ >
27
+ <span>You</span>
28
+ <div className="audio-bars" aria-hidden="true" data-enabled={enabled}>
29
+ {Array.from({ length: 5 }, (_, index) => (
30
+ <span
31
+ key={index}
32
+ style={{
33
+ height: `${Math.max(4, (enabled ? Math.min(1, Math.max(0, bands[index] ?? 0)) : 0) * 24)}px`,
34
+ }}
35
+ />
36
+ ))}
37
+ </div>
38
+ <span className="audio-label">
39
+ {enabled ? 'Microphone on' : 'Microphone off'}
40
+ </span>
41
+ </div>
42
+ );
43
+ }
@@ -0,0 +1,78 @@
1
+ // Copyright LiveKit. Apache-2.0. Extracted/adapted from AgentControlBar; see ./PROVENANCE.md.
2
+ import { useRef, useState } from 'react';
3
+ import { Icon } from '../icons.js';
4
+ export function AgentChatInput({
5
+ disabled = false,
6
+ onSend,
7
+ }: {
8
+ disabled?: boolean;
9
+ onSend: (message: string) => Promise<unknown>;
10
+ }) {
11
+ const [message, setMessage] = useState('');
12
+ const [isSending, setIsSending] = useState(false);
13
+ const [error, setError] = useState('');
14
+ const sending = useRef(false);
15
+ const isDisabled = disabled || isSending || !message.trim();
16
+ const handleSend = async () => {
17
+ if (isDisabled || sending.current) return;
18
+ sending.current = true;
19
+ setIsSending(true);
20
+ setError('');
21
+ try {
22
+ await onSend(message.trim());
23
+ setMessage('');
24
+ } catch {
25
+ setError('Message not sent. Your text is still here — try again.');
26
+ } finally {
27
+ sending.current = false;
28
+ setIsSending(false);
29
+ }
30
+ };
31
+ return (
32
+ <div className="chat-input-group">
33
+ <form
34
+ className="chat-input"
35
+ onSubmit={(event) => {
36
+ event.preventDefault();
37
+ void handleSend();
38
+ }}
39
+ >
40
+ <label className="sr-only" htmlFor="conversation-message">
41
+ Message your agent
42
+ </label>
43
+ <textarea
44
+ id="conversation-message"
45
+ rows={1}
46
+ value={message}
47
+ disabled={disabled || isSending}
48
+ placeholder="Type a message…"
49
+ onChange={(event) => setMessage(event.target.value)}
50
+ onKeyDown={(event) => {
51
+ if (
52
+ event.key === 'Enter' &&
53
+ !event.shiftKey &&
54
+ !event.nativeEvent.isComposing &&
55
+ event.keyCode !== 229
56
+ ) {
57
+ event.preventDefault();
58
+ void handleSend();
59
+ }
60
+ }}
61
+ />
62
+ <button
63
+ className="send-button"
64
+ type="submit"
65
+ aria-label={isSending ? 'Sending message' : 'Send message'}
66
+ disabled={isDisabled}
67
+ >
68
+ <Icon name="send" />
69
+ </button>
70
+ </form>
71
+ {error && (
72
+ <p role="alert" className="control-error">
73
+ {error}
74
+ </p>
75
+ )}
76
+ </div>
77
+ );
78
+ }
@@ -0,0 +1,145 @@
1
+ // Copyright LiveKit. Apache-2.0. Adapted from Agents UI; see ./PROVENANCE.md.
2
+ import { useLayoutEffect, useRef, useState } from 'react';
3
+ import type { AgentState, ReceivedMessage } from '@livekit/components-react';
4
+ export type TranscriptMessage = Pick<
5
+ ReceivedMessage,
6
+ 'id' | 'timestamp' | 'message'
7
+ > & {
8
+ isUser: boolean;
9
+ source?: 'user' | 'agent' | 'avatar';
10
+ senderIdentity?: string;
11
+ };
12
+ export type ConversationParticipants = {
13
+ agentIdentity?: string;
14
+ avatarIdentity?: string;
15
+ };
16
+ export function snapshotMessages(
17
+ messages: ReceivedMessage[],
18
+ participants: ConversationParticipants = {},
19
+ previous: readonly TranscriptMessage[] = [],
20
+ ): TranscriptMessage[] {
21
+ const previousById = new Map(
22
+ previous.map((message) => [message.id, message]),
23
+ );
24
+ return messages.flatMap(({ id, timestamp, message, from, type }) => {
25
+ if (!message.trim()) return [];
26
+ const isUser = type === 'userTranscript' || Boolean(from?.isLocal);
27
+ // The SDK can lose `from` after a participant leaves. Retain attribution by
28
+ // stream ID so the final update and interrupted text still replace in place.
29
+ const prior = previousById.get(id);
30
+ const senderIdentity = from?.identity ?? prior?.senderIdentity;
31
+ const isAvatar =
32
+ Boolean(
33
+ senderIdentity && senderIdentity === participants.avatarIdentity,
34
+ ) ||
35
+ Boolean(
36
+ participants.agentIdentity &&
37
+ from?.attributes?.['lk.publish_on_behalf'] ===
38
+ participants.agentIdentity,
39
+ );
40
+ if (
41
+ !isUser &&
42
+ senderIdentity &&
43
+ participants.agentIdentity &&
44
+ senderIdentity !== participants.agentIdentity &&
45
+ !isAvatar
46
+ )
47
+ return [];
48
+ // The avatar relays speech; service chat isn't a voice-agent response.
49
+ if (isAvatar && type !== 'agentTranscript') return [];
50
+ return [
51
+ {
52
+ id,
53
+ timestamp,
54
+ message,
55
+ isUser,
56
+ senderIdentity,
57
+ source: isUser
58
+ ? 'user'
59
+ : isAvatar
60
+ ? 'avatar'
61
+ : (prior?.source ?? 'agent'),
62
+ },
63
+ ];
64
+ });
65
+ }
66
+ export function AgentChatTranscript({
67
+ messages = [],
68
+ agentState,
69
+ agentName = 'Agent',
70
+ }: {
71
+ messages?: readonly TranscriptMessage[];
72
+ agentState?: AgentState;
73
+ agentName?: string;
74
+ }) {
75
+ const viewport = useRef<HTMLDivElement>(null);
76
+ const atBottom = useRef(true);
77
+ const [showLatest, setShowLatest] = useState(false);
78
+ const scrollToLatest = () => {
79
+ const element = viewport.current;
80
+ if (element) element.scrollTop = element.scrollHeight;
81
+ atBottom.current = true;
82
+ setShowLatest(false);
83
+ };
84
+ useLayoutEffect(() => {
85
+ if (atBottom.current) scrollToLatest();
86
+ else setShowLatest(true);
87
+ }, [messages, agentState]);
88
+ return (
89
+ <div className="transcript-container">
90
+ <div
91
+ ref={viewport}
92
+ className="transcript-scroll"
93
+ role="log"
94
+ aria-label="Conversation transcript"
95
+ aria-live="polite"
96
+ onScroll={(event) => {
97
+ const element = event.currentTarget;
98
+ atBottom.current =
99
+ element.scrollHeight - element.scrollTop - element.clientHeight <
100
+ 48;
101
+ if (atBottom.current) setShowLatest(false);
102
+ }}
103
+ >
104
+ {messages.length === 0 && (
105
+ <p className="transcript-empty">
106
+ Your conversation will appear here as you speak or type.
107
+ </p>
108
+ )}
109
+ {messages.map(({ id, timestamp, isUser, message, source }) => (
110
+ <article
111
+ key={id}
112
+ className="transcript-message"
113
+ data-speaker={isUser ? 'user' : 'agent'}
114
+ data-source={source}
115
+ >
116
+ <div className="message-meta">
117
+ <span>{isUser ? 'You' : agentName}</span>
118
+ <time dateTime={new Date(timestamp).toISOString()}>
119
+ {new Date(timestamp).toLocaleTimeString([], {
120
+ hour: '2-digit',
121
+ minute: '2-digit',
122
+ })}
123
+ </time>
124
+ </div>
125
+ <p>{message}</p>
126
+ </article>
127
+ ))}
128
+ {agentState === 'thinking' && (
129
+ <p className="thinking-indicator" role="status">
130
+ Thinking<span aria-hidden="true">…</span>
131
+ </p>
132
+ )}
133
+ </div>
134
+ {showLatest && (
135
+ <button
136
+ className="jump-to-latest"
137
+ type="button"
138
+ onClick={scrollToLatest}
139
+ >
140
+ Latest messages ↓
141
+ </button>
142
+ )}
143
+ </div>
144
+ );
145
+ }
@@ -0,0 +1,27 @@
1
+ // Copyright LiveKit. Apache-2.0. Adapted from Agents UI; see ./PROVENANCE.md.
2
+ import type { ComponentProps } from 'react';
3
+ import { useSessionContext } from '@livekit/components-react';
4
+ import { Icon } from '../icons.js';
5
+ export function AgentDisconnectButton({
6
+ onClick,
7
+ children,
8
+ ...props
9
+ }: ComponentProps<'button'>) {
10
+ const { end } = useSessionContext();
11
+ return (
12
+ <button
13
+ type="button"
14
+ className="disconnect-button"
15
+ aria-label="End conversation"
16
+ {...props}
17
+ onClick={(event) => {
18
+ onClick?.(event);
19
+ // The host prevents default to include avatar disposal and preserve messages.
20
+ if (!event.defaultPrevented) void end().catch(() => undefined);
21
+ }}
22
+ >
23
+ <Icon name="end" />
24
+ {children}
25
+ </button>
26
+ );
27
+ }
@@ -0,0 +1,29 @@
1
+ // Copyright LiveKit. Apache-2.0. Upstream source and adaptations: ./PROVENANCE.md.
2
+ import {
3
+ SessionProvider,
4
+ type UseSessionReturn,
5
+ RoomAudioRenderer,
6
+ type SessionProviderProps,
7
+ type RoomAudioRendererProps,
8
+ } from '@livekit/components-react';
9
+ import type { Room } from 'livekit-client';
10
+ export type AgentSessionProviderProps = SessionProviderProps &
11
+ RoomAudioRendererProps & {
12
+ room?: Room;
13
+ volume?: number;
14
+ muted?: boolean;
15
+ session: UseSessionReturn;
16
+ children: React.ReactNode;
17
+ };
18
+ export function AgentSessionProvider({
19
+ session,
20
+ children,
21
+ ...roomAudioRendererProps
22
+ }: AgentSessionProviderProps) {
23
+ return (
24
+ <SessionProvider session={session}>
25
+ {children}
26
+ <RoomAudioRenderer {...roomAudioRendererProps} />
27
+ </SessionProvider>
28
+ );
29
+ }
@@ -0,0 +1,51 @@
1
+ // Copyright LiveKit. Apache-2.0. Adapted from Agents UI; see ./PROVENANCE.md.
2
+ import { useState, type ComponentProps } from 'react';
3
+ import { Icon } from '../icons.js';
4
+ export type AgentTrackToggleProps = Omit<
5
+ ComponentProps<'button'>,
6
+ 'onChange'
7
+ > & {
8
+ pressed?: boolean;
9
+ defaultPressed?: boolean;
10
+ pending?: boolean;
11
+ onPressedChange?: (pressed: boolean) => void;
12
+ };
13
+ export function AgentTrackToggle({
14
+ pressed,
15
+ defaultPressed = false,
16
+ pending = false,
17
+ onPressedChange,
18
+ disabled,
19
+ ...props
20
+ }: AgentTrackToggleProps) {
21
+ const [uncontrolledPressed, setUncontrolledPressed] =
22
+ useState(defaultPressed);
23
+ const isControlled = pressed !== undefined;
24
+ const resolvedPressed = pressed ?? uncontrolledPressed;
25
+ const handlePressedChange = () => {
26
+ if (!isControlled) setUncontrolledPressed(!resolvedPressed);
27
+ onPressedChange?.(!resolvedPressed);
28
+ };
29
+ return (
30
+ <button
31
+ type="button"
32
+ className="microphone-toggle"
33
+ aria-label={resolvedPressed ? 'Mute microphone' : 'Enable microphone'}
34
+ aria-pressed={resolvedPressed}
35
+ aria-busy={pending}
36
+ data-state={resolvedPressed ? 'on' : 'off'}
37
+ disabled={disabled || pending}
38
+ onClick={handlePressedChange}
39
+ {...props}
40
+ >
41
+ <Icon name={resolvedPressed ? 'mic' : 'mic-off'} />
42
+ <span>
43
+ {pending
44
+ ? 'Please wait'
45
+ : resolvedPressed
46
+ ? 'Microphone on'
47
+ : 'Microphone off'}
48
+ </span>
49
+ </button>
50
+ );
51
+ }
@@ -0,0 +1,13 @@
1
+ // Copyright LiveKit. Apache-2.0. Adapted from Agents UI; see ./PROVENANCE.md.
2
+ import { useStartAudio } from '@livekit/components-react';
3
+ import type { Room } from 'livekit-client';
4
+ import { Icon } from '../icons.js';
5
+ export function StartAudioButton({ room }: { room: Room }) {
6
+ const { mergedProps } = useStartAudio({ room, props: {} });
7
+ return (
8
+ <button type="button" {...mergedProps} className="audio-unlock">
9
+ <Icon name="audio" />
10
+ Enable sound
11
+ </button>
12
+ );
13
+ }