@karmaniverous/jeeves-server-core 0.1.4 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -14,6 +14,21 @@ Shared workspace package providing cryptographic utilities and API types consume
14
14
  | `computeDeepShareKey(seed, path, params)` | HMAC-SHA256 key for deep directory shares with depth/dirs/stack/expiry |
15
15
  | `timingSafeEqual(a, b)` | Constant-time string comparison to prevent timing attacks |
16
16
 
17
+ ### Config Schema
18
+
19
+ | Export | Description |
20
+ |--------|-------------|
21
+ | `loggingConfigSchema` | Zod schema for `logging` config block (`level`, `file`, both optional) |
22
+ | `LoggingConfig` | Inferred TypeScript type from `loggingConfigSchema` |
23
+ | `jeevesConfigSchema` | Full server config Zod schema (includes `logging`, `oauth`, all other fields) |
24
+
25
+ ### Endpoint Catalog
26
+
27
+ | Export | Description |
28
+ |--------|-------------|
29
+ | `EndpointEntry` | Interface describing a single API operation (method, path, description, auth flags) |
30
+ | `serverEndpoints` | Complete declarative catalog of all jeeves-server API endpoints (38 entries) |
31
+
17
32
  ### API Types
18
33
 
19
34
  | Type | Description |
@@ -36,6 +51,9 @@ Shared workspace package providing cryptographic utilities and API types consume
36
51
  import {
37
52
  computeInsiderKey,
38
53
  computePathKey,
54
+ serverEndpoints,
55
+ type EndpointEntry,
56
+ type LoggingConfig,
39
57
  type ShareRequest,
40
58
  } from '@karmaniverous/jeeves-server-core';
41
59
  ```
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Declarative endpoint catalog — single source of truth for the jeeves-server
3
+ * API surface. Consumers (CLI, OpenClaw plugin) derive their registrations
4
+ * from this catalog.
5
+ *
6
+ * @packageDocumentation
7
+ */
8
+ import type { z } from 'zod';
9
+ /**
10
+ * Describes a single API operation.
11
+ */
12
+ export interface EndpointEntry {
13
+ /** HTTP method. */
14
+ method: 'GET' | 'POST' | 'PUT' | 'DELETE';
15
+ /** URL path pattern (Fastify-style, e.g. '/api/file/*'). */
16
+ path: string;
17
+ /** Human-readable operation description. */
18
+ description: string;
19
+ /** Zod schema for query params (GET) or request body (POST/PUT). */
20
+ params?: z.ZodType;
21
+ /** Zod schema for the response body. */
22
+ response?: z.ZodType;
23
+ /** Whether the endpoint requires insider auth. */
24
+ insiderOnly?: boolean;
25
+ /** Whether the endpoint is unauthenticated. */
26
+ unauthenticated?: boolean;
27
+ }
28
+ /**
29
+ * Complete API endpoint catalog for jeeves-server.
30
+ *
31
+ * Keys are stable operation identifiers used by consumers to reference
32
+ * specific endpoints. Values describe the HTTP operation.
33
+ */
34
+ export declare const serverEndpoints: Record<string, EndpointEntry>;
@@ -0,0 +1,235 @@
1
+ /**
2
+ * Declarative endpoint catalog — single source of truth for the jeeves-server
3
+ * API surface. Consumers (CLI, OpenClaw plugin) derive their registrations
4
+ * from this catalog.
5
+ *
6
+ * @packageDocumentation
7
+ */
8
+ /**
9
+ * Complete API endpoint catalog for jeeves-server.
10
+ *
11
+ * Keys are stable operation identifiers used by consumers to reference
12
+ * specific endpoints. Values describe the HTTP operation.
13
+ */
14
+ const serverEndpoints = {
15
+ // --- Status & Config ---
16
+ status: {
17
+ method: 'GET',
18
+ path: '/status',
19
+ description: 'Server health: name, version, uptime, status, health details.',
20
+ unauthenticated: true,
21
+ },
22
+ config: {
23
+ method: 'GET',
24
+ path: '/config',
25
+ description: 'Query running config with optional JSONPath filter (?path=).',
26
+ unauthenticated: true,
27
+ },
28
+ 'config-apply': {
29
+ method: 'POST',
30
+ path: '/config/apply',
31
+ description: 'Push config patch to running service (deep-merge + validate + reload).',
32
+ insiderOnly: true,
33
+ },
34
+ // --- Authentication ---
35
+ 'auth-status': {
36
+ method: 'GET',
37
+ path: '/api/auth/status',
38
+ description: 'Check current authentication status (no auth required).',
39
+ unauthenticated: true,
40
+ },
41
+ 'auth-login': {
42
+ method: 'GET',
43
+ path: '/auth/login',
44
+ description: 'Redirect to Google OAuth consent screen.',
45
+ unauthenticated: true,
46
+ },
47
+ 'auth-callback': {
48
+ method: 'GET',
49
+ path: '/auth/callback',
50
+ description: 'Handle Google OAuth callback, set session cookie.',
51
+ unauthenticated: true,
52
+ },
53
+ 'auth-logout': {
54
+ method: 'GET',
55
+ path: '/auth/logout',
56
+ description: 'Clear session cookie.',
57
+ unauthenticated: true,
58
+ },
59
+ // --- File Browser ---
60
+ drives: {
61
+ method: 'GET',
62
+ path: '/api/drives',
63
+ description: 'List available root drives/labels configured on the server.',
64
+ },
65
+ path: {
66
+ method: 'GET',
67
+ path: '/api/path/*',
68
+ description: 'Get directory listing for a path.',
69
+ },
70
+ 'file-get': {
71
+ method: 'GET',
72
+ path: '/api/file/*',
73
+ description: 'Get file content with type-specific handling (markdown, csv, diagrams, etc.).',
74
+ },
75
+ 'file-put': {
76
+ method: 'PUT',
77
+ path: '/api/file/*',
78
+ description: 'Overwrite file content (insider-only; file must already exist).',
79
+ insiderOnly: true,
80
+ },
81
+ 'file-mutate': {
82
+ method: 'POST',
83
+ path: '/api/file/*',
84
+ description: 'Apply structured .md mutations: edit-block, delete-block, insert-block, edit-cell, toggle-checkbox.',
85
+ insiderOnly: true,
86
+ },
87
+ raw: {
88
+ method: 'GET',
89
+ path: '/api/raw/*',
90
+ description: 'Direct file download with appropriate content type.',
91
+ },
92
+ // --- Export ---
93
+ export: {
94
+ method: 'GET',
95
+ path: '/api/export/*',
96
+ description: 'Trigger export (PDF, DOCX, ZIP) and return download.',
97
+ },
98
+ 'export-cache-clear': {
99
+ method: 'DELETE',
100
+ path: '/api/export-cache/*',
101
+ description: 'Clear export and diagram caches for a given path.',
102
+ insiderOnly: true,
103
+ },
104
+ 'mermaid-export': {
105
+ method: 'GET',
106
+ path: '/api/mermaid-export/*',
107
+ description: 'Export Mermaid diagram (svg, png, pdf).',
108
+ },
109
+ 'plantuml-export': {
110
+ method: 'GET',
111
+ path: '/api/plantuml-export/*',
112
+ description: 'Export PlantUML diagram (svg, png, pdf, eps).',
113
+ },
114
+ // --- Diagrams ---
115
+ diagram: {
116
+ method: 'GET',
117
+ path: '/api/diagram/:type/:hash',
118
+ description: 'Fetch rendered diagram by type and content hash.',
119
+ unauthenticated: true,
120
+ },
121
+ // --- Sharing ---
122
+ share: {
123
+ method: 'POST',
124
+ path: '/api/share',
125
+ description: 'Generate HMAC-signed share link for a path.',
126
+ insiderOnly: true,
127
+ },
128
+ 'share-for': {
129
+ method: 'POST',
130
+ path: '/api/util/share-for',
131
+ description: 'Generate share link targeting specific insiders with configurable expiry/depth.',
132
+ insiderOnly: true,
133
+ },
134
+ 'readme-link': {
135
+ method: 'GET',
136
+ path: '/api/readme-link',
137
+ description: 'Pre-computed share link for the first README.md in the directory hierarchy.',
138
+ unauthenticated: true,
139
+ },
140
+ 'content-link': {
141
+ method: 'GET',
142
+ path: '/api/content-link/:file',
143
+ description: 'Pre-computed deep share URL for pinned content files (privacy, terms).',
144
+ unauthenticated: true,
145
+ },
146
+ 'link-info': {
147
+ method: 'GET',
148
+ path: '/api/link-info/*',
149
+ description: 'Query available link types for a path (page, raw, PDF, DOCX, etc.).',
150
+ },
151
+ 'rotate-key': {
152
+ method: 'POST',
153
+ path: '/api/rotate-key',
154
+ description: 'Rotate the authenticated insider API key (invalidates all existing share links).',
155
+ insiderOnly: true,
156
+ },
157
+ // --- Events ---
158
+ event: {
159
+ method: 'POST',
160
+ path: '/event',
161
+ description: 'Event gateway webhook receiver (scoped key auth).',
162
+ },
163
+ events: {
164
+ method: 'GET',
165
+ path: '/api/events',
166
+ description: 'Query recent event log entries (limit capped at 100).',
167
+ },
168
+ // --- Search ---
169
+ search: {
170
+ method: 'POST',
171
+ path: '/api/search',
172
+ description: 'Semantic search (proxied to watcher).',
173
+ },
174
+ 'search-facets': {
175
+ method: 'GET',
176
+ path: '/api/search/facets',
177
+ description: 'Search facet metadata (proxied to watcher).',
178
+ },
179
+ // --- Runner ---
180
+ runner: {
181
+ method: 'GET',
182
+ path: '/api/runner/*',
183
+ description: 'Runner API proxy (proxied to jeeves-runner).',
184
+ },
185
+ // --- OAuth ---
186
+ 'oauth-start': {
187
+ method: 'POST',
188
+ path: '/api/oauth/start',
189
+ description: 'Initiate OAuth2 authorization code flow.',
190
+ insiderOnly: true,
191
+ },
192
+ 'oauth-status': {
193
+ method: 'GET',
194
+ path: '/api/oauth/status',
195
+ description: 'Check if valid OAuth2 credentials exist for a provider/account.',
196
+ insiderOnly: true,
197
+ },
198
+ 'oauth-token': {
199
+ method: 'GET',
200
+ path: '/api/oauth/token',
201
+ description: 'Retrieve valid access token (with lazy refresh).',
202
+ insiderOnly: true,
203
+ },
204
+ 'oauth-callback': {
205
+ method: 'GET',
206
+ path: '/oauth/callback',
207
+ description: 'OAuth2 callback (browser redirect target, unauthenticated).',
208
+ unauthenticated: true,
209
+ },
210
+ // --- Keys ---
211
+ 'insider-key': {
212
+ method: 'GET',
213
+ path: '/insider-key',
214
+ description: 'Get insider key for the authenticated user.',
215
+ },
216
+ key: {
217
+ method: 'GET',
218
+ path: '/key',
219
+ description: 'Compute path-specific key.',
220
+ },
221
+ 'share-legacy': {
222
+ method: 'GET',
223
+ path: '/share',
224
+ description: 'Generate outsider share link (top-level legacy route).',
225
+ },
226
+ // --- Shortlinks ---
227
+ go: {
228
+ method: 'GET',
229
+ path: '/go/:slug',
230
+ description: 'Shortlink redirect (unauthenticated).',
231
+ unauthenticated: true,
232
+ },
233
+ };
234
+
235
+ export { serverEndpoints };
@@ -0,0 +1 @@
1
+ export {};
@@ -4,5 +4,6 @@
4
4
  * @packageDocumentation
5
5
  */
6
6
  export { computeDeepShareKey, computeInsiderKey, computeOutsiderKeyWithExpiry, computePathKey, type DeepShareParams, timingSafeEqual, } from './crypto.js';
7
- export { type AuthMode, authModeSchema, authSchema, DEPRECATED_CONFIG_PROPS, eventConfigSchema, googleAuthSchema, insiderEntrySchema, isScopeName, type JeevesConfig, jeevesConfigSchema, keyEntrySchema, oauthProviderSchema, oauthSchema, scopesObjectSchema, scopesSchema, } from './schema.js';
7
+ export { type EndpointEntry, serverEndpoints } from './endpoints.js';
8
+ export { type AuthMode, authModeSchema, authSchema, type Branding, brandingSchema, DEFAULT_BRANDING, DEPRECATED_CONFIG_PROPS, emailAuthSchema, eventConfigSchema, googleAuthSchema, insiderEntrySchema, isScopeName, type JeevesConfig, jeevesConfigSchema, keyEntrySchema, type LoggingConfig, loggingConfigSchema, oauthProviderSchema, oauthSchema, scopesObjectSchema, scopesSchema, themeOverridesSchema, } from './schema.js';
8
9
  export type { AuthStatusResponse, DriveEntry, ExportCacheClearResponse, ExportLink, FileMutationAction, LinkInfoResponse, OAuthStartRequest, OAuthStartResponse, RotateKeyResponse, ShareForRequest, ShareForResponse, ShareRequest, ShareResponse, } from './types.js';
package/dist/src/index.js CHANGED
@@ -1,2 +1,3 @@
1
1
  export { computeDeepShareKey, computeInsiderKey, computeOutsiderKeyWithExpiry, computePathKey, timingSafeEqual } from './crypto.js';
2
- export { DEPRECATED_CONFIG_PROPS, authModeSchema, authSchema, eventConfigSchema, googleAuthSchema, insiderEntrySchema, isScopeName, jeevesConfigSchema, keyEntrySchema, oauthProviderSchema, oauthSchema, scopesObjectSchema, scopesSchema } from './schema.js';
2
+ export { serverEndpoints } from './endpoints.js';
3
+ export { DEFAULT_BRANDING, DEPRECATED_CONFIG_PROPS, authModeSchema, authSchema, brandingSchema, emailAuthSchema, eventConfigSchema, googleAuthSchema, insiderEntrySchema, isScopeName, jeevesConfigSchema, keyEntrySchema, loggingConfigSchema, oauthProviderSchema, oauthSchema, scopesObjectSchema, scopesSchema, themeOverridesSchema } from './schema.js';
@@ -3,25 +3,36 @@ import { z } from 'zod';
3
3
  export declare const DEPRECATED_CONFIG_PROPS: readonly ["host", "runnerUrl", "watcherUrl", "metaUrl"];
4
4
  /** Supported authentication methods */
5
5
  export declare const authModeSchema: z.ZodEnum<{
6
- google: "google";
7
6
  keys: "keys";
7
+ google: "google";
8
+ email: "email";
8
9
  }>;
9
10
  /** Google OAuth configuration */
10
11
  export declare const googleAuthSchema: z.ZodObject<{
11
12
  clientId: z.ZodString;
12
13
  clientSecret: z.ZodString;
13
14
  }, z.core.$strip>;
15
+ /** Email (magic link) authentication configuration */
16
+ export declare const emailAuthSchema: z.ZodObject<{
17
+ smtpUrl: z.ZodString;
18
+ fromAddress: z.ZodEmail;
19
+ }, z.core.$strip>;
14
20
  /** Auth configuration */
15
21
  export declare const authSchema: z.ZodObject<{
16
22
  modes: z.ZodArray<z.ZodEnum<{
17
- google: "google";
18
23
  keys: "keys";
24
+ google: "google";
25
+ email: "email";
19
26
  }>>;
20
27
  google: z.ZodOptional<z.ZodObject<{
21
28
  clientId: z.ZodString;
22
29
  clientSecret: z.ZodString;
23
30
  }, z.core.$strip>>;
24
31
  sessionSecret: z.ZodOptional<z.ZodString>;
32
+ email: z.ZodOptional<z.ZodObject<{
33
+ smtpUrl: z.ZodString;
34
+ fromAddress: z.ZodEmail;
35
+ }, z.core.$strip>>;
25
36
  }, z.core.$strip>;
26
37
  /** Event webhook configuration */
27
38
  export declare const eventConfigSchema: z.ZodObject<{
@@ -75,6 +86,13 @@ export declare const keyEntrySchema: z.ZodUnion<readonly [z.ZodString, z.ZodObje
75
86
  }, z.core.$strip>]>;
76
87
  /** Check if a string is a named scope identifier (not a path glob). */
77
88
  export declare function isScopeName(v: string): boolean;
89
+ /** Logging configuration. Not hot-reloadable — changes require service restart. */
90
+ export declare const loggingConfigSchema: z.ZodObject<{
91
+ level: z.ZodOptional<z.ZodString>;
92
+ file: z.ZodOptional<z.ZodString>;
93
+ }, z.core.$strip>;
94
+ /** Inferred logging config type. */
95
+ export type LoggingConfig = z.infer<typeof loggingConfigSchema>;
78
96
  /** OAuth provider configuration */
79
97
  export declare const oauthProviderSchema: z.ZodObject<{
80
98
  authUrl: z.ZodPipe<z.ZodString, z.ZodURL>;
@@ -92,20 +110,37 @@ export declare const oauthSchema: z.ZodObject<{
92
110
  defaultScopes: z.ZodDefault<z.ZodArray<z.ZodString>>;
93
111
  }, z.core.$strip>>>;
94
112
  }, z.core.$strip>;
113
+ /** Theme overrides for light and dark modes */
114
+ export declare const themeOverridesSchema: z.ZodRecord<z.ZodString, z.ZodString>;
115
+ /** Instance branding configuration */
116
+ export declare const brandingSchema: z.ZodObject<{
117
+ name: z.ZodDefault<z.ZodString>;
118
+ emoji: z.ZodDefault<z.ZodString>;
119
+ theme: z.ZodOptional<z.ZodObject<{
120
+ light: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
121
+ dark: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
122
+ }, z.core.$strip>>;
123
+ emailTemplate: z.ZodOptional<z.ZodString>;
124
+ }, z.core.$strip>;
95
125
  /** Top-level Jeeves Server configuration */
96
126
  export declare const jeevesConfigSchema: z.ZodObject<{
97
127
  port: z.ZodDefault<z.ZodNumber>;
98
128
  chromePath: z.ZodString;
99
129
  auth: z.ZodObject<{
100
130
  modes: z.ZodArray<z.ZodEnum<{
101
- google: "google";
102
131
  keys: "keys";
132
+ google: "google";
133
+ email: "email";
103
134
  }>>;
104
135
  google: z.ZodOptional<z.ZodObject<{
105
136
  clientId: z.ZodString;
106
137
  clientSecret: z.ZodString;
107
138
  }, z.core.$strip>>;
108
139
  sessionSecret: z.ZodOptional<z.ZodString>;
140
+ email: z.ZodOptional<z.ZodObject<{
141
+ smtpUrl: z.ZodString;
142
+ fromAddress: z.ZodEmail;
143
+ }, z.core.$strip>>;
109
144
  }, z.core.$strip>;
110
145
  scopes: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
111
146
  allow: z.ZodOptional<z.ZodArray<z.ZodString>>;
@@ -151,6 +186,10 @@ export declare const jeevesConfigSchema: z.ZodObject<{
151
186
  allow: z.ZodOptional<z.ZodArray<z.ZodString>>;
152
187
  deny: z.ZodOptional<z.ZodArray<z.ZodString>>;
153
188
  }, z.core.$strip>]>>;
189
+ logging: z.ZodOptional<z.ZodObject<{
190
+ level: z.ZodOptional<z.ZodString>;
191
+ file: z.ZodOptional<z.ZodString>;
192
+ }, z.core.$strip>>;
154
193
  oauth: z.ZodOptional<z.ZodObject<{
155
194
  credentialDir: z.ZodString;
156
195
  providers: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
@@ -161,8 +200,24 @@ export declare const jeevesConfigSchema: z.ZodObject<{
161
200
  }, z.core.$strip>>>;
162
201
  }, z.core.$strip>>;
163
202
  go: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
203
+ branding: z.ZodOptional<z.ZodObject<{
204
+ name: z.ZodDefault<z.ZodString>;
205
+ emoji: z.ZodDefault<z.ZodString>;
206
+ theme: z.ZodOptional<z.ZodObject<{
207
+ light: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
208
+ dark: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
209
+ }, z.core.$strip>>;
210
+ emailTemplate: z.ZodOptional<z.ZodString>;
211
+ }, z.core.$strip>>;
164
212
  }, z.core.$strip>;
165
213
  /** Inferred config type */
166
214
  export type JeevesConfig = z.infer<typeof jeevesConfigSchema>;
167
215
  /** Auth mode type */
168
216
  export type AuthMode = z.infer<typeof authModeSchema>;
217
+ /** Inferred branding type */
218
+ export type Branding = z.infer<typeof brandingSchema>;
219
+ /** Default branding values. Single source of truth for all fallbacks. */
220
+ export declare const DEFAULT_BRANDING: {
221
+ readonly name: "Jeeves Server";
222
+ readonly emoji: "🎩";
223
+ };
@@ -9,12 +9,19 @@ const DEPRECATED_CONFIG_PROPS = [
9
9
  'metaUrl',
10
10
  ];
11
11
  /** Supported authentication methods */
12
- const authModeSchema = z.enum(['google', 'keys']);
12
+ const authModeSchema = z.enum(['google', 'keys', 'email']);
13
13
  /** Google OAuth configuration */
14
14
  const googleAuthSchema = z.object({
15
15
  clientId: z.string().min(1),
16
16
  clientSecret: z.string().min(1),
17
17
  });
18
+ /** Email (magic link) authentication configuration */
19
+ const emailAuthSchema = z.object({
20
+ /** SMTP connection string (e.g. smtps://user:pass@smtp.example.com:465) */
21
+ smtpUrl: z.string().min(1),
22
+ /** Sender email address for magic link emails */
23
+ fromAddress: z.email(),
24
+ });
18
25
  /** Auth configuration */
19
26
  const authSchema = z.object({
20
27
  /** Active authentication methods. Order determines priority. */
@@ -25,6 +32,8 @@ const authSchema = z.object({
25
32
  google: googleAuthSchema.optional(),
26
33
  /** Session cookie signing secret. Required if modes includes "google". */
27
34
  sessionSecret: z.string().min(1).optional(),
35
+ /** Email (magic link) auth config. Required if modes includes "email". */
36
+ email: emailAuthSchema.optional(),
28
37
  });
29
38
  /** Event webhook configuration */
30
39
  const eventConfigSchema = z.object({
@@ -101,6 +110,19 @@ function getScopeRefs(scopes) {
101
110
  }
102
111
  return [];
103
112
  }
113
+ /** Logging configuration. Not hot-reloadable — changes require service restart. */
114
+ const loggingConfigSchema = z.object({
115
+ /** Log level. */
116
+ level: z
117
+ .string()
118
+ .optional()
119
+ .describe('Logging level (trace, debug, info, warn, error, fatal).'),
120
+ /** Log file path. */
121
+ file: z
122
+ .string()
123
+ .optional()
124
+ .describe('Path to log file (logs to stdout if omitted).'),
125
+ });
104
126
  /** OAuth provider configuration */
105
127
  const oauthProviderSchema = z.object({
106
128
  authUrl: z.string().pipe(z.url()),
@@ -113,6 +135,24 @@ const oauthSchema = z.object({
113
135
  credentialDir: z.string(),
114
136
  providers: z.record(z.string(), oauthProviderSchema).default({}),
115
137
  });
138
+ /** Theme overrides for light and dark modes */
139
+ const themeOverridesSchema = z.record(z.string(), z.string());
140
+ /** Instance branding configuration */
141
+ const brandingSchema = z.object({
142
+ /** Instance display name (used in page title, navbar, emails) */
143
+ name: z.string().default('Jeeves Server'),
144
+ /** Instance emoji (used as home icon in navbar) */
145
+ emoji: z.string().default('🎩'),
146
+ /** CSS variable overrides grouped by theme mode */
147
+ theme: z
148
+ .object({
149
+ light: themeOverridesSchema.optional(),
150
+ dark: themeOverridesSchema.optional(),
151
+ })
152
+ .optional(),
153
+ /** Handlebars template for magic link emails. Supports {{magicLink}}, {{branding.name}}, {{branding.emoji}} */
154
+ emailTemplate: z.string().optional(),
155
+ });
116
156
  /** Top-level Jeeves Server configuration */
117
157
  const jeevesConfigSchema = z
118
158
  .object({
@@ -163,6 +203,11 @@ const jeevesConfigSchema = z
163
203
  * If omitted, all paths are shareable with outsiders.
164
204
  */
165
205
  outsiderPolicy: scopesSchema.optional(),
206
+ /**
207
+ * Logging configuration.
208
+ * Not hot-reloadable — changes require a service restart.
209
+ */
210
+ logging: loggingConfigSchema.optional(),
166
211
  /** OAuth2 credential management configuration */
167
212
  oauth: oauthSchema.optional(),
168
213
  /**
@@ -180,6 +225,8 @@ const jeevesConfigSchema = z
180
225
  message: 'Target must be a relative path starting with / or an absolute URL',
181
226
  }))
182
227
  .default({}),
228
+ /** Instance branding (name, emoji, theme overrides, email template) */
229
+ branding: brandingSchema.optional(),
183
230
  })
184
231
  .superRefine((config, ctx) => {
185
232
  // Google auth mode requires google config + sessionSecret
@@ -199,6 +246,23 @@ const jeevesConfigSchema = z
199
246
  });
200
247
  }
201
248
  }
249
+ // Email auth mode requires email config
250
+ if (config.auth.modes.includes('email')) {
251
+ if (!config.auth.email) {
252
+ ctx.addIssue({
253
+ code: 'custom',
254
+ message: 'auth.email is required when auth.modes includes "email"',
255
+ path: ['auth', 'email'],
256
+ });
257
+ }
258
+ if (!config.auth.sessionSecret) {
259
+ ctx.addIssue({
260
+ code: 'custom',
261
+ message: 'auth.sessionSecret is required when auth.modes includes "email"',
262
+ path: ['auth', 'sessionSecret'],
263
+ });
264
+ }
265
+ }
202
266
  // Keys auth mode requires at least one key
203
267
  if (config.auth.modes.includes('keys') &&
204
268
  Object.keys(config.keys).length === 0) {
@@ -248,5 +312,10 @@ const jeevesConfigSchema = z
248
312
  if (outsiderRefs.length > 0)
249
313
  validateRefs(outsiderRefs, ['outsiderPolicy']);
250
314
  });
315
+ /** Default branding values. Single source of truth for all fallbacks. */
316
+ const DEFAULT_BRANDING = {
317
+ name: 'Jeeves Server',
318
+ emoji: '🎩',
319
+ };
251
320
 
252
- export { DEPRECATED_CONFIG_PROPS, authModeSchema, authSchema, eventConfigSchema, googleAuthSchema, insiderEntrySchema, isScopeName, jeevesConfigSchema, keyEntrySchema, oauthProviderSchema, oauthSchema, scopesObjectSchema, scopesSchema };
321
+ export { DEFAULT_BRANDING, DEPRECATED_CONFIG_PROPS, authModeSchema, authSchema, brandingSchema, emailAuthSchema, eventConfigSchema, googleAuthSchema, insiderEntrySchema, isScopeName, jeevesConfigSchema, keyEntrySchema, loggingConfigSchema, oauthProviderSchema, oauthSchema, scopesObjectSchema, scopesSchema, themeOverridesSchema };
package/package.json CHANGED
@@ -86,5 +86,5 @@
86
86
  },
87
87
  "type": "module",
88
88
  "types": "dist/src/index.d.ts",
89
- "version": "0.1.4"
89
+ "version": "0.1.6"
90
90
  }