()
| 54 | } |
| 55 | |
| 56 | export function useControlApi() { |
| 57 | const { base, token } = resolveControlConfig() |
| 58 | const headers: Record<string, string> = {} |
| 59 | if (token) headers.Authorization = `Bearer ${token}` |
| 60 | // ky v2: use `prefix` (prefixUrl was renamed in v2). All method paths below |
| 61 | // are passed WITHOUT a leading slash so ky joins them cleanly. |
| 62 | const client = ky.create({ prefix: base, headers, timeout: 15000 }) |
| 63 | |
| 64 | return { |
| 65 | base, |
| 66 | token, |
| 67 | getInfo: () => client.get('info').json<ControlInfo>(), |
| 68 | getKernelStatus: () => client.get('kernel/status').json<KernelState>(), |
| 69 | startKernel: () => client.post('kernel/start').json<KernelState>(), |
| 70 | stopKernel: () => client.post('kernel/stop').json<KernelState>(), |
| 71 | restartKernel: () => client.post('kernel/restart').json<KernelState>(), |
| 72 | // EventSource cannot send Authorization headers, so the SSE route also |
| 73 | // accepts ?token= (SHARED CONTRACTS). Desktop in-process binding skips |
| 74 | // auth, but passing the token there is harmless. |
| 75 | logsUrl: () => |
| 76 | token ? `${base}/kernel/logs?token=${token}` : `${base}/kernel/logs`, |
| 77 | |
| 78 | listProfiles: () => client.get('profiles').json<ProfileMeta[]>(), |
| 79 | // `type: 'merge'` mints a YAML overlay profile (composed onto the active |
| 80 | // base); `type: 'script'` mints a JS transform run after merges; omitting |
| 81 | // type defaults to a plain local profile (SHARED CONTRACTS). |
| 82 | createProfile: (body: { |
| 83 | name: string |
| 84 | content?: string |
| 85 | type?: 'local' | 'merge' | 'script' |
| 86 | }) => client.post('profiles', { json: body }).json<ProfileMeta>(), |
| 87 | getProfile: (id: string) => |
| 88 | client.get(`profiles/${id}`).json<ProfileDetail>(), |
| 89 | updateProfile: ( |
| 90 | id: string, |
| 91 | body: { name?: string; content?: string; enabled?: boolean }, |
| 92 | ) => client.put(`profiles/${id}`, { json: body }).json<ProfileMeta>(), |
| 93 | deleteProfile: (id: string) => client.delete(`profiles/${id}`).json<void>(), |
| 94 | duplicateProfile: (id: string, name?: string) => |
| 95 | client |
| 96 | .post(`profiles/${id}/duplicate`, { json: { name } }) |
| 97 | .json<ProfileMeta>(), |
| 98 | importProfile: (url: string, name?: string) => |
| 99 | client |
| 100 | .post('profiles/import', { json: { url, name } }) |
| 101 | .json<ProfileMeta>(), |
| 102 | activateProfile: (id: string) => |
| 103 | client.post(`profiles/${id}/activate`).json<KernelState>(), |
| 104 | // Re-fetch a REMOTE subscription in place (agent overwrites content + |
| 105 | // subscriptionInfo + updatedAt, keeping the same id). The agent rejects this |
| 106 | // for non-remote profiles. |
| 107 | refreshProfile: (id: string) => |
| 108 | client.post(`profiles/${id}/refresh`).json<ProfileMeta>(), |
| 109 | validateProfile: (id: string) => |
| 110 | client.post(`profiles/${id}/validate`).json<ValidateResult>(), |
| 111 | |
| 112 | // System proxy (capability-gated 'system-proxy'). GET reflects the current |
| 113 | // OS proxy state; POST { enabled, bypass? } toggles it and echoes the state. |
no test coverage detected