@wolpertingerlabs/drawlatch 1.0.0-alpha.2 → 1.0.0-alpha.29

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 (119) hide show
  1. package/CONNECTIONS.md +3 -0
  2. package/README.md +395 -465
  3. package/bin/drawlatch.js +1018 -49
  4. package/dist/auth/auth.d.ts +10 -0
  5. package/dist/auth/auth.js +146 -0
  6. package/dist/auth/env-writer.d.ts +6 -0
  7. package/dist/auth/env-writer.js +52 -0
  8. package/dist/auth/password.d.ts +8 -0
  9. package/dist/auth/password.js +28 -0
  10. package/dist/auth/sessions.d.ts +13 -0
  11. package/dist/auth/sessions.js +85 -0
  12. package/dist/cli/generate-keys.d.ts +4 -4
  13. package/dist/cli/generate-keys.js +30 -25
  14. package/dist/connections/{anthropic.json → ai/anthropic.json} +19 -1
  15. package/dist/connections/{devin.json → ai/devin.json} +7 -1
  16. package/dist/connections/{google-ai.json → ai/google-ai.json} +7 -1
  17. package/dist/connections/{openai.json → ai/openai.json} +7 -1
  18. package/dist/connections/{openrouter.json → ai/openrouter.json} +7 -1
  19. package/dist/connections/developer-tools/github.json +138 -0
  20. package/dist/connections/{hex.json → developer-tools/hex.json} +7 -1
  21. package/dist/connections/developer-tools/linear.json +75 -0
  22. package/dist/connections/{lichess.json → gaming/lichess.json} +7 -1
  23. package/dist/connections/messaging/agentmail.json +21 -0
  24. package/dist/connections/messaging/discord-bot.json +114 -0
  25. package/dist/connections/{discord-oauth.json → messaging/discord-oauth.json} +7 -1
  26. package/dist/connections/messaging/slack.json +75 -0
  27. package/dist/connections/messaging/telegram.json +66 -0
  28. package/dist/connections/{google.json → productivity/google.json} +7 -1
  29. package/dist/connections/productivity/notion.json +75 -0
  30. package/dist/connections/productivity/stripe.json +74 -0
  31. package/dist/connections/productivity/trello.json +113 -0
  32. package/dist/connections/{bluesky.json → social-media/bluesky.json} +41 -0
  33. package/dist/connections/social-media/mastodon.json +65 -0
  34. package/dist/connections/social-media/reddit.json +80 -0
  35. package/dist/connections/{twitch.json → social-media/twitch.json} +40 -0
  36. package/dist/connections/social-media/x.json +67 -0
  37. package/dist/mcp/server.js +544 -31
  38. package/dist/remote/admin-mutations.d.ts +37 -0
  39. package/dist/remote/admin-mutations.js +251 -0
  40. package/dist/remote/admin-types.d.ts +149 -0
  41. package/dist/remote/admin-types.js +11 -0
  42. package/dist/remote/admin.d.ts +37 -0
  43. package/dist/remote/admin.js +316 -0
  44. package/dist/remote/caller-bootstrap.d.ts +71 -0
  45. package/dist/remote/caller-bootstrap.js +141 -0
  46. package/dist/remote/ingestors/base-ingestor.d.ts +19 -3
  47. package/dist/remote/ingestors/base-ingestor.js +27 -6
  48. package/dist/remote/ingestors/discord/discord-gateway.d.ts +2 -2
  49. package/dist/remote/ingestors/discord/discord-gateway.js +29 -6
  50. package/dist/remote/ingestors/e2e/setup.d.ts +69 -0
  51. package/dist/remote/ingestors/e2e/setup.js +147 -0
  52. package/dist/remote/ingestors/manager.d.ts +110 -10
  53. package/dist/remote/ingestors/manager.js +449 -42
  54. package/dist/remote/ingestors/poll/poll-ingestor.d.ts +4 -2
  55. package/dist/remote/ingestors/poll/poll-ingestor.js +26 -5
  56. package/dist/remote/ingestors/registry.d.ts +2 -2
  57. package/dist/remote/ingestors/registry.js +2 -2
  58. package/dist/remote/ingestors/slack/socket-mode.d.ts +2 -2
  59. package/dist/remote/ingestors/slack/socket-mode.js +28 -6
  60. package/dist/remote/ingestors/types.d.ts +25 -0
  61. package/dist/remote/ingestors/webhook/base-webhook-ingestor.d.ts +46 -7
  62. package/dist/remote/ingestors/webhook/base-webhook-ingestor.js +115 -10
  63. package/dist/remote/ingestors/webhook/github-webhook-ingestor.d.ts +30 -0
  64. package/dist/remote/ingestors/webhook/github-webhook-ingestor.js +73 -2
  65. package/dist/remote/ingestors/webhook/lifecycle-types.d.ts +63 -0
  66. package/dist/remote/ingestors/webhook/lifecycle-types.js +12 -0
  67. package/dist/remote/ingestors/webhook/stripe-webhook-ingestor.js +2 -2
  68. package/dist/remote/ingestors/webhook/trello-webhook-ingestor.d.ts +17 -5
  69. package/dist/remote/ingestors/webhook/trello-webhook-ingestor.js +32 -26
  70. package/dist/remote/ingestors/webhook/webhook-lifecycle-manager.d.ts +46 -0
  71. package/dist/remote/ingestors/webhook/webhook-lifecycle-manager.js +261 -0
  72. package/dist/remote/server.d.ts +25 -33
  73. package/dist/remote/server.js +681 -195
  74. package/dist/remote/tool-dispatch.d.ts +84 -0
  75. package/dist/remote/tool-dispatch.js +910 -0
  76. package/dist/remote/triggers/rule-engine.d.ts +40 -0
  77. package/dist/remote/triggers/rule-engine.js +228 -0
  78. package/dist/remote/triggers/types.d.ts +69 -0
  79. package/dist/remote/triggers/types.js +10 -0
  80. package/dist/remote/tunnel-state.d.ts +14 -0
  81. package/dist/remote/tunnel-state.js +20 -0
  82. package/dist/remote/tunnel.d.ts +53 -0
  83. package/dist/remote/tunnel.js +149 -0
  84. package/dist/shared/config.d.ts +97 -25
  85. package/dist/shared/config.js +48 -35
  86. package/dist/shared/connections.d.ts +21 -5
  87. package/dist/shared/connections.js +56 -14
  88. package/dist/shared/crypto/index.d.ts +1 -0
  89. package/dist/shared/crypto/index.js +1 -0
  90. package/dist/shared/crypto/key-manager.d.ts +67 -0
  91. package/dist/shared/crypto/key-manager.js +152 -0
  92. package/dist/shared/env-utils.d.ts +42 -0
  93. package/dist/shared/env-utils.js +150 -0
  94. package/dist/shared/listener-config.d.ts +157 -0
  95. package/dist/shared/listener-config.js +10 -0
  96. package/dist/shared/migrations.d.ts +40 -0
  97. package/dist/shared/migrations.js +122 -0
  98. package/dist/shared/protocol/handshake.js +14 -1
  99. package/dist/shared/protocol/index.d.ts +2 -0
  100. package/dist/shared/protocol/index.js +2 -0
  101. package/dist/shared/protocol/sync-client.d.ts +52 -0
  102. package/dist/shared/protocol/sync-client.js +99 -0
  103. package/dist/shared/protocol/sync.d.ts +71 -0
  104. package/dist/shared/protocol/sync.js +176 -0
  105. package/frontend/dist/assets/index-D4k5QKBP.css +1 -0
  106. package/frontend/dist/assets/index-IzwnSOmu.js +267 -0
  107. package/frontend/dist/index.html +15 -0
  108. package/package.json +66 -13
  109. package/dist/connections/discord-bot.json +0 -24
  110. package/dist/connections/github.json +0 -25
  111. package/dist/connections/linear.json +0 -29
  112. package/dist/connections/mastodon.json +0 -25
  113. package/dist/connections/notion.json +0 -33
  114. package/dist/connections/reddit.json +0 -28
  115. package/dist/connections/slack.json +0 -23
  116. package/dist/connections/stripe.json +0 -25
  117. package/dist/connections/telegram.json +0 -26
  118. package/dist/connections/trello.json +0 -25
  119. package/dist/connections/x.json +0 -27
@@ -0,0 +1,251 @@
1
+ /**
2
+ * Mutating admin API (item A), mounted behind `requireAuth` at `/api/admin/*`
3
+ * alongside the read-only endpoints in `admin.ts`.
4
+ *
5
+ * Every mutation reuses the canonical tool-dispatch (item D) so there is exactly
6
+ * one implementation of connection/secret/listener logic. Caller create/delete
7
+ * uses the programmatic bootstrap (item E).
8
+ *
9
+ * Security invariants (same as the read-only side):
10
+ * - Secret values are write-only: set via PUT, read back only as booleans.
11
+ * - No secret value, AES channel key, or process.env value is ever serialized.
12
+ * - After any config mutation, the daemon's live-reload path runs so ingestors
13
+ * and routes pick up the change (replacing callboard's restart banner).
14
+ */
15
+ import { saveRemoteConfig } from '../shared/config.js';
16
+ import { dispatchTool } from './tool-dispatch.js';
17
+ import { createCallerWithKeys, deleteCaller, CALLER_ALIAS_REGEX } from './caller-bootstrap.js';
18
+ /** Translate a thrown error into an HTTP status + message. */
19
+ function errorStatus(message) {
20
+ if (/already exists/i.test(message))
21
+ return 409;
22
+ if (/not found|unknown caller/i.test(message))
23
+ return 404;
24
+ if (/cannot delete|invalid|required/i.test(message))
25
+ return 400;
26
+ return 500;
27
+ }
28
+ /**
29
+ * Mount the mutating routes onto an existing admin router.
30
+ *
31
+ * The router is already behind `requireAuth` (wired in server.ts), so these
32
+ * handlers can assume the request is authenticated.
33
+ */
34
+ export function mountAdminMutations(router, deps) {
35
+ /** Build a ToolContext for a caller, wired to live reload on mutation. */
36
+ function ctx(alias) {
37
+ return {
38
+ callerAlias: alias,
39
+ ingestorManager: deps.ingestorManager(),
40
+ refreshRoutes: () => deps.refreshCaller(alias),
41
+ };
42
+ }
43
+ /** Guard: 404 unless the caller exists in config. */
44
+ function callerExists(alias) {
45
+ return alias in deps.loadConfig().callers;
46
+ }
47
+ /** Run a tool and send its result, mapping thrown errors to HTTP codes. */
48
+ async function runTool(res, alias, tool, input) {
49
+ if (!callerExists(alias)) {
50
+ res.status(404).json({ error: `Unknown caller: ${alias}` });
51
+ return;
52
+ }
53
+ try {
54
+ const result = await dispatchTool(tool, input, deps.resolveRoutesForCaller(alias), ctx(alias));
55
+ res.json(result);
56
+ }
57
+ catch (err) {
58
+ const message = err instanceof Error ? err.message : String(err);
59
+ res.status(errorStatus(message)).json({ error: message });
60
+ }
61
+ }
62
+ // ── Callers (create / delete) ──────────────────────────────────────────
63
+ // POST /api/admin/callers { alias, name?, connections? }
64
+ router.post('/callers', (req, res) => {
65
+ const { alias, name, connections } = (req.body ?? {});
66
+ if (!alias || typeof alias !== 'string') {
67
+ res.status(400).json({ error: 'alias is required and must be a string' });
68
+ return;
69
+ }
70
+ if (!CALLER_ALIAS_REGEX.test(alias)) {
71
+ res.status(400).json({
72
+ error: 'alias must start with a letter or number and contain only letters, numbers, hyphens, and underscores',
73
+ });
74
+ return;
75
+ }
76
+ try {
77
+ const result = createCallerWithKeys(alias, {
78
+ ...(name !== undefined && { name }),
79
+ ...(Array.isArray(connections) && { connections }),
80
+ });
81
+ deps.reloadPeer(alias);
82
+ res.status(201).json({
83
+ alias: result.alias,
84
+ name: result.name,
85
+ fingerprint: result.fingerprint,
86
+ publicKeys: result.publicKeys,
87
+ keysDir: result.keysDir,
88
+ connections: result.connections,
89
+ });
90
+ }
91
+ catch (err) {
92
+ const message = err instanceof Error ? err.message : String(err);
93
+ res.status(errorStatus(message)).json({ error: message });
94
+ }
95
+ });
96
+ // DELETE /api/admin/callers/:alias
97
+ router.delete('/callers/:alias', (req, res) => {
98
+ try {
99
+ deleteCaller(req.params.alias);
100
+ deps.removePeer(req.params.alias);
101
+ res.json({ deleted: req.params.alias });
102
+ }
103
+ catch (err) {
104
+ const message = err instanceof Error ? err.message : String(err);
105
+ res.status(errorStatus(message)).json({ error: message });
106
+ }
107
+ });
108
+ // ── Tunnel (cloudflared) config-only toggle ────────────────────────────
109
+ //
110
+ // Persists `config.tunnel` to remote.config.json. The daemon reads this flag
111
+ // at boot to decide whether to spawn cloudflared, so a restart is required
112
+ // for the change to take effect. The dashboard surfaces this expectation via
113
+ // a banner when `tunnelEnabled` (intent) disagrees with `tunnelUrl` (runtime).
114
+ //
115
+ // PUT /api/admin/tunnel { enabled: boolean }
116
+ router.put('/tunnel', (req, res) => {
117
+ const { enabled } = (req.body ?? {});
118
+ if (typeof enabled !== 'boolean') {
119
+ res.status(400).json({ error: 'enabled must be a boolean' });
120
+ return;
121
+ }
122
+ const config = deps.loadConfig();
123
+ config.tunnel = enabled;
124
+ saveRemoteConfig(config);
125
+ res.json({ tunnel: enabled });
126
+ });
127
+ // ── Connections (enable / secrets / test) ──────────────────────────────
128
+ // POST /api/admin/callers/:alias/connections/:connection { enabled }
129
+ router.post('/callers/:alias/connections/:connection', async (req, res) => {
130
+ const { enabled } = (req.body ?? {});
131
+ if (typeof enabled !== 'boolean') {
132
+ res.status(400).json({ error: 'enabled must be a boolean' });
133
+ return;
134
+ }
135
+ await runTool(res, req.params.alias, 'set_connection_enabled', {
136
+ connection: req.params.connection,
137
+ enabled,
138
+ });
139
+ });
140
+ // PUT /api/admin/callers/:alias/connections/:connection/secrets { secrets }
141
+ router.put('/callers/:alias/connections/:connection/secrets', async (req, res) => {
142
+ const { secrets } = (req.body ?? {});
143
+ if (!secrets || typeof secrets !== 'object') {
144
+ res.status(400).json({ error: 'secrets must be an object of name → value' });
145
+ return;
146
+ }
147
+ await runTool(res, req.params.alias, 'set_secrets', { secrets });
148
+ });
149
+ // POST /api/admin/callers/:alias/connections/:connection/test
150
+ router.post('/callers/:alias/connections/:connection/test', async (req, res) => {
151
+ await runTool(res, req.params.alias, 'test_connection', {
152
+ connection: req.params.connection,
153
+ });
154
+ });
155
+ // POST /api/admin/callers/:alias/connections/:connection/test-ingestor
156
+ router.post('/callers/:alias/connections/:connection/test-ingestor', async (req, res) => {
157
+ await runTool(res, req.params.alias, 'test_ingestor', {
158
+ connection: req.params.connection,
159
+ });
160
+ });
161
+ // ── Listener / ingestor control ────────────────────────────────────────
162
+ // POST /api/admin/callers/:alias/connections/:connection/listener/control
163
+ // { action: 'start'|'stop'|'restart', instance_id? }
164
+ router.post('/callers/:alias/connections/:connection/listener/control', async (req, res) => {
165
+ const { action, instance_id } = (req.body ?? {});
166
+ if (action !== 'start' && action !== 'stop' && action !== 'restart') {
167
+ res.status(400).json({ error: "action must be 'start', 'stop', or 'restart'" });
168
+ return;
169
+ }
170
+ await runTool(res, req.params.alias, 'control_listener', {
171
+ connection: req.params.connection,
172
+ action,
173
+ ...(instance_id !== undefined && { instance_id }),
174
+ });
175
+ });
176
+ // GET /api/admin/callers/:alias/listener-configs
177
+ router.get('/callers/:alias/listener-configs', async (req, res) => {
178
+ await runTool(res, req.params.alias, 'list_listener_configs', {});
179
+ });
180
+ // GET /api/admin/callers/:alias/connections/:connection/listener/params?instance_id=
181
+ router.get('/callers/:alias/connections/:connection/listener/params', async (req, res) => {
182
+ const instanceId = req.query.instance_id;
183
+ await runTool(res, req.params.alias, 'get_listener_params', {
184
+ connection: req.params.connection,
185
+ ...(typeof instanceId === 'string' && { instance_id: instanceId }),
186
+ });
187
+ });
188
+ // PUT /api/admin/callers/:alias/connections/:connection/listener/params
189
+ // { params, instance_id?, create_instance? }
190
+ router.put('/callers/:alias/connections/:connection/listener/params', async (req, res) => {
191
+ const { params, instance_id, create_instance } = (req.body ?? {});
192
+ if (!params || typeof params !== 'object') {
193
+ res.status(400).json({ error: 'params must be an object' });
194
+ return;
195
+ }
196
+ await runTool(res, req.params.alias, 'set_listener_params', {
197
+ connection: req.params.connection,
198
+ params,
199
+ ...(instance_id !== undefined && { instance_id }),
200
+ ...(create_instance !== undefined && { create_instance }),
201
+ });
202
+ });
203
+ // GET /api/admin/callers/:alias/connections/:connection/listener/instances
204
+ router.get('/callers/:alias/connections/:connection/listener/instances', async (req, res) => {
205
+ await runTool(res, req.params.alias, 'list_listener_instances', {
206
+ connection: req.params.connection,
207
+ });
208
+ });
209
+ // POST /api/admin/callers/:alias/connections/:connection/listener/instances
210
+ // { instance_id, params? } — creates a new multi-instance listener
211
+ router.post('/callers/:alias/connections/:connection/listener/instances', async (req, res) => {
212
+ const { instance_id, params } = (req.body ?? {});
213
+ if (!instance_id || typeof instance_id !== 'string') {
214
+ res.status(400).json({ error: 'instance_id is required and must be a string' });
215
+ return;
216
+ }
217
+ if (!CALLER_ALIAS_REGEX.test(instance_id)) {
218
+ res.status(400).json({
219
+ error: 'instance_id must start with a letter or number and contain only letters, numbers, hyphens, and underscores',
220
+ });
221
+ return;
222
+ }
223
+ await runTool(res, req.params.alias, 'set_listener_params', {
224
+ connection: req.params.connection,
225
+ instance_id,
226
+ params: params ?? {},
227
+ create_instance: true,
228
+ });
229
+ });
230
+ // DELETE /api/admin/callers/:alias/connections/:connection/listener/instances/:instanceId
231
+ router.delete('/callers/:alias/connections/:connection/listener/instances/:instanceId', async (req, res) => {
232
+ await runTool(res, req.params.alias, 'delete_listener_instance', {
233
+ connection: req.params.connection,
234
+ instance_id: req.params.instanceId,
235
+ });
236
+ });
237
+ // POST /api/admin/callers/:alias/connections/:connection/listener/resolve-options
238
+ // { paramKey }
239
+ router.post('/callers/:alias/connections/:connection/listener/resolve-options', async (req, res) => {
240
+ const { paramKey } = (req.body ?? {});
241
+ if (!paramKey || typeof paramKey !== 'string') {
242
+ res.status(400).json({ error: 'paramKey is required and must be a string' });
243
+ return;
244
+ }
245
+ await runTool(res, req.params.alias, 'resolve_listener_options', {
246
+ connection: req.params.connection,
247
+ paramKey,
248
+ });
249
+ });
250
+ }
251
+ //# sourceMappingURL=admin-mutations.js.map
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Response DTOs for the daemon's `/admin/*` API (served at `/api/admin/*`).
3
+ *
4
+ * This file is the single source of truth for these shapes. `src/remote/admin.ts`
5
+ * imports them to annotate its handler responses, and the frontend imports them
6
+ * (type-only) instead of maintaining a duplicate `shared` mirror.
7
+ *
8
+ * Type-only — compiles to nothing.
9
+ */
10
+ export interface AdminMeta {
11
+ version: string;
12
+ port: number;
13
+ pid: number;
14
+ startedAt: number;
15
+ uptimeSec: number;
16
+ configPath: string;
17
+ callerKeysDir: string;
18
+ serverKeysDir: string;
19
+ envFilePath: string;
20
+ /** Public URL of the self-managed cloudflared tunnel, when active (item C). */
21
+ tunnelUrl: string | null;
22
+ /** Whether the cloudflared tunnel is enabled in config (intent flag).
23
+ * Distinct from `tunnelUrl`, which reflects the active runtime state —
24
+ * the two disagree until the daemon restarts after a toggle. */
25
+ tunnelEnabled: boolean;
26
+ }
27
+ export interface AdminIngestorCounts {
28
+ connected: number;
29
+ error: number;
30
+ starting: number;
31
+ stopped: number;
32
+ }
33
+ export interface AdminHealth {
34
+ status: 'ok';
35
+ activeSessions: number;
36
+ ingestorCounts: AdminIngestorCounts;
37
+ uptimeSec: number;
38
+ }
39
+ export interface AdminCaller {
40
+ alias: string;
41
+ name: string | null;
42
+ connections: string[];
43
+ envKeys: string[];
44
+ fingerprint: string | null;
45
+ keysDirExists: boolean;
46
+ }
47
+ export type ConnectionCategory = 'ai' | 'developer-tools' | 'gaming' | 'messaging' | 'productivity' | 'social-media';
48
+ export interface AdminConnectionTemplate {
49
+ alias: string;
50
+ name: string;
51
+ description?: string;
52
+ docsUrl?: string;
53
+ openApiUrl?: string;
54
+ stability: 'stable' | 'beta' | 'dev';
55
+ category: ConnectionCategory;
56
+ requiredSecrets: string[];
57
+ optionalSecrets: string[];
58
+ hasIngestor: boolean;
59
+ ingestorType?: 'websocket' | 'webhook' | 'poll';
60
+ hasTestConnection: boolean;
61
+ hasTestIngestor: boolean;
62
+ hasListenerConfig: boolean;
63
+ supportsMultiInstance: boolean;
64
+ allowedEndpoints: string[];
65
+ }
66
+ /** Per-caller status for EVERY connection template — whether the caller has it
67
+ * enabled, and which secrets are configured (booleans only, never values).
68
+ * This is the one-call payload the dashboard Connections page renders from. */
69
+ export interface AdminConnectionStatus extends AdminConnectionTemplate {
70
+ enabled: boolean;
71
+ requiredSecretsSet: Record<string, boolean>;
72
+ optionalSecretsSet: Record<string, boolean>;
73
+ }
74
+ export interface AdminSecretRef {
75
+ name: string;
76
+ present: boolean;
77
+ }
78
+ export interface AdminListenerInstance {
79
+ instanceId: string;
80
+ enabled: boolean;
81
+ params: Record<string, unknown>;
82
+ }
83
+ export interface AdminCallerConnection {
84
+ connectionAlias: string;
85
+ enabled: boolean;
86
+ isCustom: boolean;
87
+ requiredSecrets: AdminSecretRef[];
88
+ optionalSecrets: AdminSecretRef[];
89
+ hasIngestor: boolean;
90
+ instances: AdminListenerInstance[];
91
+ }
92
+ export type IngestorState = 'starting' | 'connected' | 'reconnecting' | 'stopped' | 'error';
93
+ export interface AdminIngestor {
94
+ callerAlias: string;
95
+ connection: string;
96
+ instanceId?: string;
97
+ type: 'websocket' | 'webhook' | 'poll';
98
+ state: IngestorState;
99
+ bufferedEvents: number;
100
+ totalEventsReceived: number;
101
+ lastEventAt: string | null;
102
+ error?: string;
103
+ webhookRegistration?: {
104
+ registered: boolean;
105
+ webhookId?: string;
106
+ error?: string;
107
+ };
108
+ }
109
+ export interface AdminSession {
110
+ sessionIdShort: string;
111
+ callerAlias: string;
112
+ createdAt: number;
113
+ lastActivity: number;
114
+ requestCount: number;
115
+ windowRequests: number;
116
+ windowStart: number;
117
+ }
118
+ /** A buffered ingestor event, as surfaced to the dashboard Logs viewer.
119
+ * This is the ingested payload from an external service (e.g. a Discord
120
+ * message) — never a drawlatch secret. */
121
+ export interface AdminEvent {
122
+ id: number;
123
+ idempotencyKey: string;
124
+ receivedAt: string;
125
+ receivedAtMs: number;
126
+ callerAlias: string;
127
+ source: string;
128
+ instanceId?: string;
129
+ eventType: string;
130
+ data: unknown;
131
+ }
132
+ export interface AdminSecret {
133
+ callerAlias: string;
134
+ connection: string;
135
+ name: string;
136
+ required: boolean;
137
+ present: boolean;
138
+ }
139
+ /**
140
+ * Legacy envelope from the standalone drawlatch-ui backend, which proxied to a
141
+ * separate daemon process and needed to surface an "offline" state when that
142
+ * process was unreachable. Retained as a type for the frontend's transition;
143
+ * same-origin calls in the merged daemon cannot refuse themselves.
144
+ */
145
+ export interface DaemonOfflineEnvelope {
146
+ daemon: 'down';
147
+ error: string;
148
+ }
149
+ //# sourceMappingURL=admin-types.d.ts.map
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Response DTOs for the daemon's `/admin/*` API (served at `/api/admin/*`).
3
+ *
4
+ * This file is the single source of truth for these shapes. `src/remote/admin.ts`
5
+ * imports them to annotate its handler responses, and the frontend imports them
6
+ * (type-only) instead of maintaining a duplicate `shared` mirror.
7
+ *
8
+ * Type-only — compiles to nothing.
9
+ */
10
+ export {};
11
+ //# sourceMappingURL=admin-types.js.map
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Read-only admin HTTP API for the merged dashboard, served at `/api/admin/*`.
3
+ *
4
+ * Trust boundary: the scrypt password gate (the admin router is mounted behind
5
+ * `requireAuth` in server.ts, wired in Step 2). This replaces the old
6
+ * loopback-only posture, so the dashboard can be exposed on a host bind
7
+ * (DRAWLATCH_HOST=0.0.0.0) while still being password-protected.
8
+ *
9
+ * This file is the type authority for the admin DTOs — see `admin-types.ts`.
10
+ *
11
+ * SECURITY non-negotiables (mirrored in /admin.test.ts):
12
+ * - Never serializes Session.channel (AES keys) or ResolvedRoute.secrets.
13
+ * - Never returns process.env values; only key names. Secret presence is
14
+ * reported via `isSecretSetForCaller()` (booleans only).
15
+ * - Caller `env` mappings (e.g. "GITHUB_TOKEN": "${ACME_GITHUB_TOKEN}") are
16
+ * reduced to key-name lists — the value strings are NOT returned.
17
+ * - No mutations: every endpoint is GET.
18
+ * - No CORS.
19
+ */
20
+ import express from 'express';
21
+ import { type RemoteServerConfig } from '../shared/config.js';
22
+ import { type AdminMutationDeps } from './admin-mutations.js';
23
+ import type { IngestorManager } from './ingestors/index.js';
24
+ import type { SessionSnapshot } from './server.js';
25
+ export interface AdminRouterDeps extends AdminMutationDeps {
26
+ /** Sanitized session snapshot — see Session in server.ts. */
27
+ getSessionsSnapshot: () => SessionSnapshot[];
28
+ /** Late-bound so tests can swap the manager without rebuilding the router. */
29
+ ingestorManager: () => IngestorManager;
30
+ /** Late-bound so changes to disk config are picked up between requests. */
31
+ loadConfig: () => RemoteServerConfig;
32
+ version: string;
33
+ port: number;
34
+ startedAt: number;
35
+ }
36
+ export declare function createAdminRouter(deps: AdminRouterDeps): express.Router;
37
+ //# sourceMappingURL=admin.d.ts.map