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,243 @@
1
+ //! EKKA Desktop - Entry Point
2
+ //!
3
+ //! Minimal main.rs that sets up Tauri with the engine commands.
4
+ //! Starts embedded runner loop on app startup.
5
+
6
+ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
7
+
8
+ mod bootstrap;
9
+ mod commands;
10
+ mod device_secret;
11
+ mod engine_process;
12
+ mod grants;
13
+ mod handlers;
14
+ mod node_auth;
15
+ mod node_credentials;
16
+ mod node_runner;
17
+ mod node_vault_crypto;
18
+ mod node_vault_store;
19
+ mod ops;
20
+ mod state;
21
+ mod types;
22
+
23
+ use commands::{engine_connect, engine_disconnect, engine_request};
24
+ use engine_process::EngineProcess;
25
+ use state::EngineState;
26
+ use std::path::PathBuf;
27
+ use std::sync::Arc;
28
+ use tauri::Manager;
29
+
30
+ fn main() {
31
+ // Load .env.local for development (before anything else)
32
+ // This provides ENGINE_GRANT_VERIFY_KEY_B64, EKKA_SECURITY_EPOCH, etc.
33
+ if let Err(e) = dotenvy::from_filename(".env.local") {
34
+ // Also try parent directory (when running from src-tauri)
35
+ let _ = dotenvy::from_filename("../.env.local");
36
+ // Silence error in production where .env.local may not exist
37
+ if std::env::var("ENGINE_GRANT_VERIFY_KEY_B64").is_err() {
38
+ eprintln!("Warning: .env.local not loaded and ENGINE_GRANT_VERIFY_KEY_B64 not set: {}", e);
39
+ }
40
+ }
41
+
42
+ // Initialize tracing for runner logs
43
+ tracing_subscriber::fmt()
44
+ .with_env_filter(
45
+ tracing_subscriber::EnvFilter::from_default_env()
46
+ .add_directive("ekka_runner_core=info".parse().unwrap())
47
+ .add_directive("ekka_runner_local=info".parse().unwrap())
48
+ .add_directive("ekka_desktop_app=info".parse().unwrap())
49
+ .add_directive("ekka_node_auth=info".parse().unwrap()),
50
+ )
51
+ .with_target(true)
52
+ .init();
53
+
54
+ // Create engine process holder
55
+ let engine_process = Arc::new(EngineProcess::new());
56
+ let engine_for_state = engine_process.clone();
57
+ let engine_for_setup = engine_process.clone();
58
+ let engine_for_shutdown = engine_process;
59
+
60
+ tauri::Builder::default()
61
+ .plugin(tauri_plugin_dialog::init())
62
+ .manage(EngineState::with_engine(engine_for_state))
63
+ .setup(move |app| {
64
+ // Attempt to spawn engine process
65
+ tracing::info!(op = "desktop.startup", "EKKA Desktop starting");
66
+
67
+ // Log required env vars status
68
+ let grant_key_set = std::env::var("ENGINE_GRANT_VERIFY_KEY_B64").is_ok();
69
+ let security_epoch_set = std::env::var("EKKA_SECURITY_EPOCH").is_ok();
70
+ tracing::info!(
71
+ op = "desktop.required_env.loaded",
72
+ ENGINE_GRANT_VERIFY_KEY_B64 = grant_key_set,
73
+ EKKA_SECURITY_EPOCH = security_epoch_set,
74
+ "Required security env vars"
75
+ );
76
+
77
+ // Check for stored node credentials (vault-backed)
78
+ let has_creds = node_credentials::has_credentials();
79
+
80
+ if !has_creds {
81
+ tracing::warn!(
82
+ op = "desktop.node.credentials.missing",
83
+ "Node credentials not configured - onboarding required, engine start blocked"
84
+ );
85
+ // Engine will not be spawned - available will remain false
86
+ // UI should show onboarding flow
87
+ }
88
+
89
+ // Resolve resource path for bootstrap binary
90
+ let resource_path: Option<PathBuf> = app
91
+ .path()
92
+ .resource_dir()
93
+ .ok()
94
+ .map(|dir: PathBuf| dir.join("resources").join("ekka-engine-bootstrap"));
95
+
96
+ // Get node auth token holder and auth state to pass to spawn thread
97
+ let state_handle = app.state::<EngineState>();
98
+ let node_auth_holder = state_handle.node_auth_token.clone();
99
+ let node_auth_state = state_handle.node_auth_state.clone();
100
+
101
+ // Spawn engine in background thread to not block UI
102
+ let engine = engine_for_setup.clone();
103
+ std::thread::spawn(move || {
104
+ // Gate: Skip engine spawn if no credentials
105
+ if !has_creds {
106
+ tracing::info!(
107
+ op = "desktop.engine.start.blocked",
108
+ reason = "missing_credentials",
109
+ "Engine start blocked - node credentials required"
110
+ );
111
+ return;
112
+ }
113
+
114
+ // Authenticate node with server before spawning engine
115
+ let engine_url = match std::env::var("EKKA_ENGINE_URL") {
116
+ Ok(url) => url,
117
+ Err(_) => {
118
+ tracing::warn!(
119
+ op = "desktop.engine.start.blocked",
120
+ reason = "missing_engine_url",
121
+ "Engine start blocked - EKKA_ENGINE_URL not set"
122
+ );
123
+ return;
124
+ }
125
+ };
126
+
127
+ // Single-flight: try to acquire auth lock
128
+ if !node_auth_state.try_start() {
129
+ tracing::info!(
130
+ op = "desktop.node.auth.skipped",
131
+ reason = "already_in_progress_or_completed",
132
+ "Skipping auth - already attempted"
133
+ );
134
+ return;
135
+ }
136
+
137
+ tracing::info!(
138
+ op = "desktop.node.auth.attempt",
139
+ reason = "startup",
140
+ "Authenticating node from vault"
141
+ );
142
+
143
+ match node_credentials::authenticate_node(&engine_url) {
144
+ Ok(token) => {
145
+ // Store token in state (in-memory only)
146
+ node_auth_holder.set(token);
147
+ node_auth_state.set_authenticated();
148
+ tracing::info!(
149
+ op = "desktop.node.auth.complete",
150
+ "Node authenticated, proceeding to engine spawn"
151
+ );
152
+ }
153
+ Err(node_credentials::CredentialsError::AuthFailed(status, ref body)) => {
154
+ let error_msg = format!("Auth failed: HTTP {}", status);
155
+ node_auth_state.set_failed(error_msg);
156
+
157
+ // Check if this is a secret error (invalid or revoked)
158
+ if node_credentials::is_secret_error(status, body) {
159
+ tracing::warn!(
160
+ op = "desktop.node.auth.failed",
161
+ status = status,
162
+ "Node secret is invalid or revoked - clearing credentials"
163
+ );
164
+ // Clear invalid credentials from keychain
165
+ let _ = node_credentials::clear_credentials();
166
+ } else {
167
+ tracing::warn!(
168
+ op = "desktop.node.auth.failed",
169
+ status = status,
170
+ "Node authentication failed - will not retry"
171
+ );
172
+ }
173
+ return;
174
+ }
175
+ Err(e) => {
176
+ let error_msg = format!("Auth failed: {}", e);
177
+ node_auth_state.set_failed(error_msg);
178
+ tracing::warn!(
179
+ op = "desktop.node.auth.failed",
180
+ error = %e,
181
+ "Node authentication failed - will not retry"
182
+ );
183
+ return;
184
+ }
185
+ }
186
+
187
+ // Install bootstrap binary from resources if not present
188
+ if let Err(e) = engine_process::ensure_bootstrap_installed_from_resources(resource_path) {
189
+ tracing::debug!(op = "engine.bootstrap.skip", error = %e, "Bootstrap install skipped");
190
+ }
191
+
192
+ // Log setup complete
193
+ tracing::info!(
194
+ op = "desktop.setup.complete",
195
+ "Device setup complete - credentials valid, proceeding to engine"
196
+ );
197
+
198
+ engine_process::spawn_and_wait(&engine);
199
+ let status = engine.get_status();
200
+ tracing::info!(
201
+ op = "desktop.engine_status",
202
+ installed = status.installed,
203
+ running = status.running,
204
+ available = status.available,
205
+ pid = ?status.pid,
206
+ version = ?status.version,
207
+ build = ?status.build,
208
+ "Engine status"
209
+ );
210
+
211
+ if status.available {
212
+ tracing::info!(
213
+ op = "desktop.engine.ready",
214
+ "Engine is ready"
215
+ );
216
+ }
217
+ });
218
+
219
+ // Node runner is started via nodeSession.bootstrap operation.
220
+ // Node auth happens at app startup (above) using node_id + node_secret.
221
+ // This ensures:
222
+ // 1. Node credentials loaded from vault (encrypted at rest)
223
+ // 2. Node authenticated via POST /engine/nodes/auth
224
+ // 3. Node JWT (role=node) stored in memory
225
+ // 4. Runner uses node JWT (NOT user JWT or internal service key)
226
+
227
+ Ok(())
228
+ })
229
+ .invoke_handler(tauri::generate_handler![
230
+ engine_connect,
231
+ engine_disconnect,
232
+ engine_request,
233
+ ])
234
+ .build(tauri::generate_context!())
235
+ .expect("error while building tauri application")
236
+ .run(move |_app, event| {
237
+ if let tauri::RunEvent::Exit = event {
238
+ // Shutdown engine process on app exit
239
+ tracing::debug!(op = "desktop.shutdown", "Shutting down engine process");
240
+ engine_process::shutdown(&engine_for_shutdown);
241
+ }
242
+ });
243
+ }