@qodalis/mcp-cli 0.0.0-stage → 0.2.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 (57) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/LICENSE +21 -0
  3. package/README.md +384 -2
  4. package/dist/src/cli.d.ts +3 -0
  5. package/dist/src/cli.d.ts.map +1 -0
  6. package/dist/src/cli.js +102 -0
  7. package/dist/src/cli.js.map +1 -0
  8. package/dist/src/config.d.ts +60 -0
  9. package/dist/src/config.d.ts.map +1 -0
  10. package/dist/src/config.js +149 -0
  11. package/dist/src/config.js.map +1 -0
  12. package/dist/src/dev/api.d.ts +94 -0
  13. package/dist/src/dev/api.d.ts.map +1 -0
  14. package/dist/src/dev/api.js +425 -0
  15. package/dist/src/dev/api.js.map +1 -0
  16. package/dist/src/dev/docs.d.ts +93 -0
  17. package/dist/src/dev/docs.d.ts.map +1 -0
  18. package/dist/src/dev/docs.js +275 -0
  19. package/dist/src/dev/docs.js.map +1 -0
  20. package/dist/src/dev/prompts.d.ts +11 -0
  21. package/dist/src/dev/prompts.d.ts.map +1 -0
  22. package/dist/src/dev/prompts.js +141 -0
  23. package/dist/src/dev/prompts.js.map +1 -0
  24. package/dist/src/dev/scaffold.d.ts +55 -0
  25. package/dist/src/dev/scaffold.d.ts.map +1 -0
  26. package/dist/src/dev/scaffold.js +213 -0
  27. package/dist/src/dev/scaffold.js.map +1 -0
  28. package/dist/src/dev/tools.d.ts +170 -0
  29. package/dist/src/dev/tools.d.ts.map +1 -0
  30. package/dist/src/dev/tools.js +258 -0
  31. package/dist/src/dev/tools.js.map +1 -0
  32. package/dist/src/env-file.d.ts +9 -0
  33. package/dist/src/env-file.d.ts.map +1 -0
  34. package/dist/src/env-file.js +54 -0
  35. package/dist/src/env-file.js.map +1 -0
  36. package/dist/src/errors.d.ts +22 -0
  37. package/dist/src/errors.d.ts.map +1 -0
  38. package/dist/src/errors.js +115 -0
  39. package/dist/src/errors.js.map +1 -0
  40. package/dist/src/filters.d.ts +13 -0
  41. package/dist/src/filters.d.ts.map +1 -0
  42. package/dist/src/filters.js +16 -0
  43. package/dist/src/filters.js.map +1 -0
  44. package/dist/src/index.d.ts +12 -0
  45. package/dist/src/index.d.ts.map +1 -0
  46. package/dist/src/index.js +12 -0
  47. package/dist/src/index.js.map +1 -0
  48. package/dist/src/remote.d.ts +64 -0
  49. package/dist/src/remote.d.ts.map +1 -0
  50. package/dist/src/remote.js +242 -0
  51. package/dist/src/remote.js.map +1 -0
  52. package/dist/src/server.d.ts +22 -0
  53. package/dist/src/server.d.ts.map +1 -0
  54. package/dist/src/server.js +156 -0
  55. package/dist/src/server.js.map +1 -0
  56. package/docs/llms-full.txt +1740 -0
  57. package/package.json +70 -5
package/CHANGELOG.md ADDED
@@ -0,0 +1,50 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project uses
5
+ [Semantic Versioning](https://semver.org/).
6
+
7
+ ## [0.2.0] - 2026-10-27
8
+
9
+ ### Added
10
+
11
+ - Plugin-development tools, served locally: `qcli_docs_search` and
12
+ `qcli_docs_read` over the plugin guide (`llms-full.txt` from
13
+ `QCLI_DOCS_URL`, default https://cli.qodalis.com/llms-full.txt, cached for an
14
+ hour, with a snapshot bundled in the package for offline use), `qcli_api`
15
+ (TypeScript declarations of the `@qodalis/*` packages from the project's
16
+ node_modules or the latest npm version) and `qcli_scaffold_plugin`
17
+ (`@qodalis/create-cli-plugin`).
18
+ - MCP prompts `create-web-plugin` and `create-server-command`, and the docs
19
+ topics as resources (`qcli-docs://<section>/<topic>`).
20
+ - `QCLI_DEV_TOOLS` (default `true`) and `QCLI_DOCS_URL`; `npm run sync-docs`
21
+ refreshes the bundled docs snapshot.
22
+
23
+ ### Changed
24
+
25
+ - `QCLI_URL` is optional: without it the server runs in developer mode with only
26
+ the plugin-development tools. With it, the dev tools are listed before the
27
+ server's tools, and still listed when the server is unreachable.
28
+ - `QCLI_READ_ONLY`, `QCLI_TOOLS` and `QCLI_EXCLUDE` apply to the dev tools too
29
+ (read-only mode hides `qcli_scaffold_plugin`).
30
+ - `createProxyServer(remote, config, { dev })` accepts the dev tools and an
31
+ `undefined` remote.
32
+
33
+ ## [0.1.0] - 2026-10-15
34
+
35
+ ### Added
36
+
37
+ - First release: a stdio MCP server that proxies Claude Code (or any MCP client)
38
+ to a Qodalis CLI server's MCP endpoint (`<QCLI_URL>/api/qcli/mcp`). Tools are
39
+ taken from the server's `tools/list` and passed through unchanged; calls are
40
+ forwarded.
41
+ - Authentication with an API token or the server's static MCP token
42
+ (`QCLI_TOKEN`), or with `QCLI_USER` / `QCLI_PASSWORD` (signs in again after a
43
+ 401).
44
+ - `QCLI_READ_ONLY`, `QCLI_TOOLS` and `QCLI_EXCLUDE` client-side tool filters.
45
+ - Resilience: connect on first use, failed connects are not cached, one retry on
46
+ connection loss, fast failure (`QCLI_CONNECTION_TIMEOUT`) with actionable
47
+ messages for unreachable servers, rejected credentials, missing permissions and
48
+ MCP not enabled; 60 s tool-list cache refreshed for unknown tools.
49
+ - `QCLI_REQUEST_TIMEOUT`, `QCLI_TRUST_CERT`, `--env-file` / `QCLI_ENV_FILE`,
50
+ `QODALIS_CLI_*` aliases, `--help`, `--version`.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Qodalis Solutions SRL
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 CHANGED
@@ -1,3 +1,385 @@
1
- # Temporary Holding Version
1
+ # @qodalis/mcp-cli
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ A [Model Context Protocol](https://modelcontextprotocol.io) server that helps
4
+ [Claude Code](https://code.claude.com) **build [Qodalis CLI](https://cli.qodalis.com) plugins** (docs search, exact API
5
+ declarations, scaffolding) and gives it the commands, files, data explorer, jobs and plugin tools of a running
6
+ Qodalis CLI server through a resilient proxy.
7
+
8
+ [![npm](https://img.shields.io/npm/v/@qodalis/mcp-cli)](https://www.npmjs.com/package/@qodalis/mcp-cli)
9
+ [![Build & Test](https://github.com/qodalis-solutions/mcp-cli/actions/workflows/build.yml/badge.svg)](https://github.com/qodalis-solutions/mcp-cli/actions/workflows/build.yml)
10
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
11
+
12
+ `mcp-cli` runs on your machine as a stdio MCP server in one of two modes:
13
+
14
+ - **Developer mode** (no `QCLI_URL`): the [plugin-development tools](#plugin-development) only. No server needed.
15
+ - **Server mode** (`QCLI_URL` set): the same tools plus a proxy to the server's MCP endpoint
16
+ (`<server>/api/qcli/mcp`): Claude Code sees exactly the server's tools, with the permissions of the account you
17
+ configure. It handles sign-in, reconnects after network drops and lets you narrow what the assistant may use.
18
+
19
+ ## Where it fits
20
+
21
+ | Repository | Role |
22
+ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
23
+ | [web-cli](https://github.com/qodalis-solutions/web-cli) | The browser terminal (Angular, React, Vue) and its plugins, including `token create` for API tokens |
24
+ | [cli-server-node](https://github.com/qodalis-solutions/cli-server-node), [cli-server-dotnet](https://github.com/qodalis-solutions/cli-server-dotnet), [cli-server-python](https://github.com/qodalis-solutions/cli-server-python) | The servers that host the MCP endpoint |
25
+ | [cli-server-dashboard](https://github.com/qodalis-solutions/cli-server-dashboard) | Admin dashboard served by the servers |
26
+ | [pwa-cli](https://github.com/qodalis-solutions/pwa-cli) | The CLI as a PWA and Android app |
27
+ | **mcp-cli** | This repository: the bridge from Claude Code to a server |
28
+
29
+ ## Highlights
30
+
31
+ - **Plugin development built in.** Search the plugin guide, read the exact
32
+ TypeScript declarations of `@qodalis/cli-core` and scaffold a plugin, with
33
+ prompts that walk the assistant through a web plugin or a server command.
34
+ - **No tool-specific code.** Tools come from the server's `tools/list` and are
35
+ passed through unchanged (names, schemas, annotations); calls are forwarded.
36
+ New commands on the server show up without updating the proxy.
37
+ - **Token or password.** Use an API token (`QCLI_TOKEN`), or sign in with
38
+ `QCLI_USER` / `QCLI_PASSWORD`; an expired session is renewed automatically on
39
+ the next call.
40
+ - **Read-only mode** (`QCLI_READ_ONLY=true`) exposes only the tools the server
41
+ marks read-only, and refuses calls to anything else.
42
+ - **Client-side allow and deny lists** (`QCLI_TOOLS`, `QCLI_EXCLUDE`, trailing
43
+ `*` allowed) on top of the server's own permissions.
44
+ - **Self-healing connection.** It connects on first use, never caches a failed
45
+ connect, retries once when the connection drops, and fails fast (8 s) when the
46
+ server is unreachable, with a message that says what to check.
47
+ - **Clear errors**: a rejected token, MCP not enabled on the server, a missing
48
+ permission or an unreachable host each come back as one actionable sentence.
49
+
50
+ ## Quick start
51
+
52
+ For plugin development, nothing else is needed:
53
+
54
+ ```bash
55
+ claude mcp add qcli -- npx -y @qodalis/mcp-cli
56
+ ```
57
+
58
+ Then ask, for example, _"Create a Qodalis CLI plugin that converts temperatures"_ or run the
59
+ `/mcp__qcli__create-web-plugin` prompt. See [Plugin development](#plugin-development).
60
+
61
+ To also reach a running server, add `QCLI_URL` and credentials as described below.
62
+
63
+ ```bash
64
+ npx -y @qodalis/mcp-cli --help
65
+ ```
66
+
67
+ The server must have MCP enabled (default ports: .NET `8046`, Node `8047`, Python `8048`):
68
+
69
+ | Server | Enable MCP |
70
+ | ------ | ---------------------------------------------- |
71
+ | Node | `createCliServer({ mcp: true })` |
72
+ | Python | `CliServerOptions(mcp=True)` |
73
+ | .NET | `builder.Services.AddCli(cli => cli.AddMcp())` |
74
+
75
+ Each also takes options (tool allow/deny lists, a static token, instructions); see
76
+ [MCP endpoint](https://cli.qodalis.com/docs/server-integration#mcp-endpoint).
77
+
78
+ ### Claude Code
79
+
80
+ ```bash
81
+ claude mcp add qcli -- npx -y @qodalis/mcp-cli
82
+ ```
83
+
84
+ then set the environment in `~/.claude.json`:
85
+
86
+ ```json
87
+ {
88
+ "mcpServers": {
89
+ "qcli": {
90
+ "command": "npx",
91
+ "args": ["-y", "@qodalis/mcp-cli"],
92
+ "env": {
93
+ "QCLI_URL": "http://localhost:8047",
94
+ "QCLI_TOKEN": "qcli_pat_..."
95
+ }
96
+ }
97
+ }
98
+ }
99
+ ```
100
+
101
+ or sign in with a user name and password instead of a token:
102
+
103
+ ```json
104
+ "env": {
105
+ "QCLI_URL": "http://localhost:8047",
106
+ "QCLI_USER": "ops",
107
+ "QCLI_PASSWORD": "********"
108
+ }
109
+ ```
110
+
111
+ The same in one command, with `-e` flags:
112
+
113
+ ```bash
114
+ claude mcp add qcli -e QCLI_URL=http://localhost:8047 -e QCLI_TOKEN=qcli_pat_... -- npx -y @qodalis/mcp-cli
115
+ ```
116
+
117
+ Add `--scope user` to make it available in every project. With
118
+ `--scope project` the entry goes into the project's `.mcp.json`, which is
119
+ usually committed: keep the secret out of it with
120
+ `"QCLI_TOKEN": "${QCLI_TOKEN}"` and set the variable in your shell. A `.env`
121
+ file works too:
122
+
123
+ ```json
124
+ { "command": "npx", "args": ["-y", "@qodalis/mcp-cli", "--env-file", "/path/to/.env"] }
125
+ ```
126
+
127
+ Pin a version in shared configs (`@qodalis/mcp-cli@0.2.0`) so an upgrade is a
128
+ deliberate change.
129
+
130
+ ### Getting a token
131
+
132
+ In the web CLI connected to the server (users plugin), signed in as the account
133
+ the assistant should act as:
134
+
135
+ ```bash
136
+ token create claude --expires=90
137
+ # token create claude --permissions=commands:execute,files:read (narrower scope)
138
+ ```
139
+
140
+ The token (`qcli_pat_…`) is shown once. Without the web CLI, sign in and create
141
+ one with curl:
142
+
143
+ ```bash
144
+ SESSION=$(curl -s http://localhost:8047/api/v1/qcli/sessions \
145
+ -H 'Content-Type: application/json' -d '{"username":"ops","password":"..."}' | jq -r .token)
146
+ curl -s http://localhost:8047/api/v1/qcli/tokens -H "Authorization: Bearer $SESSION" \
147
+ -H 'Content-Type: application/json' -d '{"name":"claude","expiresInDays":90}' | jq -r .token
148
+ curl -s -X DELETE http://localhost:8047/api/v1/qcli/sessions/current -H "Authorization: Bearer $SESSION"
149
+ ```
150
+
151
+ A server **without** the users plugin protects its endpoint with a static token
152
+ (its `mcp.token` option): put that value in `QCLI_TOKEN`. If it sets none, the
153
+ endpoint is open and no auth variable is needed.
154
+
155
+ ## Plugin development
156
+
157
+ These tools are served locally and work without a server (developer mode) or next to
158
+ a server's tools. Turn them off with `QCLI_DEV_TOOLS=false`.
159
+
160
+ ```bash
161
+ claude mcp add qcli -- npx -y @qodalis/mcp-cli # developer mode
162
+ claude mcp add qcli --scope user -- npx -y @qodalis/mcp-cli # in every project
163
+ ```
164
+
165
+ ### Tools
166
+
167
+ | Tool | Arguments | What it does |
168
+ | ---------------------- | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
169
+ | `qcli_docs_search` | `query`, `limit?` (default 5, max 20) | Ranked topics of the plugin guide as `Section › Topic`, with their summary and an excerpt. |
170
+ | `qcli_docs_read` | `topic` | A topic in full (heading match, case-insensitive; `Section › Topic` when a heading is in several sections), or a whole section (`Web plugins`). Lists the topics when nothing matches. |
171
+ | `qcli_api` | `symbol`, `package?` (default `@qodalis/cli-core`) | The TypeScript declaration of an interface, type, class, function, enum or constant, with its JSDoc and the related declarations to look up next. `package` takes any `@qodalis/*` package (`cli` is short for `@qodalis/cli`). |
172
+ | `qcli_scaffold_plugin` | `name`, `description?`, `processorName?`, `directory?` | Runs `npx -y @qodalis/create-cli-plugin@latest` and returns the created files and next steps. Inside a web-cli checkout it creates `packages/plugins/<name>`; elsewhere a standalone `qodalis-cli-<name>` project. |
173
+
174
+ Where the data comes from:
175
+
176
+ - **Docs**: [`https://cli.qodalis.com/llms-full.txt`](https://cli.qodalis.com/llms-full.txt)
177
+ (`QCLI_DOCS_URL`), cached for an hour. When it cannot be fetched, the last
178
+ download is reused, else the snapshot bundled in the package
179
+ (`docs/llms-full.txt`), so the tools work offline.
180
+ - **API declarations**: the project's `node_modules` (the MCP server's working
181
+ directory and its parents) when the package is installed there, so the answer
182
+ matches the version you build against. Otherwise the `latest` version on npm,
183
+ downloaded from jsDelivr (unpkg as fallback) and cached in memory and in
184
+ `<tmp>/qodalis-mcp-cli`; offline, the newest cached version is used.
185
+ - **Scaffolding**: validates the name (lowercase letters, digits and hyphens, no
186
+ `cli-` prefix), refuses an existing target directory, runs without a TTY and
187
+ stops after 5 minutes. The standalone project installs its dependencies.
188
+
189
+ ### Prompts
190
+
191
+ | Prompt | Arguments | Walks the assistant through |
192
+ | ----------------------- | -------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
193
+ | `create-web-plugin` | `name`, `description` | Reading the guide, looking up the contracts with `qcli_api`, scaffolding, implementing the processor and module, tests, registering it. |
194
+ | `create-server-command` | `language` (`node`, `dotnet`, `python`), `command` | Adding a command processor to a Node, .NET or Python server, registering it and testing it. |
195
+
196
+ In Claude Code they appear as `/mcp__qcli__create-web-plugin` and
197
+ `/mcp__qcli__create-server-command`. The docs topics are also MCP resources
198
+ (`qcli-docs://<section>/<topic>`), which Claude Code can attach with `@`.
199
+
200
+ ### Example requests
201
+
202
+ - _"Search the Qodalis plugin docs for reading user input."_ → `qcli_docs_search`
203
+ - _"Show me ICliExecutionContext."_ → `qcli_api { "symbol": "ICliExecutionContext" }`
204
+ - _"Which options does CliEngine take?"_ → `qcli_api { "symbol": "CliEngine", "package": "cli" }`
205
+ - _"Scaffold a plugin called json-tools."_ → `qcli_scaffold_plugin { "name": "json-tools" }`
206
+
207
+ ### Filters and read-only mode
208
+
209
+ The dev tools are tools like any other: `QCLI_TOOLS` and `QCLI_EXCLUDE` apply to
210
+ them (`QCLI_EXCLUDE=qcli_scaffold_plugin`, or `QCLI_TOOLS=qcli_*` for the dev
211
+ tools alone), and `QCLI_READ_ONLY=true` hides and refuses `qcli_scaffold_plugin`
212
+ (the docs and API tools are read-only). A server tool with the same name as a dev
213
+ tool is hidden. When the server is unreachable, `tools/list` still returns the
214
+ dev tools.
215
+
216
+ ## Configuration
217
+
218
+ Every setting is an environment variable. `QODALIS_CLI_*` names are accepted as
219
+ aliases for every key.
220
+
221
+ | Variable | Default | Description |
222
+ | ------------------------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
223
+ | `QCLI_URL` | none | Server base URL, e.g. `https://cli.example.com` (a pasted `…/api/qcli/mcp` URL is accepted). Without it: developer mode. |
224
+ | `QCLI_TOKEN` | none | API token (`token create` in the web CLI) or the server's static `mcp.token`. |
225
+ | `QCLI_USER` | none | Sign in with this user name instead of a token (needs `QCLI_PASSWORD`). |
226
+ | `QCLI_PASSWORD` | none | Password for `QCLI_USER`. Never logged. |
227
+ | `QCLI_READ_ONLY` | `false` | Expose and allow only tools annotated `readOnlyHint: true`. See [read-only mode](#read-only-mode). |
228
+ | `QCLI_TOOLS` | all | Comma-separated allow-list of tool names; a trailing `*` matches a prefix (`fs_*`). |
229
+ | `QCLI_EXCLUDE` | none | Comma-separated deny-list, same syntax, applied after `QCLI_TOOLS`. |
230
+ | `QCLI_CONNECTION_TIMEOUT` | `8000` | Milliseconds to wait for the TCP/TLS connection before reporting the server unreachable. |
231
+ | `QCLI_REQUEST_TIMEOUT` | `60000` | Milliseconds a single request (`tools/list`, `tools/call`) may take. |
232
+ | `QCLI_TRUST_CERT` | `false` | Accept a self-signed TLS certificate. |
233
+ | `QCLI_ENV_FILE` | none | Path to a `.env` file (same as `--env-file`). Variables already set in the environment win. |
234
+ | `QCLI_DEV_TOOLS` | `true` | The `qcli_*` plugin-development tools, prompts and docs resources. `false` requires `QCLI_URL`. |
235
+ | `QCLI_DOCS_URL` | `https://cli.qodalis.com/llms-full.txt` | Where the plugin guide is fetched from (falls back to the bundled snapshot). |
236
+
237
+ Set either `QCLI_TOKEN` or `QCLI_USER` + `QCLI_PASSWORD`, not both.
238
+
239
+ ## Tools
240
+
241
+ The tools are whatever the server exposes to your account. A typical server
242
+ offers:
243
+
244
+ | Tools | Available when |
245
+ | -------------------------------------------------------------------- | -------------------------------------- |
246
+ | `server_info` | always: version, enabled features |
247
+ | one tool per CLI command (`weather`, `weather_current`, ...) | always |
248
+ | `fs_list`, `fs_stat`, `fs_read`, `fs_write`, `fs_mkdir`, `fs_delete` | the server's filesystem API is enabled |
249
+ | `data_sources`, `data_schema`, `data_query` | the data explorer is enabled |
250
+ | `jobs_list`, `jobs_get`, `jobs_history`, `jobs_trigger`, ... | the jobs plugin is installed |
251
+ | plugin tools | a server plugin contributes them |
252
+
253
+ Each one is limited by the account's permissions on the server (for example
254
+ `commands:execute`, `files:read` / `files:write`, `data:query`, `jobs:manage`),
255
+ and by the token's scope when it was created with `--permissions`.
256
+
257
+ ## Read-only mode
258
+
259
+ With `QCLI_READ_ONLY=true` the proxy lists only the tools the server annotates
260
+ `readOnlyHint: true` (such as `server_info`, `fs_list`, `fs_read`,
261
+ `data_schema`, `jobs_list`) and refuses calls to any other tool before they
262
+ reach the server. Command tools carry no read-only annotation, so they are
263
+ hidden in this mode; add the ones you want with a separate, non-read-only entry
264
+ and `QCLI_TOOLS`.
265
+
266
+ The filter is defence in depth. The real boundary is the account: give the
267
+ assistant a user (or a token scope) with only the permissions it should have.
268
+
269
+ ## Several servers
270
+
271
+ Add one entry per server (and per access level) with a distinct name:
272
+
273
+ ```json
274
+ {
275
+ "mcpServers": {
276
+ "qcli-dev": {
277
+ "command": "npx",
278
+ "args": ["-y", "@qodalis/mcp-cli"],
279
+ "env": { "QCLI_URL": "http://localhost:8047", "QCLI_TOKEN": "qcli_pat_dev..." }
280
+ },
281
+ "qcli-prod": {
282
+ "command": "npx",
283
+ "args": ["-y", "@qodalis/mcp-cli"],
284
+ "env": {
285
+ "QCLI_URL": "https://cli.example.com",
286
+ "QCLI_TOKEN": "qcli_pat_prod...",
287
+ "QCLI_READ_ONLY": "true"
288
+ }
289
+ },
290
+ "qcli-prod-jobs": {
291
+ "command": "npx",
292
+ "args": ["-y", "@qodalis/mcp-cli"],
293
+ "env": {
294
+ "QCLI_URL": "https://cli.example.com",
295
+ "QCLI_TOKEN": "qcli_pat_prod...",
296
+ "QCLI_TOOLS": "jobs_*,server_info",
297
+ "QCLI_EXCLUDE": "jobs_cancel"
298
+ }
299
+ }
300
+ }
301
+ }
302
+ ```
303
+
304
+ ## Troubleshooting
305
+
306
+ `claude mcp list` shows whether each server connects; inside a session `/mcp`
307
+ shows its status and tools and lets you reconnect. The proxy logs to stderr
308
+ (`claude --debug` shows it).
309
+
310
+ | Message | What to do |
311
+ | ----------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
312
+ | `… answered 401 (not authenticated): QCLI_TOKEN is wrong, expired or revoked` | Create a new token (`token create claude`) and update `QCLI_TOKEN`. `token list` in the web CLI shows expiry dates. |
313
+ | `… rejected QCLI_USER / QCLI_PASSWORD` | Check the credentials; the account may be disabled or locked out after failed sign-ins. |
314
+ | `… answered 403 …` | The account lacks `commands:execute`, or the server rejected the request origin. |
315
+ | `MCP is not enabled on this server (enable the mcp option)` | Turn on the server's `mcp` option and restart it; check `QCLI_URL` (base URL, not a page of the web app). |
316
+ | `Cannot reach … Check QCLI_URL / VPN.` | The server is down, the URL or port is wrong, or the VPN is disconnected. Retry the call once it is back: the proxy reconnects by itself. |
317
+ | `… did not answer within 60000 ms` | A slow command; raise `QCLI_REQUEST_TIMEOUT`. |
318
+ | A tool is missing | The account (or token scope) lacks its permission, the feature is not enabled on the server, or `QCLI_READ_ONLY` / `QCLI_TOOLS` / `QCLI_EXCLUDE` hide it. |
319
+ | `Nothing to serve: set QCLI_URL …` at startup | `QCLI_DEV_TOOLS=false` without `QCLI_URL`: set the URL (check the variable name; `npx -y @qodalis/mcp-cli --help` lists every variable). |
320
+ | Only the `qcli_*` tools are listed | No `QCLI_URL` (developer mode), or the server is unreachable: the log says which. |
321
+
322
+ ## How reconnection works
323
+
324
+ 1. The proxy connects to the endpoint on the first `tools/list` or
325
+ `tools/call`, not at startup, so Claude Code starts even when the server is
326
+ down.
327
+ 2. A failed connect is not cached: the next call tries again.
328
+ 3. A connection-level error (refused, reset, socket closed, DNS failure) drops
329
+ the connection and retries the request once on a fresh one. A connect timeout
330
+ is not retried, so an unreachable host fails within `QCLI_CONNECTION_TIMEOUT`.
331
+ 4. With `QCLI_USER` / `QCLI_PASSWORD`, a 401 signs in again and retries once.
332
+ 5. The tool list is cached for 60 s; a call to a tool the proxy does not know
333
+ yet refreshes it first, so new server commands work without a restart.
334
+
335
+ ## Security notes
336
+
337
+ - Tokens and passwords are read from the environment, sent only to `QCLI_URL`,
338
+ and never written to logs or tool results.
339
+ - Prefer an API token with a narrow `--permissions` scope and an expiry over a
340
+ password; revoke it with `token revoke <id>` when it is no longer needed.
341
+ - Use `https://` for anything but `localhost`. `QCLI_TRUST_CERT=true` disables
342
+ certificate verification for this process; use it only for self-signed
343
+ certificates you control.
344
+ - Nothing is written to stdout except the MCP protocol stream.
345
+
346
+ See [SECURITY.md](SECURITY.md) for reporting vulnerabilities.
347
+
348
+ ## Library use
349
+
350
+ The pieces are exported for embedding:
351
+
352
+ ```ts
353
+ import { createProxyServer, loadConfig, RemoteServer } from '@qodalis/mcp-cli';
354
+
355
+ const config = loadConfig(process.env);
356
+ const remote = new RemoteServer(config, { clientInfo: { name: 'my-app', version: '1.0.0' } });
357
+ const server = createProxyServer(remote, config); // a low-level MCP Server: connect any transport
358
+ ```
359
+
360
+ With the plugin-development tools (`remote` may be `undefined` for developer mode):
361
+
362
+ ```ts
363
+ import { createProxyServer, DevTools, loadConfig } from '@qodalis/mcp-cli';
364
+
365
+ const config = loadConfig(process.env);
366
+ const server = createProxyServer(undefined, config, { dev: new DevTools(config) });
367
+ ```
368
+
369
+ ## Development
370
+
371
+ ```bash
372
+ npm install
373
+ npm run build
374
+ npm test # unit tests and stdio end-to-end tests against a fake server (build first)
375
+ npm run typecheck
376
+ npm run check-format
377
+ npm run dev -- --env-file .env # run from source
378
+ npm run sync-docs # refresh docs/llms-full.txt: a path/URL argument, else generated by ../web-cli/tools/build-llms.js, else the site
379
+ ```
380
+
381
+ See [CONTRIBUTING.md](CONTRIBUTING.md).
382
+
383
+ ## License
384
+
385
+ [MIT](LICENSE) © Qodalis Solutions SRL
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":""}
@@ -0,0 +1,102 @@
1
+ #!/usr/bin/env node
2
+ import { serveStdio } from '@modelcontextprotocol/server/stdio';
3
+ import { ConfigError, describeConfig, ENV_FILE_VARIABLES, loadConfig } from './config.js';
4
+ import { DevTools } from './dev/tools.js';
5
+ import { applyEnvFile } from './env-file.js';
6
+ import { RemoteServer } from './remote.js';
7
+ import { createProxyServer, SERVER_NAME, SERVER_VERSION } from './server.js';
8
+ const SHUTDOWN_GRACE_MS = 5000;
9
+ const usage = `Plugin-development tools for Qodalis CLI (docs search, API declarations, plugin
10
+ scaffolding) and a proxy to a Qodalis CLI server's MCP endpoint (<QCLI_URL>/api/qcli/mcp).
11
+ Without QCLI_URL it runs in developer mode: only the plugin-development tools.
12
+ Configuration is read from the environment (QCLI_* or QODALIS_CLI_* names):
13
+ QCLI_URL server base URL, e.g. http://localhost:8047 (optional)
14
+ QCLI_TOKEN API token (token create in the web CLI) or the server's mcp token
15
+ QCLI_USER, QCLI_PASSWORD sign in with a password instead (signs in again on 401)
16
+ QCLI_READ_ONLY (false) only tools the server marks read-only
17
+ QCLI_TOOLS comma-separated allow-list of tool names (trailing * allowed)
18
+ QCLI_EXCLUDE comma-separated deny-list, applied after QCLI_TOOLS
19
+ QCLI_CONNECTION_TIMEOUT (8000 ms) QCLI_REQUEST_TIMEOUT (60000 ms)
20
+ QCLI_TRUST_CERT (false) accept a self-signed TLS certificate
21
+ QCLI_ENV_FILE path to a .env file (same as --env-file)
22
+ QCLI_DEV_TOOLS (true) the qcli_* plugin-development tools, prompts and docs resources
23
+ QCLI_DOCS_URL plugin docs (default https://cli.qodalis.com/llms-full.txt)
24
+ `;
25
+ const log = (message) => {
26
+ // Anything written to stdout corrupts the JSON-RPC stream: all logging goes to stderr.
27
+ process.stderr.write(`[${SERVER_NAME}] ${message}\n`);
28
+ };
29
+ const parseArgs = (argv) => {
30
+ const result = {};
31
+ for (let i = 0; i < argv.length; i++) {
32
+ const arg = argv[i];
33
+ if (arg === '--help' || arg === '-h') {
34
+ process.stdout.write(`Usage: mcp-cli [--env-file <path>] [--help] [--version]\n\n${usage}`);
35
+ process.exit(0);
36
+ }
37
+ if (arg === '--version' || arg === '-v') {
38
+ process.stdout.write(`${SERVER_VERSION}\n`);
39
+ process.exit(0);
40
+ }
41
+ if (arg === '--env-file') {
42
+ const value = argv[i + 1];
43
+ if (!value) {
44
+ throw new ConfigError('--env-file requires a path.');
45
+ }
46
+ result.envFile = value;
47
+ i++;
48
+ continue;
49
+ }
50
+ throw new ConfigError(`Unknown argument "${arg}".`);
51
+ }
52
+ return result;
53
+ };
54
+ const main = () => {
55
+ try {
56
+ const args = parseArgs(process.argv.slice(2));
57
+ const envFile = args.envFile ?? ENV_FILE_VARIABLES.map((name) => process.env[name]).find(Boolean);
58
+ if (envFile) {
59
+ applyEnvFile(envFile, process.env);
60
+ }
61
+ const config = loadConfig(process.env);
62
+ if (config.trustCert) {
63
+ log('QCLI_TRUST_CERT=true: TLS certificates are not verified');
64
+ }
65
+ if (!config.url && config.auth.kind !== 'none') {
66
+ log('QCLI_TOKEN / QCLI_USER are set but QCLI_URL is not: no server is used');
67
+ }
68
+ const remote = config.url
69
+ ? new RemoteServer(config, {
70
+ clientInfo: { name: SERVER_NAME, version: SERVER_VERSION },
71
+ log,
72
+ })
73
+ : undefined;
74
+ const dev = config.devTools ? new DevTools(config, { log }) : undefined;
75
+ const handle = serveStdio(() => createProxyServer(remote, config, { dev, log }), {
76
+ onerror: (error) => log(`transport error: ${error.message}`),
77
+ });
78
+ log(`v${SERVER_VERSION} ready for ${describeConfig(config)}${remote ? '; connects on first use' : ''}`);
79
+ let shuttingDown = false;
80
+ const shutdown = async (reason) => {
81
+ if (shuttingDown) {
82
+ return;
83
+ }
84
+ shuttingDown = true;
85
+ log(`shutting down (${reason})`);
86
+ setTimeout(() => process.exit(0), SHUTDOWN_GRACE_MS).unref();
87
+ await handle.close().catch(() => undefined);
88
+ await remote?.close();
89
+ process.exit(0);
90
+ };
91
+ process.once('SIGINT', () => void shutdown('SIGINT'));
92
+ process.once('SIGTERM', () => void shutdown('SIGTERM'));
93
+ process.stdin.once('end', () => void shutdown('stdin closed'));
94
+ }
95
+ catch (error) {
96
+ const message = error instanceof Error ? error.message : String(error);
97
+ log(`fatal: ${message}`);
98
+ process.exit(1);
99
+ }
100
+ };
101
+ main();
102
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,UAAU,EAAE,MAAM,oCAAoC,CAAC;AAChE,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC1F,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE7E,MAAM,iBAAiB,GAAG,IAAI,CAAC;AAE/B,MAAM,KAAK,GAAG;;;;;;;;;;;;;;;CAeb,CAAC;AAEF,MAAM,GAAG,GAAG,CAAC,OAAe,EAAQ,EAAE;IAClC,uFAAuF;IACvF,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,WAAW,KAAK,OAAO,IAAI,CAAC,CAAC;AAC1D,CAAC,CAAC;AAEF,MAAM,SAAS,GAAG,CAAC,IAAc,EAAwB,EAAE;IACvD,MAAM,MAAM,GAAyB,EAAE,CAAC;IACxC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACnC,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACpB,IAAI,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;YACnC,OAAO,CAAC,MAAM,CAAC,KAAK,CAChB,8DAA8D,KAAK,EAAE,CACxE,CAAC;YACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACpB,CAAC;QACD,IAAI,GAAG,KAAK,WAAW,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;YACtC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,cAAc,IAAI,CAAC,CAAC;YAC5C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACpB,CAAC;QACD,IAAI,GAAG,KAAK,YAAY,EAAE,CAAC;YACvB,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YAC1B,IAAI,CAAC,KAAK,EAAE,CAAC;gBACT,MAAM,IAAI,WAAW,CAAC,6BAA6B,CAAC,CAAC;YACzD,CAAC;YACD,MAAM,CAAC,OAAO,GAAG,KAAK,CAAC;YACvB,CAAC,EAAE,CAAC;YACJ,SAAS;QACb,CAAC;QACD,MAAM,IAAI,WAAW,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAAC;IACxD,CAAC;IACD,OAAO,MAAM,CAAC;AAClB,CAAC,CAAC;AAEF,MAAM,IAAI,GAAG,GAAS,EAAE;IACpB,IAAI,CAAC;QACD,MAAM,IAAI,GAAG,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QAC9C,MAAM,OAAO,GACT,IAAI,CAAC,OAAO,IAAI,kBAAkB,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACtF,IAAI,OAAO,EAAE,CAAC;YACV,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC;QACvC,CAAC;QACD,MAAM,MAAM,GAAG,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACvC,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC;YACnB,GAAG,CAAC,yDAAyD,CAAC,CAAC;QACnE,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YAC7C,GAAG,CAAC,uEAAuE,CAAC,CAAC;QACjF,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG;YACrB,CAAC,CAAC,IAAI,YAAY,CAAC,MAAM,EAAE;gBACrB,UAAU,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,cAAc,EAAE;gBAC1D,GAAG;aACN,CAAC;YACJ,CAAC,CAAC,SAAS,CAAC;QAChB,MAAM,GAAG,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,QAAQ,CAAC,MAAM,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACxE,MAAM,MAAM,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE;YAC7E,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,CAAC,oBAAoB,KAAK,CAAC,OAAO,EAAE,CAAC;SAC/D,CAAC,CAAC;QACH,GAAG,CACC,IAAI,cAAc,cAAc,cAAc,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,yBAAyB,CAAC,CAAC,CAAC,EAAE,EAAE,CACrG,CAAC;QAEF,IAAI,YAAY,GAAG,KAAK,CAAC;QACzB,MAAM,QAAQ,GAAG,KAAK,EAAE,MAAc,EAAiB,EAAE;YACrD,IAAI,YAAY,EAAE,CAAC;gBACf,OAAO;YACX,CAAC;YACD,YAAY,GAAG,IAAI,CAAC;YACpB,GAAG,CAAC,kBAAkB,MAAM,GAAG,CAAC,CAAC;YACjC,UAAU,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,iBAAiB,CAAC,CAAC,KAAK,EAAE,CAAC;YAC7D,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;YAC5C,MAAM,MAAM,EAAE,KAAK,EAAE,CAAC;YACtB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACpB,CAAC,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,KAAK,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC;QACtD,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,KAAK,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC;QACxD,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,KAAK,QAAQ,CAAC,cAAc,CAAC,CAAC,CAAC;IACnE,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACb,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACvE,GAAG,CAAC,UAAU,OAAO,EAAE,CAAC,CAAC;QACzB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;AACL,CAAC,CAAC;AAEF,IAAI,EAAE,CAAC"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Environment-driven configuration. Primary names use `QCLI_`; `QODALIS_CLI_`
3
+ * is accepted as an alias for every key.
4
+ */
5
+ export type Env = Record<string, string | undefined>;
6
+ export declare class ConfigError extends Error {
7
+ constructor(message: string);
8
+ }
9
+ /** How the proxy authenticates to the Qodalis CLI server. */
10
+ export type AuthConfig = {
11
+ kind: 'none';
12
+ } | {
13
+ kind: 'token';
14
+ token: string;
15
+ } | {
16
+ kind: 'password';
17
+ password: string;
18
+ user: string;
19
+ };
20
+ export type Config = {
21
+ auth: AuthConfig;
22
+ connectionTimeoutMs: number;
23
+ /** Expose the local plugin-development tools and prompts (`qcli_*`) */
24
+ devTools: boolean;
25
+ /** Where the plugin-development docs (`llms-full.txt`) are fetched from */
26
+ docsUrl: string;
27
+ /** Client-side deny-list (trailing `*` allowed), applied after `tools` */
28
+ exclude: string[];
29
+ /** Expose and allow only tools annotated `readOnlyHint: true` */
30
+ readOnly: boolean;
31
+ requestTimeoutMs: number;
32
+ /** Client-side allow-list (trailing `*` allowed); empty = every tool */
33
+ tools: string[];
34
+ /** Accept self-signed TLS certificates */
35
+ trustCert: boolean;
36
+ /**
37
+ * Server base URL, e.g. `https://cli.example.com` (no trailing slash). Without it
38
+ * the process runs in developer mode: only the local plugin-development tools.
39
+ */
40
+ url: string | undefined;
41
+ };
42
+ export declare const DEFAULT_DOCS_URL = "https://cli.qodalis.com/llms-full.txt";
43
+ export declare const PREFIXES: string[];
44
+ /** `QCLI_<suffix>` plus the `QODALIS_CLI_<suffix>` alias. */
45
+ export declare const names: (suffix: string) => string[];
46
+ export declare const ENV_FILE_VARIABLES: string[];
47
+ export type EnvReader = {
48
+ boolean: (names: string[], fallback: boolean) => boolean;
49
+ list: (names: string[]) => string[];
50
+ optional: (names: string[]) => string | undefined;
51
+ positiveInteger: (names: string[], fallback: number) => number;
52
+ required: (names: string[]) => string;
53
+ };
54
+ /** Typed readers over an environment map; the first non-empty name wins. */
55
+ export declare const createEnvReader: (env: Env) => EnvReader;
56
+ export declare const normalizeUrl: (input: string) => string;
57
+ export declare const loadConfig: (env?: Env) => Config;
58
+ /** One-line summary for the startup log (never contains secrets). */
59
+ export declare const describeConfig: (config: Config) => string;
60
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;AAErD,qBAAa,WAAY,SAAQ,KAAK;gBACtB,OAAO,EAAE,MAAM;CAI9B;AAED,6DAA6D;AAC7D,MAAM,MAAM,UAAU,GAChB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAChC;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAE3D,MAAM,MAAM,MAAM,GAAG;IACjB,IAAI,EAAE,UAAU,CAAC;IACjB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,uEAAuE;IACvE,QAAQ,EAAE,OAAO,CAAC;IAClB,2EAA2E;IAC3E,OAAO,EAAE,MAAM,CAAC;IAChB,0EAA0E;IAC1E,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,iEAAiE;IACjE,QAAQ,EAAE,OAAO,CAAC;IAClB,gBAAgB,EAAE,MAAM,CAAC;IACzB,wEAAwE;IACxE,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,0CAA0C;IAC1C,SAAS,EAAE,OAAO,CAAC;IACnB;;;OAGG;IACH,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;CAC3B,CAAC;AAEF,eAAO,MAAM,gBAAgB,0CAA0C,CAAC;AAExE,eAAO,MAAM,QAAQ,UAA0B,CAAC;AAEhD,6DAA6D;AAC7D,eAAO,MAAM,KAAK,GAAI,QAAQ,MAAM,KAAG,MAAM,EAAqD,CAAC;AAEnG,eAAO,MAAM,kBAAkB,UAAoB,CAAC;AAKpD,MAAM,MAAM,SAAS,GAAG;IACpB,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,OAAO,KAAK,OAAO,CAAC;IACzD,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,MAAM,EAAE,CAAC;IACpC,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,MAAM,GAAG,SAAS,CAAC;IAClD,eAAe,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,MAAM,KAAK,MAAM,CAAC;IAC/D,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,MAAM,CAAC;CACzC,CAAC;AAEF,4EAA4E;AAC5E,eAAO,MAAM,eAAe,GAAI,KAAK,GAAG,KAAG,SAoD1C,CAAC;AAmBF,eAAO,MAAM,YAAY,GAAI,OAAO,MAAM,KAAG,MAI5C,CAAC;AAqBF,eAAO,MAAM,UAAU,GAAI,MAAK,GAAiB,KAAG,MAwBnD,CAAC;AAEF,qEAAqE;AACrE,eAAO,MAAM,cAAc,GAAI,QAAQ,MAAM,KAAG,MAmB/C,CAAC"}