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,68 @@
1
+ /**
2
+ * Debug Operations
3
+ *
4
+ * Utility operations for development and debugging.
5
+ * Only available when EKKA_ENV=development.
6
+ */
7
+
8
+ import { _internal, makeRequest } from '../internal';
9
+
10
+ // =============================================================================
11
+ // Types
12
+ // =============================================================================
13
+
14
+ export interface DebugBundleInfo {
15
+ debug_bundle_ref: string;
16
+ raw_output_sha256: string;
17
+ raw_output_len: number;
18
+ files: string[];
19
+ }
20
+
21
+ export interface ResolvedVaultPath {
22
+ vaultUri: string;
23
+ filesystemPath: string;
24
+ exists: boolean;
25
+ }
26
+
27
+ // =============================================================================
28
+ // Operations
29
+ // =============================================================================
30
+
31
+ /**
32
+ * Check if running in development mode.
33
+ */
34
+ export async function isDevMode(): Promise<boolean> {
35
+ const req = makeRequest('debug.isDevMode', {});
36
+ const response = await _internal.request(req);
37
+ const result = response.result as { isDevMode?: boolean } | undefined;
38
+ return result?.isDevMode ?? false;
39
+ }
40
+
41
+ /**
42
+ * Open a folder in the system file browser.
43
+ * Only works in development mode.
44
+ *
45
+ * @param path - Path to open (supports vault:// URIs)
46
+ */
47
+ export async function openFolder(path: string): Promise<void> {
48
+ const req = makeRequest('debug.openFolder', { path });
49
+ const response = await _internal.request(req);
50
+ if (!response.ok) {
51
+ throw new Error(response.error?.message || 'Failed to open folder');
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Resolve a vault:// URI to filesystem path.
57
+ * Only works in development mode.
58
+ *
59
+ * @param vaultUri - Vault URI (e.g., "vault://tmp/telemetry/...")
60
+ */
61
+ export async function resolveVaultPath(vaultUri: string): Promise<ResolvedVaultPath> {
62
+ const req = makeRequest('debug.resolveVaultPath', { path: vaultUri });
63
+ const response = await _internal.request(req);
64
+ if (!response.ok) {
65
+ throw new Error(response.error?.message || 'Failed to resolve vault path');
66
+ }
67
+ return response.result as ResolvedVaultPath;
68
+ }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Home Operations
3
+ *
4
+ * Wraps Rust ops/home.rs
5
+ */
6
+
7
+ import { OPS } from '../constants';
8
+ import { _internal, makeRequest } from '../internal';
9
+
10
+ // =============================================================================
11
+ // Types
12
+ // =============================================================================
13
+
14
+ export type HomeState =
15
+ | 'BOOTSTRAP_PRE_LOGIN'
16
+ | 'AUTHENTICATED_NO_HOME_GRANT'
17
+ | 'HOME_GRANTED';
18
+
19
+ export interface HomeStatus {
20
+ state: HomeState;
21
+ homePath: string;
22
+ grantPresent: boolean;
23
+ reason: string | null;
24
+ }
25
+
26
+ export interface GrantResult {
27
+ success: boolean;
28
+ grant_id: string;
29
+ expires_at: string | null;
30
+ }
31
+
32
+ // =============================================================================
33
+ // Raw Operations (Advanced API)
34
+ // =============================================================================
35
+
36
+ /**
37
+ * Get home directory status.
38
+ * Maps to Rust: home.status
39
+ */
40
+ export async function status(): Promise<HomeStatus> {
41
+ const req = makeRequest(OPS.HOME_STATUS, {});
42
+ const response = await _internal.request(req);
43
+
44
+ if (!response.ok) {
45
+ throw new Error(response.error?.message || 'Failed to get home status');
46
+ }
47
+
48
+ return response.result as HomeStatus;
49
+ }
50
+
51
+ /**
52
+ * Request HOME grant from engine.
53
+ * Maps to Rust: home.grant
54
+ */
55
+ export async function grant(): Promise<GrantResult> {
56
+ const req = makeRequest(OPS.HOME_GRANT, {});
57
+ const response = await _internal.request(req);
58
+
59
+ if (!response.ok) {
60
+ throw new Error(response.error?.message || 'Failed to request grant');
61
+ }
62
+
63
+ return response.result as GrantResult;
64
+ }
65
+
66
+ // =============================================================================
67
+ // Simple Operations (Public API)
68
+ // =============================================================================
69
+
70
+ /**
71
+ * Check if home is ready to use.
72
+ * Simple boolean - no scary state machines.
73
+ */
74
+ export async function isReady(): Promise<boolean> {
75
+ try {
76
+ const s = await status();
77
+ return s.state === 'HOME_GRANTED';
78
+ } catch {
79
+ return false;
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Set up home directory.
85
+ * Does everything needed - checks status, requests grant if needed.
86
+ * Just call this and you're done.
87
+ */
88
+ export async function setup(): Promise<void> {
89
+ const s = await status();
90
+
91
+ if (s.state === 'HOME_GRANTED') {
92
+ return; // Already ready
93
+ }
94
+
95
+ if (s.state === 'BOOTSTRAP_PRE_LOGIN') {
96
+ throw new Error('Please login first before setting up home');
97
+ }
98
+
99
+ // State is AUTHENTICATED_NO_HOME_GRANT - request the grant
100
+ await grant();
101
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Operations
3
+ *
4
+ * Mirrors Rust src-tauri/src/ops/ and handlers/
5
+ */
6
+
7
+ export * as auth from './auth';
8
+ export * as debug from './debug';
9
+ export * as home from './home';
10
+ export * as paths from './paths';
11
+ export * as runtime from './runtime';
12
+ export * as runner from './runner';
13
+ export * as setup from './setup';
14
+ export * as vault from './vault';
15
+ export * as nodeSession from './nodeSession';
16
+ export * as nodeCredentials from './nodeCredentials';
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Node Credentials Operations
3
+ *
4
+ * Manages node_id + node_secret for headless engine startup.
5
+ * Credentials are stored securely in OS keychain.
6
+ *
7
+ * ## Usage
8
+ * 1. User provides node_id + node_secret once (onboarding)
9
+ * 2. Credentials stored in OS keychain
10
+ * 3. Engine uses them automatically on startup (no login required)
11
+ */
12
+
13
+ import { OPS } from '../constants';
14
+ import { _internal, makeRequest } from '../internal';
15
+
16
+ // =============================================================================
17
+ // TYPES
18
+ // =============================================================================
19
+
20
+ /** Input for setting node credentials */
21
+ export interface SetCredentialsInput {
22
+ nodeId: string;
23
+ nodeSecret: string;
24
+ }
25
+
26
+ /** Result from setting credentials */
27
+ export interface SetCredentialsResult {
28
+ ok: boolean;
29
+ nodeId: string;
30
+ }
31
+
32
+ /** Auth session info from node authentication */
33
+ export interface NodeAuthSession {
34
+ sessionId: string;
35
+ tenantId: string;
36
+ workspaceId: string;
37
+ expiresAt: string;
38
+ }
39
+
40
+ /** Credentials status */
41
+ export interface CredentialsStatus {
42
+ hasCredentials: boolean;
43
+ nodeId: string | null;
44
+ isAuthenticated: boolean;
45
+ authSession: NodeAuthSession | null;
46
+ }
47
+
48
+ // =============================================================================
49
+ // OPERATIONS
50
+ // =============================================================================
51
+
52
+ /**
53
+ * Set node credentials.
54
+ *
55
+ * Stores node_id + node_secret in OS keychain.
56
+ * Validates:
57
+ * - node_id must be valid UUID
58
+ * - node_secret must be non-empty and at least 16 characters
59
+ *
60
+ * @param input - { nodeId: UUID, nodeSecret: string }
61
+ * @returns { ok: true, nodeId: string } on success
62
+ * @throws Error if validation fails or keychain error
63
+ */
64
+ export async function set(input: SetCredentialsInput): Promise<SetCredentialsResult> {
65
+ const req = makeRequest(OPS.NODE_CREDENTIALS_SET, {
66
+ nodeId: input.nodeId,
67
+ nodeSecret: input.nodeSecret,
68
+ });
69
+ const response = await _internal.request(req);
70
+
71
+ if (!response.ok) {
72
+ throw new Error(response.error?.message || 'Failed to store node credentials');
73
+ }
74
+
75
+ return response.result as SetCredentialsResult;
76
+ }
77
+
78
+ /**
79
+ * Get credentials status.
80
+ *
81
+ * Returns whether credentials are configured and the node_id (if present).
82
+ * Does NOT return the node_secret.
83
+ */
84
+ export async function status(): Promise<CredentialsStatus> {
85
+ const req = makeRequest(OPS.NODE_CREDENTIALS_STATUS, {});
86
+ const response = await _internal.request(req);
87
+
88
+ if (!response.ok) {
89
+ throw new Error(response.error?.message || 'Failed to get credentials status');
90
+ }
91
+
92
+ return response.result as CredentialsStatus;
93
+ }
94
+
95
+ /**
96
+ * Clear node credentials from OS keychain.
97
+ *
98
+ * @returns { ok: true } on success
99
+ */
100
+ export async function clear(): Promise<{ ok: boolean }> {
101
+ const req = makeRequest(OPS.NODE_CREDENTIALS_CLEAR, {});
102
+ const response = await _internal.request(req);
103
+
104
+ if (!response.ok) {
105
+ throw new Error(response.error?.message || 'Failed to clear credentials');
106
+ }
107
+
108
+ return response.result as { ok: boolean };
109
+ }
110
+
111
+ /**
112
+ * Validate node_id format (UUID).
113
+ *
114
+ * @param nodeId - String to validate
115
+ * @returns true if valid UUID format
116
+ */
117
+ export function isValidNodeId(nodeId: string): boolean {
118
+ const uuidRegex =
119
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
120
+ return uuidRegex.test(nodeId);
121
+ }
122
+
123
+ /**
124
+ * Validate node_secret format.
125
+ *
126
+ * @param nodeSecret - String to validate
127
+ * @returns true if valid (non-empty, at least 16 chars)
128
+ */
129
+ export function isValidNodeSecret(nodeSecret: string): boolean {
130
+ return nodeSecret.length >= 16;
131
+ }
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Node Session Operations
3
+ *
4
+ * Wraps Rust node_auth module for Ed25519-based node authentication.
5
+ *
6
+ * ## Flow
7
+ * 1. ensureIdentity() - Create/load Ed25519 keypair (no auth required)
8
+ * 2. bootstrap({ startRunner: true }) - Register with engine + start runner
9
+ * 3. status() - Check current identity and session state
10
+ */
11
+
12
+ import { OPS } from '../constants';
13
+ import { _internal, makeRequest } from '../internal';
14
+
15
+ // =============================================================================
16
+ // TYPES
17
+ // =============================================================================
18
+
19
+ /** Node identity (public metadata - safe to log) */
20
+ export interface NodeIdentity {
21
+ node_id: string;
22
+ public_key_b64: string;
23
+ private_key_vault_ref: string;
24
+ created_at: string;
25
+ }
26
+
27
+ /** Result from ensureIdentity */
28
+ export interface EnsureIdentityResult {
29
+ ok: boolean;
30
+ node_id: string;
31
+ public_key_b64: string;
32
+ private_key_vault_ref: string;
33
+ created_at: string;
34
+ }
35
+
36
+ /** Options for bootstrap */
37
+ export interface BootstrapOptions {
38
+ /** Start the runner after session is established (default: false) */
39
+ startRunner?: boolean;
40
+ }
41
+
42
+ /** Session info from bootstrap */
43
+ export interface SessionInfo {
44
+ session_id: string;
45
+ tenant_id: string;
46
+ workspace_id: string;
47
+ expires_at: string;
48
+ }
49
+
50
+ /** Result from bootstrap */
51
+ export interface BootstrapResult {
52
+ ok: boolean;
53
+ node_id: string;
54
+ public_key_b64: string;
55
+ registered: boolean;
56
+ session?: SessionInfo;
57
+ }
58
+
59
+ /** Node session status */
60
+ export interface NodeSessionStatus {
61
+ hasIdentity: boolean;
62
+ hasSession: boolean;
63
+ sessionValid: boolean;
64
+ identity?: {
65
+ node_id: string;
66
+ public_key_b64: string;
67
+ created_at: string;
68
+ };
69
+ session?: {
70
+ session_id: string;
71
+ tenant_id: string;
72
+ workspace_id: string;
73
+ expires_at: string;
74
+ is_expired: boolean;
75
+ };
76
+ }
77
+
78
+ // =============================================================================
79
+ // OPERATIONS
80
+ // =============================================================================
81
+
82
+ /**
83
+ * Ensure node identity exists (load or create keypair).
84
+ *
85
+ * Creates Ed25519 keypair if none exists.
86
+ * Does NOT require authentication.
87
+ *
88
+ * Call this during app startup before login.
89
+ */
90
+ export async function ensureIdentity(): Promise<EnsureIdentityResult> {
91
+ const req = makeRequest(OPS.NODE_SESSION_ENSURE_IDENTITY, {});
92
+ const response = await _internal.request(req);
93
+
94
+ if (!response.ok) {
95
+ throw new Error(response.error?.message || 'Failed to ensure node identity');
96
+ }
97
+
98
+ return response.result as EnsureIdentityResult;
99
+ }
100
+
101
+ /**
102
+ * Bootstrap full node session.
103
+ *
104
+ * 1. Ensures identity exists
105
+ * 2. Registers node with engine (idempotent)
106
+ * 3. Gets challenge from engine
107
+ * 4. Signs challenge with Ed25519 private key
108
+ * 5. Exchanges signature for session token
109
+ * 6. Optionally starts the runner
110
+ *
111
+ * REQUIRES: User must be logged in (JWT required for registration).
112
+ */
113
+ export async function bootstrap(options?: BootstrapOptions): Promise<BootstrapResult> {
114
+ const req = makeRequest(OPS.NODE_SESSION_BOOTSTRAP, {
115
+ startRunner: options?.startRunner ?? false,
116
+ });
117
+ const response = await _internal.request(req);
118
+
119
+ if (!response.ok) {
120
+ throw new Error(response.error?.message || 'Failed to bootstrap node session');
121
+ }
122
+
123
+ return response.result as BootstrapResult;
124
+ }
125
+
126
+ /**
127
+ * Get current node session status.
128
+ *
129
+ * Returns:
130
+ * - hasIdentity: Whether Ed25519 keypair exists
131
+ * - hasSession: Whether session token exists
132
+ * - sessionValid: Whether session is not expired
133
+ * - identity: Node identity metadata
134
+ * - session: Session metadata including expiry
135
+ */
136
+ export async function status(): Promise<NodeSessionStatus> {
137
+ const req = makeRequest(OPS.NODE_SESSION_STATUS, {});
138
+ const response = await _internal.request(req);
139
+
140
+ if (!response.ok) {
141
+ throw new Error(response.error?.message || 'Failed to get node session status');
142
+ }
143
+
144
+ return response.result as NodeSessionStatus;
145
+ }
@@ -0,0 +1,183 @@
1
+ /**
2
+ * Path Operations
3
+ *
4
+ * Wraps Rust handlers/paths.rs
5
+ */
6
+
7
+ import { OPS } from '../constants';
8
+ import { _internal, makeRequest } from '../internal';
9
+
10
+ // =============================================================================
11
+ // Types
12
+ // =============================================================================
13
+
14
+ export type PathType =
15
+ | 'GENERAL'
16
+ | 'WORKSPACE'
17
+ | 'DATA'
18
+ | 'TEMP'
19
+ | 'CACHE'
20
+ | 'HOME';
21
+
22
+ export type PathAccess = 'READ_ONLY' | 'READ_WRITE';
23
+
24
+ export interface PathInfo {
25
+ path: string;
26
+ pathType: PathType;
27
+ access: PathAccess;
28
+ grantId: string;
29
+ expiresAt: string | null;
30
+ isValid: boolean;
31
+ /** Who issued the grant (e.g., "ekka-engine") */
32
+ issuer: string;
33
+ /** When the grant was issued (RFC3339) */
34
+ issuedAt: string;
35
+ /** User/subject who owns the grant */
36
+ subject: string;
37
+ /** Tenant ID */
38
+ tenantId: string;
39
+ /** Purpose of the grant */
40
+ purpose: string;
41
+ }
42
+
43
+ export interface PathCheckResult {
44
+ allowed: boolean;
45
+ reason: string;
46
+ pathType: PathType | null;
47
+ access: PathAccess | null;
48
+ /** The path prefix that granted access (use this for revoke) */
49
+ grantedBy: string | null;
50
+ }
51
+
52
+ export interface PathGrantResult {
53
+ success: boolean;
54
+ grantId: string | null;
55
+ expiresAt: string | null;
56
+ error: string | null;
57
+ }
58
+
59
+ export interface PathRequestOptions {
60
+ pathType?: PathType;
61
+ access?: PathAccess;
62
+ }
63
+
64
+ // =============================================================================
65
+ // Raw Operations (Advanced API)
66
+ // =============================================================================
67
+
68
+ /**
69
+ * Check if an operation is allowed on a path.
70
+ * Maps to Rust: paths.check
71
+ */
72
+ export async function check(
73
+ path: string,
74
+ operation: string = 'read'
75
+ ): Promise<PathCheckResult> {
76
+ const req = makeRequest(OPS.PATHS_CHECK, { path, operation });
77
+ const response = await _internal.request(req);
78
+
79
+ if (!response.ok) {
80
+ throw new Error(response.error?.message || 'Failed to check path');
81
+ }
82
+
83
+ return response.result as PathCheckResult;
84
+ }
85
+
86
+ /**
87
+ * List all path grants.
88
+ * Maps to Rust: paths.list
89
+ */
90
+ export async function list(pathType?: PathType): Promise<PathInfo[]> {
91
+ const req = makeRequest(OPS.PATHS_LIST, { pathType });
92
+ const response = await _internal.request(req);
93
+
94
+ if (!response.ok) {
95
+ throw new Error(response.error?.message || 'Failed to list paths');
96
+ }
97
+
98
+ const result = response.result as { paths: PathInfo[] };
99
+ return result.paths;
100
+ }
101
+
102
+ /**
103
+ * Get information about a specific path.
104
+ * Maps to Rust: paths.get
105
+ */
106
+ export async function get(path: string): Promise<PathInfo | null> {
107
+ const req = makeRequest(OPS.PATHS_GET, { path });
108
+ const response = await _internal.request(req);
109
+
110
+ if (!response.ok) {
111
+ throw new Error(response.error?.message || 'Failed to get path');
112
+ }
113
+
114
+ return response.result as PathInfo | null;
115
+ }
116
+
117
+ /**
118
+ * Request access to a path.
119
+ * Maps to Rust: paths.request
120
+ */
121
+ export async function request(
122
+ path: string,
123
+ pathType: PathType = 'GENERAL',
124
+ access: PathAccess = 'READ_ONLY'
125
+ ): Promise<PathGrantResult> {
126
+ const req = makeRequest(OPS.PATHS_REQUEST, { path, pathType, access });
127
+ const response = await _internal.request(req);
128
+
129
+ if (!response.ok) {
130
+ throw new Error(response.error?.message || 'Failed to request path access');
131
+ }
132
+
133
+ return response.result as PathGrantResult;
134
+ }
135
+
136
+ /**
137
+ * Remove a path grant.
138
+ * Maps to Rust: paths.remove
139
+ */
140
+ export async function remove(path: string): Promise<boolean> {
141
+ const req = makeRequest(OPS.PATHS_REMOVE, { path });
142
+ const response = await _internal.request(req);
143
+
144
+ if (!response.ok) {
145
+ throw new Error(response.error?.message || 'Failed to remove path');
146
+ }
147
+
148
+ const result = response.result as { removed: boolean };
149
+ return result.removed;
150
+ }
151
+
152
+ // =============================================================================
153
+ // Simple Operations (Public API)
154
+ // =============================================================================
155
+
156
+ /**
157
+ * Check if an operation is allowed on a path.
158
+ * Simple boolean - no scary details.
159
+ */
160
+ export async function isAllowed(
161
+ path: string,
162
+ operation: string = 'read'
163
+ ): Promise<boolean> {
164
+ try {
165
+ const result = await check(path, operation);
166
+ return result.allowed;
167
+ } catch {
168
+ return false;
169
+ }
170
+ }
171
+
172
+ /**
173
+ * Allow access to a path.
174
+ * Simple wrapper for request with sensible defaults.
175
+ */
176
+ export async function allow(
177
+ path: string,
178
+ options?: PathRequestOptions
179
+ ): Promise<PathGrantResult> {
180
+ const pathType = options?.pathType || 'WORKSPACE';
181
+ const access = options?.access || 'READ_WRITE';
182
+ return request(path, pathType, access);
183
+ }