synabun 1.3.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 (187) hide show
  1. package/CHANGELOG.md +665 -0
  2. package/LICENSE +191 -0
  3. package/README.md +853 -0
  4. package/hooks/claude-code/post-plan.mjs +391 -0
  5. package/hooks/claude-code/post-remember.mjs +239 -0
  6. package/hooks/claude-code/pre-compact.mjs +241 -0
  7. package/hooks/claude-code/pre-websearch.mjs +90 -0
  8. package/hooks/claude-code/prompt-submit.mjs +861 -0
  9. package/hooks/claude-code/session-start.mjs +494 -0
  10. package/hooks/claude-code/shared.mjs +368 -0
  11. package/hooks/claude-code/stop.mjs +646 -0
  12. package/hooks/claude-code/test-user-learning.mjs +531 -0
  13. package/mcp-server/package.json +38 -0
  14. package/mcp-server/run.mjs +10 -0
  15. package/mcp-server/src/config.ts +70 -0
  16. package/mcp-server/src/http.ts +93 -0
  17. package/mcp-server/src/index.ts +203 -0
  18. package/mcp-server/src/preload-http.ts +25 -0
  19. package/mcp-server/src/preload.ts +29 -0
  20. package/mcp-server/src/scripts/migrate-qdrant-to-sqlite.ts +506 -0
  21. package/mcp-server/src/services/categories.ts +429 -0
  22. package/mcp-server/src/services/discord.ts +702 -0
  23. package/mcp-server/src/services/file-checksums.ts +33 -0
  24. package/mcp-server/src/services/local-embeddings.ts +77 -0
  25. package/mcp-server/src/services/neural-interface.ts +344 -0
  26. package/mcp-server/src/services/sqlite.ts +823 -0
  27. package/mcp-server/src/tools/browser-advanced.ts +123 -0
  28. package/mcp-server/src/tools/browser-interact.ts +255 -0
  29. package/mcp-server/src/tools/browser-navigate.ts +77 -0
  30. package/mcp-server/src/tools/browser-observe.ts +1330 -0
  31. package/mcp-server/src/tools/browser.ts +107 -0
  32. package/mcp-server/src/tools/card-tools.ts +193 -0
  33. package/mcp-server/src/tools/card.ts +22 -0
  34. package/mcp-server/src/tools/category.ts +379 -0
  35. package/mcp-server/src/tools/discord-channel.ts +274 -0
  36. package/mcp-server/src/tools/discord-guild.ts +188 -0
  37. package/mcp-server/src/tools/discord-member.ts +158 -0
  38. package/mcp-server/src/tools/discord-message.ts +231 -0
  39. package/mcp-server/src/tools/discord-onboarding.ts +228 -0
  40. package/mcp-server/src/tools/discord-role.ts +209 -0
  41. package/mcp-server/src/tools/discord-thread.ts +110 -0
  42. package/mcp-server/src/tools/discord-webhook.ts +176 -0
  43. package/mcp-server/src/tools/discord.ts +40 -0
  44. package/mcp-server/src/tools/forget.ts +38 -0
  45. package/mcp-server/src/tools/git-tools.ts +141 -0
  46. package/mcp-server/src/tools/git.ts +13 -0
  47. package/mcp-server/src/tools/image-tools.ts +98 -0
  48. package/mcp-server/src/tools/image.ts +17 -0
  49. package/mcp-server/src/tools/leonardo-browser-tools.ts +281 -0
  50. package/mcp-server/src/tools/leonardo.ts +28 -0
  51. package/mcp-server/src/tools/loop.ts +348 -0
  52. package/mcp-server/src/tools/memories.ts +118 -0
  53. package/mcp-server/src/tools/recall.ts +323 -0
  54. package/mcp-server/src/tools/reflect.ts +145 -0
  55. package/mcp-server/src/tools/remember.ts +115 -0
  56. package/mcp-server/src/tools/response.ts +26 -0
  57. package/mcp-server/src/tools/restore.ts +34 -0
  58. package/mcp-server/src/tools/sync.ts +114 -0
  59. package/mcp-server/src/tools/tictactoe-tools.ts +98 -0
  60. package/mcp-server/src/tools/tictactoe.ts +13 -0
  61. package/mcp-server/src/tools/utils.ts +25 -0
  62. package/mcp-server/src/tools/whiteboard-tools.ts +260 -0
  63. package/mcp-server/src/tools/whiteboard.ts +22 -0
  64. package/mcp-server/src/tui.ts +940 -0
  65. package/mcp-server/src/types.ts +85 -0
  66. package/mcp-server/tsconfig.json +19 -0
  67. package/neural-interface/package.json +27 -0
  68. package/neural-interface/public/apple-touch-icon.png +0 -0
  69. package/neural-interface/public/category-logos/claude.svg +1 -0
  70. package/neural-interface/public/category-logos/gemini-color.svg +1 -0
  71. package/neural-interface/public/category-logos/openai.svg +1 -0
  72. package/neural-interface/public/category-logos/synabun.png +0 -0
  73. package/neural-interface/public/claude-chat.css +878 -0
  74. package/neural-interface/public/claude-chat.html +92 -0
  75. package/neural-interface/public/claude-chat.js +1168 -0
  76. package/neural-interface/public/favicon-16x16.png +0 -0
  77. package/neural-interface/public/favicon-32x32.png +0 -0
  78. package/neural-interface/public/gallery.svg +6 -0
  79. package/neural-interface/public/icon-192.png +0 -0
  80. package/neural-interface/public/icon-512.png +0 -0
  81. package/neural-interface/public/index-legacy.html +14677 -0
  82. package/neural-interface/public/index.html +212 -0
  83. package/neural-interface/public/index2d-legacy.html +8193 -0
  84. package/neural-interface/public/index2d.html +127 -0
  85. package/neural-interface/public/invite.html +351 -0
  86. package/neural-interface/public/leonardoai.svg +1 -0
  87. package/neural-interface/public/logo-synabun.png +0 -0
  88. package/neural-interface/public/logoHD.png +0 -0
  89. package/neural-interface/public/manifest.json +14 -0
  90. package/neural-interface/public/offline.html +299 -0
  91. package/neural-interface/public/onboarding.html +2335 -0
  92. package/neural-interface/public/openclaw-logo-text.png +0 -0
  93. package/neural-interface/public/pin-svgrepo-com.svg +25 -0
  94. package/neural-interface/public/sessionicon.svg +8 -0
  95. package/neural-interface/public/shared/ansi-parser.js +305 -0
  96. package/neural-interface/public/shared/api.js +1043 -0
  97. package/neural-interface/public/shared/colors.js +153 -0
  98. package/neural-interface/public/shared/constants.js +141 -0
  99. package/neural-interface/public/shared/html-shell.js +777 -0
  100. package/neural-interface/public/shared/html-term-renderer.js +1022 -0
  101. package/neural-interface/public/shared/i18n.js +147 -0
  102. package/neural-interface/public/shared/registry.js +82 -0
  103. package/neural-interface/public/shared/state.js +86 -0
  104. package/neural-interface/public/shared/storage.js +150 -0
  105. package/neural-interface/public/shared/styles.css +21212 -0
  106. package/neural-interface/public/shared/ui-automation-studio.js +3573 -0
  107. package/neural-interface/public/shared/ui-bookmarks.js +231 -0
  108. package/neural-interface/public/shared/ui-claude-panel.js +6333 -0
  109. package/neural-interface/public/shared/ui-command-runner.js +534 -0
  110. package/neural-interface/public/shared/ui-cost-widget.js +602 -0
  111. package/neural-interface/public/shared/ui-detail.js +1454 -0
  112. package/neural-interface/public/shared/ui-explorer.js +1356 -0
  113. package/neural-interface/public/shared/ui-file-explorer.js +2750 -0
  114. package/neural-interface/public/shared/ui-games.js +411 -0
  115. package/neural-interface/public/shared/ui-help.js +107 -0
  116. package/neural-interface/public/shared/ui-image-gallery.js +461 -0
  117. package/neural-interface/public/shared/ui-invite.js +519 -0
  118. package/neural-interface/public/shared/ui-keybinds.js +452 -0
  119. package/neural-interface/public/shared/ui-layouts.js +297 -0
  120. package/neural-interface/public/shared/ui-link.js +731 -0
  121. package/neural-interface/public/shared/ui-loading.js +255 -0
  122. package/neural-interface/public/shared/ui-menubar.js +499 -0
  123. package/neural-interface/public/shared/ui-multiselect.js +93 -0
  124. package/neural-interface/public/shared/ui-navbar.js +222 -0
  125. package/neural-interface/public/shared/ui-notifications.js +287 -0
  126. package/neural-interface/public/shared/ui-panels.js +309 -0
  127. package/neural-interface/public/shared/ui-resume.js +653 -0
  128. package/neural-interface/public/shared/ui-search.js +145 -0
  129. package/neural-interface/public/shared/ui-sessions.js +531 -0
  130. package/neural-interface/public/shared/ui-settings.js +5987 -0
  131. package/neural-interface/public/shared/ui-sidebar.js +1905 -0
  132. package/neural-interface/public/shared/ui-skills.js +2805 -0
  133. package/neural-interface/public/shared/ui-stats.js +38 -0
  134. package/neural-interface/public/shared/ui-sync.js +201 -0
  135. package/neural-interface/public/shared/ui-terminal.js +6385 -0
  136. package/neural-interface/public/shared/ui-tooltip.js +123 -0
  137. package/neural-interface/public/shared/ui-trash.js +439 -0
  138. package/neural-interface/public/shared/ui-tutorial-draw.js +875 -0
  139. package/neural-interface/public/shared/ui-tutorial-steps.js +2276 -0
  140. package/neural-interface/public/shared/ui-tutorial.js +1117 -0
  141. package/neural-interface/public/shared/ui-update.js +244 -0
  142. package/neural-interface/public/shared/ui-whiteboard.js +3262 -0
  143. package/neural-interface/public/shared/ui-workspaces.js +722 -0
  144. package/neural-interface/public/shared/utils.js +360 -0
  145. package/neural-interface/public/shared/vterm-buffer.js +1102 -0
  146. package/neural-interface/public/sw.js +52 -0
  147. package/neural-interface/public/synabun.png +0 -0
  148. package/neural-interface/public/toolbar-album.svg +8 -0
  149. package/neural-interface/public/toolbar-question.svg +6 -0
  150. package/neural-interface/public/toolbar-sessions.svg +6 -0
  151. package/neural-interface/public/variant/2d/background.js +113 -0
  152. package/neural-interface/public/variant/2d/context-menu.js +133 -0
  153. package/neural-interface/public/variant/2d/gfx.js +45 -0
  154. package/neural-interface/public/variant/2d/graph.js +1635 -0
  155. package/neural-interface/public/variant/2d/hulls.js +207 -0
  156. package/neural-interface/public/variant/2d/lasso.js +135 -0
  157. package/neural-interface/public/variant/2d/layout.js +594 -0
  158. package/neural-interface/public/variant/2d/main.js +521 -0
  159. package/neural-interface/public/variant/2d/minimap.js +312 -0
  160. package/neural-interface/public/variant/2d/settings-gfx.js +98 -0
  161. package/neural-interface/public/variant/3d/background.js +884 -0
  162. package/neural-interface/public/variant/3d/camera.js +661 -0
  163. package/neural-interface/public/variant/3d/gfx.js +116 -0
  164. package/neural-interface/public/variant/3d/graph.js +2399 -0
  165. package/neural-interface/public/variant/3d/main.js +500 -0
  166. package/neural-interface/public/variant/3d/settings-gfx.js +228 -0
  167. package/neural-interface/scripts/rebuild-pty.js +100 -0
  168. package/neural-interface/server.js +15000 -0
  169. package/neural-interface/templates/CLAUDE-template.md +211 -0
  170. package/package.json +53 -0
  171. package/postinstall.js +135 -0
  172. package/setup.js +260 -0
  173. package/skills/leonardo/SKILL.md +159 -0
  174. package/skills/leonardo/icon.svg +1 -0
  175. package/skills/leonardo/modules/browser-guide.md +378 -0
  176. package/skills/leonardo/modules/image-prompter.md +277 -0
  177. package/skills/leonardo/modules/model-advisor.md +100 -0
  178. package/skills/leonardo/modules/prompt-library.md +110 -0
  179. package/skills/leonardo/modules/style-guide.md +186 -0
  180. package/skills/leonardo/modules/video-prompter.md +311 -0
  181. package/skills/synabun/SKILL.md +185 -0
  182. package/skills/synabun/icon.png +0 -0
  183. package/skills/synabun/modules/audit.md +374 -0
  184. package/skills/synabun/modules/changelog.md +194 -0
  185. package/skills/synabun/modules/idea.md +144 -0
  186. package/skills/synabun/modules/memorize.md +188 -0
  187. package/skills/synabun/synabunicon.png +0 -0
@@ -0,0 +1,33 @@
1
+ import { createHash } from 'crypto';
2
+ import { readFileSync } from 'fs';
3
+ import { resolve } from 'path';
4
+
5
+ /**
6
+ * Compute SHA-256 hash of a file's content.
7
+ * Absolute paths are used as-is. Relative paths resolve from process.cwd()
8
+ * (which is the project root when invoked by Claude Code).
9
+ * Returns null if the file doesn't exist or can't be read.
10
+ */
11
+ export function hashFile(filePath: string): string | null {
12
+ try {
13
+ const absPath = resolve(filePath);
14
+ const content = readFileSync(absPath);
15
+ return createHash('sha256').update(content).digest('hex');
16
+ } catch {
17
+ return null;
18
+ }
19
+ }
20
+
21
+ /**
22
+ * Compute checksums for an array of related file paths.
23
+ * Returns a Record mapping each file path to its SHA-256 hash.
24
+ * Files that can't be read are omitted from the result.
25
+ */
26
+ export function computeChecksums(filePaths: string[]): Record<string, string> {
27
+ const checksums: Record<string, string> = {};
28
+ for (const fp of filePaths) {
29
+ const hash = hashFile(fp);
30
+ if (hash) checksums[fp] = hash;
31
+ }
32
+ return checksums;
33
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Local embedding generation using Transformers.js (ONNX runtime).
3
+ * Drop-in replacement for embeddings.ts — same exported function signatures.
4
+ * Uses all-MiniLM-L6-v2 (384 dimensions, ~23MB quantized model).
5
+ * No API key, no internet required after first model download.
6
+ */
7
+
8
+ import type { FeatureExtractionPipeline } from '@huggingface/transformers';
9
+
10
+ const MODEL_NAME = 'Xenova/all-MiniLM-L6-v2';
11
+ export const EMBEDDING_DIMENSIONS = 384;
12
+
13
+ let extractor: FeatureExtractionPipeline | null = null;
14
+ let initPromise: Promise<FeatureExtractionPipeline> | null = null;
15
+
16
+ /**
17
+ * Lazy-loads the embedding pipeline. First call downloads the model (~23MB)
18
+ * and caches it permanently in ~/.cache/huggingface/. Subsequent calls reuse
19
+ * the cached model with no network access needed.
20
+ */
21
+ async function getExtractor(): Promise<FeatureExtractionPipeline> {
22
+ if (extractor) return extractor;
23
+
24
+ // Prevent concurrent initialization (multiple tools calling at once)
25
+ if (initPromise) return initPromise;
26
+
27
+ initPromise = (async () => {
28
+ // Dynamic import to avoid blocking module load
29
+ const { pipeline } = await import('@huggingface/transformers');
30
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
31
+ extractor = await (pipeline as any)('feature-extraction', MODEL_NAME, {
32
+ dtype: 'fp32',
33
+ }) as FeatureExtractionPipeline;
34
+ return extractor;
35
+ })();
36
+
37
+ try {
38
+ return await initPromise;
39
+ } finally {
40
+ initPromise = null;
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Generate a single embedding vector for the given text.
46
+ * Returns a 384-dimensional normalized vector (unit length).
47
+ */
48
+ export async function generateEmbedding(text: string): Promise<number[]> {
49
+ const ext = await getExtractor();
50
+ const output = await ext(text, { pooling: 'mean', normalize: true });
51
+ return Array.from(output.data as Float32Array);
52
+ }
53
+
54
+ /**
55
+ * Generate embeddings for multiple texts.
56
+ * Processes sequentially to avoid memory spikes on large batches.
57
+ */
58
+ export async function generateEmbeddingBatch(texts: string[]): Promise<number[][]> {
59
+ if (texts.length === 0) return [];
60
+ if (texts.length === 1) return [await generateEmbedding(texts[0])];
61
+
62
+ const ext = await getExtractor();
63
+ const results: number[][] = [];
64
+ for (const text of texts) {
65
+ const output = await ext(text, { pooling: 'mean', normalize: true });
66
+ results.push(Array.from(output.data as Float32Array));
67
+ }
68
+ return results;
69
+ }
70
+
71
+ /**
72
+ * Trigger model download/warmup without generating an actual embedding.
73
+ * Call this at server startup for faster first-use latency.
74
+ */
75
+ export async function warmupEmbeddings(): Promise<void> {
76
+ await getExtractor();
77
+ }
@@ -0,0 +1,344 @@
1
+ /**
2
+ * HTTP client for communicating with the Neural Interface Express server.
3
+ * The MCP server delegates all browser operations to the Neural Interface
4
+ * which manages Playwright sessions, CDP screencast, stealth, etc.
5
+ */
6
+
7
+ const BASE_URL = process.env.NEURAL_INTERFACE_URL
8
+ || `http://localhost:${process.env.NEURAL_PORT || '3344'}`;
9
+ const DEFAULT_TIMEOUT = 10_000;
10
+ const LONG_TIMEOUT = 30_000;
11
+
12
+ export interface BrowserSessionInfo {
13
+ id: string;
14
+ url: string;
15
+ title: string;
16
+ createdAt: number;
17
+ clients: number;
18
+ }
19
+
20
+ interface NiResponse {
21
+ ok?: boolean;
22
+ error?: string;
23
+ [key: string]: unknown;
24
+ }
25
+
26
+ async function request(
27
+ method: string,
28
+ path: string,
29
+ body?: Record<string, unknown>,
30
+ timeout = DEFAULT_TIMEOUT
31
+ ): Promise<NiResponse> {
32
+ const controller = new AbortController();
33
+ const timer = setTimeout(() => controller.abort(), timeout);
34
+
35
+ try {
36
+ const opts: RequestInit = {
37
+ method,
38
+ headers: { 'Content-Type': 'application/json' },
39
+ signal: controller.signal,
40
+ };
41
+ if (body && method !== 'GET') {
42
+ opts.body = JSON.stringify(body);
43
+ }
44
+ const res = await fetch(`${BASE_URL}${path}`, opts);
45
+ const data = await res.json() as NiResponse;
46
+ if (!res.ok && !data.error) {
47
+ data.error = `HTTP ${res.status}`;
48
+ }
49
+ return data;
50
+ } catch (err: unknown) {
51
+ const msg = err instanceof Error ? err.message : String(err);
52
+ if (msg.includes('abort')) {
53
+ return { error: `Request timed out after ${timeout}ms` };
54
+ }
55
+ return { error: `Neural Interface unreachable: ${msg}. Is the Neural Interface server running?` };
56
+ } finally {
57
+ clearTimeout(timer);
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Resolve which session ID to use.
63
+ * - If sessionId provided, return it immediately (server returns 404 if invalid).
64
+ * - If 1 session exists, auto-select it.
65
+ * - If 0 sessions exist and autoCreate is true, create one.
66
+ * - If multiple sessions and no ID, return error listing them.
67
+ */
68
+ export async function resolveSession(
69
+ sessionId?: string,
70
+ autoCreate?: { url?: string }
71
+ ): Promise<{ sessionId: string } | { error: string }> {
72
+ // Agent-scoped browser session — set by the agent orchestrator to pin
73
+ // this MCP instance to a specific browser session (multi-agent isolation).
74
+ const pinnedSession = process.env.SYNABUN_BROWSER_SESSION;
75
+ if (pinnedSession && !sessionId) {
76
+ return { sessionId: pinnedSession };
77
+ }
78
+
79
+ if (sessionId) {
80
+ // Trust the server — it will 404 if the session doesn't exist.
81
+ // Skipping the extra GET /api/browser/sessions verification call saves a full round-trip.
82
+ return { sessionId };
83
+ }
84
+
85
+ // List sessions
86
+ const data = await request('GET', '/api/browser/sessions');
87
+ if (data.error) return { error: data.error };
88
+ const sessions = (data.sessions || []) as BrowserSessionInfo[];
89
+
90
+ if (sessions.length === 1) {
91
+ return { sessionId: sessions[0].id };
92
+ }
93
+
94
+ if (sessions.length === 0) {
95
+ if (autoCreate) {
96
+ const created = await request('POST', '/api/browser/sessions', {
97
+ url: autoCreate.url || 'about:blank',
98
+ });
99
+ if (created.error) return { error: `Failed to auto-create session: ${created.error}` };
100
+ return { sessionId: created.sessionId as string };
101
+ }
102
+ return { error: 'No browser sessions open. Use browser_session to create one first, or use browser_navigate with a URL to auto-create.' };
103
+ }
104
+
105
+ // Multiple sessions — require explicit ID
106
+ const list = sessions.map(s => ` ${s.id} — ${s.title || s.url}`).join('\n');
107
+ return { error: `Multiple browser sessions open. Specify sessionId:\n${list}` };
108
+ }
109
+
110
+ // ── Cache invalidation ──
111
+
112
+ export async function invalidateCache(reason: string): Promise<void> {
113
+ try {
114
+ await request('POST', '/api/cache/invalidate', { reason });
115
+ } catch {
116
+ // Fire-and-forget — don't block MCP tools if Neural Interface is down
117
+ }
118
+ }
119
+
120
+ // ── Session management ──
121
+
122
+ export async function listSessions(): Promise<NiResponse> {
123
+ return request('GET', '/api/browser/sessions');
124
+ }
125
+
126
+ export async function createSession(url?: string): Promise<NiResponse> {
127
+ return request('POST', '/api/browser/sessions', {
128
+ url: url || 'about:blank',
129
+ });
130
+ }
131
+
132
+ export async function closeSession(sessionId: string): Promise<NiResponse> {
133
+ return request('DELETE', `/api/browser/sessions/${sessionId}`);
134
+ }
135
+
136
+ // ── Navigation ──
137
+
138
+ export async function navigate(sessionId: string, url: string): Promise<NiResponse> {
139
+ return request('POST', `/api/browser/sessions/${sessionId}/navigate`, { url }, LONG_TIMEOUT);
140
+ }
141
+
142
+ export async function goBack(sessionId: string): Promise<NiResponse> {
143
+ return request('POST', `/api/browser/sessions/${sessionId}/back`);
144
+ }
145
+
146
+ export async function goForward(sessionId: string): Promise<NiResponse> {
147
+ return request('POST', `/api/browser/sessions/${sessionId}/forward`);
148
+ }
149
+
150
+ export async function reload(sessionId: string): Promise<NiResponse> {
151
+ return request('POST', `/api/browser/sessions/${sessionId}/reload`, undefined, LONG_TIMEOUT);
152
+ }
153
+
154
+ // ── Interaction (selector-based) ──
155
+
156
+ export async function click(sessionId: string, selector: string, nthMatch?: number): Promise<NiResponse> {
157
+ return request('POST', `/api/browser/sessions/${sessionId}/click`, { selector, ...(nthMatch !== undefined && { nthMatch }) });
158
+ }
159
+
160
+ export async function fill(sessionId: string, selector: string, value: string, nthMatch?: number): Promise<NiResponse> {
161
+ return request('POST', `/api/browser/sessions/${sessionId}/fill`, { selector, value, ...(nthMatch !== undefined && { nthMatch }) });
162
+ }
163
+
164
+ export async function type(sessionId: string, selector: string | null, text: string, nthMatch?: number): Promise<NiResponse> {
165
+ return request('POST', `/api/browser/sessions/${sessionId}/type`, { selector, text, ...(nthMatch !== undefined && { nthMatch }) });
166
+ }
167
+
168
+ export async function hover(sessionId: string, selector: string, nthMatch?: number): Promise<NiResponse> {
169
+ return request('POST', `/api/browser/sessions/${sessionId}/hover`, { selector, ...(nthMatch !== undefined && { nthMatch }) });
170
+ }
171
+
172
+ export async function selectOption(sessionId: string, selector: string, value: string, nthMatch?: number): Promise<NiResponse> {
173
+ return request('POST', `/api/browser/sessions/${sessionId}/select`, { selector, value, ...(nthMatch !== undefined && { nthMatch }) });
174
+ }
175
+
176
+ export async function pressKey(sessionId: string, key: string): Promise<NiResponse> {
177
+ return request('POST', `/api/browser/sessions/${sessionId}/press`, { key });
178
+ }
179
+
180
+ export async function scroll(
181
+ sessionId: string,
182
+ opts: { direction: string; distance?: number; selector?: string }
183
+ ): Promise<NiResponse> {
184
+ return request('POST', `/api/browser/sessions/${sessionId}/scroll`, opts as Record<string, unknown>);
185
+ }
186
+
187
+ export async function upload(
188
+ sessionId: string,
189
+ selector: string,
190
+ filePaths: string[],
191
+ nthMatch?: number
192
+ ): Promise<NiResponse> {
193
+ return request('POST', `/api/browser/sessions/${sessionId}/upload`, { selector, filePaths, ...(nthMatch !== undefined && { nthMatch }) }, LONG_TIMEOUT);
194
+ }
195
+
196
+ // ── Observation ──
197
+
198
+ export async function snapshot(sessionId: string, selector?: string): Promise<NiResponse> {
199
+ if (selector) return request('POST', `/api/browser/sessions/${sessionId}/snapshot`, { selector });
200
+ return request('GET', `/api/browser/sessions/${sessionId}/snapshot`);
201
+ }
202
+
203
+ export async function getContent(sessionId: string): Promise<NiResponse> {
204
+ return request('GET', `/api/browser/sessions/${sessionId}/content`);
205
+ }
206
+
207
+ export async function getMarkdown(sessionId: string): Promise<NiResponse> {
208
+ return request('GET', `/api/browser/sessions/${sessionId}/markdown`, undefined, LONG_TIMEOUT);
209
+ }
210
+
211
+ export async function fetchMarkdown(url: string, timeout?: number): Promise<NiResponse> {
212
+ return request('POST', '/api/fetch-markdown', { url, timeout }, LONG_TIMEOUT);
213
+ }
214
+
215
+ export async function screenshot(sessionId: string): Promise<NiResponse> {
216
+ return request('GET', `/api/browser/sessions/${sessionId}/screenshot-base64`);
217
+ }
218
+
219
+ // ── Advanced ──
220
+
221
+ export async function evaluate(sessionId: string, script: string): Promise<NiResponse> {
222
+ return request('POST', `/api/browser/sessions/${sessionId}/evaluate`, { script }, LONG_TIMEOUT);
223
+ }
224
+
225
+ export async function waitFor(
226
+ sessionId: string,
227
+ opts: { selector?: string; state?: string; loadState?: string; timeout?: number }
228
+ ): Promise<NiResponse> {
229
+ return request('POST', `/api/browser/sessions/${sessionId}/wait`, opts, LONG_TIMEOUT);
230
+ }
231
+
232
+ // ── Whiteboard ──
233
+
234
+ export async function getWhiteboard(): Promise<NiResponse> {
235
+ return request('GET', '/api/whiteboard');
236
+ }
237
+
238
+ export async function addWhiteboardElements(
239
+ elements: Record<string, unknown>[],
240
+ coordMode?: string,
241
+ layout?: string
242
+ ): Promise<NiResponse> {
243
+ return request('POST', '/api/whiteboard/elements', { elements, coordMode, layout } as Record<string, unknown>);
244
+ }
245
+
246
+ export async function updateWhiteboardElement(
247
+ id: string,
248
+ updates: Record<string, unknown>,
249
+ coordMode?: string
250
+ ): Promise<NiResponse> {
251
+ return request('PUT', `/api/whiteboard/elements/${id}`, { updates, coordMode } as Record<string, unknown>);
252
+ }
253
+
254
+ export async function removeWhiteboardElement(id: string): Promise<NiResponse> {
255
+ return request('DELETE', `/api/whiteboard/elements/${id}`);
256
+ }
257
+
258
+ export async function clearWhiteboard(): Promise<NiResponse> {
259
+ return request('POST', '/api/whiteboard/clear');
260
+ }
261
+
262
+ export async function whiteboardScreenshot(): Promise<NiResponse> {
263
+ return request('GET', '/api/whiteboard/screenshot', undefined, 15_000);
264
+ }
265
+
266
+ // ── Cards (Memory Card MCP integration) ──
267
+
268
+ export async function getCards(): Promise<NiResponse> {
269
+ return request('GET', '/api/cards');
270
+ }
271
+
272
+ export async function openCard(
273
+ memoryId: string,
274
+ opts?: { left?: number; top?: number; compact?: boolean; coordMode?: string }
275
+ ): Promise<NiResponse> {
276
+ return request('POST', '/api/cards/open', { memoryId, ...opts } as Record<string, unknown>);
277
+ }
278
+
279
+ export async function closeCard(memoryId?: string): Promise<NiResponse> {
280
+ return request('POST', '/api/cards/close', memoryId ? { memoryId } : {} as Record<string, unknown>);
281
+ }
282
+
283
+ export async function updateCard(
284
+ memoryId: string,
285
+ updates: Record<string, unknown>,
286
+ coordMode?: string
287
+ ): Promise<NiResponse> {
288
+ return request('PUT', `/api/cards/${memoryId}`, { ...updates, coordMode } as Record<string, unknown>);
289
+ }
290
+
291
+ export async function cardsScreenshot(): Promise<NiResponse> {
292
+ return request('GET', '/api/cards/screenshot', undefined, 15_000);
293
+ }
294
+
295
+ // ── TicTacToe ──
296
+
297
+ export async function tictactoeStart(piece?: string): Promise<NiResponse> {
298
+ return request('POST', '/api/games/tictactoe/start', { piece } as Record<string, unknown>);
299
+ }
300
+
301
+ export async function tictactoeMove(cell: number): Promise<NiResponse> {
302
+ return request('POST', '/api/games/tictactoe/move', { cell } as Record<string, unknown>);
303
+ }
304
+
305
+ export async function tictactoeState(): Promise<NiResponse> {
306
+ return request('GET', '/api/games/tictactoe/state');
307
+ }
308
+
309
+ export async function tictactoeEnd(): Promise<NiResponse> {
310
+ return request('POST', '/api/games/tictactoe/end');
311
+ }
312
+
313
+ // ── Git ──
314
+
315
+ export async function gitStatus(path: string): Promise<NiResponse> {
316
+ return request('GET', `/api/git/status?path=${encodeURIComponent(path)}`);
317
+ }
318
+
319
+ export async function gitDiff(path: string, maxLines?: number): Promise<NiResponse> {
320
+ const qs = `path=${encodeURIComponent(path)}${maxLines ? `&maxLines=${maxLines}` : ''}`;
321
+ return request('GET', `/api/git/diff?${qs}`, undefined, LONG_TIMEOUT);
322
+ }
323
+
324
+ export async function gitCommit(path: string, message: string, files?: string[]): Promise<NiResponse> {
325
+ return request('POST', '/api/git/commit', { path, message, files } as Record<string, unknown>);
326
+ }
327
+
328
+ export async function gitLog(path: string, count?: number): Promise<NiResponse> {
329
+ return request('GET', `/api/git/log?path=${encodeURIComponent(path)}&count=${count || 10}`);
330
+ }
331
+
332
+ export async function gitBranches(path: string): Promise<NiResponse> {
333
+ return request('GET', `/api/terminal/branches?path=${encodeURIComponent(path)}`);
334
+ }
335
+
336
+ // ── Image store ──
337
+
338
+ export async function listImages(): Promise<NiResponse> {
339
+ return request('GET', '/api/images');
340
+ }
341
+
342
+ export async function deleteImage(filename: string): Promise<NiResponse> {
343
+ return request('DELETE', `/api/images/${encodeURIComponent(filename)}`);
344
+ }