@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.
- package/README.md +121 -0
- package/bin/cua.js +37 -0
- package/browser/cua_sdk-ffi.d.ts +6 -0
- package/browser/cua_sdk-ffi.js +3 -0
- package/browser/cua_sdk-ffi.ts +14 -0
- package/browser/cua_sdk.d.ts +2237 -0
- package/browser/cua_sdk.js +2607 -0
- package/browser/cua_sdk.ts +3644 -0
- package/browser/cyclops_sdk_schema-ffi.d.ts +6 -0
- package/browser/cyclops_sdk_schema-ffi.js +3 -0
- package/browser/cyclops_sdk_schema-ffi.ts +14 -0
- package/browser/cyclops_sdk_schema.d.ts +996 -0
- package/browser/cyclops_sdk_schema.js +2194 -0
- package/browser/cyclops_sdk_schema.ts +2934 -0
- package/browser/fleet_sdk-ffi.d.ts +28 -0
- package/browser/fleet_sdk-ffi.js +3 -0
- package/browser/fleet_sdk-ffi.ts +35 -0
- package/browser/fleet_sdk.d.ts +3056 -0
- package/browser/fleet_sdk.js +5088 -0
- package/browser/fleet_sdk.ts +6830 -0
- package/browser/index.d.ts +2 -0
- package/browser/index.js +18 -0
- package/browser/index.web.ts +34 -0
- package/browser/tsconfig.json +20 -0
- package/browser/wasm-bindgen/index.d.ts +2346 -0
- package/browser/wasm-bindgen/index.js +6642 -0
- package/browser/wasm-bindgen/index_bg.wasm +0 -0
- package/browser/wasm-bindgen/index_bg.wasm.d.ts +1154 -0
- package/dist/index.d.ts +118 -0
- package/dist/index.js +207 -0
- package/dist/mcp.d.ts +33 -0
- package/dist/mcp.js +41 -0
- package/dist/native/cua_sdk-ffi.d.ts +1038 -0
- package/dist/native/cua_sdk-ffi.js +5095 -0
- package/dist/native/cua_sdk.d.ts +15906 -0
- package/dist/native/cua_sdk.js +27061 -0
- package/dist/native/index.d.ts +7 -0
- package/dist/native/index.js +12 -0
- package/dist/native/node-runtime.d.ts +72 -0
- package/dist/native/node-runtime.js +35 -0
- package/dist/spaces/cursorArt.d.ts +27 -0
- package/dist/spaces/cursorArt.js +59 -0
- package/dist/spaces/errors.d.ts +81 -0
- package/dist/spaces/errors.js +102 -0
- package/dist/spaces/events.d.ts +154 -0
- package/dist/spaces/events.js +182 -0
- package/dist/spaces/groups.d.ts +130 -0
- package/dist/spaces/groups.js +275 -0
- package/dist/spaces/host.d.ts +71 -0
- package/dist/spaces/host.js +154 -0
- package/dist/spaces/index.d.ts +48 -0
- package/dist/spaces/index.js +44 -0
- package/dist/spaces/pip.d.ts +128 -0
- package/dist/spaces/pip.js +250 -0
- package/dist/spaces/presence.d.ts +187 -0
- package/dist/spaces/presence.js +449 -0
- package/dist/spaces/presenceTypes.check.d.ts +12 -0
- package/dist/spaces/presenceTypes.check.js +1 -0
- package/dist/spaces/presenceTypes.d.ts +47 -0
- package/dist/spaces/presenceTypes.js +7 -0
- package/dist/spaces/routines.d.ts +158 -0
- package/dist/spaces/routines.js +339 -0
- package/dist/spaces/thread.d.ts +147 -0
- package/dist/spaces/thread.js +285 -0
- package/dist/spaces/transport/http.d.ts +51 -0
- package/dist/spaces/transport/http.js +113 -0
- package/dist/spaces/transport/index.d.ts +19 -0
- package/dist/spaces/transport/index.js +19 -0
- package/dist/spaces/transport/session.d.ts +120 -0
- package/dist/spaces/transport/session.js +188 -0
- package/dist/spaces/transport/tauri.d.ts +50 -0
- package/dist/spaces/transport/tauri.js +90 -0
- package/dist/spaces/transport/types.d.ts +123 -0
- package/dist/spaces/transport/types.js +59 -0
- package/dist/teleport/controller.d.ts +34 -0
- package/dist/teleport/controller.js +107 -0
- package/dist/teleport/drop.d.ts +62 -0
- package/dist/teleport/drop.js +138 -0
- package/dist/teleport/dropZone.d.ts +59 -0
- package/dist/teleport/dropZone.js +166 -0
- package/dist/teleport/element.d.ts +28 -0
- package/dist/teleport/element.js +340 -0
- package/dist/teleport/index.d.ts +32 -0
- package/dist/teleport/index.js +32 -0
- package/dist/teleport/install.d.ts +31 -0
- package/dist/teleport/install.js +65 -0
- package/dist/teleport/model.d.ts +245 -0
- package/dist/teleport/model.js +334 -0
- package/dist/teleport/windowDrag.d.ts +86 -0
- package/dist/teleport/windowDrag.js +71 -0
- package/package.json +93 -0
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Framework-agnostic web components over the headless picker:
|
|
3
|
+
*
|
|
4
|
+
* - `<cua-teleport-picker>`: "Teleport an app…" (search, icons, recents,
|
|
5
|
+
* capability levels, the move choice, the consent screen listing every
|
|
6
|
+
* path and secret, progress). Set `.host` (a {@link TeleportHost}) and the
|
|
7
|
+
* `space-name` attribute; optional `.preselect` jumps to one app. Fires
|
|
8
|
+
* `cua-teleport-close` and `cua-teleport-done` (`detail`: the report).
|
|
9
|
+
* - `<cua-drop-zone>`: the one dashed zone for files, apps and windows
|
|
10
|
+
* (see `dropZone.ts`).
|
|
11
|
+
* - `<cua-teleport-drop>`: the drop zone shown while a window or app is
|
|
12
|
+
* dragged: the dragged window's preview, the app and its capability, the
|
|
13
|
+
* target Space. Set `.state` (a {@link WindowDropState}) and `space-name`.
|
|
14
|
+
*
|
|
15
|
+
* Call {@link defineTeleportElements} once in a browser or webview; nothing
|
|
16
|
+
* is registered on import, so Node can import this module.
|
|
17
|
+
*
|
|
18
|
+
* Theming: CSS custom properties `--cua-teleport-bg`, `-fg`, `-muted`,
|
|
19
|
+
* `-accent`, `-border`, `-danger`, `-radius`; light and dark defaults follow
|
|
20
|
+
* `prefers-color-scheme`.
|
|
21
|
+
*/
|
|
22
|
+
import { TeleportPickerController } from "./controller.js";
|
|
23
|
+
import { CAPABILITY_LABEL, MOVE_LABEL, canConfirm, canPlan, formatBytes, progress, sections, } from "./model.js";
|
|
24
|
+
import { defineDropZoneElement } from "./dropZone.js";
|
|
25
|
+
const esc = (s) => String(s ?? "").replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
26
|
+
export const TELEPORT_STYLES = `
|
|
27
|
+
:host { --bg: var(--cua-teleport-bg, #ffffff); --fg: var(--cua-teleport-fg, #16181d);
|
|
28
|
+
--muted: var(--cua-teleport-muted, #6b7280); --accent: var(--cua-teleport-accent, #2563eb);
|
|
29
|
+
--border: var(--cua-teleport-border, #e5e7eb); --danger: var(--cua-teleport-danger, #b91c1c);
|
|
30
|
+
--radius: var(--cua-teleport-radius, 10px); --row: color-mix(in srgb, var(--fg) 5%, var(--bg));
|
|
31
|
+
display: block; color: var(--fg); background: var(--bg); font: 13px/1.4 system-ui, -apple-system, sans-serif; }
|
|
32
|
+
@media (prefers-color-scheme: dark) { :host { --bg: var(--cua-teleport-bg, #1c1d21); --fg: var(--cua-teleport-fg, #eceef2);
|
|
33
|
+
--muted: var(--cua-teleport-muted, #9aa0aa); --accent: var(--cua-teleport-accent, #5b8def);
|
|
34
|
+
--border: var(--cua-teleport-border, #2e3036); --danger: var(--cua-teleport-danger, #f87171); } }
|
|
35
|
+
.panel { display: flex; flex-direction: column; gap: 12px; padding: 16px; min-height: 100%; box-sizing: border-box; }
|
|
36
|
+
h1 { font-size: 15px; margin: 0; font-weight: 600; }
|
|
37
|
+
.hint { color: var(--muted); margin: 0; }
|
|
38
|
+
input[type=search] { width: 100%; box-sizing: border-box; padding: 8px 10px; border-radius: 8px; border: 1px solid var(--border);
|
|
39
|
+
background: var(--bg); color: var(--fg); font: inherit; }
|
|
40
|
+
.list { display: flex; flex-direction: column; gap: 2px; overflow: auto; flex: 1; min-height: 120px; }
|
|
41
|
+
.section { color: var(--muted); font-size: 11px; text-transform: uppercase; letter-spacing: .04em; margin: 8px 4px 2px; }
|
|
42
|
+
.row { display: grid; grid-template-columns: 28px 1fr auto; gap: 10px; align-items: center; padding: 6px 8px;
|
|
43
|
+
border-radius: 8px; border: 1px solid transparent; background: none; color: inherit; font: inherit; text-align: left; cursor: pointer; }
|
|
44
|
+
.row[aria-selected=true] { background: var(--row); border-color: var(--accent); }
|
|
45
|
+
.row:disabled { cursor: default; opacity: .55; }
|
|
46
|
+
.icon { width: 28px; height: 28px; border-radius: 7px; object-fit: contain; }
|
|
47
|
+
.mono { width: 28px; height: 28px; border-radius: 7px; display: grid; place-items: center; background: var(--row); font-weight: 600; }
|
|
48
|
+
.name { font-weight: 500; } .sub { color: var(--muted); font-size: 12px; }
|
|
49
|
+
.badge { font-size: 11px; padding: 2px 8px; border-radius: 999px; border: 1px solid var(--border); color: var(--muted); white-space: nowrap; }
|
|
50
|
+
.badge.full { color: var(--accent); border-color: var(--accent); }
|
|
51
|
+
.choices { display: flex; flex-direction: column; gap: 6px; }
|
|
52
|
+
.choice { display: flex; gap: 8px; align-items: flex-start; padding: 8px; border: 1px solid var(--border); border-radius: 8px; }
|
|
53
|
+
.files { display: flex; flex-direction: column; gap: 4px; }
|
|
54
|
+
.file { display: flex; justify-content: space-between; gap: 8px; padding: 4px 8px; background: var(--row); border-radius: 6px; font-family: ui-monospace, monospace; font-size: 12px; }
|
|
55
|
+
.items { display: flex; flex-direction: column; gap: 4px; }
|
|
56
|
+
.item { display: grid; grid-template-columns: auto 1fr auto; gap: 8px; align-items: baseline; padding: 6px 8px; border-radius: 6px; background: var(--row); }
|
|
57
|
+
.item .k { font-size: 11px; color: var(--muted); text-transform: uppercase; }
|
|
58
|
+
.item.secret .k { color: var(--danger); }
|
|
59
|
+
.detail { color: var(--muted); font-size: 12px; word-break: break-all; }
|
|
60
|
+
.warn { color: var(--muted); font-size: 12px; margin: 0; }
|
|
61
|
+
.error { color: var(--danger); }
|
|
62
|
+
.bar { height: 6px; border-radius: 3px; background: var(--row); overflow: hidden; }
|
|
63
|
+
.bar > span { display: block; height: 100%; background: var(--accent); }
|
|
64
|
+
footer { display: flex; justify-content: flex-end; gap: 8px; margin-top: auto; }
|
|
65
|
+
button.btn { padding: 6px 14px; border-radius: 8px; border: 1px solid var(--border); background: var(--bg); color: var(--fg); font: inherit; cursor: pointer; }
|
|
66
|
+
a.btn { padding: 6px 14px; border-radius: 8px; border: 1px solid var(--border); font: inherit; text-decoration: none; }
|
|
67
|
+
a.primary, button.primary { background: var(--accent); border-color: var(--accent); color: #fff; }
|
|
68
|
+
button.btn:disabled { opacity: .5; cursor: default; }
|
|
69
|
+
.drop { display: grid; grid-template-columns: 96px 1fr; gap: 12px; align-items: center; padding: 12px; border-radius: var(--radius);
|
|
70
|
+
border: 2px dashed var(--border); }
|
|
71
|
+
.drop.over { border-color: var(--accent); }
|
|
72
|
+
.drop .thumb { width: 96px; height: 64px; border-radius: 6px; object-fit: cover; background: var(--row); }
|
|
73
|
+
`;
|
|
74
|
+
function monogram(name) {
|
|
75
|
+
return `<span class="mono" aria-hidden="true">${esc(name.charAt(0).toUpperCase() || "?")}</span>`;
|
|
76
|
+
}
|
|
77
|
+
function iconHtml(e, icons) {
|
|
78
|
+
const url = icons.get(e.id);
|
|
79
|
+
return url ? `<img class="icon" src="${esc(url)}" alt="">` : monogram(e.name);
|
|
80
|
+
}
|
|
81
|
+
export function renderPicker(c, icons) {
|
|
82
|
+
const s = c.state;
|
|
83
|
+
const space = esc(s.spaceName);
|
|
84
|
+
switch (s.step) {
|
|
85
|
+
case "loading":
|
|
86
|
+
return `<div class="panel"><h1>Teleport an app to ${space}</h1><p class="hint">Looking for apps on this machine…</p></div>`;
|
|
87
|
+
case "pick": {
|
|
88
|
+
const rows = sections(s)
|
|
89
|
+
.map((sec) => `<div class="section">${esc(sec.title)}</div>` +
|
|
90
|
+
sec.entries
|
|
91
|
+
.map((e) => {
|
|
92
|
+
const disabled = e.capability === "unsupported";
|
|
93
|
+
return `<button class="row" role="option" data-id="${esc(e.id)}" aria-selected="${e.id === s.selectedId}" ${disabled ? "disabled" : ""}
|
|
94
|
+
title="${esc(disabled ? (e.reason ?? "") : CAPABILITY_LABEL[e.capability])}">
|
|
95
|
+
${iconHtml(e, icons)}<span><span class="name">${esc(e.name)}</span>${disabled && e.reason ? `<br><span class="sub">${esc(e.reason)}</span>` : ""}</span>
|
|
96
|
+
<span class="badge ${esc(e.capability.replace("_", "-"))}">${esc(CAPABILITY_LABEL[e.capability])}</span></button>`;
|
|
97
|
+
})
|
|
98
|
+
.join(""))
|
|
99
|
+
.join("");
|
|
100
|
+
return `<div class="panel"><h1>Teleport an app to ${space}</h1>
|
|
101
|
+
<input type="search" placeholder="Search apps" aria-label="Search apps" value="${esc(s.query)}">
|
|
102
|
+
<div class="list" role="listbox" aria-label="Apps">${rows || `<p class="hint">No apps match.</p>`}</div>
|
|
103
|
+
<footer><button class="btn" data-act="close">Cancel</button>
|
|
104
|
+
<button class="btn primary" data-act="choose" ${s.selectedId ? "" : "disabled"}>Continue</button></footer></div>`;
|
|
105
|
+
}
|
|
106
|
+
case "options": {
|
|
107
|
+
const e = s.entry;
|
|
108
|
+
const choices = e.moves
|
|
109
|
+
.map((m) => `<label class="choice"><input type="radio" name="move" value="${m}" ${m === s.move ? "checked" : ""}>
|
|
110
|
+
<span>${esc(MOVE_LABEL[m])}</span></label>`)
|
|
111
|
+
.join("");
|
|
112
|
+
const files = s.move === "app_with_files"
|
|
113
|
+
? `<div class="files">${s.files
|
|
114
|
+
.map((f) => `<div class="file"><span>${esc(f)}</span><button class="btn" data-remove="${esc(f)}">Remove</button></div>`)
|
|
115
|
+
.join("")}${c.host.chooseFiles ? `<button class="btn" data-act="files">Add files or folders…</button>` : `<p class="hint">Drop files or folders here.</p>`}</div>`
|
|
116
|
+
: "";
|
|
117
|
+
return `<div class="panel"><h1>${iconHtml(e, icons)} ${esc(e.name)}</h1>
|
|
118
|
+
<p class="hint">What should move to ${space}?</p><div class="choices">${choices}</div>${files}
|
|
119
|
+
${e.reason ? `<p class="warn">${esc(e.reason)}</p>` : ""}
|
|
120
|
+
<footer><button class="btn" data-act="back">Back</button>
|
|
121
|
+
<button class="btn primary" data-act="plan" ${canPlan(s) ? "" : "disabled"}>Review</button></footer></div>`;
|
|
122
|
+
}
|
|
123
|
+
case "planning":
|
|
124
|
+
return `<div class="panel"><h1>${esc(s.entry?.name)}</h1><p class="hint">Working out what will move…</p></div>`;
|
|
125
|
+
case "consent": {
|
|
126
|
+
const p = s.plan;
|
|
127
|
+
const items = p.consent
|
|
128
|
+
.map((i) => `<div class="item ${i.sensitive ? "secret" : ""}"><span class="k">${esc(i.kind)}</span>
|
|
129
|
+
<span><span class="name">${esc(i.label)}</span><br><span class="detail">${esc(i.detail)}</span></span>
|
|
130
|
+
<span class="sub">${i.bytes ? esc(formatBytes(i.bytes)) : ""}</span></div>`)
|
|
131
|
+
.join("");
|
|
132
|
+
return `<div class="panel"><h1>Teleport ${esc(p.app.name)} to ${space}?</h1>
|
|
133
|
+
<p class="hint">${esc(p.steps.map((x) => x.summary).join(" · "))}</p>
|
|
134
|
+
<div class="items" role="list">${items}</div>
|
|
135
|
+
<p class="hint">${p.totalBytes ? `${esc(formatBytes(p.totalBytes))} leaves this machine.` : "No files or app data leave this machine."}</p>
|
|
136
|
+
${p.warnings.map((w) => `<p class="warn">${esc(w)}</p>`).join("")}
|
|
137
|
+
${p.sensitive ? `<label class="choice"><input type="checkbox" data-act="ack" ${s.acknowledged ? "checked" : ""}><span>I understand the secrets above leave this machine.</span></label>` : ""}
|
|
138
|
+
<footer><button class="btn" data-act="back">Back</button>
|
|
139
|
+
<button class="btn primary" data-act="confirm" ${canConfirm(s) ? "" : "disabled"}>Teleport</button></footer></div>`;
|
|
140
|
+
}
|
|
141
|
+
case "running": {
|
|
142
|
+
const last = s.events[s.events.length - 1];
|
|
143
|
+
return `<div class="panel"><h1>Teleporting ${esc(s.plan?.app.name)} to ${space}…</h1>
|
|
144
|
+
<div class="bar" role="progressbar" aria-valuenow="${Math.round(progress(s) * 100)}"><span style="width:${Math.round(progress(s) * 100)}%"></span></div>
|
|
145
|
+
<p class="hint">${esc(last ? `${last.kind}: ${last.detail}` : "Starting…")}</p></div>`;
|
|
146
|
+
}
|
|
147
|
+
case "done": {
|
|
148
|
+
const r = s.report;
|
|
149
|
+
return `<div class="panel"><h1>${esc(s.plan?.app.name)} is in ${space}</h1>
|
|
150
|
+
<p class="hint">${[
|
|
151
|
+
r.installed.length ? `Installed ${esc(r.installed.join(", "))}.` : "",
|
|
152
|
+
r.sent.length ? `Sent ${r.sent.length} item(s).` : "",
|
|
153
|
+
r.imported.length ? `Imported ${r.imported.length} item(s).` : "",
|
|
154
|
+
r.launched ? "Opened." : "",
|
|
155
|
+
].join(" ")}</p><footer><button class="btn primary" data-act="close">Done</button></footer></div>`;
|
|
156
|
+
}
|
|
157
|
+
case "error":
|
|
158
|
+
if (s.installPrompt) {
|
|
159
|
+
const p = s.installPrompt;
|
|
160
|
+
return `<div class="panel install-cua"><h1>${esc(p.title)}</h1>
|
|
161
|
+
<p class="hint">${esc(p.message)}</p>
|
|
162
|
+
<footer><button class="btn" data-act="back">Back</button><a class="btn primary" data-act="install-cua" href="${esc(p.url)}" target="_blank" rel="noopener noreferrer">${esc(p.actionLabel)}</a></footer></div>`;
|
|
163
|
+
}
|
|
164
|
+
return `<div class="panel"><h1>Could not teleport${s.entry ? ` ${esc(s.entry.name)}` : ""}</h1>
|
|
165
|
+
<p class="error" role="alert">${esc(s.error)}</p>
|
|
166
|
+
<footer><button class="btn" data-act="close">Close</button><button class="btn primary" data-act="back">Back</button></footer></div>`;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
export function renderDropZone(state, spaceName) {
|
|
170
|
+
if (!state.active || !state.app)
|
|
171
|
+
return "";
|
|
172
|
+
const thumb = state.thumbnail
|
|
173
|
+
? `<img class="thumb" src="${esc(state.thumbnail)}" alt="">`
|
|
174
|
+
: `<span class="thumb" aria-hidden="true"></span>`;
|
|
175
|
+
const title = state.window?.title ? ` · ${esc(state.window.title)}` : "";
|
|
176
|
+
return `<div class="drop ${state.overId ? "over" : ""}">${thumb}<div><div class="name">${esc(state.app.name)}${title}</div>
|
|
177
|
+
<div class="sub">${esc(CAPABILITY_LABEL[state.app.capability])}</div>
|
|
178
|
+
<div class="hint">${state.overId ? `Release to teleport to ${esc(spaceName)}` : `Drop on a Space to teleport`}</div></div></div>`;
|
|
179
|
+
}
|
|
180
|
+
/** Registers `<cua-teleport-picker>`, `<cua-teleport-drop>` and `<cua-drop-zone>` (idempotent). */
|
|
181
|
+
export function defineTeleportElements(registry = customElements) {
|
|
182
|
+
defineDropZoneElement(registry);
|
|
183
|
+
if (!registry.get("cua-teleport-picker")) {
|
|
184
|
+
class Picker extends HTMLElement {
|
|
185
|
+
#host = null;
|
|
186
|
+
#c = null;
|
|
187
|
+
#icons = new Map();
|
|
188
|
+
#unsub = null;
|
|
189
|
+
preselect = null;
|
|
190
|
+
set host(h) {
|
|
191
|
+
this.#host = h;
|
|
192
|
+
if (this.isConnected)
|
|
193
|
+
this.#start();
|
|
194
|
+
}
|
|
195
|
+
get host() {
|
|
196
|
+
return this.#host;
|
|
197
|
+
}
|
|
198
|
+
get controller() {
|
|
199
|
+
return this.#c;
|
|
200
|
+
}
|
|
201
|
+
#doneSent = false;
|
|
202
|
+
connectedCallback() {
|
|
203
|
+
if (!this.shadowRoot) {
|
|
204
|
+
const root = this.attachShadow({ mode: "open" });
|
|
205
|
+
root.addEventListener("click", this.#onClick);
|
|
206
|
+
root.addEventListener("input", this.#onInput);
|
|
207
|
+
root.addEventListener("change", this.#onChange);
|
|
208
|
+
root.addEventListener("dblclick", this.#onDbl);
|
|
209
|
+
}
|
|
210
|
+
if (this.#host)
|
|
211
|
+
this.#start();
|
|
212
|
+
}
|
|
213
|
+
disconnectedCallback() {
|
|
214
|
+
this.#unsub?.();
|
|
215
|
+
this.#unsub = null;
|
|
216
|
+
}
|
|
217
|
+
#start() {
|
|
218
|
+
if (!this.#host || !this.shadowRoot)
|
|
219
|
+
return;
|
|
220
|
+
this.#unsub?.();
|
|
221
|
+
const opts = {
|
|
222
|
+
spaceName: this.getAttribute("space-name") ?? "this Space",
|
|
223
|
+
};
|
|
224
|
+
if (this.preselect)
|
|
225
|
+
opts.preselect = this.preselect;
|
|
226
|
+
this.#c = new TeleportPickerController(this.#host, opts);
|
|
227
|
+
this.#unsub = this.#c.subscribe(() => this.#render());
|
|
228
|
+
this.#render();
|
|
229
|
+
this.#doneSent = false;
|
|
230
|
+
void this.#c.load();
|
|
231
|
+
}
|
|
232
|
+
#render() {
|
|
233
|
+
const c = this.#c;
|
|
234
|
+
if (!c || !this.shadowRoot)
|
|
235
|
+
return;
|
|
236
|
+
const focusSearch = this.shadowRoot.activeElement?.matches("input[type=search]");
|
|
237
|
+
this.shadowRoot.innerHTML = `<style>${TELEPORT_STYLES}</style>${renderPicker(c, this.#icons)}`;
|
|
238
|
+
if (focusSearch || c.state.step === "pick") {
|
|
239
|
+
const input = this.shadowRoot.querySelector("input[type=search]");
|
|
240
|
+
if (input && focusSearch) {
|
|
241
|
+
input.focus();
|
|
242
|
+
input.setSelectionRange(input.value.length, input.value.length);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
const host = this.#host;
|
|
246
|
+
if (host?.icon && c.state.entries) {
|
|
247
|
+
for (const e of c.state.entries) {
|
|
248
|
+
if (this.#icons.has(e.id))
|
|
249
|
+
continue;
|
|
250
|
+
this.#icons.set(e.id, null);
|
|
251
|
+
void host.icon(e).then((url) => {
|
|
252
|
+
if (!url)
|
|
253
|
+
return;
|
|
254
|
+
this.#icons.set(e.id, url);
|
|
255
|
+
this.#render();
|
|
256
|
+
}, () => { });
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
if (c.state.step === "done" && !this.#doneSent) {
|
|
260
|
+
this.#doneSent = true;
|
|
261
|
+
this.dispatchEvent(new CustomEvent("cua-teleport-done", { detail: c.state.report, bubbles: true, composed: true }));
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
#onClick = (ev) => {
|
|
265
|
+
const c = this.#c;
|
|
266
|
+
const t = ev.target;
|
|
267
|
+
if (!c)
|
|
268
|
+
return;
|
|
269
|
+
const row = t.closest("[data-id]");
|
|
270
|
+
if (row)
|
|
271
|
+
c.dispatch({ type: "select", id: row.dataset.id });
|
|
272
|
+
const remove = t.closest("[data-remove]");
|
|
273
|
+
if (remove)
|
|
274
|
+
c.dispatch({ type: "remove-file", path: remove.dataset.remove });
|
|
275
|
+
switch (t.closest("[data-act]")?.dataset.act) {
|
|
276
|
+
case "close":
|
|
277
|
+
this.dispatchEvent(new CustomEvent("cua-teleport-close", { bubbles: true, composed: true }));
|
|
278
|
+
break;
|
|
279
|
+
case "choose":
|
|
280
|
+
c.choose();
|
|
281
|
+
break;
|
|
282
|
+
case "back":
|
|
283
|
+
c.dispatch({ type: "back" });
|
|
284
|
+
break;
|
|
285
|
+
case "files":
|
|
286
|
+
void c.chooseFiles();
|
|
287
|
+
break;
|
|
288
|
+
case "plan":
|
|
289
|
+
void c.plan();
|
|
290
|
+
break;
|
|
291
|
+
case "confirm":
|
|
292
|
+
void c.confirm();
|
|
293
|
+
break;
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
#onDbl = (ev) => {
|
|
297
|
+
const row = ev.target.closest("[data-id]");
|
|
298
|
+
if (row && this.#c)
|
|
299
|
+
this.#c.choose(row.dataset.id);
|
|
300
|
+
};
|
|
301
|
+
#onInput = (ev) => {
|
|
302
|
+
const t = ev.target;
|
|
303
|
+
if (t.type === "search")
|
|
304
|
+
this.#c?.dispatch({ type: "query", query: t.value });
|
|
305
|
+
};
|
|
306
|
+
#onChange = (ev) => {
|
|
307
|
+
const t = ev.target;
|
|
308
|
+
if (t.name === "move")
|
|
309
|
+
this.#c?.dispatch({ type: "move", move: t.value });
|
|
310
|
+
if (t.dataset.act === "ack")
|
|
311
|
+
this.#c?.dispatch({ type: "acknowledge", value: t.checked });
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
registry.define("cua-teleport-picker", Picker);
|
|
315
|
+
}
|
|
316
|
+
if (!registry.get("cua-teleport-drop")) {
|
|
317
|
+
class Drop extends HTMLElement {
|
|
318
|
+
#state = null;
|
|
319
|
+
set state(s) {
|
|
320
|
+
this.#state = s;
|
|
321
|
+
this.#render();
|
|
322
|
+
}
|
|
323
|
+
get state() {
|
|
324
|
+
return this.#state;
|
|
325
|
+
}
|
|
326
|
+
connectedCallback() {
|
|
327
|
+
if (!this.shadowRoot)
|
|
328
|
+
this.attachShadow({ mode: "open" });
|
|
329
|
+
this.#render();
|
|
330
|
+
}
|
|
331
|
+
#render() {
|
|
332
|
+
if (!this.shadowRoot)
|
|
333
|
+
return;
|
|
334
|
+
const html = this.#state ? renderDropZone(this.#state, this.getAttribute("space-name") ?? "") : "";
|
|
335
|
+
this.shadowRoot.innerHTML = html ? `<style>${TELEPORT_STYLES}</style>${html}` : "";
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
registry.define("cua-teleport-drop", Drop);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@trycua/cua/teleport`: the shared "Teleport an app…" and drag-and-drop
|
|
3
|
+
* UX, headless and framework-agnostic. Browser, webview and Node safe (no
|
|
4
|
+
* native library): the Spaces app, the OpenKoalaBots examples and any
|
|
5
|
+
* client render the same flow over a {@link TeleportHost}.
|
|
6
|
+
*
|
|
7
|
+
* ```ts
|
|
8
|
+
* import { TeleportPickerController, defineTeleportElements } from "@trycua/cua/teleport"
|
|
9
|
+
*
|
|
10
|
+
* // Any framework: the controller is the state machine.
|
|
11
|
+
* const c = new TeleportPickerController(host, { spaceName: "dev" })
|
|
12
|
+
* c.subscribe(() => render(c.state))
|
|
13
|
+
* await c.load()
|
|
14
|
+
*
|
|
15
|
+
* // Or the web component.
|
|
16
|
+
* defineTeleportElements()
|
|
17
|
+
* const el = document.createElement("cua-teleport-picker")
|
|
18
|
+
* el.setAttribute("space-name", "dev")
|
|
19
|
+
* el.host = host
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* A host implements {@link TeleportHost}: the Cua Spaces apps back it with
|
|
23
|
+
* their own teleport (host-side app teleport ships with Cua Spaces,
|
|
24
|
+
* source-available), a test with a fake.
|
|
25
|
+
*/
|
|
26
|
+
export * from "./model.js";
|
|
27
|
+
export * from "./install.js";
|
|
28
|
+
export * from "./drop.js";
|
|
29
|
+
export * from "./windowDrag.js";
|
|
30
|
+
export * from "./dropZone.js";
|
|
31
|
+
export { TeleportPickerController, type ControllerOptions } from "./controller.js";
|
|
32
|
+
export { defineTeleportElements, renderDropZone, renderPicker, TELEPORT_STYLES } from "./element.js";
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@trycua/cua/teleport`: the shared "Teleport an app…" and drag-and-drop
|
|
3
|
+
* UX, headless and framework-agnostic. Browser, webview and Node safe (no
|
|
4
|
+
* native library): the Spaces app, the OpenKoalaBots examples and any
|
|
5
|
+
* client render the same flow over a {@link TeleportHost}.
|
|
6
|
+
*
|
|
7
|
+
* ```ts
|
|
8
|
+
* import { TeleportPickerController, defineTeleportElements } from "@trycua/cua/teleport"
|
|
9
|
+
*
|
|
10
|
+
* // Any framework: the controller is the state machine.
|
|
11
|
+
* const c = new TeleportPickerController(host, { spaceName: "dev" })
|
|
12
|
+
* c.subscribe(() => render(c.state))
|
|
13
|
+
* await c.load()
|
|
14
|
+
*
|
|
15
|
+
* // Or the web component.
|
|
16
|
+
* defineTeleportElements()
|
|
17
|
+
* const el = document.createElement("cua-teleport-picker")
|
|
18
|
+
* el.setAttribute("space-name", "dev")
|
|
19
|
+
* el.host = host
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* A host implements {@link TeleportHost}: the Cua Spaces apps back it with
|
|
23
|
+
* their own teleport (host-side app teleport ships with Cua Spaces,
|
|
24
|
+
* source-available), a test with a fake.
|
|
25
|
+
*/
|
|
26
|
+
export * from "./model.js";
|
|
27
|
+
export * from "./install.js";
|
|
28
|
+
export * from "./drop.js";
|
|
29
|
+
export * from "./windowDrag.js";
|
|
30
|
+
export * from "./dropZone.js";
|
|
31
|
+
export { TeleportPickerController } from "./controller.js";
|
|
32
|
+
export { defineTeleportElements, renderDropZone, renderPicker, TELEPORT_STYLES } from "./element.js";
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The "Install Cua" affordance for a teleport the Keyvault refused.
|
|
3
|
+
*
|
|
4
|
+
* Moving a signed-in session goes through the Cua Keyvault, which only the
|
|
5
|
+
* installed, signed Cua app hosts. An app that embeds the SDK is refused by
|
|
6
|
+
* design with the `requires_cua_app` code; this turns that refusal into a
|
|
7
|
+
* prompt a UI can show (title, one line, an install button) instead of the
|
|
8
|
+
* raw error. The refusal itself is unchanged: nothing here retries or
|
|
9
|
+
* bypasses it.
|
|
10
|
+
*/
|
|
11
|
+
/** Where people install Cua (the CLI and the Cua app). */
|
|
12
|
+
export declare const CUA_INSTALL_URL = "https://cua.ai/install";
|
|
13
|
+
/** Opens the Keyvault page of an installed Cua app. */
|
|
14
|
+
export declare const CUA_OPEN_URL = "cua://keyvault";
|
|
15
|
+
export interface InstallCuaPrompt {
|
|
16
|
+
/** Cua is installed but not running (open it instead of installing). */
|
|
17
|
+
installed: boolean;
|
|
18
|
+
title: string;
|
|
19
|
+
message: string;
|
|
20
|
+
/** The button label. */
|
|
21
|
+
actionLabel: string;
|
|
22
|
+
/** The button target: always one of the constants above, never a URL
|
|
23
|
+
* taken from the error, so a remote message cannot supply the link. */
|
|
24
|
+
url: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The prompt when `error` (an `Error`, a message, or a tool result such as
|
|
28
|
+
* `{"error":{"code":"requires_cua_app"}}`) is the Keyvault's "needs the Cua
|
|
29
|
+
* app" refusal; `null` for any other failure.
|
|
30
|
+
*/
|
|
31
|
+
export declare function requiresCuaApp(error: unknown): InstallCuaPrompt | null;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The "Install Cua" affordance for a teleport the Keyvault refused.
|
|
3
|
+
*
|
|
4
|
+
* Moving a signed-in session goes through the Cua Keyvault, which only the
|
|
5
|
+
* installed, signed Cua app hosts. An app that embeds the SDK is refused by
|
|
6
|
+
* design with the `requires_cua_app` code; this turns that refusal into a
|
|
7
|
+
* prompt a UI can show (title, one line, an install button) instead of the
|
|
8
|
+
* raw error. The refusal itself is unchanged: nothing here retries or
|
|
9
|
+
* bypasses it.
|
|
10
|
+
*/
|
|
11
|
+
/** Where people install Cua (the CLI and the Cua app). */
|
|
12
|
+
export const CUA_INSTALL_URL = "https://cua.ai/install";
|
|
13
|
+
/** Opens the Keyvault page of an installed Cua app. */
|
|
14
|
+
export const CUA_OPEN_URL = "cua://keyvault";
|
|
15
|
+
const CODE = /\brequires_cua_app\b|\bRequiresCuaApp\b/;
|
|
16
|
+
const NOT_RUNNING = /installed but not running|needs the Cua app running/i;
|
|
17
|
+
function texts(error, depth = 0) {
|
|
18
|
+
if (error == null || depth > 3)
|
|
19
|
+
return [];
|
|
20
|
+
if (typeof error === "string")
|
|
21
|
+
return [error];
|
|
22
|
+
if (typeof error !== "object")
|
|
23
|
+
return [String(error)];
|
|
24
|
+
const o = error;
|
|
25
|
+
const out = [];
|
|
26
|
+
for (const key of ["code", "kind", "tag", "message", "detail"]) {
|
|
27
|
+
if (typeof o[key] === "string")
|
|
28
|
+
out.push(o[key]);
|
|
29
|
+
}
|
|
30
|
+
if (o.error !== undefined)
|
|
31
|
+
out.push(...texts(o.error, depth + 1));
|
|
32
|
+
if (o.cause !== undefined)
|
|
33
|
+
out.push(...texts(o.cause, depth + 1));
|
|
34
|
+
if (typeof o.installed === "boolean" && typeof o.open_url === "string" && o.open_url === CUA_OPEN_URL) {
|
|
35
|
+
out.push("requires_cua_app");
|
|
36
|
+
}
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The prompt when `error` (an `Error`, a message, or a tool result such as
|
|
41
|
+
* `{"error":{"code":"requires_cua_app"}}`) is the Keyvault's "needs the Cua
|
|
42
|
+
* app" refusal; `null` for any other failure.
|
|
43
|
+
*/
|
|
44
|
+
export function requiresCuaApp(error) {
|
|
45
|
+
const all = texts(error);
|
|
46
|
+
if (!all.some((t) => CODE.test(t)))
|
|
47
|
+
return null;
|
|
48
|
+
const installed = all.some((t) => NOT_RUNNING.test(t)) ||
|
|
49
|
+
(typeof error === "object" && error !== null && error.installed === true);
|
|
50
|
+
return installed
|
|
51
|
+
? {
|
|
52
|
+
installed: true,
|
|
53
|
+
title: "Open Cua to teleport your session",
|
|
54
|
+
message: "The Cua app keeps your logins in its Keyvault and asks you before sharing them. Open it, then try again.",
|
|
55
|
+
actionLabel: "Open Cua",
|
|
56
|
+
url: CUA_OPEN_URL,
|
|
57
|
+
}
|
|
58
|
+
: {
|
|
59
|
+
installed: false,
|
|
60
|
+
title: "Install Cua to teleport your session",
|
|
61
|
+
message: "The Cua app keeps your logins in its Keyvault and asks you before sharing them.",
|
|
62
|
+
actionLabel: "Install Cua",
|
|
63
|
+
url: CUA_INSTALL_URL,
|
|
64
|
+
};
|
|
65
|
+
}
|