@klhapp/skillmux 1.9.3 → 1.10.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.
- package/CHANGELOG.md +15 -0
- package/README.md +1 -1
- package/docs/README.md +1 -1
- package/docs/cli.md +74 -3
- package/docs/concepts.md +1 -1
- package/docs/configuration.md +1 -1
- package/docs/deployment.md +10 -6
- package/docs/getting-started.md +1 -1
- package/docs/skill-management.md +6 -0
- package/package.json +1 -1
- package/src/adapters.ts +148 -2
- package/src/cli.ts +266 -1299
- package/src/commands/audit.ts +53 -56
- package/src/commands/config.ts +11 -12
- package/src/commands/context.ts +103 -0
- package/src/commands/core.ts +5 -1
- package/src/commands/doctor.ts +76 -0
- package/src/commands/eval.ts +10 -13
- package/src/commands/init.ts +621 -0
- package/src/commands/install.ts +132 -0
- package/src/commands/local-vault.ts +60 -0
- package/src/commands/models.ts +10 -0
- package/src/commands/outdated.ts +2 -1
- package/src/commands/project.ts +37 -11
- package/src/commands/report.ts +66 -0
- package/src/commands/scan.ts +61 -0
- package/src/commands/skill.ts +32 -0
- package/src/commands/sync.ts +232 -0
- package/src/commands/target.ts +18 -6
- package/src/commands/update.ts +2 -1
- package/src/config-service.ts +1 -51
- package/src/context.ts +8 -3
- package/src/db-audit.ts +286 -0
- package/src/db-index.ts +238 -0
- package/src/db.ts +3 -521
- package/src/global-flags.ts +46 -0
- package/src/logger.ts +26 -0
- package/src/output.ts +30 -5
- package/src/router-core.ts +8 -27
- package/src/server.ts +160 -13
- package/src/toml-writer.ts +51 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,21 @@ All notable changes to this project are documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.10.0](https://github.com/klhq/skillmux/compare/v1.9.3...v1.10.0) (2026-09-01)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
* **cli:** reject remote context for local-only commands ([#165](https://github.com/klhq/skillmux/issues/165)) ([934bb18](https://github.com/klhq/skillmux/commit/934bb18bce2b86dca8ac44cd8431ebdb53d12024))
|
|
14
|
+
* **cli:** remote admin parity for report, audit prune, eval, doctor ([#166](https://github.com/klhq/skillmux/issues/166)) ([615a396](https://github.com/klhq/skillmux/commit/615a396212d5c0e2df07712bf588c25e91e99773))
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
* **cli:** align context terminology ([#163](https://github.com/klhq/skillmux/issues/163)) ([e942075](https://github.com/klhq/skillmux/commit/e94207584d10e16c17d6d2f5ca9c23bd8aa7846f))
|
|
20
|
+
* **server:** consolidate redactedErrorLog into logger.ts ([#169](https://github.com/klhq/skillmux/issues/169)) ([f7f29e4](https://github.com/klhq/skillmux/commit/f7f29e4ed6ea516e243d47d677e7ede207e47937))
|
|
21
|
+
* split db.ts, extract cli.ts commands, consolidate flag parsing ([#168](https://github.com/klhq/skillmux/issues/168)) ([28a798a](https://github.com/klhq/skillmux/commit/28a798abacfb830aff7a6510c6e892bb28009e5c))
|
|
22
|
+
|
|
8
23
|
## [1.9.3](https://github.com/klhq/skillmux/compare/v1.9.2...v1.9.3) (2026-08-31)
|
|
9
24
|
|
|
10
25
|
|
package/README.md
CHANGED
|
@@ -189,7 +189,7 @@ for all three setups.
|
|
|
189
189
|
| Surface | User | Purpose | CLI required |
|
|
190
190
|
| --- | --- | --- | --- |
|
|
191
191
|
| `/mcp` | AI clients | Resolve and fetch skills | No |
|
|
192
|
-
| `/admin/v1/*` | Operators | Inspect
|
|
192
|
+
| `/admin/v1/*` (and `GET /stats`) | Operators | Inspect/update config, stats, audit prune, evaluation, and remote diagnostics | Yes, when using named CLI contexts |
|
|
193
193
|
|
|
194
194
|
MCP clients authenticate only to `/mcp` with the MCP bearer token. Operators
|
|
195
195
|
use a separate administrative bearer token for `/admin/v1/*`; neither token
|
package/docs/README.md
CHANGED
|
@@ -34,7 +34,7 @@ to customize behavior; see [Configuration](configuration.md#machine-config-boots
|
|
|
34
34
|
| Surface | User | Purpose | CLI required |
|
|
35
35
|
| --- | --- | --- | --- |
|
|
36
36
|
| `/mcp` | AI clients | Resolve and fetch skills | No |
|
|
37
|
-
| `/admin/v1/*` | Operators | Inspect
|
|
37
|
+
| `/admin/v1/*` (and `GET /stats`) | Operators | Inspect/update config, stats, audit prune, evaluation, and remote diagnostics | Yes, when using named CLI contexts |
|
|
38
38
|
|
|
39
39
|
The MCP and administrative surfaces use separate bearer tokens; possession of
|
|
40
40
|
one does not grant access to the other. A named CLI context administers the
|
package/docs/cli.md
CHANGED
|
@@ -14,6 +14,9 @@ In this guide, **local target** means the filesystem and process selected by
|
|
|
14
14
|
the built-in CLI context. It does not describe local inference. A local target
|
|
15
15
|
can call remote inference endpoints.
|
|
16
16
|
|
|
17
|
+
Run `skillmux --help` for the full command list, or `skillmux <command> --help`
|
|
18
|
+
(`-h` also works) for one command's usage and flags.
|
|
19
|
+
|
|
17
20
|
For task-oriented workflows, start with [Getting started](getting-started.md)
|
|
18
21
|
or [Managing skills](skill-management.md).
|
|
19
22
|
|
|
@@ -25,13 +28,31 @@ CLI. Its `skillmux --help` surface is intentionally limited to `serve`,
|
|
|
25
28
|
`skill which`, and read-only `config` inspection (`show`, `get`, `validate`,
|
|
26
29
|
`diff`, and `status`). Run `init`, `install`, pinning, `sync`, project or
|
|
27
30
|
target management, model downloads, contexts, and bare `eval` (vault ranking
|
|
28
|
-
evaluation, which needs
|
|
31
|
+
evaluation, which needs an embeddings client and the vault) on the host.
|
|
29
32
|
|
|
30
33
|
When the image rejects one of those commands, it exits with code 2. JSON mode
|
|
31
34
|
uses `CONTAINER_COMMAND_UNSUPPORTED` and includes `rejected_command`,
|
|
32
35
|
`recommended_host_command`, and a deployment-guide URL. See [the container
|
|
33
36
|
command contract](deployment.md#container-command-contract) for examples.
|
|
34
37
|
|
|
38
|
+
## Local-only commands and remote context rejection
|
|
39
|
+
|
|
40
|
+
Commands that operate directly on the local vault checkout reject `--context` and
|
|
41
|
+
`--server` (or a configured remote default context) with exit code 2. These 14
|
|
42
|
+
commands are local-only: `install`, `update`, `outdated`, `sync`, `core`,
|
|
43
|
+
`project`, `target`, `local-vault`, `index`, `models`, `scan`, `init`, `serve`,
|
|
44
|
+
and `skill which`.
|
|
45
|
+
|
|
46
|
+
When one of these commands receives a remote context, it exits with code 2.
|
|
47
|
+
Human-mode output reports:
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
error: `<command>` operates on the local vault only; --context/--server isn't supported here
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
In `--json` mode, the CLI emits a structured error envelope with `code: "REMOTE_CONTEXT_UNSUPPORTED"`
|
|
54
|
+
and includes `rejected_command`.
|
|
55
|
+
|
|
35
56
|
## Global options and target resolution
|
|
36
57
|
|
|
37
58
|
Every target-aware command resolves its execution target deterministically in this order:
|
|
@@ -52,6 +73,27 @@ Every target-aware command resolves its execution target deterministically in th
|
|
|
52
73
|
| `--allow-insecure` | Allow plaintext HTTP admin requests to non-loopback addresses |
|
|
53
74
|
| `--verbose` | Output diagnostic stack traces for errors |
|
|
54
75
|
|
|
76
|
+
`--context`/`--server` selects which Skillmux admin instance a command talks to
|
|
77
|
+
over `/admin/v1/*` — nothing more. That's one of three independent axes in this
|
|
78
|
+
system:
|
|
79
|
+
|
|
80
|
+
1. Which vault checkout backs Core/Project pinning — always a local Git checkout
|
|
81
|
+
on the machine running the CLI. `install`/`update`/`sync`/`core`/`project`/
|
|
82
|
+
`target`/`local-vault`/`index`/`models`/`scan`/`init`/`outdated`/`serve`/
|
|
83
|
+
`skill which` operate on it. There is no remote version of this — Git and the
|
|
84
|
+
deployment process move content between checkouts, not Skillmux's own
|
|
85
|
+
commands.
|
|
86
|
+
2. Which MCP server an agent queries for Routed retrieval — the agent's own MCP
|
|
87
|
+
client configuration (local stdio vs. remote HTTP), entirely separate from
|
|
88
|
+
`skillmux context`.
|
|
89
|
+
3. Which Skillmux instance the CLI's admin commands act on — `config`, `report`,
|
|
90
|
+
`audit prune`, `eval`/`eval promote`, and `doctor`. This is the only thing
|
|
91
|
+
`--context`/`--server` actually selects.
|
|
92
|
+
|
|
93
|
+
The commands above belong entirely to axis 1. `--context` has no meaning for
|
|
94
|
+
them — not "risky," a category error, the same way `--context prod` wouldn't
|
|
95
|
+
mean anything on `ls`.
|
|
96
|
+
|
|
55
97
|
---
|
|
56
98
|
|
|
57
99
|
## Context management (`skillmux context`)
|
|
@@ -286,6 +328,11 @@ Show which root actually serves a skill_id, and every root it shadows:
|
|
|
286
328
|
skillmux skill which csv-formatter
|
|
287
329
|
```
|
|
288
330
|
|
|
331
|
+
> [!NOTE]
|
|
332
|
+
> `skill which` performs local vault-checkout shadow resolution (which root
|
|
333
|
+
> wins on the local filesystem) and is unrelated to semantic MCP skill routing
|
|
334
|
+
> (`resolve_skill`/`fetch_skill`).
|
|
335
|
+
|
|
289
336
|
---
|
|
290
337
|
|
|
291
338
|
## Observability and evaluation (`skillmux report`, `audit`, `eval`)
|
|
@@ -352,10 +399,10 @@ The HTTP server has two separate surfaces:
|
|
|
352
399
|
| Surface | User | Purpose | CLI required |
|
|
353
400
|
| --- | --- | --- | --- |
|
|
354
401
|
| `/mcp` | AI clients | Resolve and fetch skills | No |
|
|
355
|
-
| `/admin/v1/*` | Operators | Inspect
|
|
402
|
+
| `/admin/v1/*` (and `GET /stats`) | Operators | Inspect/update config, stats, audit prune, evaluation, and remote diagnostics | Yes, when using named CLI contexts |
|
|
356
403
|
|
|
357
404
|
The MCP bearer token applies only to `/mcp`. The administrative bearer token
|
|
358
|
-
below applies only to `/admin/v1
|
|
405
|
+
below applies only to `/admin/v1/*` (and `GET /stats`); neither credential grants access to the
|
|
359
406
|
other surface. Named contexts use the latter to administer the deployed server,
|
|
360
407
|
not any remote client skill directory.
|
|
361
408
|
|
|
@@ -374,6 +421,30 @@ Requests require `Authorization: Bearer <token>` where `<token>` matches the env
|
|
|
374
421
|
| `/admin/v1/capabilities` | `GET` | Advertises server features (`config_read`, `config_write`, `persistence`) |
|
|
375
422
|
| `/admin/v1/config` | `GET` | Returns desired/effective config, sources, and `ETag` revision hash |
|
|
376
423
|
| `/admin/v1/config` | `PATCH` | Applies dotted-key updates; requires matching `If-Match` header |
|
|
424
|
+
| `/admin/v1/audit/prune` | `POST` | Prunes audit/fetch/admin_audit rows older than cutoff; requires `confirm: true` unless `dry_run: true` |
|
|
425
|
+
| `/admin/v1/eval` | `POST` | Runs vault ranking evaluation server-side and returns `EvalReport` |
|
|
426
|
+
| `/admin/v1/eval/promote` | `POST` | Returns candidate promoted eval cases from the server's audit database; requires `since` |
|
|
427
|
+
|
|
428
|
+
### Remote command capabilities
|
|
429
|
+
|
|
430
|
+
Named CLI contexts (`--context <name>` or `--server <url>`) support the following administrative and diagnostic operations:
|
|
431
|
+
|
|
432
|
+
- `skillmux config` (`show`, `get`, `set`, `validate`, `diff`, `status`): inspect and modify remote server configuration over `/admin/v1/*`.
|
|
433
|
+
- `skillmux report --since <window>`: fetches usage and retrieval metrics from the remote server's `GET /stats`.
|
|
434
|
+
- `skillmux audit prune [--older-than <window>] [--dry-run] [--yes]`: prunes or dry-run counts audit records on the remote server via `POST /admin/v1/audit/prune`.
|
|
435
|
+
- `skillmux eval`: executes ranking evaluation against the remote server's in-process runtime via `POST /admin/v1/eval`.
|
|
436
|
+
- `skillmux eval promote --since <window>`: fetches promotable candidates from the remote server's audit db via `POST /admin/v1/eval/promote`, dedups against the local fixture file, and writes locally.
|
|
437
|
+
- `skillmux doctor`: inspects remote server status, readiness, deployment runtime, and capabilities without requiring local vault access.
|
|
438
|
+
|
|
439
|
+
---
|
|
440
|
+
|
|
441
|
+
## Color
|
|
442
|
+
|
|
443
|
+
Interactive terminal output uses a small amount of color: red for error
|
|
444
|
+
messages, yellow for `warning:` lines, green/red for `doctor`'s `ok`/`fail`
|
|
445
|
+
checks, and bold table headers. Color is automatic — it's on only when
|
|
446
|
+
stdout is a TTY and off whenever `NO_COLOR` is set or output is piped or
|
|
447
|
+
redirected. `--json` output never includes color codes.
|
|
377
448
|
|
|
378
449
|
---
|
|
379
450
|
|
package/docs/concepts.md
CHANGED
|
@@ -94,7 +94,7 @@ An HTTP server has two separate surfaces:
|
|
|
94
94
|
| Surface | User | Purpose | CLI required |
|
|
95
95
|
| --- | --- | --- | --- |
|
|
96
96
|
| `/mcp` | AI clients | Resolve and fetch skills | No |
|
|
97
|
-
| `/admin/v1/*` | Operators | Inspect
|
|
97
|
+
| `/admin/v1/*` (and `GET /stats`) | Operators | Inspect/update config, stats, audit prune, evaluation, and remote diagnostics | Yes, when using named CLI contexts |
|
|
98
98
|
|
|
99
99
|
MCP authentication protects `/mcp`; administrative authentication protects
|
|
100
100
|
`/admin/v1/*`. Their bearer tokens are distinct and do not grant access across
|
package/docs/configuration.md
CHANGED
|
@@ -184,7 +184,7 @@ The HTTP server provides separate MCP and administrative surfaces:
|
|
|
184
184
|
| Surface | User | Purpose | CLI required |
|
|
185
185
|
| --- | --- | --- | --- |
|
|
186
186
|
| `/mcp` | AI clients | Resolve and fetch skills | No |
|
|
187
|
-
| `/admin/v1/*` | Operators | Inspect
|
|
187
|
+
| `/admin/v1/*` (and `GET /stats`) | Operators | Inspect/update config, stats, audit prune, evaluation, and remote diagnostics | Yes, when using named CLI contexts |
|
|
188
188
|
|
|
189
189
|
```toml
|
|
190
190
|
[server]
|
package/docs/deployment.md
CHANGED
|
@@ -180,7 +180,7 @@ The shared server exposes two distinct HTTP surfaces:
|
|
|
180
180
|
| Surface | User | Purpose | CLI required |
|
|
181
181
|
| --- | --- | --- | --- |
|
|
182
182
|
| `/mcp` | AI clients | Resolve and fetch skills | No |
|
|
183
|
-
| `/admin/v1/*` | Operators | Inspect
|
|
183
|
+
| `/admin/v1/*` (and `GET /stats`) | Operators | Inspect/update config, stats, audit prune, evaluation, and remote diagnostics | Yes, when using named CLI contexts |
|
|
184
184
|
|
|
185
185
|
Configure and distribute separate bearer tokens. An MCP token authenticates an
|
|
186
186
|
AI client to `/mcp` only; an administrative token authenticates an operator to
|
|
@@ -280,6 +280,9 @@ skillmux context add prod \
|
|
|
280
280
|
--token-env SKILLMUX_PROD_ADMIN_TOKEN
|
|
281
281
|
skillmux context use prod
|
|
282
282
|
skillmux config status
|
|
283
|
+
skillmux --context prod report --since 7d
|
|
284
|
+
skillmux --context prod audit prune --dry-run
|
|
285
|
+
skillmux --context prod doctor
|
|
283
286
|
```
|
|
284
287
|
|
|
285
288
|
The context stores the token environment variable name. Export its value in
|
|
@@ -291,11 +294,12 @@ Enable the admin API and use a separate admin token in server configuration.
|
|
|
291
294
|
Read [CLI reference](cli.md#administrative-http-api-adminv1) for routes and
|
|
292
295
|
[Configuration](configuration.md#http-server) for reload behavior.
|
|
293
296
|
|
|
294
|
-
Named contexts administer the deployed server
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
297
|
+
Named contexts administer the deployed server only — configuration, stats,
|
|
298
|
+
audit prune, evaluation, and remote diagnostics. They do not install, pin,
|
|
299
|
+
synchronize, or otherwise manage skill directories on remote client machines.
|
|
300
|
+
Run those filesystem-management commands through Skillmux CLI on the machine
|
|
301
|
+
that owns the directories. The full and slim server images read their mounted
|
|
302
|
+
vault checkout and do not manage host agent directories.
|
|
299
303
|
|
|
300
304
|
## Persistent data and backups
|
|
301
305
|
|
package/docs/getting-started.md
CHANGED
|
@@ -261,7 +261,7 @@ The server keeps its two HTTP surfaces separate:
|
|
|
261
261
|
| Surface | User | Purpose | CLI required |
|
|
262
262
|
| --- | --- | --- | --- |
|
|
263
263
|
| `/mcp` | AI clients | Resolve and fetch skills | No |
|
|
264
|
-
| `/admin/v1/*` | Operators | Inspect
|
|
264
|
+
| `/admin/v1/*` (and `GET /stats`) | Operators | Inspect/update config, stats, audit prune, evaluation, and remote diagnostics | Yes, when using named CLI contexts |
|
|
265
265
|
|
|
266
266
|
Configure separate MCP and administrative bearer tokens; one never grants
|
|
267
267
|
access to the other. Named CLI contexts can administer this deployed server,
|
package/docs/skill-management.md
CHANGED
|
@@ -267,6 +267,12 @@ true` with a token, or it refuses to start. `--stats-port` is rejected
|
|
|
267
267
|
alongside `--transport http`, since that transport already serves `/stats` on
|
|
268
268
|
`--port`.
|
|
269
269
|
|
|
270
|
+
`--context`/`--server` work the same way for `audit prune`, `eval`, `eval
|
|
271
|
+
promote`, and `doctor` — each hits the matching `/admin/v1/*` route on the
|
|
272
|
+
named deployment instead of the local vault and audit db. See [CLI
|
|
273
|
+
reference](cli.md#administrative-http-api-adminv1) for the full remote
|
|
274
|
+
command surface and route table.
|
|
275
|
+
|
|
270
276
|
## Target ownership and recovery
|
|
271
277
|
|
|
272
278
|
`skillmux target remove <name> --yes` removes the manifest record and preserves
|
package/package.json
CHANGED
package/src/adapters.ts
CHANGED
|
@@ -14,8 +14,11 @@ import {
|
|
|
14
14
|
type ConfigStatusResponse,
|
|
15
15
|
type SetConfigResult,
|
|
16
16
|
} from "./config-service";
|
|
17
|
-
import type {
|
|
17
|
+
import type { ResolvedContext } from "./context";
|
|
18
18
|
import type { Clients, Config } from "./types";
|
|
19
|
+
import { getStats, parseSince, type StatsResponse } from "./stats";
|
|
20
|
+
import { countPrunable, openAudit, pruneAuditBefore, type PruneResult } from "./db";
|
|
21
|
+
import { buildPromotedCases, evalVault, queryPromotableFetches, type EvalCase, type EvalReport } from "./eval";
|
|
19
22
|
|
|
20
23
|
export interface Capabilities {
|
|
21
24
|
config_read: boolean;
|
|
@@ -31,6 +34,21 @@ export interface TargetAdapterOptions {
|
|
|
31
34
|
clients?: Clients;
|
|
32
35
|
}
|
|
33
36
|
|
|
37
|
+
export interface AuditPruneOptions {
|
|
38
|
+
older_than?: string;
|
|
39
|
+
dry_run?: boolean;
|
|
40
|
+
confirm?: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface AuditPruneResult extends PruneResult {
|
|
44
|
+
dry_run: boolean;
|
|
45
|
+
cutoff: string | null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Target adapter: `local` = this CLI process has the Skillmux runtime (vault, index,
|
|
50
|
+
* audit db, embeddings/reranker clients) in-process; `remote` = thin network client to an external process.
|
|
51
|
+
*/
|
|
34
52
|
export interface TargetAdapter {
|
|
35
53
|
getCapabilities(): Promise<Capabilities>;
|
|
36
54
|
getConfigShow(): Promise<{ effective: Config; sources: Record<string, string>; active_revision: string }>;
|
|
@@ -39,6 +57,11 @@ export interface TargetAdapter {
|
|
|
39
57
|
configDiff(): Promise<{ diff: Record<string, { prior: unknown; resulting: unknown }> }>;
|
|
40
58
|
configSet(key: string, rawValStr: string, opts?: { dryRun?: boolean }): Promise<SetConfigResult>;
|
|
41
59
|
configStatus(): Promise<ConfigStatusResponse>;
|
|
60
|
+
getStats(since: string): Promise<StatsResponse>;
|
|
61
|
+
auditPrune(opts?: AuditPruneOptions): Promise<AuditPruneResult>;
|
|
62
|
+
auditCount(older_than?: string): Promise<AuditPruneResult>;
|
|
63
|
+
evalRun(): Promise<EvalReport>;
|
|
64
|
+
evalPromote(since: string): Promise<EvalCase[]>;
|
|
42
65
|
}
|
|
43
66
|
|
|
44
67
|
export function isLoopbackHost(hostname: string): boolean {
|
|
@@ -116,6 +139,75 @@ export class LocalAdapter implements TargetAdapter {
|
|
|
116
139
|
async configStatus(): Promise<ConfigStatusResponse> {
|
|
117
140
|
return getLocalConfigStatus(this.configPath);
|
|
118
141
|
}
|
|
142
|
+
|
|
143
|
+
async getStats(since: string): Promise<StatsResponse> {
|
|
144
|
+
const config = await loadConfig(this.configPath);
|
|
145
|
+
const stateDir = expandHome(config.state_dir);
|
|
146
|
+
const db = openAudit(stateDir);
|
|
147
|
+
try {
|
|
148
|
+
return getStats(db, since);
|
|
149
|
+
} finally {
|
|
150
|
+
db.close();
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async auditPrune(opts?: AuditPruneOptions): Promise<AuditPruneResult> {
|
|
155
|
+
const config = await loadConfig(this.configPath);
|
|
156
|
+
const stateDir = expandHome(config.state_dir);
|
|
157
|
+
const olderThan = opts?.older_than;
|
|
158
|
+
const dryRun = opts?.dry_run ?? false;
|
|
159
|
+
|
|
160
|
+
let cutoff: Date;
|
|
161
|
+
if (olderThan) {
|
|
162
|
+
cutoff = parseSince(olderThan);
|
|
163
|
+
} else {
|
|
164
|
+
const retentionDays = config.audit?.retention_days ?? 90;
|
|
165
|
+
if (retentionDays <= 0) {
|
|
166
|
+
return {
|
|
167
|
+
audit_deleted: 0,
|
|
168
|
+
fetch_deleted: 0,
|
|
169
|
+
admin_audit_deleted: 0,
|
|
170
|
+
dry_run: dryRun,
|
|
171
|
+
cutoff: null,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
cutoff = new Date(Date.now() - retentionDays * 86_400_000);
|
|
175
|
+
}
|
|
176
|
+
const cutoffIso = cutoff.toISOString();
|
|
177
|
+
|
|
178
|
+
const db = openAudit(stateDir);
|
|
179
|
+
try {
|
|
180
|
+
if (dryRun) {
|
|
181
|
+
const counts = countPrunable(db, cutoffIso);
|
|
182
|
+
return { ...counts, dry_run: true, cutoff: cutoffIso };
|
|
183
|
+
}
|
|
184
|
+
const counts = pruneAuditBefore(db, cutoffIso);
|
|
185
|
+
return { ...counts, dry_run: false, cutoff: cutoffIso };
|
|
186
|
+
} finally {
|
|
187
|
+
db.close();
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async auditCount(older_than?: string): Promise<AuditPruneResult> {
|
|
192
|
+
return this.auditPrune({ older_than, dry_run: true });
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async evalRun(): Promise<EvalReport> {
|
|
196
|
+
return evalVault();
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async evalPromote(since: string): Promise<EvalCase[]> {
|
|
200
|
+
const sinceDate = parseSince(since);
|
|
201
|
+
const sinceIso = sinceDate.toISOString();
|
|
202
|
+
const config = await loadConfig(this.configPath);
|
|
203
|
+
const stateDir = expandHome(config.state_dir);
|
|
204
|
+
const db = openAudit(stateDir);
|
|
205
|
+
try {
|
|
206
|
+
return buildPromotedCases(queryPromotableFetches(db, sinceIso));
|
|
207
|
+
} finally {
|
|
208
|
+
db.close();
|
|
209
|
+
}
|
|
210
|
+
}
|
|
119
211
|
}
|
|
120
212
|
|
|
121
213
|
export class RemoteAdapter implements TargetAdapter {
|
|
@@ -281,9 +373,63 @@ export class RemoteAdapter implements TargetAdapter {
|
|
|
281
373
|
}
|
|
282
374
|
return data.runtime;
|
|
283
375
|
}
|
|
376
|
+
|
|
377
|
+
async getStats(since: string): Promise<StatsResponse> {
|
|
378
|
+
const searchParams = new URLSearchParams({ since });
|
|
379
|
+
const { status, data } = await this.fetchJson(`/stats?${searchParams.toString()}`);
|
|
380
|
+
if (status !== 200) {
|
|
381
|
+
throw new Error(`Remote stats fetch failed (${status}): ${typeof data === "object" ? data?.message || data?.error || JSON.stringify(data) : data}`);
|
|
382
|
+
}
|
|
383
|
+
return data as StatsResponse;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
async auditPrune(opts?: AuditPruneOptions): Promise<AuditPruneResult> {
|
|
387
|
+
const { status, data } = await this.fetchJson("/admin/v1/audit/prune", {
|
|
388
|
+
method: "POST",
|
|
389
|
+
headers: { "Content-Type": "application/json" },
|
|
390
|
+
body: JSON.stringify({
|
|
391
|
+
...(opts?.older_than ? { older_than: opts.older_than } : {}),
|
|
392
|
+
...(opts?.dry_run !== undefined ? { dry_run: opts.dry_run } : {}),
|
|
393
|
+
...(opts?.confirm !== undefined ? { confirm: opts.confirm } : {}),
|
|
394
|
+
}),
|
|
395
|
+
});
|
|
396
|
+
if (status !== 200) {
|
|
397
|
+
const message = typeof data === "object" ? data?.message || data?.error || JSON.stringify(data) : data;
|
|
398
|
+
throw new Error(`Remote audit prune failed (${status}): ${message}`);
|
|
399
|
+
}
|
|
400
|
+
return data as AuditPruneResult;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
async auditCount(older_than?: string): Promise<AuditPruneResult> {
|
|
404
|
+
return this.auditPrune({ older_than, dry_run: true });
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
async evalRun(): Promise<EvalReport> {
|
|
408
|
+
const { status, data } = await this.fetchJson("/admin/v1/eval", {
|
|
409
|
+
method: "POST",
|
|
410
|
+
});
|
|
411
|
+
if (status !== 200) {
|
|
412
|
+
const message = typeof data === "object" ? data?.message || data?.error || JSON.stringify(data) : data;
|
|
413
|
+
throw new Error(`Remote eval failed (${status}): ${message}`);
|
|
414
|
+
}
|
|
415
|
+
return data as EvalReport;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
async evalPromote(since: string): Promise<EvalCase[]> {
|
|
419
|
+
const { status, data } = await this.fetchJson("/admin/v1/eval/promote", {
|
|
420
|
+
method: "POST",
|
|
421
|
+
headers: { "Content-Type": "application/json" },
|
|
422
|
+
body: JSON.stringify({ since }),
|
|
423
|
+
});
|
|
424
|
+
if (status !== 200) {
|
|
425
|
+
const message = typeof data === "object" ? data?.message || data?.error || JSON.stringify(data) : data;
|
|
426
|
+
throw new Error(`Remote eval promote failed (${status}): ${message}`);
|
|
427
|
+
}
|
|
428
|
+
return data.candidates as EvalCase[];
|
|
429
|
+
}
|
|
284
430
|
}
|
|
285
431
|
|
|
286
|
-
export function createTargetAdapter(target:
|
|
432
|
+
export function createTargetAdapter(target: ResolvedContext, opts?: TargetAdapterOptions): TargetAdapter {
|
|
287
433
|
if (target.type === "local") {
|
|
288
434
|
return new LocalAdapter(opts);
|
|
289
435
|
} else {
|