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,86 @@
1
+ /**
2
+ * Runner Operations
3
+ *
4
+ * Local runner status for this desktop instance.
5
+ * Task queue stats from engine API (proxied via Rust).
6
+ */
7
+
8
+ import { OPS } from '../constants';
9
+ import { _internal, makeRequest } from '../internal';
10
+
11
+ // =============================================================================
12
+ // TYPES
13
+ // =============================================================================
14
+
15
+ export type RunnerLoopState = 'running' | 'stopped' | 'error';
16
+
17
+ export interface RunnerStatus {
18
+ enabled: boolean;
19
+ state: RunnerLoopState;
20
+ runnerId: string | null;
21
+ engineUrl: string | null;
22
+ lastPollAt: string | null;
23
+ lastClaimAt: string | null;
24
+ lastCompleteAt: string | null;
25
+ lastTaskId: string | null;
26
+ lastError: string | null;
27
+ }
28
+
29
+ /** Task queue stats from engine API */
30
+ export interface RunnerTaskStats {
31
+ counts: {
32
+ pending: number;
33
+ claimed: number;
34
+ completed_5m: number;
35
+ failed_5m: number;
36
+ };
37
+ by_subtype: Record<string, { pending: number; claimed: number }>;
38
+ recent: Array<{
39
+ task_id: string;
40
+ task_subtype: string | null;
41
+ status: string;
42
+ runner_id: string | null;
43
+ created_at: string;
44
+ claimed_at: string | null;
45
+ lease_expires_at: string | null;
46
+ }>;
47
+ active_runners: Array<{
48
+ runner_id: string;
49
+ last_claimed_at: string;
50
+ }>;
51
+ }
52
+
53
+ // =============================================================================
54
+ // OPERATIONS
55
+ // =============================================================================
56
+
57
+ /**
58
+ * Get local runner status for this desktop instance.
59
+ */
60
+ export async function status(): Promise<RunnerStatus> {
61
+ const req = makeRequest('runner.status', {});
62
+ const response = await _internal.request(req);
63
+
64
+ if (!response.ok) {
65
+ throw new Error(response.error?.message || 'Failed to get runner status');
66
+ }
67
+
68
+ return response.result as RunnerStatus;
69
+ }
70
+
71
+ /**
72
+ * Get runner task queue stats from engine API.
73
+ *
74
+ * Proxied through Rust backend to avoid CORS.
75
+ * Requires node authentication (setup must be complete).
76
+ */
77
+ export async function taskStats(): Promise<RunnerTaskStats> {
78
+ const req = makeRequest(OPS.RUNNER_TASK_STATS, {});
79
+ const response = await _internal.request(req);
80
+
81
+ if (!response.ok) {
82
+ throw new Error(response.error?.message || 'Failed to fetch runner task stats');
83
+ }
84
+
85
+ return response.result as RunnerTaskStats;
86
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Runtime Operations
3
+ *
4
+ * Wraps Rust ops/runtime.rs
5
+ */
6
+
7
+ import { OPS } from '../constants';
8
+ import { _internal, makeRequest } from '../internal';
9
+
10
+ export interface RuntimeInfo {
11
+ runtime: string;
12
+ engine_present: boolean;
13
+ mode: string;
14
+ homeState: string;
15
+ homePath: string;
16
+ }
17
+
18
+ /**
19
+ * Get runtime info.
20
+ * Maps to Rust: runtime.info
21
+ */
22
+ export async function info(): Promise<RuntimeInfo> {
23
+ const req = makeRequest(OPS.RUNTIME_INFO, {});
24
+ const response = await _internal.request(req);
25
+
26
+ if (!response.ok) {
27
+ throw new Error(response.error?.message || 'Failed to get runtime info');
28
+ }
29
+
30
+ return response.result as RuntimeInfo;
31
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Setup Operations
3
+ *
4
+ * Pre-login setup status for device configuration.
5
+ * Only checks node credentials - home folder grant is post-login.
6
+ */
7
+
8
+ import { OPS } from '../constants';
9
+ import { _internal, makeRequest } from '../internal';
10
+
11
+ // =============================================================================
12
+ // Types
13
+ // =============================================================================
14
+
15
+ export type SetupState = 'configured' | 'not_configured';
16
+
17
+ export interface SetupStatus {
18
+ /** Node credentials status */
19
+ nodeIdentity: SetupState;
20
+ /** True if node credentials are configured */
21
+ setupComplete: boolean;
22
+ }
23
+
24
+ // =============================================================================
25
+ // Operations
26
+ // =============================================================================
27
+
28
+ /**
29
+ * Get setup status.
30
+ *
31
+ * Returns status of:
32
+ * - nodeIdentity: configured | not_configured
33
+ * - setupComplete: true if node credentials are configured
34
+ *
35
+ * This is called before login to determine if setup wizard is needed.
36
+ * Home folder grant is handled post-login via HomeSetupPage.
37
+ */
38
+ export async function status(): Promise<SetupStatus> {
39
+ const req = makeRequest(OPS.SETUP_STATUS, {});
40
+ const response = await _internal.request(req);
41
+
42
+ if (!response.ok) {
43
+ throw new Error(response.error?.message || 'Failed to get setup status');
44
+ }
45
+
46
+ return response.result as SetupStatus;
47
+ }
@@ -0,0 +1,459 @@
1
+ /**
2
+ * Vault Operations
3
+ *
4
+ * Secrets, bundles, files, and audit operations.
5
+ *
6
+ * ## Security Invariant
7
+ *
8
+ * Secret values are NEVER returned by the API. The API returns metadata only.
9
+ * Values are only accepted in create/update operations as input.
10
+ *
11
+ * ## Scoping
12
+ *
13
+ * - Secrets and Bundles: tenant-scoped (shared across workspaces)
14
+ * - Files: tenant + workspace scoped
15
+ */
16
+
17
+ import { OPS } from '../constants';
18
+ import { _internal, makeRequest } from '../internal';
19
+
20
+ // =============================================================================
21
+ // Status Types
22
+ // =============================================================================
23
+
24
+ /** Vault status information */
25
+ export interface VaultStatus {
26
+ /** Whether the vault is initialized */
27
+ initialized: boolean;
28
+ /** Current tenant ID */
29
+ tenantId?: string;
30
+ /** Available workspace IDs */
31
+ workspaces: string[];
32
+ }
33
+
34
+ /** Vault capabilities */
35
+ export interface VaultCapabilities {
36
+ /** Supported features */
37
+ features: string[];
38
+ /** Maximum secret value size in bytes */
39
+ maxSecretSize: number;
40
+ /** Maximum file size in bytes */
41
+ maxFileSize: number;
42
+ /** Maximum path depth */
43
+ maxPathDepth: number;
44
+ }
45
+
46
+ // =============================================================================
47
+ // Secret Types
48
+ // =============================================================================
49
+
50
+ /** Secret type classification */
51
+ export type SecretType =
52
+ | 'PASSWORD'
53
+ | 'API_KEY'
54
+ | 'TOKEN'
55
+ | 'CERTIFICATE'
56
+ | 'SSH_KEY'
57
+ | 'GENERIC_TEXT';
58
+
59
+ /** Secret metadata - NEVER contains the actual value */
60
+ export interface SecretMeta {
61
+ id: string;
62
+ name: string;
63
+ secretType: SecretType;
64
+ tags: string[];
65
+ bundleId?: string;
66
+ createdAt: string;
67
+ updatedAt: string;
68
+ }
69
+
70
+ /** Input for creating a secret (value accepted here only) */
71
+ export interface SecretCreateInput {
72
+ name: string;
73
+ value: string;
74
+ secretType?: SecretType;
75
+ tags?: string[];
76
+ bundleId?: string;
77
+ }
78
+
79
+ /** Input for updating a secret (value accepted here only) */
80
+ export interface SecretUpdateInput {
81
+ name?: string;
82
+ value?: string;
83
+ secretType?: SecretType;
84
+ tags?: string[];
85
+ bundleId?: string;
86
+ }
87
+
88
+ /** Options for listing secrets */
89
+ export interface SecretListOptions {
90
+ secretType?: SecretType;
91
+ tag?: string;
92
+ bundleId?: string;
93
+ }
94
+
95
+ // =============================================================================
96
+ // Bundle Types
97
+ // =============================================================================
98
+
99
+ /** Bundle metadata */
100
+ export interface BundleMeta {
101
+ id: string;
102
+ name: string;
103
+ description?: string;
104
+ secretIds: string[];
105
+ createdAt: string;
106
+ updatedAt: string;
107
+ }
108
+
109
+ /** Input for creating a bundle */
110
+ export interface BundleCreateInput {
111
+ name: string;
112
+ description?: string;
113
+ }
114
+
115
+ /** Options for listing bundles */
116
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type -- Reserved for future filtering options
117
+ export interface BundleListOptions {}
118
+
119
+ // =============================================================================
120
+ // File Types (NEW)
121
+ // =============================================================================
122
+
123
+ /** File entry kind */
124
+ export type FileKind = 'FILE' | 'DIR';
125
+
126
+ /** File entry metadata */
127
+ export interface FileEntry {
128
+ /** Relative path from workspace root */
129
+ path: string;
130
+ /** Filename only */
131
+ name: string;
132
+ /** File or Dir */
133
+ kind: FileKind;
134
+ /** Size in bytes (for files only) */
135
+ sizeBytes?: number;
136
+ /** Last modified timestamp */
137
+ modifiedAt?: string;
138
+ }
139
+
140
+ /** Options for file operations */
141
+ export interface FileOptions {
142
+ /** Workspace ID (defaults to "default") */
143
+ workspaceId?: string;
144
+ }
145
+
146
+ /** Options for listing files */
147
+ export interface FileListOptions {
148
+ /** Workspace ID (defaults to "default") */
149
+ workspaceId?: string;
150
+ /** Whether to list recursively */
151
+ recursive?: boolean;
152
+ }
153
+
154
+ /** Options for deleting files/directories */
155
+ export interface FileDeleteOptions {
156
+ /** Workspace ID (defaults to "default") */
157
+ workspaceId?: string;
158
+ /** Whether to delete recursively (for directories) */
159
+ recursive?: boolean;
160
+ }
161
+
162
+ // =============================================================================
163
+ // SecretRef Types (Non-Revealing Usage)
164
+ // =============================================================================
165
+
166
+ /** How to inject a secret value */
167
+ export type SecretInjection =
168
+ | { type: 'ENV_VAR'; name: string }
169
+ | { type: 'FILE'; path: string }
170
+ | { type: 'HEADER'; name: string };
171
+
172
+ /** Reference to a secret for non-revealing usage */
173
+ export interface SecretRef {
174
+ /** Secret ID (preferred) or name */
175
+ secretId?: string;
176
+ /** Secret name (for lookup if id not provided) */
177
+ name?: string;
178
+ /** How to inject the secret */
179
+ injectAs: SecretInjection;
180
+ }
181
+
182
+ // =============================================================================
183
+ // Audit Types
184
+ // =============================================================================
185
+
186
+ /** Audit event action */
187
+ export type AuditAction =
188
+ // Secret events
189
+ | 'secret.created'
190
+ | 'secret.updated'
191
+ | 'secret.deleted'
192
+ | 'secret.accessed'
193
+ // Bundle events
194
+ | 'bundle.created'
195
+ | 'bundle.updated'
196
+ | 'bundle.deleted'
197
+ | 'bundle.secret_added'
198
+ | 'bundle.secret_removed'
199
+ // File events
200
+ | 'file.written'
201
+ | 'file.read'
202
+ | 'file.deleted'
203
+ | 'file.mkdir'
204
+ | 'file.moved'
205
+ // Legacy
206
+ | 'secrets_injected';
207
+
208
+ /** Audit event */
209
+ export interface AuditEvent {
210
+ eventId: string;
211
+ action: AuditAction;
212
+ timestamp: string;
213
+ secretId?: string;
214
+ secretName?: string;
215
+ bundleId?: string;
216
+ path?: string;
217
+ actorId?: string;
218
+ }
219
+
220
+ /** Options for listing audit events (cursor-based pagination) */
221
+ export interface AuditListOptions {
222
+ /** Maximum number of events to return (default 50, max 100) */
223
+ limit?: number;
224
+ /** Opaque cursor for pagination */
225
+ cursor?: string;
226
+ /** Filter by action (e.g., "secret.created", "file.written") */
227
+ action?: string;
228
+ /** Text search across event data */
229
+ search?: string;
230
+ /** Filter by secret ID */
231
+ secretId?: string;
232
+ /** Filter by bundle ID */
233
+ bundleId?: string;
234
+ /** Filter by path prefix */
235
+ pathPrefix?: string;
236
+ }
237
+
238
+ /** Audit list result with cursor-based pagination */
239
+ export interface AuditListResult {
240
+ events: AuditEvent[];
241
+ nextCursor?: string;
242
+ hasMore: boolean;
243
+ }
244
+
245
+ // =============================================================================
246
+ // Internal helper
247
+ // =============================================================================
248
+
249
+ async function doRequest<T>(op: string, payload: object): Promise<T> {
250
+ const req = makeRequest(op, payload);
251
+ const response = await _internal.request(req);
252
+ if (!response.ok) {
253
+ throw new Error(response.error?.message || `Operation ${op} failed`);
254
+ }
255
+ return response.result as T;
256
+ }
257
+
258
+ // =============================================================================
259
+ // Status Operations
260
+ // =============================================================================
261
+
262
+ /** Get vault status */
263
+ export async function status(): Promise<VaultStatus> {
264
+ return doRequest<VaultStatus>(OPS.VAULT_STATUS, {});
265
+ }
266
+
267
+ /** Get vault capabilities */
268
+ export async function capabilities(): Promise<VaultCapabilities> {
269
+ return doRequest<VaultCapabilities>(OPS.VAULT_CAPABILITIES, {});
270
+ }
271
+
272
+ // =============================================================================
273
+ // Secrets Operations
274
+ // =============================================================================
275
+
276
+ export const secrets = {
277
+ /** List all secrets (metadata only, NO values) */
278
+ async list(opts?: SecretListOptions): Promise<SecretMeta[]> {
279
+ const result = await doRequest<{ secrets: SecretMeta[] }>(OPS.VAULT_SECRETS_LIST, { opts });
280
+ return result.secrets;
281
+ },
282
+
283
+ /** Get a secret by ID (metadata only, NO value) */
284
+ async get(id: string): Promise<SecretMeta> {
285
+ return doRequest<SecretMeta>(OPS.VAULT_SECRETS_GET, { id });
286
+ },
287
+
288
+ /** Create a new secret (value accepted here only) */
289
+ async create(input: SecretCreateInput): Promise<SecretMeta> {
290
+ return doRequest<SecretMeta>(OPS.VAULT_SECRETS_CREATE, input);
291
+ },
292
+
293
+ /** Update a secret (value accepted here only) */
294
+ async update(id: string, input: SecretUpdateInput): Promise<SecretMeta> {
295
+ return doRequest<SecretMeta>(OPS.VAULT_SECRETS_UPDATE, { id, input });
296
+ },
297
+
298
+ /** Delete a secret */
299
+ async delete(id: string): Promise<boolean> {
300
+ const result = await doRequest<{ deleted: boolean }>(OPS.VAULT_SECRETS_DELETE, { id });
301
+ return result.deleted;
302
+ },
303
+
304
+ /** Upsert a secret (create or update by name) */
305
+ async upsert(input: SecretCreateInput): Promise<SecretMeta> {
306
+ return doRequest<SecretMeta>(OPS.VAULT_SECRETS_UPSERT, input);
307
+ },
308
+ };
309
+
310
+ // =============================================================================
311
+ // Bundles Operations
312
+ // =============================================================================
313
+
314
+ export const bundles = {
315
+ /** List all bundles */
316
+ async list(opts?: BundleListOptions): Promise<BundleMeta[]> {
317
+ const result = await doRequest<{ bundles: BundleMeta[] }>(OPS.VAULT_BUNDLES_LIST, { opts });
318
+ return result.bundles;
319
+ },
320
+
321
+ /** Get a bundle by ID */
322
+ async get(id: string): Promise<BundleMeta> {
323
+ return doRequest<BundleMeta>(OPS.VAULT_BUNDLES_GET, { id });
324
+ },
325
+
326
+ /** Create a new bundle */
327
+ async create(input: BundleCreateInput): Promise<BundleMeta> {
328
+ return doRequest<BundleMeta>(OPS.VAULT_BUNDLES_CREATE, input);
329
+ },
330
+
331
+ /** Rename a bundle */
332
+ async rename(id: string, name: string): Promise<BundleMeta> {
333
+ return doRequest<BundleMeta>(OPS.VAULT_BUNDLES_RENAME, { id, name });
334
+ },
335
+
336
+ /** Delete a bundle */
337
+ async delete(id: string): Promise<boolean> {
338
+ const result = await doRequest<{ deleted: boolean }>(OPS.VAULT_BUNDLES_DELETE, { id });
339
+ return result.deleted;
340
+ },
341
+
342
+ /** List secrets in a bundle */
343
+ async listSecrets(bundleId: string, opts?: SecretListOptions): Promise<SecretMeta[]> {
344
+ const result = await doRequest<{ secrets: SecretMeta[] }>(OPS.VAULT_BUNDLES_LIST_SECRETS, { bundleId, opts });
345
+ return result.secrets;
346
+ },
347
+
348
+ /** Add a secret to a bundle */
349
+ async addSecret(bundleId: string, secretId: string): Promise<BundleMeta> {
350
+ return doRequest<BundleMeta>(OPS.VAULT_BUNDLES_ADD_SECRET, { bundleId, secretId });
351
+ },
352
+
353
+ /** Remove a secret from a bundle */
354
+ async removeSecret(bundleId: string, secretId: string): Promise<BundleMeta> {
355
+ return doRequest<BundleMeta>(OPS.VAULT_BUNDLES_REMOVE_SECRET, { bundleId, secretId });
356
+ },
357
+ };
358
+
359
+ // =============================================================================
360
+ // Files Operations (NEW)
361
+ // =============================================================================
362
+
363
+ export const files = {
364
+ /** Write text content to a file */
365
+ async writeText(path: string, content: string, opts?: FileOptions): Promise<void> {
366
+ await doRequest<{ written: boolean }>(OPS.VAULT_FILES_WRITE_TEXT, { path, content, opts });
367
+ },
368
+
369
+ /** Write binary content to a file (content as base64) */
370
+ async writeBytes(path: string, contentBytes: string, opts?: FileOptions): Promise<void> {
371
+ await doRequest<{ written: boolean }>(OPS.VAULT_FILES_WRITE_BYTES, { path, contentBytes, opts });
372
+ },
373
+
374
+ /** Read text content from a file */
375
+ async readText(path: string, opts?: FileOptions): Promise<string> {
376
+ const result = await doRequest<{ content: string }>(OPS.VAULT_FILES_READ_TEXT, { path, opts });
377
+ return result.content;
378
+ },
379
+
380
+ /** Read binary content from a file (returns base64) */
381
+ async readBytes(path: string, opts?: FileOptions): Promise<string> {
382
+ const result = await doRequest<{ contentBytes: string }>(OPS.VAULT_FILES_READ_BYTES, { path, opts });
383
+ return result.contentBytes;
384
+ },
385
+
386
+ /** List files and directories */
387
+ async list(dir: string, opts?: FileListOptions): Promise<FileEntry[]> {
388
+ const result = await doRequest<{ entries: FileEntry[] }>(OPS.VAULT_FILES_LIST, { dir, opts });
389
+ return result.entries;
390
+ },
391
+
392
+ /** Check if a file or directory exists */
393
+ async exists(path: string, opts?: FileOptions): Promise<boolean> {
394
+ const result = await doRequest<{ exists: boolean }>(OPS.VAULT_FILES_EXISTS, { path, opts });
395
+ return result.exists;
396
+ },
397
+
398
+ /** Delete a file or directory */
399
+ async delete(path: string, opts?: FileDeleteOptions): Promise<boolean> {
400
+ const result = await doRequest<{ deleted: boolean }>(OPS.VAULT_FILES_DELETE, { path, opts });
401
+ return result.deleted;
402
+ },
403
+
404
+ /** Create a directory */
405
+ async mkdir(path: string, opts?: FileOptions): Promise<void> {
406
+ await doRequest<{ created: boolean }>(OPS.VAULT_FILES_MKDIR, { path, opts });
407
+ },
408
+
409
+ /** Move a file or directory */
410
+ async move(from: string, to: string, opts?: FileOptions): Promise<void> {
411
+ await doRequest<{ moved: boolean }>(OPS.VAULT_FILES_MOVE, { from, to, opts });
412
+ },
413
+ };
414
+
415
+ // =============================================================================
416
+ // Injection Operations (DEFERRED - return NOT_IMPLEMENTED)
417
+ // =============================================================================
418
+
419
+ /**
420
+ * Attach secrets to a connector configuration (DEFERRED)
421
+ * @returns Promise that resolves on success
422
+ * @throws Error with code NOT_IMPLEMENTED
423
+ */
424
+ export async function attachSecretsToConnector(
425
+ connectorId: string,
426
+ mappings: SecretRef[]
427
+ ): Promise<void> {
428
+ await doRequest<{ attached: boolean }>(OPS.VAULT_ATTACH_SECRETS_TO_CONNECTOR, {
429
+ connectorId,
430
+ mappings,
431
+ });
432
+ }
433
+
434
+ /**
435
+ * Inject secrets into a run (DEFERRED)
436
+ * Note: This returns success/failure only, NOT the injected values
437
+ * @returns Promise that resolves on success
438
+ * @throws Error with code NOT_IMPLEMENTED
439
+ */
440
+ export async function injectSecretsIntoRun(
441
+ runId: string,
442
+ mappings: SecretRef[]
443
+ ): Promise<void> {
444
+ await doRequest<{ injected: boolean }>(OPS.VAULT_INJECT_SECRETS_INTO_RUN, {
445
+ runId,
446
+ mappings,
447
+ });
448
+ }
449
+
450
+ // =============================================================================
451
+ // Audit Operations
452
+ // =============================================================================
453
+
454
+ export const audit = {
455
+ /** List audit events with cursor-based pagination */
456
+ async list(opts?: AuditListOptions): Promise<AuditListResult> {
457
+ return doRequest<AuditListResult>(OPS.VAULT_AUDIT_LIST, { opts });
458
+ },
459
+ };