@thingd/cli 0.76.0 → 0.77.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/dist/commands/cloud.d.ts +3 -0
- package/dist/commands/cloud.d.ts.map +1 -0
- package/dist/commands/cloud.js +669 -0
- package/dist/commands/mcp-connect.d.ts +3 -0
- package/dist/commands/mcp-connect.d.ts.map +1 -0
- package/dist/commands/mcp-connect.js +154 -0
- package/dist/commands/sync.d.ts +3 -0
- package/dist/commands/sync.d.ts.map +1 -0
- package/dist/commands/sync.js +197 -0
- package/dist/dashboard/public/assets/favicon-CgGFvG_0.svg +4 -0
- package/dist/dashboard/public/assets/index-C6PkDB7y.css +1 -0
- package/dist/dashboard/public/assets/index-DrpfyClj.js +4 -0
- package/dist/dashboard/public/index.html +19 -0
- package/dist/dashboard/server.d.ts +6 -0
- package/dist/dashboard/server.d.ts.map +1 -0
- package/dist/dashboard/server.js +684 -0
- package/dist/data-movement.d.ts +6 -0
- package/dist/data-movement.d.ts.map +1 -0
- package/dist/data-movement.js +475 -0
- package/dist/doctor.d.ts +3 -0
- package/dist/doctor.d.ts.map +1 -0
- package/dist/doctor.js +108 -0
- package/dist/index.d.ts +48 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1446 -0
- package/dist/install.d.ts +3 -0
- package/dist/install.d.ts.map +1 -0
- package/dist/install.js +229 -0
- package/dist/interactive.d.ts +2 -0
- package/dist/interactive.d.ts.map +1 -0
- package/dist/interactive.js +3039 -0
- package/dist/lib/cloud-api.d.ts +143 -0
- package/dist/lib/cloud-api.d.ts.map +1 -0
- package/dist/lib/cloud-api.js +207 -0
- package/dist/lib/cloud-config.d.ts +33 -0
- package/dist/lib/cloud-config.d.ts.map +1 -0
- package/dist/lib/cloud-config.js +42 -0
- package/dist/lib/mcp-config-writer.d.ts +26 -0
- package/dist/lib/mcp-config-writer.d.ts.map +1 -0
- package/dist/lib/mcp-config-writer.js +86 -0
- package/dist/lib/sync-config.d.ts +22 -0
- package/dist/lib/sync-config.d.ts.map +1 -0
- package/dist/lib/sync-config.js +25 -0
- package/dist/logo.d.ts +3 -0
- package/dist/logo.d.ts.map +1 -0
- package/dist/logo.js +8 -0
- package/dist/mcp/cluster.d.ts +69 -0
- package/dist/mcp/cluster.d.ts.map +1 -0
- package/dist/mcp/cluster.js +304 -0
- package/dist/mcp/config.d.ts +14 -0
- package/dist/mcp/config.d.ts.map +1 -0
- package/dist/mcp/config.js +67 -0
- package/dist/mcp/http.d.ts +35 -0
- package/dist/mcp/http.d.ts.map +1 -0
- package/dist/mcp/http.js +957 -0
- package/dist/mcp/index.d.ts +5 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +3 -0
- package/dist/mcp-http.d.ts +3 -0
- package/dist/mcp-http.d.ts.map +1 -0
- package/dist/mcp-http.js +42 -0
- package/dist/mcp.d.ts +3 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +28 -0
- package/dist/paths.d.ts +4 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +14 -0
- package/package.json +2 -2
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { createInterface } from "node:readline/promises";
|
|
2
|
+
import pc from "picocolors";
|
|
3
|
+
import { listInstances, listProjects } from "../lib/cloud-api.js";
|
|
4
|
+
import { readCloudConfig } from "../lib/cloud-config.js";
|
|
5
|
+
import { printMcpConfigJson, updateAntigravityConfig, updateClaudeDesktopConfig, } from "../lib/mcp-config-writer.js";
|
|
6
|
+
async function askQuestion(query) {
|
|
7
|
+
const rl = createInterface({
|
|
8
|
+
input: process.stdin,
|
|
9
|
+
output: process.stderr,
|
|
10
|
+
});
|
|
11
|
+
try {
|
|
12
|
+
return await rl.question(query);
|
|
13
|
+
}
|
|
14
|
+
finally {
|
|
15
|
+
rl.close();
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export async function runMcpConnect(context) {
|
|
19
|
+
const config = readCloudConfig();
|
|
20
|
+
if (!config?.token || !config.url) {
|
|
21
|
+
context.stderr.write(`${pc.red("Not logged in.")} Run ${pc.cyan("thingd cloud login")} first.\n`);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
// ── Fetch projects ────────────────────────────────────────────────
|
|
25
|
+
let projects;
|
|
26
|
+
try {
|
|
27
|
+
const result = await listProjects(config);
|
|
28
|
+
projects = result.projects;
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
context.stderr.write(pc.red(`Failed to fetch projects: ${err}\n`));
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (projects.length === 0) {
|
|
35
|
+
context.stderr.write(`${pc.red("No projects found.")} Create one with ${pc.cyan("thingd cloud project create <name>")}.\n`);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
context.stderr.write(`\n${pc.bold("Select a project")}\n`);
|
|
39
|
+
for (let i = 0; i < projects.length; i++) {
|
|
40
|
+
const p = projects[i];
|
|
41
|
+
if (p) {
|
|
42
|
+
context.stderr.write(` [${i + 1}] ${pc.cyan(p.slug)} ${p.name}\n`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const projectChoice = await askQuestion(`Select project [1-${projects.length}] (default 1): `);
|
|
46
|
+
const projectIndex = Math.max(0, Math.min(projects.length - 1, (Number(projectChoice.trim()) || 1) - 1));
|
|
47
|
+
const selectedProject = projects[projectIndex];
|
|
48
|
+
if (!selectedProject) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
context.stderr.write("\n");
|
|
52
|
+
// ── Fetch instances ───────────────────────────────────────────────
|
|
53
|
+
let instances;
|
|
54
|
+
try {
|
|
55
|
+
const result = await listInstances(config, selectedProject.id);
|
|
56
|
+
instances = result.instances;
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
context.stderr.write(pc.red(`Failed to fetch instances: ${err}\n`));
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (instances.length === 0) {
|
|
63
|
+
context.stderr.write(`${pc.red("No instances found for project.")} Create one with ${pc.cyan(`thingd cloud instance create ${selectedProject.slug} <name>`)}.\n`);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
context.stderr.write(`${pc.bold("Select an instance")}\n`);
|
|
67
|
+
for (let i = 0; i < instances.length; i++) {
|
|
68
|
+
const inst = instances[i];
|
|
69
|
+
if (inst) {
|
|
70
|
+
context.stderr.write(` [${i + 1}] ${pc.cyan(inst.slug)} ${inst.name} ${pc.dim(inst.mcpUrl || "no URL")}\n`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const instanceChoice = await askQuestion(`Select instance [1-${instances.length}] (default 1): `);
|
|
74
|
+
const instanceIndex = Math.max(0, Math.min(instances.length - 1, (Number(instanceChoice.trim()) || 1) - 1));
|
|
75
|
+
const selectedInstance = instances[instanceIndex];
|
|
76
|
+
if (!selectedInstance) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
context.stderr.write("\n");
|
|
80
|
+
// ── Pre-fill URL and token ────────────────────────────────────────
|
|
81
|
+
let mcpUrl = selectedInstance.mcpUrl || `${config.url}/mcp/${selectedProject.slug}/${selectedInstance.slug}`;
|
|
82
|
+
let authToken = config.token;
|
|
83
|
+
context.stderr.write(`${pc.bold("MCP Connection Details")}\n`);
|
|
84
|
+
const urlAnswer = await askQuestion(`MCP URL (default ${pc.cyan(mcpUrl)}): `);
|
|
85
|
+
if (urlAnswer.trim()) {
|
|
86
|
+
mcpUrl = urlAnswer.trim();
|
|
87
|
+
}
|
|
88
|
+
const tokenAnswer = await askQuestion(`Auth Token (default ${pc.cyan(`${authToken.slice(0, 8)}...`)}): `);
|
|
89
|
+
if (tokenAnswer.trim()) {
|
|
90
|
+
authToken = tokenAnswer.trim();
|
|
91
|
+
}
|
|
92
|
+
context.stderr.write("\n");
|
|
93
|
+
// ── Build config ──────────────────────────────────────────────────
|
|
94
|
+
const mcpConfig = {
|
|
95
|
+
url: mcpUrl,
|
|
96
|
+
headers: {
|
|
97
|
+
Authorization: `Bearer ${authToken}`,
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
// ── Preview ───────────────────────────────────────────────────────
|
|
101
|
+
context.stderr.write(`${pc.bold("Generated Config:")}\n`);
|
|
102
|
+
context.stderr.write(` ${pc.dim(printMcpConfigJson(mcpConfig).replace(/\n/g, "\n "))}\n\n`);
|
|
103
|
+
// ── Where to install ──────────────────────────────────────────────
|
|
104
|
+
context.stderr.write(`${pc.bold("Where would you like to write the MCP configuration?")}\n`);
|
|
105
|
+
context.stderr.write(` [1] Claude Desktop & Antigravity (Default)\n`);
|
|
106
|
+
context.stderr.write(` [2] Claude Desktop only\n`);
|
|
107
|
+
context.stderr.write(` [3] Antigravity only\n`);
|
|
108
|
+
context.stderr.write(` [4] Print raw JSON only\n`);
|
|
109
|
+
context.stderr.write(` [5] Print Cursor-compatible JSON\n\n`);
|
|
110
|
+
const choice = (await askQuestion("Select option [1-5] (default 1): ")).trim() || "1";
|
|
111
|
+
const showClaude = choice === "1" || choice === "2";
|
|
112
|
+
const showAntigravity = choice === "1" || choice === "3";
|
|
113
|
+
const showRaw = choice === "4";
|
|
114
|
+
const showCursor = choice === "5";
|
|
115
|
+
if (showClaude) {
|
|
116
|
+
const result = updateClaudeDesktopConfig(mcpConfig);
|
|
117
|
+
if (result.updated) {
|
|
118
|
+
context.stderr.write(` ${pc.bold("Claude Desktop:")}\n`);
|
|
119
|
+
context.stderr.write(` ${pc.green("✓")} Updated ${pc.cyan(result.path)}\n\n`);
|
|
120
|
+
}
|
|
121
|
+
else if (result.skipped) {
|
|
122
|
+
context.stderr.write(` ${pc.bold("Claude Desktop:")}\n`);
|
|
123
|
+
context.stderr.write(` ${pc.yellow("⊘")} Skipped: ${result.reason}\n\n`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (showAntigravity) {
|
|
127
|
+
const result = updateAntigravityConfig(mcpConfig);
|
|
128
|
+
if (result.updated) {
|
|
129
|
+
context.stderr.write(` ${pc.bold("Antigravity IDE:")}\n`);
|
|
130
|
+
context.stderr.write(` ${pc.green("✓")} Updated ${pc.cyan(result.path)}\n\n`);
|
|
131
|
+
}
|
|
132
|
+
else if (result.skipped) {
|
|
133
|
+
context.stderr.write(` ${pc.bold("Antigravity IDE:")}\n`);
|
|
134
|
+
context.stderr.write(` ${pc.yellow("⊘")} Skipped: ${result.reason}\n\n`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (showCursor) {
|
|
138
|
+
context.stderr.write(` ${pc.bold("Cursor:")}\n`);
|
|
139
|
+
context.stderr.write(` Paste this into Cursor Settings → Features → MCP → Add New MCP Server:\n\n`);
|
|
140
|
+
context.stdout.write(`${printMcpConfigJson(mcpConfig)}\n`);
|
|
141
|
+
}
|
|
142
|
+
if (showRaw) {
|
|
143
|
+
context.stdout.write(`${printMcpConfigJson(mcpConfig)}\n`);
|
|
144
|
+
}
|
|
145
|
+
if (showClaude) {
|
|
146
|
+
context.stderr.write(`\n Restart Claude Desktop or Antigravity to activate.\n\n`);
|
|
147
|
+
}
|
|
148
|
+
else if (showCursor) {
|
|
149
|
+
context.stderr.write(`\n Cursor activates immediately after pasting.\n\n`);
|
|
150
|
+
}
|
|
151
|
+
else if (showRaw) {
|
|
152
|
+
context.stderr.write("\n");
|
|
153
|
+
}
|
|
154
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/commands/sync.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAuI9C,wBAAsB,cAAc,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAkIvE"}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import pc from "picocolors";
|
|
2
|
+
import { hasFlag, requiredFlag, stringFlag, writeJson } from "../index.js";
|
|
3
|
+
import { readSyncConfig, removeSyncConfig, writeSyncConfig, } from "../lib/sync-config.js";
|
|
4
|
+
function endpoint(url, path) {
|
|
5
|
+
return `${url.replace(/\/$/, "")}${path}`;
|
|
6
|
+
}
|
|
7
|
+
async function request(url, token, init) {
|
|
8
|
+
const headers = new Headers(init?.headers);
|
|
9
|
+
headers.set("Accept", "application/json");
|
|
10
|
+
if (init?.body !== undefined) {
|
|
11
|
+
headers.set("Content-Type", "application/json");
|
|
12
|
+
}
|
|
13
|
+
if (token) {
|
|
14
|
+
headers.set("Authorization", `Bearer ${token}`);
|
|
15
|
+
}
|
|
16
|
+
const response = await fetch(url, { ...init, headers });
|
|
17
|
+
const body = (await response.json());
|
|
18
|
+
if (!response.ok) {
|
|
19
|
+
throw new Error(`${response.status}: ${JSON.stringify(body)}`);
|
|
20
|
+
}
|
|
21
|
+
return body.data ?? body;
|
|
22
|
+
}
|
|
23
|
+
function syncHeaders(config) {
|
|
24
|
+
const headers = {};
|
|
25
|
+
if (config.provider) {
|
|
26
|
+
headers["X-Thingd-Provider"] = config.provider;
|
|
27
|
+
}
|
|
28
|
+
if (config.projectId) {
|
|
29
|
+
headers["X-Thingd-Project-Id"] = config.projectId;
|
|
30
|
+
}
|
|
31
|
+
if (config.instanceSlug) {
|
|
32
|
+
headers["X-Thingd-Instance-Slug"] = config.instanceSlug;
|
|
33
|
+
}
|
|
34
|
+
if (config.sourceId) {
|
|
35
|
+
headers["X-Thingd-Source-Id"] = config.sourceId;
|
|
36
|
+
}
|
|
37
|
+
if (config.allowCloudTarget && config.targetConfirmed) {
|
|
38
|
+
headers["X-Thingd-Allow-Cloud-Target"] = "true";
|
|
39
|
+
}
|
|
40
|
+
return headers;
|
|
41
|
+
}
|
|
42
|
+
function requireConfig() {
|
|
43
|
+
const config = readSyncConfig();
|
|
44
|
+
if (!config) {
|
|
45
|
+
throw new Error("No sync configuration. Run `thingd sync configure` first.");
|
|
46
|
+
}
|
|
47
|
+
return config;
|
|
48
|
+
}
|
|
49
|
+
function sourceAndTarget(config) {
|
|
50
|
+
if (config.role === "source") {
|
|
51
|
+
return {
|
|
52
|
+
sourceUrl: config.localUrl,
|
|
53
|
+
sourceToken: config.localToken,
|
|
54
|
+
targetUrl: config.remoteUrl,
|
|
55
|
+
targetToken: config.remoteToken,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
sourceUrl: config.remoteUrl,
|
|
60
|
+
sourceToken: config.remoteToken,
|
|
61
|
+
targetUrl: config.localUrl,
|
|
62
|
+
targetToken: config.localToken,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
async function runSync(config) {
|
|
66
|
+
if (config.paused) {
|
|
67
|
+
return config;
|
|
68
|
+
}
|
|
69
|
+
const { sourceUrl, sourceToken, targetUrl, targetToken } = sourceAndTarget(config);
|
|
70
|
+
const page = await request(endpoint(sourceUrl, `/v1/replication/events?after=${config.cursor}&limit=500`), sourceToken, { headers: syncHeaders(config) });
|
|
71
|
+
if (page.changes.length > 0) {
|
|
72
|
+
await request(endpoint(targetUrl, "/v1/replication/apply"), targetToken, {
|
|
73
|
+
method: "POST",
|
|
74
|
+
body: JSON.stringify({ changes: page.changes }),
|
|
75
|
+
headers: syncHeaders(config),
|
|
76
|
+
});
|
|
77
|
+
return { ...config, cursor: page.next };
|
|
78
|
+
}
|
|
79
|
+
return config;
|
|
80
|
+
}
|
|
81
|
+
async function runBootstrap(config, replace) {
|
|
82
|
+
if (!config.role || config.role !== "replica") {
|
|
83
|
+
throw new Error("Bootstrap must run from a configured replica.");
|
|
84
|
+
}
|
|
85
|
+
const { sourceUrl, sourceToken, targetUrl, targetToken } = sourceAndTarget(config);
|
|
86
|
+
const snapshot = await request(endpoint(sourceUrl, "/v1/replication/snapshot"), sourceToken, {
|
|
87
|
+
headers: syncHeaders(config),
|
|
88
|
+
});
|
|
89
|
+
const effective = { ...config, sourceId: snapshot.sourceId };
|
|
90
|
+
await request(endpoint(targetUrl, "/v1/replication/snapshot"), targetToken, {
|
|
91
|
+
method: "POST",
|
|
92
|
+
headers: syncHeaders(effective),
|
|
93
|
+
body: JSON.stringify({ sourceId: snapshot.sourceId, snapshot, replace }),
|
|
94
|
+
});
|
|
95
|
+
return { ...effective, cursor: snapshot.cursor };
|
|
96
|
+
}
|
|
97
|
+
export async function runSyncCommand(context) {
|
|
98
|
+
const subcommand = context.parsed.tokens[1] ?? "status";
|
|
99
|
+
if (subcommand === "configure") {
|
|
100
|
+
const role = stringFlag(context.parsed, "role");
|
|
101
|
+
if (role !== "source" && role !== "replica") {
|
|
102
|
+
throw new Error("Sync role is required. Choose --role source or --role replica; cloud authority is never inferred.");
|
|
103
|
+
}
|
|
104
|
+
const provider = stringFlag(context.parsed, "provider") ?? "self-hosted";
|
|
105
|
+
const allowCloudTarget = hasFlag(context.parsed, "allow-cloud-target");
|
|
106
|
+
const targetConfirmed = hasFlag(context.parsed, "confirm-target");
|
|
107
|
+
if (provider === "thingd.cloud" && role === "source" && !allowCloudTarget) {
|
|
108
|
+
throw new Error("Cloud targets are protected by default. Use --allow-cloud-target --confirm-target only for an explicitly authorized cloud replica.");
|
|
109
|
+
}
|
|
110
|
+
const config = {
|
|
111
|
+
localUrl: requiredFlag(context.parsed, "local-url"),
|
|
112
|
+
remoteUrl: requiredFlag(context.parsed, "remote-url"),
|
|
113
|
+
localToken: stringFlag(context.parsed, "local-token"),
|
|
114
|
+
remoteToken: stringFlag(context.parsed, "remote-token"),
|
|
115
|
+
role,
|
|
116
|
+
cursor: 0,
|
|
117
|
+
paused: false,
|
|
118
|
+
provider,
|
|
119
|
+
projectId: stringFlag(context.parsed, "project-id"),
|
|
120
|
+
instanceSlug: stringFlag(context.parsed, "instance-slug"),
|
|
121
|
+
sourceId: stringFlag(context.parsed, "source-id"),
|
|
122
|
+
allowCloudTarget,
|
|
123
|
+
targetConfirmed,
|
|
124
|
+
};
|
|
125
|
+
writeSyncConfig(config);
|
|
126
|
+
writeJson(context.stdout, { configured: true, role: config.role, provider }, context.pretty);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (subcommand === "status") {
|
|
130
|
+
const config = readSyncConfig();
|
|
131
|
+
if (!config) {
|
|
132
|
+
writeJson(context.stdout, { configured: false }, context.pretty);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if (!config.role) {
|
|
136
|
+
writeJson(context.stdout, {
|
|
137
|
+
configured: true,
|
|
138
|
+
needsReconfigure: true,
|
|
139
|
+
message: "Sync role is missing; re-run configure with --role.",
|
|
140
|
+
}, context.pretty);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
const normalized = config;
|
|
144
|
+
const { sourceUrl, sourceToken, targetUrl, targetToken } = sourceAndTarget(normalized);
|
|
145
|
+
const [source, target] = await Promise.all([
|
|
146
|
+
request(endpoint(sourceUrl, "/v1/replication/status"), sourceToken, {
|
|
147
|
+
headers: syncHeaders(normalized),
|
|
148
|
+
}),
|
|
149
|
+
request(endpoint(targetUrl, "/v1/replication/status"), targetToken, {
|
|
150
|
+
headers: syncHeaders(normalized),
|
|
151
|
+
}),
|
|
152
|
+
]);
|
|
153
|
+
writeJson(context.stdout, { configured: true, config: normalized, source, target }, context.pretty);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
if (subcommand === "pause" || subcommand === "resume") {
|
|
157
|
+
const config = requireConfig();
|
|
158
|
+
writeSyncConfig({ ...config, paused: subcommand === "pause" });
|
|
159
|
+
writeJson(context.stdout, { paused: subcommand === "pause" }, context.pretty);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
if (subcommand === "reset") {
|
|
163
|
+
const config = requireConfig();
|
|
164
|
+
removeSyncConfig();
|
|
165
|
+
writeJson(context.stdout, { reset: true, previousCursor: config.cursor }, context.pretty);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (subcommand === "push" || subcommand === "pull") {
|
|
169
|
+
const config = requireConfig();
|
|
170
|
+
if (!config.role) {
|
|
171
|
+
throw new Error("Sync configuration has no role. Re-run `thingd sync configure --role ...`.");
|
|
172
|
+
}
|
|
173
|
+
if (config.role === "source" && subcommand === "pull") {
|
|
174
|
+
throw new Error("A source instance pushes changes; configure the replica and run `sync pull` there.");
|
|
175
|
+
}
|
|
176
|
+
if (config.role === "replica" && subcommand === "push") {
|
|
177
|
+
throw new Error("A replica pulls changes; configure the source and run `sync push` there.");
|
|
178
|
+
}
|
|
179
|
+
const next = await runSync(config);
|
|
180
|
+
writeSyncConfig(next);
|
|
181
|
+
writeJson(context.stdout, { synced: next.cursor !== config.cursor, cursor: next.cursor, role: next.role }, context.pretty);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
if (subcommand === "bootstrap") {
|
|
185
|
+
const config = requireConfig();
|
|
186
|
+
const next = await runBootstrap(config, hasFlag(context.parsed, "replace"));
|
|
187
|
+
writeSyncConfig(next);
|
|
188
|
+
writeJson(context.stdout, {
|
|
189
|
+
bootstrapped: true,
|
|
190
|
+
cursor: next.cursor,
|
|
191
|
+
sourceId: next.sourceId,
|
|
192
|
+
replaced: hasFlag(context.parsed, "replace"),
|
|
193
|
+
}, context.pretty);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
throw new Error(`${pc.red(`Unknown sync command: ${subcommand}`)}. Use configure, status, push, pull, bootstrap, pause, resume, or reset.`);
|
|
197
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" role="img" aria-label="thingd">
|
|
2
|
+
<rect width="48" height="48" rx="9" fill="#0b0d19"/>
|
|
3
|
+
<text x="24" y="32" font-family="ui-monospace, SFMono-Regular, Menlo, monospace" font-size="17" font-weight="700" text-anchor="middle"><tspan fill="#6366f1">{</tspan><tspan fill="#06b6d4">t</tspan><tspan fill="#a855f7">:d}</tspan></text>
|
|
4
|
+
</svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
:root{--bg-base:#0b0d19;--bg-sidebar:#0a0c1ad9;--glass-bg:#111428a6;--glass-border:#ffffff14;--glass-highlight:#ffffff0a;--color-primary:#6366f1;--color-primary-glow:#6366f140;--color-primary-hover:#4f46e5;--color-accent:#a855f7;--color-cyan:#06b6d4;--text-main:#f3f4f6;--text-muted:#9ca3af;--text-dark:#6b7280;--color-success:#10b981;--color-success-glow:#10b98126;--color-warning:#f59e0b;--color-warning-glow:#f59e0b26;--color-danger:#ef4444;--color-danger-glow:#ef444426;--font-sans:"Plus Jakarta Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;--font-mono:"JetBrains Mono", source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace;--radius-sm:8px;--radius-md:12px;--radius-lg:16px;--shadow-premium:0 8px 32px 0 #0000005e;--shadow-glow:0 0 15px #6366f166}*{box-sizing:border-box;font-family:var(--font-sans);margin:0;padding:0}body,html{background-color:var(--bg-base);height:100%;color:var(--text-main);background-image:radial-gradient(circle at 10% 20%,#6366f114 0%,#0000 40%),radial-gradient(circle at 85% 80%,#a855f712 0%,#0000 45%);background-attachment:fixed;font-size:14px;overflow:hidden}.app-container{width:100vw;height:100vh;display:flex;position:relative}.sidebar{background:var(--bg-sidebar);border-right:1px solid var(--glass-border);-webkit-backdrop-filter:blur(20px);backdrop-filter:blur(20px);z-index:10;flex-direction:column;width:260px;display:flex}.sidebar-brand{border-bottom:1px solid var(--glass-border);align-items:center;gap:12px;padding:24px;display:flex}.brand-logo{background:linear-gradient(135deg, var(--color-primary), var(--color-accent));color:#fff;text-transform:uppercase;border-radius:8px;justify-content:center;align-items:center;width:32px;height:32px;font-size:14px;font-weight:700;display:flex;box-shadow:0 4px 12px #6366f14d}.brand-text{letter-spacing:-.5px;align-items:center;gap:6px;font-size:18px;font-weight:700;display:flex}.version-badge{color:var(--text-muted);background:#ffffff14;border-radius:4px;padding:2px 6px;font-size:10px;font-weight:500}.sidebar-nav{flex-direction:column;flex:1;gap:6px;padding:20px 12px;display:flex}.nav-item{color:var(--text-muted);border-radius:var(--radius-sm);cursor:pointer;text-align:left;background:0 0;border:none;align-items:center;gap:12px;width:100%;padding:12px 16px;font-size:13.5px;font-weight:500;transition:all .25s;display:flex}.nav-item:hover{background:var(--glass-highlight);color:var(--text-main)}.nav-item.active{background:var(--color-primary-glow);color:#fff;font-weight:600;box-shadow:inset 0 0 0 1px #6366f14d}.nav-icon{fill:currentColor;opacity:.85;width:18px;height:18px}.sidebar-footer{border-top:1px solid var(--glass-border);padding:16px 20px}.status-indicator{color:var(--text-muted);align-items:center;gap:10px;font-size:12px;display:flex}.pulse-dot{background-color:var(--color-success);border-radius:50%;width:8px;height:8px;position:relative}.pulse-dot:after{content:"";background-color:var(--color-success);border-radius:50%;width:100%;height:100%;animation:1.8s infinite pulse-ring;position:absolute;top:0;left:0}@keyframes pulse-ring{0%{opacity:.8;transform:scale(1)}to{opacity:0;transform:scale(2.8)}}.main-panel{flex-direction:column;flex:1;height:100%;display:flex;position:relative;overflow:hidden}.topbar{border-bottom:1px solid var(--glass-border);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);z-index:5;background:#0b0d1966;justify-content:space-between;align-items:center;height:70px;min-height:70px;padding:0 32px;display:flex}.topbar h1{letter-spacing:-.3px;font-size:20px;font-weight:700}.connection-meta{background:var(--glass-highlight);border:1px solid var(--glass-border);color:var(--text-muted);white-space:nowrap;border-radius:30px;align-items:center;gap:8px;padding:6px 16px;font-size:12px;display:flex;box-shadow:inset 0 1px #ffffff0d}.meta-label{color:var(--text-dark);text-transform:uppercase;letter-spacing:.5px;font-size:10px;font-weight:500}.meta-val{color:var(--text-main);font-weight:600}.meta-val.path-val{text-overflow:ellipsis;white-space:nowrap;vertical-align:middle;max-width:260px;transition:max-width .2s;display:inline-block;overflow:hidden}@media (width<=1024px){.meta-val.path-val{max-width:160px}}@media (width<=768px){.meta-val.path-val{max-width:100px}}.meta-sep{opacity:.2;margin:0 4px}.topbar-right{align-items:center;gap:16px;display:flex}.view-viewport{flex:1;padding:32px;position:relative;overflow:hidden}.tab-panel{opacity:0;height:100%;transition:all .35s cubic-bezier(.16,1,.3,1);display:none;transform:translateY(8px)}.tab-panel.active{opacity:1;flex-direction:column;gap:24px;display:flex;transform:translateY(0)}.card{background:var(--glass-bg);border:1px solid var(--glass-border);border-radius:var(--radius-lg);box-shadow:var(--shadow-premium);-webkit-backdrop-filter:blur(24px);backdrop-filter:blur(24px);flex-direction:column;display:flex;position:relative;overflow:hidden}.card:before{content:"";background:linear-gradient(90deg,#ffffff1f,#0000);height:1px;position:absolute;top:0;left:0;right:0}.card-header{border-bottom:1px solid var(--glass-border);padding:20px 24px}.card-header h2,.card-header h3{color:#fff;font-size:16px;font-weight:600}.card-body{flex:1;padding:24px}.card.fill-height{height:100%}.split-layout{flex:1;gap:24px;height:100%;display:flex;overflow:hidden}.list-side{flex-direction:column;width:280px;min-width:280px;display:flex}.inspect-side{flex-direction:column;flex:1;display:flex}.scroll-y{overflow-y:auto}.scroll-x{overflow-x:auto}::-webkit-scrollbar{width:6px;height:6px}::-webkit-scrollbar-track{background:0 0}::-webkit-scrollbar-thumb{background:#ffffff1a;border-radius:10px}::-webkit-scrollbar-thumb:hover{background:#fff3}.btn{border-radius:var(--radius-sm);cursor:pointer;color:#fff;border:1px solid #0000;justify-content:center;align-items:center;gap:8px;padding:8px 16px;font-size:13px;font-weight:600;text-decoration:none;transition:all .2s;display:inline-flex}.btn-sm{padding:5px 10px;font-size:11px}.btn-primary{background:linear-gradient(135deg, var(--color-primary), var(--color-accent));box-shadow:0 4px 14px #6366f14d}.btn-primary:hover:not(:disabled){box-shadow:0 6px 18px #6366f173, var(--shadow-glow);transform:translateY(-1px)}.btn-secondary{background:var(--glass-highlight);border-color:var(--glass-border);color:var(--text-main)}.btn-secondary:hover:not(:disabled){background:#ffffff14;border-color:#fff3}.btn-success{background:linear-gradient(135deg, var(--color-success), #059669);box-shadow:0 4px 12px #10b98133}.btn-success:hover{box-shadow:0 6px 16px #10b98159}.btn-warning{background:linear-gradient(135deg, var(--color-warning), #d97706);box-shadow:0 4px 12px #f59e0b33}.btn-warning:hover{box-shadow:0 6px 16px #f59e0b59}.btn-danger{background:linear-gradient(135deg, var(--color-danger), #dc2626);box-shadow:0 4px 12px #ef444433}.btn-danger:hover{box-shadow:0 6px 16px #ef444459}.btn:disabled{opacity:.4;cursor:not-allowed}.metrics-grid{grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:20px;display:grid}.metric-card{background:var(--glass-bg);border:1px solid var(--glass-border);border-radius:var(--radius-lg);box-shadow:var(--shadow-premium);-webkit-backdrop-filter:blur(20px);backdrop-filter:blur(20px);align-items:center;gap:20px;padding:24px;display:flex;position:relative;overflow:hidden}.metric-card:before{content:"";background:linear-gradient(90deg,#ffffff0f,#0000);height:1px;position:absolute;top:0;left:0;right:0}.metric-icon{border-radius:var(--radius-md);justify-content:center;align-items:center;width:48px;height:48px;display:flex}.metric-icon svg{fill:#fff;width:24px;height:24px}.objects-icon{background:#6366f126;border:1px solid #6366f14d}.objects-icon svg{fill:var(--color-primary)}.events-icon{background:#a855f726;border:1px solid #a855f74d}.events-icon svg{fill:var(--color-accent)}.queues-icon{background:#06b6d426;border:1px solid #06b6d44d}.queues-icon svg{fill:var(--color-cyan)}.dead-icon{background:#ef444426;border:1px solid #ef44444d}.dead-icon svg{fill:var(--color-danger)}.metric-body{flex-direction:column;display:flex}.metric-num{color:#fff;letter-spacing:-.5px;font-size:28px;font-weight:700;line-height:1.1}.metric-title{color:var(--text-muted);margin-top:4px;font-size:12px;font-weight:500}.row-flex{gap:24px;display:flex}.gap-lg{gap:24px}.gap-md{gap:16px}.gap-sm{gap:8px}.flex-1{flex:1}.flex-2{flex:2}.flex-3{flex:3}.flex-column{flex-direction:column;display:flex}.flex-header,.side-header{justify-content:space-between;align-items:center;display:flex}.info-table{flex-direction:column;gap:16px;display:flex}.info-row{border-bottom:1px solid #ffffff0a;justify-content:space-between;align-items:center;padding-bottom:12px;display:flex}.info-row:last-child{border-bottom:none;padding-bottom:0}.info-label{color:var(--text-muted);font-weight:500}.info-val{color:#fff;font-weight:600}.info-val.code{font-family:var(--font-mono);word-break:break-all;background:#00000040;border-radius:4px;padding:4px 8px;font-size:12px}.terminal-panel{border:1px solid var(--glass-border);font-family:var(--font-mono);background:#05060c99;flex-direction:column;display:flex;padding:0!important}.terminal-header{background:#ffffff08;border-bottom:1px solid #ffffff0d;align-items:center;gap:6px;padding:8px 16px;display:flex}.terminal-dot{border-radius:50%;width:10px;height:10px}.terminal-dot.red{background-color:var(--color-danger)}.terminal-dot.yellow{background-color:var(--color-warning)}.terminal-dot.green{background-color:var(--color-success)}.terminal-title{color:var(--text-dark);text-transform:uppercase;margin-left:6px;font-size:11px;font-weight:600}.terminal-body{flex:1;padding:16px;font-size:12px;line-height:1.6;overflow-y:auto}.log-line{color:var(--text-main);word-break:break-all;margin-bottom:6px}.text-muted{color:var(--text-muted)!important}.text-success{color:var(--color-success)!important}.text-warning{color:var(--color-warning)!important}.text-danger{color:var(--color-danger)!important}.text-center{text-align:center}.sidebar-list-item{border-radius:var(--radius-sm);color:var(--text-muted);cursor:pointer;text-align:left;background:0 0;border:none;justify-content:space-between;align-items:center;width:100%;margin-bottom:4px;padding:12px 16px;font-weight:500;transition:all .2s;display:flex}.sidebar-list-item:hover{color:var(--text-main);background:#ffffff0a}.sidebar-list-item.active{color:#fff;background:#ffffff0f;border:1px solid #ffffff14;font-weight:600}.item-count-badge{font-family:var(--font-mono);color:var(--color-cyan);background:#0000004d;border-radius:10px;padding:2px 6px;font-size:11px}.scroll-table-wrapper{flex:1;width:100%;overflow:auto}.data-table{border-collapse:collapse;text-align:left;width:100%;font-size:13px}.data-table th{color:var(--text-muted);border-bottom:1px solid var(--glass-border);z-index:1;background:#0000001a;padding:14px 16px;font-weight:600;position:sticky;top:0}.data-table td{color:var(--text-main);vertical-align:middle;border-bottom:1px solid #ffffff08;padding:14px 16px}.data-table tr:hover td{background:#ffffff05}.data-table .code-cell{font-family:var(--font-mono);text-overflow:ellipsis;white-space:nowrap;max-width:250px;font-size:11.5px;overflow:hidden}.data-table .text-cell{white-space:nowrap;text-overflow:ellipsis;max-width:320px;overflow:hidden}.timeline-viewport{padding:12px}.timeline{flex-direction:column;gap:16px;padding-left:20px;display:flex;position:relative}.timeline:before{content:"";background:#ffffff0d;width:2px;position:absolute;top:8px;bottom:8px;left:5px}.timeline-item{position:relative}.timeline-marker{background:var(--bg-base);border:2px solid var(--color-primary);z-index:2;border-radius:50%;width:12px;height:12px;transition:all .2s;position:absolute;top:6px;left:-20px}.timeline-item:hover .timeline-marker{box-shadow:0 0 10px var(--color-primary);transform:scale(1.3)}.timeline-card{border-radius:var(--radius-md);background:#ffffff05;border:1px solid #ffffff0a;padding:16px;transition:all .2s}.timeline-card:hover{background:#ffffff08;border-color:#ffffff14}.timeline-header{justify-content:space-between;align-items:center;margin-bottom:8px;display:flex}.timeline-title{align-items:center;gap:10px;display:flex}.timeline-badge{text-transform:uppercase;letter-spacing:.3px;background:#ffffff0f;border-radius:30px;padding:2px 8px;font-size:11px;font-weight:700}.timeline-time{color:var(--text-dark);font-size:11px}.timeline-text{color:var(--text-main);margin-bottom:8px;font-size:13.5px;line-height:1.5}.timeline-data{font-family:var(--font-mono);border-radius:var(--radius-sm);color:var(--text-muted);white-space:pre-wrap;background:#00000040;border:1px solid #ffffff08;padding:8px 12px;font-size:11px}.timeline-empty{text-align:center;color:var(--text-muted);padding:40px}.tabs-subnav{border-bottom:1px solid var(--glass-border);gap:8px;margin-bottom:20px;display:flex}.subnav-item{color:var(--text-muted);cursor:pointer;background:0 0;border:none;border-bottom:2px solid #0000;padding:10px 16px;font-size:13px;font-weight:600;transition:all .2s}.subnav-item:hover{color:var(--text-main)}.subnav-item.active{color:var(--color-primary);border-bottom-color:var(--color-primary)}.subtab-content{flex:1;display:none}.subtab-content.active{flex-direction:column;display:flex}.badge{text-transform:uppercase;border-radius:20px;align-items:center;padding:2px 8px;font-size:11px;font-weight:600;display:inline-flex}.badge-ready{background-color:var(--color-success-glow);color:var(--color-success)}.badge-leased{color:#818cf8;background-color:#6366f126}.badge-dead{background-color:var(--color-danger-glow);color:var(--color-danger)}.search-form-grid{gap:16px;margin-bottom:20px;display:flex}.form-group{flex-direction:column;gap:8px;margin-bottom:16px;display:flex}.form-group label{color:var(--text-muted);text-transform:uppercase;letter-spacing:.5px;font-size:12px;font-weight:600}.form-input{border:1px solid var(--glass-border);border-radius:var(--radius-sm);color:#fff;background:#00000040;outline:none;padding:10px 14px;font-size:13px;transition:all .2s}.form-input:focus{border-color:var(--color-primary);box-shadow:0 0 0 3px var(--color-primary-glow)}.form-input.code{font-family:var(--font-mono);font-size:12px}.search-input-group{gap:10px;display:flex}.search-input{flex:1}.search-results-section{border-top:1px solid var(--glass-border);flex-direction:column;flex:1;margin-top:8px;padding-top:24px;display:flex}.section-title{color:var(--text-muted);margin-bottom:16px;font-size:13.5px;font-weight:600}.highlighted-match{color:var(--color-warning);background:#f59e0b33;border-radius:4px;padding:2px 4px;font-weight:600}.modal-overlay{-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);z-index:100;opacity:0;pointer-events:none;background:#04050ab3;justify-content:center;align-items:center;transition:all .3s cubic-bezier(.16,1,.3,1);display:flex;position:fixed;inset:0}.modal-overlay.active{opacity:1;pointer-events:auto}.modal-card{background:var(--glass-bg);border:1px solid var(--glass-border);border-radius:var(--radius-lg);width:480px;max-width:90%;box-shadow:var(--shadow-premium);flex-direction:column;transition:all .3s cubic-bezier(.16,1,.3,1);display:flex;transform:translateY(20px)}.modal-overlay.active .modal-card{transform:translateY(0)}.modal-card.wide{width:680px}.modal-header{border-bottom:1px solid var(--glass-border);justify-content:space-between;align-items:center;padding:20px 24px;display:flex}.modal-header h3{color:#fff;font-size:16px;font-weight:600}.modal-close-btn{color:var(--text-muted);cursor:pointer;background:0 0;border:none;font-size:24px;line-height:1;transition:color .2s}.modal-close-btn:hover{color:#fff}.modal-body{padding:24px}.modal-footer{justify-content:flex-end;gap:12px;margin-top:24px;display:flex}.input-tip{color:var(--text-dark);margin-top:4px;font-size:11.5px}.code-view{font-family:var(--font-mono);border-radius:var(--radius-sm);border:1px solid var(--glass-border);color:var(--text-main);white-space:pre-wrap;background:#0000004d;padding:12px;font-size:11.5px}.job-detail-card{border-radius:var(--radius-md);background:#ffffff04;border:1px solid #ffffff08;flex-direction:column;gap:14px;margin-bottom:20px;padding:16px;display:flex}.info-group{flex-direction:column;gap:6px;display:flex}.info-group label{color:var(--text-dark);text-transform:uppercase;font-size:11px;font-weight:700}.ops-section-title{color:var(--text-muted);text-transform:uppercase;margin-bottom:12px;font-size:12.5px;font-weight:600}.queue-stats-bar{gap:8px;margin-top:4px;display:flex}.toast-container{z-index:1000;flex-direction:column;gap:10px;display:flex;position:fixed;bottom:24px;right:24px}.toast{background:var(--glass-bg);border-left:4px solid var(--color-primary);border-top:1px solid var(--glass-border);border-bottom:1px solid var(--glass-border);border-right:1px solid var(--glass-border);border-radius:var(--radius-sm);box-shadow:var(--shadow-premium);-webkit-backdrop-filter:blur(20px);backdrop-filter:blur(20px);align-items:center;gap:12px;min-width:250px;max-width:380px;padding:14px 20px;animation:.3s cubic-bezier(.16,1,.3,1) forwards slide-in;display:flex}.toast.success{border-left-color:var(--color-success)}.toast.warning{border-left-color:var(--color-warning)}.toast.error{border-left-color:var(--color-danger)}.toast-message{color:#fff;font-size:13px;font-weight:500}@keyframes slide-in{0%{opacity:0;transform:translate(50px)}to{opacity:1;transform:translate(0)}}.loader{border:2.5px solid #ffffff0d;border-top:2.5px solid var(--color-primary);border-radius:50%;width:24px;height:24px;margin:20px auto;animation:1s linear infinite spin}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.empty-state{text-align:center;color:var(--text-muted);padding:40px}.mt-md{margin-top:16px}@keyframes flash-highlight{0%{background-color:#ffd54f66}to{background-color:#0000}}.flash-highlight{animation:1.5s ease-out flash-highlight}tbody tr,.sidebar-list-item,.timeline-item,.log-line{will-change:transform, opacity}.json-editor-container.svelte-1w5oz74{flex-direction:column;width:100%;margin-bottom:8px;display:flex}.json-editor-header.svelte-1w5oz74{justify-content:space-between;align-items:center;margin-bottom:8px;display:flex}.json-editor-label.svelte-1w5oz74{color:var(--text-muted);text-transform:uppercase;letter-spacing:.5px;font-size:12px;font-weight:600}.json-editor-tabs.svelte-1w5oz74{border-radius:var(--radius-sm);border:1px solid var(--glass-border);background:#00000040;gap:4px;padding:2px;display:flex}.json-tab-btn.svelte-1w5oz74{color:var(--text-muted);cursor:pointer;background:0 0;border:none;border-radius:4px;align-items:center;gap:6px;padding:4px 10px;font-size:11.5px;font-weight:600;transition:all .2s;display:inline-flex}.json-tab-btn.svelte-1w5oz74:hover{color:var(--text-main);background:#ffffff08}.json-tab-btn.active.svelte-1w5oz74{color:#fff;background:#6366f126;box-shadow:inset 0 0 0 1px #6366f140}.tab-icon.svelte-1w5oz74{fill:currentColor;width:12px;height:12px}.json-editor-content.svelte-1w5oz74{width:100%}.raw-editor-view.svelte-1w5oz74{flex-direction:column;width:100%;display:flex}.raw-textarea.svelte-1w5oz74{resize:vertical;width:100%;min-height:110px}.raw-textarea.invalid-border.svelte-1w5oz74{border-color:#ef444480}.raw-textarea.invalid-border.svelte-1w5oz74:focus{border-color:var(--color-danger);box-shadow:0 0 0 3px var(--color-danger-glow)}.json-editor-status-bar.svelte-1w5oz74{justify-content:space-between;align-items:center;margin-top:6px;padding:0 2px;display:flex}.status-badge.svelte-1w5oz74{align-items:center;gap:6px;font-size:11.5px;font-weight:600;display:flex}.status-dot.svelte-1w5oz74{border-radius:50%;width:6px;height:6px}.status-badge.valid.svelte-1w5oz74{color:var(--color-success)}.status-badge.valid.svelte-1w5oz74 .status-dot:where(.svelte-1w5oz74){background-color:var(--color-success);box-shadow:0 0 6px var(--color-success)}.status-badge.invalid.svelte-1w5oz74{color:var(--color-danger);cursor:help}.status-badge.invalid.svelte-1w5oz74 .status-dot:where(.svelte-1w5oz74){background-color:var(--color-danger);box-shadow:0 0 6px var(--color-danger)}.error-msg-banner.svelte-1w5oz74{border-radius:var(--radius-sm);color:#fca5a5;font-size:11.5px;font-family:var(--font-mono);word-break:break-all;background:#ef444414;border:1px solid #ef444426;margin-top:8px;padding:8px 12px;line-height:1.4}.visual-editor-view.svelte-1w5oz74{width:100%}.error-visual-fallback.svelte-1w5oz74{background:#ef444408;border-color:#ef44441a}.visual-rows-container.svelte-1w5oz74{border:1px solid var(--glass-border);border-radius:var(--radius-sm);background:#00000026;flex-direction:column;gap:8px;max-height:280px;padding:12px;display:flex;overflow-y:auto}.empty-rows-state.svelte-1w5oz74{text-align:center;color:var(--text-muted);padding:24px 12px;font-size:12px}.rows-header-grid.svelte-1w5oz74{border-bottom:1px solid var(--glass-border);grid-template-columns:140px 100px 1fr 34px;gap:8px;padding:0 4px 6px;display:grid}.header-col.svelte-1w5oz74{color:var(--text-dark);text-transform:uppercase;letter-spacing:.5px;font-size:10px;font-weight:700}.rows-list.svelte-1w5oz74{flex-direction:column;gap:8px;margin-top:8px;display:flex}.row-builder-item.svelte-1w5oz74{grid-template-columns:140px 100px 1fr 34px;align-items:center;gap:8px;display:grid}.row-builder-item.svelte-1w5oz74 .form-input{padding:6px 10px;font-size:12.5px}.key-input.svelte-1w5oz74{font-family:var(--font-mono)}.type-select.svelte-1w5oz74{cursor:pointer}.value-input-wrapper.svelte-1w5oz74{width:100%;display:flex}.val-input.svelte-1w5oz74,.value-select.svelte-1w5oz74{width:100%}.null-placeholder.svelte-1w5oz74{width:100%;color:var(--text-dark);border:1px dashed var(--glass-border);border-radius:var(--radius-sm);background:#00000026;padding:6px 10px;font-size:12.5px;font-style:italic}.val-textarea.svelte-1w5oz74{resize:vertical;width:100%;min-height:31px;padding-top:6px;padding-bottom:6px}.remove-row-btn.svelte-1w5oz74{border-radius:var(--radius-sm);justify-content:center;align-items:center;width:32px;height:32px;padding:0;display:flex}.remove-row-btn.svelte-1w5oz74:hover{background:var(--color-danger-glow);color:var(--color-danger);border-color:#ef44444d}.visual-actions-footer.svelte-1w5oz74{border-top:1px solid #ffffff0a;justify-content:flex-start;margin-top:10px;padding-top:10px;display:flex}
|