mouaif 0.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 (116) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +140 -0
  3. package/bin/mouaif.js +281 -0
  4. package/frontend/dist/assets/AgentFilePicker-CcKLJorU.js +1 -0
  5. package/frontend/dist/assets/CliModal-Hs5phmNZ.js +7 -0
  6. package/frontend/dist/assets/DictationPage-BI23lp42.js +2 -0
  7. package/frontend/dist/assets/FileEditor-DDl31c6d.js +2 -0
  8. package/frontend/dist/assets/GitModal-3EC_gpJ5.js +2 -0
  9. package/frontend/dist/assets/Inspector-Ba3R1w04.js +73 -0
  10. package/frontend/dist/assets/SettingsAbout-bvZGDEDw.js +1 -0
  11. package/frontend/dist/assets/SettingsActions-Dk6WX9jv.js +1 -0
  12. package/frontend/dist/assets/SettingsAgents-BNV0MgDB.js +1 -0
  13. package/frontend/dist/assets/SettingsDefaults-DbMmQbzc.js +1 -0
  14. package/frontend/dist/assets/SettingsHiddenContent-BZ2sloH1.js +1 -0
  15. package/frontend/dist/assets/SettingsMcp-DOrfbQd1.js +1 -0
  16. package/frontend/dist/assets/SettingsMcpEdit-BGMQ2CWC.js +3 -0
  17. package/frontend/dist/assets/SettingsMcpRegistry-BywXee_A.js +1 -0
  18. package/frontend/dist/assets/SettingsNotifications-B0LEs11a.js +1 -0
  19. package/frontend/dist/assets/SettingsPricing-BAg33iVF.js +1 -0
  20. package/frontend/dist/assets/SettingsProject-DNrKhCcZ.js +14 -0
  21. package/frontend/dist/assets/SettingsProjects-IqkBfDcm.js +1 -0
  22. package/frontend/dist/assets/SettingsPrompts-BgeiASuk.js +1 -0
  23. package/frontend/dist/assets/SettingsProviders-k0xJN0IK.js +1 -0
  24. package/frontend/dist/assets/SettingsTags-B5kjFdQi.js +1 -0
  25. package/frontend/dist/assets/agentNavigation-BiiCpFz5.js +1 -0
  26. package/frontend/dist/assets/codemirror-Bp6CUUFk.js +30 -0
  27. package/frontend/dist/assets/index-BGvI4n0T.js +61 -0
  28. package/frontend/dist/assets/index-Bgg1gnDf.css +1 -0
  29. package/frontend/dist/assets/index-C1sQFIC-.css +1 -0
  30. package/frontend/dist/assets/index-CANPYzQg.css +1 -0
  31. package/frontend/dist/assets/index-Crn1LdzK.css +1 -0
  32. package/frontend/dist/assets/index-FbCWDPiB.css +1 -0
  33. package/frontend/dist/assets/projectQS-D1cSZ7Gr.js +1 -0
  34. package/frontend/dist/assets/virtual-list-6H9b4K51.js +1 -0
  35. package/frontend/dist/icons/favicon-32.png +0 -0
  36. package/frontend/dist/icons/icon-180-apple.png +0 -0
  37. package/frontend/dist/icons/icon-192.png +0 -0
  38. package/frontend/dist/icons/icon-512.png +0 -0
  39. package/frontend/dist/icons/icon-maskable-512.png +0 -0
  40. package/frontend/dist/index.html +83 -0
  41. package/frontend/dist/manifest.webmanifest +33 -0
  42. package/frontend/dist/sw.js +482 -0
  43. package/package.json +98 -0
  44. package/scripts/patch-zimmerframe.js +58 -0
  45. package/src/access-auth.js +515 -0
  46. package/src/agentFeatures.js +294 -0
  47. package/src/agentFiles.js +164 -0
  48. package/src/agentSkills.js +147 -0
  49. package/src/agents.js +230 -0
  50. package/src/ai-chat.js +21 -0
  51. package/src/ai-endpoints.js +1880 -0
  52. package/src/ai-stream.js +2048 -0
  53. package/src/ai.js +68 -0
  54. package/src/auth.js +391 -0
  55. package/src/chatdb.js +816 -0
  56. package/src/chats.js +275 -0
  57. package/src/custom-actions.js +65 -0
  58. package/src/files.js +431 -0
  59. package/src/hideFileContent.js +327 -0
  60. package/src/http-server.js +535 -0
  61. package/src/index.js +15 -0
  62. package/src/inspector.js +731 -0
  63. package/src/inspectorProfiles.js +503 -0
  64. package/src/live-chat.js +107 -0
  65. package/src/mcp.js +1517 -0
  66. package/src/messages.js +238 -0
  67. package/src/modelList.js +137 -0
  68. package/src/notifications.js +52 -0
  69. package/src/oauth-anthropic.js +280 -0
  70. package/src/oauth-github-copilot.js +417 -0
  71. package/src/oauth-mcp.js +216 -0
  72. package/src/oauth-openrouter.js +285 -0
  73. package/src/package-version.js +20 -0
  74. package/src/projects.js +285 -0
  75. package/src/promptProfiles.js +256 -0
  76. package/src/prompts.js +384 -0
  77. package/src/providerShapes.js +44 -0
  78. package/src/providers/base.js +41 -0
  79. package/src/providers/index.js +25 -0
  80. package/src/push.js +315 -0
  81. package/src/qr.js +192 -0
  82. package/src/restart.js +47 -0
  83. package/src/server-handlers-access.js +306 -0
  84. package/src/server-handlers-actions.js +100 -0
  85. package/src/server-handlers-ai.js +248 -0
  86. package/src/server-handlers-auth.js +273 -0
  87. package/src/server-handlers-chats.js +1436 -0
  88. package/src/server-handlers-git.js +467 -0
  89. package/src/server-handlers-mcp-oauth.js +56 -0
  90. package/src/server-handlers-misc.js +783 -0
  91. package/src/server-handlers-projects.js +289 -0
  92. package/src/server-handlers-prompts.js +259 -0
  93. package/src/server-handlers-push.js +102 -0
  94. package/src/server-handlers-settings.js +406 -0
  95. package/src/server-handlers-tools.js +654 -0
  96. package/src/server-handlers-transcribe.js +399 -0
  97. package/src/server-shared.js +780 -0
  98. package/src/server-web-static.js +191 -0
  99. package/src/settings.js +898 -0
  100. package/src/statusBar.js +541 -0
  101. package/src/tags.js +414 -0
  102. package/src/toolFeedback.js +225 -0
  103. package/src/tools/ask.js +154 -0
  104. package/src/tools/authorization.js +932 -0
  105. package/src/tools/files.js +1150 -0
  106. package/src/tools/progress.js +71 -0
  107. package/src/tools/restart.js +32 -0
  108. package/src/tools/searchEngine.js +957 -0
  109. package/src/tools/shell.js +341 -0
  110. package/src/tools/subagent.js +47 -0
  111. package/src/tools/task.js +234 -0
  112. package/src/tools/webpreview.js +448 -0
  113. package/src/trace.js +103 -0
  114. package/src/transcribe.js +683 -0
  115. package/src/usage.js +389 -0
  116. package/src/util.js +151 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Antoine Gjeloshaj
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,140 @@
1
+ # mouaif 🚀
2
+
3
+ mouaif is a mobile-first AI coding assistant for local projects. Connect your preferred AI providers, chat about a project, and choose which coding tools the assistant may use.
4
+
5
+ ## Requirements
6
+
7
+ - Node.js 18 or newer
8
+ - A supported AI provider account, or a local Ollama installation
9
+
10
+ ## Install
11
+
12
+ The npm package name is [`mouaif`](https://www.npmjs.com/package/mouaif). Start it directly with `npx`, without a global install, and require a login:
13
+
14
+ ```bash
15
+ npx mouaif serve --auth
16
+ ```
17
+
18
+ On the first authenticated start, the terminal prints a setup link, QR code, and short code for creating your username and password. Later starts reuse those access settings and show the login screen.
19
+
20
+ Install the `mouaif` command globally:
21
+
22
+ ```bash
23
+ npm install -g mouaif
24
+ ```
25
+
26
+ Or install from a checkout of this repository:
27
+
28
+ ```bash
29
+ git clone <repo-url>
30
+ cd mouaif
31
+ npm install
32
+ npm link
33
+ ```
34
+ `npm install` builds the web UI once when its sources are newer than `frontend/dist/` (the Vite toolchain is present in a source checkout), so there is no separate build step. `npm link` makes the `mouaif` command available in your terminal.
35
+
36
+ Every install ships the pre-built web UI in `frontend/dist/`, so `mouaif serve` never builds the frontend. The package depends on `better-sqlite3` and `@napi-rs/keyring`, which ship prebuilt binaries for common platforms; on an unusual platform Node compiles them, so the first install can take a few minutes.
37
+
38
+ ## Run
39
+
40
+ With `npx`:
41
+
42
+ ```bash
43
+ npx mouaif serve --auth
44
+ ```
45
+
46
+ Or, after a global install:
47
+
48
+ ```bash
49
+ mouaif serve --auth
50
+ ```
51
+
52
+ Open `http://127.0.0.1:5732/` in a browser and create or enter your access credentials. Keep the terminal open while using mouaif and press `Ctrl+C` to stop it. Omit `--auth` only when you intentionally want the app to be accessible without a login.
53
+
54
+ Useful commands:
55
+
56
+ ```bash
57
+ mouaif serve --port 9000 # use another port
58
+ mouaif serve --host 0.0.0.0 # listen on your local network
59
+ mouaif info # show version and default port
60
+ ```
61
+
62
+ ## First setup
63
+
64
+ 1. Open the **Chats** tab and tap **Add project**.
65
+ 2. Choose an existing folder or create one.
66
+ 3. Open **Settings → Providers** and connect an AI provider.
67
+ 4. Open the project settings and add or select a model.
68
+ 5. Create a chat and send your first message.
69
+
70
+ ## Authentication
71
+
72
+ ### Connect an AI provider
73
+
74
+ Open **Settings → Providers**, select a provider, then enter its API key or use **Sign in** when offered. Supported connections include OpenAI-compatible services, Anthropic, Google Gemini, Ollama, OpenRouter, GitHub Copilot, Azure OpenAI, Mistral, Groq, and DeepSeek.
75
+
76
+ ### Protect access to mouaif
77
+
78
+ The recommended command requires login and creates a setup invitation when no user exists yet:
79
+
80
+ ```bash
81
+ npx mouaif serve --auth
82
+ ```
83
+
84
+ To replace the access user or explicitly generate a fresh expiring setup link, QR code, and short code:
85
+
86
+ ```bash
87
+ npx mouaif serve --auth-setup
88
+ ```
89
+
90
+ You can also set credentials while keeping the password out of shell history:
91
+
92
+ ```bash
93
+ MOUAIF_PASSWORD='a-long-password' \
94
+ npx mouaif serve --auth --user alice
95
+ ```
96
+
97
+ PowerShell:
98
+
99
+ ```powershell
100
+ $env:MOUAIF_PASSWORD = 'a-long-password'
101
+ npx mouaif serve --auth --user alice
102
+ ```
103
+
104
+ After setup, require login on future starts with:
105
+
106
+ ```bash
107
+ npx mouaif serve --auth
108
+ ```
109
+
110
+ If you installed `mouaif` globally, the shorter equivalent is `mouaif serve --auth`; the auth options are identical. Use HTTPS and `--public-origin` before making mouaif available outside the computer running it.
111
+
112
+ ## App abilities
113
+
114
+ - Organize chats by local project and select models per chat.
115
+ - Attach files and images, use custom prompts, and control reasoning options.
116
+ - Use **Draft Craft** to send selected code or an annotated Inspector image to any chat draft.
117
+ - Let the assistant read and edit project files.
118
+ - Run approved non-interactive shell commands and view live output.
119
+ - Track tasks, answer structured questions, and delegate work to project agents.
120
+ - Connect additional tools through MCP.
121
+ - Preview pages and inspect console and network activity in the Inspector.
122
+ - Gate tools per project with **Off**, **Ask**, or **Allow**.
123
+ - Export chat traces and receive browser notifications for long-running work.
124
+
125
+ ## Documentation
126
+
127
+ - [Getting started](docs/features/getting-started.md) — install, run, update, and first setup.
128
+ - [Authentication](docs/features/authentication.md) — connect AI providers and protect app access.
129
+ - [App abilities](docs/features/app-abilities.md) — projects, chats, coding tools, agents, MCP, and Inspector.
130
+ - [Draft Craft](docs/features/draft-craft.md) — add selected code or annotated Inspector images to a chat draft.
131
+
132
+ Build the static documentation site with:
133
+
134
+ ```bash
135
+ npm run docs:build
136
+ ```
137
+
138
+ ## License
139
+
140
+ MIT — see [LICENSE](LICENSE).
package/bin/mouaif.js ADDED
@@ -0,0 +1,281 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const os = require('os');
6
+ const { spawn } = require('child_process');
7
+ const { program } = require('commander');
8
+ const { createServer, destroyOpenSockets, DEFAULT_PORT } = require('../src/index.js');
9
+ const accessAuth = require('../src/access-auth.js');
10
+ const qr = require('../src/qr.js');
11
+
12
+ const { name, version, description } = require('../package.json');
13
+
14
+ const SERVE_CHILD_ENV = 'MOUAIF_SERVE_CHILD';
15
+ const WATCH_EXTS = new Set(['.js', '.jsx', '.json', '.css', '.html']);
16
+
17
+ function closeServer(server) {
18
+ return new Promise((resolve) => {
19
+ if (!server || !server.listening) return resolve();
20
+ // Force-close keep-alive / SSE sockets so server.close() can resolve
21
+ // promptly instead of hanging on connections that never end.
22
+ server.close(() => {
23
+ destroyOpenSockets();
24
+ resolve();
25
+ });
26
+ // Destroy immediately for sockets that are idle but still counted;
27
+ // server.close() only fires once all connections are gone.
28
+ destroyOpenSockets();
29
+ });
30
+ }
31
+
32
+ function displayOrigin(options, port) {
33
+ if (options.publicOrigin) return options.publicOrigin;
34
+ const host = String(options.host || '127.0.0.1');
35
+ if (host !== '0.0.0.0' && host !== '::') return `http://${host.includes(':') ? '[' + host + ']' : host}:${port}`;
36
+ const interfaces = os.networkInterfaces();
37
+ for (const entries of Object.values(interfaces)) {
38
+ for (const entry of entries || []) {
39
+ if (entry && entry.family === 'IPv4' && !entry.internal) return `http://${entry.address}:${port}`;
40
+ }
41
+ }
42
+ return `http://127.0.0.1:${port}`;
43
+ }
44
+
45
+ function collectWatchFiles(dir, out = []) {
46
+ let entries = [];
47
+ try { entries = fs.readdirSync(dir, { withFileTypes: true }); }
48
+ catch { return out; }
49
+ for (const entry of entries) {
50
+ if (entry.name === 'node_modules' || entry.name === '.git' || entry.name === 'dist' || entry.name === 'build') continue;
51
+ const full = path.join(dir, entry.name);
52
+ if (entry.isDirectory()) collectWatchFiles(full, out);
53
+ else if (WATCH_EXTS.has(path.extname(entry.name))) out.push(full);
54
+ }
55
+ return out;
56
+ }
57
+
58
+ function runSupervisor(options) {
59
+ const watch = !!options.watch;
60
+ const port = String(parseInt(options.port, 10));
61
+ const host = String(options.host || '127.0.0.1');
62
+ const publicOrigin = String(options.publicOrigin || process.env.MOUAIF_PUBLIC_ORIGIN || '');
63
+ const binPath = path.join(__dirname, 'mouaif.js');
64
+ const watchRoots = [path.join(__dirname), path.join(__dirname, '..', 'src')];
65
+ const watched = new Set();
66
+ let child = null;
67
+ let stopping = false;
68
+ let pendingRestart = false;
69
+ let debounce = null;
70
+
71
+ function startChild() {
72
+ const args = [binPath, 'serve', '--port', port, '--host', host];
73
+ if (publicOrigin) args.push('--public-origin', publicOrigin);
74
+ if (options.auth || options.user || options.authSetup) args.push('--auth');
75
+ if (options.user) args.push('--user', options.user);
76
+ if (options.authSetup) args.push('--auth-setup');
77
+ child = spawn(process.execPath, args, {
78
+ stdio: 'inherit',
79
+ env: { ...process.env, [SERVE_CHILD_ENV]: '1', ...(options.password ? { MOUAIF_PASSWORD: options.password } : {}) }
80
+ });
81
+ child.on('exit', (code, signal) => {
82
+ child = null;
83
+ if (stopping) return;
84
+ if (pendingRestart || code === 0) {
85
+ pendingRestart = false;
86
+ startChild();
87
+ return;
88
+ }
89
+ const hint = watch ? 'waiting for changes...' : 'not restarting (use --watch or a manual restart to try again).';
90
+ console.error(`[mouaif] server stopped (${signal || code}); ${hint}`);
91
+ });
92
+ }
93
+
94
+ function restartChild(file) {
95
+ if (debounce) clearTimeout(debounce);
96
+ debounce = setTimeout(() => {
97
+ console.log(`[mouaif] change detected: ${path.relative(process.cwd(), file)}; restarting...`);
98
+ pendingRestart = true;
99
+ if (child) child.kill('SIGTERM');
100
+ else startChild();
101
+ }, 100);
102
+ }
103
+
104
+ function refreshWatchFiles() {
105
+ for (const root of watchRoots) {
106
+ for (const file of collectWatchFiles(root)) {
107
+ if (watched.has(file)) continue;
108
+ watched.add(file);
109
+ fs.watchFile(file, { interval: 500 }, (cur, prev) => {
110
+ if (cur.mtimeMs !== prev.mtimeMs || cur.size !== prev.size) restartChild(file);
111
+ });
112
+ }
113
+ }
114
+ }
115
+
116
+ function stop() {
117
+ stopping = true;
118
+ for (const file of watched) fs.unwatchFile(file);
119
+ if (child) child.kill('SIGTERM');
120
+ }
121
+
122
+ if (watch) {
123
+ console.log('[mouaif] watch mode enabled');
124
+ refreshWatchFiles();
125
+ setInterval(refreshWatchFiles, 2000);
126
+ }
127
+ startChild();
128
+ process.on('SIGINT', () => { stop(); process.exit(0); });
129
+ process.on('SIGTERM', () => { stop(); process.exit(0); });
130
+ }
131
+
132
+ // Worker process — the actual HTTP server. Runs as a child of the supervisor
133
+ // so POST /api/restart (or a --watch source change) can drop it and let the
134
+ // supervisor spawn a brand-new process: every module is re-read from disk and
135
+ // runtime state (MCP children, DB handles, OAuth flows) is re-initialized.
136
+ function runWorker(options) {
137
+ const port = parseInt(options.port, 10);
138
+ const authEnabled = !!(options.auth || options.user || options.authSetup);
139
+ let server;
140
+ const lifecycle = {
141
+ restarting: false,
142
+ restart: async () => {
143
+ // Hand control back to the supervisor: close the listening socket,
144
+ // then exit with code 0 so the supervisor respawns a fresh worker.
145
+ await closeServer(server);
146
+ process.exit(0);
147
+ }
148
+ };
149
+
150
+ function start() {
151
+ server = createServer(port, { lifecycle, publicOrigin: options.publicOrigin, authEnabled });
152
+ server.listen(port, options.host, () => {
153
+ const servedOrigin = displayOrigin(options, port);
154
+ console.log(`🚀 mouaif server running at ${servedOrigin}`);
155
+ console.log(` Web: / — mobile UI`);
156
+ console.log(` REST: GET /data — get data`);
157
+ console.log(` REST: POST /data — update data`);
158
+ console.log(` SSE: GET /events — subscribe to events`);
159
+ console.log(` CDP: /api/inspector/ + WS /api/inspector/proxy`);
160
+ if (authEnabled && accessAuth.disabled()) {
161
+ console.log('');
162
+ console.log('🔓 App access is disabled from the app (Settings → Access & passkeys).');
163
+ console.log(' Re-enable it in Settings, or restart with --auth-setup to re-assert protection.');
164
+ } else if (authEnabled && (options.authSetup || !accessAuth.configured())) {
165
+ const setup = accessAuth.createSetupCode();
166
+ const setupUrl = servedOrigin + '/#/setup?code=' + encodeURIComponent(setup.code);
167
+ console.log('');
168
+ console.log('🔐 Set up app access (expires in 15 minutes)');
169
+ console.log(` Link: ${setupUrl}`);
170
+ console.log(` Code: ${setup.code}`);
171
+ try { console.log('\n' + qr.terminal(setupUrl)); } catch (_) { /* narrow terminals can use the link */ }
172
+ const securePasskeys = /^https:\/\//i.test(servedOrigin) || /^http:\/\/(localhost|127(?:\.\d+){3}|\[::1\])(?::|\/|$)/i.test(servedOrigin);
173
+ console.log(securePasskeys
174
+ ? ' The setup page can create a password and register a passkey.'
175
+ : ' Password setup is available. Passkeys require HTTPS for remote devices.');
176
+ }
177
+ console.log(' Press Ctrl+C to stop');
178
+ });
179
+ }
180
+
181
+ start();
182
+
183
+ // Last-resort breadcrumbs. The HTTP dispatcher (src/http-server.js
184
+ // handleRequest) already converts anything a request handler throws or
185
+ // rejects into a logged 500, so reaching these handlers means the
186
+ // failure came from outside a request (a timer, an SSE write, a
187
+ // background MCP child). Log them loudly and keep serving rather than
188
+ // letting Node's default `--unhandled-rejections=throw` exit the
189
+ // process and drop every connected client.
190
+ process.on('unhandledRejection', (reason) => {
191
+ console.error('[mouaif] unhandled rejection:', (reason && reason.stack) || reason);
192
+ });
193
+ process.on('uncaughtException', (error) => {
194
+ console.error('[mouaif] uncaught exception:', (error && error.stack) || error);
195
+ });
196
+
197
+ function shutdown() {
198
+ console.log('\n⏹ Shutting down...');
199
+ closeServer(server).then(() => process.exit(0));
200
+ }
201
+ process.on('SIGINT', shutdown);
202
+ process.on('SIGTERM', shutdown);
203
+ }
204
+
205
+ program
206
+ .name(name)
207
+ .version(version)
208
+ .description(description);
209
+
210
+ program
211
+ .command('serve')
212
+ .description('Start the HTTP server')
213
+ .option('-p, --port <port>', 'Port to listen on', DEFAULT_PORT)
214
+ .option('-h, --host <host>', 'Host to bind to', '127.0.0.1')
215
+ .option('--public-origin <origin>', 'Public HTTP(S) origin when served through a proxy', process.env.MOUAIF_PUBLIC_ORIGIN)
216
+ .option('--auth', 'Require app access authentication (disabled unless explicitly enabled)')
217
+ .option('--user <user>', 'Set the app access user before serving')
218
+ .option('--password <password>', 'Set the app access password before serving (prefer MOUAIF_PASSWORD to avoid shell history)')
219
+ .option('--auth-setup', 'Print a one-time setup link, QR code, and short code')
220
+ .option('-w, --watch', 'Restart the server when local source files change')
221
+ .action((options) => {
222
+ // `mouaif serve` always runs as a supervisor + worker pair. The
223
+ // supervisor (this process) stays alive and respawns a fresh worker
224
+ // process on POST /api/restart (and on source changes with --watch),
225
+ // so a restart always loads the latest code from disk.
226
+ if (process.env[SERVE_CHILD_ENV] === '1') {
227
+ return runWorker(options);
228
+ }
229
+
230
+ const suppliedPassword = options.password || process.env.MOUAIF_PASSWORD || '';
231
+ if ((options.user && !suppliedPassword) || (!options.user && suppliedPassword)) {
232
+ console.error('❌ --user and --password (or MOUAIF_PASSWORD) must be supplied together');
233
+ process.exitCode = 1;
234
+ return;
235
+ }
236
+ if (options.user) {
237
+ try {
238
+ accessAuth.setPassword(options.user, suppliedPassword);
239
+ console.log(`🔐 App access user set to ${accessAuth.user().username}`);
240
+ } catch (error) {
241
+ console.error('❌ Could not set app access:', error.message);
242
+ process.exitCode = 1;
243
+ return;
244
+ }
245
+ }
246
+ return runSupervisor(options);
247
+ });
248
+
249
+ program
250
+ .command('info')
251
+ .description('Show server info')
252
+ .action(() => {
253
+ console.log(`📦 mouaif v${version}`);
254
+ console.log(` ${description}`);
255
+ console.log(` Default port: ${DEFAULT_PORT}`);
256
+ });
257
+
258
+ program
259
+ .command('import-chats')
260
+ .description('Import chat transcripts from JSON files into the SQLite store')
261
+ .argument('<projectDir>', 'Absolute path to the project directory')
262
+ .option('--skip-existing', 'Skip chats already imported (only import missing messages)')
263
+ .action((projectDir, options) => {
264
+ const abs = path.resolve(projectDir);
265
+ if (!fs.existsSync(abs)) {
266
+ console.error('❌ Project directory does not exist:', abs);
267
+ process.exit(1);
268
+ }
269
+ const chatdb = require('../src/chatdb.js');
270
+ console.log(`📦 Importing chats from ${abs}...`);
271
+ const result = chatdb.importFromJson(abs, { skipExisting: !!options.skipExisting });
272
+ console.log(` ✅ ${result.chats} chats, ${result.messages} messages imported`);
273
+ if (result.errors.length) {
274
+ for (const e of result.errors) console.error(' ⚠️ ' + e);
275
+ }
276
+ if (result.chats === 0 && result.messages === 0) {
277
+ console.log(' Nothing to import.');
278
+ }
279
+ });
280
+
281
+ program.parse(process.argv);
@@ -0,0 +1 @@
1
+ import{d as f,h as M,u as B,k as a,f as T}from"./index-BGvI4n0T.js";async function D(e,o){const s=new URLSearchParams;e&&s.set("projectDir",e),o&&s.set("dir",o);const i=await T("/api/files?"+s.toString());return i.status!==200?{error:i.body&&i.body.error||"HTTP "+i.status}:{body:i.body}}function H(e){const o=e.projectDir||"",s=e.onPick||(()=>{}),i=e.onClose||(()=>{}),[d,m]=f(o||""),[u,C]=f(""),[b,h]=f([]),[k,p]=f(!1),[_,y]=f("");async function l(t){const n=typeof t=="string"&&t?t:o||"";p(!0),y("");const r=await D(o,n);if(r.error){y(r.error),h([]),p(!1);return}m(r.body.dir),C(r.body.browseTop||""),h(Array.isArray(r.body.entries)?r.body.entries:[]),p(!1)}M(()=>{l(o)},[o]);const x=B({onClose:()=>{i&&i()}});function w(){const t=d||"";if(!t)return;const n=u||"";if(n&&t===n)return;const r=t.replace(/[\\/]+$/,""),c=Math.max(r.lastIndexOf("\\"),r.lastIndexOf("/"));if(c<=0){l(n);return}const v=r.slice(0,c);if(n&&v===n){l(n);return}l(v)}function g(t){if(t.type==="dir"){l(t.path);return}t.binary||s(t.relPath)}function P(){const t=d||"";if(!t)return null;const n=u||"";if(n&&t===n)return null;const r=t.replace(/[\\/]+$/,""),c=Math.max(r.lastIndexOf("\\"),r.lastIndexOf("/"));return c>0?r.slice(0,c):n||null}return a("div",{class:"afp__overlay",role:"dialog","aria-modal":"true","aria-label":e.label||"Pick an agent file",onClick:t=>{t.target===t.currentTarget&&i()}},a("div",{class:"afp__sheet",ref:x},a("div",{class:"afp__head"},a("div",{class:"afp__title"},"Pick a file"),a("div",{class:"afp__sub"},e.description||"Tapping a file adds its project-relative path to the list."),a("div",{class:"afp__path-row"},a("button",{class:"icon-btn afp__iconbtn",type:"button",onClick:w,"aria-label":"Up one folder",title:"Up"},a("svg",{viewBox:"0 0 24 24",width:16,height:16,"aria-hidden":"true"},a("path",{d:"M12 4 4 12l8 8 1.4-1.4L8.8 14H20v-2H8.8l4.6-4.6L12 4Z",fill:"currentColor",transform:"rotate(-90 12 12)"}))),a("div",{class:"afp__path"},d||"project"),a("button",{class:"icon-btn icon-btn--close afp__iconbtn",type:"button",onClick:i,"aria-label":"Close",title:"Close"},a("svg",{viewBox:"0 0 24 24",width:16,height:16,"aria-hidden":"true"},a("path",{d:"M18.3 5.71 12 12l6.3 6.29-1.41 1.42L10.59 13.4 4.3 19.71 2.88 18.3 9.17 12 2.88 5.71 4.3 4.3l6.29 6.29 6.3-6.29 1.41 1.41Z",fill:"currentColor"}))))),a("div",{class:"afp__body"},_?a("p",{class:"afp__err"},_):a("ul",{class:"afp__list","aria-label":"Files and folders"},b.length===0?a("li",{class:"afp__empty"},k?"loading…":"no files here"):b.map(t=>a("li",{key:t.path,class:"afp__row"+(t.binary?" afp__row--binary":""),role:"button",tabindex:t.binary?-1:0,"aria-disabled":t.binary?"true":"false",title:t.binary?"binary file — cannot be picked here":t.relPath||t.path,onClick:()=>g(t),onKeydown:n=>{t.binary||(n.key==="Enter"||n.key===" ")&&(n.preventDefault(),g(t))}},a("span",{class:"afp__row-icon","aria-hidden":"true"},t.type==="dir"?a("svg",{viewBox:"0 0 24 24",width:16,height:16},a("path",{d:"M3 6a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V6Z",fill:"currentColor"})):a("svg",{viewBox:"0 0 24 24",width:16,height:16},a("path",{d:"M6 2h8l4 4v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2Zm7 1.5V7h3.5L13 3.5Z",fill:"currentColor"}))),a("span",{class:"afp__row-name"},t.name),t.type==="dir"?a("span",{class:"afp__row-meta"},t.hasChildren?"…":"·"):a("span",{class:"afp__row-meta"},t.binary?"binary":t.size<1024?t.size+" B":Math.round(t.size/1024)+" KB")))),a("div",{class:"afp__foot"},P()?a("button",{class:"btn btn--ghost afp__up",type:"button",onClick:w},"↑ Up"):null,a("button",{class:"btn",type:"button",onClick:i},"Cancel")))))}export{H as A};
@@ -0,0 +1,7 @@
1
+ import{d as s,A as a,am as D,a3 as C,h as T,f as S,u as A,k as t}from"./index-BGvI4n0T.js";function U(E){const{projectDir:i,onClose:p}=E,[I,_]=s(!0),[w,L]=s(""),[H,B]=s(""),[x,J]=s(i||""),[N,R]=s(!1),[O,P]=s(!1),l=a(null),u=a(null),h=a(null),K=a(null),[f,j]=s(""),c=a(null);c.current||(c.current=new D);const y=a(!0),m=C((e,r)=>{if(c.current||(c.current=new D),r==="exit"){c.current.write(`\r
2
+  └─ process exited with code `+e+`
3
+ `),j(c.current.render());return}c.current.write(String(e||"")),j(c.current.render())},[]);T(()=>{const e=l.current;if(!e)return;const r=e.scrollHeight-e.scrollTop-e.clientHeight<48;if(c.current?c.current.isFullScreen:!1){const n=e.scrollTop,g=e.scrollHeight;e.textContent=f,y.current||r?e.scrollTop=e.scrollHeight:e.scrollTop=n+(e.scrollHeight-g)}else if(y.current||r)e.textContent=f,e.scrollTop=e.scrollHeight;else{const n=e.scrollTop;e.textContent=f,e.scrollTop=n}},[f]);const M=C(e=>{if(l.current&&l.current._onScroll&&l.current.removeEventListener("scroll",l.current._onScroll),l.current=e,e){const r=()=>{y.current=e.scrollHeight-e.scrollTop-e.clientHeight<48};e._onScroll=r,e.addEventListener("scroll",r,{passive:!0})}},[]);T(()=>()=>{l.current&&l.current._onScroll&&l.current.removeEventListener("scroll",l.current._onScroll)},[]),T(()=>{let e=!1,r=null;async function o(){try{const n=await S("/api/tools/cli/session?projectDir="+encodeURIComponent(i||""));if(e)return;if(n.status!==200){L(n.body&&n.body.error||"HTTP "+n.status),_(!1);return}h.current=n.body.id,B(n.body.shell||""),P(!!n.body.interactive),n.body.projectDir&&J(n.body.projectDir),r=new EventSource("/events"),K.current=r,r.addEventListener("cli_output",g=>{let d;try{d=JSON.parse(g.data)}catch{return}!d||d.id!==h.current||m(d.data,d.stream)}),_(!1),u.current&&u.current.focus()}catch(n){e||(L(String(n)),_(!1))}}return o(),()=>{e=!0,r&&r.close(),h.current&&S("/api/tools/cli/close",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({projectDir:i})}).catch(()=>{})}},[i]);const q=A({onClose:()=>{p&&p()}}),[v,b]=s(""),k=C(async(e,r)=>{R(!0);try{const o=await S("/api/tools/cli/command",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({projectDir:i,cmd:e,raw:!!r})});o.status!==200&&m(`
4
+ `+(o.body&&o.body.error||"HTTP "+o.status)+`
5
+ `,"stderr")}catch(o){m(`
6
+ error: `+String(o)+`
7
+ `,"stderr")}finally{R(!1),u.current&&u.current.focus()}},[i,m]);async function z(){const e=v;!e.trim()&&!O||(b(""),await k(e,!1))}return t("div",{class:"cli__overlay",role:"dialog","aria-modal":"true","aria-label":"Command prompt"},t("div",{class:"cli__sheet",ref:q},t("div",{class:"cli__head"},t("div",{class:"cli__title-stack"},t("span",{class:"cli__title"},H?"CLI — "+H:"CLI"),t("span",{class:"cli__dir",title:x},x),O?t("span",{class:"cli__badge",title:"Interactive terminal — prompting programs can read your answer"},"interactive"):null),t("button",{class:"icon-btn icon-btn--close cli__iconbtn",type:"button",onClick:p,"aria-label":"Close",title:"Close"},t("svg",{viewBox:"0 0 24 24",width:16,height:16,"aria-hidden":"true"},t("path",{d:"M18.3 5.71 12 12l6.3 6.29-1.41 1.42L10.59 13.4 4.3 19.71 2.88 18.3 9.17 12 2.88 5.71 4.3 4.3l6.29 6.29 6.3-6.29 1.41 1.41Z",fill:"currentColor"})))),t("div",{class:"cli__body"},I?t("div",{class:"cli__empty"},"Starting command prompt…"):w?t("div",{class:"cli__error"},t("p",null,w),t("button",{class:"btn",type:"button",onClick:p},"Close")):t("div",{class:"cli__terminal"},t("pre",{ref:M,class:"cli__out","aria-label":"Command output",tabindex:"-1"}),t("div",{class:"cli__prompt-row"},t("span",{class:"cli__prompt-mark","aria-hidden":"true"},"❯"),t("input",{ref:u,class:"input cli__prompt",type:"text",value:v,onInput:e=>b(e.currentTarget.value),placeholder:"Type a command — runs in the project folder","aria-label":"Command line",autocomplete:"off",autocapitalize:"off",spellcheck:"false",disabled:N,onKeyDown:e=>{if(e.key==="Enter")if(e.preventDefault(),e.ctrlKey||e.metaKey){const r=v;b(""),k(r,!0)}else z()}}))))))}export{U as CliModal};
@@ -0,0 +1,2 @@
1
+ import{d as o,b as R,h as W,f as T,X as Bt,A as f,Y as he,Z as $,a0 as Et,a1 as Nt,a2 as me,a3 as tt,a4 as ye,a5 as be,a6 as ve,a7 as ge,k as e,a8 as _e,a9 as et,aa as Ot,R as we,ab as ke,ac as Te,ad as Re,ae as Ce,af as Me,ag as Ie,ah as Pe,ai as Ae,aj as Se,ak as je,al as De,p as Le,s as xe}from"./index-BGvI4n0T.js";const rt="dictation";function Ue(){const[l,p]=o([]),[m,Ft]=o([]),[y,nt]=o(""),[A,at]=o(""),[ot,Ut]=o(""),[st,Ht]=o(""),[Kt,it]=o(!0),[w,ct]=o(!0),[lt,dt]=o(""),[ut,zt]=o(()=>({dir:R.value&&R.value.dir||"",name:R.value&&R.value.name||""})),u=ut.dir;W(()=>{if(u)return;let t=!1;return(async()=>{try{const r=await T("/api/projects/registered"),a=r.status===200&&Array.isArray(r.body&&r.body.projects)?r.body.projects[0]:null;!t&&a&&zt({dir:a.path||"",name:a.name||a.path||""})}catch{}})(),()=>{t=!0}},[u]);const[pt]=o(()=>Bt()),[_,S]=o(!1),[ft,ht]=o(0),[mt,yt]=o(0),[g,bt]=o(null),[vt,gt]=o(""),[C,Y]=o(""),[M,j]=o(!1),[_t,D]=o(""),[Wt,i]=o(""),[Yt,c]=o(""),[q,qt]=o(null),wt=f(""),L=f(null),x=f(null),I=f([]),B=f(0),E=f(null),N=f(null),O=f(null),F=f(null),J=f(null),V=f(null),[k,U]=o(!1),[kt,Jt]=o([]),[Tt,Vt]=o([]);function X(t,r,a){const s=!!(a&&a.onlyIfEmpty),n=t.models;p(n),Ft(t.kinds),Jt(Array.isArray(t.liveFailures)?t.liveFailures:[]),Vt(Array.isArray(t.providers)?t.providers:[]);const d=Re(n,r);d&&(!s||!wt.current)&&(nt(d.modelId),at(d.providerId))}W(()=>{let t=!1;return(async()=>{ct(!0),dt("");let r={};try{const n=await T("/api/settings");n.status===200&&n.body&&n.body.app&&(r=n.body.app[rt]||{})}catch{}t||it(he(r));let a={models:[],kinds:[],total:0};try{a=await $(u,{live:!1})}catch(n){t||dt(n&&n.message?n.message:"Could not load models")}if(t)return;X(a,r);const s=Array.isArray(a.providers)&&a.providers.length>0;if(ct(!1),!(!u||!s)){U(!0);try{const n=await $(u);t||X(n,r,{onlyIfEmpty:!0})}catch{}finally{t||U(!1)}}})(),()=>{t=!0}},[u]);const Rt=f(null),[Xt,Ct]=o(()=>new Set),[Gt,Mt]=o([]);function P(){const t=Rt.current||{props:{projectDir:""},recentModels:[]};return Rt.current=t,t.props.projectDir=u,t}W(()=>{let t=!1;return Ct(Et(P())),Mt(Nt(P())),me(P()).then(r=>{!t&&r&&Mt(Nt(P()))}),()=>{t=!0}},[u]);function Qt(t){const r=P();je(r,t.provider||"",t.id),Ct(Et(r))}const H=tt(()=>{E.current&&(clearInterval(E.current),E.current=null),N.current&&(clearInterval(N.current),N.current=null),O.current&&(clearTimeout(O.current),O.current=null)},[]),G=tt(()=>{const t=x.current;if(x.current=null,t&&typeof t.getTracks=="function")for(const r of t.getTracks())try{r.stop()}catch{}if(F.current){try{F.current.close()}catch{}F.current=null}J.current=null,V.current=null,yt(0)},[]),It=tt(()=>{H();const t=L.current;if(t&&t.state!=="inactive")try{t.stop()}catch{}S(!1)},[H]);W(()=>()=>{H();const t=L.current;if(t&&t.state!=="inactive")try{t.stop()}catch{}const r=x.current;if(r&&typeof r.getTracks=="function")for(const a of r.getTracks())try{a.stop()}catch{}},[H]);function Zt(t,r){try{const a=r.AudioContext||r.webkitAudioContext;if(!a)return;const s=new a,n=s.createMediaStreamSource(t),d=s.createAnalyser();d.fftSize=1024,n.connect(d),F.current=s,J.current=d,V.current=new Uint8Array(d.fftSize),N.current=setInterval(()=>{const z=J.current,h=V.current;if(!z||!h)return;z.getByteTimeDomainData(h);let Lt=0;for(let Z=0;Z<h.length;Z++){const xt=(h[Z]-128)/128;Lt+=xt*xt}yt(Math.min(1,Math.sqrt(Lt/h.length)*Math.SQRT2))},250)}catch{}}async function $t(){i(""),c("");const t=typeof window<"u"?window:null;if(!t||!Bt(t)){i("This browser cannot record audio."),c("error");return}let r;try{r=await t.navigator.mediaDevices.getUserMedia({audio:!0})}catch(n){i(n&&n.name==="NotAllowedError"?"Microphone permission was refused. Allow it in the browser, then tap again.":"Could not open the microphone: "+(n&&n.message||n)),c("error");return}x.current=r;const a=Me(t.MediaRecorder,t);let s;try{s=a?new t.MediaRecorder(r,{mimeType:a}):new t.MediaRecorder(r)}catch{try{s=new t.MediaRecorder(r)}catch(n){G(),i("Could not start the recorder: "+(n&&n.message||n)),c("error");return}}I.current=[],s.ondataavailable=n=>{n&&n.data&&n.data.size&&I.current.push(n.data)},s.onstop=()=>{const n=s.mimeType||a||"audio/webm",d=I.current.length?new Blob(I.current,{type:n}):null;I.current=[],L.current=null,G(),S(!1),d&&d.size?(bt(d),gt(n),i("Recorded "+et(Date.now()-B.current)+". Ready to transcribe."),c("success")):(i("Nothing was captured — check that the microphone is not muted."),c("error"))},s.onerror=()=>{G(),S(!1),i("The recorder stopped unexpectedly."),c("error")},L.current=s,B.current=Date.now(),bt(null),gt(""),ht(0),S(!0),s.start(),E.current=setInterval(()=>ht(Date.now()-B.current),200),O.current=setTimeout(()=>It(),Ot),Zt(r,t)}async function te(){if(!y){i("Pick a dictation model first."),c("error");return}if(!g){i("Record something first."),c("error");return}j(!0),D("transcribe"),i("Transcribing with "+y+"…"),c("busy");try{const t=await Ie(g),r=await Pe({projectDir:u,modelId:y,providerId:A,kind:v&&v.kind||"",audioBase64:t,mimeType:vt||g.type||"audio/webm",filename:Ae(vt||g.type,B.current),language:ot,prompt:st});Y(r.text||""),qt({model:r.model,kind:r.kind,bytes:r.bytes,durationMs:r.durationMs,usage:r.usage||null,costLabel:Se(r).label}),i("Transcribed with "+(r.model&&r.model.id||y)+"."),c("success")}catch(t){i(t&&t.message||"Transcription failed"),c("error")}finally{j(!1),D("")}}async function ee(){try{await navigator.clipboard.writeText(C),i("Transcript copied."),c("success")}catch{i("Could not copy — long-press the text instead."),c("error")}}async function re(t){const r=u;if(!r){i("Open a project first, then this can hand the text to a chat."),c("error");return}j(!0),D("handoff");try{const a=await T("/api/chats?projectDir="+encodeURIComponent(r)+"&limit=1");if(a.status!==200)throw new Error(a.body&&a.body.error||"HTTP "+a.status);const s=a.body&&a.body.chats&&a.body.chats[0]||null;if(!s)throw new Error("This project has no chat yet — start one in the Chats tab.");const n=await T("/api/chats/"+encodeURIComponent(s.id)+"?projectDir="+encodeURIComponent(r));if(n.status!==200||!n.body||!n.body.chat)throw new Error(n.body&&n.body.error||"HTTP "+n.status);const d=typeof n.body.chat.draft=="string"?n.body.chat.draft:"",z=d.trim()?d.replace(/\s+$/,"")+`
2
+ `+C:C,h=await T("/api/chats/"+encodeURIComponent(s.id),{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({projectDir:r,draft:z})});if(h.status!==200)throw new Error(h.body&&h.body.error||"HTTP "+h.status);i(t?'Draft filled in "'+(s.title||s.id)+'" — send it from the chat.':'Draft filled in "'+(s.title||s.id)+'".'),c("success")}catch(a){i(a&&a.message||"Could not reach the chat."),c("error")}finally{j(!1),D("")}}const ne=ye(l),ae=be(l).map(t=>({id:t.id,provider:t.provider||""})),oe=y?{providerId:A,modelId:y}:null,se=ve({text:C,chatId:u,hasRecording:!!g}),ie=l.filter(t=>t.source!=="live").length,ce=l.some(t=>t.source==="live"),Pt=Array.isArray(Tt)?Tt:[],b=Ce({catalogBusy:w,liveBusy:k,transcribing:M&&_t==="transcribe",handoff:M&&_t==="handoff"}),Q=De({catalogBusy:w,liveBusy:k,projectCount:ie,hasLive:ce});async function le(){if(!(k||w)){U(!0),i(""),c("");try{const t=await $(u,{refresh:!0});X(t,{modelId:y,providerId:A},{onlyIfEmpty:!1})}catch(t){i(t&&t.message||"Could not refresh the model list."),c("error")}finally{U(!1)}}}function de(t){const r=t?t.modelId:"",a=t?t.providerId:"";wt.current=r,nt(r),at(a),i(""),c(""),At({modelId:r,providerId:a})}const ue=f({record:null,chain:Promise.resolve()});async function At(t){const r=ue.current;return r.record=Object.assign({},r.record,t),r.chain=r.chain.then(async()=>{try{const a=await T("/api/settings"),s=a.status===200&&a.body&&a.body.app&&a.body.app[rt]||{},n=Object.assign({},s,r.record);r.record=n,await xe({[rt]:n})}catch{}}),r.chain}function pe(t){it(t),At({live:t})}const v=l.find(t=>t.id===y&&(t.provider||"")===A)||null,St=v&&v.source==="live"?ge(v):"",K=v&&v.kind||"",jt=Ee(ut,u),Dt=_?"Stop":ft?"Record again":"Record",fe=!!g&&!!y&&!M&&!_;return e("section",{class:"dictation"},e("div",{class:"view-head"},e("a",{href:"#/settings",class:"view-back","aria-label":"Back to settings"},"←"),e("h2",{class:"view-title"},"Dictation")),e("div",{class:"group"},e("div",{class:"group__title"},"Dictation model",jt?e("span",{class:"group__title-note"},jt):null),e("div",{class:"dictation__fields"},e("div",{class:"dictation__field"},e(we,{models:ne,value:oe,onChange:de,onOpen:()=>{},placeholder:w?"Loading models…":Be(l)||"Pick a model",ariaLabel:"Pick dictation model",recommended:ae,pinned:Xt,onTogglePin:Qt,recent:Gt,variant:"sheet",refreshEmpty:"No dictation models"})),v&&K?e("div",{class:"dictation__field"},e("span",{class:"label"},"Sends as"),e("span",{class:"dictation__kind-readout",title:ke(m,K)||K},Te(K))):null),e("ul",{class:"dictation__options"},e("li",null,e("label",{class:"dictation__option-row",for:"dictation-live"},e("span",{class:"dictation__option-body"},e("span",{class:"dictation__option-title"},"Live transcription"),e("span",{class:"dictation__option-note"},"In a chat, the composer fills in as you speak instead of waiting for you to stop. This page still records and transcribes once.")),e("span",{class:"switch"},e("input",{id:"dictation-live",type:"checkbox",checked:Kt,onChange:t=>pe(!!(t.target&&t.target.checked))}),e("span",{class:"switch__track","aria-hidden":"true"},e("span",{class:"switch__thumb"}))))),e("li",null,e("div",{class:"dictation__option-row"},e("span",{class:"dictation__option-body"},e("label",{class:"dictation__option-title",for:"dictation-language"},"Language"),e("span",{class:"dictation__option-note"},"Optional. An ISO-639-1 or BCP-47 code (en, fr, de) that biases decoding instead of leaving it to guess.")),e("input",{class:"input dictation__option-input",id:"dictation-language",type:"text",placeholder:"en, fr, de…",value:ot,onInput:t=>Ut(t.target.value.slice(0,20))}))),e("li",null,e("div",{class:"dictation__option-row"},e("span",{class:"dictation__option-body"},e("label",{class:"dictation__option-title",for:"dictation-prompt"},"Vocabulary hint"),e("span",{class:"dictation__option-note"},"Optional. Names and jargon the provider should expect — the OpenAI-style prompt field.")),e("input",{class:"input dictation__option-input",id:"dictation-prompt",type:"text",placeholder:"mouaif, MediaRecorder, SSE…",value:st,onInput:t=>Ht(t.target.value.slice(0,400))})))),e("div",{class:"dictation__catalog-note"},e("span",{class:"hint hint--compact dictation__catalog-line","aria-busy":Q.loading?"true":void 0},Q.loading?e("span",{class:"dictation__spinner","aria-hidden":"true"}):null,Q.text),u&&Pt.length?e("button",{class:"dictation__refresh",type:"button",disabled:k||w,onClick:le,"aria-label":"Refresh the model list from the provider"},k?"Refreshing…":"Refresh"):null),St?e("p",{class:"hint hint--compact dictation__selected-badge"},v.id+" — "+St):null,lt?e("p",{class:"hint hint--compact dictation__error"},"Could not read the model list: "+lt):null,kt.length?e("div",{class:"dictation__failures"},kt.map(t=>e("p",{key:t.provider,class:"hint hint--compact dictation__error"},"No models from "+Ne(t.provider)+" — "+t.error))):null,!w&&!k&&!l.length?e("p",{class:"hint hint--compact"},Pt.length?"Your providers returned no usable models. Check the connection in Settings → Providers, then tap Refresh.":"No models yet, and no provider connection to list them from. Connect a provider in Settings → Providers (or add a model to this project in .mouaif.json), then come back."):null),e("div",{class:"group"},e("div",{class:"group__title"},"Test"),e("div",{class:"dictation__card dictation__card--recorder"},e("div",{class:"dictation__clock"},e("span",{class:"dictation__time","aria-live":"polite"},et(ft)),e("span",{class:"dictation__limit"},"max "+et(Ot))),e("div",{class:"dictation__meter",role:"meter","aria-label":"Input level","aria-valuenow":Math.round(mt*100),"aria-valuemin":0,"aria-valuemax":100},e("span",{class:"dictation__meter-fill",style:{width:Math.round((_?mt:0)*100)+"%"}})),e("button",{class:"dictation__record"+(_?" is-recording":g?" is-secondary":""),type:"button",disabled:!pt||M,onClick:_?It:$t,"aria-busy":b==="handoff"?"true":void 0,"aria-label":_?"Stop recording":b==="handoff"?"Working…":Dt},b==="handoff"?e("span",{class:"dictation__spinner","aria-hidden":"true"}):e("span",{class:"dictation__record-dot","aria-hidden":"true"}),e("span",{class:"dictation__record-label"},b==="handoff"?"Working…":Dt)),g&&!_?e("div",{class:"dictation__recorder-actions"},e("span",{class:"dictation__ready"},"Recording ready"),e("button",{class:"dictation__transcribe",type:"button",disabled:!fe,onClick:te,"aria-busy":b==="transcribe"?"true":void 0,"aria-label":"Transcribe the recording"},b==="transcribe"?e("span",{class:"dictation__spinner dictation__spinner--on-accent","aria-hidden":"true"}):null,b==="transcribe"?"Transcribing…":"Transcribe")):null,pt?null:e("p",{class:"hint hint--compact"},"This browser cannot record audio. Dictation needs a browser with MediaRecorder and microphone access."))),e("div",{class:"group"},e("div",{class:"group__title"},"Transcript"),e("div",{class:"dictation__transcript","aria-busy":b==="transcribe"||b==="handoff"?"true":void 0},e("textarea",{class:"input dictation__textarea",rows:6,placeholder:"The transcript appears here and stays editable.",value:C,"aria-label":"Transcript",onInput:t=>Y(t.target.value)}),e("div",{class:"dictation__actions"},se.map(t=>e("button",{key:t.id,class:"btn"+(t.id==="send"?" btn--primary":"")+(t.id==="clear"?" btn--ghost":""),type:"button",disabled:t.disabled||M,onClick:()=>{if(t.id==="copy")return ee();if(t.id==="clear"){Y(""),i(""),c("");return}return re(t.id==="send")}},t.label)))),q?e("p",{class:"hint hint--compact dictation__last-run",title:Oe(q)},_e(q)):null),e("p",{class:"status dictation__status","aria-live":"polite","data-state":Yt||void 0},Wt))}function Be(l){return l&&l.length?"":"No models in this project"}function Ee(l,p){if(!p)return"no project";const m=R.value&&R.value.dir||"";return m&&m===p?"":l&&l.name||""}function Ne(l){const p=Le(l);return p&&p.label||l}function Oe(l){const p=l&&l.usage;if(!p)return"The provider reported no token counts for this run";const m=[];return p.promptTokens&&m.push("input "+p.promptTokens+" tok"),p.completionTokens&&m.push("output "+p.completionTokens+" tok"),m.length?"Provider usage: "+m.join(", "):"The provider reported no token counts for this run"}export{Ue as DictationView};
@@ -0,0 +1,2 @@
1
+ import{A as S,d,a3 as y,h as D,_ as bt,k as e,S as M,D as pt,f as L}from"./index-BGvI4n0T.js";import{b as F,o as yt,l as mt,h as _t,H as vt,c as gt,d as wt,i as Ct,e as xt,f as kt,I as Pt,s as St,k as Dt,m as Mt,r as Lt,q as Ht,J as Tt,a as jt,z as Bt,A as zt,B as At,C as Et,F as Rt,v as Y,g as Zt}from"./codemirror-Bp6CUUFk.js";function Ft(l){if(!l)return null;const n=l.toLowerCase(),o=n.split("/").pop()||n;if(o==="dockerfile")return null;const a=(o.match(/\.[a-z0-9]+$/)||[""])[0];switch(a){case".js":case".jsx":case".mjs":case".cjs":return Y({jsx:a===".jsx"});case".ts":case".tsx":return Y({jsx:a===".tsx",typescript:!0});case".html":case".htm":case".svg":case".xml":case".mdx":return Rt();case".css":case".scss":case".sass":case".less":return Et();case".json":return At();case".md":case".markdown":return zt();case".py":return Bt();default:return null}}async function Ot(l,n){const o=new URLSearchParams;l&&o.set("projectDir",l),n&&o.set("dir",n);const a=await L("/api/files?"+o.toString());return a.status!==200?{error:a.body&&a.body.error||"HTTP "+a.status,code:a.body&&a.body.code}:{body:a.body}}async function Ut(l,n){const o=new URLSearchParams;l&&o.set("projectDir",l),o.set("path",n);const a=await L("/api/file?"+o.toString());return a.status!==200?{error:a.body&&a.body.error||"HTTP "+a.status,code:a.body&&a.body.code}:{body:a.body}}async function Vt(l,n,o){const a=await L("/api/file",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({projectDir:l,path:n,content:o})});return a.status!==200?{error:a.body&&a.body.error||"HTTP "+a.status,code:a.body&&a.body.code}:{body:a.body}}async function It(l,n){const o=new URLSearchParams;l&&o.set("projectDir",l),o.set("path",n);const a=await L("/api/file-media?"+o.toString());return a.status!==200?{error:a.body&&a.body.error||"HTTP "+a.status,code:a.body&&a.body.code}:{body:a.body}}function qt(l){const n=l.projectDir||"",o=l.onClose||(()=>{}),a=S(n),[H,O]=d(n||""),[v,tt]=d(""),[U,V]=d([]),[et,T]=d(""),[rt,j]=d(!1),[c,I]=d(null),[u,x]=d(null),[Kt,K]=d(!1),[at,g]=d(""),[m,w]=d(!1),[k,G]=d(!1),[B,h]=d(""),[st,z]=d(!1),[nt,it]=d(null),A=S(null),i=S(null),[P,W]=d(!1),E=S(null),b=y(async t=>{const r=typeof t=="string"&&t?t:a.current||n;j(!0),T("loading…");const s=await Ot(n,r);if(s.error){T(s.error),V([]),j(!1);return}a.current=s.body.dir,O(s.body.dir),tt(s.body.browseTop||""),V(Array.isArray(s.body.entries)?s.body.entries:[]),T((s.body.entries?s.body.entries.length:0)+" items"),j(!1)},[n]);D(()=>{b(n)},[b,n]);const _=y(()=>m?window.confirm("Discard unsaved changes?"):!0,[m]),R=y(async t=>{if(!_())return;x(null),g(""),h("loading…");const r=await Ut(n,t);if(r.error){h(r.error);return}const s={relPath:r.body.relPath,absPath:r.body.path,content:r.body.content,size:r.body.size,ext:r.body.ext};I(s),w(!1),h(s.size+" bytes")},[n,_]),q=y(async(t,r)=>{if(r&&r.forceText)return R(t);K(!0),g("loading…");const s=await It(n,t);if(K(!1),s.error){g(s.error),x(null);return}I(null),w(!1),h(""),x({relPath:s.body.relPath,absPath:s.body.path,dataUrl:s.body.dataUrl,mime:s.body.mime,size:s.body.size,ext:s.body.ext}),g(s.body.size+" bytes · "+s.body.mime)},[n,R]);bt(()=>{c&&ot(c)},[c]);function ot(t){if(!A.current)return;i.current&&(i.current.destroy(),i.current=null);const r=Ft(t.relPath),s=[mt(),_t(),vt(),gt(),wt(),Ct(),xt(),kt(),Pt(),St(Zt),Dt.of([{key:"Mod-s",preventDefault:!0,run:()=>(E.current&&E.current(),!0)},Mt,...Lt,...Ht,...Tt]),F.lineWrapping,yt,F.updateListener.of(p=>{p.docChanged&&(m||w(!0),B&&B.indexOf("saved")!==0&&h("edited"))})];r&&s.push(r);const f=jt.create({doc:t.content,extensions:s});i.current=new F({state:f,parent:A.current}),requestAnimationFrame(()=>{i.current&&i.current.requestMeasure()})}D(()=>()=>{i.current&&(i.current.destroy(),i.current=null)},[]),D(()=>{u&&i.current&&(i.current.destroy(),i.current=null)},[u]);const Z=y(async()=>{if(!c||!i.current)return;G(!0),h("saving…");const t=i.current.state.doc.toString(),r=await Vt(n,c.relPath,t);if(G(!1),r.error){h("save failed: "+r.error);return}w(!1),h("saved · "+r.body.size+" bytes")},[c,n]);D(()=>{E.current=Z},[Z]);const lt=y(()=>{!c||!i.current||_()&&(i.current.dispatch({changes:{from:0,to:i.current.state.doc.length,insert:c.content}}),w(!1),h("reverted"))},[c,_]),J=y(()=>{_()&&o()},[_,o]);function $(){const t=a.current||"";if(!t)return;const r=v||"";if(r&&t===r)return;const s=t.replace(/[\\/]+$/,""),f=Math.max(s.lastIndexOf("\\"),s.lastIndexOf("/"));if(f<=0){b(r);return}const p=s.slice(0,f);if(r&&p===r){b(r);return}b(p)}function ct(t){if(t.key==="Enter"){t.preventDefault();const r=(H||"").trim();b(r||v||n)}}function dt(){const t=(H||"").trim();b(t||v||n)}function N(t){if(t.type==="dir"){b(t.path);return}if(t.image){q(t.relPath);return}t.binary||R(t.relPath)}function ut(){if(!c||!i.current)return;const t=i.current.state.selection.main;if(t.empty){h("Select code before using Draft Craft."),i.current.focus();return}const r=i.current.state.sliceDoc(t.from,t.to),s=i.current.state.doc.lineAt(t.from).number,f=i.current.state.doc.lineAt(t.to).number;it({projectDir:n,text:c.relPath+":"+s+(f!==s?"-"+f:"")+`
2
+ `+r}),z(!0)}function ft(){const t=a.current||"";if(!t)return null;const r=v||"";if(r&&t===r)return null;const s=t.replace(/[\\/]+$/,""),f=Math.max(s.lastIndexOf("\\"),s.lastIndexOf("/"));return f>0?s.slice(0,f):r||null}function ht(){const t=v||n||"",r=a.current||t;if(!t||r===t)return e(M,null,e("span",{class:"fe__crumb fe__crumb--root"},t||"project"));const s=r.startsWith(t)?r.slice(t.length).replace(/^[\\/]+/,""):r,f=s?s.split(/[\\/]+/):[],p=[];p.push(e("button",{key:"root",type:"button",class:"fe__crumb fe__crumb--root",onClick:()=>b(t)},t.split(/[\\/]+/).pop()||t));let C=t;return f.forEach((Q,X)=>{C=C+(C.endsWith("\\")||C.endsWith("/")?"":"/")+Q,p.push(e("span",{key:"sep-"+X,class:"fe__crumb-sep","aria-hidden":"true"},"/")),p.push(e("button",{key:"seg-"+X,type:"button",class:"fe__crumb",onClick:()=>b(C)},Q))}),e(M,null,p)}return e("div",{class:"fe__overlay",role:"dialog","aria-modal":"true","aria-label":"File editor",onClick:t=>{}},e("div",{class:"fe__sheet"+(P?" fe__sheet--full":"")},e("div",{class:"fe__head"},P?e("div",{class:"fe__path-row"},e("button",{class:"icon-btn fe__iconbtn is-active",type:"button",onClick:()=>W(!1),"aria-label":"Show file list","aria-pressed":"true",title:"Show file list"},e("svg",{viewBox:"0 0 24 24",width:18,height:18,"aria-hidden":"true"},e("path",{d:"M4 4h7v16H4zM13 4h7v16h-7z",fill:"currentColor"}))),e("button",{class:"icon-btn icon-btn--close fe__iconbtn",type:"button",onClick:J,"aria-label":"Close",title:"Close"},e("svg",{viewBox:"0 0 24 24",width:16,height:16,"aria-hidden":"true"},e("path",{d:"M18.3 5.71 12 12l6.3 6.29-1.41 1.42L10.59 13.4 4.3 19.71 2.88 18.3 9.17 12 2.88 5.71 4.3 4.3l6.29 6.29 6.3-6.29 1.41 1.41Z",fill:"currentColor"})))):e("div",{class:"fe__path-row"},e("button",{class:"icon-btn fe__iconbtn",type:"button",onClick:$,"aria-label":"Up one folder",title:"Up"},e("svg",{viewBox:"0 0 24 24",width:16,height:16,"aria-hidden":"true"},e("path",{d:"M12 4 4 12l8 8 1.4-1.4L8.8 14H20v-2H8.8l4.6-4.6L12 4Z",fill:"currentColor",transform:"rotate(-90 12 12)"}))),e("div",{class:"fe__path-input-wrap"},e("input",{class:"input fe__path-input",type:"text",value:H,onInput:t=>O(t.currentTarget.value),onKeydown:ct,placeholder:"Folder path (absolute, or under this project)","aria-label":"Current folder",spellcheck:"false",autocomplete:"off"})),e("button",{class:"icon-btn fe__iconbtn",type:"button",onClick:dt,"aria-label":"Go to folder",title:"Go"},"Go"),e("button",{class:"icon-btn fe__iconbtn",type:"button",onClick:()=>b(a.current),"aria-label":"Refresh",title:"Refresh"},e("svg",{viewBox:"0 0 24 24",width:16,height:16,"aria-hidden":"true"},e("path",{d:"M12 4V1L7 6l5 5V7c3.31 0 6 2.69 6 6 0 1-.25 1.97-.7 2.8l1.46 1.46A7.93 7.93 0 0 0 20 13c0-4.42-3.58-8-8-8Zm-5.3 7.7A7.93 7.93 0 0 0 4 13c0 4.42 3.58 8 8 8v3l5-5-5-5v3c-3.31 0-6-2.69-6-6 0-1 .25-1.97.7-2.8L5.24 10.24Z",fill:"currentColor"}))),e("button",{class:"icon-btn fe__iconbtn",type:"button",onClick:()=>W(!0),"aria-label":"Hide file list","aria-pressed":"false",title:"Hide file list"},e("svg",{viewBox:"0 0 24 24",width:18,height:18,"aria-hidden":"true"},e("path",{d:"M3 5h18v14H3z",fill:"currentColor"}))),e("button",{class:"icon-btn icon-btn--close fe__iconbtn",type:"button",onClick:J,"aria-label":"Close",title:"Close"},e("svg",{viewBox:"0 0 24 24",width:16,height:16,"aria-hidden":"true"},e("path",{d:"M18.3 5.71 12 12l6.3 6.29-1.41 1.42L10.59 13.4 4.3 19.71 2.88 18.3 9.17 12 2.88 5.71 4.3 4.3l6.29 6.29 6.3-6.29 1.41 1.41Z",fill:"currentColor"})))),!P&&e("div",{class:"fe__crumbs","aria-label":"Breadcrumb"},ht()),!P&&e("div",{class:"fe__list-status"},e("span",{class:"status"},et||" "),ft()?e("button",{class:"btn btn--ghost fe__list-up",type:"button",onClick:$},"↑ Up"):null)),e("div",{class:"fe__body"},e("div",{class:"fe__list-wrap"},e("ul",{class:"fe__list","aria-label":"Files and folders"},U.length===0?e("li",{class:"fe__empty"},rt?"loading…":"no files here"):U.map(t=>e("li",{key:t.path,class:"fe__row"+(t.binary?" fe__row--binary":"")+(t.image?" fe__row--image":"")+(c&&c.relPath===t.relPath||u&&u.relPath===t.relPath?" is-open":""),role:"button",tabindex:t.binary?-1:0,"aria-disabled":t.binary?"true":"false",title:t.binary&&!t.image?"binary file — cannot be opened here":t.path,onClick:()=>N(t),onKeydown:r=>{t.binary&&!t.image||(r.key==="Enter"||r.key===" ")&&(r.preventDefault(),N(t))}},e("span",{class:"fe__row-icon","aria-hidden":"true"},t.type==="dir"?e("svg",{viewBox:"0 0 24 24",width:16,height:16},e("path",{d:"M3 6a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V6Z",fill:"currentColor"})):t.image?e("svg",{viewBox:"0 0 24 24",width:16,height:16},e("path",{d:"M21 5H3a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h18a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2Zm-9 11-3-4-2.5 3L4 13.5V7h16v9l-4-3-4 3Zm-4-7a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3Z",fill:"currentColor"})):e("svg",{viewBox:"0 0 24 24",width:16,height:16},e("path",{d:"M6 2h8l4 4v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2Zm7 1.5V7h3.5L13 3.5Z",fill:"currentColor"}))),e("span",{class:"fe__row-name"},t.name),t.type==="dir"?e("span",{class:"fe__row-meta"},t.hasChildren?"…":"·"):e("span",{class:"fe__row-meta"},t.image?"image":t.binary?"binary":t.size<1024?t.size+" B":Math.round(t.size/1024)+" KB"))))),e("div",{class:"fe__editor-wrap"},u?e(M,null,e("div",{class:"fe__editor-head"},e("div",{class:"fe__editor-path",title:u.absPath},u.relPath),e("div",{class:"fe__editor-actions"},u.ext===".svg"||u.mime==="image/svg+xml"?e("button",{class:"btn",type:"button",onClick:()=>q(u.relPath,{forceText:!0}),title:"Open this file in the code editor"},"Edit"):null,e("button",{class:"btn",type:"button",onClick:()=>{x(null),g("")},title:"Close preview"},"Close"))),e("div",{class:"fe__media-host"},e("img",{class:"fe__media-img",src:u.dataUrl,alt:u.relPath,draggable:"false"})),e("div",{class:"fe__editor-status"},e("span",{class:"status"},at||" "))):c?e(M,null,e("div",{class:"fe__editor-head"},e("div",{class:"fe__editor-path",title:c.absPath},c.relPath+(m?" •":"")),e("div",{class:"fe__editor-actions"},e("button",{class:"btn fe__draft-craft",type:"button",onClick:ut,disabled:k,title:"Add the selected code to any chat draft"},e("svg",{viewBox:"0 0 24 24",width:18,height:18,"aria-hidden":"true"},e("path",{d:"M4 4h16v13a3 3 0 0 1-3 3H7a3 3 0 0 1-3-3V4Zm2 2v9h4a2 2 0 0 0 4 0h4V6H6Z",fill:"currentColor"})),e("span",null,"Draft Craft")),e("button",{class:"btn",type:"button",onClick:lt,disabled:!m||k,title:"Revert to the last saved version"},"Revert"),e("button",{class:"btn btn--primary",type:"button",onClick:Z,disabled:!m||k,title:"Save (Ctrl/Cmd+S is wired to the editor save keymap via the browser default)","aria-label":"Save file"},k?"Saving…":"Save"))),e("div",{ref:A,class:"fe__editor-host"}),e("div",{class:"fe__editor-status"},e("span",{class:"status"},B||" "))):e("div",{class:"fe__editor-empty"},e("p",null,"Pick a file from the list to start editing, or tap an image to preview it."),e("p",{class:"fe__editor-hint"},"Tip: type a path above or use the breadcrumb to jump to a folder.")))),e(pt,{open:st,payload:nt,placement:"top",onClose:()=>z(!1),onAdded:t=>{z(!1),h("Selected code added with Draft Craft."),l.onDraftCraftAdded&&l.onDraftCraftAdded(t)}})))}export{qt as FileEditorView};
@@ -0,0 +1,2 @@
1
+ import{d as m,a3 as H,h as gt,u as K,k as t,S as z,f as O,A as ht,c as pt}from"./index-BGvI4n0T.js";async function Q(s,a,n,o){const c=await O("/api/git",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({projectDir:s,action:a,args:n||"",message:o||""})});return c.status!==200?{ok:!1,stderr:c.body&&c.body.error||"HTTP "+c.status}:c.body||{ok:!1,stderr:"no response"}}async function X(s,a,n){const o=await O("/api/git",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({projectDir:s,action:a,files:n})});return o.status!==200?{ok:!1,stderr:o.body&&o.body.error||"HTTP "+o.status}:o.body||{ok:!1,stderr:"no response"}}function Y(s){return String(s||"").split(" -> ")[0]}function _t({diff:s}){if(!s)return null;const a=s.split(`
2
+ `);return t("div",{class:"gm__diff"},t("pre",{class:"gm__diff-pre","aria-label":"Diff"},a.map((n,o)=>{let c="";return n.startsWith("@@")?c=" gm__diff-hunk":n.startsWith("+")&&!n.startsWith("+++")?c=" gm__diff-add":n.startsWith("-")&&!n.startsWith("---")?c=" gm__diff-del":n.startsWith("diff --git")&&(c=" gm__diff-file"),t("span",{key:o,class:"gm__diff-line"+c},n||" ")})))}function et({file:s,defaultOpen:a,action:n,onAction:o,busy:c}){const[h,r]=m(!!a),p=!!(s.diff&&s.diff.trim()),f=n==="unstage"?"Unstage":"Stage";return t("div",{class:"gm__file"},t("div",{class:"gm__file-head"},t("button",{class:"gm__file-toggle",type:"button",onClick:()=>r(!h),"aria-expanded":String(h),"aria-label":(p?"Toggle diff for ":"")+s.path},t("span",{class:"gm__file-status gm__file-status--"+s.status},s.statusText||s.status),t("span",{class:"gm__file-path",title:s.path},s.path),p?t("span",{class:"gm__file-caret","aria-hidden":"true"},h?"▾":"▸"):null),o?t("button",{class:"gm__file-action"+(n==="unstage"?" gm__file-action--unstage":""),type:"button",disabled:c,onClick:()=>o(s),"aria-label":f+" "+s.path,title:f+" "+s.path},f):null),h&&p?t(_t,{diff:s.diff}):null)}function ft({projectDir:s,commit:a,busy:n,onAction:o}){const[c,h]=m(!1),[r,p]=m(null),[f,l]=m(""),[g,w]=m(!1),[d,T]=m(""),y=H(async()=>{if(!(g||d===a.hash)){w(!0),l("");try{const _=new URLSearchParams;_.set("projectDir",s),_.set("hash",a.hash);const b=await O("/api/git/commit-files?"+_.toString());b.status===200&&b.body&&b.body.ok?(p(b.body.files||[]),T(a.hash)):l(b.body&&b.body.error||"HTTP "+b.status)}catch(_){l(String(_))}w(!1)}},[s,a.hash,g,d]);function C(){const _=!c;h(_),_&&d!==a.hash&&y()}return t("div",{class:"gm__commit"},t("div",{class:"gm__commit-row"},t("button",{class:"gm__commit-head"+(c?" is-open":""),type:"button",onClick:C,"aria-expanded":String(c),"aria-label":"Toggle commit "+a.short},t("span",{class:"gm__commit-caret","aria-hidden":"true"},c?"▾":"▸"),t("span",{class:"gm__commit-hash"},a.short),t("span",{class:"gm__commit-subject",title:a.subject},a.subject),t("span",{class:"gm__commit-meta"},a.author?t("span",{class:"gm__commit-author"},a.author):null,a.date?t("span",{class:"gm__commit-date"},yt(a.date)):null)),t(bt,{commit:a,busy:n,onAction:o})),c&&t("div",{class:"gm__commit-body"},g?t("div",{class:"gm__empty"},"Loading files…"):f?t("div",{class:"gm__error"},t("p",null,f),t("button",{class:"btn",type:"button",onClick:y},"Retry")):r===null?t("div",{class:"gm__empty"},"Loading files…"):r.length===0?t("div",{class:"gm__empty"},"No file changes in this commit"):r.map((_,b)=>t(et,{key:_.path+"-"+b,file:_}))))}async function $(s){try{return await navigator.clipboard.writeText(s||""),!0}catch{try{const n=document.createElement("textarea");n.value=s||"",n.style.position="fixed",n.style.opacity="0",document.body.appendChild(n),n.select();const o=document.execCommand("copy");return document.body.removeChild(n),o}catch{return!1}}}function bt({commit:s,busy:a,onAction:n}){const[o,c]=m(!1),h=ht(null);return pt(h,()=>c(!1),o),t("div",{ref:h,class:"gm__commit-menu"},t("button",{class:"gm__commit-menu-btn",type:"button","aria-haspopup":"true","aria-expanded":String(o),"aria-label":"Commit options for "+s.short,title:"Commit options",disabled:!!a,onClick:r=>{r.stopPropagation(),c(!o)}},"⋯"),t("div",{class:"gm__commit-menu-pop",hidden:!o,role:"menu",onClick:r=>r.stopPropagation()},t("button",{type:"button",onClick:()=>{c(!1),n(s,"copy-hash")}},"Copy hash"),t("button",{type:"button",onClick:()=>{c(!1),n(s,"copy-message")}},"Copy message"),t("div",{class:"gm__commit-menu-sep",role:"separator"}),t("button",{type:"button",disabled:!!a,onClick:()=>{c(!1),n(s,"checkout")}},"Checkout"),t("button",{type:"button",disabled:!!a,onClick:()=>{c(!1),n(s,"cherry-pick")}},"Cherry-pick"),t("button",{type:"button",disabled:!!a,onClick:()=>{c(!1),n(s,"revert")}},"Revert")))}function yt(s){if(!s)return"";const a=new Date(s);if(isNaN(a.getTime()))return s;const n={day:"numeric",month:"short",year:"numeric",hour:"2-digit",minute:"2-digit"};try{return a.toLocaleString(void 0,n)}catch{return s}}function Ct(s){const a=/stash@\{(\d+)\}/.exec(s||"");return a?a[1]:s}function St(s){const{projectDir:a,onClose:n}=s,[o,c]=m(null),[h,r]=m(""),[p,f]=m(!0),[l,g]=m(""),[w,d]=m(""),[T,y]=m(0),[C,_]=m([]),[b,G]=m(0),[M,W]=m(!1),[j,st]=m(!1),[A,at]=m(!1),[nt,Z]=m(!1),[E,V]=m(""),[P,L]=m(null),v=H(async()=>{if(!a){f(!1),r("No project selected");return}if(!nt){Z(!0),f(!0),r(""),d(""),_([]),y(0),G(0);try{const e=new URLSearchParams;e.set("projectDir",a);const i=await O("/api/git/info?"+e.toString());i.status!==200?r(i.body&&i.body.error||"HTTP "+i.status):i.body&&i.body.ok===!1?r(i.body&&i.body.error||"Not a git repository"):(c(i.body),_(i.body.commits||[]),y(i.body.commits&&i.body.commits.length||0))}catch(e){r(String(e))}f(!1),Z(!1)}},[a]);gt(()=>{v()},[v]);const F=H(()=>{L(e=>e?null:(n&&n(),e))},[n]),ot=K({onClose:F}),it=K({onClose:F,active:!!P});async function k(e,i,u){if(l)return;g(e),d("");const q=await Q(a,e,i,u);q.ok?(d("git "+e+" ok"),await v()):d((q.stderr||"git "+e+" failed").trim()||"git "+e+" failed"),g("")}async function U(e,i){if(l)return;g(e),d("");const u=await X(a,e,[Y(i.path)]);u.ok?(d(""),await v()):d((u.stderr||"git "+e+" failed").trim()||"git "+e+" failed"),g("")}async function ct(){if(l)return;g("add"),d("");const e=R.map(u=>Y(u.path));if(!e.length){g("");return}const i=await X(a,"add",e);i.ok?(d(""),await v()):d((i.stderr||"git add failed").trim()||"git add failed"),g("")}async function J(){if(l)return;const e=E.trim();if(!e){d("Enter a commit message");return}g("commit"),d("");const i=await Q(a,"commit","",e);i.ok?(V(""),d("Committed…"),await v()):d((i.stderr||"git commit failed").trim()||"git commit failed"),g("")}function lt(e){l||e===(o&&o.branch)||k("checkout",e)}async function rt(e,i){if(!l){if(i==="copy-hash"){const u=await $(e.hash);d(u?"Copied hash "+e.short:"Copy failed");return}if(i==="copy-message"){const u=await $(e.subject);d(u?"Copied message":"Copy failed");return}if(i==="checkout"){L({commit:e,action:"checkout"});return}if(i==="cherry-pick"){L({commit:e,action:"cherry-pick"});return}if(i==="revert"){L({commit:e,action:"revert"});return}}}function dt(){const e=P;L(null),e&&(e.action==="checkout"?k("checkout",e.commit.hash):e.action==="cherry-pick"?k("cherry-pick",e.commit.hash):e.action==="revert"&&k("revert",e.commit.hash))}async function mt(){if(!M){W(!0);try{const e=new URLSearchParams;e.set("projectDir",a),e.set("offset",String(T)),e.set("count","20");const i=await O("/api/git/commits?"+e.toString());i.status===200&&i.body&&i.body.ok&&(_(u=>u.concat(i.body.commits||[])),G(i.body.total||0),y(u=>u+(i.body.commits?i.body.commits.length:0)))}catch{}W(!1)}}const D=o&&o.staged||[],R=o&&o.unstaged||[],N=o&&o.stashes||[],B=o&&o.branch||"",I=o&&o.branches||[],S=o&&o.ahead||0,x=o&&o.behind||0,ut=b>0?C.length<b:C.length>=20;return t("div",{class:"gm__overlay",role:"dialog","aria-modal":"true","aria-label":"Git"},t("div",{class:"gm__sheet",ref:ot},t("div",{class:"gm__head"},t("label",{class:"gm__branch-wrap"},t("span",{class:"gm__branch-label"},"Branch"),t("select",{class:"gm__branch-select",value:B,disabled:l==="checkout",onChange:e=>lt(e.currentTarget.value),"aria-label":"Branch"},I.length===0?t("option",{value:B},B||"(none)"):I.map(e=>t("option",{key:e,value:e},e)))),t("button",{class:"icon-btn gm__iconbtn"+(x>0?" gm__iconbtn--has-count":""),type:"button",onClick:()=>k("pull"),disabled:!!l,"aria-label":"Pull from remote"+(x>0?" ("+x+" behind)":""),title:"Pull"+(x>0?" ("+x+" behind)":"")},x>0?t("span",{class:"gm__iconbtn-badge"},x):null,t("svg",{viewBox:"0 0 24 24",width:18,height:18,"aria-hidden":"true"},t("path",{d:"M11 3v9.6L8.4 10 7 11.4l5 5 5-5L15.6 10 13 12.6V3h-2Zm-7 15h16v2H4v-2Z",fill:"currentColor"}))),t("button",{class:"icon-btn gm__iconbtn"+(S>0?" gm__iconbtn--has-count":""),type:"button",onClick:()=>k("push"),disabled:!!l,"aria-label":"Push to remote"+(S>0?" ("+S+" ahead)":""),title:"Push"+(S>0?" ("+S+" ahead)":"")},S>0?t("span",{class:"gm__iconbtn-badge"},S):null,t("svg",{viewBox:"0 0 24 24",width:18,height:18,"aria-hidden":"true"},t("path",{d:"M12 3a1 1 0 0 1 .7.3l5 5-1.4 1.4L13 7.4V20h-2V7.4L7.7 9.7 6.3 8.3l5-5A1 1 0 0 1 12 3Z",fill:"currentColor"}))),t("button",{class:"icon-btn gm__iconbtn",type:"button",onClick:v,disabled:p||!!l,"aria-label":"Refresh git status",title:"Refresh"},t("svg",{viewBox:"0 0 24 24",width:16,height:16,"aria-hidden":"true"},t("path",{d:"M12 4V1L7 6l5 5V7c3.31 0 6 2.69 6 6 0 1-.25 1.97-.7 2.8l1.46 1.46A7.93 7.93 0 0 0 20 13c0-4.42-3.58-8-8-8Zm-5.3 7.7A7.93 7.93 0 0 0 4 13c0 4.42 3.58 8 8 8v3l5-5-5-5v3c-3.31 0-6-2.69-6-6 0-1 .25-1.97.7-2.8L5.24 10.24Z",fill:"currentColor"}))),t("button",{class:"icon-btn icon-btn--close gm__iconbtn",type:"button",onClick:n,"aria-label":"Close",title:"Close"},t("svg",{viewBox:"0 0 24 24",width:16,height:16,"aria-hidden":"true"},t("path",{d:"M18.3 5.71 12 12l6.3 6.29-1.41 1.42L10.59 13.4 4.3 19.71 2.88 18.3 9.17 12 2.88 5.71 4.3 4.3l6.29 6.29 6.3-6.29 1.41 1.41Z",fill:"currentColor"})))),w?t("div",{class:"gm__notice"},w):null,t("div",{class:"gm__body"},p?t("div",{class:"gm__empty"},"Loading git status…"):h?t("div",{class:"gm__error"},t("p",null,h),t("button",{class:"btn",type:"button",onClick:v},"Retry")):t(z,null,t("div",{class:"gm__section"},t("button",{class:"gm__section-head",type:"button",onClick:()=>at(!A),"aria-expanded":String(A),"aria-controls":"gm-section-stash"},t("span",{class:"gm__section-caret","aria-hidden":"true"},A?"▾":"▸"),t("span",{class:"gm__section-title"},"Stash"),t("span",{class:"gm__section-count"},N.length||"")),A&&t("div",{id:"gm-section-stash",class:"gm__section-body"},t("div",{class:"gm__stash-toolbar"},t("button",{class:"gm__stash-btn gm__stash-btn--stashup",type:"button",disabled:!!l,onClick:()=>k("stash"),"aria-label":"Stash working changes",title:"Stash up"},t("svg",{viewBox:"0 0 24 24",width:14,height:14,"aria-hidden":"true"},t("path",{d:"M12 4v9.6L9.4 11 8 12.4l5 5 5-5-1.4-1.4L14 13.6V4h-2ZM5 20h14v2H5v-2Z",fill:"currentColor"}))," Stash up")),N.length===0?t("div",{class:"gm__empty"},"No stashed changes"):N.map((e,i)=>t(vt,{key:i,stash:e,busy:l,onApply:u=>k("stash-apply",u),onPop:u=>k("stash-pop",u),onDrop:u=>k("stash-drop",u)})))),t(tt,{id:"staged",title:"Staged changes",files:D,defaultOpen:D.length>0,emptyText:"Nothing staged",action:"unstage",onAction:e=>U("unstage",e),busy:!!l,header:t("div",{class:"gm__commit-bar"},t("input",{class:"input gm__commit-input",type:"text",value:E,onInput:e=>V(e.currentTarget.value),placeholder:"Commit message","aria-label":"Commit message",disabled:!!l,onKeyDown:e=>{e.key==="Enter"&&(e.preventDefault(),J())}}),t("button",{class:"gm__commit-btn",type:"button",disabled:!!l||D.length===0,onClick:J,"aria-label":"Commit staged changes",title:"Commit staged changes"},l==="commit"?"Committing…":"Commit"))}),t(tt,{id:"unstaged",title:"Unstaged changes",files:R,defaultOpen:R.length>0,emptyText:"Working tree clean",action:"add",onAction:e=>U("add",e),busy:!!l}),D.length>0&&R.length>0?t("button",{class:"gm__stage-all",type:"button",disabled:!!l,onClick:ct,"aria-label":"Stage all unstaged changes",title:"Stage all unstaged changes"},"Stage all ("+R.length+")"):null,t("div",{class:"gm__section"},t("button",{class:"gm__section-head",type:"button",onClick:()=>st(!j),"aria-expanded":String(j),"aria-controls":"gm-section-commits"},t("span",{class:"gm__section-caret","aria-hidden":"true"},j?"▾":"▸"),t("span",{class:"gm__section-title"},"Recent commits"),t("span",{class:"gm__section-count"},b||C.length||"")),j&&t("div",{id:"gm-section-commits",class:"gm__section-body"},C.length===0?t("div",{class:"gm__empty"},"No commits yet"):t(z,null,C.map((e,i)=>t(ft,{key:e.hash||i,projectDir:a,commit:e,busy:!!l,onAction:rt})),ut?t("button",{class:"gm__load-more",type:"button",disabled:M,onClick:mt},M?"Loading…":"Load more"):null)))))),P?t(kt,{commit:P.commit,action:P.action,busy:!!l,sheetRef:it,onCancel:()=>L(null),onConfirm:dt}):null)}function kt({commit:s,action:a,busy:n,onCancel:o,onConfirm:c,sheetRef:h}){let r="Confirm git action",p="";a==="checkout"?(r="Checkout commit?",p="Check out "+s.short+" as a detached HEAD. Any current changes must be committed or stashed first."):a==="cherry-pick"?(r="Cherry-pick commit?",p="Apply "+s.short+(' "'+s.subject+'"')+" onto the current branch as a new commit."):a==="revert"&&(r="Revert commit?",p="Create a new commit that undoes "+s.short+(' "'+s.subject+'"')+".");const f=a==="checkout"?"Checkout":a==="cherry-pick"?"Cherry-pick":"Revert";return t("div",{class:"gm__overlay gm__confirm",role:"presentation",onClick:n?void 0:o},t("div",{class:"gm__sheet gm__confirm-sheet",role:"alertdialog","aria-modal":"true","aria-label":r,ref:h,onClick:l=>l.stopPropagation()},t("div",{class:"gm__confirm-body"},t("strong",{class:"gm__confirm-title"},r),t("p",{class:"gm__confirm-msg"},p),t("div",{class:"gm__confirm-actions"},t("button",{class:"btn gm__confirm-cancel",type:"button",disabled:n,onClick:o},"Cancel"),t("button",{class:"btn gm__confirm-go",type:"button","data-danger":"1",disabled:n,onClick:c},n?"Working…":f)))))}function tt({id:s,title:a,files:n,defaultOpen:o,emptyText:c,renderFile:h,action:r,onAction:p,busy:f,header:l}){const[g,w]=m(o),d=n?n.length:0,T=h||((y,C)=>t(et,{key:y.path+"-"+C,file:y,action:r,onAction:p,busy:f}));return t("div",{class:"gm__section"},t("button",{class:"gm__section-head"+(g?" is-open":""),type:"button",onClick:()=>w(!g),"aria-expanded":String(g),"aria-controls":"gm-section-"+s},t("span",{class:"gm__section-caret","aria-hidden":"true"},g?"▾":"▸"),t("span",{class:"gm__section-title"},a),t("span",{class:"gm__section-count"},d||"")),g&&t("div",{id:"gm-section-"+s,class:"gm__section-body"},l||null,d===0?t("div",{class:"gm__empty"},c||"Nothing here"):n.map((y,C)=>T(y,C))))}function vt({stash:s,busy:a,onApply:n,onPop:o,onDrop:c}){return t("div",{class:"gm__stash-row"},t("span",{class:"gm__stash-index"},Ct(s.index)),t("span",{class:"gm__stash-main"},t("span",{class:"gm__stash-subject",title:s.subject},s.subject),s.date?t("span",{class:"gm__stash-date"},s.date):null),t("div",{class:"gm__stash-actions"},t("button",{class:"gm__stash-btn",type:"button",disabled:!!a,onClick:()=>n(s.index),title:"Apply stash without removing it"},"Apply"),t("button",{class:"gm__stash-btn",type:"button",disabled:!!a,onClick:()=>o(s.index),title:"Apply stash and remove it"},"Pop"),t("button",{class:"gm__stash-btn gm__stash-btn--danger",type:"button",disabled:!!a,onClick:()=>c(s.index),title:"Delete stash"},"Drop")))}export{St as GitModal};