@rightkit/agent-ui-settings 0.0.0-stage → 0.1.1
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/CHANGELOG.md +10 -0
- package/LICENSE-APACHE +201 -0
- package/LICENSE-MIT +21 -0
- package/README.md +44 -2
- package/dist/appName.d.ts +1 -0
- package/dist/appName.js +1 -0
- package/dist/components/PluginContributionHost.d.ts +17 -0
- package/dist/components/PluginContributionHost.js +23 -0
- package/dist/components/ReadinessCard.d.ts +4 -0
- package/dist/components/ReadinessCard.js +19 -0
- package/dist/components/remote/RemoteDevicesPanel.d.ts +7 -0
- package/dist/components/remote/RemoteDevicesPanel.js +153 -0
- package/dist/components/remote/RemoteHostsPanel.d.ts +8 -0
- package/dist/components/remote/RemoteHostsPanel.js +139 -0
- package/dist/components/settings/analytics/AnalyticsConsentCard.d.ts +14 -0
- package/dist/components/settings/analytics/AnalyticsConsentCard.js +22 -0
- package/dist/components/settings/cliUpdate/CliUpdatePanel.d.ts +9 -0
- package/dist/components/settings/cliUpdate/CliUpdatePanel.js +18 -0
- package/dist/components/settings/helpers.d.ts +7 -0
- package/dist/components/settings/helpers.js +40 -0
- package/dist/components/settings/keyboard/KeyboardShortcutsSettings.d.ts +12 -0
- package/dist/components/settings/keyboard/KeyboardShortcutsSettings.js +75 -0
- package/dist/components/settings/keyboard/ShortcutRecordingDialog.d.ts +27 -0
- package/dist/components/settings/keyboard/ShortcutRecordingDialog.js +154 -0
- package/dist/components/settings/marketplace/McpDiscoverPanel.d.ts +34 -0
- package/dist/components/settings/marketplace/McpDiscoverPanel.js +66 -0
- package/dist/components/settings/marketplace/SkillMarketplace.d.ts +22 -0
- package/dist/components/settings/marketplace/SkillMarketplace.js +51 -0
- package/dist/components/settings/tabs/ApiKeyAccounts.d.ts +9 -0
- package/dist/components/settings/tabs/ApiKeyAccounts.js +67 -0
- package/dist/components/settings/tabs/AppearanceTab.d.ts +1 -0
- package/dist/components/settings/tabs/AppearanceTab.js +31 -0
- package/dist/components/settings/tabs/AppleSyncPanel.d.ts +14 -0
- package/dist/components/settings/tabs/AppleSyncPanel.js +65 -0
- package/dist/components/settings/tabs/ChildProviderPolicy.d.ts +9 -0
- package/dist/components/settings/tabs/ChildProviderPolicy.js +84 -0
- package/dist/components/settings/tabs/ConductorsTab.d.ts +6 -0
- package/dist/components/settings/tabs/ConductorsTab.js +135 -0
- package/dist/components/settings/tabs/DiagnosticsTab.d.ts +13 -0
- package/dist/components/settings/tabs/DiagnosticsTab.js +39 -0
- package/dist/components/settings/tabs/ExecutionBackendsTab.d.ts +29 -0
- package/dist/components/settings/tabs/ExecutionBackendsTab.js +13 -0
- package/dist/components/settings/tabs/LicenseTab.d.ts +20 -0
- package/dist/components/settings/tabs/LicenseTab.js +66 -0
- package/dist/components/settings/tabs/LinkComputerPanel.d.ts +6 -0
- package/dist/components/settings/tabs/LinkComputerPanel.js +39 -0
- package/dist/components/settings/tabs/LspTab.d.ts +11 -0
- package/dist/components/settings/tabs/LspTab.js +34 -0
- package/dist/components/settings/tabs/MemoryTab.d.ts +7 -0
- package/dist/components/settings/tabs/MemoryTab.js +68 -0
- package/dist/components/settings/tabs/ProvidersTab.d.ts +10 -0
- package/dist/components/settings/tabs/ProvidersTab.js +214 -0
- package/dist/components/settings/tabs/RepairTab.d.ts +6 -0
- package/dist/components/settings/tabs/RepairTab.js +44 -0
- package/dist/components/settings/tabs/RootModelPicker.d.ts +10 -0
- package/dist/components/settings/tabs/RootModelPicker.js +42 -0
- package/dist/components/settings/tabs/RuntimeMiddlewareTab.d.ts +8 -0
- package/dist/components/settings/tabs/RuntimeMiddlewareTab.js +12 -0
- package/dist/components/settings/tabs/WorkspaceTab.d.ts +6 -0
- package/dist/components/settings/tabs/WorkspaceTab.js +9 -0
- package/dist/components/settings/tabs/execution/CoreSandboxTierPanel.d.ts +9 -0
- package/dist/components/settings/tabs/execution/CoreSandboxTierPanel.js +43 -0
- package/dist/components/settings/tabs/execution/ExecutionBackendCard.d.ts +20 -0
- package/dist/components/settings/tabs/execution/ExecutionBackendCard.js +48 -0
- package/dist/components/settings/tabs/execution/ExecutionBackendLimitsForm.d.ts +10 -0
- package/dist/components/settings/tabs/execution/ExecutionBackendLimitsForm.js +62 -0
- package/dist/components/settings/tabs/execution/ExecutionBackendList.d.ts +19 -0
- package/dist/components/settings/tabs/execution/ExecutionBackendList.js +8 -0
- package/dist/components/settings/tabs/execution/ExecutionNetworkProfilesPanel.d.ts +16 -0
- package/dist/components/settings/tabs/execution/ExecutionNetworkProfilesPanel.js +53 -0
- package/dist/components/settings/tabs/execution/RemoteStatusCard.d.ts +2 -0
- package/dist/components/settings/tabs/execution/RemoteStatusCard.js +5 -0
- package/dist/components/settings/tabs/execution/TextSettingInput.d.ts +15 -0
- package/dist/components/settings/tabs/execution/TextSettingInput.js +10 -0
- package/dist/components/settings/tabs/execution/types.d.ts +27 -0
- package/dist/components/settings/tabs/execution/types.js +1 -0
- package/dist/components/settings/tabs/models/ModelEmptyStates.d.ts +12 -0
- package/dist/components/settings/tabs/models/ModelEmptyStates.js +63 -0
- package/dist/components/settings/tabs/permissions/PersistentPermissionRuleList.d.ts +7 -0
- package/dist/components/settings/tabs/permissions/PersistentPermissionRuleList.js +15 -0
- package/dist/components/settings/tabs/plugins/InstalledPluginsList.d.ts +6 -0
- package/dist/components/settings/tabs/plugins/InstalledPluginsList.js +38 -0
- package/dist/components/settings/tabs/remote/RemoteChannelsPanel.d.ts +4 -0
- package/dist/components/settings/tabs/remote/RemoteChannelsPanel.js +14 -0
- package/dist/components/settings/tabs/remote/RemoteStatusCard.d.ts +8 -0
- package/dist/components/settings/tabs/remote/RemoteStatusCard.js +6 -0
- package/dist/components/settings/tabs/remote/TelegramStatusPanel.d.ts +6 -0
- package/dist/components/settings/tabs/remote/TelegramStatusPanel.js +41 -0
- package/dist/components/settings/tabs/shared.d.ts +18 -0
- package/dist/components/settings/tabs/shared.js +36 -0
- package/dist/components/settings/tabs/skills/SkillShared.d.ts +20 -0
- package/dist/components/settings/tabs/skills/SkillShared.js +28 -0
- package/dist/components/settings/tabs/skills/SkillTrainingRunList.d.ts +6 -0
- package/dist/components/settings/tabs/skills/SkillTrainingRunList.js +11 -0
- package/dist/components/settings/tools/ManagedToolHealthPanel.d.ts +16 -0
- package/dist/components/settings/tools/ManagedToolHealthPanel.js +24 -0
- package/dist/env.d.ts +59 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.js +33 -0
- package/package.json +165 -3
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
3
|
+
import { parseRemoteCandidates, remoteClient, remoteRelativeTime, shortRemoteFingerprint, } from "@rightkit/agent-ui-core/lib/remote/remoteClient";
|
|
4
|
+
import { hostAppName } from "../../appName.js";
|
|
5
|
+
function describeProjects(projects) {
|
|
6
|
+
if (Array.isArray(projects))
|
|
7
|
+
return `${projects.length} project${projects.length === 1 ? "" : "s"}`;
|
|
8
|
+
if (projects && typeof projects === "object")
|
|
9
|
+
return `${Object.keys(projects).length} project fields`;
|
|
10
|
+
return "project state returned";
|
|
11
|
+
}
|
|
12
|
+
export default function RemoteHostsPanel({ open = true, client = remoteClient, }) {
|
|
13
|
+
const [hosts, setHosts] = useState([]);
|
|
14
|
+
const [error, setError] = useState(null);
|
|
15
|
+
const [loading, setLoading] = useState(false);
|
|
16
|
+
const [actionId, setActionId] = useState(null);
|
|
17
|
+
const [reachability, setReachability] = useState({});
|
|
18
|
+
const [editingId, setEditingId] = useState(null);
|
|
19
|
+
const [name, setName] = useState("");
|
|
20
|
+
const [host, setHost] = useState("");
|
|
21
|
+
const [fingerprint, setFingerprint] = useState("");
|
|
22
|
+
const [code, setCode] = useState("");
|
|
23
|
+
const [candidates, setCandidates] = useState("");
|
|
24
|
+
const alive = useRef(true);
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
alive.current = true;
|
|
27
|
+
return () => {
|
|
28
|
+
alive.current = false;
|
|
29
|
+
};
|
|
30
|
+
}, []);
|
|
31
|
+
const refresh = useCallback(async () => {
|
|
32
|
+
setLoading(true);
|
|
33
|
+
try {
|
|
34
|
+
const nextHosts = await client.remoteHostsList();
|
|
35
|
+
if (!alive.current)
|
|
36
|
+
return;
|
|
37
|
+
setHosts(Array.isArray(nextHosts) ? nextHosts : []);
|
|
38
|
+
setError(null);
|
|
39
|
+
}
|
|
40
|
+
catch (reason) {
|
|
41
|
+
if (alive.current)
|
|
42
|
+
setError(reason instanceof Error ? reason.message : String(reason));
|
|
43
|
+
}
|
|
44
|
+
finally {
|
|
45
|
+
if (alive.current)
|
|
46
|
+
setLoading(false);
|
|
47
|
+
}
|
|
48
|
+
}, [client]);
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
if (!open)
|
|
51
|
+
return;
|
|
52
|
+
void refresh();
|
|
53
|
+
const timer = window.setInterval(() => void refresh(), 5000);
|
|
54
|
+
return () => window.clearInterval(timer);
|
|
55
|
+
}, [open, refresh]);
|
|
56
|
+
const runAction = useCallback(async (id, operation) => {
|
|
57
|
+
setActionId(id);
|
|
58
|
+
try {
|
|
59
|
+
await operation();
|
|
60
|
+
await refresh();
|
|
61
|
+
}
|
|
62
|
+
catch (reason) {
|
|
63
|
+
setError(reason instanceof Error ? reason.message : String(reason));
|
|
64
|
+
}
|
|
65
|
+
finally {
|
|
66
|
+
if (alive.current)
|
|
67
|
+
setActionId(null);
|
|
68
|
+
}
|
|
69
|
+
}, [refresh]);
|
|
70
|
+
const pairHost = useCallback(async () => {
|
|
71
|
+
const trimmedHost = host.trim();
|
|
72
|
+
const trimmedFingerprint = fingerprint.trim();
|
|
73
|
+
const trimmedCode = code.trim();
|
|
74
|
+
if (!trimmedHost || !trimmedFingerprint || !trimmedCode) {
|
|
75
|
+
setError("Host, fingerprint, and pairing code are required.");
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
await runAction("pair", async () => {
|
|
79
|
+
await client.remoteHostPair({
|
|
80
|
+
host: trimmedHost,
|
|
81
|
+
fingerprint: trimmedFingerprint,
|
|
82
|
+
code: trimmedCode,
|
|
83
|
+
candidates: parseRemoteCandidates(candidates),
|
|
84
|
+
});
|
|
85
|
+
setHost("");
|
|
86
|
+
setFingerprint("");
|
|
87
|
+
setCode("");
|
|
88
|
+
setCandidates("");
|
|
89
|
+
});
|
|
90
|
+
}, [candidates, client, code, fingerprint, host, runAction]);
|
|
91
|
+
const renameHost = useCallback(async (id) => {
|
|
92
|
+
const trimmed = name.trim();
|
|
93
|
+
if (!trimmed)
|
|
94
|
+
return;
|
|
95
|
+
await runAction(`rename:${id}`, async () => {
|
|
96
|
+
await client.remoteHostRename(id, trimmed);
|
|
97
|
+
setEditingId(null);
|
|
98
|
+
setName("");
|
|
99
|
+
});
|
|
100
|
+
}, [client, name, runAction]);
|
|
101
|
+
const checkReachability = useCallback(async (id) => {
|
|
102
|
+
setActionId(`check:${id}`);
|
|
103
|
+
try {
|
|
104
|
+
const snapshot = await client.remoteHostProjectSnapshot(id);
|
|
105
|
+
if (alive.current) {
|
|
106
|
+
setReachability((current) => ({
|
|
107
|
+
...current,
|
|
108
|
+
[id]: { state: "reachable", detail: describeProjects(snapshot.projects) },
|
|
109
|
+
}));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
catch (reason) {
|
|
113
|
+
if (alive.current) {
|
|
114
|
+
setReachability((current) => ({
|
|
115
|
+
...current,
|
|
116
|
+
[id]: {
|
|
117
|
+
state: "unreachable",
|
|
118
|
+
detail: reason instanceof Error ? reason.message : String(reason),
|
|
119
|
+
},
|
|
120
|
+
}));
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
finally {
|
|
124
|
+
if (alive.current)
|
|
125
|
+
setActionId(null);
|
|
126
|
+
}
|
|
127
|
+
}, [client]);
|
|
128
|
+
return (_jsxs("section", { "aria-label": "Remote hosts", className: "grid gap-3 rounded-xl border border-line bg-panel p-4", "data-testid": "remote-hosts-panel", children: [_jsxs("header", { className: "flex flex-wrap items-start justify-between gap-3", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("h2", { className: "text-sm font-semibold text-text", children: "Remote hosts" }), _jsxs("p", { className: "mt-0.5 text-[12px] text-text-3", children: ["Pair another ", hostAppName(), " desktop and inspect its project reachability. Host tokens remain in the native store."] })] }), _jsx("button", { type: "button", "aria-label": "Refresh remote hosts", disabled: loading, onClick: () => void refresh(), className: "h-8 rounded-lg border border-line bg-bg px-3 text-[12px] text-text-2 hover:text-text disabled:opacity-40", children: loading ? "Refreshing…" : "Refresh" })] }), error ? (_jsxs("p", { role: "alert", className: "rounded-lg border border-danger/40 bg-danger/10 p-2 text-[12px] text-danger", children: ["Remote host service unavailable: ", error] })) : null, _jsxs("form", { className: "grid gap-2 rounded-lg border border-line bg-bg p-3", onSubmit: (event) => {
|
|
129
|
+
event.preventDefault();
|
|
130
|
+
void pairHost();
|
|
131
|
+
}, "data-testid": "remote-host-pair-form", children: [_jsxs("div", { children: [_jsx("h3", { className: "text-[12px] font-semibold text-text", children: "Pair a desktop host" }), _jsx("p", { className: "mt-0.5 text-[11px] text-text-3", children: "Start pairing on the other host, then enter its address, fingerprint, and code." })] }), _jsxs("div", { className: "grid gap-2 sm:grid-cols-2", children: [_jsx("input", { "aria-label": "Remote host address", placeholder: "Host address", value: host, onChange: (event) => setHost(event.target.value), className: "h-8 rounded-lg border border-line bg-panel px-2 text-[12px] text-text" }), _jsx("input", { "aria-label": "Remote host fingerprint", placeholder: "Certificate fingerprint", value: fingerprint, onChange: (event) => setFingerprint(event.target.value), className: "h-8 rounded-lg border border-line bg-panel px-2 font-mono text-[11px] text-text" }), _jsx("input", { "aria-label": "Remote host pairing code", inputMode: "numeric", placeholder: "8-digit pairing code", value: code, onChange: (event) => setCode(event.target.value), className: "h-8 rounded-lg border border-line bg-panel px-2 font-mono text-[12px] text-text" }), _jsx("input", { "aria-label": "Remote host candidate addresses", placeholder: "Other addresses (comma-separated)", value: candidates, onChange: (event) => setCandidates(event.target.value), className: "h-8 rounded-lg border border-line bg-panel px-2 text-[12px] text-text" })] }), _jsx("button", { type: "submit", disabled: actionId === "pair", className: "h-8 justify-self-start rounded-lg border border-accent bg-[color-mix(in_srgb,var(--color-accent)_10%,transparent)] px-3 text-[12px] font-semibold text-text disabled:opacity-40", children: actionId === "pair" ? "Pairing…" : "Pair host" })] }), hosts.length === 0 ? (_jsx("p", { className: "rounded-lg border border-line bg-bg px-3 py-2 text-[12px] text-text-3", children: "No paired desktop hosts." })) : (_jsx("ul", { className: "grid gap-2", "data-testid": "remote-host-list", children: hosts.map((remoteHost) => {
|
|
132
|
+
const status = reachability[remoteHost.id];
|
|
133
|
+
return (_jsxs("li", { "data-testid": `remote-host-${remoteHost.id}`, className: "grid gap-2 rounded-lg border border-line bg-bg p-3", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx("span", { className: "font-semibold text-[12px] text-text", children: remoteHost.displayName }), _jsx("span", { className: "text-[11px] text-text-3", children: remoteHost.hostname }), status ? (_jsx("span", { className: status.state === "reachable" ? "text-accent-2" : "text-danger", children: status.state })) : (_jsx("span", { className: "text-[11px] text-text-3", children: "not checked" }))] }), _jsxs("div", { className: "grid gap-1 text-[11px] text-text-3", children: [_jsxs("code", { className: "font-mono", title: remoteHost.fingerprint, children: ["cert ", shortRemoteFingerprint(remoteHost.fingerprint)] }), _jsx("span", { children: remoteHost.candidates.length ? remoteHost.candidates.join(" · ") : "No address candidates" }), _jsxs("span", { children: ["last seen ", remoteRelativeTime(remoteHost.lastSeenAt)] }), status?.detail ? _jsx("span", { className: "break-words", children: status.detail }) : null] }), editingId === remoteHost.id ? (_jsxs("div", { className: "flex flex-wrap gap-2", children: [_jsx("input", { type: "text", "aria-label": `New name for ${remoteHost.displayName}`, value: name, onChange: (event) => setName(event.target.value), className: "h-8 min-w-[16ch] flex-1 rounded-lg border border-line bg-panel px-2 text-[12px] text-text" }), _jsx("button", { type: "button", disabled: !name.trim() || actionId === `rename:${remoteHost.id}`, onClick: () => void renameHost(remoteHost.id), className: "h-8 rounded-lg border border-accent px-3 text-[12px] text-text disabled:opacity-40", children: "Save name" }), _jsx("button", { type: "button", onClick: () => setEditingId(null), className: "h-8 rounded-lg border border-line px-3 text-[12px] text-text-2", children: "Cancel" })] })) : (_jsxs("div", { className: "flex flex-wrap gap-2", children: [_jsx("button", { type: "button", disabled: actionId === `check:${remoteHost.id}`, onClick: () => void checkReachability(remoteHost.id), className: "h-7 rounded-md border border-accent bg-panel px-2 text-[11px] text-text disabled:opacity-40", children: actionId === `check:${remoteHost.id}` ? "Checking…" : "Check reachability" }), _jsx("button", { type: "button", disabled: actionId === `reconnect:${remoteHost.id}`, onClick: () => void runAction(`reconnect:${remoteHost.id}`, () => client.remoteHostReconnect(remoteHost.id)), className: "h-7 rounded-md border border-line bg-panel px-2 text-[11px] text-text-2 hover:text-text disabled:opacity-40", children: "Reconnect" }), _jsx("button", { type: "button", onClick: () => {
|
|
134
|
+
setEditingId(remoteHost.id);
|
|
135
|
+
setName(remoteHost.displayName);
|
|
136
|
+
}, className: "h-7 rounded-md border border-line bg-panel px-2 text-[11px] text-text-2 hover:text-text", children: "Rename" }), _jsx("button", { type: "button", disabled: actionId === `forget:${remoteHost.id}`, onClick: () => void runAction(`forget:${remoteHost.id}`, () => client.remoteHostForget(remoteHost.id)), className: "h-7 rounded-md border border-danger/40 bg-panel px-2 text-[11px] text-danger hover:border-danger disabled:opacity-40", children: "Forget" })] }))] }, remoteHost.id));
|
|
137
|
+
}) }))] }));
|
|
138
|
+
}
|
|
139
|
+
export { describeProjects };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { AnalyticsProjection } from "@rightkit/agent-ui-core/lib/analytics/analyticsProjection";
|
|
2
|
+
export interface AnalyticsConsentCardProps {
|
|
3
|
+
/** Optional native/background analytics handle. The UI remains usable when absent. */
|
|
4
|
+
projection?: AnalyticsProjection;
|
|
5
|
+
initialEnabled?: boolean;
|
|
6
|
+
onChange?: (enabled: boolean) => void;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Waku's anonymous-analytics setting, adapted to the host app settings tokens.
|
|
10
|
+
* Consent is explicit state, can be withdrawn immediately, and never contains
|
|
11
|
+
* a provider prompt, project path, response, or account identifier.
|
|
12
|
+
*/
|
|
13
|
+
export declare function AnalyticsConsentCard({ projection, initialEnabled, onChange, }: AnalyticsConsentCardProps): import("react").JSX.Element;
|
|
14
|
+
export default AnalyticsConsentCard;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import Icon from "@rightkit/agent-ui-core/components/Icon";
|
|
4
|
+
import { readAnalyticsConsent, writeAnalyticsConsent } from "@rightkit/agent-ui-core/lib/analytics/analyticsProjection";
|
|
5
|
+
import { hostAppName } from "../../../appName.js";
|
|
6
|
+
/**
|
|
7
|
+
* Waku's anonymous-analytics setting, adapted to the host app settings tokens.
|
|
8
|
+
* Consent is explicit state, can be withdrawn immediately, and never contains
|
|
9
|
+
* a provider prompt, project path, response, or account identifier.
|
|
10
|
+
*/
|
|
11
|
+
export function AnalyticsConsentCard({ projection, initialEnabled, onChange, }) {
|
|
12
|
+
const [enabled, setEnabled] = useState(() => initialEnabled ?? projection?.enabled ?? readAnalyticsConsent());
|
|
13
|
+
const update = (next) => {
|
|
14
|
+
setEnabled(next);
|
|
15
|
+
projection?.setEnabled(next);
|
|
16
|
+
if (!projection)
|
|
17
|
+
writeAnalyticsConsent(next);
|
|
18
|
+
onChange?.(next);
|
|
19
|
+
};
|
|
20
|
+
return (_jsxs("section", { className: "flex items-center justify-between gap-4 rounded-xl border border-line bg-panel px-4 py-3", "aria-labelledby": "analytics-consent-title", children: [_jsxs("div", { className: "flex min-w-0 items-start gap-3", children: [_jsx("span", { className: "mt-0.5 text-text-3", "aria-hidden": "true", children: _jsx(Icon, { name: "activity", size: 16 }) }), _jsxs("div", { className: "min-w-0", children: [_jsx("h4", { id: "analytics-consent-title", className: "text-[13px] font-semibold text-text", children: "Share anonymous usage data" }), _jsxs("p", { className: "mt-1 text-[12px] leading-snug text-text-3", children: ["Help improve ", hostAppName(), " with coarse product events only. Prompts, project names and paths, provider output, and account identity are never included."] })] })] }), _jsxs("label", { className: "flex shrink-0 items-center gap-2 text-[12px] text-text-2", children: [_jsx("input", { type: "checkbox", className: "h-6 w-6 cursor-pointer accent-accent", checked: enabled, onChange: (event) => update(event.currentTarget.checked), "aria-label": "Share anonymous usage data" }), enabled ? "On" : "Off"] })] }));
|
|
21
|
+
}
|
|
22
|
+
export default AnalyticsConsentCard;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ManagedCliUpdate } from "@rightkit/agent-ui-core/lib/cliUpdate/updateProjection";
|
|
2
|
+
export interface CliUpdatePanelProps {
|
|
3
|
+
updates: ManagedCliUpdate[];
|
|
4
|
+
loading?: boolean;
|
|
5
|
+
error?: string | null;
|
|
6
|
+
onRefresh?: () => void | Promise<void>;
|
|
7
|
+
}
|
|
8
|
+
/** Settings projection for managed CLI versions. Installation stays daemon-owned. */
|
|
9
|
+
export default function CliUpdatePanel({ updates, loading, error, onRefresh, }: CliUpdatePanelProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { hostAppName } from "../../../appName.js";
|
|
3
|
+
function stateLabel(state) {
|
|
4
|
+
switch (state) {
|
|
5
|
+
case "current":
|
|
6
|
+
return "Current";
|
|
7
|
+
case "update-available":
|
|
8
|
+
return "Update available";
|
|
9
|
+
case "not-installed":
|
|
10
|
+
return "Not installed";
|
|
11
|
+
case "unavailable":
|
|
12
|
+
return "Status unavailable";
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/** Settings projection for managed CLI versions. Installation stays daemon-owned. */
|
|
16
|
+
export default function CliUpdatePanel({ updates, loading = false, error = null, onRefresh, }) {
|
|
17
|
+
return (_jsxs("section", { className: "rounded-xl border border-line bg-panel p-4", "aria-label": "CLI updates", children: [_jsxs("div", { className: "flex items-start justify-between gap-3", children: [_jsxs("div", { children: [_jsx("h3", { className: "text-[14px] font-semibold text-text", children: "CLI updates" }), _jsxs("p", { className: "mt-1 text-[12px] text-text-3", children: ["Managed CLI versions come from the daemon. ", hostAppName(), " never updates a vendor binary directly."] })] }), onRefresh ? (_jsx("button", { type: "button", className: "h-8 rounded-lg border border-line px-3 text-[12px] text-text-2 hover:bg-panel-2 disabled:opacity-40", disabled: loading, onClick: () => void onRefresh(), children: loading ? "Refreshing" : "Refresh" })) : null] }), error ? _jsx("p", { className: "mt-3 text-[12px] text-danger", children: error }) : null, _jsxs("div", { className: "mt-4 flex flex-col gap-2", children: [updates.length === 0 ? _jsx("p", { className: "text-[12px] text-text-3", children: "No managed CLIs detected." }) : null, updates.map((update) => (_jsxs("article", { className: "flex items-center gap-3 rounded-lg border border-line/70 px-3 py-2", children: [_jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("strong", { className: "block truncate text-[12px] text-text", children: update.displayName }), _jsxs("span", { className: "block truncate text-[11px] text-text-3", children: ["Installed: ", update.installedVersion ?? "—"] })] }), _jsxs("div", { className: "shrink-0 text-right", children: [_jsx("span", { className: "block text-[12px] text-text-2", children: stateLabel(update.state) }), _jsx("span", { className: "block max-w-[220px] text-[10px] text-text-3", children: update.reason })] })] }, update.providerId)))] })] }));
|
|
18
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { McpServerRow, McpServerType } from "@rightkit/agent-ui-core/lib/types";
|
|
2
|
+
export declare function formatIsoTime(value: string): string;
|
|
3
|
+
export declare function shortHash(value: string | undefined): string;
|
|
4
|
+
export declare function mcpServerKey(server: Pick<McpServerRow, "scope" | "name">): string;
|
|
5
|
+
export declare function describeMcpServer(server: McpServerRow): string;
|
|
6
|
+
export declare function mcpTrustLabel(preset: string | undefined): string;
|
|
7
|
+
export declare function defaultMcpTrustPreset(type: McpServerType): string;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export function formatIsoTime(value) {
|
|
2
|
+
const date = new Date(value);
|
|
3
|
+
if (Number.isNaN(date.getTime()))
|
|
4
|
+
return value;
|
|
5
|
+
return date.toLocaleString();
|
|
6
|
+
}
|
|
7
|
+
export function shortHash(value) {
|
|
8
|
+
if (!value)
|
|
9
|
+
return "missing";
|
|
10
|
+
return value.length > 12 ? `${value.slice(0, 12)}...` : value;
|
|
11
|
+
}
|
|
12
|
+
export function mcpServerKey(server) {
|
|
13
|
+
return `${server.scope}:${server.name}`;
|
|
14
|
+
}
|
|
15
|
+
export function describeMcpServer(server) {
|
|
16
|
+
if (server.config.type === "http" || server.config.type === "sse") {
|
|
17
|
+
return server.config.url;
|
|
18
|
+
}
|
|
19
|
+
const args = server.config.args?.join(" ") ?? "";
|
|
20
|
+
return `${server.config.command}${args ? ` ${args}` : ""}`;
|
|
21
|
+
}
|
|
22
|
+
export function mcpTrustLabel(preset) {
|
|
23
|
+
switch (preset) {
|
|
24
|
+
case "local_process":
|
|
25
|
+
return "local process";
|
|
26
|
+
case "local_dev":
|
|
27
|
+
return "local dev";
|
|
28
|
+
case "remote_read_only":
|
|
29
|
+
return "remote read-only";
|
|
30
|
+
case "remote_full":
|
|
31
|
+
return "remote full";
|
|
32
|
+
case "remote_unreviewed":
|
|
33
|
+
return "remote unreviewed";
|
|
34
|
+
default:
|
|
35
|
+
return preset || "unknown";
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export function defaultMcpTrustPreset(type) {
|
|
39
|
+
return type === "stdio" ? "local_process" : "local_dev";
|
|
40
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type ShortcutCombo } from "@rightkit/agent-ui-core/lib/shortcuts/bindings";
|
|
2
|
+
export declare const SHORTCUT_OVERRIDES_STORAGE_KEY = "coderight.shortcuts.v1";
|
|
3
|
+
/** Fired on `window` after this window writes overrides, so the live registry re-syncs (other windows get `storage`). */
|
|
4
|
+
export declare const SHORTCUT_OVERRIDES_CHANGED_EVENT = "coderight:shortcut-overrides-changed";
|
|
5
|
+
export type ShortcutOverrides = Record<string, ShortcutCombo>;
|
|
6
|
+
export declare function readShortcutOverrides(fallback?: ShortcutOverrides): ShortcutOverrides;
|
|
7
|
+
export declare function writeShortcutOverrides(overrides: ShortcutOverrides): void;
|
|
8
|
+
export interface KeyboardShortcutsSettingsProps {
|
|
9
|
+
initialOverrides?: ShortcutOverrides;
|
|
10
|
+
onOverridesChange?: (overrides: ShortcutOverrides) => void;
|
|
11
|
+
}
|
|
12
|
+
export default function KeyboardShortcutsSettings({ initialOverrides, onOverridesChange, }: KeyboardShortcutsSettingsProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useMemo, useState } from "react";
|
|
3
|
+
import { createKeymapStore } from "@rightkit/react-shell";
|
|
4
|
+
import { getCustomizableShortcutActions, getEffectiveShortcutCombo, getEffectiveShortcutPrefix, } from "@rightkit/agent-ui-core/lib/shortcuts/schema";
|
|
5
|
+
import { formatShortcutForDisplay, UNASSIGNED_SHORTCUT, } from "@rightkit/agent-ui-core/lib/shortcuts/bindings";
|
|
6
|
+
import ShortcutRecordingDialog from "./ShortcutRecordingDialog.js";
|
|
7
|
+
export const SHORTCUT_OVERRIDES_STORAGE_KEY = "coderight.shortcuts.v1";
|
|
8
|
+
/** Fired on `window` after this window writes overrides, so the live registry re-syncs (other windows get `storage`). */
|
|
9
|
+
export const SHORTCUT_OVERRIDES_CHANGED_EVENT = "coderight:shortcut-overrides-changed";
|
|
10
|
+
const keymapStore = createKeymapStore({ storageKey: SHORTCUT_OVERRIDES_STORAGE_KEY });
|
|
11
|
+
export function readShortcutOverrides(fallback = {}) {
|
|
12
|
+
const loaded = Object.fromEntries(Object.entries(keymapStore.load()).filter((entry) => typeof entry[1] === "string"));
|
|
13
|
+
return Object.keys(loaded).length > 0 ? loaded : { ...fallback };
|
|
14
|
+
}
|
|
15
|
+
export function writeShortcutOverrides(overrides) {
|
|
16
|
+
keymapStore.save(overrides);
|
|
17
|
+
if (typeof window !== "undefined")
|
|
18
|
+
window.dispatchEvent(new Event(SHORTCUT_OVERRIDES_CHANGED_EVENT));
|
|
19
|
+
}
|
|
20
|
+
const CATEGORIES = ["session", "models", "panels", "navigation", "application"];
|
|
21
|
+
function labelForAction(action) {
|
|
22
|
+
return action.id.split("_").map((part) => part[0].toUpperCase() + part.slice(1)).join(" ");
|
|
23
|
+
}
|
|
24
|
+
function categoryLabel(category) {
|
|
25
|
+
return category[0].toUpperCase() + category.slice(1);
|
|
26
|
+
}
|
|
27
|
+
export default function KeyboardShortcutsSettings({ initialOverrides = {}, onOverridesChange, }) {
|
|
28
|
+
const [shortcutOverrides, setShortcutOverrides] = useState(() => readShortcutOverrides(initialOverrides));
|
|
29
|
+
const [editingAction, setEditingAction] = useState(null);
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
const onStorage = (event) => {
|
|
32
|
+
if (event.key !== SHORTCUT_OVERRIDES_STORAGE_KEY)
|
|
33
|
+
return;
|
|
34
|
+
setShortcutOverrides(readShortcutOverrides(initialOverrides));
|
|
35
|
+
};
|
|
36
|
+
window.addEventListener("storage", onStorage);
|
|
37
|
+
return () => window.removeEventListener("storage", onStorage);
|
|
38
|
+
}, [initialOverrides]);
|
|
39
|
+
const actions = useMemo(() => getCustomizableShortcutActions(), []);
|
|
40
|
+
const updateOverrides = (next) => {
|
|
41
|
+
setShortcutOverrides(next);
|
|
42
|
+
writeShortcutOverrides(next);
|
|
43
|
+
onOverridesChange?.(next);
|
|
44
|
+
};
|
|
45
|
+
const save = (actionId, combo, replaceActionId) => {
|
|
46
|
+
const next = { ...shortcutOverrides, [actionId]: combo };
|
|
47
|
+
if (replaceActionId)
|
|
48
|
+
next[replaceActionId] = UNASSIGNED_SHORTCUT;
|
|
49
|
+
updateOverrides(next);
|
|
50
|
+
};
|
|
51
|
+
const resetOne = (actionId) => {
|
|
52
|
+
const next = { ...shortcutOverrides };
|
|
53
|
+
delete next[actionId];
|
|
54
|
+
updateOverrides(next);
|
|
55
|
+
};
|
|
56
|
+
const displayFor = (action) => {
|
|
57
|
+
const prefixStyle = "prefixStyle" in action && action.prefixStyle;
|
|
58
|
+
const combo = prefixStyle
|
|
59
|
+
? getEffectiveShortcutPrefix(action.id, shortcutOverrides)
|
|
60
|
+
: getEffectiveShortcutCombo(action.id, shortcutOverrides);
|
|
61
|
+
const formatted = formatShortcutForDisplay(combo);
|
|
62
|
+
if (!prefixStyle || !combo || combo === UNASSIGNED_SHORTCUT)
|
|
63
|
+
return formatted;
|
|
64
|
+
return `${formatted} + digit`;
|
|
65
|
+
};
|
|
66
|
+
return (_jsxs("section", { className: "flex max-w-[720px] flex-col gap-5 p-6", "aria-label": "Keyboard shortcuts settings", children: [_jsxs("div", { children: [_jsx("h3", { className: "text-[16px] font-bold text-text", children: "Keyboard shortcuts" }), _jsx("p", { className: "mt-1 text-[13px] text-text-2", children: "Remap application commands and review conflicts before saving." })] }), CATEGORIES.map((category, index) => {
|
|
67
|
+
const categoryActions = actions.filter((action) => action.category === category);
|
|
68
|
+
if (categoryActions.length === 0)
|
|
69
|
+
return null;
|
|
70
|
+
return (_jsxs("section", { className: `grid gap-2 ${index === 0 ? "" : "border-t border-line pt-5"}`, children: [_jsxs("div", { className: "flex items-center justify-between gap-3", children: [_jsx("h4", { className: "text-[13px] font-semibold text-text", children: categoryLabel(category) }), index === 0 ? (_jsx("button", { type: "button", className: "inline-flex h-8 items-center rounded-lg border border-line bg-transparent px-3 text-[12px] text-text-2 cursor-pointer hover:bg-panel-2 hover:text-text", onClick: () => updateOverrides({}), children: "Reset all" })) : null] }), _jsx("div", { className: "grid gap-2", children: categoryActions.map((action) => (_jsxs("div", { className: "flex min-h-9 items-center justify-between gap-3 rounded-lg border border-line bg-panel px-3 py-2", children: [_jsx("span", { className: "min-w-0 text-[12px] text-text-2", children: labelForAction(action) }), _jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [_jsx("kbd", { className: "min-w-24 rounded-md border border-line bg-bg px-2 py-1 text-center text-[11px] font-mono text-text", children: displayFor(action) }), _jsx("button", { type: "button", className: "inline-flex h-8 items-center rounded-lg border border-line bg-transparent px-3 text-[12px] text-text-2 cursor-pointer hover:bg-panel-2 hover:text-text", onClick: () => setEditingAction(action), children: "Edit" }), action.id in shortcutOverrides ? (_jsx("button", { type: "button", className: "inline-flex h-8 items-center rounded-lg border border-transparent bg-transparent px-2 text-[12px] text-text-3 cursor-pointer hover:text-text", onClick: () => resetOne(action.id), children: "Reset" })) : null] })] }, action.id))) })] }, category));
|
|
71
|
+
}), _jsx(ShortcutRecordingDialog, { action: editingAction, overrides: shortcutOverrides, onSave: save, onOpenChange: (open) => {
|
|
72
|
+
if (!open)
|
|
73
|
+
setEditingAction(null);
|
|
74
|
+
} })] }));
|
|
75
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type CustomizableShortcutAction, type ShortcutActionId } from "@rightkit/agent-ui-core/lib/shortcuts/schema";
|
|
2
|
+
import { type ShortcutCombo } from "@rightkit/agent-ui-core/lib/shortcuts/bindings";
|
|
3
|
+
type RecordingKeyboardEvent = {
|
|
4
|
+
altKey: boolean;
|
|
5
|
+
code: string;
|
|
6
|
+
ctrlKey: boolean;
|
|
7
|
+
isComposing: boolean;
|
|
8
|
+
key: string;
|
|
9
|
+
metaKey: boolean;
|
|
10
|
+
repeat: boolean;
|
|
11
|
+
shiftKey: boolean;
|
|
12
|
+
};
|
|
13
|
+
export type ShortcutRecordingState = {
|
|
14
|
+
chords: ShortcutCombo[];
|
|
15
|
+
livePreview: ShortcutCombo | null;
|
|
16
|
+
settled: boolean;
|
|
17
|
+
};
|
|
18
|
+
interface ShortcutRecordingDialogProps {
|
|
19
|
+
action: CustomizableShortcutAction | null;
|
|
20
|
+
overrides: Record<string, string>;
|
|
21
|
+
onSave: (actionId: ShortcutActionId, combo: ShortcutCombo, replaceActionId?: ShortcutActionId) => void;
|
|
22
|
+
onOpenChange: (open: boolean) => void;
|
|
23
|
+
}
|
|
24
|
+
export declare function settleShortcutRecordingState(state: ShortcutRecordingState): ShortcutRecordingState;
|
|
25
|
+
export declare function updateShortcutRecordingState(state: ShortcutRecordingState, event: RecordingKeyboardEvent, phase: "keydown" | "keyup"): ShortcutRecordingState;
|
|
26
|
+
export default function ShortcutRecordingDialog({ action, overrides, onSave, onOpenChange }: ShortcutRecordingDialogProps): import("react").JSX.Element | null;
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef, useState } from "react";
|
|
3
|
+
import { Button, Dialog } from "@rightkit/react-shell/react";
|
|
4
|
+
import { getShortcutBindingConflicts, } from "@rightkit/agent-ui-core/lib/shortcuts/schema";
|
|
5
|
+
import { formatShortcutForDisplay, isRiskyBrowserShortcut, keyToShortcutToken, normalizeCombo, resolveShortcutEventKey, } from "@rightkit/agent-ui-core/lib/shortcuts/bindings";
|
|
6
|
+
import { rightKitShortcuts } from "@rightkit/agent-ui-core/lib/shortcuts/dispatcher";
|
|
7
|
+
const MODIFIER_KEYS = new Set(["shift", "control", "alt", "meta"]);
|
|
8
|
+
const MAX_SHORTCUT_KEY_COUNT = 3;
|
|
9
|
+
const SECOND_CHORD_TIMEOUT_MS = 3000;
|
|
10
|
+
function getPhysicalKeyCount(event, includeEventKey = false) {
|
|
11
|
+
const keys = new Set();
|
|
12
|
+
if (event.altKey)
|
|
13
|
+
keys.add("alt");
|
|
14
|
+
if (event.ctrlKey)
|
|
15
|
+
keys.add("control");
|
|
16
|
+
if (event.metaKey)
|
|
17
|
+
keys.add("meta");
|
|
18
|
+
if (event.shiftKey)
|
|
19
|
+
keys.add("shift");
|
|
20
|
+
if (includeEventKey)
|
|
21
|
+
keys.add(event.key.toLowerCase());
|
|
22
|
+
return keys.size;
|
|
23
|
+
}
|
|
24
|
+
function isCustomizableConflict(conflict) {
|
|
25
|
+
return conflict.action.customizable;
|
|
26
|
+
}
|
|
27
|
+
function getModifierPreview(event) {
|
|
28
|
+
if (getPhysicalKeyCount(event) > MAX_SHORTCUT_KEY_COUNT)
|
|
29
|
+
return null;
|
|
30
|
+
const parts = [];
|
|
31
|
+
if (event.metaKey || event.ctrlKey)
|
|
32
|
+
parts.push("mod");
|
|
33
|
+
if (event.shiftKey)
|
|
34
|
+
parts.push("shift");
|
|
35
|
+
if (event.altKey)
|
|
36
|
+
parts.push("alt");
|
|
37
|
+
return parts.length > 0 ? normalizeCombo(parts.join("+")) : null;
|
|
38
|
+
}
|
|
39
|
+
function keyboardEventToCombo(event) {
|
|
40
|
+
if (MODIFIER_KEYS.has(event.key.toLowerCase()))
|
|
41
|
+
return null;
|
|
42
|
+
if (getPhysicalKeyCount(event, true) > MAX_SHORTCUT_KEY_COUNT)
|
|
43
|
+
return null;
|
|
44
|
+
const key = keyToShortcutToken(resolveShortcutEventKey(event));
|
|
45
|
+
if (!key)
|
|
46
|
+
return null;
|
|
47
|
+
const parts = [];
|
|
48
|
+
if (event.metaKey || event.ctrlKey)
|
|
49
|
+
parts.push("mod");
|
|
50
|
+
if (event.shiftKey)
|
|
51
|
+
parts.push("shift");
|
|
52
|
+
if (event.altKey)
|
|
53
|
+
parts.push("alt");
|
|
54
|
+
parts.push(key);
|
|
55
|
+
return normalizeCombo(parts.join("+"));
|
|
56
|
+
}
|
|
57
|
+
function modifierKeyUpToCombo(event) {
|
|
58
|
+
const key = event.key.toLowerCase();
|
|
59
|
+
if (!MODIFIER_KEYS.has(key))
|
|
60
|
+
return null;
|
|
61
|
+
if (getPhysicalKeyCount(event, true) > MAX_SHORTCUT_KEY_COUNT)
|
|
62
|
+
return null;
|
|
63
|
+
const parts = [];
|
|
64
|
+
if (event.metaKey || event.ctrlKey || key === "meta" || key === "control")
|
|
65
|
+
parts.push("mod");
|
|
66
|
+
if (event.shiftKey || key === "shift")
|
|
67
|
+
parts.push("shift");
|
|
68
|
+
if (event.altKey || key === "alt")
|
|
69
|
+
parts.push("alt");
|
|
70
|
+
return parts.length > 0 ? normalizeCombo(parts.join("+")) : null;
|
|
71
|
+
}
|
|
72
|
+
export function settleShortcutRecordingState(state) {
|
|
73
|
+
return state.chords.length > 0 ? { ...state, livePreview: null, settled: true } : state;
|
|
74
|
+
}
|
|
75
|
+
export function updateShortcutRecordingState(state, event, phase) {
|
|
76
|
+
if (event.repeat || event.isComposing)
|
|
77
|
+
return state;
|
|
78
|
+
if (phase === "keyup")
|
|
79
|
+
return { ...state, livePreview: getModifierPreview(event) };
|
|
80
|
+
if (event.key === "Backspace")
|
|
81
|
+
return { chords: state.chords.slice(0, -1), livePreview: null, settled: false };
|
|
82
|
+
const chord = keyboardEventToCombo(event);
|
|
83
|
+
if (chord) {
|
|
84
|
+
if (state.settled)
|
|
85
|
+
return { chords: [chord], livePreview: null, settled: false };
|
|
86
|
+
const chords = state.chords.length < 2 ? [...state.chords, chord] : state.chords;
|
|
87
|
+
return { chords, livePreview: null, settled: chords.length === 2 };
|
|
88
|
+
}
|
|
89
|
+
return { ...state, livePreview: getModifierPreview(event) };
|
|
90
|
+
}
|
|
91
|
+
export default function ShortcutRecordingDialog({ action, overrides, onSave, onOpenChange }) {
|
|
92
|
+
const [recording, setRecording] = useState({ chords: [], livePreview: null, settled: false });
|
|
93
|
+
const recordingRef = useRef(null);
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
if (!action)
|
|
96
|
+
return;
|
|
97
|
+
setRecording({ chords: [], livePreview: null, settled: false });
|
|
98
|
+
recordingRef.current?.focus();
|
|
99
|
+
rightKitShortcuts.suspend();
|
|
100
|
+
return () => rightKitShortcuts.resume();
|
|
101
|
+
}, [action]);
|
|
102
|
+
const waitingForSecondChord = recording.chords.length === 1 && !recording.settled;
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
if (!waitingForSecondChord)
|
|
105
|
+
return;
|
|
106
|
+
const timeout = window.setTimeout(() => setRecording(settleShortcutRecordingState), SECOND_CHORD_TIMEOUT_MS);
|
|
107
|
+
return () => window.clearTimeout(timeout);
|
|
108
|
+
}, [waitingForSecondChord]);
|
|
109
|
+
const combo = normalizeCombo(recording.chords.join(" "));
|
|
110
|
+
const conflicts = action && combo ? getShortcutBindingConflicts(action.id, combo, overrides) : [];
|
|
111
|
+
const protectedConflict = conflicts.find((conflict) => !conflict.action.customizable && conflict.kind !== "contextual-prefix");
|
|
112
|
+
const customizableConflicts = conflicts.filter(isCustomizableConflict);
|
|
113
|
+
const prefixConflict = customizableConflicts.find((conflict) => conflict.kind === "prefix");
|
|
114
|
+
const exactConflict = customizableConflicts.find((conflict) => conflict.kind === "exact");
|
|
115
|
+
const contextualPrefixConflict = conflicts.find((conflict) => conflict.kind === "contextual-prefix");
|
|
116
|
+
const close = () => onOpenChange(false);
|
|
117
|
+
const confirm = () => {
|
|
118
|
+
if (!recording.settled)
|
|
119
|
+
setRecording(settleShortcutRecordingState);
|
|
120
|
+
if (!action || !combo || protectedConflict || prefixConflict)
|
|
121
|
+
return;
|
|
122
|
+
onSave(action.id, combo, exactConflict?.action.id);
|
|
123
|
+
close();
|
|
124
|
+
};
|
|
125
|
+
const handleRecordingEvent = (event, phase) => {
|
|
126
|
+
event.preventDefault();
|
|
127
|
+
event.stopPropagation();
|
|
128
|
+
const isPrefixStyleAction = Boolean(action && "prefixStyle" in action && action.prefixStyle);
|
|
129
|
+
if (phase === "keyup" && isPrefixStyleAction && recording.chords.length === 0) {
|
|
130
|
+
const modifierCombo = modifierKeyUpToCombo(event);
|
|
131
|
+
if (modifierCombo) {
|
|
132
|
+
setRecording({ chords: [modifierCombo], livePreview: null, settled: true });
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const nextRecording = updateShortcutRecordingState(recording, {
|
|
137
|
+
altKey: event.altKey,
|
|
138
|
+
code: event.nativeEvent.code,
|
|
139
|
+
ctrlKey: event.ctrlKey,
|
|
140
|
+
isComposing: event.nativeEvent.isComposing,
|
|
141
|
+
key: event.key,
|
|
142
|
+
metaKey: event.metaKey,
|
|
143
|
+
repeat: event.repeat,
|
|
144
|
+
shiftKey: event.shiftKey,
|
|
145
|
+
}, phase);
|
|
146
|
+
setRecording(isPrefixStyleAction && nextRecording.chords.length > 1 ? recording : nextRecording);
|
|
147
|
+
};
|
|
148
|
+
// The recorder owns every key including Escape, so the dialog opts out of Escape dismissal.
|
|
149
|
+
if (!action)
|
|
150
|
+
return null;
|
|
151
|
+
return (_jsxs(Dialog, { title: `Edit shortcut: ${action.id.split("_").join(" ")}`, description: "Press one key or a two-key sequence. The recording stays local until you confirm.", onClose: close, escape: false, autoFocus: false, width: 400, children: [_jsx("div", { ref: recordingRef, role: "application",
|
|
152
|
+
// biome-ignore lint/a11y/noNoninteractiveTabindex: recorder is intentional keyboard capture target.
|
|
153
|
+
tabIndex: 0, className: "mt-4 flex min-h-28 items-center justify-center rounded-lg border border-line bg-panel-2 px-4 py-5 text-center outline-none focus-visible:ring-2 focus-visible:ring-accent/60", onKeyDown: (event) => handleRecordingEvent(event, "keydown"), onKeyUp: (event) => handleRecordingEvent(event, "keyup"), onBlur: () => setRecording((current) => ({ ...current, livePreview: null })), "aria-label": "Shortcut recorder", children: _jsxs("div", { className: "flex flex-wrap items-center justify-center gap-2", children: [recording.chords.map((chord, index) => (_jsx("kbd", { className: "rounded-md border border-line bg-bg px-3 py-2 text-[12px] font-mono text-text", children: formatShortcutForDisplay(chord) }, `${chord}-${index}`))), recording.livePreview ? (_jsx("kbd", { className: "rounded-md border border-dashed border-line bg-bg px-3 py-2 text-[12px] font-mono text-text-3", children: formatShortcutForDisplay(recording.livePreview) })) : null, recording.chords.length === 0 && !recording.livePreview ? _jsx("span", { className: "text-[12px] text-text-3", children: "Waiting for a key\u2026" }) : null] }) }), recording.settled && protectedConflict ? _jsxs("p", { className: "mt-3 text-[12px] text-danger", children: ["This shortcut is reserved by ", protectedConflict.action.id, "."] }) : null, recording.settled && prefixConflict ? _jsxs("p", { className: "mt-3 text-[12px] text-danger", children: ["This shortcut conflicts with ", prefixConflict.action.id, "."] }) : null, recording.settled && exactConflict && !protectedConflict && !prefixConflict ? _jsxs("p", { className: "mt-3 text-[12px] text-warning", children: ["This replaces the shortcut for ", exactConflict.action.id, "."] }) : null, recording.settled && contextualPrefixConflict && !protectedConflict && !prefixConflict ? _jsxs("p", { className: "mt-3 text-[12px] text-warning", children: ["This may overlap ", contextualPrefixConflict.action.id, "."] }) : null, recording.settled && combo && isRiskyBrowserShortcut(combo) ? _jsx("p", { className: "mt-3 text-[12px] text-warning", children: "This may be intercepted by the browser or operating system." }) : null, _jsxs("div", { className: "mt-4 flex justify-end gap-2", children: [_jsx(Button, { type: "button", variant: "secondary", size: "sm", onClick: close, children: "Cancel" }), _jsx(Button, { type: "button", variant: "primary", size: "sm", disabled: !combo || Boolean(protectedConflict) || Boolean(prefixConflict), onClick: confirm, children: "Confirm" })] })] }));
|
|
154
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { ConfiguredMcpServerProjection, McpRegistryServer, RegistryHealthProjection } from "@rightkit/agent-ui-core/lib/daemon/marketplaces";
|
|
2
|
+
export type McpInstallAgent = "claude" | "codex";
|
|
3
|
+
export type McpInstallScope = "user" | "project" | "local";
|
|
4
|
+
export interface McpInstallSelection {
|
|
5
|
+
agent: McpInstallAgent;
|
|
6
|
+
scope: McpInstallScope;
|
|
7
|
+
cwd?: string;
|
|
8
|
+
environment: Record<string, string>;
|
|
9
|
+
headers: Record<string, string>;
|
|
10
|
+
}
|
|
11
|
+
export interface McpDiscoverPanelProps {
|
|
12
|
+
projectCwd?: string;
|
|
13
|
+
query: string;
|
|
14
|
+
results: McpRegistryServer[];
|
|
15
|
+
popularResults?: McpRegistryServer[];
|
|
16
|
+
selectedServer?: McpRegistryServer | null;
|
|
17
|
+
installedServers?: ConfiguredMcpServerProjection[];
|
|
18
|
+
health?: RegistryHealthProjection | null;
|
|
19
|
+
searching?: boolean;
|
|
20
|
+
refreshing?: boolean;
|
|
21
|
+
operationLoading?: string | null;
|
|
22
|
+
installAgent?: McpInstallAgent;
|
|
23
|
+
installScope?: McpInstallScope;
|
|
24
|
+
onQueryChange: (query: string) => void;
|
|
25
|
+
onAgentChange?: (agent: McpInstallAgent) => void;
|
|
26
|
+
onScopeChange?: (scope: McpInstallScope) => void;
|
|
27
|
+
onRefresh?: () => void;
|
|
28
|
+
onSelect: (server: McpRegistryServer) => void;
|
|
29
|
+
onClearSelection?: () => void;
|
|
30
|
+
onInstall: (server: McpRegistryServer, selection: McpInstallSelection) => void | Promise<void>;
|
|
31
|
+
onImportClaudeDesktop?: () => void | Promise<void>;
|
|
32
|
+
onImportJson?: (file: File) => void | Promise<void>;
|
|
33
|
+
}
|
|
34
|
+
export default function McpDiscoverPanel({ projectCwd, query, results, popularResults, selectedServer, installedServers, health, searching, refreshing, operationLoading, installAgent, installScope, onQueryChange, onAgentChange, onScopeChange, onRefresh, onSelect, onClearSelection, onInstall, onImportClaudeDesktop, onImportJson, }: McpDiscoverPanelProps): import("react").JSX.Element;
|