create-ekka-desktop-app 0.2.2

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 (96) hide show
  1. package/README.md +137 -0
  2. package/bin/cli.js +72 -0
  3. package/package.json +23 -0
  4. package/template/branding/app.json +6 -0
  5. package/template/branding/icon.icns +0 -0
  6. package/template/eslint.config.js +98 -0
  7. package/template/index.html +29 -0
  8. package/template/package.json +40 -0
  9. package/template/src/app/App.tsx +24 -0
  10. package/template/src/demo/DemoApp.tsx +260 -0
  11. package/template/src/demo/components/Banner.tsx +82 -0
  12. package/template/src/demo/components/EmptyState.tsx +61 -0
  13. package/template/src/demo/components/InfoPopover.tsx +171 -0
  14. package/template/src/demo/components/InfoTooltip.tsx +76 -0
  15. package/template/src/demo/components/LearnMore.tsx +98 -0
  16. package/template/src/demo/components/NodeCredentialsOnboarding.tsx +219 -0
  17. package/template/src/demo/components/SetupWizard.tsx +48 -0
  18. package/template/src/demo/components/StatusBadge.tsx +83 -0
  19. package/template/src/demo/components/index.ts +10 -0
  20. package/template/src/demo/hooks/index.ts +6 -0
  21. package/template/src/demo/hooks/useAuditEvents.ts +30 -0
  22. package/template/src/demo/layout/Shell.tsx +110 -0
  23. package/template/src/demo/layout/Sidebar.tsx +192 -0
  24. package/template/src/demo/pages/AuditLogPage.tsx +235 -0
  25. package/template/src/demo/pages/DocGenPage.tsx +874 -0
  26. package/template/src/demo/pages/HomeSetupPage.tsx +182 -0
  27. package/template/src/demo/pages/LoginPage.tsx +192 -0
  28. package/template/src/demo/pages/PathPermissionsPage.tsx +873 -0
  29. package/template/src/demo/pages/RunnerPage.tsx +445 -0
  30. package/template/src/demo/pages/SystemPage.tsx +557 -0
  31. package/template/src/demo/pages/VaultPage.tsx +805 -0
  32. package/template/src/ekka/__tests__/demo-backend.test.ts +187 -0
  33. package/template/src/ekka/audit/index.ts +7 -0
  34. package/template/src/ekka/audit/store.ts +68 -0
  35. package/template/src/ekka/audit/types.ts +22 -0
  36. package/template/src/ekka/auth/client.ts +212 -0
  37. package/template/src/ekka/auth/index.ts +30 -0
  38. package/template/src/ekka/auth/storage.ts +114 -0
  39. package/template/src/ekka/auth/types.ts +67 -0
  40. package/template/src/ekka/backend/demo.ts +151 -0
  41. package/template/src/ekka/backend/interface.ts +36 -0
  42. package/template/src/ekka/config.ts +48 -0
  43. package/template/src/ekka/constants.ts +143 -0
  44. package/template/src/ekka/errors.ts +54 -0
  45. package/template/src/ekka/index.ts +516 -0
  46. package/template/src/ekka/internal/backend.ts +156 -0
  47. package/template/src/ekka/internal/index.ts +7 -0
  48. package/template/src/ekka/ops/auth.ts +29 -0
  49. package/template/src/ekka/ops/debug.ts +68 -0
  50. package/template/src/ekka/ops/home.ts +101 -0
  51. package/template/src/ekka/ops/index.ts +16 -0
  52. package/template/src/ekka/ops/nodeCredentials.ts +131 -0
  53. package/template/src/ekka/ops/nodeSession.ts +145 -0
  54. package/template/src/ekka/ops/paths.ts +183 -0
  55. package/template/src/ekka/ops/runner.ts +86 -0
  56. package/template/src/ekka/ops/runtime.ts +31 -0
  57. package/template/src/ekka/ops/setup.ts +47 -0
  58. package/template/src/ekka/ops/vault.ts +459 -0
  59. package/template/src/ekka/ops/workflowRuns.ts +116 -0
  60. package/template/src/ekka/types.ts +82 -0
  61. package/template/src/ekka/utils/idempotency.ts +14 -0
  62. package/template/src/ekka/utils/index.ts +7 -0
  63. package/template/src/ekka/utils/time.ts +77 -0
  64. package/template/src/main.tsx +12 -0
  65. package/template/src/vite-env.d.ts +12 -0
  66. package/template/src-tauri/Cargo.toml +41 -0
  67. package/template/src-tauri/build.rs +3 -0
  68. package/template/src-tauri/capabilities/default.json +11 -0
  69. package/template/src-tauri/icons/icon.icns +0 -0
  70. package/template/src-tauri/icons/icon.png +0 -0
  71. package/template/src-tauri/resources/ekka-engine-bootstrap +0 -0
  72. package/template/src-tauri/src/bootstrap.rs +37 -0
  73. package/template/src-tauri/src/commands.rs +1215 -0
  74. package/template/src-tauri/src/device_secret.rs +111 -0
  75. package/template/src-tauri/src/engine_process.rs +538 -0
  76. package/template/src-tauri/src/grants.rs +129 -0
  77. package/template/src-tauri/src/handlers/home.rs +65 -0
  78. package/template/src-tauri/src/handlers/mod.rs +7 -0
  79. package/template/src-tauri/src/handlers/paths.rs +128 -0
  80. package/template/src-tauri/src/handlers/vault.rs +680 -0
  81. package/template/src-tauri/src/main.rs +243 -0
  82. package/template/src-tauri/src/node_auth.rs +858 -0
  83. package/template/src-tauri/src/node_credentials.rs +541 -0
  84. package/template/src-tauri/src/node_runner.rs +882 -0
  85. package/template/src-tauri/src/node_vault_crypto.rs +113 -0
  86. package/template/src-tauri/src/node_vault_store.rs +267 -0
  87. package/template/src-tauri/src/ops/auth.rs +50 -0
  88. package/template/src-tauri/src/ops/home.rs +251 -0
  89. package/template/src-tauri/src/ops/mod.rs +7 -0
  90. package/template/src-tauri/src/ops/runtime.rs +21 -0
  91. package/template/src-tauri/src/state.rs +639 -0
  92. package/template/src-tauri/src/types.rs +84 -0
  93. package/template/src-tauri/tauri.conf.json +41 -0
  94. package/template/tsconfig.json +26 -0
  95. package/template/tsconfig.tsbuildinfo +1 -0
  96. package/template/vite.config.ts +34 -0
@@ -0,0 +1,516 @@
1
+ /**
2
+ * EKKA Client
3
+ *
4
+ * Simple API for frontend developers.
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * import { ekka } from '@ekka/client';
9
+ *
10
+ * // Connect and setup
11
+ * await ekka.connect();
12
+ * await ekka.auth.login('user@example.com', 'password');
13
+ * await ekka.home.setup();
14
+ *
15
+ * // Check status
16
+ * const ready = await ekka.home.isReady();
17
+ * ```
18
+ */
19
+
20
+ import { _internal } from './internal';
21
+ import * as authClient from './auth/client';
22
+ import * as ops from './ops';
23
+
24
+ // =============================================================================
25
+ // SIMPLE API (for most developers)
26
+ // =============================================================================
27
+
28
+ export const ekka = {
29
+ // ---------------------------------------------------------------------------
30
+ // Setup (pre-login device configuration)
31
+ // ---------------------------------------------------------------------------
32
+
33
+ setup: {
34
+ /**
35
+ * Get setup status.
36
+ * Call before login to check if setup wizard is needed.
37
+ * Returns { nodeIdentity, setupComplete }
38
+ * Home folder grant is handled post-login via HomeSetupPage.
39
+ */
40
+ status: () => ops.setup.status(),
41
+ },
42
+
43
+ // ---------------------------------------------------------------------------
44
+ // Connection
45
+ // ---------------------------------------------------------------------------
46
+
47
+ /** Connect to EKKA. Call this first. */
48
+ connect: () => _internal.connect(),
49
+
50
+ /** Disconnect from EKKA. */
51
+ disconnect: () => _internal.disconnect(),
52
+
53
+ /** Check if connected. */
54
+ isConnected: () => _internal.isConnected(),
55
+
56
+ // ---------------------------------------------------------------------------
57
+ // Node Credentials (headless engine startup)
58
+ // ---------------------------------------------------------------------------
59
+
60
+ nodeCredentials: {
61
+ /**
62
+ * Set node credentials.
63
+ * Stores node_id + node_secret in OS keychain.
64
+ * Validates UUID format and secret length.
65
+ */
66
+ set: (nodeId: string, nodeSecret: string) =>
67
+ ops.nodeCredentials.set({ nodeId, nodeSecret }),
68
+
69
+ /**
70
+ * Get credentials status.
71
+ * Returns { hasCredentials, nodeId } - does NOT return secret.
72
+ */
73
+ status: () => ops.nodeCredentials.status(),
74
+
75
+ /**
76
+ * Clear credentials from keychain.
77
+ */
78
+ clear: () => ops.nodeCredentials.clear(),
79
+
80
+ /**
81
+ * Validate node_id format (UUID).
82
+ */
83
+ isValidNodeId: ops.nodeCredentials.isValidNodeId,
84
+
85
+ /**
86
+ * Validate node_secret format.
87
+ */
88
+ isValidNodeSecret: ops.nodeCredentials.isValidNodeSecret,
89
+ },
90
+
91
+ // ---------------------------------------------------------------------------
92
+ // Auth (simple - login/logout)
93
+ // ---------------------------------------------------------------------------
94
+
95
+ auth: {
96
+ /**
97
+ * Login with email and password.
98
+ * Handles HTTP auth + engine context setup + node session bootstrap automatically.
99
+ *
100
+ * After login:
101
+ * 1. Sets engine auth context
102
+ * 2. Bootstraps node session (Ed25519 challenge/response)
103
+ * 3. Starts the runner automatically
104
+ */
105
+ login: async (identifier: string, password: string): Promise<void> => {
106
+ const response = await authClient.login(identifier, password);
107
+
108
+ // Set engine auth context
109
+ const tenantId = response.user.company?.id || 'default';
110
+ const sub = response.user.id;
111
+ const jwt = response.access_token;
112
+
113
+ // Use tenant_id as default workspace_id (can be overridden later)
114
+ // TODO: Get workspace_id from JWT claims or user selection
115
+ const workspaceId = tenantId;
116
+
117
+ await ops.auth.setContext({ tenantId, sub, jwt, workspaceId });
118
+
119
+ // Bootstrap node session and start runner
120
+ try {
121
+ await ops.nodeSession.bootstrap({ startRunner: true });
122
+ } catch (e) {
123
+ // Log but don't fail login - runner can be started later
124
+ console.warn('[ekka] Node session bootstrap failed:', e);
125
+ }
126
+ },
127
+
128
+ /** Logout and clear session. */
129
+ logout: async (): Promise<void> => {
130
+ await authClient.logout();
131
+ },
132
+
133
+ /** Check if logged in. */
134
+ isLoggedIn: (): boolean => authClient.isAuthenticated(),
135
+
136
+ /** Get current user info. */
137
+ user: (): authClient.UserInfo | null => authClient.getCurrentUser(),
138
+ },
139
+
140
+ // ---------------------------------------------------------------------------
141
+ // Home (simple - setup/isReady)
142
+ // ---------------------------------------------------------------------------
143
+
144
+ home: {
145
+ /**
146
+ * Set up home directory.
147
+ * Just call this after login - it handles everything.
148
+ */
149
+ setup: () => ops.home.setup(),
150
+
151
+ /**
152
+ * Check if home is ready.
153
+ * Returns true/false - no scary state machines.
154
+ */
155
+ isReady: () => ops.home.isReady(),
156
+ },
157
+
158
+ // ---------------------------------------------------------------------------
159
+ // Paths (simple - check/allow access)
160
+ // ---------------------------------------------------------------------------
161
+
162
+ paths: {
163
+ /**
164
+ * Check if an operation is allowed on a path.
165
+ * Returns true/false - simple boolean.
166
+ */
167
+ isAllowed: (path: string, operation?: string) =>
168
+ ops.paths.isAllowed(path, operation),
169
+
170
+ /**
171
+ * List all path grants.
172
+ */
173
+ list: (type?: ops.paths.PathType) => ops.paths.list(type),
174
+
175
+ /**
176
+ * Get information about a specific path.
177
+ */
178
+ get: (path: string) => ops.paths.get(path),
179
+
180
+ /**
181
+ * Allow access to a path.
182
+ * Requests a grant from the engine.
183
+ */
184
+ allow: (path: string, options?: ops.paths.PathRequestOptions) =>
185
+ ops.paths.allow(path, options),
186
+
187
+ /**
188
+ * Remove a path grant.
189
+ */
190
+ remove: (path: string) => ops.paths.remove(path),
191
+ },
192
+
193
+ // ---------------------------------------------------------------------------
194
+ // Vault (status, secrets, bundles, files, audit)
195
+ // SECURITY: Secret values are NEVER returned by the API.
196
+ // ---------------------------------------------------------------------------
197
+
198
+ vault: {
199
+ /** Get vault status */
200
+ status: () => ops.vault.status(),
201
+
202
+ /** Get vault capabilities */
203
+ capabilities: () => ops.vault.capabilities(),
204
+
205
+ /**
206
+ * Secret operations (metadata only - values never returned)
207
+ */
208
+ secrets: {
209
+ /** List all secrets (metadata only, NO values) */
210
+ list: (opts?: ops.vault.SecretListOptions) => ops.vault.secrets.list(opts),
211
+ /** Get a secret by ID (metadata only, NO value) */
212
+ get: (id: string) => ops.vault.secrets.get(id),
213
+ /** Create a new secret (value accepted here only) */
214
+ create: (input: ops.vault.SecretCreateInput) => ops.vault.secrets.create(input),
215
+ /** Update a secret (value accepted here only) */
216
+ update: (id: string, input: ops.vault.SecretUpdateInput) => ops.vault.secrets.update(id, input),
217
+ /** Delete a secret */
218
+ delete: (id: string) => ops.vault.secrets.delete(id),
219
+ /** Upsert a secret (create or update by name) */
220
+ upsert: (input: ops.vault.SecretCreateInput) => ops.vault.secrets.upsert(input),
221
+ },
222
+
223
+ /**
224
+ * Bundle operations (groups of related secrets)
225
+ */
226
+ bundles: {
227
+ /** List all bundles */
228
+ list: (opts?: ops.vault.BundleListOptions) => ops.vault.bundles.list(opts),
229
+ /** Get a bundle by ID */
230
+ get: (id: string) => ops.vault.bundles.get(id),
231
+ /** Create a new bundle */
232
+ create: (input: ops.vault.BundleCreateInput) => ops.vault.bundles.create(input),
233
+ /** Rename a bundle */
234
+ rename: (id: string, name: string) => ops.vault.bundles.rename(id, name),
235
+ /** Delete a bundle */
236
+ delete: (id: string) => ops.vault.bundles.delete(id),
237
+ /** List secrets in a bundle */
238
+ listSecrets: (bundleId: string, opts?: ops.vault.SecretListOptions) => ops.vault.bundles.listSecrets(bundleId, opts),
239
+ /** Add a secret to a bundle */
240
+ addSecret: (bundleId: string, secretId: string) => ops.vault.bundles.addSecret(bundleId, secretId),
241
+ /** Remove a secret from a bundle */
242
+ removeSecret: (bundleId: string, secretId: string) => ops.vault.bundles.removeSecret(bundleId, secretId),
243
+ },
244
+
245
+ /**
246
+ * Files operations (encrypted file storage)
247
+ * Paths are relative to workspace root. Chroot enforced.
248
+ */
249
+ files: {
250
+ /** Write text content to a file */
251
+ writeText: (path: string, content: string, opts?: ops.vault.FileOptions) =>
252
+ ops.vault.files.writeText(path, content, opts),
253
+ /** Write binary content to a file (content as base64) */
254
+ writeBytes: (path: string, contentBytes: string, opts?: ops.vault.FileOptions) =>
255
+ ops.vault.files.writeBytes(path, contentBytes, opts),
256
+ /** Read text content from a file */
257
+ readText: (path: string, opts?: ops.vault.FileOptions) =>
258
+ ops.vault.files.readText(path, opts),
259
+ /** Read binary content from a file (returns base64) */
260
+ readBytes: (path: string, opts?: ops.vault.FileOptions) =>
261
+ ops.vault.files.readBytes(path, opts),
262
+ /** List files and directories */
263
+ list: (dir: string, opts?: ops.vault.FileListOptions) =>
264
+ ops.vault.files.list(dir, opts),
265
+ /** Check if a file or directory exists */
266
+ exists: (path: string, opts?: ops.vault.FileOptions) =>
267
+ ops.vault.files.exists(path, opts),
268
+ /** Delete a file or directory */
269
+ delete: (path: string, opts?: ops.vault.FileDeleteOptions) =>
270
+ ops.vault.files.delete(path, opts),
271
+ /** Create a directory */
272
+ mkdir: (path: string, opts?: ops.vault.FileOptions) =>
273
+ ops.vault.files.mkdir(path, opts),
274
+ /** Move a file or directory */
275
+ move: (from: string, to: string, opts?: ops.vault.FileOptions) =>
276
+ ops.vault.files.move(from, to, opts),
277
+ },
278
+
279
+ /**
280
+ * Attach secrets to a connector (DEFERRED - returns NOT_IMPLEMENTED)
281
+ */
282
+ attachSecretsToConnector: (connectorId: string, mappings: ops.vault.SecretRef[]) =>
283
+ ops.vault.attachSecretsToConnector(connectorId, mappings),
284
+
285
+ /**
286
+ * Inject secrets into a run (DEFERRED - returns NOT_IMPLEMENTED)
287
+ */
288
+ injectSecretsIntoRun: (runId: string, mappings: ops.vault.SecretRef[]) =>
289
+ ops.vault.injectSecretsIntoRun(runId, mappings),
290
+
291
+ /**
292
+ * Audit log operations (cursor-based pagination)
293
+ */
294
+ audit: {
295
+ /** List audit events */
296
+ list: (opts?: ops.vault.AuditListOptions) => ops.vault.audit.list(opts),
297
+ },
298
+ },
299
+ };
300
+
301
+ // =============================================================================
302
+ // ADVANCED API (for power users)
303
+ // =============================================================================
304
+
305
+ export const advanced = {
306
+ /**
307
+ * Auth operations (low-level)
308
+ */
309
+ auth: {
310
+ /** Set engine auth context directly. */
311
+ setContext: ops.auth.setContext,
312
+
313
+ /** Refresh access token. */
314
+ refresh: authClient.refresh,
315
+
316
+ /** Subscribe to auth state changes. */
317
+ onAuthChange: authClient.onAuthChange,
318
+ },
319
+
320
+ /**
321
+ * Home operations (low-level)
322
+ */
323
+ home: {
324
+ /** Get raw home status (state machine). */
325
+ status: ops.home.status,
326
+
327
+ /** Request home grant manually. */
328
+ grant: ops.home.grant,
329
+ },
330
+
331
+ /**
332
+ * Paths operations (low-level)
333
+ */
334
+ paths: {
335
+ /** Check if operation is allowed (with details). */
336
+ check: ops.paths.check,
337
+
338
+ /** List all path grants. */
339
+ list: ops.paths.list,
340
+
341
+ /** Get path info. */
342
+ get: ops.paths.get,
343
+
344
+ /** Request path access. */
345
+ request: ops.paths.request,
346
+
347
+ /** Remove path grant. */
348
+ remove: ops.paths.remove,
349
+ },
350
+
351
+ /**
352
+ * Runtime operations
353
+ */
354
+ runtime: {
355
+ /** Get runtime info. */
356
+ info: ops.runtime.info,
357
+ },
358
+
359
+ /**
360
+ * Runner operations (local runner status + task queue stats)
361
+ */
362
+ runner: {
363
+ /** Get local runner status for this desktop instance. */
364
+ status: ops.runner.status,
365
+ /** Get task queue stats from engine API (proxied via Rust). */
366
+ taskStats: ops.runner.taskStats,
367
+ },
368
+
369
+ /**
370
+ * Node session operations (Ed25519-based authentication)
371
+ */
372
+ nodeSession: {
373
+ /** Ensure node identity exists (keypair generation). No auth required. */
374
+ ensureIdentity: ops.nodeSession.ensureIdentity,
375
+ /** Bootstrap full node session. Requires user to be logged in. */
376
+ bootstrap: ops.nodeSession.bootstrap,
377
+ /** Get current node session status. */
378
+ status: ops.nodeSession.status,
379
+ },
380
+
381
+ /**
382
+ * Node credentials operations (keychain-stored)
383
+ */
384
+ nodeCredentials: {
385
+ /** Set node credentials in keychain. */
386
+ set: ops.nodeCredentials.set,
387
+ /** Get credentials status. */
388
+ status: ops.nodeCredentials.status,
389
+ /** Clear credentials from keychain. */
390
+ clear: ops.nodeCredentials.clear,
391
+ },
392
+
393
+ /**
394
+ * Vault operations (low-level)
395
+ */
396
+ vault: {
397
+ /** Vault status */
398
+ status: ops.vault.status,
399
+ /** Vault capabilities */
400
+ capabilities: ops.vault.capabilities,
401
+ /** Secret operations (metadata only) */
402
+ secrets: ops.vault.secrets,
403
+ /** Bundle operations */
404
+ bundles: ops.vault.bundles,
405
+ /** Files operations */
406
+ files: ops.vault.files,
407
+ /** Attach secrets to connector */
408
+ attachSecretsToConnector: ops.vault.attachSecretsToConnector,
409
+ /** Inject secrets into run */
410
+ injectSecretsIntoRun: ops.vault.injectSecretsIntoRun,
411
+ /** Audit operations */
412
+ audit: ops.vault.audit,
413
+ },
414
+
415
+ /**
416
+ * Internal backend access
417
+ */
418
+ internal: {
419
+ /** Get current transport mode. */
420
+ mode: () => _internal.getMode(),
421
+ },
422
+ };
423
+
424
+ // =============================================================================
425
+ // TYPE EXPORTS
426
+ // =============================================================================
427
+
428
+ export type { SetupState, SetupStatus } from './ops/setup';
429
+ export type { HomeState, HomeStatus, GrantResult } from './ops/home';
430
+ export type {
431
+ PathType,
432
+ PathAccess,
433
+ PathInfo,
434
+ PathCheckResult,
435
+ PathGrantResult,
436
+ PathRequestOptions,
437
+ } from './ops/paths';
438
+ export type { RuntimeInfo } from './ops/runtime';
439
+ export type { RunnerStatus, RunnerLoopState, RunnerTaskStats } from './ops/runner';
440
+ export type { AuthContext } from './ops/auth';
441
+ export type {
442
+ NodeIdentity,
443
+ EnsureIdentityResult,
444
+ BootstrapOptions,
445
+ BootstrapResult,
446
+ SessionInfo,
447
+ NodeSessionStatus,
448
+ } from './ops/nodeSession';
449
+ export type {
450
+ SetCredentialsInput,
451
+ SetCredentialsResult,
452
+ CredentialsStatus,
453
+ NodeAuthSession,
454
+ } from './ops/nodeCredentials';
455
+ export type { UserInfo, AuthTokens } from './auth/types';
456
+ export type { TransportMode } from './internal';
457
+
458
+ // Vault types
459
+ export type {
460
+ VaultStatus,
461
+ VaultCapabilities,
462
+ SecretType,
463
+ SecretMeta,
464
+ SecretCreateInput,
465
+ SecretUpdateInput,
466
+ SecretListOptions,
467
+ BundleMeta,
468
+ BundleCreateInput,
469
+ BundleListOptions,
470
+ FileKind,
471
+ FileEntry,
472
+ FileOptions,
473
+ FileListOptions,
474
+ FileDeleteOptions,
475
+ SecretInjection,
476
+ SecretRef,
477
+ AuditAction,
478
+ AuditEvent,
479
+ AuditListOptions,
480
+ AuditListResult,
481
+ } from './ops/vault';
482
+
483
+ // =============================================================================
484
+ // ERROR EXPORTS
485
+ // =============================================================================
486
+
487
+ export {
488
+ EkkaError,
489
+ EkkaNotConnectedError,
490
+ EkkaConnectionError,
491
+ EkkaApiError,
492
+ } from './errors';
493
+
494
+ // =============================================================================
495
+ // UTILITIES (optional helpers)
496
+ // =============================================================================
497
+
498
+ export {
499
+ formatRelativeTime,
500
+ formatLocalTime,
501
+ formatExpiryInfo,
502
+ } from './utils/time';
503
+
504
+ export { generateIdempotencyKey } from './utils/idempotency';
505
+
506
+ // =============================================================================
507
+ // AUDIT (client-side event logging)
508
+ // =============================================================================
509
+
510
+ export type { AuditEvent as ClientAuditEvent } from './audit/types';
511
+ export {
512
+ addAuditEvent,
513
+ getAuditEvents,
514
+ clearAuditEvents,
515
+ subscribeToAuditEvents,
516
+ } from './audit/store';
@@ -0,0 +1,156 @@
1
+ /**
2
+ * EKKA Internal Backend
3
+ *
4
+ * SmartBackend auto-detects engine vs demo mode on connect.
5
+ * This module is NOT exported publicly - only accessible via _internal.
6
+ */
7
+
8
+ import type { EngineRequest, EngineResponse } from '../types';
9
+ import { err, makeRequest } from '../types';
10
+ import { DemoBackend } from '../backend/demo';
11
+
12
+ /**
13
+ * Transport mode - determined on connect.
14
+ */
15
+ export type TransportMode = 'unknown' | 'engine' | 'demo';
16
+
17
+ /**
18
+ * SmartBackend - single backend that auto-detects engine vs demo.
19
+ *
20
+ * On connect():
21
+ * - Tries to connect to Tauri engine
22
+ * - If successful: engine mode
23
+ * - If fails: demo mode (in-memory)
24
+ */
25
+ class SmartBackend {
26
+ private mode: TransportMode = 'unknown';
27
+ private connected = false;
28
+ private demoBackend = new DemoBackend();
29
+
30
+ /**
31
+ * Connect to the backend.
32
+ * Auto-detects engine vs demo mode.
33
+ */
34
+ async connect(): Promise<void> {
35
+ if (this.connected) return;
36
+
37
+ // Try engine first (only works in Tauri with engine present)
38
+ try {
39
+ const { invoke } = await import('@tauri-apps/api/core');
40
+ await invoke('engine_connect');
41
+ this.mode = 'engine';
42
+ this.connected = true;
43
+ return;
44
+ } catch {
45
+ // Engine not available - use demo mode
46
+ }
47
+
48
+ // Fall back to demo mode
49
+ this.mode = 'demo';
50
+ await this.demoBackend.connect();
51
+ this.connected = true;
52
+ }
53
+
54
+ /**
55
+ * Disconnect from the backend.
56
+ */
57
+ disconnect(): void {
58
+ if (!this.connected) return;
59
+
60
+ if (this.mode === 'engine') {
61
+ // Fire and forget
62
+ import('@tauri-apps/api/core')
63
+ .then(({ invoke }) => invoke('engine_disconnect'))
64
+ .catch(() => {});
65
+ } else {
66
+ this.demoBackend.disconnect();
67
+ }
68
+
69
+ this.connected = false;
70
+ this.mode = 'unknown';
71
+ }
72
+
73
+ /**
74
+ * Check if connected.
75
+ */
76
+ isConnected(): boolean {
77
+ return this.connected;
78
+ }
79
+
80
+ /**
81
+ * Get current transport mode.
82
+ */
83
+ getMode(): TransportMode {
84
+ return this.mode;
85
+ }
86
+
87
+ /**
88
+ * Send a request to the backend.
89
+ */
90
+ async request(req: EngineRequest): Promise<EngineResponse> {
91
+ // LOCAL-ONLY OPERATIONS: Always route to Tauri, never to demo backend
92
+ // These are desktop-specific operations that must be handled by Rust handlers
93
+ const localOnlyOps = [
94
+ 'setup.status',
95
+ 'nodeCredentials.set',
96
+ 'nodeCredentials.status',
97
+ 'nodeCredentials.clear',
98
+ ];
99
+
100
+ const isLocalOnlyOp = localOnlyOps.includes(req.op);
101
+
102
+ // Local-only ops ALWAYS go to Tauri - regardless of connection state or mode
103
+ // This ensures setup operations never accidentally route to demo backend
104
+ if (isLocalOnlyOp) {
105
+ console.log(`[ts.op.dispatch] op=${req.op} backend=tauri (local-only)`);
106
+ try {
107
+ const { invoke } = await import('@tauri-apps/api/core');
108
+ return await invoke<EngineResponse>('engine_request', { req });
109
+ } catch (e) {
110
+ const message = e instanceof Error ? e.message : 'Tauri not available';
111
+ console.error(`[ts.op.dispatch] op=${req.op} backend=tauri FAILED: ${message}`);
112
+ return err('TAURI_NOT_READY', message);
113
+ }
114
+ }
115
+
116
+ if (!this.connected) {
117
+ return err('NOT_CONNECTED', 'Not connected. Call ekka.connect() first.');
118
+ }
119
+
120
+ console.log(`[ts.op.dispatch] op=${req.op} backend=${this.mode} connected=${this.connected}`);
121
+
122
+ if (this.mode === 'engine') {
123
+ try {
124
+ const { invoke } = await import('@tauri-apps/api/core');
125
+ return await invoke<EngineResponse>('engine_request', { req });
126
+ } catch (e) {
127
+ const message = e instanceof Error ? e.message : 'Unknown invoke error';
128
+ return err('INTERNAL_ERROR', message);
129
+ }
130
+ }
131
+
132
+ // Demo mode
133
+ return this.demoBackend.request(req);
134
+ }
135
+ }
136
+
137
+ // =============================================================================
138
+ // INTERNAL API (not exported from package)
139
+ // =============================================================================
140
+
141
+ const backend = new SmartBackend();
142
+
143
+ /**
144
+ * Internal API - only accessible within the ekka package.
145
+ * NOT exported from the main index.ts.
146
+ */
147
+ export const _internal = {
148
+ connect: () => backend.connect(),
149
+ disconnect: () => backend.disconnect(),
150
+ isConnected: () => backend.isConnected(),
151
+ getMode: () => backend.getMode(),
152
+ request: (req: EngineRequest) => backend.request(req),
153
+ };
154
+
155
+ // Re-export makeRequest for use by index.ts
156
+ export { makeRequest };
@@ -0,0 +1,7 @@
1
+ /**
2
+ * EKKA Internal Module
3
+ *
4
+ * Internal implementation details - NOT exported from package.
5
+ */
6
+
7
+ export { _internal, makeRequest, type TransportMode } from './backend';
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Auth Operations
3
+ *
4
+ * Wraps Rust ops/auth.rs
5
+ */
6
+
7
+ import { OPS } from '../constants';
8
+ import { _internal, makeRequest } from '../internal';
9
+
10
+ export interface AuthContext {
11
+ tenantId: string;
12
+ sub: string;
13
+ jwt: string;
14
+ /** Workspace ID (required for node session registration) */
15
+ workspaceId?: string;
16
+ }
17
+
18
+ /**
19
+ * Set authentication context in engine.
20
+ * Maps to Rust: auth.set
21
+ */
22
+ export async function setContext(ctx: AuthContext): Promise<void> {
23
+ const req = makeRequest(OPS.AUTH_SET, ctx);
24
+ const response = await _internal.request(req);
25
+
26
+ if (!response.ok) {
27
+ throw new Error(response.error?.message || 'Failed to set auth context');
28
+ }
29
+ }