tabminal 1.3.7 → 1.3.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md ADDED
@@ -0,0 +1,312 @@
1
+ # Tabminal Agent Notes
2
+
3
+ Last updated: 2026-02-10
4
+
5
+ This file is for future AI/code agents working in this repo.
6
+ Goal: keep context accurate, avoid reintroducing old bugs, and preserve current UX
7
+ contracts after the multi-host refactor.
8
+
9
+ ## 1) Project Snapshot
10
+
11
+ - Runtime: Node.js >= 22, ESM project.
12
+ - Backend entry: `src/server.mjs`.
13
+ - Frontend entry: `public/app.js` (plus modules in `public/modules/`).
14
+ - PWA shell: `public/index.html` + `public/sw.js`.
15
+ - Multi-host registry persistence: `~/.tabminal/cluster.json` via backend API.
16
+
17
+ Core idea now:
18
+ - One web app can connect to multiple Tabminal backends.
19
+ - Main host controls page-level auth modal/state.
20
+ - Sub-hosts are independent connection/auth units and can reconnect separately.
21
+
22
+ ## 2) Non-Negotiable Behavior Contracts
23
+
24
+ ### 2.1 Host model and state isolation
25
+
26
+ - UI term is `Host` (not `Server`) for user-facing labels.
27
+ - Every session belongs to exactly one host.
28
+ - Session/editor/file-tree/expanded-path state is host-isolated.
29
+ - Do not merge runtime state across hosts.
30
+
31
+ Relevant code:
32
+ - `public/app.js` (`state.servers`, `state.sessions`, `makeSessionKey` usage)
33
+ - `public/modules/session-meta.js`
34
+
35
+ ### 2.2 Auth model
36
+
37
+ - Main host (`id = 'main'`) auth controls the app login modal.
38
+ - Only main host 401/403 should trigger global login modal.
39
+ - Sub-host 401/403 should mark that host as reconnect/login required, not global logout.
40
+ - Sub-host may require Cloudflare Access login without password change.
41
+
42
+ Relevant code:
43
+ - `public/app.js` `ServerClient.handleUnauthorized`
44
+ - `public/app.js` `ServerClient.handleAccessRedirect`
45
+
46
+ ### 2.3 Token storage contract
47
+
48
+ - Main host token:
49
+ - persisted in browser `localStorage` key `tabminal_auth_token:main`.
50
+ - Sub-host tokens:
51
+ - persisted in backend registry `~/.tabminal/cluster.json`.
52
+ - should not persist in browser localStorage.
53
+ - Removing a host should remove any stale local token key for that host id.
54
+
55
+ Relevant code:
56
+ - `public/app.js` `ServerClient.constructor`, `ServerClient.setToken`
57
+ - `src/persistence.mjs` `loadCluster` / `saveCluster`
58
+
59
+ ### 2.4 Host registry persistence contract
60
+
61
+ - Frontend does not own host list persistence anymore.
62
+ - Source of truth: backend `GET/PUT /api/cluster`.
63
+ - File format in `~/.tabminal/cluster.json`:
64
+ - `{ "servers": [{ id, baseUrl, host, token }, ...] }`
65
+ - On page load, host list is restored from backend after main host auth succeeds.
66
+
67
+ Relevant code:
68
+ - `public/app.js` `loadServerRegistryFromBackend`, `saveServerRegistryToBackend`
69
+ - `src/server.mjs` `/api/cluster`
70
+ - `src/persistence.mjs` cluster helpers
71
+
72
+ ### 2.5 Deduplication and self-host skip
73
+
74
+ - Host uniqueness key is normalized `hostname[:port]` in lowercase.
75
+ - Path is not part of dedupe key.
76
+ - Registry hydration skips entries that resolve to current main node
77
+ (same endpoint key or same hostname) to prevent self-loop duplicates.
78
+ - This is intentional to support sharing one cluster config across nodes.
79
+
80
+ Relevant code:
81
+ - `public/modules/url-auth.js` `getServerEndpointKeyFromUrl`
82
+ - `public/app.js` `findServerByEndpointKey`, `hydrateServerRegistry`
83
+
84
+ Important implication:
85
+ - Path-based multi-host on one domain (like `/a/*`, `/b/*`) is not supported by current
86
+ client routing assumptions.
87
+
88
+ ### 2.6 Session creation ownership
89
+
90
+ - Backend no longer auto-creates a default session/tab.
91
+ - Frontend ensures usability:
92
+ - if no sessions after init, create one on main host.
93
+ - if user closes last session, create one on main host.
94
+
95
+ Relevant code:
96
+ - `public/app.js` `initApp`, `closeSession`
97
+ - `src/server.mjs` (no auto-create fallback)
98
+
99
+ ### 2.7 Polling / heartbeat behavior
100
+
101
+ - Online heartbeat sync interval: `1000ms` (frontend).
102
+ - Reconnect retry cadence when host is down: `5000ms` throttle per host.
103
+ - Do not remove strong polling; it is a UX requirement.
104
+
105
+ Relevant code:
106
+ - `public/app.js`
107
+ - `HEARTBEAT_INTERVAL_MS = 1000`
108
+ - `RECONNECT_RETRY_MS = 5000`
109
+ - `syncServer`, `ServerClient.startHeartbeat`
110
+
111
+ ### 2.8 Cloudflare Access handling
112
+
113
+ Current behavior for sub-hosts:
114
+ - requests use `credentials: 'include'` so Access cookies can be sent.
115
+ - requests default to `redirect: 'manual'` on non-main hosts.
116
+ - Access redirect is treated as reconnect reason, not generic password failure.
117
+ - reconnect UI can trigger opening host root in a new tab for Access login.
118
+
119
+ Relevant code:
120
+ - `public/app.js`
121
+ - `ServerClient.fetch`
122
+ - `probeAccessLoginUrl`
123
+ - `openAccessLoginPage`
124
+ - `public/modules/url-auth.js`
125
+ - `isAccessRedirectResponse`
126
+ - `buildAccessLoginUrl` (root origin)
127
+
128
+ ### 2.9 CORS policy in backend
129
+
130
+ - Backend currently allows cross-origin by reflecting request origin when present.
131
+ - For requests without origin, backend returns `Access-Control-Allow-Origin: *`.
132
+ - OPTIONS is handled with 204 directly.
133
+ - No per-origin `cors-origin` config is used now.
134
+
135
+ Relevant code:
136
+ - `src/server.mjs` top-level CORS middleware
137
+
138
+ ### 2.10 Runtime version and PWA cache coherence
139
+
140
+ - Backend heartbeat returns runtime boot id.
141
+ - Frontend appends `?rt=<bootId>` to URL and reloads on server restart/version change.
142
+ - `index.html` loads `styles.css` and `app.js` using runtime key.
143
+ - SW is registered with `?rt=<bootId>`, cache key includes that runtime id.
144
+ - App shell (`/`, `/index.html`, `/app.js`, `/styles.css`, `/modules/*`) uses
145
+ network-first.
146
+
147
+ Relevant code:
148
+ - `src/server.mjs` `/api/heartbeat` -> `runtime.bootId`
149
+ - `public/app.js` `handlePrimaryRuntimeVersion`
150
+ - `public/index.html` runtime loader script
151
+ - `public/sw.js`
152
+
153
+ ## 3) UX Contracts to Keep
154
+
155
+ ### 3.1 Sidebar host controls
156
+
157
+ - Per-host row: primary action button + (non-main only) remove button.
158
+ - Remove button is overlay style (top-left), hidden by default, fades in on hover/focus.
159
+ - Main host has no remove button.
160
+ - Main button text:
161
+ - normal: `New Tab @ <Host>`
162
+ - reconnect: `Reconnect <Host>`
163
+ - access flow: `Cloudflare Login <Host>`
164
+ - Second line includes latency text + heartbeat dot + mini heartbeat canvas.
165
+
166
+ Relevant code:
167
+ - `public/app.js` `renderServerControls`
168
+ - `public/styles.css` `.server-row`, `.server-main-button`, `.server-delete-button`
169
+
170
+ ### 3.2 Host naming display
171
+
172
+ Display priority for host name:
173
+ 1. configured `host` alias
174
+ 2. runtime hostname from host heartbeat
175
+ 3. URL hostname
176
+ 4. `'unknown'`
177
+
178
+ Session metadata line should stay:
179
+ - `HOST: user@<host>`
180
+ - host text uses `.host-emphasis` style.
181
+
182
+ Relevant code:
183
+ - `public/modules/session-meta.js`
184
+
185
+ ### 3.3 Path display
186
+
187
+ - PWD display in tab meta uses fish-style compact path shortening.
188
+ - Keep this style unless a full UX redesign is explicitly requested.
189
+
190
+ Relevant code:
191
+ - `public/modules/session-meta.js` `shortenPathFishStyle`
192
+
193
+ ### 3.4 Small-screen rule (< 600px height)
194
+
195
+ - `new-tab-item` region is capped to two-button area height and scrollable.
196
+ - This is intentional for small-height mobile/embedded layouts.
197
+
198
+ Relevant code:
199
+ - `public/styles.css` `@media (max-height: 600px)` on `.new-tab-item`
200
+
201
+ ## 4) Known Pitfalls and Their Root Causes
202
+
203
+ ### 4.1 `SecurityError: insecure WebSocket from HTTPS page`
204
+
205
+ Cause:
206
+ - trying `ws://` from HTTPS page.
207
+ Current mitigation:
208
+ - WS URL builder chooses `wss://` if page is HTTPS or host URL is HTTPS.
209
+
210
+ Check:
211
+ - `public/app.js` `ServerClient.resolveWsUrl`
212
+ - ensure host URL is HTTPS when used from secure origin.
213
+
214
+ ### 4.2 `TypeError: Failed to fetch` during heartbeat
215
+
216
+ Usually means:
217
+ - host down, DNS issue, TLS failure, CORS block, or network unreachable.
218
+ Expected behavior:
219
+ - warning-level reconnect messaging, not noisy crash behavior.
220
+
221
+ Check:
222
+ - browser network tab
223
+ - host availability
224
+ - Access auth state for that host
225
+
226
+ ### 4.3 Cloudflare Access 302/login loops
227
+
228
+ Important:
229
+ - CORS headers alone do not fix Access redirect login requirements.
230
+ - Main issue is Access auth challenge during API request.
231
+ - Current design handles this by detecting redirect and opening host root login page.
232
+
233
+ ### 4.4 Host list not restoring after refresh
234
+
235
+ Check in order:
236
+ 1. main host authenticated?
237
+ 2. `/api/cluster` returns expected `servers` array?
238
+ 3. entries include valid `id` and `baseUrl`?
239
+ 4. entry skipped as self-host by dedupe/hostname rule?
240
+
241
+ ### 4.5 Same domain with different path does not behave as separate hosts
242
+
243
+ Current architecture dedupes by `hostname[:port]` and uses absolute `/api/*`/`/ws/*`.
244
+ Do not assume path-prefix multiplexing works without deeper routing redesign.
245
+
246
+ ## 5) File Map for Fast Onboarding
247
+
248
+ Backend:
249
+ - `src/server.mjs`: API routes, WS upgrade, CORS, runtime boot id, startup/shutdown.
250
+ - `src/config.mjs`: merged config parser (defaults/home/local/CLI/env), validation.
251
+ - `src/auth.mjs`: hash auth, lockout logic, API and WS auth checks.
252
+ - `src/persistence.mjs`: sessions, memory, cluster registry disk persistence.
253
+ - `src/terminal-manager.mjs`: PTY session lifecycle + persistence glue.
254
+ - `src/terminal-session.mjs`: terminal stream parsing, history, metadata, AI context.
255
+
256
+ Frontend:
257
+ - `public/app.js`: host/session state, sync loop, UI orchestration.
258
+ - `public/modules/url-auth.js`: URL normalization, dedupe key, auth helpers.
259
+ - `public/modules/session-meta.js`: host display and path shortening.
260
+ - `public/styles.css`: sidebar/tab/host control styling and responsive rules.
261
+ - `public/index.html`: shell DOM, runtime versioned loader, SW register.
262
+ - `public/sw.js`: runtime-versioned caching strategy.
263
+
264
+ ## 6) Logs and Debug Guidance
265
+
266
+ Expected, low-noise warnings:
267
+ - host unreachable / reconnect transitions.
268
+ - invalid cluster entries skipped.
269
+
270
+ Avoid:
271
+ - spamming full stack traces for normal offline scenarios.
272
+ - debug-only console noise left enabled by default.
273
+
274
+ Note:
275
+ - old `cluster-debug` helper has been intentionally removed; do not reintroduce
276
+ unless there is a concrete observability requirement.
277
+
278
+ ## 7) Security and Risk Notes
279
+
280
+ - Product is high-privilege by design (terminal + file write).
281
+ - AI features may send terminal context to model providers.
282
+ - Current policy is explicit risk acknowledgment (`--accept-terms` / config flag).
283
+ - Choose trusted model providers and least-privilege credentials.
284
+
285
+ ## 8) Deployment and Ops Notes
286
+
287
+ - Local helper script: `reploy.sh` (intentionally ignored by git and npm package).
288
+ - It restarts one macOS launchctl node + several Linux pm2 nodes via SSH.
289
+ - Script contains aggressive cleanup on Linux nodes (reset/clean fallback);
290
+ use carefully in shared environments.
291
+
292
+ ## 9) Quality Gates Before Release
293
+
294
+ Recommended checks:
295
+ 1. `npm run lint`
296
+ 2. `npm test`
297
+ 3. `npm run build`
298
+ 4. quick manual smoke:
299
+ - main host login
300
+ - add host (with and without password, inheritance path)
301
+ - reconnect flow (normal + Access login path)
302
+ - delete host
303
+ - restart backend and confirm runtime cache refresh (`rt` flow)
304
+
305
+ ## 10) Change Safety Rules for Future Refactors
306
+
307
+ - Do not move host list back to localStorage.
308
+ - Do not make sub-host auth failures trigger global logout.
309
+ - Do not remove `credentials: 'include'` from host fetch wrapper.
310
+ - Do not remove reconnect backoff (`5s`) or online heartbeat cadence (`1s`).
311
+ - Do not reintroduce backend auto-create-session fallback.
312
+ - Do not add path-based host assumptions without redesigning URL/WS routing model.
package/README.md CHANGED
@@ -6,9 +6,9 @@
6
6
 
7
7
  ![Tabminal Banner](public/favicon.svg)
8
8
 
9
- ## 🌟 Why Tabminal?
9
+ ## 🌟 Why `t> Tabminal`?
10
10
 
11
- Tabminal bridges the gap between traditional CLI tools and modern AI capabilities, all while solving the UX challenges of coding on desktop and mobile devices.
11
+ `t> Tabminal` bridges the gap between traditional CLI tools and modern AI capabilities, all while solving the UX challenges of coding on desktop and mobile devices.
12
12
 
13
13
  ![IMG_0918](https://github.com/user-attachments/assets/a0cb7d8d-924c-4ba0-852e-bd0b1f2928ae)
14
14
 
@@ -22,7 +22,7 @@ As a long-time terminal user who frequently needs to step away from my computer
22
22
  - `Proactive AI integration` means your terminal becomes your work context. You can ask questions about your current session at any time, and the AI will automatically retrieve the context to accurately solve problems, even proactively offering assistance when commands fail.
23
23
  - `Cloud-native design` enables access via Zero Trust or VPN, providing unprecedented convenience for managing cloud servers.
24
24
 
25
- <img width="1632" height="1317" alt="Screenshot 2025-11-24 at 10 15 28PM" src="https://github.com/user-attachments/assets/ad864233-7b22-4b29-8b90-dc81993dd623" />
25
+ <img width="1689" height="1386" alt="Screenshot 2026-02-10 at 1 10 01AM" src="https://github.com/user-attachments/assets/608e21c3-bae2-4f16-85b7-4bd6520fd4f5" />
26
26
 
27
27
  <details>
28
28
 
@@ -39,11 +39,12 @@ As a long-time terminal user who frequently needs to step away from my computer
39
39
  </details>
40
40
 
41
41
  ### 🧠 AI-Native Intelligence
42
- Powered by **modern AI models** (via OpenRouter or OpenAI), Tabminal understands your context.
42
+ Powered by **modern AI models** (via OpenRouter or OpenAI), `t> Tabminal` understands your context.
43
43
  *(Defaults to **Gemini 3 Flash** for OpenRouter or **GPT-5.2** for OpenAI if not configured)*
44
44
  * **Context-Aware Chat**: Type `# how do I...` to ask questions. The AI knows your **CWD**, **Environment**, and **Recent History**.
45
- * **Auto-Fix**: Command failed? Tabminal automatically analyzes the exit code and error output to suggest fixes. No copy-pasting required.
45
+ * **Auto-Fix**: Command failed? `t> Tabminal` automatically analyzes the exit code and error output to suggest fixes. No copy-pasting required.
46
46
  * **Web Search**: Enable Google Search integration to let the AI fetch real-time answers from the web.
47
+ * **Provider Risk Notice**: AI features may send terminal context to your selected model provider. You are responsible for choosing trusted providers/models and acceptable data boundaries.
47
48
 
48
49
  ### 📱 Ultimate Mobile Experience
49
50
  Built from the ground up for **iPadOS**, **iOS** and **Android**.
@@ -56,6 +57,15 @@ Built from the ground up for **iPadOS**, **iOS** and **Android**.
56
57
  * **Built-in Editor**: Integrated **Monaco Editor** (VS Code core) allows you to edit files directly on the server.
57
58
  * **Visual File Manager**: Sidebar file tree for easy navigation.
58
59
  * **Network Heartbeat**: Real-time latency visualization.
60
+ * **Cluster Host Registry**: Multi-host entries are stored on the main server at `~/.tabminal/cluster.json` (including per-host auth hash). Entries that resolve to the current node are ignored at runtime to avoid self-loop duplicates. Non-main host tokens are restored from this server-side registry; main-host auth stays in browser local storage.
61
+
62
+ ### 🌐 Multi-Server Support
63
+ `t> Tabminal` can manage multiple backend nodes from one UI.
64
+ * Register hosts from the sidebar using `+ Add Host`.
65
+ * Open sessions on a specific host with `New Tab @ Host`.
66
+ * Each host maintains its own heartbeat, session list, and file/editor state.
67
+ * Authentication is host-scoped: the main host controls page login, while sub-host login state is shown per-host in the sidebar.
68
+ * Host registry is saved on the main host at `~/.tabminal/cluster.json`, so added hosts can be restored after refresh and across browsers/devices.
59
69
 
60
70
  ## 🚀 Getting Started
61
71
 
@@ -65,8 +75,9 @@ Built from the ground up for **iPadOS**, **iOS** and **Android**.
65
75
  * (Optional) A pair of Google API Key and Search Engine ID (CX) for web search capabilities.
66
76
 
67
77
  ### ⚠️ Security Warning
68
- Tabminal provides **full read/write access** to the underlying file system.
78
+ `t> Tabminal` provides **full read/write access** to the underlying file system.
69
79
  * **Do NOT expose this to the public internet** without proper protection (VPN, etc).
80
+ * If AI features are enabled, terminal history/environment/context may be sent to your configured model provider. You are responsible for this risk and provider selection.
70
81
  * The `--accept-terms` flag is required to acknowledge that you understand these risks.
71
82
 
72
83
 
@@ -103,12 +114,12 @@ npm start -- --openrouter-key "YOUR_API_KEY" --accept-terms
103
114
 
104
115
  ### Configuration
105
116
 
106
- You can configure Tabminal via command-line arguments, environment variables, or a `config.json` file.
117
+ You can configure `t> Tabminal` via command-line arguments, environment variables, or a `config.json` file.
107
118
 
108
119
  | Argument | Env Variable | Description | Default |
109
120
  | :--- | :--- | :--- | :--- |
110
- | `-p`, `--port` | `PORT` | Server port | `9846` |
111
- | `-h`, `--host` | `HOST` | Bind address | `127.0.0.1` |
121
+ | `-p`, `--port` | `TABMINAL_PORT` | Server port | `9846` |
122
+ | `-h`, `--host` | `TABMINAL_HOST` | Bind address | `127.0.0.1` |
112
123
  | `-a`, `--password` | `TABMINAL_PASSWORD` | Access password | (Randomly Generated) |
113
124
  | `-k`, `--openrouter-key` | `TABMINAL_OPENROUTER_KEY` | OpenRouter API Key (Mutually exclusive with OpenAI) | `null` |
114
125
  | `-o`, `--openai-key` | `TABMINAL_OPENAI_KEY` | OpenAI API Key (Mutually exclusive with OpenRouter) | `null` |
@@ -118,7 +129,13 @@ You can configure Tabminal via command-line arguments, environment variables, or
118
129
  | `-g`, `--google-key` | `TABMINAL_GOOGLE_KEY` | Google Search API Key | `null` |
119
130
  | `-c`, `--google-cx` | `TABMINAL_GOOGLE_CX` | Google Search Engine ID (CX) | `null` |
120
131
  | `-d`, `--debug` | `TABMINAL_DEBUG` | Enable debug logs | `false` |
121
- | `-y`, `--accept-terms` | `TABMINAL_ACCEPT` | **Required**: Accept security risks (Full FS Access) | `false` |
132
+ | `--heartbeat` | `TABMINAL_HEARTBEAT` | WebSocket heartbeat interval (ms, minimum `1000`) | `10000` |
133
+ | `--history` | `TABMINAL_HISTORY` | Terminal history limit (characters) | `1048576` |
134
+ | `-y`, `--accept-terms` | `TABMINAL_ACCEPT` / `TABMINAL_ACCEPT_TERMS` | **Required**: Accept security risks (Full FS Access) | `false` |
135
+
136
+ `config.json` also supports:
137
+ - `heartbeatInterval` or `heartbeat-interval`
138
+ - `historyLimit` or `history-limit`
122
139
 
123
140
  ## ⌨️ Shortcuts & Gestures
124
141
 
@@ -139,5 +156,9 @@ You can configure Tabminal via command-line arguments, environment variables, or
139
156
  * **Frontend**: [Vanilla JS](http://vanilla-js.com/) 😝, [xterm.js](https://github.com/xtermjs/xterm.js), [Monaco Editor](https://github.com/microsoft/monaco-editor).
140
157
  * **AI**: Integration via [utilitas](https://github.com/leask/utilitas).
141
158
 
159
+ ## 🐛 Troubleshooting
160
+
161
+ - On macOS, you may need to run `chmod +x node_modules/node-pty/prebuilds/darwin-*/spawn-helper` to fix permission issues.
162
+
142
163
  ## 📄 License
143
164
  [MIT](LICENSE)
@@ -4,8 +4,10 @@
4
4
  "password": "YOUR_SECURE_PASSWORD",
5
5
  "openrouter-key": "YOUR_OPENROUTER_API_KEY",
6
6
  "model": "gemini-2.5-flash-preview-09-2025",
7
+ "heartbeat-interval": 10000,
8
+ "history-limit": 1048576,
7
9
  "google-key": "YOUR_GOOGLE_SEARCH_API_KEY",
8
10
  "google-cx": "YOUR_GOOGLE_SEARCH_ENGINE_ID",
9
11
  "accept-terms": true,
10
12
  "debug": false
11
- }
13
+ }
@@ -0,0 +1,31 @@
1
+ export default [
2
+ {
3
+ ignores: [
4
+ 'node_modules/**',
5
+ '.history/**',
6
+ 'coverage/**',
7
+ 'public/icons/**'
8
+ ]
9
+ },
10
+ {
11
+ files: ['**/*.{js,mjs,cjs}'],
12
+ languageOptions: {
13
+ ecmaVersion: 'latest',
14
+ sourceType: 'module'
15
+ },
16
+ rules: {
17
+ 'no-unused-vars': ['error', {
18
+ vars: 'all',
19
+ args: 'after-used',
20
+ argsIgnorePattern: '^_',
21
+ varsIgnorePattern: '^_',
22
+ caughtErrors: 'all',
23
+ caughtErrorsIgnorePattern: '^_'
24
+ }],
25
+ 'no-empty': ['error', {
26
+ allowEmptyCatch: true
27
+ }],
28
+ 'no-redeclare': 'error'
29
+ }
30
+ }
31
+ ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tabminal",
3
- "version": "1.3.7",
3
+ "version": "1.3.9",
4
4
  "description": "A modern, persistent web terminal with multi-tab support and real-time system monitoring.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -52,5 +52,8 @@
52
52
  "bugs": {
53
53
  "url": "https://github.com/leask/tabminal/issues"
54
54
  },
55
- "homepage": "https://github.com/leask/tabminal#readme"
55
+ "homepage": "https://github.com/leask/tabminal#readme",
56
+ "devDependencies": {
57
+ "eslint": "^9.39.2"
58
+ }
56
59
  }