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,182 @@
1
+ /**
2
+ * Home Setup Page
3
+ *
4
+ * Shown after login when HOME grant is not yet present.
5
+ * User must click "Next" to request HOME grant from engine.
6
+ */
7
+
8
+ import { useState, type ReactElement } from 'react';
9
+ import { advanced, type HomeStatus } from '../../ekka';
10
+
11
+ interface HomeSetupPageProps {
12
+ homeStatus: HomeStatus;
13
+ onGranted: () => void;
14
+ darkMode: boolean;
15
+ }
16
+
17
+ export function HomeSetupPage({ homeStatus, onGranted, darkMode }: HomeSetupPageProps): ReactElement {
18
+ const [error, setError] = useState<string | null>(null);
19
+ const [loading, setLoading] = useState(false);
20
+
21
+ const colors = {
22
+ bg: darkMode ? '#1c1c1e' : '#ffffff',
23
+ cardBg: darkMode ? '#2c2c2e' : '#f5f5f7',
24
+ border: darkMode ? '#3a3a3c' : '#e5e5e5',
25
+ text: darkMode ? '#ffffff' : '#1d1d1f',
26
+ textMuted: darkMode ? '#98989d' : '#86868b',
27
+ codeBg: darkMode ? '#1c1c1e' : '#f0f0f0',
28
+ buttonBg: '#007aff',
29
+ buttonHover: '#0066d6',
30
+ errorBg: darkMode ? '#3c1618' : '#fef2f2',
31
+ errorBorder: darkMode ? '#7f1d1d' : '#fecaca',
32
+ errorText: darkMode ? '#fca5a5' : '#991b1b',
33
+ };
34
+
35
+ const styles: Record<string, React.CSSProperties> = {
36
+ container: {
37
+ display: 'flex',
38
+ alignItems: 'center',
39
+ justifyContent: 'center',
40
+ minHeight: '100vh',
41
+ background: colors.bg,
42
+ fontFamily: '-apple-system, BlinkMacSystemFont, "SF Pro Text", system-ui, sans-serif',
43
+ },
44
+ card: {
45
+ width: '100%',
46
+ maxWidth: '480px',
47
+ padding: '32px',
48
+ background: colors.cardBg,
49
+ borderRadius: '12px',
50
+ border: `1px solid ${colors.border}`,
51
+ },
52
+ title: {
53
+ fontSize: '20px',
54
+ fontWeight: 600,
55
+ color: colors.text,
56
+ textAlign: 'center',
57
+ marginBottom: '8px',
58
+ },
59
+ subtitle: {
60
+ fontSize: '13px',
61
+ color: colors.textMuted,
62
+ textAlign: 'center',
63
+ marginBottom: '24px',
64
+ },
65
+ section: {
66
+ marginBottom: '20px',
67
+ },
68
+ label: {
69
+ fontSize: '12px',
70
+ fontWeight: 500,
71
+ color: colors.textMuted,
72
+ textTransform: 'uppercase',
73
+ letterSpacing: '0.5px',
74
+ marginBottom: '6px',
75
+ },
76
+ codePath: {
77
+ padding: '10px 12px',
78
+ fontSize: '13px',
79
+ fontFamily: 'SF Mono, Monaco, monospace',
80
+ background: colors.codeBg,
81
+ borderRadius: '6px',
82
+ color: colors.text,
83
+ wordBreak: 'break-all',
84
+ },
85
+ description: {
86
+ fontSize: '13px',
87
+ color: colors.textMuted,
88
+ lineHeight: 1.5,
89
+ marginBottom: '24px',
90
+ },
91
+ button: {
92
+ width: '100%',
93
+ padding: '12px 16px',
94
+ fontSize: '14px',
95
+ fontWeight: 500,
96
+ color: '#ffffff',
97
+ background: colors.buttonBg,
98
+ border: 'none',
99
+ borderRadius: '8px',
100
+ cursor: 'pointer',
101
+ transition: 'background 0.15s ease',
102
+ },
103
+ buttonDisabled: {
104
+ background: colors.textMuted,
105
+ cursor: 'not-allowed',
106
+ },
107
+ error: {
108
+ padding: '10px 12px',
109
+ fontSize: '13px',
110
+ color: colors.errorText,
111
+ background: colors.errorBg,
112
+ border: `1px solid ${colors.errorBorder}`,
113
+ borderRadius: '8px',
114
+ marginBottom: '16px',
115
+ },
116
+ };
117
+
118
+ async function handleRequestGrant(): Promise<void> {
119
+ setError(null);
120
+ setLoading(true);
121
+
122
+ try {
123
+ await advanced.home.grant();
124
+
125
+ // Re-check status
126
+ const status = await advanced.home.status();
127
+ if (status.state === 'HOME_GRANTED') {
128
+ onGranted();
129
+ } else {
130
+ setError(status.reason || 'Grant request completed but HOME not granted');
131
+ }
132
+ } catch (err) {
133
+ const message = err instanceof Error ? err.message : 'Failed to request HOME grant';
134
+ setError(message);
135
+ } finally {
136
+ setLoading(false);
137
+ }
138
+ }
139
+
140
+ return (
141
+ <div style={styles.container}>
142
+ <div style={styles.card}>
143
+ <div style={styles.title}>Home Folder Setup</div>
144
+ <div style={styles.subtitle}>EKKA needs permission to use your home folder</div>
145
+
146
+ {error && <div style={styles.error}>{error}</div>}
147
+
148
+ <div style={styles.section}>
149
+ <div style={styles.label}>Home Path</div>
150
+ <div style={styles.codePath}>{homeStatus.homePath || '(resolving...)'}</div>
151
+ </div>
152
+
153
+ <div style={styles.description}>
154
+ EKKA stores encrypted data, configuration, and temporary files in this folder.
155
+ Click "Continue" to authorize EKKA to use this location.
156
+ This requires an internet connection to verify with the EKKA engine.
157
+ </div>
158
+
159
+ <button
160
+ style={{
161
+ ...styles.button,
162
+ ...(loading ? styles.buttonDisabled : {}),
163
+ }}
164
+ disabled={loading}
165
+ onClick={handleRequestGrant}
166
+ onMouseEnter={(e) => {
167
+ if (!loading) {
168
+ e.currentTarget.style.background = colors.buttonHover;
169
+ }
170
+ }}
171
+ onMouseLeave={(e) => {
172
+ if (!loading) {
173
+ e.currentTarget.style.background = colors.buttonBg;
174
+ }
175
+ }}
176
+ >
177
+ {loading ? 'Requesting...' : 'Continue'}
178
+ </button>
179
+ </div>
180
+ </div>
181
+ );
182
+ }
@@ -0,0 +1,192 @@
1
+ /**
2
+ * Login Page
3
+ *
4
+ * Simple email/password authentication form.
5
+ */
6
+
7
+ import { useState, type ReactElement, type FormEvent } from 'react';
8
+ import { ekka } from '../../ekka';
9
+
10
+ interface LoginPageProps {
11
+ onLoginSuccess: () => void;
12
+ darkMode: boolean;
13
+ }
14
+
15
+ export function LoginPage({ onLoginSuccess, darkMode }: LoginPageProps): ReactElement {
16
+ const [email, setEmail] = useState(import.meta.env.VITE_DEV_EMAIL || '');
17
+ const [password, setPassword] = useState(import.meta.env.VITE_DEV_PASSWORD || '');
18
+ const [error, setError] = useState<string | null>(null);
19
+ const [loading, setLoading] = useState(false);
20
+
21
+ const colors = {
22
+ bg: darkMode ? '#1c1c1e' : '#ffffff',
23
+ cardBg: darkMode ? '#2c2c2e' : '#f5f5f7',
24
+ border: darkMode ? '#3a3a3c' : '#e5e5e5',
25
+ text: darkMode ? '#ffffff' : '#1d1d1f',
26
+ textMuted: darkMode ? '#98989d' : '#86868b',
27
+ inputBg: darkMode ? '#1c1c1e' : '#ffffff',
28
+ buttonBg: '#007aff',
29
+ buttonHover: '#0066d6',
30
+ errorBg: darkMode ? '#3c1618' : '#fef2f2',
31
+ errorBorder: darkMode ? '#7f1d1d' : '#fecaca',
32
+ errorText: darkMode ? '#fca5a5' : '#991b1b',
33
+ };
34
+
35
+ const styles: Record<string, React.CSSProperties> = {
36
+ container: {
37
+ display: 'flex',
38
+ alignItems: 'center',
39
+ justifyContent: 'center',
40
+ minHeight: '100vh',
41
+ background: colors.bg,
42
+ fontFamily: '-apple-system, BlinkMacSystemFont, "SF Pro Text", system-ui, sans-serif',
43
+ },
44
+ card: {
45
+ width: '100%',
46
+ maxWidth: '360px',
47
+ padding: '32px',
48
+ background: colors.cardBg,
49
+ borderRadius: '12px',
50
+ border: `1px solid ${colors.border}`,
51
+ },
52
+ logo: {
53
+ fontSize: '20px',
54
+ fontWeight: 600,
55
+ color: colors.text,
56
+ textAlign: 'center',
57
+ marginBottom: '8px',
58
+ },
59
+ subtitle: {
60
+ fontSize: '13px',
61
+ color: colors.textMuted,
62
+ textAlign: 'center',
63
+ marginBottom: '24px',
64
+ },
65
+ form: {
66
+ display: 'flex',
67
+ flexDirection: 'column',
68
+ gap: '16px',
69
+ },
70
+ inputGroup: {
71
+ display: 'flex',
72
+ flexDirection: 'column',
73
+ gap: '6px',
74
+ },
75
+ label: {
76
+ fontSize: '13px',
77
+ fontWeight: 500,
78
+ color: colors.text,
79
+ },
80
+ input: {
81
+ padding: '10px 12px',
82
+ fontSize: '14px',
83
+ border: `1px solid ${colors.border}`,
84
+ borderRadius: '8px',
85
+ background: colors.inputBg,
86
+ color: colors.text,
87
+ outline: 'none',
88
+ transition: 'border-color 0.15s ease',
89
+ },
90
+ button: {
91
+ marginTop: '8px',
92
+ padding: '12px 16px',
93
+ fontSize: '14px',
94
+ fontWeight: 500,
95
+ color: '#ffffff',
96
+ background: colors.buttonBg,
97
+ border: 'none',
98
+ borderRadius: '8px',
99
+ cursor: 'pointer',
100
+ transition: 'background 0.15s ease',
101
+ },
102
+ buttonDisabled: {
103
+ background: colors.textMuted,
104
+ cursor: 'not-allowed',
105
+ },
106
+ error: {
107
+ padding: '10px 12px',
108
+ fontSize: '13px',
109
+ color: colors.errorText,
110
+ background: colors.errorBg,
111
+ border: `1px solid ${colors.errorBorder}`,
112
+ borderRadius: '8px',
113
+ },
114
+ };
115
+
116
+ async function handleSubmit(e: FormEvent): Promise<void> {
117
+ e.preventDefault();
118
+ setError(null);
119
+ setLoading(true);
120
+
121
+ try {
122
+ await ekka.auth.login(email, password);
123
+ onLoginSuccess();
124
+ } catch (err) {
125
+ const message = err instanceof Error ? err.message : 'Login failed';
126
+ setError(message);
127
+ } finally {
128
+ setLoading(false);
129
+ }
130
+ }
131
+
132
+ const isValid = email.trim() !== '' && password !== '';
133
+
134
+ return (
135
+ <div style={styles.container}>
136
+ <div style={styles.card}>
137
+ <div style={styles.logo}>EKKA Desktop</div>
138
+ <div style={styles.subtitle}>Sign in to continue</div>
139
+
140
+ <form style={styles.form} onSubmit={handleSubmit}>
141
+ {error && <div style={styles.error}>{error}</div>}
142
+
143
+ <div style={styles.inputGroup}>
144
+ <label style={styles.label}>Email</label>
145
+ <input
146
+ type="email"
147
+ value={email}
148
+ onChange={(e) => setEmail(e.target.value)}
149
+ placeholder="you@example.com"
150
+ style={styles.input}
151
+ autoFocus
152
+ disabled={loading}
153
+ />
154
+ </div>
155
+
156
+ <div style={styles.inputGroup}>
157
+ <label style={styles.label}>Password</label>
158
+ <input
159
+ type="password"
160
+ value={password}
161
+ onChange={(e) => setPassword(e.target.value)}
162
+ placeholder="Enter your password"
163
+ style={styles.input}
164
+ disabled={loading}
165
+ />
166
+ </div>
167
+
168
+ <button
169
+ type="submit"
170
+ style={{
171
+ ...styles.button,
172
+ ...(!isValid || loading ? styles.buttonDisabled : {}),
173
+ }}
174
+ disabled={!isValid || loading}
175
+ onMouseEnter={(e) => {
176
+ if (isValid && !loading) {
177
+ e.currentTarget.style.background = colors.buttonHover;
178
+ }
179
+ }}
180
+ onMouseLeave={(e) => {
181
+ if (isValid && !loading) {
182
+ e.currentTarget.style.background = colors.buttonBg;
183
+ }
184
+ }}
185
+ >
186
+ {loading ? 'Signing in...' : 'Sign In'}
187
+ </button>
188
+ </form>
189
+ </div>
190
+ </div>
191
+ );
192
+ }