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,116 @@
1
+ /**
2
+ * Workflow Runs Operations
3
+ *
4
+ * Create and poll workflow runs via Rust proxy.
5
+ * All HTTP is handled by Rust - no fetch() in TS.
6
+ */
7
+
8
+ import { OPS } from '../constants';
9
+ import { _internal, makeRequest } from '../internal';
10
+ import { getAccessToken } from '../auth/storage';
11
+
12
+ // =============================================================================
13
+ // TYPES
14
+ // =============================================================================
15
+
16
+ export interface WorkflowRunCreateRequest {
17
+ type: string;
18
+ confidentiality: 'public' | 'confidential';
19
+ context: {
20
+ prompt_ref: {
21
+ provider: string;
22
+ prompt_slug: string;
23
+ prompt_version: string;
24
+ };
25
+ variables: Record<string, string>;
26
+ };
27
+ }
28
+
29
+ export interface WorkflowRunCreateResponse {
30
+ id: string;
31
+ status: string;
32
+ }
33
+
34
+ /** Debug bundle info (only present for REPORT_INVALID in dev mode) */
35
+ export interface DebugBundleInfo {
36
+ /** Vault URI (e.g., "vault://tmp/telemetry/llm_debug/{tenant}/{run_id}/") */
37
+ debug_bundle_ref: string;
38
+ /** SHA256 hash of raw output */
39
+ raw_output_sha256: string;
40
+ /** Length of raw output in bytes */
41
+ raw_output_len: number;
42
+ /** Files in the bundle */
43
+ files: string[];
44
+ }
45
+
46
+ export interface WorkflowRunResult {
47
+ output_text?: string;
48
+ /** Debug bundle info (only present for REPORT_INVALID failures in dev mode) */
49
+ debug_bundle?: DebugBundleInfo;
50
+ [key: string]: unknown;
51
+ }
52
+
53
+ export interface WorkflowRun {
54
+ id: string;
55
+ type?: string;
56
+ workflow_definition_id?: string;
57
+ status: 'pending' | 'dispatched' | 'running' | 'completed' | 'failed' | 'cancelled';
58
+ progress: number;
59
+ result?: WorkflowRunResult;
60
+ error?: string;
61
+ created_at: string;
62
+ started_at?: string;
63
+ completed_at?: string;
64
+ }
65
+
66
+ // =============================================================================
67
+ // OPERATIONS
68
+ // =============================================================================
69
+
70
+ /**
71
+ * Create a new workflow run.
72
+ *
73
+ * @param request - Workflow run creation request
74
+ * @returns Created workflow run with ID
75
+ */
76
+ export async function createWorkflowRun(
77
+ request: WorkflowRunCreateRequest
78
+ ): Promise<WorkflowRunCreateResponse> {
79
+ const jwt = getAccessToken();
80
+
81
+ const req = makeRequest(OPS.WORKFLOW_RUNS_CREATE, {
82
+ request,
83
+ jwt,
84
+ });
85
+
86
+ const response = await _internal.request(req);
87
+
88
+ if (!response.ok) {
89
+ throw new Error(response.error?.message || 'Failed to create workflow run');
90
+ }
91
+
92
+ return response.result as WorkflowRunCreateResponse;
93
+ }
94
+
95
+ /**
96
+ * Get workflow run status and output.
97
+ *
98
+ * @param id - Workflow run ID
99
+ * @returns Workflow run details
100
+ */
101
+ export async function getWorkflowRun(id: string): Promise<WorkflowRun> {
102
+ const jwt = getAccessToken();
103
+
104
+ const req = makeRequest(OPS.WORKFLOW_RUNS_GET, {
105
+ id,
106
+ jwt,
107
+ });
108
+
109
+ const response = await _internal.request(req);
110
+
111
+ if (!response.ok) {
112
+ throw new Error(response.error?.message || 'Failed to get workflow run');
113
+ }
114
+
115
+ return response.result as WorkflowRun;
116
+ }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * EKKA Type Definitions
3
+ *
4
+ * Core types for the EKKA client library.
5
+ * Only includes types for IMPLEMENTED features.
6
+ */
7
+
8
+ import { CONTRACT_VERSION } from './constants';
9
+
10
+ // =============================================================================
11
+ // CONTRACT TYPES
12
+ // =============================================================================
13
+
14
+ /**
15
+ * Request format for all engine operations.
16
+ */
17
+ export interface EngineRequest {
18
+ op: string;
19
+ v: number;
20
+ payload: unknown;
21
+ correlationId: string;
22
+ }
23
+
24
+ /**
25
+ * Error detail in response.
26
+ */
27
+ export interface EngineErrorDetail {
28
+ code: string;
29
+ message: string;
30
+ details?: unknown;
31
+ status?: number;
32
+ }
33
+
34
+ /**
35
+ * Response format for all engine operations.
36
+ */
37
+ export interface EngineResponse {
38
+ ok: boolean;
39
+ result?: unknown;
40
+ error?: EngineErrorDetail;
41
+ }
42
+
43
+ // =============================================================================
44
+ // CONTRACT HELPERS
45
+ // =============================================================================
46
+
47
+ /**
48
+ * Generate a correlation ID (UUID v4).
49
+ */
50
+ export function mkCorrelationId(): string {
51
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
52
+ const r = (Math.random() * 16) | 0;
53
+ const v = c === 'x' ? r : (r & 0x3) | 0x8;
54
+ return v.toString(16);
55
+ });
56
+ }
57
+
58
+ /**
59
+ * Create an engine request.
60
+ */
61
+ export function makeRequest(op: string, payload: unknown = {}): EngineRequest {
62
+ return {
63
+ op,
64
+ v: CONTRACT_VERSION,
65
+ payload,
66
+ correlationId: mkCorrelationId(),
67
+ };
68
+ }
69
+
70
+ /**
71
+ * Create a successful response.
72
+ */
73
+ export function ok<T = unknown>(result: T): EngineResponse {
74
+ return { ok: true, result };
75
+ }
76
+
77
+ /**
78
+ * Create an error response.
79
+ */
80
+ export function err(code: string, message: string): EngineResponse {
81
+ return { ok: false, error: { code, message } };
82
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Idempotency Key Utilities
3
+ * Generate unique keys for idempotent operations.
4
+ */
5
+
6
+ /**
7
+ * Generate a unique idempotency key.
8
+ * Format: timestamp-random (e.g., "1706284800000-a1b2c3d4e5f6")
9
+ */
10
+ export function generateIdempotencyKey(): string {
11
+ const timestamp = Date.now();
12
+ const random = Math.random().toString(36).substring(2, 14);
13
+ return `${timestamp}-${random}`;
14
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * EKKA Utilities
3
+ * Re-exports all utility functions.
4
+ */
5
+
6
+ export * from './time';
7
+ export * from './idempotency';
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Time Formatting Utilities
3
+ * Human-readable time formatting for the EKKA client.
4
+ */
5
+
6
+ /**
7
+ * Format a date as relative time (e.g., "2 minutes ago", "in 5 hours").
8
+ */
9
+ export function formatRelativeTime(date: Date): string {
10
+ const now = new Date();
11
+ const diffMs = date.getTime() - now.getTime();
12
+ const absDiffMs = Math.abs(diffMs);
13
+ const isPast = diffMs < 0;
14
+
15
+ const seconds = Math.floor(absDiffMs / 1000);
16
+ const minutes = Math.floor(seconds / 60);
17
+ const hours = Math.floor(minutes / 60);
18
+ const days = Math.floor(hours / 24);
19
+
20
+ let value: number;
21
+ let unit: string;
22
+
23
+ if (seconds < 60) {
24
+ value = seconds;
25
+ unit = 'second';
26
+ } else if (minutes < 60) {
27
+ value = minutes;
28
+ unit = 'minute';
29
+ } else if (hours < 24) {
30
+ value = hours;
31
+ unit = 'hour';
32
+ } else {
33
+ value = days;
34
+ unit = 'day';
35
+ }
36
+
37
+ const plural = value !== 1 ? 's' : '';
38
+ if (isPast) {
39
+ return value === 0 ? 'just now' : `${value} ${unit}${plural} ago`;
40
+ }
41
+ return `in ${value} ${unit}${plural}`;
42
+ }
43
+
44
+ /**
45
+ * Format a date as local time string (e.g., "3:45:30 PM").
46
+ */
47
+ export function formatLocalTime(date: Date): string {
48
+ return date.toLocaleTimeString(undefined, {
49
+ hour: 'numeric',
50
+ minute: '2-digit',
51
+ second: '2-digit',
52
+ hour12: true,
53
+ });
54
+ }
55
+
56
+ /**
57
+ * Format expiry information from an ISO date string.
58
+ * Returns null if the input is invalid.
59
+ */
60
+ export function formatExpiryInfo(
61
+ expiresAtIso: string
62
+ ): { text: string; isExpired: boolean } | null {
63
+ try {
64
+ const expiresAt = new Date(expiresAtIso);
65
+ if (isNaN(expiresAt.getTime())) {
66
+ return null;
67
+ }
68
+
69
+ const now = new Date();
70
+ const isExpired = expiresAt.getTime() < now.getTime();
71
+ const text = formatRelativeTime(expiresAt);
72
+
73
+ return { text, isExpired };
74
+ } catch {
75
+ return null;
76
+ }
77
+ }
@@ -0,0 +1,12 @@
1
+ import { StrictMode } from 'react';
2
+ import { createRoot } from 'react-dom/client';
3
+
4
+ // Toggle between DemoApp and App here:
5
+ // import { App } from './app/App';
6
+ import { DemoApp as App } from './demo/DemoApp';
7
+
8
+ createRoot(document.getElementById('root')!).render(
9
+ <StrictMode>
10
+ <App />
11
+ </StrictMode>
12
+ );
@@ -0,0 +1,12 @@
1
+ /// <reference types="vite/client" />
2
+
3
+ interface ImportMetaEnv {
4
+ readonly VITE_EKKA_API_URL: string;
5
+ readonly VITE_EKKA_ENGINE_URL: string;
6
+ readonly VITE_DEV_EMAIL: string;
7
+ readonly VITE_DEV_PASSWORD: string;
8
+ }
9
+
10
+ interface ImportMeta {
11
+ readonly env: ImportMetaEnv;
12
+ }
@@ -0,0 +1,41 @@
1
+ [package]
2
+ name = "ekka-desktop-app"
3
+ version = "0.2.0"
4
+ description = "EKKA Desktop Application"
5
+ authors = ["EKKA"]
6
+ edition = "2021"
7
+
8
+ # Prevent inheriting parent workspace
9
+ [workspace]
10
+
11
+ [build-dependencies]
12
+ tauri-build = { version = "2", features = [] }
13
+
14
+ [dependencies]
15
+ tauri = { version = "2", features = [] }
16
+ tauri-plugin-dialog = "2"
17
+ serde = { version = "1", features = ["derive"] }
18
+ serde_json = "1"
19
+ chrono = { version = "0.4", features = ["serde"] }
20
+ ekka-sdk-core = { path = "../../ekka-execution-node-sdk-rust/crates/framework/ekka-sdk-core" }
21
+ ekka-runner-core = { path = "../../ekka-execution-node-sdk-rust/crates/framework/ekka-runner-core" }
22
+ ekka-runner-local = { path = "../../ekka-execution-node-sdk-rust/crates/apps/ekka-runner-local" }
23
+ reqwest = { version = "0.11", features = ["blocking", "json"] }
24
+ uuid = { version = "1.0", features = ["v4"] }
25
+ tokio = { version = "1", features = ["sync"] }
26
+ tracing = "0.1"
27
+ tracing-subscriber = { version = "0.3", features = ["env-filter"] }
28
+ ed25519-dalek = { version = "2.1", features = ["rand_core"] }
29
+ rand = "0.8"
30
+ base64 = "0.22"
31
+ zeroize = { version = "1.7", features = ["derive"] }
32
+ dotenvy = "0.15"
33
+ hex = "0.4"
34
+ anyhow = "1.0"
35
+
36
+ [dev-dependencies]
37
+ tempfile = "3"
38
+
39
+ [features]
40
+ default = ["custom-protocol"]
41
+ custom-protocol = ["tauri/custom-protocol"]
@@ -0,0 +1,3 @@
1
+ fn main() {
2
+ tauri_build::build()
3
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "$schema": "../gen/schemas/desktop-schema.json",
3
+ "identifier": "default",
4
+ "description": "Default capabilities for EKKA Desktop",
5
+ "windows": ["main"],
6
+ "permissions": [
7
+ "core:default",
8
+ "dialog:default",
9
+ "dialog:allow-open"
10
+ ]
11
+ }
@@ -0,0 +1,37 @@
1
+ //! Home directory bootstrap
2
+ //!
3
+ //! Handles initialization and resolution of the EKKA home directory.
4
+
5
+ use ekka_sdk_core::ekka_home_bootstrap::{BootstrapConfig, EpochSource, HomeBootstrap, HomeStrategy};
6
+ use std::path::PathBuf;
7
+
8
+ /// Standard bootstrap configuration for EKKA Desktop
9
+ pub fn bootstrap_config() -> BootstrapConfig {
10
+ BootstrapConfig {
11
+ app_name: "ekka-desktop".to_string(),
12
+ default_folder_name: ".ekka-desktop".to_string(),
13
+ home_strategy: HomeStrategy::DataHome {
14
+ env_var: "EKKA_DATA_HOME".to_string(),
15
+ },
16
+ marker_filename: ".ekka-marker.json".to_string(),
17
+ keychain_service: "ai.ekka.desktop".to_string(),
18
+ subdirs: vec!["vault".to_string(), "db".to_string(), "tmp".to_string()],
19
+ epoch_source: EpochSource::EnvVar("EKKA_SECURITY_EPOCH".to_string()),
20
+ storage_layout_version: "v1".to_string(),
21
+ }
22
+ }
23
+
24
+ /// Resolve the home path without initializing
25
+ pub fn resolve_home_path() -> Result<PathBuf, String> {
26
+ let config = bootstrap_config();
27
+ let bootstrap = HomeBootstrap::new(config).map_err(|e| e.to_string())?;
28
+ Ok(bootstrap.home_path().to_path_buf())
29
+ }
30
+
31
+ /// Initialize home directory and return the bootstrap instance
32
+ pub fn initialize_home() -> Result<HomeBootstrap, String> {
33
+ let config = bootstrap_config();
34
+ let bootstrap = HomeBootstrap::new(config).map_err(|e| e.to_string())?;
35
+ bootstrap.initialize().map_err(|e| e.to_string())?;
36
+ Ok(bootstrap)
37
+ }