@karmaniverous/jeeves-server 3.4.2 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/.tsbuildinfo +1 -1
  2. package/CHANGELOG.md +38 -1
  3. package/README.md +18 -17
  4. package/client/package.json +19 -19
  5. package/client/src/components/SearchModal.tsx +11 -1
  6. package/client/src/components/layout/Header.tsx +3 -3
  7. package/client/src/lib/api.ts +10 -5
  8. package/dist/client/assets/CodeEditor-Brh86AGF.js +1 -0
  9. package/dist/client/assets/CodeViewer-Cegj3cEn.js +1 -0
  10. package/dist/client/assets/dist-2YqVIvgv.js +2 -0
  11. package/dist/client/assets/dist-5vamY028.js +1 -0
  12. package/dist/client/assets/dist-6_auAGci.js +1 -0
  13. package/dist/client/assets/dist-B0kq1DQG.js +1 -0
  14. package/dist/client/assets/dist-B2SZD_eN.js +1 -0
  15. package/dist/client/assets/dist-B2t4dYA2.js +1 -0
  16. package/dist/client/assets/dist-B5gFYAn7.js +1 -0
  17. package/dist/client/assets/dist-BPy6CnYN.js +1 -0
  18. package/dist/client/assets/dist-CL6VCrQn.js +9 -0
  19. package/dist/client/assets/dist-CWsHar9N.js +1 -0
  20. package/dist/client/assets/dist-CnFc5Ssx.js +1 -0
  21. package/dist/client/assets/dist-DSgLBuTS.js +1 -0
  22. package/dist/client/assets/dist-DUcac0X_.js +7 -0
  23. package/dist/client/assets/dist-DcTcc-BG.js +6 -0
  24. package/dist/client/assets/dist-DvfTyWk_.js +1 -0
  25. package/dist/client/assets/dist-Dz1Ulpqa.js +1 -0
  26. package/dist/client/assets/dist-Kr-mUYW1.js +5 -0
  27. package/dist/client/assets/dist-OX4k3MMG.js +2 -0
  28. package/dist/client/assets/dist-qiU0qoeK.js +1 -0
  29. package/dist/client/assets/dist-ui4J6fvl.js +23 -0
  30. package/dist/client/assets/index-Dk_myGs4.css +2 -0
  31. package/dist/client/assets/index-DrBXupPz.js +62 -0
  32. package/dist/client/assets/theme-CPpIxvB0.js +2 -0
  33. package/dist/client/index.html +3 -2
  34. package/dist/src/cli/commands/config.test.js +5 -40
  35. package/dist/src/cli/index.js +9 -15
  36. package/dist/src/cli/start-server.js +16 -0
  37. package/dist/src/config/index.js +48 -37
  38. package/dist/src/config/loadConfig.test.js +27 -25
  39. package/dist/src/config/migration.js +60 -0
  40. package/dist/src/config/schema.js +4 -3
  41. package/dist/src/descriptor.js +46 -0
  42. package/dist/src/routes/api/diagramExport.js +101 -0
  43. package/dist/src/routes/api/diagramExport.test.js +134 -0
  44. package/dist/src/routes/api/events.js +13 -0
  45. package/dist/src/routes/api/export.js +6 -82
  46. package/dist/src/routes/api/index.js +4 -0
  47. package/dist/src/routes/api/search.js +9 -50
  48. package/dist/src/routes/api/sharing.js +40 -23
  49. package/dist/src/routes/api/sharing.test.js +52 -0
  50. package/dist/src/routes/auth.js +1 -1
  51. package/dist/src/routes/config.js +8 -2
  52. package/dist/src/routes/keys.js +4 -4
  53. package/dist/src/routes/path/index.js +1 -1
  54. package/dist/src/routes/status.js +15 -16
  55. package/dist/src/routes/status.test.js +13 -8
  56. package/dist/src/server.js +21 -16
  57. package/dist/src/services/markdown.js +2 -1
  58. package/dist/src/services/markdown.test.js +22 -0
  59. package/dist/src/util/packageVersion.js +7 -16
  60. package/dist/src/util/packageVersion.test.js +7 -0
  61. package/guides/api-integration.md +4 -0
  62. package/guides/deployment.md +11 -10
  63. package/guides/event-gateway.md +4 -0
  64. package/guides/exports.md +4 -0
  65. package/guides/index.md +1 -1
  66. package/guides/setup.md +17 -16
  67. package/guides/sharing.md +4 -0
  68. package/package.json +3 -3
  69. package/scripts/download-plantuml.js +0 -1
  70. package/src/cli/commands/config.test.ts +5 -45
  71. package/src/cli/index.ts +9 -16
  72. package/src/cli/start-server.ts +21 -0
  73. package/src/config/index.ts +56 -43
  74. package/src/config/loadConfig.test.ts +27 -29
  75. package/src/config/migration.ts +76 -0
  76. package/src/config/schema.ts +5 -4
  77. package/src/descriptor.ts +55 -0
  78. package/src/routes/api/diagramExport.test.ts +200 -0
  79. package/src/routes/api/diagramExport.ts +170 -0
  80. package/src/routes/api/events.ts +22 -0
  81. package/src/routes/api/export.ts +6 -131
  82. package/src/routes/api/index.ts +4 -0
  83. package/src/routes/api/search.ts +9 -63
  84. package/src/routes/api/sharing.test.ts +66 -0
  85. package/src/routes/api/sharing.ts +47 -23
  86. package/src/routes/auth.ts +1 -1
  87. package/src/routes/config.ts +15 -2
  88. package/src/routes/keys.ts +4 -4
  89. package/src/routes/path/index.ts +1 -1
  90. package/src/routes/status.test.ts +14 -8
  91. package/src/routes/status.ts +56 -62
  92. package/src/server.ts +29 -17
  93. package/src/services/markdown.test.ts +26 -0
  94. package/src/services/markdown.ts +2 -1
  95. package/src/util/packageVersion.test.ts +9 -0
  96. package/src/util/packageVersion.ts +11 -18
  97. package/src/util/platform.ts +1 -1
  98. package/dist/client/assets/CodeEditor-DQZZL5Rq.js +0 -1
  99. package/dist/client/assets/CodeViewer-ofJVD1Vn.js +0 -1
  100. package/dist/client/assets/index--MBieNJA.js +0 -1
  101. package/dist/client/assets/index-BENeXQI_.js +0 -1
  102. package/dist/client/assets/index-BbBpoOxz.js +0 -1
  103. package/dist/client/assets/index-BdV9g5AM.js +0 -6
  104. package/dist/client/assets/index-BjAilRri.js +0 -2
  105. package/dist/client/assets/index-BqbhWo2I.js +0 -3
  106. package/dist/client/assets/index-CVbycZ0H.js +0 -1
  107. package/dist/client/assets/index-Cs5oz2oJ.js +0 -5
  108. package/dist/client/assets/index-D-RC7ZS6.css +0 -1
  109. package/dist/client/assets/index-D8KZVveX.js +0 -1
  110. package/dist/client/assets/index-DC4HMHxY.js +0 -13
  111. package/dist/client/assets/index-DcY2RXqX.js +0 -1
  112. package/dist/client/assets/index-Duy-tZYV.js +0 -1
  113. package/dist/client/assets/index-Dw7rDFmE.js +0 -7
  114. package/dist/client/assets/index-FlCUvrjv.js +0 -2
  115. package/dist/client/assets/index-K6OVmfhg.js +0 -1
  116. package/dist/client/assets/index-MLwyFRN0.js +0 -1
  117. package/dist/client/assets/index-OpqBpSjn.js +0 -1
  118. package/dist/client/assets/index-SsHei0HE.js +0 -1
  119. package/dist/client/assets/index-jSGuHSeS.js +0 -62
  120. package/dist/client/assets/index-uQa2yckk.js +0 -1
  121. package/dist/client/assets/index-udkXoIER.js +0 -1
  122. package/dist/src/cli/commands/config.js +0 -105
  123. package/dist/src/cli/commands/service.js +0 -93
  124. package/dist/src/cli/commands/start.js +0 -24
  125. package/src/cli/commands/config.ts +0 -117
  126. package/src/cli/commands/service.ts +0 -129
  127. package/src/cli/commands/start.ts +0 -27
@@ -2,6 +2,8 @@
2
2
  * Sharing API routes.
3
3
  *
4
4
  * Handles: /api/share, /api/util/share-for, /api/readme-link, /api/rotate-key
5
+ *
6
+ * @packageDocumentation
5
7
  */
6
8
 
7
9
  import crypto from 'node:crypto';
@@ -9,7 +11,7 @@ import fs from 'node:fs';
9
11
  import path from 'node:path';
10
12
  import { fileURLToPath } from 'node:url';
11
13
 
12
- import type { FastifyPluginAsync } from 'fastify';
14
+ import type { FastifyPluginAsync, FastifyReply } from 'fastify';
13
15
 
14
16
  import { _pathMatchesScopes } from '../../auth/keys.js';
15
17
  import { findInsider } from '../../auth/resolve.js';
@@ -24,20 +26,41 @@ import {
24
26
  import { fsPathToUrl, getRoots } from '../../util/platform.js';
25
27
  import { setInsiderKey } from '../../util/state.js';
26
28
 
29
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
30
+ const serverRoot = path.resolve(__dirname, '..', '..', '..');
31
+
32
+ /** Build a deep-share browse URL from its constituent parts. */
33
+ function buildDeepShareUrl(
34
+ targetPath: string,
35
+ key: string,
36
+ params: { depth: number; dirs: boolean; stack: string; exp?: string },
37
+ ): string {
38
+ let url = `/browse${targetPath}?key=${key}&d=${String(params.depth)}&dirs=${params.dirs ? '1' : '0'}&s=${params.stack}`;
39
+ if (params.exp) url += `&exp=${params.exp}`;
40
+ return url;
41
+ }
42
+
27
43
  // eslint-disable-next-line @typescript-eslint/require-await
28
44
  export const sharingRoutes: FastifyPluginAsync = async (fastify) => {
29
45
  const roots = getRoots(getConfig().roots);
30
46
 
47
+ /** Resolve the _internal key seed, or send a 503 error. */
48
+ function getInternalSeed(reply: FastifyReply): string | null {
49
+ const internalKey = getConfig().resolvedKeys.find(
50
+ (k) => k.name === '_internal',
51
+ );
52
+ if (!internalKey?.seed) {
53
+ void reply.code(503).send({ error: 'No _internal key configured' });
54
+ return null;
55
+ }
56
+ return internalKey.seed;
57
+ }
58
+
31
59
  // GET /api/readme-link
32
60
  fastify.get('/api/readme-link', async (_request, reply) => {
33
- const config = getConfig();
34
- const internalKey = config.resolvedKeys.find((k) => k.name === '_internal');
35
- if (!internalKey?.seed)
36
- return reply.code(503).send({ error: 'No _internal key configured' });
61
+ const seed = getInternalSeed(reply);
62
+ if (!seed) return;
37
63
 
38
- const seed = internalKey.seed;
39
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
40
- const serverRoot = path.resolve(__dirname, '..', '..', '..');
41
64
  const readmePath = path.join(serverRoot, 'README.md');
42
65
  if (!fs.existsSync(readmePath))
43
66
  return reply.code(404).send({ error: 'README.md not found' });
@@ -46,25 +69,21 @@ export const sharingRoutes: FastifyPluginAsync = async (fastify) => {
46
69
  const stack = encodeStack([urlPath]);
47
70
  const deepParams = { depth: 2, dirs: false, stack, exp: undefined };
48
71
  const key = computeDeepShareKey(seed, urlPath, deepParams);
49
- const shareUrl = `/browse${urlPath}?key=${key}&d=2&dirs=0&s=${stack}`;
50
72
 
51
- return reply.send({ url: shareUrl });
73
+ return reply.send({
74
+ url: buildDeepShareUrl(urlPath, key, { depth: 2, dirs: false, stack }),
75
+ });
52
76
  });
53
77
 
54
78
  // GET /api/content-link/:file — share link for content/*.md (terms, privacy)
55
79
  fastify.get('/api/content-link/:file', async (request, reply) => {
56
- const config = getConfig();
57
- const internalKey = config.resolvedKeys.find((k) => k.name === '_internal');
58
- if (!internalKey?.seed)
59
- return reply.code(503).send({ error: 'No _internal key configured' });
80
+ const seed = getInternalSeed(reply);
81
+ if (!seed) return;
60
82
 
61
83
  const { file } = request.params as { file: string };
62
84
  if (!/^[\w-]+$/.test(file))
63
85
  return reply.code(400).send({ error: 'Invalid file name' });
64
86
 
65
- const seed = internalKey.seed;
66
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
67
- const serverRoot = path.resolve(__dirname, '..', '..', '..');
68
87
  const contentPath = path.join(serverRoot, 'content', `${file}.md`);
69
88
  if (!fs.existsSync(contentPath))
70
89
  return reply.code(404).send({ error: `${file}.md not found` });
@@ -73,9 +92,10 @@ export const sharingRoutes: FastifyPluginAsync = async (fastify) => {
73
92
  const stack = encodeStack([urlPath]);
74
93
  const deepParams = { depth: 0, dirs: false, stack, exp: undefined };
75
94
  const key = computeDeepShareKey(seed, urlPath, deepParams);
76
- const shareUrl = `/browse${urlPath}?key=${key}&d=0&dirs=0&s=${stack}`;
77
95
 
78
- return reply.send({ url: shareUrl });
96
+ return reply.send({
97
+ url: buildDeepShareUrl(urlPath, key, { depth: 0, dirs: false, stack }),
98
+ });
79
99
  });
80
100
 
81
101
  // POST /api/share
@@ -100,8 +120,12 @@ export const sharingRoutes: FastifyPluginAsync = async (fastify) => {
100
120
  exp: expiry,
101
121
  };
102
122
  outsiderKey = computeDeepShareKey(seed, targetPath, deepParams);
103
- shareUrl = `/browse${targetPath}?key=${outsiderKey}&d=${String(depth ?? 0)}&dirs=${dirs ? '1' : '0'}&s=${stack}`;
104
- if (expiry) shareUrl += `&exp=${expiry}`;
123
+ shareUrl = buildDeepShareUrl(targetPath, outsiderKey, {
124
+ depth: depth ?? 0,
125
+ dirs: dirs ?? false,
126
+ stack,
127
+ exp: expiry,
128
+ });
105
129
  } else if (expiry) {
106
130
  outsiderKey = computeOutsiderKeyWithExpiry(seed, targetPath, expiry);
107
131
  shareUrl = `/browse${targetPath}?key=${outsiderKey}&exp=${expiry}`;
@@ -120,7 +144,7 @@ export const sharingRoutes: FastifyPluginAsync = async (fastify) => {
120
144
  });
121
145
 
122
146
  // POST /api/rotate-key
123
- fastify.post('/api/rotate-key', async (request, reply) => {
147
+ fastify.post('/api/rotate-key', (request, reply) => {
124
148
  const insiderEmail = request.insiderEmail;
125
149
  if (!insiderEmail)
126
150
  return reply.code(403).send({ error: 'Insider auth required' });
@@ -132,7 +156,7 @@ export const sharingRoutes: FastifyPluginAsync = async (fastify) => {
132
156
  const newSeed = crypto.randomBytes(32).toString('hex');
133
157
  const now = new Date().toISOString();
134
158
  setInsiderKey(insider.email, newSeed, now);
135
- await resetConfig();
159
+ resetConfig();
136
160
 
137
161
  return reply.send({ ok: true, keyCreatedAt: now });
138
162
  });
@@ -108,7 +108,7 @@ export const authRoute: FastifyPluginAsync = async (fastify) => {
108
108
 
109
109
  // Persist to state.json (mutable runtime state)
110
110
  setInsiderKey(insider.email, newSeed, timestamp);
111
- await resetConfig(); // Reload to pick up new state
111
+ resetConfig(); // Reload to pick up new state
112
112
  }
113
113
 
114
114
  // Set session cookie
@@ -6,11 +6,15 @@
6
6
  * @packageDocumentation
7
7
  */
8
8
 
9
- import { createConfigQueryHandler } from '@karmaniverous/jeeves';
9
+ import {
10
+ createConfigApplyHandler,
11
+ createConfigQueryHandler,
12
+ } from '@karmaniverous/jeeves';
10
13
  import type { FastifyInstance } from 'fastify';
11
14
 
12
15
  import { getConfig } from '../config/index.js';
13
16
  import type { RuntimeConfig } from '../config/types.js';
17
+ import { serverDescriptor } from '../descriptor.js';
14
18
 
15
19
  /** Return a sanitized copy of the config (redact sensitive fields). */
16
20
  export function sanitizeConfig(config: RuntimeConfig): unknown {
@@ -35,7 +39,7 @@ export function sanitizeConfig(config: RuntimeConfig): unknown {
35
39
  };
36
40
  }
37
41
 
38
- /** Register the GET /config route. */
42
+ /** Register the GET /config and POST /config/apply routes. */
39
43
  export function registerConfigRoute(app: FastifyInstance): void {
40
44
  const configHandler = createConfigQueryHandler(() =>
41
45
  sanitizeConfig(getConfig()),
@@ -46,4 +50,13 @@ export function registerConfigRoute(app: FastifyInstance): void {
46
50
  const result = await configHandler({ path });
47
51
  return reply.status(result.status).send(result.body);
48
52
  });
53
+
54
+ const applyHandler = createConfigApplyHandler(serverDescriptor);
55
+
56
+ app.post('/config/apply', async (request, reply) => {
57
+ const result = await applyHandler(
58
+ request.body as { patch: Record<string, unknown>; replace?: boolean },
59
+ );
60
+ return reply.status(result.status).send(result.body);
61
+ });
49
62
  }
@@ -86,7 +86,7 @@ export const keysRoute: FastifyPluginAsync = async (fastify) => {
86
86
  const insiderResult = resolveInsiderKeyAuth(config, provided);
87
87
  if (insiderResult.valid && insiderResult.email) {
88
88
  // Insider key rotation
89
- return await rotateInsiderSeed(insiderResult.email, config);
89
+ return rotateInsiderSeed(insiderResult.email, config);
90
90
  }
91
91
 
92
92
  // Machine key rotation is not supported with TS config
@@ -105,7 +105,7 @@ export const keysRoute: FastifyPluginAsync = async (fastify) => {
105
105
  // Try session-based auth
106
106
  const sessionResult = resolveSessionAuth(config, request);
107
107
  if (sessionResult.valid && sessionResult.email) {
108
- return await rotateInsiderSeed(sessionResult.email, config);
108
+ return rotateInsiderSeed(sessionResult.email, config);
109
109
  }
110
110
 
111
111
  return reply.code(401).send({ error: 'Invalid insider key' });
@@ -149,7 +149,7 @@ export const keysRoute: FastifyPluginAsync = async (fastify) => {
149
149
  );
150
150
  };
151
151
 
152
- async function rotateInsiderSeed(
152
+ function rotateInsiderSeed(
153
153
  email: string,
154
154
  config: ReturnType<typeof getConfig>,
155
155
  ) {
@@ -166,7 +166,7 @@ async function rotateInsiderSeed(
166
166
  at: timestamp,
167
167
  });
168
168
  setKeyRotationTimestamp(timestamp);
169
- await resetConfig();
169
+ resetConfig();
170
170
 
171
171
  return { ok: true, keyName: insider.email };
172
172
  }
@@ -16,7 +16,7 @@ export const pathRoute: FastifyPluginAsync = async (fastify) => {
16
16
  '/path/*',
17
17
  async (request, reply) => {
18
18
  const reqPath = request.params['*'];
19
- const url = new URL(request.url, 'http://localhost');
19
+ const url = new URL(request.url, 'http://127.0.0.1');
20
20
  const query = url.search;
21
21
  return reply.redirect(`/browse/${reqPath}${query}`);
22
22
  },
@@ -31,7 +31,7 @@ vi.mock('../config/index.js', () => ({
31
31
  const { statusRoutes } = await import('./status.js');
32
32
 
33
33
  describe('GET /status', () => {
34
- it('returns structured status', async () => {
34
+ it('returns structured status with SDK shape', async () => {
35
35
  // Create a minimal Fastify-like test harness
36
36
  const routes: Record<string, (req: unknown) => Promise<unknown>> = {};
37
37
  const fakeFastify = {
@@ -48,14 +48,20 @@ describe('GET /status', () => {
48
48
  const result = await handler({ accessMode: 'insider', query: {} });
49
49
  const status = result as Record<string, unknown>;
50
50
 
51
+ // Standard SDK fields at top level
52
+ expect(status).toHaveProperty('name', 'server');
51
53
  expect(status).toHaveProperty('version');
52
54
  expect(status).toHaveProperty('uptime');
53
- expect(status.port).toBe(1934);
54
- expect((status.chrome as { configured: boolean }).configured).toBe(true);
55
- expect((status.auth as { insiderCount: number }).insiderCount).toBe(2);
56
- expect((status.auth as { keyCount: number }).keyCount).toBe(1);
57
- expect(status.events).toHaveLength(2);
58
- const exports = status.exports as {
55
+ expect(status).toHaveProperty('status', 'healthy');
56
+
57
+ // Server-specific fields nested under health
58
+ const health = status.health as Record<string, unknown>;
59
+ expect(health.port).toBe(1934);
60
+ expect((health.chrome as { configured: boolean }).configured).toBe(true);
61
+ expect((health.auth as { insiderCount: number }).insiderCount).toBe(2);
62
+ expect((health.auth as { keyCount: number }).keyCount).toBe(1);
63
+ expect(health.events).toHaveLength(2);
64
+ const exports = health.exports as {
59
65
  documents: string[];
60
66
  directories: string[];
61
67
  diagrams: string[];
@@ -65,6 +71,6 @@ describe('GET /status', () => {
65
71
  expect(exports.directories).toEqual(['zip']);
66
72
  expect(exports.diagrams).toEqual(['svg', 'png']);
67
73
  expect(exports.chromeAvailable).toBe(true);
68
- expect((status.diagrams as { mermaid: boolean }).mermaid).toBe(true);
74
+ expect((health.diagrams as { mermaid: boolean }).mermaid).toBe(true);
69
75
  });
70
76
  });
@@ -1,18 +1,16 @@
1
1
  /**
2
- * Server status endpoint — structured metadata for diagnostics and TOOLS.md generation.
2
+ * Server status endpoint — uses the SDK's `createStatusHandler` factory.
3
3
  *
4
- * Returns version, uptime, port, connected services reachability,
5
- * event schemas, insider count (no PII), and export capabilities.
4
+ * Returns standard `{ name, version, uptime, status, health }` shape
5
+ * with server-specific details nested under `health`.
6
6
  */
7
7
 
8
+ import { createStatusHandler } from '@karmaniverous/jeeves';
8
9
  import type { FastifyPluginAsync } from 'fastify';
9
10
 
10
11
  import { getConfig } from '../config/index.js';
11
- import { getRecentEvents } from '../services/eventLog.js';
12
12
  import { packageVersion } from '../util/packageVersion.js';
13
13
 
14
- const startTime = Date.now();
15
-
16
14
  interface ServiceStatus {
17
15
  url: string;
18
16
  reachable: boolean;
@@ -20,7 +18,6 @@ interface ServiceStatus {
20
18
  }
21
19
 
22
20
  async function checkService(url: string): Promise<ServiceStatus> {
23
- // Try /status first (watcher), then /health (runner)
24
21
  for (const endpoint of ['/status', '/health']) {
25
22
  try {
26
23
  const res = await fetch(`${url}${endpoint}`, {
@@ -37,62 +34,59 @@ async function checkService(url: string): Promise<ServiceStatus> {
37
34
  return { url, reachable: false };
38
35
  }
39
36
 
40
- // eslint-disable-next-line @typescript-eslint/require-await
41
- export const statusRoutes: FastifyPluginAsync = async (fastify) => {
42
- fastify.get<{ Querystring: { events?: string } }>(
43
- '/status',
44
- async (request) => {
45
- const config = getConfig();
37
+ const handleStatus = createStatusHandler({
38
+ name: 'server',
39
+ version: packageVersion,
40
+ getHealth: async () => {
41
+ const config = getConfig();
46
42
 
47
- const [watcher, runner, meta] = await Promise.all([
48
- config.watcherUrl ? checkService(config.watcherUrl) : null,
49
- config.runnerUrl ? checkService(config.runnerUrl) : null,
50
- config.metaUrl ? checkService(config.metaUrl) : null,
51
- ]);
43
+ const [watcher, runner, meta] = await Promise.all([
44
+ config.watcherUrl ? checkService(config.watcherUrl) : null,
45
+ config.runnerUrl ? checkService(config.runnerUrl) : null,
46
+ config.metaUrl ? checkService(config.metaUrl) : null,
47
+ ]);
52
48
 
53
- return {
54
- version: packageVersion,
55
- uptime: Math.floor((Date.now() - startTime) / 1000),
56
- port: config.port,
57
- chrome: {
58
- configured: Boolean(config.chromePath),
59
- path: config.chromePath,
60
- },
61
- auth: {
62
- modes: config.authModes,
63
- insiderCount: config.resolvedInsiders.length,
64
- keyCount: config.resolvedKeys.length,
65
- },
66
- events: Object.entries(config.events).map(([name, schema]) => ({
67
- name,
68
- cmd: schema.cmd,
69
- })),
70
- exports: {
71
- documents: ['pdf', 'docx'],
72
- directories: ['zip'],
73
- diagrams: ['svg', 'png'],
74
- chromeAvailable: Boolean(config.chromePath),
49
+ return {
50
+ port: config.port,
51
+ chrome: {
52
+ configured: Boolean(config.chromePath),
53
+ path: config.chromePath,
54
+ },
55
+ auth: {
56
+ modes: config.authModes,
57
+ insiderCount: config.resolvedInsiders.length,
58
+ keyCount: config.resolvedKeys.length,
59
+ },
60
+ events: Object.entries(config.events).map(([name, schema]) => ({
61
+ name,
62
+ cmd: schema.cmd,
63
+ })),
64
+ exports: {
65
+ documents: ['pdf', 'docx'],
66
+ directories: ['zip'],
67
+ diagrams: ['svg', 'png'],
68
+ chromeAvailable: Boolean(config.chromePath),
69
+ },
70
+ diagrams: {
71
+ mermaid: true,
72
+ plantuml: {
73
+ localJar: Boolean(config.plantuml.jarPath),
74
+ servers: config.plantuml.servers,
75
75
  },
76
- diagrams: {
77
- mermaid: true,
78
- plantuml: {
79
- localJar: Boolean(config.plantuml.jarPath),
80
- servers: config.plantuml.servers,
81
- },
82
- },
83
- services: {
84
- watcher,
85
- runner,
86
- meta,
87
- },
88
- ...(request.query.events
89
- ? {
90
- eventLog: getRecentEvents(
91
- Math.min(parseInt(request.query.events, 10) || 20, 100),
92
- ),
93
- }
94
- : {}),
95
- };
96
- },
97
- );
76
+ },
77
+ services: {
78
+ watcher,
79
+ runner,
80
+ meta,
81
+ },
82
+ };
83
+ },
84
+ });
85
+
86
+ // eslint-disable-next-line @typescript-eslint/require-await
87
+ export const statusRoutes: FastifyPluginAsync = async (fastify) => {
88
+ fastify.get('/status', async () => {
89
+ const result = await handleStatus();
90
+ return result.body;
91
+ });
98
92
  };
package/src/server.ts CHANGED
@@ -9,7 +9,7 @@ import { fileURLToPath } from 'node:url';
9
9
 
10
10
  import cookie from '@fastify/cookie';
11
11
  import fastifyStatic from '@fastify/static';
12
- import Fastify from 'fastify';
12
+ import Fastify, { type FastifyReply, type FastifyRequest } from 'fastify';
13
13
 
14
14
  import { getConfig, initConfig, isConfigInitialized } from './config/index.js';
15
15
  import { apiRoute } from './routes/api/index.js';
@@ -28,7 +28,7 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
28
28
 
29
29
  async function start() {
30
30
  try {
31
- const config = isConfigInitialized() ? getConfig() : await initConfig();
31
+ const config = isConfigInitialized() ? getConfig() : initConfig();
32
32
 
33
33
  const fastify = Fastify({
34
34
  logger: true,
@@ -60,21 +60,33 @@ async function start() {
60
60
  prefix: '/app/',
61
61
  });
62
62
 
63
- // SPA fallback for React routes
64
- fastify.get('/', async (_request, reply) => {
65
- return reply.sendFile('index.html', clientDir);
66
- });
67
- fastify.get('/browse', async (_request, reply) => {
68
- return reply.sendFile('index.html', clientDir);
69
- });
70
- fastify.get('/browse/*', async (_request, reply) => {
71
- return reply.sendFile('index.html', clientDir);
72
- });
73
- fastify.get('/runner', async (_request, reply) => {
74
- return reply.sendFile('index.html', clientDir);
75
- });
76
- fastify.get('/runner/*', async (_request, reply) => {
77
- return reply.sendFile('index.html', clientDir);
63
+ // SPA fallback — all these routes serve index.html for client-side routing
64
+ const spaFallback = async (
65
+ _request: FastifyRequest,
66
+ reply: FastifyReply,
67
+ ) => reply.sendFile('index.html', clientDir);
68
+
69
+ for (const route of [
70
+ '/',
71
+ '/browse',
72
+ '/browse/*',
73
+ '/runner',
74
+ '/runner/*',
75
+ ]) {
76
+ fastify.get(route, spaFallback);
77
+ }
78
+
79
+ // Catch-all: serve SPA for any unmatched GET under /browse or /runner
80
+ // (handles edge cases like dotfile paths that wildcard routes may miss)
81
+ fastify.setNotFoundHandler(async (request, reply) => {
82
+ if (
83
+ request.method === 'GET' &&
84
+ (request.url.startsWith('/browse') ||
85
+ request.url.startsWith('/runner'))
86
+ ) {
87
+ return reply.sendFile('index.html', clientDir);
88
+ }
89
+ return reply.code(404).send({ error: 'Not found' });
78
90
  });
79
91
  }
80
92
 
@@ -0,0 +1,26 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { parseMarkdown } from './markdown.js';
4
+
5
+ describe('parseMarkdown', () => {
6
+ it('decodes HTML entities in heading text for TOC', () => {
7
+ const md = '# Hello &amp; "World"';
8
+ const { headings } = parseMarkdown(md);
9
+ expect(headings).toHaveLength(1);
10
+ expect(headings[0].text).toBe('Hello & "World"');
11
+ });
12
+
13
+ it('strips HTML tags from heading text for TOC', () => {
14
+ const md = '## A <em>bold</em> heading';
15
+ const { headings } = parseMarkdown(md);
16
+ expect(headings).toHaveLength(1);
17
+ expect(headings[0].text).toBe('A bold heading');
18
+ });
19
+
20
+ it('decodes &#39; and &quot; entities in headings', () => {
21
+ const md = '### It&#39;s a &quot;test&quot;';
22
+ const { headings } = parseMarkdown(md);
23
+ expect(headings).toHaveLength(1);
24
+ expect(headings[0].text).toBe('It\'s a "test"');
25
+ });
26
+ });
@@ -4,6 +4,7 @@
4
4
 
5
5
  import fs from 'node:fs';
6
6
 
7
+ import * as cheerio from 'cheerio';
7
8
  import type { Token } from 'marked';
8
9
  import { marked } from 'marked';
9
10
 
@@ -135,7 +136,7 @@ export function parseMarkdown(
135
136
 
136
137
  headings.push({
137
138
  level,
138
- text: renderedText.replace(/<[^>]+>/g, ''),
139
+ text: cheerio.load(renderedText).text(),
139
140
  slug,
140
141
  });
141
142
 
@@ -0,0 +1,9 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { packageVersion } from './packageVersion.js';
4
+
5
+ describe('packageVersion', () => {
6
+ it('returns a valid semver version string', () => {
7
+ expect(packageVersion).toMatch(/^\d+\.\d+\.\d+/);
8
+ });
9
+ });
@@ -1,30 +1,23 @@
1
1
  /**
2
- * Resolve the service package version by walking up from the caller's directory.
2
+ * Resolve the service package version using package-directory.
3
3
  */
4
4
 
5
5
  import fs from 'node:fs';
6
6
  import path from 'node:path';
7
7
  import { fileURLToPath } from 'node:url';
8
8
 
9
- function findPackageJson(startDir: string): string {
10
- let dir = startDir;
11
- while (dir !== path.dirname(dir)) {
12
- const candidate = path.join(dir, 'package.json');
13
- if (fs.existsSync(candidate)) {
14
- const pkg = JSON.parse(fs.readFileSync(candidate, 'utf8')) as {
15
- name?: string;
16
- };
17
- // Find our package specifically, not the monorepo root
18
- if (pkg.name === '@karmaniverous/jeeves-server') return candidate;
19
- }
20
- dir = path.dirname(dir);
21
- }
22
- throw new Error('Could not find @karmaniverous/jeeves-server package.json');
23
- }
9
+ import { packageDirectorySync } from 'package-directory';
24
10
 
25
11
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
26
- const pkgPath = findPackageJson(__dirname);
27
- const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')) as { version: string };
12
+ const pkgDir = packageDirectorySync({ cwd: __dirname });
13
+ if (!pkgDir) {
14
+ throw new Error('Could not find package directory for jeeves-server');
15
+ }
16
+
17
+ const pkgPath = path.join(pkgDir, 'package.json');
18
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')) as {
19
+ version: string;
20
+ };
28
21
 
29
22
  /** The package version of the jeeves-server service package. */
30
23
  export const packageVersion: string = pkg.version;
@@ -10,7 +10,7 @@ import path from 'node:path';
10
10
 
11
11
  const IS_WINDOWS = process.platform === 'win32';
12
12
 
13
- interface RootEntry {
13
+ export interface RootEntry {
14
14
  /** URL-safe identifier (drive letter lowercase on Windows, mount name on Linux) */
15
15
  id: string;
16
16
  /** Display label */
@@ -1 +0,0 @@
1
- import{r as t,u as L,l as M,g as B,j as e}from"./index-jSGuHSeS.js";function J({content:i,fileName:h,onSave:c,onCancel:b}){const n=t.useRef(null),r=t.useRef(null),[d,l]=t.useState(!1),[u,f]=t.useState(!1),[y,v]=t.useState(!0),[C]=L(),x=t.useRef(i),m=t.useCallback(async()=>{if(!r.current)return;const s=r.current.state.doc.toString();f(!0);try{await c(s),x.current=s,l(!1)}finally{f(!1)}},[c]);return t.useEffect(()=>{if(!n.current)return;let s=!1;return(async()=>{const{EditorView:o,EditorState:j,basicSetup:w,keymap:S,oneDark:N}=await M();if(s)return;const E=h.split(".").pop()??"",p=await B(E);if(s)return;const a=[w,S.of([{key:"Mod-s",run:()=>(m(),!0)}]),o.updateListener.of(g=>{if(g.docChanged){const F=g.state.doc.toString();l(F!==x.current)}}),o.theme({"&":{fontSize:"14px",height:"100%"},".cm-scroller":{overflow:"auto"},".cm-content":{fontFamily:"'JetBrains Mono', 'Fira Code', 'Consolas', monospace"},".cm-gutters":{fontFamily:"'JetBrains Mono', 'Fira Code', 'Consolas', monospace"}})];C==="dark"&&a.push(N),p&&a.push(p);const k=j.create({doc:i,extensions:a}),R=new o({state:k,parent:n.current});r.current=R,v(!1)})(),()=>{s=!0,r.current&&(r.current.destroy(),r.current=null)}},[]),e.jsxs("div",{className:"flex flex-col h-full",children:[e.jsxs("div",{className:"flex items-center gap-2 px-3 py-2 border-b border-border bg-muted/50",children:[e.jsx("span",{className:"text-sm font-medium text-foreground",children:"Editing"}),d&&e.jsx("span",{className:"text-xs px-1.5 py-0.5 bg-amber-500/20 text-amber-600 dark:text-amber-400 rounded",children:"unsaved"}),e.jsx("div",{className:"flex-1"}),e.jsx("button",{onClick:b,className:"px-3 py-1 text-sm rounded border border-border text-muted-foreground hover:bg-accent transition-colors",children:"Cancel"}),e.jsx("button",{onClick:m,disabled:u||!d,className:"px-3 py-1 text-sm rounded bg-blue-600 text-white hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors",children:u?"Saving…":"Save"}),e.jsx("span",{className:"text-xs text-muted-foreground hidden sm:inline",children:"Ctrl+S"})]}),e.jsx("div",{ref:n,className:"flex-1 overflow-hidden",children:y&&e.jsx("div",{className:"flex items-center justify-center h-32 text-muted-foreground",children:"Loading editor…"})})]})}export{J as CodeEditor};
@@ -1 +0,0 @@
1
- import{c as b,r as t,u as j,l as E,g as k,j as e,C as N}from"./index-jSGuHSeS.js";const R=[["rect",{width:"14",height:"14",x:"8",y:"8",rx:"2",ry:"2",key:"17jyea"}],["path",{d:"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2",key:"zix9uf"}]],S=b("copy",R);function M({content:o,fileName:i}){const s=t.useRef(null),r=t.useRef(null),[f,x]=t.useState(!0),[h,d]=t.useState(!1),[l]=j(),m=async()=>{await navigator.clipboard.writeText(o),d(!0),setTimeout(()=>d(!1),1500)};return t.useEffect(()=>{if(!s.current)return;let n=!1;return(async()=>{const{EditorView:a,EditorState:u,basicSetup:g,oneDark:y}=await E();if(n)return;const w=i.split(".").pop()??"",p=await k(w);if(n)return;const c=[g,u.readOnly.of(!0),a.editable.of(!1),a.theme({"&":{fontSize:"14px"},".cm-scroller":{overflow:"auto"},".cm-content":{fontFamily:"'JetBrains Mono', 'Fira Code', 'Consolas', monospace"},".cm-gutters":{fontFamily:"'JetBrains Mono', 'Fira Code', 'Consolas', monospace"},".cm-cursor":{display:"none"}})];l==="dark"&&c.push(y),p&&c.push(p);const v=u.create({doc:o,extensions:c}),C=new a({state:v,parent:s.current});r.current=C,x(!1)})(),()=>{n=!0,r.current&&(r.current.destroy(),r.current=null)}},[o,i,l]),e.jsxs("div",{className:"relative group rounded-lg border border-border overflow-hidden",children:[e.jsx("div",{className:"absolute top-2 right-2 z-10",children:e.jsx("button",{onClick:()=>{m()},className:"p-1.5 rounded bg-accent hover:bg-accent/80 text-muted-foreground hover:text-foreground opacity-0 group-hover:opacity-100 transition-all",title:"Copy to clipboard",children:h?e.jsx(N,{className:"h-3.5 w-3.5 text-green-400"}):e.jsx(S,{className:"h-3.5 w-3.5"})})}),e.jsx("div",{ref:s,children:f&&e.jsx("pre",{className:"p-4 text-sm text-muted-foreground bg-muted",children:e.jsxs("code",{children:[o.slice(0,200),"…"]})})})]})}export{M as CodeViewer};
@@ -1 +0,0 @@
1
- import{L as V,E as i,C as _}from"./index-K6OVmfhg.js";import{k as Z,t,L as w,l as L,n as B,D as v,p as E,q as M}from"./index-CVbycZ0H.js";import"./index-Cs5oz2oJ.js";import"./index-Dw7rDFmE.js";const f=63,q=64,j=1,A=2,U=3,H=4,W=5,N=6,I=7,y=65,u=66,F=8,K=9,J=10,OO=11,eO=12,Y=13,aO=19,rO=20,QO=29,tO=33,PO=34,oO=47,nO=0,$=1,b=2,d=3,g=4;class s{constructor(e,a,r){this.parent=e,this.depth=a,this.type=r,this.hash=(e?e.hash+e.hash<<8:0)+a+(a<<4)+r}}s.top=new s(null,-1,nO);function X(O,e){for(let a=0,r=e-O.pos-1;;r--,a++){let P=O.peek(r);if(o(P)||P==-1)return a}}function x(O){return O==32||O==9}function o(O){return O==10||O==13}function z(O){return x(O)||o(O)}function l(O){return O<0||z(O)}const sO=new _({start:s.top,reduce(O,e){return O.type==d&&(e==rO||e==PO)?O.parent:O},shift(O,e,a,r){if(e==U)return new s(O,X(r,r.pos),$);if(e==y||e==W)return new s(O,X(r,r.pos),b);if(e==f)return O.parent;if(e==aO||e==tO)return new s(O,0,d);if(e==Y&&O.type==g)return O.parent;if(e==oO){let P=/[1-9]/.exec(r.read(r.pos,a.pos));if(P)return new s(O,O.depth+ +P[0],g)}return O},hash(O){return O.hash}});function c(O,e,a=0){return O.peek(a)==e&&O.peek(a+1)==e&&O.peek(a+2)==e&&l(O.peek(a+3))}const lO=new i((O,e)=>{if(O.next==-1&&e.canShift(q))return O.acceptToken(q);let a=O.peek(-1);if((o(a)||a<0)&&e.context.type!=d){if(c(O,45))if(e.canShift(f))O.acceptToken(f);else return O.acceptToken(j,3);if(c(O,46))if(e.canShift(f))O.acceptToken(f);else return O.acceptToken(A,3);let r=0;for(;O.next==32;)r++,O.advance();(r<e.context.depth||r==e.context.depth&&e.context.type==$&&(O.next!=45||!l(O.peek(1))))&&O.next!=-1&&!o(O.next)&&O.next!=35&&O.acceptToken(f,-r)}},{contextual:!0}),fO=new i((O,e)=>{if(e.context.type==d){O.next==63&&(O.advance(),l(O.next)&&O.acceptToken(I));return}if(O.next==45)O.advance(),l(O.next)&&O.acceptToken(e.context.type==$&&e.context.depth==X(O,O.pos-1)?H:U);else if(O.next==63)O.advance(),l(O.next)&&O.acceptToken(e.context.type==b&&e.context.depth==X(O,O.pos-1)?N:W);else{let a=O.pos;for(;;)if(x(O.next)){if(O.pos==a)return;O.advance()}else if(O.next==33)C(O);else if(O.next==38)m(O);else if(O.next==42){m(O);break}else if(O.next==39||O.next==34){if(T(O,!0))break;return}else if(O.next==91||O.next==123){if(!XO(O))return;break}else{G(O,!0,!1,0);break}for(;x(O.next);)O.advance();if(O.next==58){if(O.pos==a&&e.canShift(QO))return;let r=O.peek(1);l(r)&&O.acceptTokenTo(e.context.type==b&&e.context.depth==X(O,a)?u:y,a)}}},{contextual:!0});function cO(O){return O>32&&O<127&&O!=34&&O!=37&&O!=44&&O!=60&&O!=62&&O!=92&&O!=94&&O!=96&&O!=123&&O!=124&&O!=125}function D(O){return O>=48&&O<=57||O>=97&&O<=102||O>=65&&O<=70}function p(O,e){return O.next==37?(O.advance(),D(O.next)&&O.advance(),D(O.next)&&O.advance(),!0):cO(O.next)||e&&O.next==44?(O.advance(),!0):!1}function C(O){if(O.advance(),O.next==60){for(O.advance();;)if(!p(O,!0)){O.next==62&&O.advance();break}}else for(;p(O,!1););}function m(O){for(O.advance();!l(O.next)&&S(O.next)!="f";)O.advance()}function T(O,e){let a=O.next,r=!1,P=O.pos;for(O.advance();;){let Q=O.next;if(Q<0)break;if(O.advance(),Q==a)if(Q==39)if(O.next==39)O.advance();else break;else break;else if(Q==92&&a==34)O.next>=0&&O.advance();else if(o(Q)){if(e)return!1;r=!0}else if(e&&O.pos>=P+1024)return!1}return!r}function XO(O){for(let e=[],a=O.pos+1024;;)if(O.next==91||O.next==123)e.push(O.next),O.advance();else if(O.next==39||O.next==34){if(!T(O,!0))return!1}else if(O.next==93||O.next==125){if(e[e.length-1]!=O.next-2)return!1;if(e.pop(),O.advance(),!e.length)return!0}else{if(O.next<0||O.pos>a||o(O.next))return!1;O.advance()}}const dO="iiisiiissisfissssssssssssisssiiissssssssssssssssssssssssssfsfssissssssssssssssssssssssssssfif";function S(O){return O<33?"u":O>125?"s":dO[O-33]}function k(O,e){let a=S(O);return a!="u"&&!(e&&a=="f")}function G(O,e,a,r){if(S(O.next)=="s"||(O.next==63||O.next==58||O.next==45)&&k(O.peek(1),a))O.advance();else return!1;let P=O.pos;for(;;){let Q=O.next,n=0,R=r+1;for(;z(Q);){if(o(Q)){if(e)return!1;R=0}else R++;Q=O.peek(++n)}if(!(Q>=0&&(Q==58?k(O.peek(n+1),a):Q==35?O.peek(n-1)!=32:k(Q,a)))||!a&&R<=r||R==0&&!a&&(c(O,45,n)||c(O,46,n)))break;if(e&&S(Q)=="f")return!1;for(let h=n;h>=0;h--)O.advance();if(e&&O.pos>P+1024)return!1}return!0}const RO=new i((O,e)=>{if(O.next==33)C(O),O.acceptToken(eO);else if(O.next==38||O.next==42){let a=O.next==38?J:OO;m(O),O.acceptToken(a)}else O.next==39||O.next==34?(T(O,!1),O.acceptToken(K)):G(O,!1,e.context.type==d,e.context.depth)&&O.acceptToken(F)}),SO=new i((O,e)=>{let a=e.context.type==g?e.context.depth:-1,r=O.pos;O:for(;;){let P=0,Q=O.next;for(;Q==32;)Q=O.peek(++P);if(!P&&(c(O,45,P)||c(O,46,P))||!o(Q)&&(a<0&&(a=Math.max(e.context.depth+1,P)),P<a))break;for(;;){if(O.next<0)break O;let n=o(O.next);if(O.advance(),n)continue O;r=O.pos}}O.acceptTokenTo(Y,r)}),iO=Z({DirectiveName:t.keyword,DirectiveContent:t.attributeValue,"DirectiveEnd DocEnd":t.meta,QuotedLiteral:t.string,BlockLiteralHeader:t.special(t.string),BlockLiteralContent:t.content,Literal:t.content,"Key/Literal Key/QuotedLiteral":t.definition(t.propertyName),"Anchor Alias":t.labelName,Tag:t.typeName,Comment:t.lineComment,": , -":t.separator,"?":t.punctuation,"[ ]":t.squareBracket,"{ }":t.brace}),kO=V.deserialize({version:14,states:"5lQ!ZQgOOO#PQfO'#CpO#uQfO'#DOOOQR'#Dv'#DvO$qQgO'#DRO%gQdO'#DUO%nQgO'#DUO&ROaO'#D[OOQR'#Du'#DuO&{QgO'#D^O'rQgO'#D`OOQR'#Dt'#DtO(iOqO'#DbOOQP'#Dj'#DjO(zQaO'#CmO)YQgO'#CmOOQP'#Cm'#CmQ)jQaOOQ)uQgOOQ]QgOOO*PQdO'#CrO*nQdO'#CtOOQO'#Dw'#DwO+]Q`O'#CxO+hQdO'#CwO+rQ`O'#CwOOQO'#Cv'#CvO+wQdO'#CvOOQO'#Cq'#CqO,UQ`O,59[O,^QfO,59[OOQR,59[,59[OOQO'#Cx'#CxO,eQ`O'#DPO,pQdO'#DPOOQO'#Dx'#DxO,zQdO'#DxO-XQ`O,59jO-aQfO,59jOOQR,59j,59jOOQR'#DS'#DSO-hQcO,59mO-sQgO'#DVO.TQ`O'#DVO.YQcO,59pOOQR'#DX'#DXO#|QfO'#DWO.hQcO'#DWOOQR,59v,59vO.yOWO,59vO/OOaO,59vO/WOaO,59vO/cQgO'#D_OOQR,59x,59xO0VQgO'#DaOOQR,59z,59zOOQP,59|,59|O0yOaO,59|O1ROaO,59|O1aOqO,59|OOQP-E7h-E7hO1oQgO,59XOOQP,59X,59XO2PQaO'#DeO2_QgO'#DeO2oQgO'#DkOOQP'#Dk'#DkQ)jQaOOO3PQdO'#CsOOQO,59^,59^O3kQdO'#CuOOQO,59`,59`OOQO,59c,59cO4VQdO,59cO4aQdO'#CzO4kQ`O'#CzOOQO,59b,59bOOQU,5:Q,5:QOOQR1G.v1G.vO4pQ`O1G.vOOQU-E7d-E7dO4xQdO,59kOOQO,59k,59kO5SQdO'#DQO5^Q`O'#DQOOQO,5:d,5:dOOQU,5:R,5:ROOQR1G/U1G/UO5cQ`O1G/UOOQU-E7e-E7eO5kQgO'#DhO5xQcO1G/XOOQR1G/X1G/XOOQR,59q,59qO6TQgO,59qO6eQdO'#DiO6lQgO'#DiO7PQcO1G/[OOQR1G/[1G/[OOQR,59r,59rO#|QfO,59rOOQR1G/b1G/bO7_OWO1G/bO7dOaO1G/bOOQR,59y,59yOOQR,59{,59{OOQP1G/h1G/hO7lOaO1G/hO7tOaO1G/hO8POaO1G/hOOQP1G.s1G.sO8_QgO,5:POOQP,5:P,5:POOQP,5:V,5:VOOQP-E7i-E7iOOQO,59_,59_OOQO,59a,59aOOQO1G.}1G.}OOQO,59f,59fO8oQdO,59fOOQR7+$b7+$bP,XQ`O'#DfOOQO1G/V1G/VOOQO,59l,59lO8yQdO,59lOOQR7+$p7+$pP9TQ`O'#DgOOQR'#DT'#DTOOQR,5:S,5:SOOQR-E7f-E7fOOQR7+$s7+$sOOQR1G/]1G/]O9YQgO'#DYO9jQ`O'#DYOOQR,5:T,5:TO#|QfO'#DZO9oQcO'#DZOOQR-E7g-E7gOOQR7+$v7+$vOOQR1G/^1G/^OOQR7+$|7+$|O:QOWO7+$|OOQP7+%S7+%SO:VOaO7+%SO:_OaO7+%SOOQP1G/k1G/kOOQO1G/Q1G/QOOQO1G/W1G/WOOQR,59t,59tO:jQgO,59tOOQR,59u,59uO#|QfO,59uOOQR<<Hh<<HhOOQP<<Hn<<HnO:zOaO<<HnOOQR1G/`1G/`OOQR1G/a1G/aOOQPAN>YAN>Y",stateData:";S~O!fOS!gOS^OS~OP_OQbORSOTUOWROXROYYOZZO[XOcPOqQO!PVO!V[O!cTO~O`cO~P]OVkOWROXROYeOZfO[dOcPOmhOqQO~OboO~P!bOVtOWROXROYeOZfO[dOcPOmrOqQO~OpwO~P#WORSOTUOWROXROYYOZZO[XOcPOqQO!PVO!cTO~OSvP!avP!bvP~P#|OWROXROYeOZfO[dOcPOqQO~OmzO~P%OOm!OOUzP!azP!bzP!dzP~P#|O^!SO!b!QO!f!TO!g!RO~ORSOTUOWROXROcPOqQO!PVO!cTO~OY!UOP!QXQ!QX!V!QX!`!QXS!QX!a!QX!b!QXU!QXm!QX!d!QX~P&aO[!WOP!SXQ!SX!V!SX!`!SXS!SX!a!SX!b!SXU!SXm!SX!d!SX~P&aO^!ZO!W![O!b!YO!f!]O!g!YO~OP!_O!V[OQaX!`aX~OPaXQaX!VaX!`aX~P#|OP!bOQ!cO!V[O~OP_O!V[O~P#|OWROXROY!fOcPOqQObfXmfXofXpfX~OWROXRO[!hOcPOqQObhXmhXohXphX~ObeXmlXoeX~ObkXokX~P%OOm!kO~Om!lObnPonP~P%OOb!pOo!oO~Ob!pO~P!bOm!sOosXpsX~OosXpsX~P%OOm!uOotPptP~P%OOo!xOp!yO~Op!yO~P#WOS!|O!a#OO!b#OO~OUyX!ayX!byX!dyX~P#|Om#QO~OU#SO!a#UO!b#UO!d#RO~Om#WOUzX!azX!bzX!dzX~O]#XO~O!b#XO!g#YO~O^#ZO!b#XO!g#YO~OP!RXQ!RX!V!RX!`!RXS!RX!a!RX!b!RXU!RXm!RX!d!RX~P&aOP!TXQ!TX!V!TX!`!TXS!TX!a!TX!b!TXU!TXm!TX!d!TX~P&aO!b#^O!g#^O~O^#_O!b#^O!f#`O!g#^O~O^#_O!W#aO!b#^O!g#^O~OPaaQaa!Vaa!`aa~P#|OP#cO!V[OQ!XX!`!XX~OP!XXQ!XX!V!XX!`!XX~P#|OP_O!V[OQ!_X!`!_X~P#|OWROXROcPOqQObgXmgXogXpgX~OWROXROcPOqQObiXmiXoiXpiX~Obkaoka~P%OObnXonX~P%OOm#kO~Ob#lOo!oO~Oosapsa~P%OOotXptX~P%OOm#pO~Oo!xOp#qO~OSwP!awP!bwP~P#|OS!|O!a#vO!b#vO~OUya!aya!bya!dya~P#|Om#xO~P%OOm#{OU}P!a}P!b}P!d}P~P#|OU#SO!a$OO!b$OO!d#RO~O]$QO~O!b$QO!g$RO~O!b$SO!g$SO~O^$TO!b$SO!g$SO~O^$TO!b$SO!f$UO!g$SO~OP!XaQ!Xa!V!Xa!`!Xa~P#|Obnaona~P%OOotapta~P%OOo!xO~OU|X!a|X!b|X!d|X~P#|Om$ZO~Om$]OU}X!a}X!b}X!d}X~O]$^O~O!b$_O!g$_O~O^$`O!b$_O!g$_O~OU|a!a|a!b|a!d|a~P#|O!b$cO!g$cO~O",goto:",]!mPPPPPPPPPPPPPPPPP!nPP!v#v#|$`#|$c$f$j$nP%VPPP!v%Y%^%a%{&O%a&R&U&X&_&b%aP&e&{&e'O'RPP']'a'g'm's'y(XPPPPPPPP(_)e*X+c,VUaObcR#e!c!{ROPQSTUXY_bcdehknrtvz!O!U!W!_!b!c!f!h!k!l!s!u!|#Q#R#S#W#c#k#p#x#{$Z$]QmPR!qnqfPQThknrtv!k!l!s!u#R#k#pR!gdR!ieTlPnTjPnSiPnSqQvQ{TQ!mkQ!trQ!vtR#y#RR!nkTsQvR!wt!RWOSUXY_bcz!O!U!W!_!b!c!|#Q#S#W#c#x#{$Z$]RySR#t!|R|TR|UQ!PUR#|#SR#z#RR#z#SyZOSU_bcz!O!_!b!c!|#Q#S#W#c#x#{$Z$]R!VXR!XYa]O^abc!a!c!eT!da!eQnPR!rnQvQR!{vQ!}yR#u!}Q#T|R#}#TW^Obc!cS!^^!aT!aa!eQ!eaR#f!eW`Obc!cQxSS}U#SQ!`_Q#PzQ#V!OQ#b!_Q#d!bQ#s!|Q#w#QQ$P#WQ$V#cQ$Y#xQ$[#{Q$a$ZR$b$]xZOSU_bcz!O!_!b!c!|#Q#S#W#c#x#{$Z$]Q!VXQ!XYQ#[!UR#]!W!QWOSUXY_bcz!O!U!W!_!b!c!|#Q#S#W#c#x#{$Z$]pfPQThknrtv!k!l!s!u#R#k#pQ!gdQ!ieQ#g!fR#h!hSgPn^pQTkrtv#RQ!jhQ#i!kQ#j!lQ#n!sQ#o!uQ$W#kR$X#pQuQR!zv",nodeNames:"⚠ DirectiveEnd DocEnd - - ? ? ? Literal QuotedLiteral Anchor Alias Tag BlockLiteralContent Comment Stream BOM Document ] [ FlowSequence Item Tagged Anchored Anchored Tagged FlowMapping Pair Key : Pair , } { FlowMapping Pair Pair BlockSequence Item Item BlockMapping Pair Pair Key Pair Pair BlockLiteral BlockLiteralHeader Tagged Anchored Anchored Tagged Directive DirectiveName DirectiveContent Document",maxTerm:74,context:sO,nodeProps:[["isolate",-3,8,9,14,""],["openedBy",18,"[",32,"{"],["closedBy",19,"]",33,"}"]],propSources:[iO],skippedNodes:[0],repeatNodeCount:6,tokenData:"-Y~RnOX#PXY$QYZ$]Z]#P]^$]^p#Ppq$Qqs#Pst$btu#Puv$yv|#P|}&e}![#P![!]'O!]!`#P!`!a'i!a!}#P!}#O*g#O#P#P#P#Q+Q#Q#o#P#o#p+k#p#q'i#q#r,U#r;'S#P;'S;=`#z<%l?HT#P?HT?HU,o?HUO#PQ#UU!WQOY#PZp#Ppq#hq;'S#P;'S;=`#z<%lO#PQ#kTOY#PZs#Pt;'S#P;'S;=`#z<%lO#PQ#}P;=`<%l#P~$VQ!f~XY$Qpq$Q~$bO!g~~$gS^~OY$bZ;'S$b;'S;=`$s<%lO$b~$vP;=`<%l$bR%OX!WQOX%kXY#PZ]%k]^#P^p%kpq#hq;'S%k;'S;=`&_<%lO%kR%rX!WQ!VPOX%kXY#PZ]%k]^#P^p%kpq#hq;'S%k;'S;=`&_<%lO%kR&bP;=`<%l%kR&lUoP!WQOY#PZp#Ppq#hq;'S#P;'S;=`#z<%lO#PR'VUmP!WQOY#PZp#Ppq#hq;'S#P;'S;=`#z<%lO#PR'p[!PP!WQOY#PZp#Ppq#hq{#P{|(f|}#P}!O(f!O!R#P!R![)p![;'S#P;'S;=`#z<%lO#PR(mW!PP!WQOY#PZp#Ppq#hq!R#P!R![)V![;'S#P;'S;=`#z<%lO#PR)^U!PP!WQOY#PZp#Ppq#hq;'S#P;'S;=`#z<%lO#PR)wY!PP!WQOY#PZp#Ppq#hq{#P{|)V|}#P}!O)V!O;'S#P;'S;=`#z<%lO#PR*nUcP!WQOY#PZp#Ppq#hq;'S#P;'S;=`#z<%lO#PR+XUbP!WQOY#PZp#Ppq#hq;'S#P;'S;=`#z<%lO#PR+rUqP!WQOY#PZp#Ppq#hq;'S#P;'S;=`#z<%lO#PR,]UpP!WQOY#PZp#Ppq#hq;'S#P;'S;=`#z<%lO#PR,vU`P!WQOY#PZp#Ppq#hq;'S#P;'S;=`#z<%lO#P",tokenizers:[lO,fO,RO,SO,0,1],topRules:{Stream:[0,15]},tokenPrec:0}),bO=L.define({name:"yaml",parser:kO.configure({props:[B.add({Stream:O=>{for(let e=O.node.resolve(O.pos,-1);e&&e.to>=O.pos;e=e.parent){if(e.name=="BlockLiteralContent"&&e.from<e.to)return O.baseIndentFor(e);if(e.name=="BlockLiteral")return O.baseIndentFor(e)+O.unit;if(e.name=="BlockSequence"||e.name=="BlockMapping")return O.column(e.from,1);if(e.name=="QuotedLiteral")return null;if(e.name=="Literal"){let a=O.column(e.from,1);if(a==O.lineIndent(e.from,1))return a;if(e.to>O.pos)return null}}return null},FlowMapping:v({closing:"}"}),FlowSequence:v({closing:"]"})}),E.add({"FlowMapping FlowSequence":M,"Item Pair BlockLiteral":(O,e)=>({from:e.doc.lineAt(O.from).to,to:O.to})})]}),languageData:{commentTokens:{line:"#"},indentOnInput:/^\s*[\]\}]$/}});function hO(){return new w(bO)}export{hO as yaml,bO as yamlLanguage};