@trycua/cua 0.2.0

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 (91) hide show
  1. package/README.md +121 -0
  2. package/bin/cua.js +37 -0
  3. package/browser/cua_sdk-ffi.d.ts +6 -0
  4. package/browser/cua_sdk-ffi.js +3 -0
  5. package/browser/cua_sdk-ffi.ts +14 -0
  6. package/browser/cua_sdk.d.ts +2237 -0
  7. package/browser/cua_sdk.js +2607 -0
  8. package/browser/cua_sdk.ts +3644 -0
  9. package/browser/cyclops_sdk_schema-ffi.d.ts +6 -0
  10. package/browser/cyclops_sdk_schema-ffi.js +3 -0
  11. package/browser/cyclops_sdk_schema-ffi.ts +14 -0
  12. package/browser/cyclops_sdk_schema.d.ts +996 -0
  13. package/browser/cyclops_sdk_schema.js +2194 -0
  14. package/browser/cyclops_sdk_schema.ts +2934 -0
  15. package/browser/fleet_sdk-ffi.d.ts +28 -0
  16. package/browser/fleet_sdk-ffi.js +3 -0
  17. package/browser/fleet_sdk-ffi.ts +35 -0
  18. package/browser/fleet_sdk.d.ts +3056 -0
  19. package/browser/fleet_sdk.js +5088 -0
  20. package/browser/fleet_sdk.ts +6830 -0
  21. package/browser/index.d.ts +2 -0
  22. package/browser/index.js +18 -0
  23. package/browser/index.web.ts +34 -0
  24. package/browser/tsconfig.json +20 -0
  25. package/browser/wasm-bindgen/index.d.ts +2346 -0
  26. package/browser/wasm-bindgen/index.js +6642 -0
  27. package/browser/wasm-bindgen/index_bg.wasm +0 -0
  28. package/browser/wasm-bindgen/index_bg.wasm.d.ts +1154 -0
  29. package/dist/index.d.ts +118 -0
  30. package/dist/index.js +207 -0
  31. package/dist/mcp.d.ts +33 -0
  32. package/dist/mcp.js +41 -0
  33. package/dist/native/cua_sdk-ffi.d.ts +1038 -0
  34. package/dist/native/cua_sdk-ffi.js +5095 -0
  35. package/dist/native/cua_sdk.d.ts +15906 -0
  36. package/dist/native/cua_sdk.js +27061 -0
  37. package/dist/native/index.d.ts +7 -0
  38. package/dist/native/index.js +12 -0
  39. package/dist/native/node-runtime.d.ts +72 -0
  40. package/dist/native/node-runtime.js +35 -0
  41. package/dist/spaces/cursorArt.d.ts +27 -0
  42. package/dist/spaces/cursorArt.js +59 -0
  43. package/dist/spaces/errors.d.ts +81 -0
  44. package/dist/spaces/errors.js +102 -0
  45. package/dist/spaces/events.d.ts +154 -0
  46. package/dist/spaces/events.js +182 -0
  47. package/dist/spaces/groups.d.ts +130 -0
  48. package/dist/spaces/groups.js +275 -0
  49. package/dist/spaces/host.d.ts +71 -0
  50. package/dist/spaces/host.js +154 -0
  51. package/dist/spaces/index.d.ts +48 -0
  52. package/dist/spaces/index.js +44 -0
  53. package/dist/spaces/pip.d.ts +128 -0
  54. package/dist/spaces/pip.js +250 -0
  55. package/dist/spaces/presence.d.ts +187 -0
  56. package/dist/spaces/presence.js +449 -0
  57. package/dist/spaces/presenceTypes.check.d.ts +12 -0
  58. package/dist/spaces/presenceTypes.check.js +1 -0
  59. package/dist/spaces/presenceTypes.d.ts +47 -0
  60. package/dist/spaces/presenceTypes.js +7 -0
  61. package/dist/spaces/routines.d.ts +158 -0
  62. package/dist/spaces/routines.js +339 -0
  63. package/dist/spaces/thread.d.ts +147 -0
  64. package/dist/spaces/thread.js +285 -0
  65. package/dist/spaces/transport/http.d.ts +51 -0
  66. package/dist/spaces/transport/http.js +113 -0
  67. package/dist/spaces/transport/index.d.ts +19 -0
  68. package/dist/spaces/transport/index.js +19 -0
  69. package/dist/spaces/transport/session.d.ts +120 -0
  70. package/dist/spaces/transport/session.js +188 -0
  71. package/dist/spaces/transport/tauri.d.ts +50 -0
  72. package/dist/spaces/transport/tauri.js +90 -0
  73. package/dist/spaces/transport/types.d.ts +123 -0
  74. package/dist/spaces/transport/types.js +59 -0
  75. package/dist/teleport/controller.d.ts +34 -0
  76. package/dist/teleport/controller.js +107 -0
  77. package/dist/teleport/drop.d.ts +62 -0
  78. package/dist/teleport/drop.js +138 -0
  79. package/dist/teleport/dropZone.d.ts +59 -0
  80. package/dist/teleport/dropZone.js +166 -0
  81. package/dist/teleport/element.d.ts +28 -0
  82. package/dist/teleport/element.js +340 -0
  83. package/dist/teleport/index.d.ts +32 -0
  84. package/dist/teleport/index.js +32 -0
  85. package/dist/teleport/install.d.ts +31 -0
  86. package/dist/teleport/install.js +65 -0
  87. package/dist/teleport/model.d.ts +245 -0
  88. package/dist/teleport/model.js +334 -0
  89. package/dist/teleport/windowDrag.d.ts +86 -0
  90. package/dist/teleport/windowDrag.js +71 -0
  91. package/package.json +93 -0
@@ -0,0 +1,245 @@
1
+ /**
2
+ * The headless "Teleport an app…" state machine, shared by every client
3
+ * (the Spaces app, the OpenKoalaBots examples). No DOM, no Node, no
4
+ * framework: a pure reducer over plain data, plus selectors.
5
+ *
6
+ * The data mirrors the cua SDK's `Teleport.catalog` / `plan` / `run`
7
+ * records (camelCase). Hosts that return the Rust core's JSON (snake_case,
8
+ * for example a Tauri command) convert it with {@link entryFromCore},
9
+ * {@link planFromCore} and {@link runEventFromCore}.
10
+ */
11
+ import { type InstallCuaPrompt } from "./install.js";
12
+ /** What teleport can do with an app. */
13
+ export type Capability = "full" | "install_only" | "unsupported";
14
+ /** What a teleport moves. */
15
+ export type Move = "app_only" | "app_with_files" | "app_with_state";
16
+ /** Credential-shaped state the signed-in state move leaves out by default,
17
+ * opted into one group at a time: `sign_ins` is the session cookies (what
18
+ * keeps the app signed in). Each group's items are secrets in the plan. */
19
+ export type SensitiveGroup = "sign_ins" | "passwords" | "history";
20
+ /** One row of "Teleport an app…". */
21
+ export interface CatalogEntry {
22
+ id: string;
23
+ name: string;
24
+ hostPath?: string | null;
25
+ hostAppId?: string | null;
26
+ version?: string | null;
27
+ capability: Capability;
28
+ /** Why it is unsupported, or a caveat. */
29
+ reason?: string | null;
30
+ /** Offered moves, in UI order (empty when unsupported). */
31
+ moves: Move[];
32
+ providerId?: string | null;
33
+ /** The opt-in groups the signed-in state move offers, UI order. */
34
+ sensitiveGroups?: SensitiveGroup[];
35
+ /** `manifest` (pinned install), `image`, `space`, or none. */
36
+ installSource?: "manifest" | "image" | "space" | null;
37
+ installId?: string | null;
38
+ installVersion?: string | null;
39
+ launchBin?: string | null;
40
+ lastUsedMs?: number | null;
41
+ /** The SDK's JSON for this entry (what `plan` reads back). */
42
+ json: string;
43
+ }
44
+ /** Options a user picks before planning. */
45
+ export interface PlanOptions {
46
+ moves: Move;
47
+ files: string[];
48
+ /** Provider manifest items; null/undefined = the provider's default. */
49
+ stateItems?: string[] | null;
50
+ /** With the default items: also these opt-in groups (each a secret). */
51
+ sensitiveGroups?: SensitiveGroup[];
52
+ scope?: "tabs" | "full";
53
+ launch?: boolean;
54
+ }
55
+ export type ConsentKind = "install" | "file" | "folder" | "state" | "secret";
56
+ /** One line of the consent screen. */
57
+ export interface ConsentItem {
58
+ kind: ConsentKind;
59
+ key: string;
60
+ label: string;
61
+ detail: string;
62
+ bytes: number;
63
+ sensitive: boolean;
64
+ }
65
+ /** Exactly what a teleport will do and move. */
66
+ export interface Plan {
67
+ app: CatalogEntry;
68
+ spaceId: string;
69
+ moves: Move;
70
+ steps: {
71
+ kind: string;
72
+ summary: string;
73
+ }[];
74
+ consent: ConsentItem[];
75
+ sensitive: boolean;
76
+ totalBytes: number;
77
+ warnings: string[];
78
+ /** This Space is reached through a relay connection that predates
79
+ * end-to-end sealing: a secret this plan sends would cross it in the
80
+ * clear (S1). Needs `acknowledgeRelayPlaintext` the same way a secret
81
+ * needs `acknowledgeSensitive`. */
82
+ relayUnsealed: boolean;
83
+ /** The SDK's JSON for this plan (what `run` reads back). */
84
+ json: string;
85
+ }
86
+ export type RunPhase = "started" | "progress" | "finished" | "failed" | "done";
87
+ export interface RunEvent {
88
+ step: number;
89
+ steps: number;
90
+ kind: string;
91
+ phase: RunPhase;
92
+ detail: string;
93
+ doneBytes: number;
94
+ totalBytes: number;
95
+ }
96
+ export interface RunReport {
97
+ appId: string;
98
+ installed: string[];
99
+ sent: string[];
100
+ imported: string[];
101
+ skipped: string[];
102
+ launched: boolean;
103
+ }
104
+ export interface Consent {
105
+ approved: boolean;
106
+ acknowledgeSensitive: boolean;
107
+ /** "Save to Keyvault": keep the captured session sealed in the user's
108
+ * own Cua Keyvault after delivery, instead of forgetting it. */
109
+ saveToKeyvault: boolean;
110
+ /** [`Plan.relayUnsealed`]'s warning acknowledged (S1). */
111
+ acknowledgeRelayPlaintext: boolean;
112
+ }
113
+ /**
114
+ * What a picker needs from its host: the SDK (Node), a Tauri command
115
+ * layer, or an HTTP server in front of the SDK.
116
+ */
117
+ export interface TeleportHost {
118
+ /** The classified catalog (the host narrows it to the Space). */
119
+ catalog(): Promise<CatalogEntry[]>;
120
+ plan(entry: CatalogEntry, options: PlanOptions): Promise<Plan>;
121
+ run(plan: Plan, consent: Consent, onEvent: (event: RunEvent) => void): Promise<RunReport>;
122
+ /** An icon as a URL (`data:` or `blob:`), or null. */
123
+ icon?(entry: CatalogEntry): Promise<string | null>;
124
+ /** A native file/folder chooser; absent in plain browsers. */
125
+ chooseFiles?(): Promise<string[]>;
126
+ }
127
+ export type Step = "loading" | "pick" | "options" | "planning" | "consent" | "running" | "done" | "error";
128
+ export interface PickerState {
129
+ step: Step;
130
+ spaceName: string;
131
+ entries: CatalogEntry[] | null;
132
+ query: string;
133
+ selectedId: string | null;
134
+ entry: CatalogEntry | null;
135
+ move: Move | null;
136
+ files: string[];
137
+ /** The opt-in groups checked on the options step. */
138
+ sensitive: SensitiveGroup[];
139
+ plan: Plan | null;
140
+ acknowledged: boolean;
141
+ /** "Save to Keyvault" checked (shown only for a sensitive plan). */
142
+ saveToKeyvault: boolean;
143
+ /** [`Plan.relayUnsealed`]'s warning acknowledged (S1). */
144
+ acknowledgedRelayPlaintext: boolean;
145
+ events: RunEvent[];
146
+ report: RunReport | null;
147
+ error: string | null;
148
+ /** Set when the error is the Keyvault's "needs the Cua app" refusal:
149
+ * show this prompt (install button) instead of the raw error. */
150
+ installPrompt: InstallCuaPrompt | null;
151
+ /** Where "back" from an error goes. */
152
+ errorBack: Step;
153
+ }
154
+ export type PickerEvent = {
155
+ type: "loaded";
156
+ entries: CatalogEntry[];
157
+ } | {
158
+ type: "failed";
159
+ message: string;
160
+ cause?: unknown;
161
+ } | {
162
+ type: "query";
163
+ query: string;
164
+ } | {
165
+ type: "select";
166
+ id: string;
167
+ } | {
168
+ type: "choose";
169
+ id?: string;
170
+ } | {
171
+ type: "preselect";
172
+ entry: CatalogEntry;
173
+ files?: string[];
174
+ } | {
175
+ type: "move";
176
+ move: Move;
177
+ } | {
178
+ type: "files";
179
+ files: string[];
180
+ } | {
181
+ type: "remove-file";
182
+ path: string;
183
+ } | {
184
+ type: "sensitive";
185
+ group: SensitiveGroup;
186
+ value: boolean;
187
+ } | {
188
+ type: "plan";
189
+ } | {
190
+ type: "planned";
191
+ plan: Plan;
192
+ } | {
193
+ type: "acknowledge";
194
+ value: boolean;
195
+ } | {
196
+ type: "save-to-keyvault";
197
+ value: boolean;
198
+ } | {
199
+ type: "acknowledge-relay-plaintext";
200
+ value: boolean;
201
+ } | {
202
+ type: "confirm";
203
+ } | {
204
+ type: "progress";
205
+ event: RunEvent;
206
+ } | {
207
+ type: "finished";
208
+ report: RunReport;
209
+ } | {
210
+ type: "back";
211
+ };
212
+ /** The initial state: loading the catalog. */
213
+ export declare function initialState(spaceName: string): PickerState;
214
+ /** The honest default: the least that moves (files when some were dropped). */
215
+ export declare function defaultMove(entry: CatalogEntry, files?: readonly string[]): Move | null;
216
+ /** The reducer. Invalid transitions return the state unchanged. */
217
+ export declare function reduce(s: PickerState, e: PickerEvent): PickerState;
218
+ /** Case-insensitive search over name, id and host app id; every word must match. */
219
+ export declare function searchEntries(entries: readonly CatalogEntry[], query: string): CatalogEntry[];
220
+ export declare function visibleEntries(s: PickerState): CatalogEntry[];
221
+ /** The pick list's sections: recents, then available, then unavailable. */
222
+ export declare function sections(s: PickerState): {
223
+ title: string;
224
+ entries: CatalogEntry[];
225
+ }[];
226
+ export declare function canPlan(s: PickerState): boolean;
227
+ /** The groups the plan asks for: the checked ones, in the entry's order,
228
+ * only with the signed-in state move. */
229
+ export declare function planSensitive(s: PickerState): SensitiveGroup[];
230
+ export declare function canConfirm(s: PickerState): boolean;
231
+ /** Overall run progress in [0, 1]. */
232
+ export declare function progress(s: PickerState): number;
233
+ export declare const CAPABILITY_LABEL: Record<Capability, string>;
234
+ export declare const MOVE_LABEL: Record<Move, string>;
235
+ export declare function formatBytes(n: number): string;
236
+ type Json = Record<string, unknown>;
237
+ /** A `cua_teleport::ux::CatalogEntry` (serde JSON) as a {@link CatalogEntry}. */
238
+ export declare function entryFromCore(j: Json): CatalogEntry;
239
+ /** A `cua_teleport::ux::TeleportPlan` (serde JSON) as a {@link Plan}. */
240
+ export declare function planFromCore(j: Json): Plan;
241
+ /** A `cua_teleport::ux::RunEvent` (serde JSON) as a {@link RunEvent}. */
242
+ export declare function runEventFromCore(j: Json): RunEvent;
243
+ /** A `cua_teleport::ux::RunReport` (serde JSON) as a {@link RunReport}. */
244
+ export declare function runReportFromCore(j: Json): RunReport;
245
+ export {};
@@ -0,0 +1,334 @@
1
+ /**
2
+ * The headless "Teleport an app…" state machine, shared by every client
3
+ * (the Spaces app, the OpenKoalaBots examples). No DOM, no Node, no
4
+ * framework: a pure reducer over plain data, plus selectors.
5
+ *
6
+ * The data mirrors the cua SDK's `Teleport.catalog` / `plan` / `run`
7
+ * records (camelCase). Hosts that return the Rust core's JSON (snake_case,
8
+ * for example a Tauri command) convert it with {@link entryFromCore},
9
+ * {@link planFromCore} and {@link runEventFromCore}.
10
+ */
11
+ import { requiresCuaApp } from "./install.js";
12
+ /** The initial state: loading the catalog. */
13
+ export function initialState(spaceName) {
14
+ return {
15
+ step: "loading",
16
+ spaceName,
17
+ entries: null,
18
+ query: "",
19
+ selectedId: null,
20
+ entry: null,
21
+ move: null,
22
+ files: [],
23
+ sensitive: [],
24
+ plan: null,
25
+ acknowledged: false,
26
+ saveToKeyvault: false,
27
+ acknowledgedRelayPlaintext: false,
28
+ events: [],
29
+ report: null,
30
+ error: null,
31
+ installPrompt: null,
32
+ errorBack: "pick",
33
+ };
34
+ }
35
+ /** The honest default: the least that moves (files when some were dropped). */
36
+ export function defaultMove(entry, files = []) {
37
+ if (files.length > 0 && entry.moves.includes("app_with_files"))
38
+ return "app_with_files";
39
+ return entry.moves[0] ?? null;
40
+ }
41
+ function toOptions(s, entry, files) {
42
+ return {
43
+ ...s,
44
+ step: "options",
45
+ entry,
46
+ selectedId: entry.id,
47
+ move: defaultMove(entry, files),
48
+ files,
49
+ sensitive: [],
50
+ plan: null,
51
+ acknowledged: false,
52
+ saveToKeyvault: false,
53
+ acknowledgedRelayPlaintext: false,
54
+ error: null,
55
+ installPrompt: null,
56
+ };
57
+ }
58
+ /** The reducer. Invalid transitions return the state unchanged. */
59
+ export function reduce(s, e) {
60
+ switch (e.type) {
61
+ case "loaded": {
62
+ const enabled = e.entries.find((x) => x.capability !== "unsupported");
63
+ // A preselected flow (options, planning, consent) keeps its step.
64
+ if (s.step !== "loading")
65
+ return { ...s, entries: e.entries };
66
+ return { ...s, step: "pick", entries: e.entries, selectedId: s.selectedId ?? enabled?.id ?? null, error: null, installPrompt: null };
67
+ }
68
+ case "failed": {
69
+ const back = s.step === "planning" ? "options" : s.step === "running" ? "consent" : s.entries ? "pick" : "loading";
70
+ const installPrompt = requiresCuaApp(e.cause) ?? requiresCuaApp(e.message);
71
+ return { ...s, step: "error", error: e.message, installPrompt, errorBack: back };
72
+ }
73
+ case "query": {
74
+ if (s.step !== "pick")
75
+ return s;
76
+ const next = { ...s, query: e.query };
77
+ const visible = visibleEntries(next);
78
+ const keep = visible.some((x) => x.id === s.selectedId);
79
+ return keep ? next : { ...next, selectedId: visible.find((x) => x.capability !== "unsupported")?.id ?? null };
80
+ }
81
+ case "select":
82
+ return s.step === "pick" ? { ...s, selectedId: e.id } : s;
83
+ case "choose": {
84
+ if (s.step !== "pick")
85
+ return s;
86
+ const id = e.id ?? s.selectedId;
87
+ const entry = s.entries?.find((x) => x.id === id);
88
+ if (!entry || entry.capability === "unsupported")
89
+ return s;
90
+ return toOptions(s, entry, []);
91
+ }
92
+ case "preselect":
93
+ if (e.entry.capability === "unsupported") {
94
+ return {
95
+ ...s,
96
+ step: "error",
97
+ entry: e.entry,
98
+ error: `${e.entry.name} cannot be teleported: ${e.entry.reason ?? "unsupported"}`,
99
+ installPrompt: null,
100
+ errorBack: "pick",
101
+ };
102
+ }
103
+ return toOptions(s, e.entry, e.files ?? []);
104
+ case "move":
105
+ return s.step === "options" && s.entry?.moves.includes(e.move) ? { ...s, move: e.move } : s;
106
+ case "files":
107
+ if (s.step !== "options")
108
+ return s;
109
+ return { ...s, files: [...new Set([...s.files, ...e.files])] };
110
+ case "remove-file":
111
+ return s.step === "options" ? { ...s, files: s.files.filter((f) => f !== e.path) } : s;
112
+ case "sensitive": {
113
+ // Only while the checkboxes show: options, signed-in state move.
114
+ const offered = s.entry?.sensitiveGroups?.includes(e.group) ?? false;
115
+ if (s.step !== "options" || s.move !== "app_with_state" || !offered)
116
+ return s;
117
+ const rest = s.sensitive.filter((g) => g !== e.group);
118
+ return { ...s, sensitive: e.value ? [...rest, e.group] : rest };
119
+ }
120
+ case "plan":
121
+ return s.step === "options" && canPlan(s) ? { ...s, step: "planning", error: null, installPrompt: null } : s;
122
+ case "planned":
123
+ return s.step === "planning"
124
+ ? {
125
+ ...s,
126
+ step: "consent",
127
+ plan: e.plan,
128
+ acknowledged: false,
129
+ saveToKeyvault: false,
130
+ acknowledgedRelayPlaintext: false,
131
+ }
132
+ : s;
133
+ case "acknowledge":
134
+ return s.step === "consent" ? { ...s, acknowledged: e.value } : s;
135
+ case "save-to-keyvault":
136
+ // Only meaningful (and only shown) for a plan with something
137
+ // sensitive to save.
138
+ return s.step === "consent" && s.plan?.sensitive ? { ...s, saveToKeyvault: e.value } : s;
139
+ case "acknowledge-relay-plaintext":
140
+ return s.step === "consent" ? { ...s, acknowledgedRelayPlaintext: e.value } : s;
141
+ case "confirm":
142
+ return s.step === "consent" && canConfirm(s) ? { ...s, step: "running", events: [] } : s;
143
+ case "progress":
144
+ return s.step === "running" ? { ...s, events: [...s.events, e.event].slice(-200) } : s;
145
+ case "finished":
146
+ return s.step === "running" ? { ...s, step: "done", report: e.report } : s;
147
+ case "back":
148
+ switch (s.step) {
149
+ case "options":
150
+ return s.entries ? { ...s, step: "pick", plan: null } : s;
151
+ case "consent":
152
+ return {
153
+ ...s,
154
+ step: "options",
155
+ plan: null,
156
+ acknowledged: false,
157
+ saveToKeyvault: false,
158
+ acknowledgedRelayPlaintext: false,
159
+ };
160
+ case "error":
161
+ return { ...s, step: s.errorBack === "loading" ? "loading" : s.errorBack, error: null, installPrompt: null };
162
+ default:
163
+ return s;
164
+ }
165
+ }
166
+ }
167
+ /** Case-insensitive search over name, id and host app id; every word must match. */
168
+ export function searchEntries(entries, query) {
169
+ const words = query.toLowerCase().split(/\s+/).filter(Boolean);
170
+ return entries.filter((e) => {
171
+ const hay = `${e.name} ${e.id} ${e.hostAppId ?? ""}`.toLowerCase();
172
+ return words.every((w) => hay.includes(w));
173
+ });
174
+ }
175
+ export function visibleEntries(s) {
176
+ return searchEntries(s.entries ?? [], s.query);
177
+ }
178
+ /** The pick list's sections: recents, then available, then unavailable. */
179
+ export function sections(s) {
180
+ const visible = visibleEntries(s);
181
+ const recents = visible.filter((e) => e.lastUsedMs != null && e.capability !== "unsupported");
182
+ const rest = visible.filter((e) => !recents.includes(e));
183
+ return [
184
+ { title: "Recent", entries: recents },
185
+ { title: "Apps", entries: rest.filter((e) => e.capability !== "unsupported") },
186
+ { title: "Not available", entries: rest.filter((e) => e.capability === "unsupported") },
187
+ ].filter((x) => x.entries.length > 0);
188
+ }
189
+ export function canPlan(s) {
190
+ if (!s.entry || !s.move || !s.entry.moves.includes(s.move))
191
+ return false;
192
+ return s.move !== "app_with_files" || s.files.length > 0;
193
+ }
194
+ /** The groups the plan asks for: the checked ones, in the entry's order,
195
+ * only with the signed-in state move. */
196
+ export function planSensitive(s) {
197
+ if (s.move !== "app_with_state")
198
+ return [];
199
+ return (s.entry?.sensitiveGroups ?? []).filter((g) => s.sensitive.includes(g));
200
+ }
201
+ export function canConfirm(s) {
202
+ return (s.plan != null &&
203
+ (!s.plan.sensitive || s.acknowledged) &&
204
+ (!s.plan.relayUnsealed || s.acknowledgedRelayPlaintext));
205
+ }
206
+ /** Overall run progress in [0, 1]. */
207
+ export function progress(s) {
208
+ const last = s.events[s.events.length - 1];
209
+ if (!last)
210
+ return 0;
211
+ if (last.phase === "done")
212
+ return 1;
213
+ const within = last.totalBytes > 0 ? Math.min(1, last.doneBytes / last.totalBytes) : last.phase === "finished" ? 1 : 0;
214
+ return Math.min(1, (last.step + within) / Math.max(1, last.steps));
215
+ }
216
+ export const CAPABILITY_LABEL = {
217
+ full: "App and signed-in state",
218
+ install_only: "App, empty or with files",
219
+ unsupported: "Not available",
220
+ };
221
+ export const MOVE_LABEL = {
222
+ app_only: "Just the app",
223
+ app_with_files: "The app with files or folders",
224
+ app_with_state: "The app with its signed-in state",
225
+ };
226
+ export function formatBytes(n) {
227
+ if (n < 1024)
228
+ return `${n} B`;
229
+ const units = ["KB", "MB", "GB", "TB"];
230
+ let v = n / 1024;
231
+ let i = 0;
232
+ while (v >= 1024 && i < units.length - 1) {
233
+ v /= 1024;
234
+ i++;
235
+ }
236
+ return `${v >= 10 ? Math.round(v) : v.toFixed(1)} ${units[i]}`;
237
+ }
238
+ const str = (v) => (typeof v === "string" ? v : null);
239
+ const num = (v) => (typeof v === "number" ? v : 0);
240
+ /** A `cua_teleport::ux::CatalogEntry` (serde JSON) as a {@link CatalogEntry}. */
241
+ export function entryFromCore(j) {
242
+ const install = (j.install ?? null);
243
+ const launch = (j.launch ?? null);
244
+ const kind = install ? str(install.kind) : null;
245
+ return {
246
+ id: String(j.id),
247
+ name: String(j.name),
248
+ hostPath: str(j.host_path),
249
+ hostAppId: str(j.host_app_id),
250
+ version: str(j.version),
251
+ capability: j.capability,
252
+ reason: str(j.reason),
253
+ moves: j.moves ?? [],
254
+ providerId: str(j.provider_id),
255
+ sensitiveGroups: Array.isArray(j.sensitive_groups) ? j.sensitive_groups : [],
256
+ installSource: kind === "manifest" || kind === "image" || kind === "space" ? kind : null,
257
+ installId: install && kind === "manifest" ? str(install.id) : null,
258
+ installVersion: install && kind === "manifest" ? str(install.version) : null,
259
+ launchBin: launch ? str(launch.bin) : null,
260
+ lastUsedMs: typeof j.last_used_ms === "number" ? j.last_used_ms : null,
261
+ json: JSON.stringify(j),
262
+ };
263
+ }
264
+ function stepSummary(step) {
265
+ const kind = String(step.kind);
266
+ const list = (v) => (Array.isArray(v) ? v : []);
267
+ switch (kind) {
268
+ case "install":
269
+ return `Install ${list(step.ids).join(", ")} (pinned, verified)`;
270
+ case "send_files":
271
+ return `Send ${list(step.paths).length} item(s) to ~/Downloads/${String(step.subdir)}`;
272
+ case "import_state":
273
+ return `Import ${list(step.items).length} ${String(step.provider_id)} item(s)`;
274
+ case "launch": {
275
+ const files = list(step.files);
276
+ return files.length ? `Open ${String(step.bin)} with ${files.length} item(s)` : `Open ${String(step.bin)}`;
277
+ }
278
+ default:
279
+ return kind;
280
+ }
281
+ }
282
+ const STEP_NAME = {
283
+ install: "install",
284
+ send_files: "files",
285
+ import_state: "state",
286
+ launch: "launch",
287
+ };
288
+ /** A `cua_teleport::ux::TeleportPlan` (serde JSON) as a {@link Plan}. */
289
+ export function planFromCore(j) {
290
+ const steps = j.steps ?? [];
291
+ return {
292
+ app: entryFromCore(j.app),
293
+ spaceId: String(j.space_id),
294
+ moves: j.moves,
295
+ steps: steps.map((s) => ({ kind: STEP_NAME[String(s.kind)] ?? String(s.kind), summary: stepSummary(s) })),
296
+ consent: (j.consent ?? []).map((c) => ({
297
+ kind: c.kind,
298
+ key: String(c.key),
299
+ label: String(c.label),
300
+ detail: String(c.detail),
301
+ bytes: num(c.bytes),
302
+ sensitive: Boolean(c.sensitive),
303
+ })),
304
+ sensitive: Boolean(j.sensitive),
305
+ relayUnsealed: Boolean(j.relay_unsealed),
306
+ totalBytes: num(j.total_bytes),
307
+ warnings: j.warnings ?? [],
308
+ json: JSON.stringify(j),
309
+ };
310
+ }
311
+ /** A `cua_teleport::ux::RunEvent` (serde JSON) as a {@link RunEvent}. */
312
+ export function runEventFromCore(j) {
313
+ return {
314
+ step: num(j.step),
315
+ steps: num(j.steps),
316
+ kind: String(j.kind),
317
+ phase: j.phase,
318
+ detail: String(j.detail ?? ""),
319
+ doneBytes: num(j.done_bytes),
320
+ totalBytes: num(j.total_bytes),
321
+ };
322
+ }
323
+ /** A `cua_teleport::ux::RunReport` (serde JSON) as a {@link RunReport}. */
324
+ export function runReportFromCore(j) {
325
+ const list = (v) => (Array.isArray(v) ? v : []);
326
+ return {
327
+ appId: String(j.app_id),
328
+ installed: list(j.installed),
329
+ sent: list(j.sent),
330
+ imported: list(j.imported),
331
+ skipped: list(j.skipped),
332
+ launched: Boolean(j.launched),
333
+ };
334
+ }
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Dragging a real app window onto a Space: the drop-zone state every client
3
+ * renders (the dragged window's preview, its app, the Space under the
4
+ * cursor). Events come from the SDK's window-drag monitor
5
+ * (`Teleport.startWindowDrag`, or a host relaying it); the preview is the
6
+ * SDK's in-memory thumbnail of that one window.
7
+ */
8
+ import type { CatalogEntry } from "./model.js";
9
+ export interface DraggedWindow {
10
+ windowId: number;
11
+ appName: string;
12
+ title: string;
13
+ bundlePath?: string | null;
14
+ }
15
+ /** One monitor event (global top-left display points). */
16
+ export interface WindowDragEvent {
17
+ phase: "start" | "move" | "end";
18
+ x: number;
19
+ y: number;
20
+ window?: DraggedWindow | null;
21
+ app?: CatalogEntry | null;
22
+ }
23
+ export interface WindowDropState {
24
+ active: boolean;
25
+ window: DraggedWindow | null;
26
+ app: CatalogEntry | null;
27
+ /** The dragged window's preview (`data:` URL), when captured. */
28
+ thumbnail: string | null;
29
+ /** The drop target under the cursor. */
30
+ overId: string | null;
31
+ x: number;
32
+ y: number;
33
+ }
34
+ export type WindowDropEvent = WindowDragEvent | {
35
+ phase: "thumbnail";
36
+ url: string | null;
37
+ } | {
38
+ phase: "over";
39
+ id: string | null;
40
+ };
41
+ export declare const idleWindowDrop: WindowDropState;
42
+ /** What a release commits: teleport `app` into `targetId`. */
43
+ export interface WindowDropCommit {
44
+ targetId: string;
45
+ app: CatalogEntry;
46
+ window: DraggedWindow;
47
+ }
48
+ /**
49
+ * The reducer. Only apps teleport can bring up start a drop (an
50
+ * unsupported app's window drag is ignored, as before). `end` returns the
51
+ * commit when released over a target.
52
+ */
53
+ export declare function reduceWindowDrop(s: WindowDropState, e: WindowDropEvent): {
54
+ state: WindowDropState;
55
+ commit: WindowDropCommit | null;
56
+ capture: number | null;
57
+ };
58
+ /** Screen point to client point, given the viewport's screen origin. */
59
+ export declare function screenToClient(x: number, y: number, origin: {
60
+ x: number;
61
+ y: number;
62
+ }): {
63
+ x: number;
64
+ y: number;
65
+ };
66
+ /**
67
+ * A browser viewport's screen origin (approximate: assumes the browser
68
+ * chrome sits above the page, as in desktop browsers).
69
+ */
70
+ export declare function browserViewportOrigin(w: {
71
+ screenX: number;
72
+ screenY: number;
73
+ outerWidth: number;
74
+ innerWidth: number;
75
+ outerHeight: number;
76
+ innerHeight: number;
77
+ }): {
78
+ x: number;
79
+ y: number;
80
+ };
81
+ /** The closest `[attr]` element's value at a client point, or null. */
82
+ export declare function targetAt(doc: {
83
+ elementFromPoint(x: number, y: number): Element | null;
84
+ }, x: number, y: number, attr?: string): string | null;
85
+ /** PNG bytes (from the SDK) as a `data:` URL for an `<img>`. */
86
+ export declare function pngDataUrl(bytes: Uint8Array | ArrayBuffer | readonly number[]): string;