@qodalis/cli-server-node 1.0.0-beta.5 → 1.0.0-beta.7
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/README.md +397 -19
- package/dist/package.json +77 -0
- package/dist/src/auth/cli-server-auth-gate.d.ts +33 -0
- package/dist/src/auth/cli-server-auth-gate.d.ts.map +1 -0
- package/dist/src/auth/cli-server-auth-gate.js +121 -0
- package/dist/src/auth/cli-server-auth-gate.js.map +1 -0
- package/dist/src/auth/cli-server-auth.d.ts +149 -0
- package/dist/src/auth/cli-server-auth.d.ts.map +1 -0
- package/dist/src/auth/cli-server-auth.js +143 -0
- package/dist/src/auth/cli-server-auth.js.map +1 -0
- package/dist/src/auth/index.d.ts +4 -0
- package/dist/src/auth/index.d.ts.map +1 -0
- package/dist/src/auth/index.js +19 -0
- package/dist/src/auth/index.js.map +1 -0
- package/dist/src/auth/ws-upgrade-auth.d.ts +30 -0
- package/dist/src/auth/ws-upgrade-auth.d.ts.map +1 -0
- package/dist/src/auth/ws-upgrade-auth.js +49 -0
- package/dist/src/auth/ws-upgrade-auth.js.map +1 -0
- package/dist/src/controllers/cli-controller.d.ts +2 -1
- package/dist/src/controllers/cli-controller.d.ts.map +1 -1
- package/dist/src/controllers/cli-controller.js +7 -2
- package/dist/src/controllers/cli-controller.js.map +1 -1
- package/dist/src/controllers/cli-version-controller.d.ts +12 -1
- package/dist/src/controllers/cli-version-controller.d.ts.map +1 -1
- package/dist/src/controllers/cli-version-controller.js +7 -2
- package/dist/src/controllers/cli-version-controller.js.map +1 -1
- package/dist/src/create-cli-server.d.ts +71 -1
- package/dist/src/create-cli-server.d.ts.map +1 -1
- package/dist/src/create-cli-server.js +146 -10
- package/dist/src/create-cli-server.js.map +1 -1
- package/dist/src/extensions/cli-builder.d.ts +10 -0
- package/dist/src/extensions/cli-builder.d.ts.map +1 -1
- package/dist/src/extensions/cli-builder.js +14 -0
- package/dist/src/extensions/cli-builder.js.map +1 -1
- package/dist/src/index.d.ts +4 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +36 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/mcp/index.d.ts +10 -0
- package/dist/src/mcp/index.d.ts.map +1 -0
- package/dist/src/mcp/index.js +34 -0
- package/dist/src/mcp/index.js.map +1 -0
- package/dist/src/mcp/mcp-controller.d.ts +54 -0
- package/dist/src/mcp/mcp-controller.d.ts.map +1 -0
- package/dist/src/mcp/mcp-controller.js +358 -0
- package/dist/src/mcp/mcp-controller.js.map +1 -0
- package/dist/src/mcp/mcp-render.d.ts +10 -0
- package/dist/src/mcp/mcp-render.d.ts.map +1 -0
- package/dist/src/mcp/mcp-render.js +66 -0
- package/dist/src/mcp/mcp-render.js.map +1 -0
- package/dist/src/mcp/mcp-schema.d.ts +8 -0
- package/dist/src/mcp/mcp-schema.d.ts.map +1 -0
- package/dist/src/mcp/mcp-schema.js +64 -0
- package/dist/src/mcp/mcp-schema.js.map +1 -0
- package/dist/src/mcp/mcp-types.d.ts +70 -0
- package/dist/src/mcp/mcp-types.d.ts.map +1 -0
- package/dist/src/mcp/mcp-types.js +21 -0
- package/dist/src/mcp/mcp-types.js.map +1 -0
- package/dist/src/mcp/providers/commands-provider.d.ts +26 -0
- package/dist/src/mcp/providers/commands-provider.d.ts.map +1 -0
- package/dist/src/mcp/providers/commands-provider.js +142 -0
- package/dist/src/mcp/providers/commands-provider.js.map +1 -0
- package/dist/src/mcp/providers/data-explorer-provider.d.ts +15 -0
- package/dist/src/mcp/providers/data-explorer-provider.d.ts.map +1 -0
- package/dist/src/mcp/providers/data-explorer-provider.js +83 -0
- package/dist/src/mcp/providers/data-explorer-provider.js.map +1 -0
- package/dist/src/mcp/providers/filesystem-provider.d.ts +15 -0
- package/dist/src/mcp/providers/filesystem-provider.d.ts.map +1 -0
- package/dist/src/mcp/providers/filesystem-provider.js +166 -0
- package/dist/src/mcp/providers/filesystem-provider.js.map +1 -0
- package/dist/src/mcp/providers/jobs-provider.d.ts +23 -0
- package/dist/src/mcp/providers/jobs-provider.d.ts.map +1 -0
- package/dist/src/mcp/providers/jobs-provider.js +121 -0
- package/dist/src/mcp/providers/jobs-provider.js.map +1 -0
- package/dist/src/mcp/providers/server-provider.d.ts +19 -0
- package/dist/src/mcp/providers/server-provider.d.ts.map +1 -0
- package/dist/src/mcp/providers/server-provider.js +73 -0
- package/dist/src/mcp/providers/server-provider.js.map +1 -0
- package/dist/src/processors/cli-base64-command-processor.d.ts.map +1 -1
- package/dist/src/processors/cli-base64-command-processor.js +2 -0
- package/dist/src/processors/cli-base64-command-processor.js.map +1 -1
- package/dist/src/processors/cli-hash-command-processor.d.ts +1 -0
- package/dist/src/processors/cli-hash-command-processor.d.ts.map +1 -1
- package/dist/src/processors/cli-hash-command-processor.js +1 -0
- package/dist/src/processors/cli-hash-command-processor.js.map +1 -1
- package/dist/src/processors/cli-http-command-processor.d.ts.map +1 -1
- package/dist/src/processors/cli-http-command-processor.js +2 -0
- package/dist/src/processors/cli-http-command-processor.js.map +1 -1
- package/dist/src/server.js +12 -0
- package/dist/src/server.js.map +1 -1
- package/dist/src/services/cli-event-socket-manager.d.ts +37 -1
- package/dist/src/services/cli-event-socket-manager.d.ts.map +1 -1
- package/dist/src/services/cli-event-socket-manager.js +126 -28
- package/dist/src/services/cli-event-socket-manager.js.map +1 -1
- package/dist/src/services/cli-log-socket-manager.d.ts +15 -1
- package/dist/src/services/cli-log-socket-manager.d.ts.map +1 -1
- package/dist/src/services/cli-log-socket-manager.js +53 -13
- package/dist/src/services/cli-log-socket-manager.js.map +1 -1
- package/dist/src/services/cli-shell-options.d.ts +107 -0
- package/dist/src/services/cli-shell-options.d.ts.map +1 -0
- package/dist/src/services/cli-shell-options.js +139 -0
- package/dist/src/services/cli-shell-options.js.map +1 -0
- package/dist/src/services/cli-shell-session-manager.d.ts +83 -10
- package/dist/src/services/cli-shell-session-manager.d.ts.map +1 -1
- package/dist/src/services/cli-shell-session-manager.js +474 -85
- package/dist/src/services/cli-shell-session-manager.js.map +1 -1
- package/dist/src/services/index.d.ts +2 -1
- package/dist/src/services/index.d.ts.map +1 -1
- package/dist/src/services/index.js +5 -1
- package/dist/src/services/index.js.map +1 -1
- package/dist/src/services/process-tree.d.ts +38 -0
- package/dist/src/services/process-tree.d.ts.map +1 -0
- package/dist/src/services/process-tree.js +249 -0
- package/dist/src/services/process-tree.js.map +1 -0
- package/dist/src/services/session-bound-sockets.d.ts +45 -0
- package/dist/src/services/session-bound-sockets.d.ts.map +1 -0
- package/dist/src/services/session-bound-sockets.js +129 -0
- package/dist/src/services/session-bound-sockets.js.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# Qodalis CLI Server (Node.js)
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@qodalis/cli-server-node)
|
|
4
|
+
[](https://www.npmjs.com/package/@qodalis/cli-server-abstractions)
|
|
5
|
+
|
|
3
6
|
A Node.js CLI server framework for the [Qodalis CLI](https://github.com/qodalis-solutions/web-cli) ecosystem. Build custom server-side commands that integrate with the Qodalis web terminal.
|
|
4
7
|
|
|
5
8
|
## Installation
|
|
@@ -303,11 +306,319 @@ The server exposes versioned endpoints:
|
|
|
303
306
|
|
|
304
307
|
| Method | Path | Description |
|
|
305
308
|
|--------|------|-------------|
|
|
306
|
-
| GET | `/api/
|
|
307
|
-
| GET | `/api/v1/
|
|
308
|
-
| GET | `/api/v1/
|
|
309
|
-
| POST | `/api/v1/
|
|
310
|
-
|
|
|
309
|
+
| GET | `/api/qcli/versions` | Version discovery (supported versions, preferred version) |
|
|
310
|
+
| GET | `/api/v1/qcli/version` | V1 server version |
|
|
311
|
+
| GET | `/api/v1/qcli/commands` | V1 commands (all processors) |
|
|
312
|
+
| POST | `/api/v1/qcli/execute` | V1 execute |
|
|
313
|
+
| POST | `/api/v1/qcli/execute/stream` | V1 SSE streaming execute |
|
|
314
|
+
| POST | `/api/qcli/mcp` | MCP endpoint (when the `mcp` option is on, see [MCP Endpoint](#mcp-endpoint)) |
|
|
315
|
+
| WS | `/ws/qcli/events` | WebSocket events (also `/ws/v1/qcli/events`) |
|
|
316
|
+
| WS | `/ws/v1/qcli/shell` | Interactive PTY shell session |
|
|
317
|
+
|
|
318
|
+
`GET /capabilities` reports `shell: true|false` and, when the shell is enabled, `shellAuth: 'users' | 'token' | 'custom' | 'anonymous' | 'locked'`.
|
|
319
|
+
|
|
320
|
+
## MCP Endpoint
|
|
321
|
+
|
|
322
|
+
The server can expose **the whole system** to AI clients such as Claude Code over the [Model Context Protocol](https://modelcontextprotocol.io): CLI commands, the filesystem API, the data explorer, jobs and tools contributed by plugins, each with the same permissions as a CLI user. It works with or without the users plugin.
|
|
323
|
+
|
|
324
|
+
### Enabling
|
|
325
|
+
|
|
326
|
+
MCP is off by default. Enable it with the `mcp` option:
|
|
327
|
+
|
|
328
|
+
```typescript
|
|
329
|
+
const { app, mcp } = createCliServer({
|
|
330
|
+
mcp: true, // or an options object:
|
|
331
|
+
// mcp: {
|
|
332
|
+
// tools: ['weather_*', 'echo'], // allow-list (trailing * = prefix); default: every tool
|
|
333
|
+
// exclude: ['fs_delete'], // deny-list, applied after `tools`
|
|
334
|
+
// token: process.env.MCP_TOKEN, // only WITHOUT an auth provider: required bearer token
|
|
335
|
+
// instructions: 'Each tool runs one command of this server.', // returned by initialize
|
|
336
|
+
// enabled: true, // default true when the object is given
|
|
337
|
+
// },
|
|
338
|
+
configure: (builder) => builder.addProcessor(new CliEchoCommandProcessor()),
|
|
339
|
+
});
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### Route and transport
|
|
343
|
+
|
|
344
|
+
- `POST /api/qcli/mcp` (`{basePath}/mcp`): MCP *Streamable HTTP*, **stateless** (no `Mcp-Session-Id`) and **JSON responses only** (no SSE). `GET`/`DELETE` → `405` with `Allow: POST`.
|
|
345
|
+
- JSON-RPC 2.0: notifications → `202` with an empty body; malformed JSON → `-32700`, invalid request (including batches) → `-32600`, unknown method → `-32601`, bad params → `-32602` (JSON-RPC errors use HTTP 200).
|
|
346
|
+
- Methods: `initialize` (protocol versions `2025-11-25`, `2025-06-18`, `2025-03-26`; the client's version is echoed when supported, otherwise the newest), `ping`, `tools/list`, `tools/call`.
|
|
347
|
+
- An `Origin` header, when present, must be in `allowedOrigins` or same-origin, otherwise `403` (DNS-rebinding protection; checked before authentication).
|
|
348
|
+
|
|
349
|
+
### Authentication
|
|
350
|
+
|
|
351
|
+
| Server | Access |
|
|
352
|
+
|---|---|
|
|
353
|
+
| users plugin mounted (`protectServer`) | a session or an [API token](plugins/users/README.md#api-tokens) (`Authorization: Bearer qcli_pat_…`) holding `commands:execute`; `401`/`403` as plain `{ "error": … }` responses. `tools/list` and `tools/call` only show the tools whose permissions the caller holds. `mcp.token` is ignored. |
|
|
354
|
+
| no auth provider, `mcp.token` set | `Authorization: Bearer <token>` (constant-time compare), else `401` |
|
|
355
|
+
| no auth provider, no token | open, like `/execute` |
|
|
356
|
+
|
|
357
|
+
### Tools
|
|
358
|
+
|
|
359
|
+
Tools are collected from **tool providers** on every request, so new registrations, enabled features and the caller's permissions are always current. The `tools`/`exclude` lists apply to every tool.
|
|
360
|
+
|
|
361
|
+
| Provider | Tools | Permissions | Annotations |
|
|
362
|
+
|---|---|---|---|
|
|
363
|
+
| server | `server_info` (version, capabilities, enabled features, command names) | — | read-only |
|
|
364
|
+
| commands | one tool per command (below) | `commands:execute` + `requiredPermissions` along the chain | — |
|
|
365
|
+
| filesystem (when a file storage provider is configured) | `fs_list {path}`, `fs_stat {path}`, `fs_read {path, maxBytes?=262144}` | `files:read` | read-only |
|
|
366
|
+
| | `fs_write {path, content}`, `fs_mkdir {path}` | `files:write` | — |
|
|
367
|
+
| | `fs_delete {path, recursive?=false}` | `files:write` | destructive |
|
|
368
|
+
| data explorer (when sources are registered) | `data_sources`, `data_schema {source}` | `data:query` | read-only |
|
|
369
|
+
| | `data_query {source, query, parameters?}` | `data:query` | — |
|
|
370
|
+
| jobs (when the jobs plugin is mounted) | `jobs_list`, `jobs_get {id}`, `jobs_history {id, limit?=20}` | `jobs:read` | read-only |
|
|
371
|
+
| | `jobs_trigger`, `jobs_pause`, `jobs_resume`, `jobs_stop`, `jobs_cancel` `{id}` | `jobs:manage` | `jobs_stop`/`jobs_cancel` destructive |
|
|
372
|
+
|
|
373
|
+
The jobs tools' `id` accepts a job id or a unique job name (exact, then case-insensitive; an ambiguous name is an error).
|
|
374
|
+
|
|
375
|
+
Feature tools use the same services as the HTTP routes (the storage provider and its path checks, the data explorer executor with its timeouts and row limits, the job scheduler); their errors become `isError: true` results. `fs_read` returns UTF-8 text (binary files → `isError` `binary file, N bytes`). Paths outside the provider's allow-list are rejected exactly as by `/api/qcli/fs` (symlinks are resolved first, so on macOS allow `/private/tmp` rather than `/tmp`).
|
|
376
|
+
|
|
377
|
+
**Command tools**: every root command and sub-command that has no sub-commands, declares parameters or takes a value (`valueRequired`, or `acceptsValue` for an optional one) becomes a tool named after its path joined with `_` (`weather`, `weather_current`), lower-cased, other characters than `[a-z0-9_-]` replaced by `-`, at most 64 characters. The input schema has one property per parameter (`string`, `number`, `boolean`, `array` of strings; anything else is a string) plus `value` (the positional input) when the command takes one: required with `valueRequired`, optional with `acceptsValue` (set it on processors that read `command.value` without requiring it; the built-in `hash`, `base64 encode|decode`, `http get|post` and the weather commands do). A call builds the same command the CLI would send and runs it through the **same executor as `/execute`**; the outputs are rendered as text (tables as `a | b` rows, lists one item per line, JSON pretty-printed, key-value as `key: value`), `isError` is `exitCode != 0`, and a single JSON-object output is also returned as `structuredContent`. Unknown tools and invalid arguments (wrong type, unknown or missing property) are JSON-RPC `-32602` errors.
|
|
378
|
+
|
|
379
|
+
Name collisions (a warning is logged): built-in feature tools win, then plugin/host provider tools in registration order, then command tools. The interactive shell is never a tool.
|
|
380
|
+
|
|
381
|
+
### Adding tools (providers)
|
|
382
|
+
|
|
383
|
+
```typescript
|
|
384
|
+
import { McpToolProvider, mcpObjectSchema } from '@qodalis/cli-server-node';
|
|
385
|
+
|
|
386
|
+
const provider: McpToolProvider = {
|
|
387
|
+
name: 'inventory',
|
|
388
|
+
getTools: () => [
|
|
389
|
+
{
|
|
390
|
+
name: 'inventory_count',
|
|
391
|
+
description: 'Count the items of a warehouse',
|
|
392
|
+
inputSchema: mcpObjectSchema({ warehouse: { type: 'string' } }, ['warehouse']),
|
|
393
|
+
requiredPermissions: ['inventory:read'], // checked for list and call (with an auth provider)
|
|
394
|
+
annotations: { readOnlyHint: true },
|
|
395
|
+
call: async (args, { auth, signal }) => ({ text: `42 items in ${args.warehouse}`, isError: false }),
|
|
396
|
+
},
|
|
397
|
+
],
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
const server = createCliServer({ mcp: true });
|
|
401
|
+
server.mcp.addProvider(provider); // or builder.addMcpToolProvider(provider) in `configure`
|
|
402
|
+
// plugins can also return `mcpProviders: [provider]` from their build result (picked up by mountPlugin)
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
Arguments are validated against `inputSchema` before `call` runs. Return `structured` to also send `structuredContent`.
|
|
406
|
+
|
|
407
|
+
### Using the MCP endpoint from an MCP client
|
|
408
|
+
|
|
409
|
+
This guide connects [Claude Code](https://docs.claude.com/en/docs/claude-code) to a server. The commands use the demo server (`cd demo && npm start`: port 8047, users plugin, user `root` / password `admin`); replace the URL and credentials with yours.
|
|
410
|
+
|
|
411
|
+
#### 1. Enable MCP on the server
|
|
412
|
+
|
|
413
|
+
MCP is enabled with the `mcp` option of `createCliServer` (`mcp: true`, or an options object).
|
|
414
|
+
|
|
415
|
+
With the users plugin (sessions, permissions, API tokens):
|
|
416
|
+
|
|
417
|
+
```typescript
|
|
418
|
+
import { createCliServer, CliEchoCommandProcessor } from '@qodalis/cli-server-node';
|
|
419
|
+
import { CliUsersBuilder } from '@qodalis/cli-server-plugin-users';
|
|
420
|
+
|
|
421
|
+
const { app, eventSocketManager, mountPlugin } = createCliServer({
|
|
422
|
+
mcp: { instructions: 'Each tool runs one command of this server.' },
|
|
423
|
+
configure: (builder) => builder.addProcessor(new CliEchoCommandProcessor()),
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
const usersPlugin = new CliUsersBuilder()
|
|
427
|
+
.setDefaultAdminPassword('admin')
|
|
428
|
+
.build((msg) => eventSocketManager.broadcastMessage(msg));
|
|
429
|
+
mountPlugin(usersPlugin);
|
|
430
|
+
|
|
431
|
+
app.listen(8047);
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
Without the users plugin, protect the endpoint with a static bearer token (without `token` it is as open as `/execute`):
|
|
435
|
+
|
|
436
|
+
```typescript
|
|
437
|
+
const { app } = createCliServer({
|
|
438
|
+
mcp: { token: process.env.QCLI_MCP_TOKEN },
|
|
439
|
+
configure: (builder) => builder.addProcessor(new CliEchoCommandProcessor()),
|
|
440
|
+
});
|
|
441
|
+
app.listen(8047);
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
The endpoint is `http://localhost:8047/api/qcli/mcp` (`{basePath}/mcp`).
|
|
445
|
+
|
|
446
|
+
#### 2. Get a token
|
|
447
|
+
|
|
448
|
+
With the users plugin, sign in and create an API token with the session token (requires `jq`; otherwise copy the `token` field of each response):
|
|
449
|
+
|
|
450
|
+
```bash
|
|
451
|
+
SESSION=$(curl -s -X POST http://localhost:8047/api/v1/qcli/sessions \
|
|
452
|
+
-H "Content-Type: application/json" \
|
|
453
|
+
-d '{"username":"root","password":"admin"}' | jq -r .token)
|
|
454
|
+
|
|
455
|
+
TOKEN=$(curl -s -X POST http://localhost:8047/api/v1/qcli/tokens \
|
|
456
|
+
-H "Authorization: Bearer $SESSION" \
|
|
457
|
+
-H "Content-Type: application/json" \
|
|
458
|
+
-d '{"name":"claude","expiresInDays":30}' | jq -r .token)
|
|
459
|
+
|
|
460
|
+
echo "$TOKEN"
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
The API token (`qcli_pat_…`) is shown only once. In the web CLI connected to the server, `token create claude` does the same. Add `"permissions": ["commands:execute", "files:read"]` to the body to give the token only part of your permissions; tokens are listed with `GET /api/v1/qcli/tokens` and revoked with `DELETE /api/v1/qcli/tokens/<id>` (see [API tokens](plugins/users/README.md#api-tokens)).
|
|
464
|
+
|
|
465
|
+
Without the users plugin, the token is the `mcp.token` value.
|
|
466
|
+
|
|
467
|
+
#### 3. Connect Claude Code
|
|
468
|
+
|
|
469
|
+
**(a) Directly over HTTP** with the token:
|
|
470
|
+
|
|
471
|
+
```bash
|
|
472
|
+
claude mcp add --transport http qcli http://localhost:8047/api/qcli/mcp --header "Authorization: Bearer <token>"
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
**(b) Through the [`@qodalis/mcp-cli`](https://github.com/qodalis-solutions/mcp-cli) bridge**, a local MCP server configured with environment variables:
|
|
476
|
+
|
|
477
|
+
```bash
|
|
478
|
+
claude mcp add qcli -e QCLI_URL=http://localhost:8047 -e QCLI_TOKEN=<token> -- npx -y @qodalis/mcp-cli
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
Use `QCLI_USER` / `QCLI_PASSWORD` instead of `QCLI_TOKEN` to sign in with a user, and `QCLI_READ_ONLY=true` to expose only read-only tools.
|
|
482
|
+
|
|
483
|
+
Check the connection with `claude mcp list` (or `/mcp` inside Claude Code); `qcli` should be listed as connected, with the tools of step 4.
|
|
484
|
+
|
|
485
|
+
#### 4. Check the endpoint with curl
|
|
486
|
+
|
|
487
|
+
Every request is one JSON-RPC message sent with `POST`; the server is stateless, so no session header is needed between calls.
|
|
488
|
+
|
|
489
|
+
```bash
|
|
490
|
+
# initialize
|
|
491
|
+
curl -s http://localhost:8047/api/qcli/mcp \
|
|
492
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
493
|
+
-H "Content-Type: application/json" \
|
|
494
|
+
-H "Accept: application/json, text/event-stream" \
|
|
495
|
+
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"1.0"}}}'
|
|
496
|
+
|
|
497
|
+
# list the tools
|
|
498
|
+
curl -s http://localhost:8047/api/qcli/mcp \
|
|
499
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
500
|
+
-H "Content-Type: application/json" \
|
|
501
|
+
-H "Accept: application/json, text/event-stream" \
|
|
502
|
+
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'
|
|
503
|
+
|
|
504
|
+
# call a tool
|
|
505
|
+
curl -s http://localhost:8047/api/qcli/mcp \
|
|
506
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
507
|
+
-H "Content-Type: application/json" \
|
|
508
|
+
-H "Accept: application/json, text/event-stream" \
|
|
509
|
+
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"echo","arguments":{"value":"hello from MCP"}}}'
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
Responses (shortened):
|
|
513
|
+
|
|
514
|
+
```json
|
|
515
|
+
{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2025-06-18","capabilities":{"tools":{"listChanged":false}},"serverInfo":{"name":"qodalis-cli-server","version":"1.0.0-beta.7"},"instructions":"…"}}
|
|
516
|
+
{"jsonrpc":"2.0","id":2,"result":{"tools":[{"name":"server_info","description":"…","inputSchema":{"type":"object","properties":{},"required":[],"additionalProperties":false},"annotations":{"readOnlyHint":true}},{"name":"echo", …}, …]}}
|
|
517
|
+
{"jsonrpc":"2.0","id":3,"result":{"content":[{"type":"text","text":"hello from MCP"}],"isError":false}}
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
A failed command is a normal result with `"isError": true`; an unknown tool or invalid arguments is a JSON-RPC error (`-32602`).
|
|
521
|
+
|
|
522
|
+
#### 5. Troubleshooting
|
|
523
|
+
|
|
524
|
+
| Symptom | Cause and fix |
|
|
525
|
+
|---|---|
|
|
526
|
+
| `claude mcp list` / `/mcp` shows `qcli` as failed | Run the curl checks of step 4 with the same URL and token to see the HTTP error below. |
|
|
527
|
+
| `401 {"error":"unauthorized"}` or `{"error":"invalid or expired token"}` | No `Authorization: Bearer …` header, or the token is wrong, expired or revoked, or its user is disabled or deleted: create a new token (step 2). Without the users plugin, check `mcp.token`. |
|
|
528
|
+
| `403 {"error":"forbidden: origin not allowed"}` | The request has an `Origin` header that is neither in `allowedOrigins` nor the server's own origin (DNS-rebinding protection). Claude Code sends no `Origin`; for browser-based clients add the origin to `allowedOrigins`. |
|
|
529
|
+
| `403 {"error":"forbidden: missing permission commands:execute"}` | The user, or the token's scope, lacks `commands:execute`, which the endpoint requires. Add it to one of the user's groups, or create a token whose scope includes it. |
|
|
530
|
+
| `403 {"error":"password change required"}` | The user must change their password first. |
|
|
531
|
+
| `404` | MCP is not enabled (`mcp` option) or the URL is not `{basePath}/mcp`. |
|
|
532
|
+
| `405` with `Allow: POST` | The endpoint only accepts `POST` (MCP clients that probe with `GET` handle this). |
|
|
533
|
+
| A tool is missing from `tools/list`, or `-32602 Unknown tool` | The user lacks one of the tool's permissions (`commands:execute` plus the command's `requiredPermissions`, `files:read`/`files:write`, `data:query`, `jobs:read`/`jobs:manage`); the token's scope narrows them further; `mcp.tools` / `mcp.exclude` filter it out; or the feature is not enabled on the server (file storage, data explorer sources, jobs plugin). A command whose parameters all live on its sub-commands appears only as `<command>_<sub>` tools. |
|
|
534
|
+
|
|
535
|
+
## Remote Shell Security
|
|
536
|
+
|
|
537
|
+
The PTY shell (`/ws/v1/qcli/shell`, `/ws/qcli/shell`) gives OS-level access to the server. It is **off** unless `enableShell: true`. When enabled, exactly one access mode applies:
|
|
538
|
+
|
|
539
|
+
| Mode (`shellAuth`) | When | Client must |
|
|
540
|
+
|---|---|---|
|
|
541
|
+
| `users` | the users plugin is mounted | connect with a WS ticket (`?ticket=`) of a session holding `shell:execute` |
|
|
542
|
+
| `token` | no users plugin, `shell.token` set | send `Authorization: Bearer <token>` (header, or the `_header_Authorization` query parameter) |
|
|
543
|
+
| `custom` | no users plugin, `shell.authorize` set | pass the host's `authorize(request, { url, remoteAddress, mode })` check |
|
|
544
|
+
| `anonymous` | no users plugin, `shell.allowAnonymous: true` | nothing (a startup WARNING is logged) |
|
|
545
|
+
| `locked` | none of the above | — every connection is closed with 4401 and a startup ERROR explains the options |
|
|
546
|
+
|
|
547
|
+
Precedence: users plugin mounted (even with `protectServer: false`) ⇒ `users`; otherwise `token`, then `custom`, then `anonymous`. Whatever the mode, **every configured check among `token` and `authorize` must pass** (so with both set, both are required and `shellAuth` reports `token`); `allowAnonymous` is ignored once either is set. Tokens are compared in constant time (SHA-256 of both, then `timingSafeEqual`) and never logged; an `authorize` that throws or rejects denies.
|
|
548
|
+
|
|
549
|
+
### With the users plugin
|
|
550
|
+
|
|
551
|
+
```typescript
|
|
552
|
+
const { mountPlugin, eventSocketManager } = createCliServer({ enableShell: true });
|
|
553
|
+
mountPlugin(new CliUsersBuilder().build((m) => eventSocketManager.broadcastMessage(m)));
|
|
554
|
+
// Grant shell:execute to a group (root/admin has '*'):
|
|
555
|
+
// PUT /api/v1/qcli/groups/ops { "permissions": ["commands:execute", "shell:execute"] }
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
An open shell is bound to the session that opened it: logout, idle/absolute session expiry, admin revoke (kick), user disabled or deleted, or losing `shell:execute` (checked once more right after the shell is registered and before it is spawned, so a logout racing the open is not missed; then immediately on session/user/group changes and every `sessionCheckIntervalMs`, default 30 s, max 60 s), or a password change becoming required closes it with **4401** `session ended` and kills its process tree. Users authenticated by an external auth adapter have no stored session: their shells are re-checked by calling the adapter again with the shell's original upgrade request (see the users plugin README). Re-checks never extend the session by themselves; stdin received since the previous check counts as session activity (at most one touch per 30 s), so an active shell user is not idle-logged-out. Opens and closes are audited as `shell_opened` / `shell_closed` (`userId`, `actorId`, `metadata: { mode: 'interactive' | 'command', sessionId, durationMs, reason, exitCode? }`), never with the command text or stdin. `reason` is one of `exited` (then `exitCode` is set), `closed`, `session ended`, `permission removed`, `shell timed out`, `server shutdown`, `error`.
|
|
559
|
+
|
|
560
|
+
### Without the users plugin (token mode)
|
|
561
|
+
|
|
562
|
+
```typescript
|
|
563
|
+
createCliServer({
|
|
564
|
+
enableShell: true,
|
|
565
|
+
shell: { token: process.env.QCLI_SHELL_TOKEN }, // long random secret
|
|
566
|
+
});
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
Browser clients cannot set WebSocket headers: configure the server in web-cli with `headers: { Authorization: 'Bearer <token>' }` and the client forwards it as `?_header_Authorization=Bearer%20<token>`. The standalone server (`src/server.ts`) enables token mode when `QCLI_SHELL_TOKEN` is set. Use HTTPS/WSS so the token is not sent in clear text.
|
|
570
|
+
|
|
571
|
+
For your own scheme (reverse-proxy identity, IP allow-list, …) use `authorize`:
|
|
572
|
+
|
|
573
|
+
```typescript
|
|
574
|
+
createCliServer({
|
|
575
|
+
enableShell: true,
|
|
576
|
+
shell: { authorize: async (req, { remoteAddress }) => remoteAddress === '127.0.0.1' && req.headers['x-ops'] === 'yes' },
|
|
577
|
+
});
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
Non-users modes log one info line per open/close (remote address, mode, reason) without the command text.
|
|
581
|
+
|
|
582
|
+
### Options
|
|
583
|
+
|
|
584
|
+
```typescript
|
|
585
|
+
shell: {
|
|
586
|
+
token?: string; // token mode (or extra requirement with the users plugin)
|
|
587
|
+
authorize?: (req, ctx) => boolean | Promise<boolean>; // custom mode (or extra requirement)
|
|
588
|
+
allowAnonymous?: boolean; // open to anyone who can reach the server (not recommended)
|
|
589
|
+
maxSessionsPerUser?: number; // per user (users mode) or per remote address (default 3; X-Forwarded-For only with trustProxy)
|
|
590
|
+
maxSessions?: number; // total concurrent shells (default 20)
|
|
591
|
+
idleTimeoutMs?: number; // close after no stdin (resize does not count; default 15 min, 0 = off)
|
|
592
|
+
maxDurationMs?: number; // absolute limit (default none)
|
|
593
|
+
sessionCheckIntervalMs?: number; // users-mode re-check (default 30 000, max 60 000)
|
|
594
|
+
cwd?: string; // working directory (default: home directory)
|
|
595
|
+
shell?: string; // shell binary (default: detected)
|
|
596
|
+
env?: { allow?: string[]; set?: Record<string, string> };
|
|
597
|
+
}
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
The shell does **not** inherit the server environment: it gets only `PATH`, `HOME`, `USER`, `LANG`, `TERM`, `COLUMNS`, `LINES`, `SHELL` (plus the variables Windows needs to start PowerShell), the names in `env.allow`, and `env.set`. Secrets such as database passwords stay out of the shell.
|
|
601
|
+
|
|
602
|
+
### Process cleanup
|
|
603
|
+
|
|
604
|
+
Whenever a shell ends (exit, client close, session ended or permission removed, timeout, server shutdown), the server first collects everything the shell started — its process group, its descendants by parent PID (including background jobs that interactive shells move to their own process groups, and `setsid`/`nohup` children), on Linux every process of its session, and every process tagged with the shell's marker (below) — and then sends SIGHUP, SIGTERM and, after a 1 s grace period, SIGKILL to all of them (re-collecting before the SIGKILL). The server's own process and process group are never targeted. Linux reads `/proc` for everything (no `ps` needed); macOS and other Unixes run `ps`. On Windows only the shell process is killed.
|
|
605
|
+
|
|
606
|
+
Every shell gets a unique marker in its environment, `QCLI_SHELL_ID=<random 128-bit hex>`, added after the `env.allow`/`env.set` filtering so configuration can neither override nor remove it. Children inherit it, so processes that were re-parented to init *and* left the shell's process group and session — e.g. `setsid sleep 100 &` from an interactive shell, double-forking daemons — are still found: on Linux by reading `/proc/<pid>/environ`, on macOS through `ps -A -E -ww` (which shows the environment of the server user's processes).
|
|
607
|
+
|
|
608
|
+
Limitations: a process escapes only if it deliberately unsets or changes `QCLI_SHELL_ID` before detaching (`env -u QCLI_SHELL_ID setsid …`), or runs as a different user whose environment the server cannot read (e.g. via `sudo`). On macOS the environment of Apple platform binaries (`/bin/sleep`, `/usr/bin/perl`, …) is hidden from `ps -E`, so such a binary that left the shell's tree and session is not found there either. Run the server in a container or under a service manager (cgroup) if every process must be contained.
|
|
609
|
+
|
|
610
|
+
### Close codes
|
|
611
|
+
|
|
612
|
+
Refusals are sent as *accept, then close* so browsers can read them (a foreign `Origin` is still rejected with HTTP 403):
|
|
613
|
+
|
|
614
|
+
| Code | Reason | When |
|
|
615
|
+
|---|---|---|
|
|
616
|
+
| 4401 | `login required` / `session ended` / `shell access is not configured` | missing ticket or (Bearer) token; session ended (logout, expiry, kick, user disabled/deleted, `shell:execute` removed, password change now required) — shell, events and logs sockets; shell locked |
|
|
617
|
+
| 4403 | `forbidden` | wrong token, `authorize` false or threw, no `shell:execute`, password change required |
|
|
618
|
+
| 4404 | `shell disabled` | `enableShell` is false |
|
|
619
|
+
| 4408 | `shell timed out` | idle timeout or `maxDurationMs` |
|
|
620
|
+
| 4429 | `too many shell sessions` | `maxSessionsPerUser` or `maxSessions` reached |
|
|
621
|
+
| 1001 | `server shutdown` | the server is shutting down |
|
|
311
622
|
|
|
312
623
|
## Processor Base Class Reference
|
|
313
624
|
|
|
@@ -322,6 +633,7 @@ The server exposes versioned endpoints:
|
|
|
322
633
|
| `processors` | `ICliCommandProcessor[]` | `undefined` | Sub-commands |
|
|
323
634
|
| `allowUnlistedCommands` | `boolean` | `undefined` | Accept sub-commands not in `processors` |
|
|
324
635
|
| `valueRequired` | `boolean` | `undefined` | Require a positional value |
|
|
636
|
+
| `acceptsValue` | `boolean` | `undefined` | Reads an optional positional value (`command.value`); MCP exposes it as an optional `value` argument |
|
|
325
637
|
| `version` | `string` | `'1.0.0'` | Processor version string |
|
|
326
638
|
| `author` | `ICliCommandAuthor` | default author | Author metadata (name, email) |
|
|
327
639
|
|
|
@@ -329,12 +641,26 @@ The server exposes versioned endpoints:
|
|
|
329
641
|
|
|
330
642
|
```typescript
|
|
331
643
|
interface CliServerOptions {
|
|
332
|
-
basePath?: string; // API base path (default: '/api/
|
|
333
|
-
cors?: boolean | cors.CorsOptions; // CORS config (default:
|
|
644
|
+
basePath?: string; // API base path (default: '/api/qcli')
|
|
645
|
+
cors?: boolean | cors.CorsOptions; // CORS config (default: only allowedOrigins; off when none)
|
|
646
|
+
allowedOrigins?: string[]; // CORS + WebSocket Origin allow-list ('http://host:*' = any port)
|
|
647
|
+
enableShell?: boolean; // Interactive shell WebSocket (default: false)
|
|
648
|
+
shell?: CliShellOptions; // Shell access mode, limits, timeouts, env (see Remote Shell Security)
|
|
649
|
+
trustProxy?: boolean | number; // Trusted proxy hops for X-Forwarded-* (true = 1; default false = none)
|
|
650
|
+
auth?: ICliServerAuth; // Protect the whole server (e.g. usersPlugin.serverAuth)
|
|
651
|
+
mcp?: boolean | CliMcpOptions; // MCP endpoint at {basePath}/mcp (default off, see MCP Endpoint)
|
|
334
652
|
configure?: (builder: CliBuilder) => void; // Processor registration
|
|
335
653
|
}
|
|
336
654
|
```
|
|
337
655
|
|
|
656
|
+
CORS is disabled unless `allowedOrigins` (or an explicit `cors` option) is given. The shell socket is off unless `enableShell: true`, and even then it is locked until an access mode applies (see [Remote Shell Security](#remote-shell-security)).
|
|
657
|
+
|
|
658
|
+
Same-origin requests (e.g. the bundled admin dashboard at `/qcli/admin`) are always allowed by the Origin, CORS and CSRF checks, even when `allowedOrigins` does not list the server's own origin (`X-Forwarded-Proto`/`X-Forwarded-Host` are honoured only with `trustProxy`, and read like `X-Forwarded-For`: the entry at index `len - hops`, the leftmost when the list is shorter, an empty entry ignored).
|
|
659
|
+
|
|
660
|
+
`trustProxy` is the number of trusted reverse-proxy hops in front of the server (`true` = 1, `false`/`0`/absent = none). With n hops the client IP (Express `req.ip`, the shell's per-address limits) is the `X-Forwarded-For` entry at index `len - n` of the comma-separated list, i.e. with one hop the rightmost entry, appended by your proxy; the leftmost entries are client-controlled and never used unless the list is shorter than n. Without the header (or when the selected entry is empty) the socket address is used. Set it to the exact number of proxies you run, otherwise clients can spoof their IP.
|
|
661
|
+
|
|
662
|
+
When the users plugin is mounted (`mountPlugin(usersPlugin)`) or passed as `auth`, every `/api/...` route and every WebSocket requires a users session (see [Users Plugin](#users-plugin)); without it the server behaves as before.
|
|
663
|
+
|
|
338
664
|
`createCliServer()` returns:
|
|
339
665
|
|
|
340
666
|
```typescript
|
|
@@ -342,7 +668,12 @@ interface CliServerOptions {
|
|
|
342
668
|
app: Express; // Configured Express app
|
|
343
669
|
registry: CliCommandRegistry; // Processor registry
|
|
344
670
|
builder: CliBuilder; // Registration builder
|
|
345
|
-
|
|
671
|
+
executor: CliCommandExecutorService; // Command executor
|
|
672
|
+
eventSocketManager: CliEventSocketManager; // WebSocket event manager
|
|
673
|
+
logSocketManager: CliLogSocketManager; // WebSocket log manager
|
|
674
|
+
mountPlugin(plugin: MountablePlugin): void; // Mount a plugin with its routes (installs plugin.serverAuth)
|
|
675
|
+
setAuth(auth?: ICliServerAuth): void; // Install/remove the server auth provider
|
|
676
|
+
mcp: CliServerMcp; // MCP endpoint: { enabled, path, addProvider(provider) }
|
|
346
677
|
}
|
|
347
678
|
```
|
|
348
679
|
|
|
@@ -391,19 +722,19 @@ import {
|
|
|
391
722
|
|
|
392
723
|
## File Storage
|
|
393
724
|
|
|
394
|
-
The server includes a pluggable file storage system exposed at `/api/
|
|
725
|
+
The server includes a pluggable file storage system exposed at `/api/qcli/fs/*`. Enable it with `setFileStorageProvider()` and choose a storage backend.
|
|
395
726
|
|
|
396
727
|
### Filesystem API Endpoints
|
|
397
728
|
|
|
398
729
|
| Method | Path | Description |
|
|
399
730
|
|--------|------|-------------|
|
|
400
|
-
| GET | `/api/
|
|
401
|
-
| GET | `/api/
|
|
402
|
-
| GET | `/api/
|
|
403
|
-
| GET | `/api/
|
|
404
|
-
| POST | `/api/
|
|
405
|
-
| POST | `/api/
|
|
406
|
-
| DELETE | `/api/
|
|
731
|
+
| GET | `/api/qcli/fs/ls?path=/` | List directory contents |
|
|
732
|
+
| GET | `/api/qcli/fs/cat?path=/file.txt` | Read file content |
|
|
733
|
+
| GET | `/api/qcli/fs/stat?path=/file.txt` | File/directory metadata |
|
|
734
|
+
| GET | `/api/qcli/fs/download?path=/file.txt` | Download file |
|
|
735
|
+
| POST | `/api/qcli/fs/upload` | Upload file (multipart) |
|
|
736
|
+
| POST | `/api/qcli/fs/mkdir` | Create directory |
|
|
737
|
+
| DELETE | `/api/qcli/fs/rm?path=/file.txt` | Delete file or directory |
|
|
407
738
|
|
|
408
739
|
### Storage Providers
|
|
409
740
|
|
|
@@ -615,6 +946,40 @@ All commands support `--region` (`-r`) for region override and `--output` (`-o`)
|
|
|
615
946
|
|
|
616
947
|
See [`plugins/aws/README.md`](plugins/aws/README.md) for the full command reference.
|
|
617
948
|
|
|
949
|
+
### Users Plugin
|
|
950
|
+
|
|
951
|
+
The users plugin (`@qodalis/cli-server-plugin-users`) adds REST endpoints under `/api/v1/qcli` for authentication, users, groups with permissions, sessions and an audit log, with swappable storage (in-memory by default). It is the server side of the `@qodalis/cli-users` browser plugin's *server mode*.
|
|
952
|
+
|
|
953
|
+
```typescript
|
|
954
|
+
import { CliUsersBuilder } from '@qodalis/cli-server-plugin-users';
|
|
955
|
+
|
|
956
|
+
const usersPlugin = new CliUsersBuilder()
|
|
957
|
+
.build((msg) => eventSocketManager.broadcastMessage(msg));
|
|
958
|
+
mountPlugin(usersPlugin); // protects the whole server (protectServer: true)
|
|
959
|
+
```
|
|
960
|
+
|
|
961
|
+
Once mounted, every server route and socket requires a users session (login, version discovery and capabilities stay public):
|
|
962
|
+
|
|
963
|
+
| Surface | Permission |
|
|
964
|
+
|---|---|
|
|
965
|
+
| `GET /commands` | authenticated |
|
|
966
|
+
| `POST /execute`, `/execute/stream` | `commands:execute` + the processor's `requiredPermissions` |
|
|
967
|
+
| `POST /api/qcli/mcp` | `commands:execute` (each tool then checks its own permissions) |
|
|
968
|
+
| fs `ls`/`cat`/`stat`/`download` | `files:read` |
|
|
969
|
+
| fs `upload`/`mkdir`/`rm` | `files:write` |
|
|
970
|
+
| data explorer | `data:query` |
|
|
971
|
+
| jobs read / manage | `jobs:read` / `jobs:manage` |
|
|
972
|
+
| events / logs / shell WebSocket | authenticated / `logs:read` / `shell:execute` (see [Remote Shell Security](#remote-shell-security)) |
|
|
973
|
+
| admin dashboard | `admin:access` |
|
|
974
|
+
|
|
975
|
+
WebSockets authenticate with a single-use ticket: `POST /api/v1/qcli/sessions/ws-ticket` → `{ ticket, expiresIn: 30 }`, then connect with `?ticket=<ticket>`. Host routes can use `usersPlugin.requireAuth()` / `usersPlugin.requirePermission('perm')`. Processors declare `requiredPermissions: ['deploy:run']` to be enforced server-side.
|
|
976
|
+
|
|
977
|
+
Events and logs sockets authenticated with a stored session are bound to it: when the session ends (logout, revoke/kick, user disabled or deleted, a password change becoming required, idle/absolute expiry) the socket is closed with **4401** `session ended`; for a revoke the events socket first receives `session-kicked`. Bound sockets are re-checked on every session/user/group change and after each expired-session sweep (`setCleanupInterval`, default 5 minutes). Sockets authenticated by an external auth adapter have no stored session and are not re-checked.
|
|
978
|
+
|
|
979
|
+
API tokens (`qcli_pat_…`) are long-lived, revocable bearer tokens for MCP clients, CI and scripts; they are accepted wherever a session token is (see [API tokens](plugins/users/README.md#api-tokens)). Sockets opened with a token identity close with 4401 when the token is revoked or expires, or the user is disabled.
|
|
980
|
+
|
|
981
|
+
See [`plugins/users/README.md`](plugins/users/README.md) for options, endpoints, API tokens, custom storage and external auth adapters.
|
|
982
|
+
|
|
618
983
|
## Built-in Processors
|
|
619
984
|
|
|
620
985
|
These processors ship with the library and are included in the standalone server:
|
|
@@ -627,7 +992,6 @@ These processors ship with the library and are included in the standalone server
|
|
|
627
992
|
| `http` | HTTP request operations |
|
|
628
993
|
| `hash` | Hash computation (MD5, SHA1, SHA256, SHA512) |
|
|
629
994
|
| `base64` | Base64 encode/decode (sub-commands) |
|
|
630
|
-
| `uuid` | UUID generation |
|
|
631
995
|
|
|
632
996
|
## Docker
|
|
633
997
|
|
|
@@ -663,10 +1027,22 @@ packages/
|
|
|
663
1027
|
cli-command-parameter-descriptor.ts # Parameter declaration
|
|
664
1028
|
cli-command-author.ts # Author metadata
|
|
665
1029
|
plugins/
|
|
1030
|
+
admin/ # Admin dashboard plugin (auth, status, config, logs)
|
|
1031
|
+
aws/ # AWS cloud services (S3, EC2, Lambda, CloudWatch, etc.)
|
|
1032
|
+
data-explorer/ # Core data explorer abstraction
|
|
1033
|
+
data-explorer-sql/ # SQL data explorer provider (SQLite)
|
|
1034
|
+
data-explorer-mongo/ # MongoDB data explorer provider
|
|
1035
|
+
data-explorer-postgres/ # PostgreSQL data explorer provider
|
|
1036
|
+
data-explorer-mysql/ # MySQL data explorer provider
|
|
1037
|
+
data-explorer-mssql/ # MS SQL data explorer provider
|
|
1038
|
+
data-explorer-redis/ # Redis data explorer provider
|
|
1039
|
+
data-explorer-elasticsearch/ # Elasticsearch data explorer provider
|
|
666
1040
|
filesystem/ # Core file storage abstraction (IFileStorageProvider, InMemory, OS)
|
|
667
1041
|
filesystem-json/ # JSON file persistence provider
|
|
668
1042
|
filesystem-sqlite/ # SQLite persistence provider (better-sqlite3)
|
|
669
1043
|
filesystem-s3/ # Amazon S3 storage provider (@aws-sdk/client-s3)
|
|
1044
|
+
jobs/ # Job scheduling plugin
|
|
1045
|
+
users/ # Users plugin (auth, sessions, groups, audit)
|
|
670
1046
|
weather/ # Weather module (example plugin)
|
|
671
1047
|
src/
|
|
672
1048
|
abstractions/ # Re-exports from @qodalis/cli-server-abstractions
|
|
@@ -679,9 +1055,11 @@ src/
|
|
|
679
1055
|
cli-command-executor-service.ts # Command execution pipeline
|
|
680
1056
|
cli-response-builder.ts # Structured output builder
|
|
681
1057
|
cli-event-socket-manager.ts # WebSocket event broadcasting
|
|
1058
|
+
cli-log-socket-manager.ts # WebSocket log streaming
|
|
1059
|
+
cli-shell-session-manager.ts # PTY shell session management
|
|
682
1060
|
controllers/
|
|
683
|
-
cli-controller.ts # V1 REST API (/api/v1/
|
|
684
|
-
cli-version-controller.ts # Version discovery (/api/
|
|
1061
|
+
cli-controller.ts # V1 REST API (/api/v1/qcli)
|
|
1062
|
+
cli-version-controller.ts # Version discovery (/api/qcli/versions)
|
|
685
1063
|
extensions/
|
|
686
1064
|
cli-builder.ts # Fluent registration API (addProcessor, addModule)
|
|
687
1065
|
processors/ # Built-in processors
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@qodalis/cli-server-node",
|
|
3
|
+
"version": "1.0.0-beta.7",
|
|
4
|
+
"description": "Node.js CLI server for Qodalis CLI ecosystem — use as a library in existing Express apps or run standalone",
|
|
5
|
+
"main": "dist/src/index.js",
|
|
6
|
+
"types": "dist/src/index.d.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"qodalis-cli-server": "dist/src/server.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"!dist/src/__tests__",
|
|
13
|
+
"!dist/plugins",
|
|
14
|
+
"LICENSE",
|
|
15
|
+
"README.md"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsc",
|
|
19
|
+
"start": "tsx src/server.ts",
|
|
20
|
+
"dev": "tsx watch src/server.ts",
|
|
21
|
+
"test": "vitest run",
|
|
22
|
+
"test:watch": "vitest"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"cli",
|
|
26
|
+
"qodalis",
|
|
27
|
+
"terminal",
|
|
28
|
+
"commands",
|
|
29
|
+
"cli-server",
|
|
30
|
+
"command-processor"
|
|
31
|
+
],
|
|
32
|
+
"author": "Nicolae Lupei <nicolae.lupei@qodalis.com>",
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "https://github.com/qodalis-solutions/cli-server-node"
|
|
37
|
+
},
|
|
38
|
+
"homepage": "https://qodalis.com/",
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@qodalis/cli-server-abstractions": "file:packages/abstractions",
|
|
41
|
+
"@qodalis/cli-server-plugin-data-explorer": "file:plugins/data-explorer",
|
|
42
|
+
"@qodalis/cli-server-plugin-filesystem": "file:plugins/filesystem",
|
|
43
|
+
"@qodalis/cli-server-plugin-jobs": "file:plugins/jobs",
|
|
44
|
+
"cors": "^2.8.5",
|
|
45
|
+
"express": "^4.21.0",
|
|
46
|
+
"multer": "^2.1.0",
|
|
47
|
+
"node-pty": "^1.1.0",
|
|
48
|
+
"ws": "^8.18.0"
|
|
49
|
+
},
|
|
50
|
+
"optionalDependencies": {
|
|
51
|
+
"@elastic/elasticsearch": "^8.16.0",
|
|
52
|
+
"@qodalis/cli-server-plugin-filesystem-json": "file:plugins/filesystem-json",
|
|
53
|
+
"@qodalis/cli-server-plugin-filesystem-s3": "file:plugins/filesystem-s3",
|
|
54
|
+
"@qodalis/cli-server-plugin-filesystem-sqlite": "file:plugins/filesystem-sqlite",
|
|
55
|
+
"better-sqlite3": "^11.10.0",
|
|
56
|
+
"ioredis": "^5.4.0",
|
|
57
|
+
"mongodb": "^6.0.0",
|
|
58
|
+
"mssql": "^11.0.0",
|
|
59
|
+
"mysql2": "^3.11.0",
|
|
60
|
+
"pg": "^8.13.0"
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
64
|
+
"@types/cors": "^2.8.17",
|
|
65
|
+
"@types/express": "^4.17.21",
|
|
66
|
+
"@types/mssql": "^9.1.9",
|
|
67
|
+
"@types/multer": "^2.0.0",
|
|
68
|
+
"@types/node": "^22.0.0",
|
|
69
|
+
"@types/pg": "^8.0.0",
|
|
70
|
+
"@types/supertest": "^7.2.0",
|
|
71
|
+
"@types/ws": "^8.5.13",
|
|
72
|
+
"supertest": "^7.2.2",
|
|
73
|
+
"tsx": "^4.19.0",
|
|
74
|
+
"typescript": "^5.6.0",
|
|
75
|
+
"vitest": "^4.0.18"
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { RequestHandler } from 'express';
|
|
2
|
+
import { ICliCommandRegistry } from '../services/cli-command-registry';
|
|
3
|
+
import { CliServerAuthContext, ICliServerAuth } from './cli-server-auth';
|
|
4
|
+
declare global {
|
|
5
|
+
namespace Express {
|
|
6
|
+
interface Request {
|
|
7
|
+
/** Set by the server auth gate when the users plugin protects the server. */
|
|
8
|
+
qcliServerAuth?: CliServerAuthContext;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/** Normalizes a request path for classification: lower-case, single slashes, no trailing slash. */
|
|
13
|
+
export declare function normalizePath(path: string): string;
|
|
14
|
+
export interface ServerAuthGateOptions {
|
|
15
|
+
registry: ICliCommandRegistry;
|
|
16
|
+
/** Prefixes serving the CLI controller (`/api/v1/qcli`, custom basePath). */
|
|
17
|
+
cliPrefixes: string[];
|
|
18
|
+
/** Extra public routes as `"METHOD /path"` (e.g. a plugin's own login). */
|
|
19
|
+
publicRoutes: Set<string>;
|
|
20
|
+
/** MCP endpoint paths (normalized), requiring `commands:execute`. */
|
|
21
|
+
mcpPaths?: string[];
|
|
22
|
+
}
|
|
23
|
+
/** Collects `requiredPermissions` along the resolved processor chain. */
|
|
24
|
+
export declare function processorPermissions(registry: ICliCommandRegistry, command: unknown, chain: unknown): string[];
|
|
25
|
+
/** Permissions required for a (protected) server route. */
|
|
26
|
+
export declare function requiredServerPermissions(method: string, path: string, body: any, options: ServerAuthGateOptions): string[];
|
|
27
|
+
/**
|
|
28
|
+
* Express middleware protecting every `/api/...` route of the CLI server when
|
|
29
|
+
* an auth provider with `protectServer` is installed. Without a provider it is
|
|
30
|
+
* a no-op, so servers without the users plugin behave as before.
|
|
31
|
+
*/
|
|
32
|
+
export declare function createServerAuthGate(getAuth: () => ICliServerAuth | undefined, options: ServerAuthGateOptions): RequestHandler;
|
|
33
|
+
//# sourceMappingURL=cli-server-auth-gate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli-server-auth-gate.d.ts","sourceRoot":"","sources":["../../../src/auth/cli-server-auth-gate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmC,cAAc,EAAE,MAAM,SAAS,CAAC;AAE1E,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AACvE,OAAO,EAAE,oBAAoB,EAA6B,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAKpG,OAAO,CAAC,MAAM,CAAC;IACX,UAAU,OAAO,CAAC;QACd,UAAU,OAAO;YACb,6EAA6E;YAC7E,cAAc,CAAC,EAAE,oBAAoB,CAAC;SACzC;KACJ;CACJ;AAED,mGAAmG;AACnG,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGlD;AAgBD,MAAM,WAAW,qBAAqB;IAClC,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,6EAA6E;IAC7E,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,2EAA2E;IAC3E,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC1B,qEAAqE;IACrE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,yEAAyE;AACzE,wBAAgB,oBAAoB,CAChC,QAAQ,EAAE,mBAAmB,EAC7B,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,OAAO,GACf,MAAM,EAAE,CAkBV;AAED,2DAA2D;AAC3D,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,qBAAqB,GAAG,MAAM,EAAE,CAoB3H;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAChC,OAAO,EAAE,MAAM,cAAc,GAAG,SAAS,EACzC,OAAO,EAAE,qBAAqB,GAC/B,cAAc,CAkDhB"}
|