@karmaniverous/jeeves-server 3.4.2 → 3.5.1
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/.tsbuildinfo +1 -1
- package/CHANGELOG.md +47 -1
- package/README.md +18 -17
- package/client/package.json +20 -19
- package/client/src/components/SearchModal.tsx +11 -1
- package/client/src/components/layout/Header.tsx +3 -3
- package/client/src/lib/api.ts +10 -5
- package/dist/client/assets/CodeEditor-Brh86AGF.js +1 -0
- package/dist/client/assets/CodeViewer-Cegj3cEn.js +1 -0
- package/dist/client/assets/dist-2YqVIvgv.js +2 -0
- package/dist/client/assets/dist-5vamY028.js +1 -0
- package/dist/client/assets/dist-6_auAGci.js +1 -0
- package/dist/client/assets/dist-B0kq1DQG.js +1 -0
- package/dist/client/assets/dist-B2SZD_eN.js +1 -0
- package/dist/client/assets/dist-B2t4dYA2.js +1 -0
- package/dist/client/assets/dist-B5gFYAn7.js +1 -0
- package/dist/client/assets/dist-BPy6CnYN.js +1 -0
- package/dist/client/assets/dist-CL6VCrQn.js +9 -0
- package/dist/client/assets/dist-CWsHar9N.js +1 -0
- package/dist/client/assets/dist-CnFc5Ssx.js +1 -0
- package/dist/client/assets/dist-DSgLBuTS.js +1 -0
- package/dist/client/assets/dist-DUcac0X_.js +7 -0
- package/dist/client/assets/dist-DcTcc-BG.js +6 -0
- package/dist/client/assets/dist-DvfTyWk_.js +1 -0
- package/dist/client/assets/dist-Dz1Ulpqa.js +1 -0
- package/dist/client/assets/dist-Kr-mUYW1.js +5 -0
- package/dist/client/assets/dist-OX4k3MMG.js +2 -0
- package/dist/client/assets/dist-qiU0qoeK.js +1 -0
- package/dist/client/assets/dist-ui4J6fvl.js +23 -0
- package/dist/client/assets/index-Dk_myGs4.css +2 -0
- package/dist/client/assets/index-DrBXupPz.js +62 -0
- package/dist/client/assets/theme-CPpIxvB0.js +2 -0
- package/dist/client/index.html +3 -2
- package/dist/src/cli/commands/config.test.js +6 -41
- package/dist/src/cli/index.js +9 -15
- package/dist/src/cli/start-server.js +16 -0
- package/dist/src/config/index.js +48 -37
- package/dist/src/config/loadConfig.test.js +27 -25
- package/dist/src/config/migration.js +60 -0
- package/dist/src/config/schema.js +4 -3
- package/dist/src/descriptor.js +51 -0
- package/dist/src/routes/api/diagramExport.js +101 -0
- package/dist/src/routes/api/diagramExport.test.js +134 -0
- package/dist/src/routes/api/events.js +13 -0
- package/dist/src/routes/api/export.js +6 -82
- package/dist/src/routes/api/index.js +4 -0
- package/dist/src/routes/api/search.js +9 -50
- package/dist/src/routes/api/sharing.js +40 -23
- package/dist/src/routes/api/sharing.test.js +52 -0
- package/dist/src/routes/auth.js +1 -1
- package/dist/src/routes/config.js +8 -2
- package/dist/src/routes/keys.js +4 -4
- package/dist/src/routes/path/index.js +1 -1
- package/dist/src/routes/status.js +15 -16
- package/dist/src/routes/status.test.js +13 -8
- package/dist/src/server.js +21 -16
- package/dist/src/services/markdown.js +2 -1
- package/dist/src/services/markdown.test.js +22 -0
- package/dist/src/util/packageVersion.js +7 -16
- package/dist/src/util/packageVersion.test.js +7 -0
- package/guides/api-integration.md +4 -0
- package/guides/deployment.md +11 -10
- package/guides/event-gateway.md +4 -0
- package/guides/exports.md +4 -0
- package/guides/index.md +1 -1
- package/guides/setup.md +17 -16
- package/guides/sharing.md +4 -0
- package/package.json +3 -3
- package/scripts/download-plantuml.js +0 -1
- package/src/cli/commands/config.test.ts +6 -46
- package/src/cli/index.ts +9 -16
- package/src/cli/start-server.ts +21 -0
- package/src/config/index.ts +56 -43
- package/src/config/loadConfig.test.ts +27 -29
- package/src/config/migration.ts +76 -0
- package/src/config/schema.ts +5 -4
- package/src/descriptor.ts +60 -0
- package/src/routes/api/diagramExport.test.ts +200 -0
- package/src/routes/api/diagramExport.ts +170 -0
- package/src/routes/api/events.ts +22 -0
- package/src/routes/api/export.ts +6 -131
- package/src/routes/api/index.ts +4 -0
- package/src/routes/api/search.ts +9 -63
- package/src/routes/api/sharing.test.ts +66 -0
- package/src/routes/api/sharing.ts +47 -23
- package/src/routes/auth.ts +1 -1
- package/src/routes/config.ts +15 -2
- package/src/routes/keys.ts +4 -4
- package/src/routes/path/index.ts +1 -1
- package/src/routes/status.test.ts +14 -8
- package/src/routes/status.ts +56 -62
- package/src/server.ts +29 -17
- package/src/services/markdown.test.ts +26 -0
- package/src/services/markdown.ts +2 -1
- package/src/util/packageVersion.test.ts +9 -0
- package/src/util/packageVersion.ts +11 -18
- package/src/util/platform.ts +1 -1
- package/dist/client/assets/CodeEditor-DQZZL5Rq.js +0 -1
- package/dist/client/assets/CodeViewer-ofJVD1Vn.js +0 -1
- package/dist/client/assets/index--MBieNJA.js +0 -1
- package/dist/client/assets/index-BENeXQI_.js +0 -1
- package/dist/client/assets/index-BbBpoOxz.js +0 -1
- package/dist/client/assets/index-BdV9g5AM.js +0 -6
- package/dist/client/assets/index-BjAilRri.js +0 -2
- package/dist/client/assets/index-BqbhWo2I.js +0 -3
- package/dist/client/assets/index-CVbycZ0H.js +0 -1
- package/dist/client/assets/index-Cs5oz2oJ.js +0 -5
- package/dist/client/assets/index-D-RC7ZS6.css +0 -1
- package/dist/client/assets/index-D8KZVveX.js +0 -1
- package/dist/client/assets/index-DC4HMHxY.js +0 -13
- package/dist/client/assets/index-DcY2RXqX.js +0 -1
- package/dist/client/assets/index-Duy-tZYV.js +0 -1
- package/dist/client/assets/index-Dw7rDFmE.js +0 -7
- package/dist/client/assets/index-FlCUvrjv.js +0 -2
- package/dist/client/assets/index-K6OVmfhg.js +0 -1
- package/dist/client/assets/index-MLwyFRN0.js +0 -1
- package/dist/client/assets/index-OpqBpSjn.js +0 -1
- package/dist/client/assets/index-SsHei0HE.js +0 -1
- package/dist/client/assets/index-jSGuHSeS.js +0 -62
- package/dist/client/assets/index-uQa2yckk.js +0 -1
- package/dist/client/assets/index-udkXoIER.js +0 -1
- package/dist/src/cli/commands/config.js +0 -105
- package/dist/src/cli/commands/service.js +0 -93
- package/dist/src/cli/commands/start.js +0 -24
- package/src/cli/commands/config.ts +0 -117
- package/src/cli/commands/service.ts +0 -129
- package/src/cli/commands/start.ts +0 -27
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
*
|
|
6
6
|
* @packageDocumentation
|
|
7
7
|
*/
|
|
8
|
-
import { createConfigQueryHandler } from '@karmaniverous/jeeves';
|
|
8
|
+
import { createConfigApplyHandler, createConfigQueryHandler, } from '@karmaniverous/jeeves';
|
|
9
9
|
import { getConfig } from '../config/index.js';
|
|
10
|
+
import { serverDescriptor } from '../descriptor.js';
|
|
10
11
|
/** Return a sanitized copy of the config (redact sensitive fields). */
|
|
11
12
|
export function sanitizeConfig(config) {
|
|
12
13
|
return {
|
|
@@ -29,7 +30,7 @@ export function sanitizeConfig(config) {
|
|
|
29
30
|
})),
|
|
30
31
|
};
|
|
31
32
|
}
|
|
32
|
-
/** Register the GET /config
|
|
33
|
+
/** Register the GET /config and POST /config/apply routes. */
|
|
33
34
|
export function registerConfigRoute(app) {
|
|
34
35
|
const configHandler = createConfigQueryHandler(() => sanitizeConfig(getConfig()));
|
|
35
36
|
app.get('/config', async (request, reply) => {
|
|
@@ -37,4 +38,9 @@ export function registerConfigRoute(app) {
|
|
|
37
38
|
const result = await configHandler({ path });
|
|
38
39
|
return reply.status(result.status).send(result.body);
|
|
39
40
|
});
|
|
41
|
+
const applyHandler = createConfigApplyHandler(serverDescriptor);
|
|
42
|
+
app.post('/config/apply', async (request, reply) => {
|
|
43
|
+
const result = await applyHandler(request.body);
|
|
44
|
+
return reply.status(result.status).send(result.body);
|
|
45
|
+
});
|
|
40
46
|
}
|
package/dist/src/routes/keys.js
CHANGED
|
@@ -53,7 +53,7 @@ export const keysRoute = async (fastify) => {
|
|
|
53
53
|
const insiderResult = resolveInsiderKeyAuth(config, provided);
|
|
54
54
|
if (insiderResult.valid && insiderResult.email) {
|
|
55
55
|
// Insider key rotation
|
|
56
|
-
return
|
|
56
|
+
return rotateInsiderSeed(insiderResult.email, config);
|
|
57
57
|
}
|
|
58
58
|
// Machine key rotation is not supported with TS config
|
|
59
59
|
const matched = config.resolvedKeys.find((rk) => timingSafeEqual(provided, computeInsiderKey(rk.seed)));
|
|
@@ -67,7 +67,7 @@ export const keysRoute = async (fastify) => {
|
|
|
67
67
|
// Try session-based auth
|
|
68
68
|
const sessionResult = resolveSessionAuth(config, request);
|
|
69
69
|
if (sessionResult.valid && sessionResult.email) {
|
|
70
|
-
return
|
|
70
|
+
return rotateInsiderSeed(sessionResult.email, config);
|
|
71
71
|
}
|
|
72
72
|
return reply.code(401).send({ error: 'Invalid insider key' });
|
|
73
73
|
});
|
|
@@ -93,7 +93,7 @@ export const keysRoute = async (fastify) => {
|
|
|
93
93
|
return reply.code(401).send({ error: 'Invalid insider key' });
|
|
94
94
|
});
|
|
95
95
|
};
|
|
96
|
-
|
|
96
|
+
function rotateInsiderSeed(email, config) {
|
|
97
97
|
const insider = findInsider(config.resolvedInsiders, email);
|
|
98
98
|
if (!insider?.seed)
|
|
99
99
|
return { ok: false, error: 'Insider not found' };
|
|
@@ -106,7 +106,7 @@ async function rotateInsiderSeed(email, config) {
|
|
|
106
106
|
at: timestamp,
|
|
107
107
|
});
|
|
108
108
|
setKeyRotationTimestamp(timestamp);
|
|
109
|
-
|
|
109
|
+
resetConfig();
|
|
110
110
|
return { ok: true, keyName: insider.email };
|
|
111
111
|
}
|
|
112
112
|
function buildShareResponse(seed, targetPath, expiry) {
|
|
@@ -10,7 +10,7 @@ export const pathRoute = async (fastify) => {
|
|
|
10
10
|
// Redirect /path/* to /browse/*
|
|
11
11
|
fastify.get('/path/*', async (request, reply) => {
|
|
12
12
|
const reqPath = request.params['*'];
|
|
13
|
-
const url = new URL(request.url, 'http://
|
|
13
|
+
const url = new URL(request.url, 'http://127.0.0.1');
|
|
14
14
|
const query = url.search;
|
|
15
15
|
return reply.redirect(`/browse/${reqPath}${query}`);
|
|
16
16
|
});
|
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Server status endpoint —
|
|
2
|
+
* Server status endpoint — uses the SDK's `createStatusHandler` factory.
|
|
3
3
|
*
|
|
4
|
-
* Returns version, uptime,
|
|
5
|
-
*
|
|
4
|
+
* Returns standard `{ name, version, uptime, status, health }` shape
|
|
5
|
+
* with server-specific details nested under `health`.
|
|
6
6
|
*/
|
|
7
|
+
import { createStatusHandler } from '@karmaniverous/jeeves';
|
|
7
8
|
import { getConfig } from '../config/index.js';
|
|
8
|
-
import { getRecentEvents } from '../services/eventLog.js';
|
|
9
9
|
import { packageVersion } from '../util/packageVersion.js';
|
|
10
|
-
const startTime = Date.now();
|
|
11
10
|
async function checkService(url) {
|
|
12
|
-
// Try /status first (watcher), then /health (runner)
|
|
13
11
|
for (const endpoint of ['/status', '/health']) {
|
|
14
12
|
try {
|
|
15
13
|
const res = await fetch(`${url}${endpoint}`, {
|
|
@@ -26,9 +24,10 @@ async function checkService(url) {
|
|
|
26
24
|
}
|
|
27
25
|
return { url, reachable: false };
|
|
28
26
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
const handleStatus = createStatusHandler({
|
|
28
|
+
name: 'server',
|
|
29
|
+
version: packageVersion,
|
|
30
|
+
getHealth: async () => {
|
|
32
31
|
const config = getConfig();
|
|
33
32
|
const [watcher, runner, meta] = await Promise.all([
|
|
34
33
|
config.watcherUrl ? checkService(config.watcherUrl) : null,
|
|
@@ -36,8 +35,6 @@ export const statusRoutes = async (fastify) => {
|
|
|
36
35
|
config.metaUrl ? checkService(config.metaUrl) : null,
|
|
37
36
|
]);
|
|
38
37
|
return {
|
|
39
|
-
version: packageVersion,
|
|
40
|
-
uptime: Math.floor((Date.now() - startTime) / 1000),
|
|
41
38
|
port: config.port,
|
|
42
39
|
chrome: {
|
|
43
40
|
configured: Boolean(config.chromePath),
|
|
@@ -70,11 +67,13 @@ export const statusRoutes = async (fastify) => {
|
|
|
70
67
|
runner,
|
|
71
68
|
meta,
|
|
72
69
|
},
|
|
73
|
-
...(request.query.events
|
|
74
|
-
? {
|
|
75
|
-
eventLog: getRecentEvents(Math.min(parseInt(request.query.events, 10) || 20, 100)),
|
|
76
|
-
}
|
|
77
|
-
: {}),
|
|
78
70
|
};
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
74
|
+
export const statusRoutes = async (fastify) => {
|
|
75
|
+
fastify.get('/status', async () => {
|
|
76
|
+
const result = await handleStatus();
|
|
77
|
+
return result.body;
|
|
79
78
|
});
|
|
80
79
|
};
|
|
@@ -27,7 +27,7 @@ vi.mock('../config/index.js', () => ({
|
|
|
27
27
|
// Must import AFTER mock
|
|
28
28
|
const { statusRoutes } = await import('./status.js');
|
|
29
29
|
describe('GET /status', () => {
|
|
30
|
-
it('returns structured status', async () => {
|
|
30
|
+
it('returns structured status with SDK shape', async () => {
|
|
31
31
|
// Create a minimal Fastify-like test harness
|
|
32
32
|
const routes = {};
|
|
33
33
|
const fakeFastify = {
|
|
@@ -40,18 +40,23 @@ describe('GET /status', () => {
|
|
|
40
40
|
expect(handler).toBeDefined();
|
|
41
41
|
const result = await handler({ accessMode: 'insider', query: {} });
|
|
42
42
|
const status = result;
|
|
43
|
+
// Standard SDK fields at top level
|
|
44
|
+
expect(status).toHaveProperty('name', 'server');
|
|
43
45
|
expect(status).toHaveProperty('version');
|
|
44
46
|
expect(status).toHaveProperty('uptime');
|
|
45
|
-
expect(status
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
expect(
|
|
49
|
-
expect(
|
|
50
|
-
|
|
47
|
+
expect(status).toHaveProperty('status', 'healthy');
|
|
48
|
+
// Server-specific fields nested under health
|
|
49
|
+
const health = status.health;
|
|
50
|
+
expect(health.port).toBe(1934);
|
|
51
|
+
expect(health.chrome.configured).toBe(true);
|
|
52
|
+
expect(health.auth.insiderCount).toBe(2);
|
|
53
|
+
expect(health.auth.keyCount).toBe(1);
|
|
54
|
+
expect(health.events).toHaveLength(2);
|
|
55
|
+
const exports = health.exports;
|
|
51
56
|
expect(exports.documents).toEqual(['pdf', 'docx']);
|
|
52
57
|
expect(exports.directories).toEqual(['zip']);
|
|
53
58
|
expect(exports.diagrams).toEqual(['svg', 'png']);
|
|
54
59
|
expect(exports.chromeAvailable).toBe(true);
|
|
55
|
-
expect(
|
|
60
|
+
expect(health.diagrams.mermaid).toBe(true);
|
|
56
61
|
});
|
|
57
62
|
});
|
package/dist/src/server.js
CHANGED
|
@@ -23,7 +23,7 @@ import { initExportCache } from './services/exportCache.js';
|
|
|
23
23
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
24
24
|
async function start() {
|
|
25
25
|
try {
|
|
26
|
-
const config = isConfigInitialized() ? getConfig() :
|
|
26
|
+
const config = isConfigInitialized() ? getConfig() : initConfig();
|
|
27
27
|
const fastify = Fastify({
|
|
28
28
|
logger: true,
|
|
29
29
|
});
|
|
@@ -49,21 +49,26 @@ async function start() {
|
|
|
49
49
|
root: clientDir,
|
|
50
50
|
prefix: '/app/',
|
|
51
51
|
});
|
|
52
|
-
// SPA fallback for
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
fastify.
|
|
66
|
-
|
|
52
|
+
// SPA fallback — all these routes serve index.html for client-side routing
|
|
53
|
+
const spaFallback = async (_request, reply) => reply.sendFile('index.html', clientDir);
|
|
54
|
+
for (const route of [
|
|
55
|
+
'/',
|
|
56
|
+
'/browse',
|
|
57
|
+
'/browse/*',
|
|
58
|
+
'/runner',
|
|
59
|
+
'/runner/*',
|
|
60
|
+
]) {
|
|
61
|
+
fastify.get(route, spaFallback);
|
|
62
|
+
}
|
|
63
|
+
// Catch-all: serve SPA for any unmatched GET under /browse or /runner
|
|
64
|
+
// (handles edge cases like dotfile paths that wildcard routes may miss)
|
|
65
|
+
fastify.setNotFoundHandler(async (request, reply) => {
|
|
66
|
+
if (request.method === 'GET' &&
|
|
67
|
+
(request.url.startsWith('/browse') ||
|
|
68
|
+
request.url.startsWith('/runner'))) {
|
|
69
|
+
return reply.sendFile('index.html', clientDir);
|
|
70
|
+
}
|
|
71
|
+
return reply.code(404).send({ error: 'Not found' });
|
|
67
72
|
});
|
|
68
73
|
}
|
|
69
74
|
// Initialize caches
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Markdown rendering with TOC generation, Windows path linking, and syntax highlighting
|
|
3
3
|
*/
|
|
4
4
|
import fs from 'node:fs';
|
|
5
|
+
import * as cheerio from 'cheerio';
|
|
5
6
|
import { marked } from 'marked';
|
|
6
7
|
import { registerDiagram } from './embeddedDiagrams.js';
|
|
7
8
|
/**
|
|
@@ -96,7 +97,7 @@ export function parseMarkdown(markdown, options = {}) {
|
|
|
96
97
|
.replace(/^-|-$/g, '');
|
|
97
98
|
headings.push({
|
|
98
99
|
level,
|
|
99
|
-
text: renderedText.
|
|
100
|
+
text: cheerio.load(renderedText).text(),
|
|
100
101
|
slug,
|
|
101
102
|
});
|
|
102
103
|
return `<h${String(level)} id="${slug}">${renderedText} <a href="#${slug}" class="anchor">#</a></h${String(level)}>\n`;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { parseMarkdown } from './markdown.js';
|
|
3
|
+
describe('parseMarkdown', () => {
|
|
4
|
+
it('decodes HTML entities in heading text for TOC', () => {
|
|
5
|
+
const md = '# Hello & "World"';
|
|
6
|
+
const { headings } = parseMarkdown(md);
|
|
7
|
+
expect(headings).toHaveLength(1);
|
|
8
|
+
expect(headings[0].text).toBe('Hello & "World"');
|
|
9
|
+
});
|
|
10
|
+
it('strips HTML tags from heading text for TOC', () => {
|
|
11
|
+
const md = '## A <em>bold</em> heading';
|
|
12
|
+
const { headings } = parseMarkdown(md);
|
|
13
|
+
expect(headings).toHaveLength(1);
|
|
14
|
+
expect(headings[0].text).toBe('A bold heading');
|
|
15
|
+
});
|
|
16
|
+
it('decodes ' and " entities in headings', () => {
|
|
17
|
+
const md = '### It's a "test"';
|
|
18
|
+
const { headings } = parseMarkdown(md);
|
|
19
|
+
expect(headings).toHaveLength(1);
|
|
20
|
+
expect(headings[0].text).toBe('It\'s a "test"');
|
|
21
|
+
});
|
|
22
|
+
});
|
|
@@ -1,25 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Resolve the service package version
|
|
2
|
+
* Resolve the service package version using package-directory.
|
|
3
3
|
*/
|
|
4
4
|
import fs from 'node:fs';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import { fileURLToPath } from 'node:url';
|
|
7
|
-
|
|
8
|
-
let dir = startDir;
|
|
9
|
-
while (dir !== path.dirname(dir)) {
|
|
10
|
-
const candidate = path.join(dir, 'package.json');
|
|
11
|
-
if (fs.existsSync(candidate)) {
|
|
12
|
-
const pkg = JSON.parse(fs.readFileSync(candidate, 'utf8'));
|
|
13
|
-
// Find our package specifically, not the monorepo root
|
|
14
|
-
if (pkg.name === '@karmaniverous/jeeves-server')
|
|
15
|
-
return candidate;
|
|
16
|
-
}
|
|
17
|
-
dir = path.dirname(dir);
|
|
18
|
-
}
|
|
19
|
-
throw new Error('Could not find @karmaniverous/jeeves-server package.json');
|
|
20
|
-
}
|
|
7
|
+
import { packageDirectorySync } from 'package-directory';
|
|
21
8
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
22
|
-
const
|
|
9
|
+
const pkgDir = packageDirectorySync({ cwd: __dirname });
|
|
10
|
+
if (!pkgDir) {
|
|
11
|
+
throw new Error('Could not find package directory for jeeves-server');
|
|
12
|
+
}
|
|
13
|
+
const pkgPath = path.join(pkgDir, 'package.json');
|
|
23
14
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
24
15
|
/** The package version of the jeeves-server service package. */
|
|
25
16
|
export const packageVersion = pkg.version;
|
package/guides/deployment.md
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Deployment"
|
|
3
|
+
---
|
|
4
|
+
|
|
1
5
|
# Deployment
|
|
2
6
|
|
|
3
7
|
How to run Jeeves Server in production.
|
|
@@ -23,8 +27,9 @@ npm install -g @karmaniverous/jeeves-server
|
|
|
23
27
|
Create your config file (see [Setup & Configuration](setup.md)):
|
|
24
28
|
|
|
25
29
|
```bash
|
|
26
|
-
# Example: JSON config
|
|
27
|
-
|
|
30
|
+
# Example: JSON config (new path convention)
|
|
31
|
+
mkdir -p /etc/jeeves-server
|
|
32
|
+
cat > /etc/jeeves-server/config.json << 'EOF'
|
|
28
33
|
{
|
|
29
34
|
"chromePath": "/usr/bin/chromium-browser",
|
|
30
35
|
"auth": { "modes": ["keys"] },
|
|
@@ -48,18 +53,14 @@ The server listens on the configured port (default: 1934) on all interfaces.
|
|
|
48
53
|
|
|
49
54
|
### As a Windows Service (NSSM)
|
|
50
55
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
```bash
|
|
54
|
-
jeeves-server service install --config "C:\\config\\jeeves-server.config.json"
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
This prints the `nssm install` commands. Run them, then:
|
|
56
|
+
The CLI installs and manages the NSSM service directly:
|
|
58
57
|
|
|
59
58
|
```bash
|
|
59
|
+
jeeves-server service install --config "C:\\config\\jeeves-server\\config.json"
|
|
60
60
|
jeeves-server service start
|
|
61
61
|
jeeves-server service stop
|
|
62
62
|
jeeves-server service restart
|
|
63
|
+
jeeves-server service status
|
|
63
64
|
```
|
|
64
65
|
|
|
65
66
|
Or use NSSM directly:
|
|
@@ -84,7 +85,7 @@ After=network.target
|
|
|
84
85
|
[Service]
|
|
85
86
|
Type=simple
|
|
86
87
|
User=jeeves
|
|
87
|
-
ExecStart=/usr/bin/env jeeves-server start --config /etc/jeeves-server
|
|
88
|
+
ExecStart=/usr/bin/env jeeves-server start --config /etc/jeeves-server/config.json
|
|
88
89
|
Restart=on-failure
|
|
89
90
|
RestartSec=5
|
|
90
91
|
Environment=NODE_ENV=production
|
package/guides/event-gateway.md
CHANGED
package/guides/exports.md
CHANGED
package/guides/index.md
CHANGED
|
@@ -12,7 +12,7 @@ children:
|
|
|
12
12
|
|
|
13
13
|
# Service Guides
|
|
14
14
|
|
|
15
|
-
- [Setup & Configuration](./setup.md) — Installation, auth modes,
|
|
15
|
+
- [Setup & Configuration](./setup.md) — Installation, auth modes, JSON config, named scopes, and config reference.
|
|
16
16
|
- [Insiders, Outsiders & Sharing](./sharing.md) — The access model, HMAC key derivation, expiring links, and key rotation.
|
|
17
17
|
- [Exporting & Downloads](./exports.md) — PDF, DOCX, SVG, PNG, and ZIP export via Puppeteer and bundled Mermaid/PlantUML.
|
|
18
18
|
- [Event Gateway](./event-gateway.md) — Webhook receiving, JSON Schema matching, JsonMap body transforms, and durable queue processing.
|
package/guides/setup.md
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Setup & Configuration"
|
|
3
|
+
---
|
|
4
|
+
|
|
1
5
|
# Setup & Configuration
|
|
2
6
|
|
|
3
7
|
## Prerequisites
|
|
@@ -15,26 +19,17 @@ The `postinstall` script automatically downloads the PlantUML jar for local diag
|
|
|
15
19
|
|
|
16
20
|
## Configuration
|
|
17
21
|
|
|
18
|
-
Jeeves Server uses
|
|
22
|
+
Jeeves Server uses **JSON-only** configuration. Config files follow the path convention `<configDir>/jeeves-server/config.json`.
|
|
19
23
|
|
|
20
24
|
```bash
|
|
21
|
-
#
|
|
22
|
-
jeeves-server
|
|
25
|
+
# Generate a starter config
|
|
26
|
+
jeeves-server init --config /path/to/config-dir
|
|
23
27
|
|
|
24
|
-
#
|
|
25
|
-
jeeves-server
|
|
26
|
-
|
|
27
|
-
# TypeScript (for type checking during authoring)
|
|
28
|
-
jeeves-server.config.ts
|
|
29
|
-
|
|
30
|
-
# Or any cosmiconfig-supported format
|
|
28
|
+
# Or specify an explicit path
|
|
29
|
+
jeeves-server start --config /path/to/jeeves-server/config.json
|
|
31
30
|
```
|
|
32
31
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
```bash
|
|
36
|
-
jeeves-server start --config /path/to/jeeves-server.config.json
|
|
37
|
-
```
|
|
32
|
+
Legacy paths (`jeeves-server.config.json`) are auto-migrated to the new convention on first use.
|
|
38
33
|
|
|
39
34
|
### Config structure (JSON)
|
|
40
35
|
|
|
@@ -96,7 +91,13 @@ String values in the config support `${VAR_NAME}` substitution from `process.env
|
|
|
96
91
|
jeeves-server config validate [--config <path>]
|
|
97
92
|
```
|
|
98
93
|
|
|
99
|
-
This loads and validates the config against the Zod schema, reporting any errors. Use `config
|
|
94
|
+
This loads and validates the config against the Zod schema, reporting any errors. Use `config [jsonpath]` to query the fully resolved configuration:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
jeeves-server config # Full resolved config
|
|
98
|
+
jeeves-server config '$.port' # Query specific field
|
|
99
|
+
jeeves-server config '$.auth.modes' # Nested query
|
|
100
|
+
```
|
|
100
101
|
|
|
101
102
|
### Platform-specific settings
|
|
102
103
|
|
package/guides/sharing.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@karmaniverous/jeeves-server",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.5.1",
|
|
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",
|
|
@@ -49,18 +49,18 @@
|
|
|
49
49
|
"@commander-js/extra-typings": "^14.0.0",
|
|
50
50
|
"@fastify/cookie": "^11.0.2",
|
|
51
51
|
"@fastify/static": "^8.3.0",
|
|
52
|
-
"@karmaniverous/jeeves": "^0.
|
|
52
|
+
"@karmaniverous/jeeves": "^0.4.5",
|
|
53
53
|
"@karmaniverous/jsonmap": "^0.3.1",
|
|
54
54
|
"@mermaid-js/mermaid-cli": "^11.12.0",
|
|
55
55
|
"@turbodocx/html-to-docx": "^1.1.0",
|
|
56
56
|
"ajv": "^8.17.1",
|
|
57
57
|
"archiver": "^7.0.1",
|
|
58
58
|
"cheerio": "^1.2.0",
|
|
59
|
-
"cosmiconfig": "^9.0.1",
|
|
60
59
|
"fastify": "^5.2.3",
|
|
61
60
|
"lz-string": "^1.5.0",
|
|
62
61
|
"marked": "^17.0.1",
|
|
63
62
|
"mime-types": "^3.0.2",
|
|
63
|
+
"package-directory": "^8.2.0",
|
|
64
64
|
"picomatch": "^4.0.3",
|
|
65
65
|
"plantuml-encoder": "^1.4.0",
|
|
66
66
|
"puppeteer": "^23.11.1",
|
|
@@ -25,7 +25,10 @@ const VALID_CONFIG = {
|
|
|
25
25
|
};
|
|
26
26
|
|
|
27
27
|
function writeConfig(dir: string, config: unknown): string {
|
|
28
|
-
|
|
28
|
+
// Write to the new convention path: {dir}/jeeves-server/config.json
|
|
29
|
+
const configDir = path.join(dir, 'jeeves-server');
|
|
30
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
31
|
+
const filePath = path.join(configDir, 'config.json');
|
|
29
32
|
fs.writeFileSync(filePath, JSON.stringify(config));
|
|
30
33
|
return filePath;
|
|
31
34
|
}
|
|
@@ -47,13 +50,10 @@ describe('jeeves-server config validate', () => {
|
|
|
47
50
|
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
48
51
|
});
|
|
49
52
|
|
|
50
|
-
it('validates a valid config and prints
|
|
53
|
+
it('validates a valid config and prints success', async () => {
|
|
51
54
|
const configPath = writeConfig(tmpDir, VALID_CONFIG);
|
|
52
55
|
const { stdout } = await runCli(['config', 'validate', '-c', configPath]);
|
|
53
|
-
expect(stdout).toContain('
|
|
54
|
-
expect(stdout).toContain('Port: 8765');
|
|
55
|
-
expect(stdout).toContain('Auth modes: keys');
|
|
56
|
-
expect(stdout).toContain('Keys: 2');
|
|
56
|
+
expect(stdout).toContain('Config is valid');
|
|
57
57
|
});
|
|
58
58
|
|
|
59
59
|
it('exits with error for invalid config', async () => {
|
|
@@ -65,43 +65,3 @@ describe('jeeves-server config validate', () => {
|
|
|
65
65
|
});
|
|
66
66
|
});
|
|
67
67
|
});
|
|
68
|
-
|
|
69
|
-
describe('jeeves-server config show', () => {
|
|
70
|
-
let tmpDir: string;
|
|
71
|
-
|
|
72
|
-
beforeEach(() => {
|
|
73
|
-
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'jeeves-cli-'));
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
afterEach(() => {
|
|
77
|
-
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it('shows resolved config with key and insider details', async () => {
|
|
81
|
-
const configPath = writeConfig(tmpDir, {
|
|
82
|
-
...VALID_CONFIG,
|
|
83
|
-
insiders: { 'test@example.com': {} },
|
|
84
|
-
watcherUrl: 'http://localhost:3458',
|
|
85
|
-
});
|
|
86
|
-
const { stdout } = await runCli(['config', 'show', '-c', configPath]);
|
|
87
|
-
expect(stdout).toContain('Config file:');
|
|
88
|
-
expect(stdout).toContain('port: 8765');
|
|
89
|
-
expect(stdout).toContain('modes: keys');
|
|
90
|
-
expect(stdout).toContain('primary:');
|
|
91
|
-
expect(stdout).toContain('unscoped');
|
|
92
|
-
expect(stdout).toContain('test@example.com');
|
|
93
|
-
expect(stdout).toContain('watcherUrl: http://localhost:3458');
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
it('shows scoped keys correctly', async () => {
|
|
97
|
-
const configPath = writeConfig(tmpDir, {
|
|
98
|
-
...VALID_CONFIG,
|
|
99
|
-
keys: {
|
|
100
|
-
...VALID_CONFIG.keys,
|
|
101
|
-
scoped: { key: 'c'.repeat(64), scopes: ['/docs'] },
|
|
102
|
-
},
|
|
103
|
-
});
|
|
104
|
-
const { stdout } = await runCli(['config', 'show', '-c', configPath]);
|
|
105
|
-
expect(stdout).toContain('scoped (allow: 1, deny: 0)');
|
|
106
|
-
});
|
|
107
|
-
});
|
package/src/cli/index.ts
CHANGED
|
@@ -1,25 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* @packageDocumentation
|
|
4
|
-
*
|
|
5
3
|
* jeeves-server CLI entrypoint.
|
|
6
|
-
*
|
|
4
|
+
*
|
|
5
|
+
* Uses `createServiceCli(descriptor)` from core for all standard commands.
|
|
6
|
+
* The `start` command uses `descriptor.startCommand` which points to
|
|
7
|
+
* `start-server.ts` for direct in-process server launch.
|
|
8
|
+
*
|
|
9
|
+
* @packageDocumentation
|
|
7
10
|
*/
|
|
8
11
|
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
import { packageVersion } from '../util/packageVersion.js';
|
|
12
|
-
import { registerConfigCommand } from './commands/config.js';
|
|
13
|
-
import { registerServiceCommand } from './commands/service.js';
|
|
14
|
-
import { registerStartCommand } from './commands/start.js';
|
|
12
|
+
import { createServiceCli } from '@karmaniverous/jeeves';
|
|
15
13
|
|
|
16
|
-
|
|
17
|
-
.name('jeeves-server')
|
|
18
|
-
.description('Self-hosted file browser, document server, and webhook gateway')
|
|
19
|
-
.version(packageVersion);
|
|
14
|
+
import { serverDescriptor } from '../descriptor.js';
|
|
20
15
|
|
|
21
|
-
|
|
22
|
-
registerConfigCommand(cli);
|
|
23
|
-
registerServiceCommand(cli);
|
|
16
|
+
const cli = createServiceCli(serverDescriptor);
|
|
24
17
|
|
|
25
18
|
cli.parse();
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Minimal server launcher for use by system service managers (NSSM, systemd, launchd).
|
|
4
|
+
*
|
|
5
|
+
* This is the entry point referenced by `descriptor.startCommand`. It initializes
|
|
6
|
+
* config from the `--config` CLI argument and starts the Fastify server directly,
|
|
7
|
+
* without going through the full Commander CLI.
|
|
8
|
+
*
|
|
9
|
+
* The CLI's `start` command uses this same logic in-process.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { initConfig } from '../config/index.js';
|
|
13
|
+
|
|
14
|
+
const configIndex = process.argv.indexOf('--config');
|
|
15
|
+
const configPath =
|
|
16
|
+
configIndex !== -1 ? process.argv[configIndex + 1] : undefined;
|
|
17
|
+
|
|
18
|
+
initConfig(configPath);
|
|
19
|
+
|
|
20
|
+
// Dynamic import to ensure config is initialized before server modules load
|
|
21
|
+
await import('../server.js');
|