@karmaniverous/jeeves-server 3.2.0 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,13 +2,32 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. Dates are displayed in UTC.
4
4
 
5
- #### [3.2.0](https://github.com/karmaniverous/jeeves-server/compare/service/3.1.3...3.2.0)
5
+ #### [3.3.0](https://github.com/karmaniverous/jeeves-server/compare/service/3.2.1...3.3.0)
6
+
7
+ - feat: core v0.2.0 SDK adoption [`#111`](https://github.com/karmaniverous/jeeves-server/pull/111)
8
+ - chore: release @karmaniverous/jeeves-server-openclaw v0.3.1 [`11309c1`](https://github.com/karmaniverous/jeeves-server/commit/11309c1c3d82f6950b3d0291546206b523df83fa)
9
+
10
+ #### [service/3.2.1](https://github.com/karmaniverous/jeeves-server/compare/service/3.2.0...service/3.2.1)
11
+
12
+ > 19 March 2026
13
+
14
+ - fix(openclaw): bundle @karmaniverous/jeeves into plugin dist [`61fb7ac`](https://github.com/karmaniverous/jeeves-server/commit/61fb7ac3f91a036cef16720c16c33fe399f7c4e6)
15
+ - chore: release @karmaniverous/jeeves-server v3.2.1 [`f13677b`](https://github.com/karmaniverous/jeeves-server/commit/f13677bd1cd020416ba510eaabc58ad85ee9aabf)
16
+ - chore(openclaw): use resolveWorkspacePath from jeeves 0.1.4 [`71ddd58`](https://github.com/karmaniverous/jeeves-server/commit/71ddd588f1b5736f0cbd4d81d42bb0f9356eafe8)
17
+ - chore(openclaw): update jeeves to 0.1.6, add servicePackage/pluginPackage [`1847ff7`](https://github.com/karmaniverous/jeeves-server/commit/1847ff750613b3770198b20b4bacd7e93bf0ec52)
18
+ - chore(openclaw): update @karmaniverous/jeeves to 0.1.5 [`c8b040b`](https://github.com/karmaniverous/jeeves-server/commit/c8b040bfa945b3814cbde9395cf1d3f00cdd64a8)
19
+ - chore(openclaw): update @karmaniverous/jeeves to 0.1.3 [`6de1c66`](https://github.com/karmaniverous/jeeves-server/commit/6de1c667a2f1b6de3660c92f96774fccbb927321)
20
+
21
+ #### [service/3.2.0](https://github.com/karmaniverous/jeeves-server/compare/service/3.1.3...service/3.2.0)
22
+
23
+ > 18 March 2026
6
24
 
7
25
  - feat(openclaw): adopt jeeves core component writer [`#105`](https://github.com/karmaniverous/jeeves-server/pull/105)
8
26
  - test(openclaw): expand coverage for openclawPaths, pluginRemove, serviceCommands [`b38ecac`](https://github.com/karmaniverous/jeeves-server/commit/b38ecaccc1fe1129630c1be993b6eafb31d86f6b)
9
27
  - chore(openclaw): update deps, clean knip config [`82cb058`](https://github.com/karmaniverous/jeeves-server/commit/82cb0585004163f0ee92f17d231795bffef2e5bb)
10
28
  - docs: full documentation pass with PlantUML diagrams [`537a9d7`](https://github.com/karmaniverous/jeeves-server/commit/537a9d79ff23ea978697c44cb0bb91c908b761de)
11
29
  - refactor(openclaw): resolve SOLID/DRY violations [`87df443`](https://github.com/karmaniverous/jeeves-server/commit/87df44307cb40eddbc9e1b217232eda194d2921b)
30
+ - chore: release @karmaniverous/jeeves-server v3.2.0 [`fda70c6`](https://github.com/karmaniverous/jeeves-server/commit/fda70c6bbacb109f00e13468ebf9052176b045d9)
12
31
  - refactor(openclaw): use createAsyncContentCache from jeeves v0.1.1 [`9732ad9`](https://github.com/karmaniverous/jeeves-server/commit/9732ad9635c747e78f9fe2d6fd5b3b893f51efc4)
13
32
  - npm audit fix [`7c578d4`](https://github.com/karmaniverous/jeeves-server/commit/7c578d4bb588514019cddf758d9f43252b25fe42)
14
33
  - test(openclaw): cover service commands [`08f478c`](https://github.com/karmaniverous/jeeves-server/commit/08f478cd4ad8aa9a1d9e06dc595535ae2e869a11)
@@ -0,0 +1,40 @@
1
+ /**
2
+ * GET /config — query service configuration with optional JSONPath.
3
+ *
4
+ * Uses the core SDK's `createConfigQueryHandler()` for JSONPath support.
5
+ *
6
+ * @module routes/config
7
+ */
8
+ import { createConfigQueryHandler } from '@karmaniverous/jeeves';
9
+ import { getConfig } from '../config/index.js';
10
+ /** Return a sanitized copy of the config (redact sensitive fields). */
11
+ export function sanitizeConfig(config) {
12
+ return {
13
+ ...config,
14
+ sessionSecret: config.sessionSecret ? '[REDACTED]' : null,
15
+ internalInsiderKey: config.internalInsiderKey ? '[REDACTED]' : null,
16
+ googleAuth: config.googleAuth
17
+ ? {
18
+ clientId: config.googleAuth.clientId,
19
+ clientSecret: '[REDACTED]',
20
+ }
21
+ : null,
22
+ resolvedKeys: config.resolvedKeys.map((k) => ({
23
+ ...k,
24
+ seed: '[REDACTED]',
25
+ })),
26
+ resolvedInsiders: config.resolvedInsiders.map((i) => ({
27
+ ...i,
28
+ seed: '[REDACTED]',
29
+ })),
30
+ };
31
+ }
32
+ /** Register the GET /config route. */
33
+ export function registerConfigRoute(app) {
34
+ const configHandler = createConfigQueryHandler(() => sanitizeConfig(getConfig()));
35
+ app.get('/config', async (request, reply) => {
36
+ const { path } = request.query;
37
+ const result = await configHandler({ path });
38
+ return reply.status(result.status).send(result.body);
39
+ });
40
+ }
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Tests for GET /config route sanitization logic.
3
+ */
4
+ import { describe, expect, it } from 'vitest';
5
+ import { sanitizeConfig } from './config.js';
6
+ /** Minimal RuntimeConfig fixture with sensitive fields populated. */
7
+ function makeConfig(overrides = {}) {
8
+ return {
9
+ port: 1934,
10
+ eventTimeoutMs: 30_000,
11
+ eventLogPurgeMs: 604_800_000,
12
+ maxZipSizeMb: 100,
13
+ chromePath: '/usr/bin/chromium',
14
+ plantuml: { servers: [] },
15
+ outsiderPolicy: null,
16
+ events: {},
17
+ authModes: ['keys'],
18
+ resolvedKeys: [
19
+ {
20
+ name: 'primary',
21
+ seed: 'secret-key-seed-abc',
22
+ scopes: null,
23
+ },
24
+ ],
25
+ resolvedInsiders: [
26
+ {
27
+ email: 'jason@example.com',
28
+ seed: 'secret-insider-seed-xyz',
29
+ scopes: null,
30
+ keyCreatedAt: '2026-01-01T00:00:00Z',
31
+ },
32
+ ],
33
+ googleAuth: {
34
+ clientId: 'public-client-id',
35
+ clientSecret: 'super-secret-oauth-secret',
36
+ },
37
+ sessionSecret: 'session-hmac-secret',
38
+ internalInsiderKey: 'internal-key-seed',
39
+ configPath: '/etc/jeeves-server.config.json',
40
+ eventsLog: '/var/log/events.log',
41
+ stateFile: '/var/state.json',
42
+ eventQueuePath: '/var/queue.jsonl',
43
+ eventQueueCursorPath: '/var/cursor.json',
44
+ eventLogPath: '/var/event-log.jsonl',
45
+ ...overrides,
46
+ };
47
+ }
48
+ describe('sanitizeConfig', () => {
49
+ it('redacts sessionSecret', () => {
50
+ const result = sanitizeConfig(makeConfig());
51
+ expect(result.sessionSecret).toBe('[REDACTED]');
52
+ });
53
+ it('returns null for sessionSecret when not configured', () => {
54
+ const result = sanitizeConfig(makeConfig({ sessionSecret: null }));
55
+ expect(result.sessionSecret).toBeNull();
56
+ });
57
+ it('redacts internalInsiderKey', () => {
58
+ const result = sanitizeConfig(makeConfig());
59
+ expect(result.internalInsiderKey).toBe('[REDACTED]');
60
+ });
61
+ it('redacts googleAuth.clientSecret but preserves clientId', () => {
62
+ const result = sanitizeConfig(makeConfig());
63
+ const auth = result.googleAuth;
64
+ expect(auth.clientId).toBe('public-client-id');
65
+ expect(auth.clientSecret).toBe('[REDACTED]');
66
+ });
67
+ it('returns null for googleAuth when not configured', () => {
68
+ const result = sanitizeConfig(makeConfig({ googleAuth: null }));
69
+ expect(result.googleAuth).toBeNull();
70
+ });
71
+ it('redacts all key seeds', () => {
72
+ const config = makeConfig({
73
+ resolvedKeys: [
74
+ { name: 'a', seed: 'seed-a', scopes: null },
75
+ { name: 'b', seed: 'seed-b', scopes: null },
76
+ ],
77
+ });
78
+ const result = sanitizeConfig(config);
79
+ const keys = result.resolvedKeys;
80
+ expect(keys).toHaveLength(2);
81
+ expect(keys[0].name).toBe('a');
82
+ expect(keys[0].seed).toBe('[REDACTED]');
83
+ expect(keys[1].seed).toBe('[REDACTED]');
84
+ });
85
+ it('redacts all insider seeds but preserves other fields', () => {
86
+ const result = sanitizeConfig(makeConfig());
87
+ const insiders = result.resolvedInsiders;
88
+ expect(insiders[0].email).toBe('jason@example.com');
89
+ expect(insiders[0].seed).toBe('[REDACTED]');
90
+ expect(insiders[0].keyCreatedAt).toBe('2026-01-01T00:00:00Z');
91
+ });
92
+ it('preserves non-sensitive fields', () => {
93
+ const result = sanitizeConfig(makeConfig());
94
+ expect(result.port).toBe(1934);
95
+ expect(result.chromePath).toBe('/usr/bin/chromium');
96
+ expect(result.configPath).toBe('/etc/jeeves-server.config.json');
97
+ });
98
+ it('never leaks raw secret values', () => {
99
+ const config = makeConfig();
100
+ const json = JSON.stringify(sanitizeConfig(config));
101
+ expect(json).not.toContain('secret-key-seed-abc');
102
+ expect(json).not.toContain('secret-insider-seed-xyz');
103
+ expect(json).not.toContain('super-secret-oauth-secret');
104
+ expect(json).not.toContain('session-hmac-secret');
105
+ expect(json).not.toContain('internal-key-seed');
106
+ });
107
+ });
@@ -11,6 +11,7 @@ import Fastify from 'fastify';
11
11
  import { getConfig, initConfig, isConfigInitialized } from './config/index.js';
12
12
  import { apiRoute } from './routes/api/index.js';
13
13
  import { authRoute } from './routes/auth.js';
14
+ import { registerConfigRoute } from './routes/config.js';
14
15
  import { eventRoute } from './routes/event.js';
15
16
  import { healthRoute } from './routes/health.js';
16
17
  import { keysRoute } from './routes/keys.js';
@@ -35,6 +36,7 @@ async function start() {
35
36
  // Register routes
36
37
  await fastify.register(staticRoutes);
37
38
  await fastify.register(healthRoute);
39
+ registerConfigRoute(fastify);
38
40
  await fastify.register(authRoute);
39
41
  await fastify.register(keysRoute);
40
42
  await fastify.register(eventRoute);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@karmaniverous/jeeves-server",
3
- "version": "3.2.0",
3
+ "version": "3.3.0",
4
4
  "description": "Secure file browser, markdown viewer, and webhook gateway with PDF/DOCX export and expiring share links",
5
5
  "keywords": [
6
6
  "fastify",
@@ -47,6 +47,7 @@
47
47
  "license": "MIT",
48
48
  "dependencies": {
49
49
  "@commander-js/extra-typings": "^14.0.0",
50
+ "@karmaniverous/jeeves": "^0.2.0",
50
51
  "@fastify/cookie": "^11.0.2",
51
52
  "@fastify/static": "^8.3.0",
52
53
  "@karmaniverous/jsonmap": "^0.3.1",
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Tests for GET /config route sanitization logic.
3
+ */
4
+
5
+ import { describe, expect, it } from 'vitest';
6
+
7
+ import type { RuntimeConfig } from '../config/types.js';
8
+ import { sanitizeConfig } from './config.js';
9
+
10
+ /** Minimal RuntimeConfig fixture with sensitive fields populated. */
11
+ function makeConfig(overrides: Partial<RuntimeConfig> = {}): RuntimeConfig {
12
+ return {
13
+ port: 1934,
14
+ eventTimeoutMs: 30_000,
15
+ eventLogPurgeMs: 604_800_000,
16
+ maxZipSizeMb: 100,
17
+ chromePath: '/usr/bin/chromium',
18
+ plantuml: { servers: [] },
19
+ outsiderPolicy: null,
20
+ events: {},
21
+ authModes: ['keys'],
22
+ resolvedKeys: [
23
+ {
24
+ name: 'primary',
25
+ seed: 'secret-key-seed-abc',
26
+ scopes: null,
27
+ },
28
+ ],
29
+ resolvedInsiders: [
30
+ {
31
+ email: 'jason@example.com',
32
+ seed: 'secret-insider-seed-xyz',
33
+ scopes: null,
34
+ keyCreatedAt: '2026-01-01T00:00:00Z',
35
+ },
36
+ ],
37
+ googleAuth: {
38
+ clientId: 'public-client-id',
39
+ clientSecret: 'super-secret-oauth-secret',
40
+ },
41
+ sessionSecret: 'session-hmac-secret',
42
+ internalInsiderKey: 'internal-key-seed',
43
+ configPath: '/etc/jeeves-server.config.json',
44
+ eventsLog: '/var/log/events.log',
45
+ stateFile: '/var/state.json',
46
+ eventQueuePath: '/var/queue.jsonl',
47
+ eventQueueCursorPath: '/var/cursor.json',
48
+ eventLogPath: '/var/event-log.jsonl',
49
+ ...overrides,
50
+ };
51
+ }
52
+
53
+ describe('sanitizeConfig', () => {
54
+ it('redacts sessionSecret', () => {
55
+ const result = sanitizeConfig(makeConfig()) as Record<string, unknown>;
56
+ expect(result.sessionSecret).toBe('[REDACTED]');
57
+ });
58
+
59
+ it('returns null for sessionSecret when not configured', () => {
60
+ const result = sanitizeConfig(
61
+ makeConfig({ sessionSecret: null }),
62
+ ) as Record<string, unknown>;
63
+ expect(result.sessionSecret).toBeNull();
64
+ });
65
+
66
+ it('redacts internalInsiderKey', () => {
67
+ const result = sanitizeConfig(makeConfig()) as Record<string, unknown>;
68
+ expect(result.internalInsiderKey).toBe('[REDACTED]');
69
+ });
70
+
71
+ it('redacts googleAuth.clientSecret but preserves clientId', () => {
72
+ const result = sanitizeConfig(makeConfig()) as Record<string, unknown>;
73
+ const auth = result.googleAuth as {
74
+ clientId: string;
75
+ clientSecret: string;
76
+ };
77
+ expect(auth.clientId).toBe('public-client-id');
78
+ expect(auth.clientSecret).toBe('[REDACTED]');
79
+ });
80
+
81
+ it('returns null for googleAuth when not configured', () => {
82
+ const result = sanitizeConfig(makeConfig({ googleAuth: null })) as Record<
83
+ string,
84
+ unknown
85
+ >;
86
+ expect(result.googleAuth).toBeNull();
87
+ });
88
+
89
+ it('redacts all key seeds', () => {
90
+ const config = makeConfig({
91
+ resolvedKeys: [
92
+ { name: 'a', seed: 'seed-a', scopes: null },
93
+ { name: 'b', seed: 'seed-b', scopes: null },
94
+ ],
95
+ });
96
+ const result = sanitizeConfig(config) as Record<string, unknown>;
97
+ const keys = result.resolvedKeys as Array<{ name: string; seed: string }>;
98
+ expect(keys).toHaveLength(2);
99
+ expect(keys[0].name).toBe('a');
100
+ expect(keys[0].seed).toBe('[REDACTED]');
101
+ expect(keys[1].seed).toBe('[REDACTED]');
102
+ });
103
+
104
+ it('redacts all insider seeds but preserves other fields', () => {
105
+ const result = sanitizeConfig(makeConfig()) as Record<string, unknown>;
106
+ const insiders = result.resolvedInsiders as Array<{
107
+ email: string;
108
+ seed: string;
109
+ keyCreatedAt: string;
110
+ }>;
111
+ expect(insiders[0].email).toBe('jason@example.com');
112
+ expect(insiders[0].seed).toBe('[REDACTED]');
113
+ expect(insiders[0].keyCreatedAt).toBe('2026-01-01T00:00:00Z');
114
+ });
115
+
116
+ it('preserves non-sensitive fields', () => {
117
+ const result = sanitizeConfig(makeConfig()) as Record<string, unknown>;
118
+ expect(result.port).toBe(1934);
119
+ expect(result.chromePath).toBe('/usr/bin/chromium');
120
+ expect(result.configPath).toBe('/etc/jeeves-server.config.json');
121
+ });
122
+
123
+ it('never leaks raw secret values', () => {
124
+ const config = makeConfig();
125
+ const json = JSON.stringify(sanitizeConfig(config));
126
+ expect(json).not.toContain('secret-key-seed-abc');
127
+ expect(json).not.toContain('secret-insider-seed-xyz');
128
+ expect(json).not.toContain('super-secret-oauth-secret');
129
+ expect(json).not.toContain('session-hmac-secret');
130
+ expect(json).not.toContain('internal-key-seed');
131
+ });
132
+ });
@@ -0,0 +1,49 @@
1
+ /**
2
+ * GET /config — query service configuration with optional JSONPath.
3
+ *
4
+ * Uses the core SDK's `createConfigQueryHandler()` for JSONPath support.
5
+ *
6
+ * @module routes/config
7
+ */
8
+
9
+ import { createConfigQueryHandler } from '@karmaniverous/jeeves';
10
+ import type { FastifyInstance } from 'fastify';
11
+
12
+ import { getConfig } from '../config/index.js';
13
+ import type { RuntimeConfig } from '../config/types.js';
14
+
15
+ /** Return a sanitized copy of the config (redact sensitive fields). */
16
+ export function sanitizeConfig(config: RuntimeConfig): unknown {
17
+ return {
18
+ ...config,
19
+ sessionSecret: config.sessionSecret ? '[REDACTED]' : null,
20
+ internalInsiderKey: config.internalInsiderKey ? '[REDACTED]' : null,
21
+ googleAuth: config.googleAuth
22
+ ? {
23
+ clientId: config.googleAuth.clientId,
24
+ clientSecret: '[REDACTED]',
25
+ }
26
+ : null,
27
+ resolvedKeys: config.resolvedKeys.map((k) => ({
28
+ ...k,
29
+ seed: '[REDACTED]',
30
+ })),
31
+ resolvedInsiders: config.resolvedInsiders.map((i) => ({
32
+ ...i,
33
+ seed: '[REDACTED]',
34
+ })),
35
+ };
36
+ }
37
+
38
+ /** Register the GET /config route. */
39
+ export function registerConfigRoute(app: FastifyInstance): void {
40
+ const configHandler = createConfigQueryHandler(() =>
41
+ sanitizeConfig(getConfig()),
42
+ );
43
+
44
+ app.get('/config', async (request, reply) => {
45
+ const { path } = request.query as { path?: string };
46
+ const result = await configHandler({ path });
47
+ return reply.status(result.status).send(result.body);
48
+ });
49
+ }
package/src/server.ts CHANGED
@@ -14,6 +14,7 @@ import Fastify from 'fastify';
14
14
  import { getConfig, initConfig, isConfigInitialized } from './config/index.js';
15
15
  import { apiRoute } from './routes/api/index.js';
16
16
  import { authRoute } from './routes/auth.js';
17
+ import { registerConfigRoute } from './routes/config.js';
17
18
  import { eventRoute } from './routes/event.js';
18
19
  import { healthRoute } from './routes/health.js';
19
20
  import { keysRoute } from './routes/keys.js';
@@ -44,6 +45,7 @@ async function start() {
44
45
  // Register routes
45
46
  await fastify.register(staticRoutes);
46
47
  await fastify.register(healthRoute);
48
+ registerConfigRoute(fastify);
47
49
  await fastify.register(authRoute);
48
50
  await fastify.register(keysRoute);
49
51
  await fastify.register(eventRoute);