@echomem/echo-memory-cloud-openclaw-plugin 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -2
- package/clawdbot.plugin.json +2 -2
- package/index.js +44 -12
- package/lib/config.js +35 -8
- package/lib/local-server.js +331 -138
- package/lib/local-ui/dist/assets/index-CKT0_swv.css +1 -0
- package/lib/local-ui/dist/assets/index-D9Bbjf7A.js +54 -0
- package/lib/local-ui/dist/index.html +2 -2
- package/lib/onboarding.js +2 -1
- package/lib/state.js +59 -4
- package/lib/sync.js +42 -15
- package/moltbot.plugin.json +2 -2
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/lib/local-ui/dist/assets/index-BoyzFR9Q.css +0 -1
- package/lib/local-ui/dist/assets/index-CJrdHn7-.js +0 -54
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>OpenClaw Memory Archive</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/assets/index-D9Bbjf7A.js"></script>
|
|
8
|
+
<link rel="stylesheet" crossorigin href="/assets/index-CKT0_swv.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
11
11
|
<div id="root"></div>
|
package/lib/onboarding.js
CHANGED
|
@@ -17,7 +17,8 @@ export function buildOnboardingText({ commandLabel, cfg }) {
|
|
|
17
17
|
"- Optional config: `memoryDir`, `autoSync`, `syncIntervalMinutes`, `batchSize`, `requestTimeoutMs`.",
|
|
18
18
|
"- In `openclaw.json`, set `tools.profile` to `full`. The default `coding` profile is too restrictive for normal plugin use.",
|
|
19
19
|
"- `memoryDir` resolution order: plugin config, `ECHOMEM_MEMORY_DIR`, then `~/.openclaw/workspace/memory`.",
|
|
20
|
-
"-
|
|
20
|
+
"- Current `.env` location: `~/.openclaw/.env`.",
|
|
21
|
+
"- Legacy `.moltbot` and `.clawdbot` `.env` files are only read as a one-release migration bridge, and new saves should move to `~/.openclaw/.env`.",
|
|
21
22
|
"- Restart `openclaw gateway` after install or config changes.",
|
|
22
23
|
"",
|
|
23
24
|
"Commands and usage:",
|
package/lib/state.js
CHANGED
|
@@ -2,21 +2,76 @@ import fs from "node:fs/promises";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
|
|
4
4
|
const STATE_FILE_NAME = "echo-memory-last-sync.json";
|
|
5
|
+
const UI_PRESENCE_FILE_NAME = "echo-memory-ui-presence.json";
|
|
5
6
|
|
|
6
7
|
export function resolveStatePath(stateDir) {
|
|
7
8
|
return path.join(stateDir, STATE_FILE_NAME);
|
|
8
9
|
}
|
|
9
10
|
|
|
10
|
-
export
|
|
11
|
+
export function resolveUiPresencePath(stateDir) {
|
|
12
|
+
return path.join(stateDir, UI_PRESENCE_FILE_NAME);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function pathExists(targetPath) {
|
|
11
16
|
try {
|
|
12
|
-
|
|
17
|
+
await fs.access(targetPath);
|
|
18
|
+
return true;
|
|
19
|
+
} catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function readJsonFile(targetPath) {
|
|
25
|
+
try {
|
|
26
|
+
const raw = await fs.readFile(targetPath, "utf8");
|
|
13
27
|
return JSON.parse(raw);
|
|
14
28
|
} catch {
|
|
15
29
|
return null;
|
|
16
30
|
}
|
|
17
31
|
}
|
|
18
32
|
|
|
33
|
+
async function writeJsonFile(targetPath, payload) {
|
|
34
|
+
await fs.mkdir(path.dirname(targetPath), { recursive: true });
|
|
35
|
+
await fs.writeFile(targetPath, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function readLastSyncState(statePath) {
|
|
39
|
+
return readJsonFile(statePath);
|
|
40
|
+
}
|
|
41
|
+
|
|
19
42
|
export async function writeLastSyncState(statePath, state) {
|
|
20
|
-
await
|
|
21
|
-
|
|
43
|
+
await writeJsonFile(statePath, state);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export async function readLocalUiPresence(presencePath) {
|
|
47
|
+
return readJsonFile(presencePath);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export async function writeLocalUiPresence(presencePath, presence) {
|
|
51
|
+
await writeJsonFile(presencePath, presence);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function migrateStateFile(targetPath, sourcePaths = []) {
|
|
55
|
+
if (!targetPath) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
if (await pathExists(targetPath)) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
for (const sourcePath of sourcePaths) {
|
|
63
|
+
if (!sourcePath || sourcePath === targetPath) {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
const raw = await fs.readFile(sourcePath, "utf8");
|
|
68
|
+
await fs.mkdir(path.dirname(targetPath), { recursive: true });
|
|
69
|
+
await fs.writeFile(targetPath, raw, "utf8");
|
|
70
|
+
return sourcePath;
|
|
71
|
+
} catch {
|
|
72
|
+
// Ignore missing or unreadable migration sources.
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return null;
|
|
22
77
|
}
|
package/lib/sync.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { scanOpenClawMemoryDir } from "./openclaw-memory-scan.js";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
2
|
+
import {
|
|
3
|
+
migrateStateFile,
|
|
4
|
+
resolveStatePath,
|
|
5
|
+
readLastSyncState,
|
|
6
|
+
writeLastSyncState,
|
|
7
|
+
} from "./state.js";
|
|
8
|
+
|
|
9
|
+
function resolveRuntimeStateDir(api) {
|
|
10
|
+
return api?.runtime?.state?.resolveStateDir?.() || null;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
function buildRunId() {
|
|
@@ -255,30 +256,56 @@ export function formatStatusText(localState, remoteStatus = null) {
|
|
|
255
256
|
return lines.join("\n");
|
|
256
257
|
}
|
|
257
258
|
|
|
258
|
-
export function createSyncRunner({ api, cfg, client, fallbackStateDir = null }) {
|
|
259
|
+
export function createSyncRunner({ api, cfg, client, fallbackStateDir = null, stableStateDir = null }) {
|
|
259
260
|
let intervalHandle = null;
|
|
260
261
|
let statePath = null;
|
|
261
262
|
let activeRun = null;
|
|
262
263
|
let activeRunInfo = null;
|
|
263
264
|
const progressListeners = new Set();
|
|
264
265
|
|
|
266
|
+
function resolveStateDirectories(overrideStateDir = null) {
|
|
267
|
+
const runtimeStateDir = overrideStateDir || resolveRuntimeStateDir(api);
|
|
268
|
+
const primaryStateDir = stableStateDir || runtimeStateDir || fallbackStateDir;
|
|
269
|
+
const legacyStateDirs = [];
|
|
270
|
+
|
|
271
|
+
for (const candidate of [runtimeStateDir, fallbackStateDir]) {
|
|
272
|
+
if (!candidate || candidate === primaryStateDir || legacyStateDirs.includes(candidate)) {
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
legacyStateDirs.push(candidate);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return {
|
|
279
|
+
primaryStateDir,
|
|
280
|
+
legacyStateDirs,
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
265
284
|
async function initialize(stateDir) {
|
|
266
|
-
const
|
|
267
|
-
if (!
|
|
285
|
+
const { primaryStateDir, legacyStateDirs } = resolveStateDirectories(stateDir);
|
|
286
|
+
if (!primaryStateDir) {
|
|
268
287
|
throw new Error("Echo memory state directory is unavailable");
|
|
269
288
|
}
|
|
270
|
-
statePath = resolveStatePath(
|
|
289
|
+
statePath = resolveStatePath(primaryStateDir);
|
|
290
|
+
|
|
291
|
+
const migratedFrom = await migrateStateFile(
|
|
292
|
+
statePath,
|
|
293
|
+
legacyStateDirs.map((legacyStateDir) => resolveStatePath(legacyStateDir)),
|
|
294
|
+
);
|
|
295
|
+
if (migratedFrom) {
|
|
296
|
+
api.logger?.info?.(`[echo-memory] migrated sync state from ${migratedFrom} to ${statePath}`);
|
|
297
|
+
}
|
|
271
298
|
}
|
|
272
299
|
|
|
273
300
|
function getStatePath() {
|
|
274
301
|
if (statePath) {
|
|
275
302
|
return statePath;
|
|
276
303
|
}
|
|
277
|
-
const
|
|
278
|
-
if (!
|
|
304
|
+
const { primaryStateDir } = resolveStateDirectories();
|
|
305
|
+
if (!primaryStateDir) {
|
|
279
306
|
throw new Error("Echo memory state directory is unavailable");
|
|
280
307
|
}
|
|
281
|
-
return resolveStatePath(
|
|
308
|
+
return resolveStatePath(primaryStateDir);
|
|
282
309
|
}
|
|
283
310
|
|
|
284
311
|
function emitProgress(event) {
|
package/moltbot.plugin.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "echo-memory-cloud-openclaw-plugin",
|
|
3
3
|
"name": "Echo Memory Cloud OpenClaw Plugin",
|
|
4
4
|
"description": "Sync OpenClaw local markdown memory files to Echo cloud",
|
|
5
|
-
"version": "0.2.
|
|
5
|
+
"version": "0.2.2",
|
|
6
6
|
"kind": "lifecycle",
|
|
7
7
|
"main": "./index.js",
|
|
8
8
|
"configSchema": {
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
},
|
|
23
23
|
"memoryDir": {
|
|
24
24
|
"type": "string",
|
|
25
|
-
"description": "Absolute path to the markdown memory directory. Falls back to ECHOMEM_MEMORY_DIR, then ~/.
|
|
25
|
+
"description": "Absolute path to the markdown memory directory. Falls back to ECHOMEM_MEMORY_DIR, then ~/.openclaw/workspace/memory."
|
|
26
26
|
},
|
|
27
27
|
"localOnlyMode": {
|
|
28
28
|
"type": "boolean",
|
package/openclaw.plugin.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "echo-memory-cloud-openclaw-plugin",
|
|
3
3
|
"name": "Echo Memory Cloud OpenClaw Plugin",
|
|
4
4
|
"description": "Sync OpenClaw local markdown memory files to Echo cloud",
|
|
5
|
-
"version": "0.2.
|
|
5
|
+
"version": "0.2.2",
|
|
6
6
|
"kind": "lifecycle",
|
|
7
7
|
"main": "./index.js",
|
|
8
8
|
"configSchema": {
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
.card{position:absolute;border-radius:4px;overflow:hidden;box-shadow:0 1px 3px #0000002e;cursor:pointer;display:flex;flex-direction:column;padding:8px 10px;contain:layout style paint;animation:cardFadeIn .5s ease-out both}@keyframes cardFadeIn{0%{opacity:0}to{opacity:1}}.card:hover{box-shadow:0 4px 16px #0000004d;z-index:10}.card-selected{transform:scale(1.08);transform-origin:center center;box-shadow:0 8px 32px #a78bfa59,0 0 0 2px #a78bfa99;z-index:50!important;transition:transform .25s cubic-bezier(.34,1.56,.64,1),box-shadow .25s ease}.card-dimmed{opacity:.35;transition:opacity .3s ease}.card-dimmed:hover{opacity:.7}.card-expand-btn{flex-shrink:0;width:20px;height:20px;border:none;border-radius:4px;background:#a78bfa33;color:#a78bfa;font-size:12px;font-weight:700;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:background .15s,transform .15s;line-height:1}.card-expand-btn:hover{background:#a78bfa66;transform:scale(1.15)}.card-checkbox{flex-shrink:0;font-size:14px;cursor:pointer;color:#999;line-height:1;transition:color .15s}.card-checkbox-on{color:#a78bfa}.card-lod0{position:absolute;border-radius:3px;contain:strict;pointer-events:none}.card-header{display:flex;align-items:center;gap:6px;flex-shrink:0;margin-bottom:4px}.card-name{font:600 11px/1.3 -apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0}.card-warning-toggle{flex-shrink:0;max-width:48%;display:inline-flex;align-items:center;gap:4px;border:1px solid rgba(160,112,48,.35);border-radius:999px;background:#a070301a;color:#8b6128;padding:2px 6px;font:600 8px/1.1 -apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;cursor:pointer;min-width:0}.card-warning-toggle-high{border-color:#b040406b;background:#b040401a;color:#9c3d3d}.card-warning-toggle__icon{flex-shrink:0}.card-warning-toggle__text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.card-private-badge{flex-shrink:0;border:1px solid rgba(156,61,61,.35);border-radius:999px;background:#9c3d3d14;color:#9c3d3d;padding:2px 6px;font:700 8px/1.1 -apple-system,BlinkMacSystemFont,Segoe UI,sans-serif}.card-cluster-badge{flex-shrink:0;border-radius:999px;padding:2px 6px;font:700 8px/1.1 -apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;letter-spacing:.3px;border:1px solid rgba(255,255,255,.18);background:#ffffff1f;color:#d6d0c6;max-width:38%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.card-cluster-badge-identity{color:#fca5a5;border-color:#fca5a547;background:#fca5a514}.card-cluster-badge-long-term{color:#fdba74;border-color:#fdba7447;background:#fdba7414}.card-cluster-badge-journal{color:#fde68a;border-color:#fde68a47;background:#fde68a14}.card-cluster-badge-goals{color:#d6b388;border-color:#d6b38847;background:#d6b38814}.card-cluster-badge-technical{color:#93c5fd;border-color:#93c5fd47;background:#93c5fd14}.card-cluster-badge-thematic{color:#d8b4fe;border-color:#d8b4fe47;background:#d8b4fe14}.card-cluster-badge-knowledge{color:#86efac;border-color:#86efac47;background:#86efac14}.card-cluster-badge-system{color:#cbd5e1;border-color:#cbd5e147;background:#cbd5e114}.card-warning-panel{margin-bottom:6px;border:1px solid rgba(176,64,64,.2);border-radius:4px;background:#fff8f4eb;padding:6px 8px;font:500 9px/1.35 -apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;color:#6b443d;pointer-events:none}.card-warning-panel__header,.card-warning-panel__row{display:flex;align-items:center;justify-content:space-between;gap:8px}.card-warning-panel__header{font-weight:700;margin-bottom:4px}.card-warning-panel__privacy{color:#9c3d3d}.card-warning-panel__row+.card-warning-panel__row{margin-top:2px}.card-content{flex:1;overflow:hidden;font:400 9.5px/1.45 -apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;letter-spacing:.01em;white-space:pre-wrap;word-break:break-word;-webkit-mask-image:linear-gradient(to bottom,#000 60%,transparent 100%);mask-image:linear-gradient(to bottom,#000 60%,transparent 100%);opacity:.72}.card-session-log{animation:cardFadeInLog .5s ease-out both}.card-session-log:hover{opacity:.75}@keyframes cardFadeInLog{0%{opacity:0}to{opacity:.5}}.session-badge{font-size:9px;flex-shrink:0}.card-content-log{flex:1;overflow:hidden;font:400 8px/1.35 -apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;color:#aaa;white-space:pre-wrap;word-break:break-word;-webkit-mask-image:linear-gradient(to bottom,#000 40%,transparent 90%);mask-image:linear-gradient(to bottom,#000 40%,transparent 90%);opacity:.5}.stamp{flex-shrink:0;font:700 7px/1 -apple-system,sans-serif;letter-spacing:.5px;padding:2px 5px;border-radius:1px;pointer-events:none;border:1.5px solid;text-transform:uppercase;transform:rotate(-2deg);opacity:.75}.stamp-new,.stamp-mod{color:#a07030;border-color:#a07030;background:#a0703014}.stamp-synced{color:#9090a0;border-color:#9090a0;background:#9090a00f;opacity:.5}.stamp-local{color:#8d7e67;border-color:#8d7e67;background:#8d7e6714}.stamp-transient{opacity:.92}.stamp-queued{color:#7da2d6;border-color:#7da2d6;background:#7da2d61f}.stamp-syncing{color:#67d5c1;border-color:#67d5c1;background:#67d5c124}.stamp-done{color:#74c98a;border-color:#74c98a;background:#74c98a1f}.stamp-failed{color:#e58a8a;border-color:#e58a8a;background:#e58a8a24}.card-unselectable{opacity:.88}.card-checkbox-disabled{opacity:.35}.stamp-overlay{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%) rotate(-8deg);font:800 14px/1 -apple-system,Songti SC,"Noto Serif SC",serif;letter-spacing:2px;color:#b04040;border:2.5px solid #b04040;padding:4px 10px;border-radius:2px;opacity:.55;pointer-events:none;white-space:nowrap;text-transform:uppercase}.stamp-overlay.stamp-sealed{text-shadow:0 0 2px rgba(176,64,64,.3);box-shadow:inset 0 0 4px #b0404026}.card-sealed .card-content{filter:blur(2px);opacity:.4}.minimap{position:absolute;bottom:40px;right:12px;border-radius:6px;overflow:hidden;border:1px solid #2a2a36;box-shadow:0 4px 16px #0006;z-index:80;cursor:pointer;opacity:.85;transition:opacity .15s}.minimap:hover{opacity:1}.minimap-canvas{display:block;width:180px;height:120px}.viewport-root{flex:1;position:relative;overflow:hidden}.viewport{width:100%;height:100%;cursor:grab;-webkit-user-select:none;user-select:none;overflow:hidden}.viewport:active{cursor:grabbing}.canvas{position:absolute;top:0;left:0;transform-origin:0 0;will-change:transform}.section-label{position:absolute;font-size:13px;font-weight:700;letter-spacing:1.2px;text-transform:uppercase;opacity:.3;pointer-events:none;white-space:nowrap;display:flex;align-items:baseline;gap:8px}.section-count{font-size:11px;font-weight:500;opacity:.6}.zoom-indicator{position:absolute;bottom:8px;right:8px;font-size:11px;color:#555;background:#0a0a0fcc;padding:3px 8px;border-radius:4px;pointer-events:none;z-index:50}.reading-panel-wrapper{flex:1;display:flex;overflow:hidden;background:#faf9f6;animation:rpFadeIn .3s ease-out}@keyframes rpFadeIn{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}.reading-panel{flex:1;display:flex;flex-direction:column;max-width:800px;margin:0 auto;width:100%}.rp-header{flex-shrink:0;padding:24px 32px 16px;border-bottom:1px solid #e0ddd8;display:flex;flex-wrap:wrap;align-items:center;gap:8px 16px}.rp-back{flex-shrink:0;width:32px;height:32px;border:none;border-radius:6px;background:#0000000f;color:#666;font-size:16px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:background .15s,color .15s;margin-right:8px}.rp-back:hover{background:#0000001f;color:#333}.rp-title{font:700 24px/1.2 Georgia,"Noto Serif",serif;color:#2a2520;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rp-path{font:400 11px/1 -apple-system,sans-serif;color:#a09888;word-break:break-all;width:100%;padding-left:40px}.rp-body{flex:1;overflow-y:auto;padding:32px 32px 64px;font:400 16px/1.8 Georgia,"Noto Serif",serif;color:#3a3530;-webkit-font-smoothing:antialiased}.rp-h1{font-size:28px;font-weight:800;margin:36px 0 14px;color:#1a1510;line-height:1.3}.rp-h2{font-size:22px;font-weight:700;margin:32px 0 12px;color:#2a2520;line-height:1.3;border-bottom:1px solid #e8e4df;padding-bottom:8px}.rp-h3{font-size:18px;font-weight:700;margin:24px 0 8px;color:#3a3530;line-height:1.3}.rp-h4{font-size:16px;font-weight:700;margin:20px 0 6px;color:#4a4540;line-height:1.4}.rp-h5{font-size:15px;font-weight:700;margin:16px 0 4px;color:#5a5550}.rp-h6{font-size:13px;font-weight:600;margin:14px 0 4px;color:#6a6560;text-transform:uppercase;letter-spacing:.5px}.rp-p{margin:0 0 4px}.rp-spacer{height:14px}.rp-list{margin:4px 0 8px 24px;padding:0}.rp-list li{margin:4px 0}.rp-quote{margin:14px 0;padding:10px 20px;border-left:3px solid #c8b898;background:#c8b89814;color:#6a6050;font-style:italic}.rp-code-block{margin:14px 0;padding:16px 18px;background:#f0ede8;border-radius:6px;overflow-x:auto;font:400 13.5px/1.55 SF Mono,Fira Code,Consolas,monospace;color:#4a4540}.rp-inline-code{background:#ede9e3;padding:2px 6px;border-radius:3px;font:400 13.5px/1 SF Mono,Fira Code,Consolas,monospace;color:#6a5540}.rp-body a{color:#7c6a4f;text-decoration:underline;text-decoration-color:#7c6a4f4d;transition:text-decoration-color .15s}.rp-body a:hover{text-decoration-color:#7c6a4fcc}.rp-hr{border:none;border-top:1px solid #ddd8d0;margin:28px 0}.rp-body strong{font-weight:700;color:#2a2520}.rp-body em{font-style:italic}.rp-body del{text-decoration:line-through;opacity:.5}.rp-private{max-width:640px;margin:24px auto 0;border:1px solid #e3c8c1;border-radius:10px;background:#fff8f5;padding:24px 26px;box-shadow:0 10px 30px #82463c14}.rp-private__title{font:700 22px/1.25 Georgia,"Noto Serif",serif;color:#7a2f2f;margin-bottom:10px}.rp-private__copy{margin:0 0 14px}.rp-private__privacy{font:600 13px/1.4 -apple-system,sans-serif;color:#9c3d3d;margin-bottom:12px}.rp-private__row{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:8px 0;border-top:1px solid rgba(156,61,61,.12);font:600 13px/1.4 -apple-system,sans-serif}.rp-body::-webkit-scrollbar{width:6px}.rp-body::-webkit-scrollbar-track{background:transparent}.rp-body::-webkit-scrollbar-thumb{background:#d0ccc4;border-radius:3px}.rp-body::-webkit-scrollbar-thumb:hover{background:#b0aaa0}:root{--canvas-bg: #1a1a22;--chrome-bg: rgba(10, 10, 15, .92);--chrome-border: #2a2a36;--accent: #a78bfa;--setup-width: 340px}*,*:before,*:after{box-sizing:border-box;margin:0;padding:0}body{background:var(--canvas-bg);color:#e0e0f0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;height:100vh;overflow:hidden}#root{display:flex;flex-direction:column;height:100vh}.setup-sidebar{position:fixed;top:44px;left:0;bottom:32px;width:22px;z-index:120;transition:width .22s ease}.setup-sidebar:hover{width:var(--setup-width)}.setup-sidebar__rail{position:absolute;inset:0 auto 0 0;width:22px;display:flex;align-items:center;justify-content:center;writing-mode:vertical-rl;transform:rotate(180deg);background:#16161feb;border-right:1px solid var(--chrome-border);color:#9389c9;font-size:10px;letter-spacing:.18em;text-transform:uppercase}.setup-sidebar__panel{position:absolute;inset:0 auto 0 22px;width:calc(var(--setup-width) - 22px);padding:18px 16px 16px;background:linear-gradient(180deg,#101019fa,#0b0b12fa),radial-gradient(circle at top,rgba(167,139,250,.18),transparent 42%);border-right:1px solid var(--chrome-border);overflow-y:auto;transform:translate(calc(-100% - 22px));transition:transform .22s ease}.setup-sidebar:hover .setup-sidebar__panel{transform:translate(0)}.setup-sidebar__header{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;margin-bottom:14px}.setup-sidebar__header h2{font-size:16px;color:#f3efff;margin-bottom:4px}.setup-sidebar__header p{color:#aaa2c8;font-size:12px;line-height:1.45}.setup-pill{border:1px solid #50476a;border-radius:999px;color:#d0c7f4;font-size:10px;padding:5px 9px;white-space:nowrap}.setup-pill--ok{border-color:#2d6f4b;color:#9ff0be}.setup-card{background:#ffffff0a;border:1px solid rgba(255,255,255,.08);border-radius:12px;padding:12px;margin-bottom:12px;box-shadow:0 10px 30px #0000002e}.setup-card__title{color:#f1eaff;font-size:12px;font-weight:700;margin-bottom:8px;text-transform:uppercase;letter-spacing:.08em}.setup-copy{color:#c4bedc;font-size:12px;line-height:1.55}.setup-copy code{color:#fff;font-size:11px;word-break:break-all}.setup-steps{margin-left:16px;margin-bottom:8px;color:#d7d0ee;font-size:12px;line-height:1.6}.setup-field{display:flex;flex-direction:column;gap:5px;margin-bottom:10px}.setup-field span{color:#efe9ff;font-size:11px;font-weight:600}.setup-field input{background:#09090fbf;border:1px solid #3a3450;border-radius:10px;color:#f4f0ff;padding:10px 12px;font-size:12px;outline:none}.setup-field input:focus{border-color:var(--accent)}.setup-field small{color:#9087af;font-size:10px}.setup-save-btn{width:100%;border:1px solid #3d2f69;border-radius:10px;background:linear-gradient(135deg,#34255f,#5b43a0);color:#fff;font-weight:700;font-size:12px;padding:10px 12px;cursor:pointer}.setup-save-btn:disabled{opacity:.65;cursor:default}.setup-msg{margin-top:10px;font-size:11px;line-height:1.4}.setup-msg--ok{color:#8df0b2}.setup-msg--error{color:#ff9898}.hdr{height:44px;background:var(--chrome-bg);-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);border-bottom:1px solid var(--chrome-border);display:flex;align-items:center;padding:0 16px;gap:12px;z-index:100;flex-shrink:0}.hdr-icon{font-size:11px;letter-spacing:.1em;text-transform:uppercase;color:#8d84b8}.hdr-title{font-size:14px;font-weight:600;color:var(--accent);letter-spacing:.3px}.hdr-title-system{color:#9ca3af}.hdr-back{font-size:12px;color:#888;cursor:pointer;padding:3px 8px;border-radius:4px;transition:background .15s,color .15s}.hdr-back:hover{background:#ffffff14;color:var(--accent)}.hdr-search{background:#16161f;border:1px solid var(--chrome-border);border-radius:6px;color:#8f8fb0;font-size:12px;padding:5px 10px;width:180px;outline:none}.hdr-spacer{flex:1}.hdr-meta{font-size:11px;color:#555}.hdr-meta b{color:#777;font-weight:500}.hdr-conn{font-size:11px}.conn-ok{color:#4ade80}.conn-off{color:#f59e0b}.ftr{height:32px;background:var(--chrome-bg);-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);border-top:1px solid var(--chrome-border);display:flex;align-items:center;padding:0 16px;gap:16px;font-size:11px;color:#555;z-index:100;flex-shrink:0}.ftr b{color:#777;font-weight:500}.ftr-spacer{flex:1}.sync-btn{background:#2d1b69;color:var(--accent);border:1px solid #3b2a7a;border-radius:4px;padding:3px 12px;font-size:11px;font-weight:600;cursor:pointer}.sync-btn:hover{background:#3b2a7a}.sync-btn:disabled{opacity:.4;cursor:default}.ftr-system{color:#666;font-size:10px;cursor:pointer;padding:2px 8px;border-radius:4px;background:#ffffff0a;border:1px solid #2a2a36}.ftr-system:hover{background:#ffffff14;color:#888}.ftr-select-toggle{background:none;border:1px solid #333;border-radius:4px;color:#888;font-size:11px;padding:2px 8px;cursor:pointer}.ftr-select-toggle:hover{background:#ffffff0f;color:#bbb}.explore-btn{background:linear-gradient(135deg,#6d28d9,#a855f7);color:#fff;border:none;border-radius:4px;padding:3px 12px;font-size:11px;font-weight:600;cursor:pointer;text-decoration:none}.explore-btn:hover{opacity:.88;transform:translateY(-1px)}.explore-btn[aria-disabled=true]{opacity:.5;cursor:default;transform:none}.sync-result{color:#4ade80}.sync-error{color:#f87171}.sync-progress-dock{height:56px;background:#0d0d13f2;border-top:1px solid var(--chrome-border);border-bottom:1px solid var(--chrome-border);padding:10px 16px 8px;display:flex;flex-direction:column;gap:6px;z-index:100;flex-shrink:0}.sync-progress-top{display:flex;align-items:center;gap:12px;font-size:11px;color:#b8b8c8;flex-wrap:wrap}.sync-progress-title{color:#f0eff8;font-weight:700}.sync-progress-meta{color:#9a98ad}.sync-progress-track{width:100%;height:8px;border-radius:999px;background:#ffffff14;overflow:hidden}.sync-progress-fill{height:100%;border-radius:999px;background:linear-gradient(90deg,#4d6cff,#68dfc4);transition:width .18s ease}.sync-progress-detail{color:#f3b2b2;font-size:11px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.empty-state{flex:1;display:flex;align-items:center;justify-content:center;color:#666;font-size:14px}.selection-copy{color:#a78bfa}@media(max-width:900px){.setup-sidebar{width:18px}.setup-sidebar:hover{width:min(92vw,var(--setup-width))}.hdr-search{width:120px}.hdr-meta{display:none}}
|