@inneranimalmedia/agentsam-sdk 1.5.0 → 1.6.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.
@@ -0,0 +1,315 @@
1
+ /**
2
+ * @file src/ui/theme.js
3
+ * @description IAM Terminal Theme System — single source of truth for
4
+ * per-lane color identity. Used by:
5
+ * - xterm.js panels in XTermShell.tsx (pass theme object to Terminal constructor)
6
+ * - CLI ANSI output in agentsam-sdk (import ANSI constants)
7
+ * - Splash/HUD renders in both contexts
8
+ *
9
+ * Lane identity:
10
+ * LOCAL → amber (samsmac tunnel, darwin)
11
+ * CLOUD → teal (GCP platform_vm, iam-pty)
12
+ * SANDBOX → purple (CF container, isolated exec)
13
+ *
14
+ * No dependencies. ESM + CJS compatible (conditional exports in package.json).
15
+ */
16
+
17
+ // ─────────────────────────────────────────────────────────────────────────────
18
+ // RAW COLOR PALETTE
19
+ // All hex values live here. Nothing else in the codebase hardcodes colors.
20
+ // ─────────────────────────────────────────────────────────────────────────────
21
+
22
+ export const PALETTE = {
23
+ // Shared blacks / neutrals (terminal voids)
24
+ void: '#080808',
25
+ void2: '#0d0d0d',
26
+ void3: '#121212',
27
+ surface: '#1a1a1a',
28
+ muted: '#3d3d3d',
29
+ dim: '#666666',
30
+ subtle: '#888888',
31
+ ghost: '#aaaaaa',
32
+ white: '#e8e8e8',
33
+
34
+ // LOCAL lane — amber/gold (samsmac, darwin_arm64)
35
+ amber900: '#0d0900',
36
+ amber800: '#1a1200',
37
+ amber700: '#2d1f00',
38
+ amber600: '#78450a',
39
+ amber500: '#b56a0f',
40
+ amber400: '#d4820f',
41
+ amber300: '#f59e0b', // PRIMARY amber
42
+ amber200: '#fbbf24',
43
+ amber100: '#fde68a',
44
+ amber050: '#fef3c7',
45
+
46
+ // CLOUD lane — teal/cyan (GCP, iam-pty, platform_vm)
47
+ teal900: '#000d0b',
48
+ teal800: '#001a17',
49
+ teal700: '#002e28',
50
+ teal600: '#065f52',
51
+ teal500: '#0d9488',
52
+ teal400: '#14b8a6',
53
+ teal300: '#22d3c8', // PRIMARY teal
54
+ teal200: '#5eead4',
55
+ teal100: '#99f6e4',
56
+ teal050: '#ccfbf1',
57
+
58
+ // SANDBOX lane — purple (CF container, isolated build exec)
59
+ purple900: '#06020d',
60
+ purple800: '#0d0520',
61
+ purple700: '#1e0a3d',
62
+ purple600: '#4c1d95',
63
+ purple500: '#6d28d9',
64
+ purple400: '#7c3aed',
65
+ purple300: '#a78bfa', // PRIMARY purple
66
+ purple200: '#c4b5fd',
67
+ purple100: '#ddd6fe',
68
+ purple050: '#ede9fe',
69
+
70
+ // Semantic
71
+ success: '#22c55e',
72
+ warning: '#f59e0b',
73
+ error: '#ef4444',
74
+ info: '#38bdf8',
75
+ connecting: '#fb923c',
76
+ };
77
+
78
+ // ─────────────────────────────────────────────────────────────────────────────
79
+ // LANE THEME OBJECTS
80
+ // ─────────────────────────────────────────────────────────────────────────────
81
+
82
+ const makeLane = ({ bg, bgSubtle, primary, secondary, dim, accent, name, label, icon, tunnelHint }) => ({
83
+ meta: { name, label, icon, tunnelHint },
84
+
85
+ xterm: {
86
+ background: bg,
87
+ foreground: '#e8e8e8',
88
+ cursor: primary,
89
+ cursorAccent: bg,
90
+ selectionBackground: primary + '44',
91
+ selectionForeground: '#e8e8e8',
92
+ black: '#080808',
93
+ brightBlack: '#3d3d3d',
94
+ red: '#ef4444',
95
+ brightRed: '#ff6b6b',
96
+ green: '#22c55e',
97
+ brightGreen: '#4ade80',
98
+ yellow: '#f59e0b',
99
+ brightYellow: '#fbbf24',
100
+ blue: '#38bdf8',
101
+ brightBlue: '#7dd3fc',
102
+ magenta: '#a78bfa',
103
+ brightMagenta: '#c4b5fd',
104
+ cyan: '#22d3c8',
105
+ brightCyan: '#5eead4',
106
+ white: '#aaaaaa',
107
+ brightWhite: '#e8e8e8',
108
+ minimumContrastRatio: 4.5,
109
+ },
110
+
111
+ ansi: {
112
+ reset: '\x1b[0m',
113
+ bold: '\x1b[1m',
114
+ dim: '\x1b[2m',
115
+ italic: '\x1b[3m',
116
+ underline: '\x1b[4m',
117
+ primary: `\x1b[38;2;${hexToRgb(primary)}m`,
118
+ secondary: `\x1b[38;2;${hexToRgb(secondary)}m`,
119
+ dimColor: `\x1b[38;2;${hexToRgb(dim)}m`,
120
+ accent: `\x1b[38;2;${hexToRgb(accent)}m`,
121
+ white: `\x1b[38;2;232;232;232m`,
122
+ ghost: `\x1b[38;2;136;136;136m`,
123
+ success: `\x1b[38;2;34;197;94m`,
124
+ warning: `\x1b[38;2;245;158;11m`,
125
+ error: `\x1b[38;2;239;68;68m`,
126
+ info: `\x1b[38;2;56;189;248m`,
127
+ },
128
+
129
+ colors: { bg, bgSubtle, primary, secondary, dim, accent },
130
+ });
131
+
132
+ // ─────────────────────────────────────────────────────────────────────────────
133
+ // THE THREE LANES
134
+ // ─────────────────────────────────────────────────────────────────────────────
135
+
136
+ export const LOCAL = makeLane({
137
+ name: 'local', label: 'Local', icon: '◈',
138
+ tunnelHint: 'samsmac → localpty.inneranimalmedia.com',
139
+ bg: '#0d0900', bgSubtle: '#1a1200',
140
+ primary: '#f59e0b', secondary: '#fbbf24', dim: '#78450a', accent: '#22d3c8',
141
+ });
142
+
143
+ export const CLOUD = makeLane({
144
+ name: 'cloud', label: 'Cloud', icon: '●',
145
+ tunnelHint: 'inneranimalmedia → terminal.inneranimalmedia.com (GCP iam-tunnel)',
146
+ bg: '#000d0b', bgSubtle: '#001a17',
147
+ primary: '#22d3c8', secondary: '#5eead4', dim: '#065f52', accent: '#f59e0b',
148
+ });
149
+
150
+ export const SANDBOX = makeLane({
151
+ name: 'sandbox', label: 'Sandbox', icon: '◌',
152
+ tunnelHint: 'CF Container → MY_CONTAINER DO (inneranimalmedia)',
153
+ bg: '#06020d', bgSubtle: '#0d0520',
154
+ primary: '#a78bfa', secondary: '#c4b5fd', dim: '#4c1d95', accent: '#22d3c8',
155
+ });
156
+
157
+ // ─────────────────────────────────────────────────────────────────────────────
158
+ // LOOKUP HELPERS
159
+ // ─────────────────────────────────────────────────────────────────────────────
160
+
161
+ /**
162
+ * Get lane theme by D1 target_type string.
163
+ * @param {'user_hosted_tunnel'|'platform_vm'|'sandbox'|string} targetType
164
+ */
165
+ export function getLaneTheme(targetType) {
166
+ switch (targetType) {
167
+ case 'user_hosted_tunnel': return LOCAL;
168
+ case 'platform_vm': return CLOUD;
169
+ case 'sandbox': return SANDBOX;
170
+ default: return CLOUD;
171
+ }
172
+ }
173
+
174
+ /**
175
+ * Get lane theme by name string (for SDK CLI use).
176
+ * @param {'local'|'cloud'|'sandbox'|string} name
177
+ */
178
+ export function getLaneThemeByName(name) {
179
+ switch (name?.toLowerCase()) {
180
+ case 'local': return LOCAL;
181
+ case 'cloud': return CLOUD;
182
+ case 'sandbox': return SANDBOX;
183
+ default: return CLOUD;
184
+ }
185
+ }
186
+
187
+ /** All lanes as ordered array */
188
+ export const LANES = [LOCAL, CLOUD, SANDBOX];
189
+
190
+ // ─────────────────────────────────────────────────────────────────────────────
191
+ // STATUS SYMBOLS
192
+ // ─────────────────────────────────────────────────────────────────────────────
193
+
194
+ export const STATUS = {
195
+ ready: '●',
196
+ connecting: '◐',
197
+ pending: '◌',
198
+ active: '◈',
199
+ error: '✗',
200
+ disabled: '○',
201
+ arrow: '▸',
202
+ check: '✓',
203
+ spinner: ['⠋','⠙','⠹','⠸','⠼','⠴','⠦','⠧','⠇','⠏'],
204
+ };
205
+
206
+ // ─────────────────────────────────────────────────────────────────────────────
207
+ // CLI RENDER HELPERS
208
+ // ─────────────────────────────────────────────────────────────────────────────
209
+
210
+ export function renderDivider(lane, width = 72) {
211
+ const { primary, reset } = lane.ansi;
212
+ return `${primary}${'─'.repeat(width)}${reset}`;
213
+ }
214
+
215
+ export function renderBanner(lane, title, width = 72) {
216
+ const { primary, accent, reset, bold } = lane.ansi;
217
+ const padded = ` ${title} `;
218
+ const sideLen = Math.max(0, Math.floor((width - padded.length) / 2));
219
+ const side = '─'.repeat(sideLen);
220
+ return `${primary}${side}${reset}${accent}${bold}${padded}${reset}${primary}${side}${reset}\n`;
221
+ }
222
+
223
+ export function renderLaneRow(lane, status, description, isActive = false) {
224
+ const { primary, ghost, reset, bold } = lane.ansi;
225
+ const sym = STATUS[status] || STATUS.pending;
226
+ const arrow = isActive ? `${primary}${STATUS.arrow}${reset} ` : ' ';
227
+ let symColor;
228
+ switch (status) {
229
+ case 'ready': symColor = `\x1b[38;2;34;197;94m`; break;
230
+ case 'connecting': symColor = `\x1b[38;2;251;146;60m`; break;
231
+ case 'error': symColor = `\x1b[38;2;239;68;68m`; break;
232
+ default: symColor = `\x1b[38;2;102;102;102m`; break;
233
+ }
234
+ return [
235
+ `${arrow}${symColor}${sym}${reset} `,
236
+ `${bold}${primary}${lane.meta.label.padEnd(10)}${reset}`,
237
+ `${ghost}${description}${reset}`,
238
+ ].join('');
239
+ }
240
+
241
+ export function renderSlashCmd(lane, cmd, description) {
242
+ const { primary, ghost, reset, bold } = lane.ansi;
243
+ return ` ${bold}${primary}${cmd.padEnd(16)}${reset}${ghost}${description}${reset}`;
244
+ }
245
+
246
+ export function renderSpinner(lane, label, tick) {
247
+ const { primary, ghost, reset } = lane.ansi;
248
+ const frame = STATUS.spinner[tick % STATUS.spinner.length];
249
+ return `${primary}${frame}${reset} ${ghost}${label}${reset}`;
250
+ }
251
+
252
+ export function renderKV(lane, key, value) {
253
+ const { ghost, white, reset } = lane.ansi;
254
+ return ` ${ghost}${key.padEnd(14)}${reset}${white}${value}${reset}`;
255
+ }
256
+
257
+ // ─────────────────────────────────────────────────────────────────────────────
258
+ // XTERM.JS INTEGRATION
259
+ // ─────────────────────────────────────────────────────────────────────────────
260
+
261
+ /**
262
+ * Get complete xterm.js Terminal options for a given lane.
263
+ * @param {'user_hosted_tunnel'|'platform_vm'|'sandbox'} targetType
264
+ */
265
+ export function getXtermOptions(targetType) {
266
+ const lane = getLaneTheme(targetType);
267
+ return {
268
+ theme: lane.xterm,
269
+ fontFamily: '"Cascadia Code", "JetBrains Mono", "Fira Code", monospace',
270
+ fontSize: 13,
271
+ lineHeight: 1.4,
272
+ letterSpacing: 0.3,
273
+ cursorBlink: true,
274
+ cursorStyle: 'block',
275
+ scrollback: 5000,
276
+ allowProposedApi: true,
277
+ };
278
+ }
279
+
280
+ /**
281
+ * Get CSS custom property string for a lane — inject on terminal panel wrapper.
282
+ * @param {'user_hosted_tunnel'|'platform_vm'|'sandbox'} targetType
283
+ */
284
+ export function getLaneCSSVars(targetType) {
285
+ const lane = getLaneTheme(targetType);
286
+ const { primary, secondary, bg, bgSubtle, dim, accent } = lane.colors;
287
+ return [
288
+ `--lane-primary: ${primary};`,
289
+ `--lane-secondary: ${secondary};`,
290
+ `--lane-bg: ${bg};`,
291
+ `--lane-bg-subtle: ${bgSubtle};`,
292
+ `--lane-dim: ${dim};`,
293
+ `--lane-accent: ${accent};`,
294
+ `--lane-name: "${lane.meta.name}";`,
295
+ ].join(' ');
296
+ }
297
+
298
+ // ─────────────────────────────────────────────────────────────────────────────
299
+ // INTERNAL
300
+ // ─────────────────────────────────────────────────────────────────────────────
301
+
302
+ function hexToRgb(hex) {
303
+ const h = hex.replace('#', '');
304
+ const r = parseInt(h.substring(0, 2), 16);
305
+ const g = parseInt(h.substring(2, 4), 16);
306
+ const b = parseInt(h.substring(4, 6), 16);
307
+ return `${r};${g};${b}`;
308
+ }
309
+
310
+ export default {
311
+ PALETTE, STATUS, LOCAL, CLOUD, SANDBOX, LANES,
312
+ getLaneTheme, getLaneThemeByName, getXtermOptions, getLaneCSSVars,
313
+ renderDivider, renderBanner, renderLaneRow, renderSlashCmd,
314
+ renderSpinner, renderKV,
315
+ };