@kvell007/embed-labs-cli 0.1.0-alpha.72 → 0.1.0-alpha.74
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 +23 -87
- package/dist/embed-labs-mcp-bridge.mjs +182 -2315
- package/dist/index.js +186 -2442
- package/dist/index.js.map +1 -1
- package/dist/local-toolchain.js +8 -2
- package/dist/local-toolchain.js.map +1 -1
- package/package.json +3 -3
- package/dist/install-progress.d.ts +0 -2
- package/dist/install-progress.js +0 -75
- package/dist/install-progress.js.map +0 -1
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
|
-
import {
|
|
4
|
-
import { copyFileSync, cpSync, existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
5
4
|
import path from "node:path";
|
|
6
5
|
import { fileURLToPath } from "node:url";
|
|
7
6
|
|
|
@@ -10,307 +9,144 @@ const REPO_ROOT = path.resolve(SCRIPT_DIR, "../../../../..");
|
|
|
10
9
|
const DEFAULT_TEMPLATE_ID = "taishanpi-1m-rk3566";
|
|
11
10
|
const DEFAULT_HOST = "198.19.77.2";
|
|
12
11
|
const DEFAULT_PORTS = "22,15301";
|
|
13
|
-
const DEFAULT_STATUS_PORTS = "22";
|
|
14
|
-
const TOOL_INTEGRITY_RELOGIN_MESSAGE = "工具完整性校验失败,请在当前电脑重新执行 embedlabs auth login --token <key>";
|
|
15
|
-
const MCP_SERVER_VERSION = "0.2.40";
|
|
16
|
-
const MCP_ICON_FILES = [
|
|
17
|
-
{ fileName: "embed-labs-icon-dark.png", theme: "dark" },
|
|
18
|
-
{ fileName: "embed-labs-icon-light.png", theme: "light" }
|
|
19
|
-
];
|
|
20
|
-
|
|
21
|
-
function iconDataUri(fileName) {
|
|
22
|
-
const candidates = [
|
|
23
|
-
path.join(SCRIPT_DIR, "..", "assets", fileName),
|
|
24
|
-
path.join(SCRIPT_DIR, "assets", fileName)
|
|
25
|
-
];
|
|
26
|
-
for (const candidate of candidates) {
|
|
27
|
-
if (!existsSync(candidate)) continue;
|
|
28
|
-
const data = readFileSync(candidate).toString("base64");
|
|
29
|
-
return `data:image/png;base64,${data}`;
|
|
30
|
-
}
|
|
31
|
-
return null;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function mcpServerInfo() {
|
|
35
|
-
const icons = MCP_ICON_FILES
|
|
36
|
-
.map(({ fileName, theme }) => {
|
|
37
|
-
const src = iconDataUri(fileName);
|
|
38
|
-
return src ? { src, mimeType: "image/png", sizes: ["100x100"], theme } : null;
|
|
39
|
-
})
|
|
40
|
-
.filter(Boolean);
|
|
41
|
-
return {
|
|
42
|
-
name: "embed-labs",
|
|
43
|
-
title: "Embed Labs",
|
|
44
|
-
version: MCP_SERVER_VERSION,
|
|
45
|
-
description: "Embed Labs MCP service for local-first embedded development boards, board knowledge, toolchain metadata, Local Bridge hardware access, and account/device-bound service logging.",
|
|
46
|
-
websiteUrl: "https://embedboard.com/",
|
|
47
|
-
...(icons.length > 0 ? { icons } : {})
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
12
|
|
|
51
13
|
const tools = [
|
|
52
|
-
{
|
|
53
|
-
name: "dbt_auth_status",
|
|
54
|
-
description: "Check whether the local Embed Labs CLI is signed in and bound to this computer. If not ready, return the exact login command and dashboard URL the user should use before any board/cloud tool call.",
|
|
55
|
-
inputSchema: {
|
|
56
|
-
type: "object",
|
|
57
|
-
properties: {}
|
|
58
|
-
}
|
|
59
|
-
},
|
|
60
14
|
{
|
|
61
15
|
name: "dbt_current_board_status",
|
|
62
|
-
description: "Get
|
|
16
|
+
description: "Get current connected development-board status through Embed Labs planning plus Local Bridge. Use this MCP tool directly for requests like 当前开发板状态, 开发板状态, board status, USB ECM, SSH, and control-port checks; it is not a shell command.",
|
|
63
17
|
inputSchema: {
|
|
64
18
|
type: "object",
|
|
65
19
|
properties: {
|
|
66
20
|
reason: { type: "string" },
|
|
67
|
-
local_device_id: { type: "string" },
|
|
68
21
|
host: { type: "string" },
|
|
69
|
-
ports: { type: "string" }
|
|
22
|
+
ports: { type: "string" },
|
|
23
|
+
provider: { type: "string" },
|
|
24
|
+
model: { type: "string" }
|
|
70
25
|
}
|
|
71
26
|
}
|
|
72
27
|
},
|
|
73
28
|
{
|
|
74
|
-
name: "
|
|
75
|
-
description: "
|
|
29
|
+
name: "dbt_cloud_status",
|
|
30
|
+
description: "Discover model routing, service modes, board registry, methods, and knowledge.",
|
|
76
31
|
inputSchema: {
|
|
77
32
|
type: "object",
|
|
78
33
|
properties: {
|
|
79
|
-
|
|
80
|
-
board: { type: "string" },
|
|
81
|
-
local_device_id: { type: "string" },
|
|
82
|
-
device_id: { type: "string" },
|
|
83
|
-
image_dir: { type: "string" },
|
|
84
|
-
artifact_path: { type: "string" },
|
|
85
|
-
approve: { type: "boolean" },
|
|
86
|
-
approved: { type: "boolean" },
|
|
87
|
-
wait: { type: "boolean", description: "Defaults to true when approved=true." },
|
|
88
|
-
timeout_ms: { type: "number" },
|
|
89
|
-
include_raw: { type: "boolean" }
|
|
34
|
+
template_id: { type: "string" }
|
|
90
35
|
}
|
|
91
36
|
}
|
|
92
37
|
},
|
|
93
38
|
{
|
|
94
|
-
name: "
|
|
95
|
-
description: "
|
|
39
|
+
name: "dbt_workspace_provision",
|
|
40
|
+
description: "Provision an account/project scoped server workspace.",
|
|
96
41
|
inputSchema: {
|
|
97
42
|
type: "object",
|
|
43
|
+
required: ["account_id", "project_id"],
|
|
98
44
|
properties: {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
port: { type: "number" },
|
|
105
|
-
include_caps: { type: "boolean" },
|
|
106
|
-
timeout_ms: { type: "number" },
|
|
107
|
-
monitor_root: { type: "string" }
|
|
45
|
+
account_id: { type: "string" },
|
|
46
|
+
project_id: { type: "string" },
|
|
47
|
+
template_id: { type: "string" },
|
|
48
|
+
copy_mode: { type: "string" },
|
|
49
|
+
dry_run: { type: "boolean" }
|
|
108
50
|
}
|
|
109
51
|
}
|
|
110
52
|
},
|
|
111
53
|
{
|
|
112
|
-
name: "
|
|
113
|
-
description: "
|
|
114
|
-
inputSchema: { type: "object", properties: {} }
|
|
115
|
-
},
|
|
116
|
-
{
|
|
117
|
-
name: "dbt_rp2350_operation",
|
|
118
|
-
description: "Run a supported RP2350 Monitor /api/operation action through the shared Local Bridge. Prefer specific tools for GPIO/UART/I2C/SPI/Wi-Fi/probe; use this for advanced catalog actions.",
|
|
54
|
+
name: "dbt_application_generate",
|
|
55
|
+
description: "Generate a server-side application artifact and optionally download it.",
|
|
119
56
|
inputSchema: {
|
|
120
57
|
type: "object",
|
|
121
|
-
required: ["
|
|
58
|
+
required: ["workspace_id", "prompt"],
|
|
122
59
|
properties: {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
60
|
+
account_id: { type: "string" },
|
|
61
|
+
workspace_id: { type: "string" },
|
|
62
|
+
prompt: { type: "string" },
|
|
63
|
+
provider: { type: "string" },
|
|
64
|
+
model: { type: "string" },
|
|
65
|
+
target: { type: "string" },
|
|
66
|
+
download_output: { type: "string" }
|
|
130
67
|
}
|
|
131
68
|
}
|
|
132
69
|
},
|
|
133
70
|
{
|
|
134
|
-
name: "
|
|
135
|
-
description: "
|
|
136
|
-
inputSchema: { type: "object", properties: { pins: { type: "array", items: { type: "number" } }, pull: { type: "string" }, approved: { type: "boolean" }, approve: { type: "boolean" }, local_device_id: { type: "string" }, timeout_ms: { type: "number" } } }
|
|
137
|
-
},
|
|
138
|
-
{
|
|
139
|
-
name: "dbt_rp2350_gpio_write",
|
|
140
|
-
description: "Drive an RP2350/Pico GPIO pin high or low through the shared monitor bridge. Requires approved=true.",
|
|
141
|
-
inputSchema: { type: "object", properties: { pin: { type: "number" }, gpio: { type: "number" }, id: { type: "number" }, level: { type: "boolean" }, approved: { type: "boolean" }, approve: { type: "boolean" }, local_device_id: { type: "string" }, timeout_ms: { type: "number" } } }
|
|
142
|
-
},
|
|
143
|
-
{
|
|
144
|
-
name: "dbt_rp2350_uart_write",
|
|
145
|
-
description: "Send text or HEX data over the current/default RP2350 UART channel, with optional tx/rx/baud override. Requires approved=true.",
|
|
146
|
-
inputSchema: { type: "object", properties: { id: { type: "number" }, instance: { type: "number" }, tx: { type: "number" }, rx: { type: "number" }, baud: { type: "number" }, text: { type: "string" }, hex: { type: "string" }, data_hex: { type: "string" }, line_ending: { type: "string" }, approved: { type: "boolean" }, approve: { type: "boolean" }, local_device_id: { type: "string" }, timeout_ms: { type: "number" } } }
|
|
147
|
-
},
|
|
148
|
-
{
|
|
149
|
-
name: "dbt_rp2350_i2c_transfer",
|
|
150
|
-
description: "Run RP2350 I2C write/read/write-read transaction with address, write payload, and read length. Requires approved=true.",
|
|
151
|
-
inputSchema: { type: "object", properties: { id: { type: "number" }, instance: { type: "number" }, sda: { type: "number" }, scl: { type: "number" }, baud: { type: "number" }, addr: { type: "string" }, address: { type: "string" }, write: { type: "string" }, write_hex: { type: "string" }, hex: { type: "string" }, read_len: { type: "number" }, approved: { type: "boolean" }, approve: { type: "boolean" }, local_device_id: { type: "string" }, timeout_ms: { type: "number" } } }
|
|
152
|
-
},
|
|
153
|
-
{
|
|
154
|
-
name: "dbt_rp2350_logic_capture",
|
|
155
|
-
description: "Capture a compact RP2350-Monitor logic-analyzer JSONL file from selected GPIO pins for AI-assisted data collection and later decode. Requires approved=true because it arms local hardware. Logic analyzer pins are configurable as a contiguous exposed GPIO range; use dbt_rp2350_capabilities/status and the user's wiring before choosing pin_base/pin_count.",
|
|
71
|
+
name: "dbt_source_list",
|
|
72
|
+
description: "List source files in an Embed Labs server workspace.",
|
|
156
73
|
inputSchema: {
|
|
157
74
|
type: "object",
|
|
158
|
-
required: ["
|
|
75
|
+
required: ["workspace_id"],
|
|
159
76
|
properties: {
|
|
160
|
-
|
|
161
|
-
local_device_id: { type: "string" },
|
|
162
|
-
monitor_bridge_url: { type: "string" },
|
|
163
|
-
serial_path: { type: "string" },
|
|
164
|
-
host: { type: "string" },
|
|
165
|
-
port: { type: "number" },
|
|
166
|
-
pin_base: { type: "number" },
|
|
167
|
-
pin_count: { type: "number" },
|
|
168
|
-
sample_rate: { type: "number" },
|
|
169
|
-
samples: { type: "number" },
|
|
170
|
-
output_path: { type: "string" },
|
|
171
|
-
pull: { type: "string" },
|
|
172
|
-
release: { type: "boolean" },
|
|
173
|
-
wait_timeout_ms: { type: "number" },
|
|
174
|
-
read_timeout_ms: { type: "number" },
|
|
175
|
-
pre_samples: { type: "number" },
|
|
176
|
-
post_samples: { type: "number" },
|
|
177
|
-
search_samples: { type: "number" },
|
|
178
|
-
burst_count: { type: "number" },
|
|
179
|
-
trigger_pin: { type: "number" },
|
|
180
|
-
trigger_mode: { type: "string" },
|
|
181
|
-
trigger_level: { type: "boolean" },
|
|
182
|
-
trigger_mask: { type: "number" },
|
|
183
|
-
trigger_value: { type: "number" },
|
|
184
|
-
channel_names: { type: "object", additionalProperties: { type: "string" } },
|
|
185
|
-
pin_pulls: { type: "object", additionalProperties: { type: "string" } },
|
|
186
|
-
stimulus: { type: "object", additionalProperties: true, description: "Optional closed-loop stimulus. For SPI, use action/protocol spi.transfer. If SPI pins are omitted, the bridge uses the documented SPI0 example SCK GP2, MOSI GP3, MISO GP0, CS GP1; this is only an example profile, not a global wiring rule." },
|
|
187
|
-
approve: { type: "boolean" },
|
|
188
|
-
approved: { type: "boolean" },
|
|
189
|
-
timeout_ms: { type: "number" },
|
|
190
|
-
monitor_root: { type: "string" }
|
|
77
|
+
workspace_id: { type: "string" }
|
|
191
78
|
}
|
|
192
79
|
}
|
|
193
80
|
},
|
|
194
81
|
{
|
|
195
|
-
name: "
|
|
196
|
-
description: "
|
|
82
|
+
name: "dbt_source_get",
|
|
83
|
+
description: "Read a source file from an Embed Labs server workspace and optionally write it to output_path.",
|
|
197
84
|
inputSchema: {
|
|
198
85
|
type: "object",
|
|
199
|
-
required: ["
|
|
86
|
+
required: ["workspace_id", "source_path"],
|
|
200
87
|
properties: {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
output_path: { type: "string" },
|
|
205
|
-
preview_limit: { type: "number" },
|
|
206
|
-
pins: { type: "array", items: { type: "number" } },
|
|
207
|
-
rx_pin: { type: "number" },
|
|
208
|
-
baud: { type: "number" },
|
|
209
|
-
sck_pin: { type: "number" },
|
|
210
|
-
mosi_pin: { type: "number" },
|
|
211
|
-
miso_pin: { type: "number" },
|
|
212
|
-
cs_pin: { type: "number" },
|
|
213
|
-
sda_pin: { type: "number" },
|
|
214
|
-
scl_pin: { type: "number" },
|
|
215
|
-
monitor_root: { type: "string" }
|
|
88
|
+
workspace_id: { type: "string" },
|
|
89
|
+
source_path: { type: "string" },
|
|
90
|
+
output_path: { type: "string" }
|
|
216
91
|
}
|
|
217
92
|
}
|
|
218
93
|
},
|
|
219
94
|
{
|
|
220
|
-
name: "
|
|
221
|
-
description: "
|
|
95
|
+
name: "dbt_source_search",
|
|
96
|
+
description: "Search source files in an Embed Labs server workspace using server-side query/glob filters.",
|
|
222
97
|
inputSchema: {
|
|
223
98
|
type: "object",
|
|
99
|
+
required: ["workspace_id"],
|
|
224
100
|
properties: {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
host: { type: "string" },
|
|
230
|
-
port: { type: "number" },
|
|
231
|
-
id: { type: "number" },
|
|
232
|
-
instance: { type: "number" },
|
|
233
|
-
sck: { type: "number", default: 2, description: "SPI SCK GPIO. Default is the documented SPI0 example GP2; other valid pins depend on the selected SPI instance." },
|
|
234
|
-
mosi: { type: "number", default: 3, description: "SPI MOSI/TX GPIO. Default is the documented SPI0 example GP3; other valid pins depend on the selected SPI instance." },
|
|
235
|
-
miso: { type: "number", default: 0, description: "SPI MISO/RX GPIO. Default is the documented SPI0 example GP0; other valid pins depend on the selected SPI instance." },
|
|
236
|
-
cs: { type: "number", default: 1, description: "Manual CS GPIO. Default is the documented SPI0 example GP1; it may be another free exposed GPIO that does not overlap SCK/MOSI/MISO." },
|
|
237
|
-
baud: { type: "number", default: 1000000 },
|
|
238
|
-
hex: { type: "string" },
|
|
239
|
-
data_hex: { type: "string" },
|
|
240
|
-
write_hex: { type: "string" },
|
|
241
|
-
read_len: { type: "number" },
|
|
242
|
-
use_current_channel: { type: "boolean", description: "Advanced: reuse active SPI channel from monitor snapshot instead of the documented SPI0 example pins." },
|
|
243
|
-
approve: { type: "boolean" },
|
|
244
|
-
approved: { type: "boolean" },
|
|
245
|
-
timeout_ms: { type: "number" },
|
|
246
|
-
monitor_root: { type: "string" }
|
|
101
|
+
workspace_id: { type: "string" },
|
|
102
|
+
query: { type: "string" },
|
|
103
|
+
glob: { type: "string" },
|
|
104
|
+
limit: { type: "integer" }
|
|
247
105
|
}
|
|
248
106
|
}
|
|
249
107
|
},
|
|
250
108
|
{
|
|
251
|
-
name: "
|
|
252
|
-
description: "
|
|
253
|
-
inputSchema: { type: "object", properties: { action: { type: "string" }, wifi_action: { type: "string" }, slot: { type: "number" }, ssid: { type: "string" }, password: { type: "string" }, save: { type: "boolean" }, approved: { type: "boolean" }, approve: { type: "boolean" }, local_device_id: { type: "string" }, timeout_ms: { type: "number" } } }
|
|
254
|
-
},
|
|
255
|
-
{
|
|
256
|
-
name: "dbt_rp2350_probe_debug",
|
|
257
|
-
description: "Run RP2350 Monitor debug-probe actions: caps, status, config, release, reset, or raw DAP packet. Mutating actions require approved=true.",
|
|
258
|
-
inputSchema: { type: "object", required: ["action"], properties: { action: { type: "string" }, swclk: { type: "number" }, swdio: { type: "number" }, reset: { type: "number" }, swclk_khz: { type: "number" }, reset_action: { type: "string" }, pulse_ms: { type: "number" }, packet_hex: { type: "string" }, approved: { type: "boolean" }, approve: { type: "boolean" }, local_device_id: { type: "string" }, timeout_ms: { type: "number" } } }
|
|
259
|
-
},
|
|
260
|
-
{
|
|
261
|
-
name: "dbt_rp2350_monitor_command",
|
|
262
|
-
description: "Run an allowlisted RP2350-Monitor command through Embed Labs Local Bridge. Mutating commands require approve=true.",
|
|
109
|
+
name: "dbt_source_put",
|
|
110
|
+
description: "Upload a locally edited source file into an Embed Labs server workspace.",
|
|
263
111
|
inputSchema: {
|
|
264
112
|
type: "object",
|
|
265
|
-
required: ["
|
|
113
|
+
required: ["workspace_id", "local_path", "source_path"],
|
|
266
114
|
properties: {
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
host: { type: "string" },
|
|
272
|
-
port: { type: "number" },
|
|
273
|
-
command: { type: "string" },
|
|
274
|
-
args: { type: "array", items: { type: "string" } },
|
|
275
|
-
approve: { type: "boolean" },
|
|
276
|
-
approved: { type: "boolean" },
|
|
277
|
-
timeout_ms: { type: "number" },
|
|
278
|
-
monitor_root: { type: "string" }
|
|
115
|
+
account_id: { type: "string" },
|
|
116
|
+
workspace_id: { type: "string" },
|
|
117
|
+
local_path: { type: "string" },
|
|
118
|
+
source_path: { type: "string" }
|
|
279
119
|
}
|
|
280
120
|
}
|
|
281
121
|
},
|
|
282
122
|
{
|
|
283
|
-
name: "
|
|
284
|
-
description: "
|
|
123
|
+
name: "dbt_source_patch",
|
|
124
|
+
description: "Apply a local unified-diff patch file to an Embed Labs server workspace source tree.",
|
|
285
125
|
inputSchema: {
|
|
286
126
|
type: "object",
|
|
127
|
+
required: ["workspace_id", "patch_path"],
|
|
287
128
|
properties: {
|
|
288
|
-
|
|
129
|
+
account_id: { type: "string" },
|
|
130
|
+
workspace_id: { type: "string" },
|
|
131
|
+
patch_path: { type: "string" }
|
|
289
132
|
}
|
|
290
133
|
}
|
|
291
134
|
},
|
|
292
135
|
{
|
|
293
|
-
name: "
|
|
294
|
-
description: "
|
|
136
|
+
name: "dbt_application_compile",
|
|
137
|
+
description: "Run a bounded server-side application compile/syntax check for a workspace source file. Successful tasks expose a runnable TaishanPi app artifact for dbt_board_debug artifact_task_id deployment.",
|
|
295
138
|
inputSchema: {
|
|
296
139
|
type: "object",
|
|
297
|
-
required: ["
|
|
140
|
+
required: ["workspace_id"],
|
|
298
141
|
properties: {
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
142
|
+
account_id: { type: "string" },
|
|
143
|
+
workspace_id: { type: "string" },
|
|
144
|
+
source_path: { type: "string" },
|
|
145
|
+
execution_mode: { type: "string" },
|
|
146
|
+
compiler: { type: "string" }
|
|
303
147
|
}
|
|
304
148
|
}
|
|
305
149
|
},
|
|
306
|
-
{
|
|
307
|
-
name: "dbt_supported_boards",
|
|
308
|
-
description: "List currently supported Embed Labs development boards across Cloud board registry and local toolchain environments. Use this directly for 当前支持哪些开发板 / supported boards questions.",
|
|
309
|
-
inputSchema: {
|
|
310
|
-
type: "object",
|
|
311
|
-
properties: {}
|
|
312
|
-
}
|
|
313
|
-
},
|
|
314
150
|
{
|
|
315
151
|
name: "dbt_task_status",
|
|
316
152
|
description: "Read an Embed Labs Cloud task status, including output_tail and artifact metadata.",
|
|
@@ -324,7 +160,7 @@ const tools = [
|
|
|
324
160
|
},
|
|
325
161
|
{
|
|
326
162
|
name: "dbt_task_log_download",
|
|
327
|
-
description: "Download the first log artifact from a Cloud task.",
|
|
163
|
+
description: "Download the first log artifact from a Cloud task, useful after Docker compile failures.",
|
|
328
164
|
inputSchema: {
|
|
329
165
|
type: "object",
|
|
330
166
|
required: ["task_id"],
|
|
@@ -334,6 +170,25 @@ const tools = [
|
|
|
334
170
|
}
|
|
335
171
|
}
|
|
336
172
|
},
|
|
173
|
+
{
|
|
174
|
+
name: "dbt_image_generate",
|
|
175
|
+
description: "Generate a server-side image artifact and optionally download it.",
|
|
176
|
+
inputSchema: {
|
|
177
|
+
type: "object",
|
|
178
|
+
required: ["workspace_id", "prompt"],
|
|
179
|
+
properties: {
|
|
180
|
+
account_id: { type: "string" },
|
|
181
|
+
workspace_id: { type: "string" },
|
|
182
|
+
prompt: { type: "string" },
|
|
183
|
+
provider: { type: "string" },
|
|
184
|
+
model: { type: "string" },
|
|
185
|
+
image_profile: { type: "string" },
|
|
186
|
+
execution_mode: { type: "string" },
|
|
187
|
+
worker_pool: { type: "string" },
|
|
188
|
+
download_output: { type: "string" }
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
},
|
|
337
192
|
{
|
|
338
193
|
name: "dbt_artifact_download",
|
|
339
194
|
description: "Download an Embed Labs Cloud artifact.",
|
|
@@ -348,7 +203,7 @@ const tools = [
|
|
|
348
203
|
},
|
|
349
204
|
{
|
|
350
205
|
name: "dbt_board_debug",
|
|
351
|
-
description: "Run
|
|
206
|
+
description: "Run local-board debug/deploy planning through Cloud API plus Local Bridge.",
|
|
352
207
|
inputSchema: {
|
|
353
208
|
type: "object",
|
|
354
209
|
required: ["prompt"],
|
|
@@ -365,39 +220,11 @@ const tools = [
|
|
|
365
220
|
artifact_task_id: { type: "string" },
|
|
366
221
|
artifact_output: { type: "string" },
|
|
367
222
|
remote_path: { type: "string" },
|
|
368
|
-
run_command: { type: "string" },
|
|
369
223
|
approve: { type: "boolean" },
|
|
370
224
|
run: { type: "boolean" }
|
|
371
225
|
}
|
|
372
226
|
}
|
|
373
227
|
},
|
|
374
|
-
{
|
|
375
|
-
name: "dbt_boot_logo_update",
|
|
376
|
-
description: "Single high-level local boot-logo action. Use this directly for 启动logo修改/刷写启动Logo/logo替换. Pass only the image path and optional approval; the tool validates the local SDK, converts/resizes the image locally, repacks the local Rockchip resource image, patches the local boot.img, and returns a compact result. Do not call dbt_update_logo and dbt_compose_boot_logo manually unless this tool reports a product gap.",
|
|
377
|
-
inputSchema: {
|
|
378
|
-
type: "object",
|
|
379
|
-
required: ["logo_path"],
|
|
380
|
-
properties: {
|
|
381
|
-
logo_path: { type: "string" },
|
|
382
|
-
logo: { type: "string" },
|
|
383
|
-
kernel_logo_path: { type: "string" },
|
|
384
|
-
board_id: { type: "string" },
|
|
385
|
-
board: { type: "string" },
|
|
386
|
-
variant_id: { type: "string" },
|
|
387
|
-
variant: { type: "string" },
|
|
388
|
-
output_dir: { type: "string" },
|
|
389
|
-
package_path: { type: "string" },
|
|
390
|
-
base_image_path: { type: "string" },
|
|
391
|
-
output_image_path: { type: "string" },
|
|
392
|
-
manifest_path: { type: "string" },
|
|
393
|
-
approve: { type: "boolean" },
|
|
394
|
-
approved: { type: "boolean" },
|
|
395
|
-
force: { type: "boolean" },
|
|
396
|
-
auto_repair: { type: "boolean" },
|
|
397
|
-
include_raw: { type: "boolean" }
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
},
|
|
401
228
|
{
|
|
402
229
|
name: "dbt_update_logo",
|
|
403
230
|
description: "Ask the server to generate a small free boot-logo package for later local image merge and approved flashing. This does not allocate a paid server workspace.",
|
|
@@ -442,40 +269,21 @@ const tools = [
|
|
|
442
269
|
}
|
|
443
270
|
},
|
|
444
271
|
{
|
|
445
|
-
name: "
|
|
446
|
-
description: "
|
|
447
|
-
inputSchema: {
|
|
448
|
-
type: "object",
|
|
449
|
-
properties: {
|
|
450
|
-
board_id: { type: "string" },
|
|
451
|
-
board: { type: "string" },
|
|
452
|
-
channel: { type: "string" },
|
|
453
|
-
metadata_root: { type: "string" },
|
|
454
|
-
metadataRoot: { type: "string" },
|
|
455
|
-
install_root: { type: "string" },
|
|
456
|
-
installRoot: { type: "string" }
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
},
|
|
460
|
-
{
|
|
461
|
-
name: "dbt_local_toolchain_installed",
|
|
462
|
-
description: "Show only development-board environments already installed on this computer, including the release root and installed mode. Use this when the user asks what board packages or SDKs are currently installed.",
|
|
272
|
+
name: "dbt_workspace_release",
|
|
273
|
+
description: "Release a server workspace so storage allocation stops accruing.",
|
|
463
274
|
inputSchema: {
|
|
464
275
|
type: "object",
|
|
276
|
+
required: ["workspace_id"],
|
|
465
277
|
properties: {
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
metadata_root: { type: "string" },
|
|
470
|
-
metadataRoot: { type: "string" },
|
|
471
|
-
install_root: { type: "string" },
|
|
472
|
-
installRoot: { type: "string" }
|
|
278
|
+
workspace_id: { type: "string" },
|
|
279
|
+
dry_run: { type: "boolean" },
|
|
280
|
+
no_settle_storage: { type: "boolean" }
|
|
473
281
|
}
|
|
474
282
|
}
|
|
475
283
|
},
|
|
476
284
|
{
|
|
477
285
|
name: "dbt_local_toolchain_latest",
|
|
478
|
-
description: "Show the latest available local
|
|
286
|
+
description: "Show the latest available local TaishanPi toolchain package metadata for this host.",
|
|
479
287
|
inputSchema: {
|
|
480
288
|
type: "object",
|
|
481
289
|
properties: {
|
|
@@ -489,7 +297,7 @@ const tools = [
|
|
|
489
297
|
},
|
|
490
298
|
{
|
|
491
299
|
name: "dbt_local_toolchain_current",
|
|
492
|
-
description: "Show the currently
|
|
300
|
+
description: "Show the currently installed local TaishanPi toolchain selected by the Embed Labs CLI registry.",
|
|
493
301
|
inputSchema: {
|
|
494
302
|
type: "object",
|
|
495
303
|
properties: {
|
|
@@ -500,7 +308,7 @@ const tools = [
|
|
|
500
308
|
},
|
|
501
309
|
{
|
|
502
310
|
name: "dbt_local_toolchain_install",
|
|
503
|
-
description: "Download
|
|
311
|
+
description: "Download or copy and install the latest TaishanPi local toolchain into the user's Embed Labs toolchain registry.",
|
|
504
312
|
inputSchema: {
|
|
505
313
|
type: "object",
|
|
506
314
|
properties: {
|
|
@@ -515,134 +323,17 @@ const tools = [
|
|
|
515
323
|
sourceReleaseRoot: { type: "string" },
|
|
516
324
|
install_root: { type: "string" },
|
|
517
325
|
installRoot: { type: "string" },
|
|
518
|
-
mode: { type: "string", enum: ["minimal", "runtime", "compile", "qt", "firmware", "full", "images"] },
|
|
519
326
|
force: { type: "boolean" }
|
|
520
327
|
}
|
|
521
328
|
}
|
|
522
329
|
},
|
|
523
|
-
{
|
|
524
|
-
name: "dbt_local_toolchain_uninstall",
|
|
525
|
-
description: "Uninstall a selected local development-board SDK/tool package from this computer and remove its CLI registry entry. Use this when the user asks to remove, uninstall, or clean up a board environment.",
|
|
526
|
-
inputSchema: {
|
|
527
|
-
type: "object",
|
|
528
|
-
required: ["board_id"],
|
|
529
|
-
properties: {
|
|
530
|
-
board_id: { type: "string" },
|
|
531
|
-
board: { type: "string" },
|
|
532
|
-
install_root: { type: "string" },
|
|
533
|
-
installRoot: { type: "string" }
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
},
|
|
537
330
|
{
|
|
538
331
|
name: "dbt_local_toolchain_validate",
|
|
539
|
-
description: "Validate
|
|
540
|
-
inputSchema: {
|
|
541
|
-
type: "object",
|
|
542
|
-
properties: {
|
|
543
|
-
release_root: { type: "string" },
|
|
544
|
-
releaseRoot: { type: "string" },
|
|
545
|
-
board_id: { type: "string" },
|
|
546
|
-
board: { type: "string" },
|
|
547
|
-
mode: { type: "string", enum: ["minimal", "runtime", "compile", "qt", "firmware", "full", "images"] }
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
},
|
|
551
|
-
{
|
|
552
|
-
name: "dbt_taishanpi_initial_image_flash",
|
|
553
|
-
description: "Install/repair the TaishanPi 1M-RK3566 images SDK, resolve images/current automatically, plan initialization image flashing through Embed Labs Local Bridge, and optionally run it when approved=true. Use this directly for TaishanPi requests like 烧写初始化镜像, 刷写1M RK3566初始化镜像, Loader/Maskrom 初始化镜像刷写. Do not use dbt_board_debug for this flow and do not ask the user to provide image_dir when the SDK is installed.",
|
|
554
|
-
inputSchema: {
|
|
555
|
-
type: "object",
|
|
556
|
-
properties: {
|
|
557
|
-
board_id: { type: "string", description: "Defaults to taishanpi-1m-rk3566 for SDK install and taishanpi for flash." },
|
|
558
|
-
board: { type: "string" },
|
|
559
|
-
local_device_id: { type: "string" },
|
|
560
|
-
device_id: { type: "string" },
|
|
561
|
-
image_dir: { type: "string" },
|
|
562
|
-
install_root: { type: "string" },
|
|
563
|
-
installRoot: { type: "string" },
|
|
564
|
-
channel: { type: "string" },
|
|
565
|
-
metadata_root: { type: "string" },
|
|
566
|
-
metadataRoot: { type: "string" },
|
|
567
|
-
force: { type: "boolean" },
|
|
568
|
-
approve: { type: "boolean" },
|
|
569
|
-
approved: { type: "boolean" }
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
},
|
|
573
|
-
{
|
|
574
|
-
name: "dbt_rp2350_monitor_firmware_flash",
|
|
575
|
-
description: "Download/install the optional RP2350 Monitor firmware package, locate rp2350_monitor.uf2, plan UF2 flashing through Embed Labs Local Bridge, and optionally flash it when approved=true. Use this only when the user explicitly asks for RP2350 Monitor, logic-analyzer, hardware-control, or Monitor firmware. For 初始化镜像/initial firmware requests use dbt_rp2350_initial_firmware_flash instead. Initialized boards use the Local Bridge inventory and picotool runtime handoff when available.",
|
|
576
|
-
inputSchema: {
|
|
577
|
-
type: "object",
|
|
578
|
-
properties: {
|
|
579
|
-
board_id: { type: "string", description: "pico2w-rp2350-monitor or coloreasypico2-rp2350-monitor. Defaults to pico2w-rp2350-monitor." },
|
|
580
|
-
board: { type: "string" },
|
|
581
|
-
install_root: { type: "string" },
|
|
582
|
-
installRoot: { type: "string" },
|
|
583
|
-
channel: { type: "string" },
|
|
584
|
-
metadata_root: { type: "string" },
|
|
585
|
-
metadataRoot: { type: "string" },
|
|
586
|
-
artifact_path: { type: "string", description: "Optional UF2 path. If omitted, the tool installs firmware mode and uses toolkit-runtime/rp2350-monitor/firmware/rp2350_monitor.uf2." },
|
|
587
|
-
artifact: { type: "string" },
|
|
588
|
-
force: { type: "boolean" },
|
|
589
|
-
approve: { type: "boolean" },
|
|
590
|
-
approved: { type: "boolean" }
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
},
|
|
594
|
-
{
|
|
595
|
-
name: "dbt_rp2350_initial_firmware_flash",
|
|
596
|
-
description: "Download/install the RP2350 board initialization firmware package, locate the board-specific initial.uf2, plan UF2 flashing through Embed Labs Local Bridge, and optionally flash it when approved=true. Use this directly for user requests like 烧写初始化镜像, 刷初始化镜像, initial firmware, or initialization UF2. Do not ask the user to choose Monitor vs initial firmware for those requests. If Local Bridge inventory already shows an initialized RP2350 runtime, the flash plan should use the picotool runtime handoff after approval.",
|
|
597
|
-
inputSchema: {
|
|
598
|
-
type: "object",
|
|
599
|
-
properties: {
|
|
600
|
-
board_id: { type: "string", description: "pico2w-rp2350-monitor or coloreasypico2-rp2350-monitor. Defaults to pico2w-rp2350-monitor." },
|
|
601
|
-
board: { type: "string" },
|
|
602
|
-
install_root: { type: "string" },
|
|
603
|
-
installRoot: { type: "string" },
|
|
604
|
-
channel: { type: "string" },
|
|
605
|
-
metadata_root: { type: "string" },
|
|
606
|
-
metadataRoot: { type: "string" },
|
|
607
|
-
artifact_path: { type: "string", description: "Optional UF2 path. If omitted, the tool installs firmware mode and uses rp2350-initial-firmware/<board>/initial.uf2." },
|
|
608
|
-
artifact: { type: "string" },
|
|
609
|
-
force: { type: "boolean" },
|
|
610
|
-
approve: { type: "boolean" },
|
|
611
|
-
approved: { type: "boolean" }
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
},
|
|
615
|
-
{
|
|
616
|
-
name: "dbt_plugin_update_check",
|
|
617
|
-
description: "Check the published Embed Labs Codex/OpenCode plugin release and report whether the local plugins should be updated.",
|
|
618
|
-
inputSchema: {
|
|
619
|
-
type: "object",
|
|
620
|
-
properties: {
|
|
621
|
-
release_url: { type: "string" },
|
|
622
|
-
releaseUrl: { type: "string" },
|
|
623
|
-
target: { type: "string" },
|
|
624
|
-
codex_target: { type: "string" },
|
|
625
|
-
codexTarget: { type: "string" },
|
|
626
|
-
opencode_target: { type: "string" },
|
|
627
|
-
opencodeTarget: { type: "string" }
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
},
|
|
631
|
-
{
|
|
632
|
-
name: "dbt_plugin_update",
|
|
633
|
-
description: "Update the local Embed Labs Codex/OpenCode plugin installation from the published release. Restart Codex/OpenCode after this finishes.",
|
|
332
|
+
description: "Validate the local TaishanPi LLVM toolchain release. MCP and authorization still flow through the Embed Labs CLI.",
|
|
634
333
|
inputSchema: {
|
|
635
334
|
type: "object",
|
|
636
335
|
properties: {
|
|
637
|
-
|
|
638
|
-
plugin: { type: "string", enum: ["codex", "opencode", "all"] },
|
|
639
|
-
release_url: { type: "string" },
|
|
640
|
-
releaseUrl: { type: "string" },
|
|
641
|
-
target: { type: "string" },
|
|
642
|
-
codex_target: { type: "string" },
|
|
643
|
-
codexTarget: { type: "string" },
|
|
644
|
-
opencode_target: { type: "string" },
|
|
645
|
-
opencodeTarget: { type: "string" }
|
|
336
|
+
release_root: { type: "string" }
|
|
646
337
|
}
|
|
647
338
|
}
|
|
648
339
|
},
|
|
@@ -662,7 +353,7 @@ const tools = [
|
|
|
662
353
|
},
|
|
663
354
|
{
|
|
664
355
|
name: "dbt_local_qt_smoke",
|
|
665
|
-
description: "Build
|
|
356
|
+
description: "Build the local TaishanPi Qt smoke application with the local Embed Labs LLVM/Qt toolchain. The user must be signed in with an Embed Labs token for account attribution.",
|
|
666
357
|
inputSchema: {
|
|
667
358
|
type: "object",
|
|
668
359
|
required: ["build_dir"],
|
|
@@ -675,31 +366,9 @@ const tools = [
|
|
|
675
366
|
}
|
|
676
367
|
}
|
|
677
368
|
},
|
|
678
|
-
{
|
|
679
|
-
name: "dbt_taishanpi_qt_app_workflow",
|
|
680
|
-
description: "Single high-level TaishanPi Qt application workflow. Use this directly for Qt/QtQuick app development, Modbus/UART demos, cross compilation, deployment, and runtime checks. The tool validates/repairs the Qt SDK, selects a board-pack template when source_path is omitted, builds with the local LLVM/Qt toolchain, and can deploy/run after approval. Do not search scripts, print CLI help, or create ad hoc serial backends.",
|
|
681
|
-
inputSchema: {
|
|
682
|
-
type: "object",
|
|
683
|
-
properties: {
|
|
684
|
-
request: { type: "string" },
|
|
685
|
-
source_path: { type: "string" },
|
|
686
|
-
project_dir: { type: "string" },
|
|
687
|
-
build_dir: { type: "string" },
|
|
688
|
-
target_name: { type: "string" },
|
|
689
|
-
local_device_id: { type: "string" },
|
|
690
|
-
host: { type: "string" },
|
|
691
|
-
remote_path: { type: "string" },
|
|
692
|
-
deploy: { type: "boolean" },
|
|
693
|
-
run: { type: "boolean" },
|
|
694
|
-
approve: { type: "boolean" },
|
|
695
|
-
approved: { type: "boolean" },
|
|
696
|
-
include_raw: { type: "boolean" }
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
|
-
},
|
|
700
369
|
{
|
|
701
370
|
name: "dbttool",
|
|
702
|
-
description: "Dispatcher for
|
|
371
|
+
description: "Dispatcher for status, debug, update-logo, compose-boot-logo, local-toolchain-latest, local-toolchain-current, local-toolchain-install, local-toolchain-validate, local-compile, local-qt-smoke, provision, source-list, source-get, source-search, source-put, source-patch, application-generate, application-compile, task-status, task-log-download, image-generate, artifact-download, or workspace-release.",
|
|
703
372
|
inputSchema: {
|
|
704
373
|
type: "object",
|
|
705
374
|
required: ["action"],
|
|
@@ -729,30 +398,6 @@ function compactObject(value) {
|
|
|
729
398
|
return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined && item !== ""));
|
|
730
399
|
}
|
|
731
400
|
|
|
732
|
-
function objectValue(value) {
|
|
733
|
-
if (value && typeof value === "object" && !Array.isArray(value)) return value;
|
|
734
|
-
const text = asString(value);
|
|
735
|
-
if (!text) return undefined;
|
|
736
|
-
try {
|
|
737
|
-
const parsed = JSON.parse(text);
|
|
738
|
-
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return parsed;
|
|
739
|
-
} catch {
|
|
740
|
-
// Fall through to key=value parsing for compact CLI-oriented prompts.
|
|
741
|
-
}
|
|
742
|
-
const entries = text
|
|
743
|
-
.split(/[,\s]+/g)
|
|
744
|
-
.map((item) => item.trim())
|
|
745
|
-
.filter(Boolean)
|
|
746
|
-
.map((item) => item.split("=", 2))
|
|
747
|
-
.filter((item) => item.length === 2 && item[0]);
|
|
748
|
-
return entries.length > 0 ? Object.fromEntries(entries) : undefined;
|
|
749
|
-
}
|
|
750
|
-
|
|
751
|
-
function unwrapRp2350Snapshot(value) {
|
|
752
|
-
const root = objectValue(value) || {};
|
|
753
|
-
return objectValue(root.snapshot) || root;
|
|
754
|
-
}
|
|
755
|
-
|
|
756
401
|
function resolveEmbedCommand() {
|
|
757
402
|
if (process.env.EMBED_CLI_BIN) {
|
|
758
403
|
const cliBin = process.env.EMBED_CLI_BIN;
|
|
@@ -797,182 +442,32 @@ async function runEmbed(args) {
|
|
|
797
442
|
child.on("error", reject);
|
|
798
443
|
child.on("close", (code) => {
|
|
799
444
|
const output = stdout.trim();
|
|
800
|
-
|
|
445
|
+
let parsed = output;
|
|
446
|
+
if (output) {
|
|
447
|
+
try {
|
|
448
|
+
parsed = JSON.parse(output);
|
|
449
|
+
} catch {
|
|
450
|
+
parsed = { raw_stdout: output };
|
|
451
|
+
}
|
|
452
|
+
}
|
|
801
453
|
if (code !== 0) {
|
|
802
|
-
reject(new Error(JSON.stringify(
|
|
454
|
+
reject(new Error(JSON.stringify({
|
|
455
|
+
command: [command.file].concat(fullArgs),
|
|
456
|
+
exit_code: code,
|
|
457
|
+
stdout: parsed,
|
|
458
|
+
stderr: stderr.trim()
|
|
459
|
+
}, null, 2)));
|
|
803
460
|
return;
|
|
804
461
|
}
|
|
805
|
-
resolve({ ok: true, response: parsed });
|
|
806
|
-
});
|
|
807
|
-
});
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
async function runProcess(command, args, options = {}) {
|
|
811
|
-
return await new Promise((resolve) => {
|
|
812
|
-
const child = spawn(command, args, {
|
|
813
|
-
cwd: asString(options.cwd) || process.cwd(),
|
|
814
|
-
env: options.env || process.env,
|
|
815
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
816
|
-
});
|
|
817
|
-
let stdout = "";
|
|
818
|
-
let stderr = "";
|
|
819
|
-
child.stdout.setEncoding("utf8");
|
|
820
|
-
child.stderr.setEncoding("utf8");
|
|
821
|
-
child.stdout.on("data", (chunk) => {
|
|
822
|
-
stdout += chunk;
|
|
823
|
-
});
|
|
824
|
-
child.stderr.on("data", (chunk) => {
|
|
825
|
-
stderr += chunk;
|
|
826
|
-
});
|
|
827
|
-
child.on("error", (error) => {
|
|
828
|
-
resolve({ code: 127, stdout, stderr: stderr || error.message, error });
|
|
829
|
-
});
|
|
830
|
-
child.on("close", (code) => {
|
|
831
|
-
resolve({ code: code ?? 1, stdout, stderr });
|
|
462
|
+
resolve({ ok: true, command: [command.file].concat(fullArgs), response: parsed, stderr: stderr.trim() });
|
|
832
463
|
});
|
|
833
464
|
});
|
|
834
465
|
}
|
|
835
466
|
|
|
836
|
-
function
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
try {
|
|
841
|
-
return JSON.parse(output);
|
|
842
|
-
} catch {
|
|
843
|
-
return { ok: false, error: { code: "non_json_cli_output", message: "Embed Labs CLI returned non-JSON output." } };
|
|
844
|
-
}
|
|
845
|
-
}
|
|
846
|
-
|
|
847
|
-
function summarizeEmbedFailure(exitCode, parsed, stderr) {
|
|
848
|
-
const body = parsed && typeof parsed === "object" ? parsed : {};
|
|
849
|
-
const error = body.error && typeof body.error === "object" ? body.error : {};
|
|
850
|
-
const message = asString(error.message) || asString(error.code) || summarizePublicText(stderr) || "Embed Labs CLI command failed.";
|
|
851
|
-
const code = asString(error.code) || "embed_cli_failed";
|
|
852
|
-
const summarizedError = {
|
|
853
|
-
code,
|
|
854
|
-
message: message.slice(0, 500)
|
|
855
|
-
};
|
|
856
|
-
const remediation = asString(error.remediation);
|
|
857
|
-
if (remediation && isAuthSetupError(code)) {
|
|
858
|
-
summarizedError.remediation = remediation.slice(0, 1200);
|
|
859
|
-
summarizedError.details = authSetupDetails(error.details);
|
|
860
|
-
}
|
|
861
|
-
return {
|
|
862
|
-
ok: false,
|
|
863
|
-
exit_code: exitCode,
|
|
864
|
-
error: summarizedError,
|
|
865
|
-
stderr_summary: summarizePublicText(stderr)
|
|
866
|
-
};
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
function isAuthSetupError(code) {
|
|
870
|
-
return code === "auth_token_missing"
|
|
871
|
-
|| code === "auth_token_rejected"
|
|
872
|
-
|| code === "auth_not_ready"
|
|
873
|
-
|| code === "tool_integrity_check_failed"
|
|
874
|
-
|| code === "device_signature_required";
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
function authSetupDetails(details) {
|
|
878
|
-
if (!details || typeof details !== "object" || Array.isArray(details)) return undefined;
|
|
879
|
-
const allowed = {};
|
|
880
|
-
for (const key of ["dashboard_url", "login_command", "env_var", "auth_status_command", "device_status_command", "auth_file"]) {
|
|
881
|
-
const value = details[key];
|
|
882
|
-
if (typeof value === "string" && value.trim()) {
|
|
883
|
-
allowed[key] = value.slice(0, 500);
|
|
884
|
-
}
|
|
885
|
-
}
|
|
886
|
-
return Object.keys(allowed).length ? allowed : undefined;
|
|
887
|
-
}
|
|
888
|
-
|
|
889
|
-
function summarizePublicText(value) {
|
|
890
|
-
const text = asString(value).trim();
|
|
891
|
-
if (!text) return undefined;
|
|
892
|
-
return text
|
|
893
|
-
.replace(/(sk-[A-Za-z0-9_-]{8,})/g, "[redacted]")
|
|
894
|
-
.replace(/(npm_[A-Za-z0-9_-]{8,})/g, "[redacted]")
|
|
895
|
-
.replace(/(re_[A-Za-z0-9_-]{8,})/g, "[redacted]")
|
|
896
|
-
.slice(0, 500);
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
function responseData(response) {
|
|
900
|
-
const body = response?.response ?? response;
|
|
901
|
-
if (body && typeof body === "object" && "data" in body) return body.data;
|
|
902
|
-
return body;
|
|
903
|
-
}
|
|
904
|
-
|
|
905
|
-
async function pluginAuthStatus() {
|
|
906
|
-
let response;
|
|
907
|
-
try {
|
|
908
|
-
response = await runEmbed(["auth", "status"]);
|
|
909
|
-
} catch (error) {
|
|
910
|
-
return authNotReadyResult({
|
|
911
|
-
authenticated: false,
|
|
912
|
-
source_error: error instanceof Error ? error.message : String(error)
|
|
913
|
-
});
|
|
914
|
-
}
|
|
915
|
-
const status = responseData(response) || {};
|
|
916
|
-
if (status.authenticated === true && status.device_id && status.device_integrity === "ok") {
|
|
917
|
-
return {
|
|
918
|
-
ok: true,
|
|
919
|
-
authenticated: true,
|
|
920
|
-
device_ready: true,
|
|
921
|
-
profile: status.profile,
|
|
922
|
-
source: status.source,
|
|
923
|
-
account_id: status.account_id,
|
|
924
|
-
api_key_id: status.api_key_id,
|
|
925
|
-
device_id: status.device_id,
|
|
926
|
-
device_integrity: status.device_integrity,
|
|
927
|
-
message: "Embed Labs token and device binding are ready."
|
|
928
|
-
};
|
|
929
|
-
}
|
|
930
|
-
return authNotReadyResult(status);
|
|
931
|
-
}
|
|
932
|
-
|
|
933
|
-
function formatCurrencyCents(value, currency = "USD") {
|
|
934
|
-
const amount = Number(value);
|
|
935
|
-
if (!Number.isFinite(amount)) return undefined;
|
|
936
|
-
return `${currency || "USD"} ${(amount / 100).toFixed(2)}`;
|
|
937
|
-
}
|
|
938
|
-
|
|
939
|
-
function authNotReadyResult(status) {
|
|
940
|
-
const authenticated = status?.authenticated === true;
|
|
941
|
-
const integrity = asString(status?.device_integrity) || (status?.device_id ? "unknown" : "unbound");
|
|
942
|
-
const code = !authenticated ? "auth_token_missing" : integrity === "failed" ? "tool_integrity_check_failed" : "auth_not_ready";
|
|
943
|
-
const message = code === "tool_integrity_check_failed"
|
|
944
|
-
? TOOL_INTEGRITY_RELOGIN_MESSAGE
|
|
945
|
-
: !authenticated
|
|
946
|
-
? "Embed Labs API Token is not configured for this plugin."
|
|
947
|
-
: "Embed Labs API Token 已配置,但还没有完成本机设备绑定。";
|
|
948
|
-
return {
|
|
949
|
-
ok: false,
|
|
950
|
-
error: {
|
|
951
|
-
code,
|
|
952
|
-
message,
|
|
953
|
-
remediation: [
|
|
954
|
-
"1. Open https://api.embedboard.com/dashboard and sign in or register.",
|
|
955
|
-
"2. Create or copy your Embed Labs API Token.",
|
|
956
|
-
"3. Update the local CLI, then run: embedlabs auth login --token <your_token>",
|
|
957
|
-
"4. Verify device binding with: embedlabs auth device status",
|
|
958
|
-
"5. Restart Codex/OpenCode so the plugin picks up the refreshed CLI and auth file."
|
|
959
|
-
].join("\n"),
|
|
960
|
-
details: {
|
|
961
|
-
dashboard_url: "https://api.embedboard.com/dashboard",
|
|
962
|
-
login_command: "embedlabs auth login --token <your_token>",
|
|
963
|
-
auth_status_command: "embedlabs auth status",
|
|
964
|
-
device_status_command: "embedlabs auth device status",
|
|
965
|
-
auth_file: ".embed-labs/auth.json",
|
|
966
|
-
current_status: {
|
|
967
|
-
authenticated,
|
|
968
|
-
source: status?.source,
|
|
969
|
-
profile: status?.profile,
|
|
970
|
-
device_id: status?.device_id,
|
|
971
|
-
device_integrity: integrity
|
|
972
|
-
}
|
|
973
|
-
}
|
|
974
|
-
}
|
|
975
|
-
};
|
|
467
|
+
function responseData(response) {
|
|
468
|
+
const body = response?.response ?? response;
|
|
469
|
+
if (body && typeof body === "object" && "data" in body) return body.data;
|
|
470
|
+
return body;
|
|
976
471
|
}
|
|
977
472
|
|
|
978
473
|
function pickArtifact(response, preferredKind) {
|
|
@@ -989,23 +484,10 @@ function pickArtifact(response, preferredKind) {
|
|
|
989
484
|
}
|
|
990
485
|
|
|
991
486
|
async function cloudStatus(args) {
|
|
992
|
-
const template = asString(args.template_id || args.template);
|
|
993
|
-
const [modelDefault, serviceModes] = await Promise.all([
|
|
487
|
+
const template = asString(args.template_id || args.template) || DEFAULT_TEMPLATE_ID;
|
|
488
|
+
const [modelDefault, serviceModes, board, methods, knowledge] = await Promise.all([
|
|
994
489
|
runEmbed(["model", "default"]),
|
|
995
|
-
runEmbed(["service", "modes"])
|
|
996
|
-
]);
|
|
997
|
-
if (!template) {
|
|
998
|
-
const boards = await supportedBoards(args);
|
|
999
|
-
return {
|
|
1000
|
-
model_default: modelDefault,
|
|
1001
|
-
service_modes: serviceModes,
|
|
1002
|
-
...boards,
|
|
1003
|
-
selected_template_id: null,
|
|
1004
|
-
board_methods: undefined,
|
|
1005
|
-
note: "No template_id was supplied, so this response lists supported boards only. Board methods are template-specific; call dbt_cloud_status with template_id for one selected board."
|
|
1006
|
-
};
|
|
1007
|
-
}
|
|
1008
|
-
const [board, methods, knowledge] = await Promise.all([
|
|
490
|
+
runEmbed(["service", "modes"]),
|
|
1009
491
|
runEmbed(["board", "registry", "show", template]),
|
|
1010
492
|
runEmbed(["board", "methods", template]),
|
|
1011
493
|
runEmbed(["board", "knowledge", template])
|
|
@@ -1013,135 +495,6 @@ async function cloudStatus(args) {
|
|
|
1013
495
|
return { model_default: modelDefault, service_modes: serviceModes, board, methods, knowledge };
|
|
1014
496
|
}
|
|
1015
497
|
|
|
1016
|
-
async function boardKnowledgeSearch(args) {
|
|
1017
|
-
const template = asString(args.template_id || args.template) || DEFAULT_TEMPLATE_ID;
|
|
1018
|
-
const query = asString(args.query || args.q || args.prompt);
|
|
1019
|
-
if (!query) {
|
|
1020
|
-
throw new Error("query is required");
|
|
1021
|
-
}
|
|
1022
|
-
const command = ["board", "knowledge", "search", template, "--query", query];
|
|
1023
|
-
pushOptional(command, "source", args.source);
|
|
1024
|
-
pushOptional(command, "limit", args.limit);
|
|
1025
|
-
return await runEmbed(command);
|
|
1026
|
-
}
|
|
1027
|
-
|
|
1028
|
-
async function supportedBoards() {
|
|
1029
|
-
const [registrySettled, localSettled] = await Promise.allSettled([
|
|
1030
|
-
runEmbed(["board", "registry", "list"]),
|
|
1031
|
-
runEmbed(["local", "toolchain", "list"])
|
|
1032
|
-
]);
|
|
1033
|
-
const registry = settledRunValue(registrySettled);
|
|
1034
|
-
const local = settledRunValue(localSettled);
|
|
1035
|
-
const profiles = Array.isArray(responseData(registry)?.profiles) ? responseData(registry).profiles : [];
|
|
1036
|
-
const environments = Array.isArray(responseData(local)?.environments) ? responseData(local).environments : [];
|
|
1037
|
-
const localByBoard = new Map(environments.map((item) => [asString(item.board_id), item]));
|
|
1038
|
-
const supported = profiles.map((profile) => {
|
|
1039
|
-
const localEnv = localByBoard.get(asString(profile.template_id));
|
|
1040
|
-
const methods = Array.isArray(profile.methods) ? profile.methods : [];
|
|
1041
|
-
const runtimes = [...new Set(methods.map((method) => asString(method.runtime)).filter(Boolean))];
|
|
1042
|
-
return compactObject({
|
|
1043
|
-
template_id: profile.template_id,
|
|
1044
|
-
board_id: profile.board_id,
|
|
1045
|
-
variant_id: profile.variant_id,
|
|
1046
|
-
display_name: normalizeSupportedBoardDisplayName(profile.display_name || profile.variant_id || profile.template_id),
|
|
1047
|
-
support_level: profile.support_level,
|
|
1048
|
-
cloud_method_count: methods.length,
|
|
1049
|
-
cloud_method_runtimes: runtimes,
|
|
1050
|
-
local_toolchain_status: localEnv?.status,
|
|
1051
|
-
local_install_modes: Array.isArray(localEnv?.install_modes) ? localEnv.install_modes : undefined,
|
|
1052
|
-
local_installed: localEnv?.installed,
|
|
1053
|
-
local_installed_version: localEnv?.installed?.version,
|
|
1054
|
-
server_latest_version: localEnv?.latest?.version,
|
|
1055
|
-
local_install_command: localEnv?.install_command,
|
|
1056
|
-
local_update_command: localEnv?.update_command,
|
|
1057
|
-
local_uninstall_command: localEnv ? `embedlabs local toolchain uninstall --board ${profile.template_id}` : undefined
|
|
1058
|
-
});
|
|
1059
|
-
});
|
|
1060
|
-
for (const env of environments) {
|
|
1061
|
-
const boardId = asString(env.board_id);
|
|
1062
|
-
if (!boardId || supported.some((item) => item.template_id === boardId)) continue;
|
|
1063
|
-
supported.push(compactObject({
|
|
1064
|
-
template_id: boardId,
|
|
1065
|
-
board_id: boardId,
|
|
1066
|
-
display_name: normalizeSupportedBoardDisplayName(env.display_name || boardId),
|
|
1067
|
-
support_level: "local_toolchain",
|
|
1068
|
-
cloud_method_count: 0,
|
|
1069
|
-
cloud_method_runtimes: [],
|
|
1070
|
-
local_toolchain_status: env.status,
|
|
1071
|
-
local_install_modes: Array.isArray(env.install_modes) ? env.install_modes : undefined,
|
|
1072
|
-
local_installed: env.installed,
|
|
1073
|
-
local_installed_version: env.installed?.version,
|
|
1074
|
-
server_latest_version: env.latest?.version,
|
|
1075
|
-
local_install_command: env.install_command,
|
|
1076
|
-
local_update_command: env.update_command,
|
|
1077
|
-
local_uninstall_command: `embedlabs local toolchain uninstall --board ${boardId}`
|
|
1078
|
-
}));
|
|
1079
|
-
}
|
|
1080
|
-
supported.sort((a, b) => asString(a.display_name).localeCompare(asString(b.display_name)));
|
|
1081
|
-
const table = supported.map((item) => ({
|
|
1082
|
-
board: item.display_name,
|
|
1083
|
-
template_id: item.template_id,
|
|
1084
|
-
support_level: item.support_level,
|
|
1085
|
-
install_status: item.local_toolchain_status || "unknown",
|
|
1086
|
-
local_version: item.local_installed_version || "not installed",
|
|
1087
|
-
server_version: item.server_latest_version || "unknown",
|
|
1088
|
-
modes: Array.isArray(item.local_install_modes) ? item.local_install_modes.join(",") : "",
|
|
1089
|
-
install: item.local_install_command,
|
|
1090
|
-
update: item.local_update_command,
|
|
1091
|
-
uninstall: item.local_uninstall_command
|
|
1092
|
-
}));
|
|
1093
|
-
return {
|
|
1094
|
-
supported_boards: supported,
|
|
1095
|
-
table,
|
|
1096
|
-
cloud_registry: {
|
|
1097
|
-
available: !!registry,
|
|
1098
|
-
count: profiles.length,
|
|
1099
|
-
error: settledRunError(registrySettled)
|
|
1100
|
-
},
|
|
1101
|
-
local_toolchains: {
|
|
1102
|
-
available: !!local,
|
|
1103
|
-
count: environments.length,
|
|
1104
|
-
error: settledRunError(localSettled)
|
|
1105
|
-
},
|
|
1106
|
-
guidance: "When the user asks which boards are supported, show the table fields as a Markdown table. Use install/update/uninstall commands through dbt_local_toolchain_install and dbt_local_toolchain_uninstall rather than shell rm.",
|
|
1107
|
-
observed_at: new Date().toISOString()
|
|
1108
|
-
};
|
|
1109
|
-
}
|
|
1110
|
-
|
|
1111
|
-
function settledRunValue(result) {
|
|
1112
|
-
return result.status === "fulfilled" ? result.value : undefined;
|
|
1113
|
-
}
|
|
1114
|
-
|
|
1115
|
-
function settledRunError(result) {
|
|
1116
|
-
if (result.status !== "rejected") return undefined;
|
|
1117
|
-
return compactEmbedError(result.reason);
|
|
1118
|
-
}
|
|
1119
|
-
|
|
1120
|
-
function compactEmbedError(error) {
|
|
1121
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1122
|
-
try {
|
|
1123
|
-
const parsed = JSON.parse(message);
|
|
1124
|
-
return parsed?.response?.error || parsed?.error || { message: redactSensitive(message) };
|
|
1125
|
-
} catch {
|
|
1126
|
-
return { message: redactSensitive(message) };
|
|
1127
|
-
}
|
|
1128
|
-
}
|
|
1129
|
-
|
|
1130
|
-
function redactSensitive(value) {
|
|
1131
|
-
return asString(value)
|
|
1132
|
-
.replace(/(sk-[A-Za-z0-9_-]{8,})/g, "[redacted]")
|
|
1133
|
-
.replace(/(npm_[A-Za-z0-9_-]{8,})/g, "[redacted]")
|
|
1134
|
-
.replace(/(re_[A-Za-z0-9_-]{8,})/g, "[redacted]")
|
|
1135
|
-
.slice(0, 500);
|
|
1136
|
-
}
|
|
1137
|
-
|
|
1138
|
-
function normalizeSupportedBoardDisplayName(value) {
|
|
1139
|
-
const text = asString(value);
|
|
1140
|
-
if (!text) return "";
|
|
1141
|
-
if (/^TaishanPi 1M RK3566 server build template$/i.test(text)) return "TaishanPi 1M-RK3566";
|
|
1142
|
-
return text;
|
|
1143
|
-
}
|
|
1144
|
-
|
|
1145
498
|
async function boardDebug(args) {
|
|
1146
499
|
const prompt = asString(args.prompt || args.request || args.reason) || "验证开发板状态";
|
|
1147
500
|
const command = ["agent", "run", "--prompt", prompt];
|
|
@@ -1156,407 +509,64 @@ async function boardDebug(args) {
|
|
|
1156
509
|
pushOptional(command, "artifact-task", args.artifact_task_id || args.task_id);
|
|
1157
510
|
pushOptional(command, "artifact-output", args.artifact_output);
|
|
1158
511
|
pushOptional(command, "remote-path", args.remote_path);
|
|
1159
|
-
pushOptional(command, "run-command", args.run_command || args.runCommand);
|
|
1160
512
|
if (boolValue(args.approve)) command.push("--approve");
|
|
1161
513
|
if (boolValue(args.run)) command.push("--run");
|
|
1162
514
|
return await runEmbed(command);
|
|
1163
515
|
}
|
|
1164
516
|
|
|
1165
|
-
async function currentBoardStatus(args
|
|
1166
|
-
|
|
1167
|
-
const explicitHost = asString(args.host);
|
|
1168
|
-
const ports = parsePorts(args.ports || DEFAULT_STATUS_PORTS);
|
|
1169
|
-
const requestedLocalDeviceId = localDeviceIdForTool(args, authStatus);
|
|
1170
|
-
const scan = await runEmbed(["device", "list"]);
|
|
1171
|
-
const taishanHost = taishanStatusHostFromScan(scan);
|
|
1172
|
-
const host = explicitHost || taishanHost;
|
|
1173
|
-
const probe = host
|
|
1174
|
-
? await runEmbed(["tool", "call", "device.probe", "--input-json", JSON.stringify({ host, ports })])
|
|
1175
|
-
: emptyBoardStatusProbe();
|
|
1176
|
-
const summary = summarizeBoardStatus(scan, probe, ports);
|
|
1177
|
-
const localDeviceId = requestedLocalDeviceId || (summary.local_device_ids?.length === 1 ? summary.local_device_ids[0] : "");
|
|
1178
|
-
if (authStatus?.ok) {
|
|
1179
|
-
summary.device_binding = {
|
|
1180
|
-
authenticated: true,
|
|
1181
|
-
account_id: authStatus.account_id,
|
|
1182
|
-
api_key_id: authStatus.api_key_id,
|
|
1183
|
-
device_id: authStatus.device_id,
|
|
1184
|
-
device_integrity: authStatus.device_integrity
|
|
1185
|
-
};
|
|
1186
|
-
}
|
|
1187
|
-
if (localDeviceId) {
|
|
1188
|
-
summary.local_device_id = localDeviceId;
|
|
1189
|
-
}
|
|
1190
|
-
const logResult = await recordMcpToolEvent("dbt_current_board_status", {
|
|
1191
|
-
client: "codex",
|
|
1192
|
-
mode: "local_ai",
|
|
1193
|
-
server_model_used: false,
|
|
1194
|
-
success: summary.query_ok === true,
|
|
1195
|
-
duration_ms: Date.now() - startedAt,
|
|
1196
|
-
local_device_id: localDeviceId,
|
|
1197
|
-
input_summary: host ? `host=${host} ports=${ports.join(",")}` : "inventory_only",
|
|
1198
|
-
output_summary: summary.status_text
|
|
1199
|
-
});
|
|
1200
|
-
summary.usage.mcp_service_log = logResult;
|
|
1201
|
-
return summary;
|
|
1202
|
-
}
|
|
1203
|
-
|
|
1204
|
-
function taishanStatusHostFromScan(scanResult) {
|
|
1205
|
-
const scan = responseData(scanResult) || {};
|
|
1206
|
-
const devices = Array.isArray(scan.development_boards) ? scan.development_boards : (Array.isArray(scan.devices) ? scan.devices : []);
|
|
1207
|
-
const taishan = devices.find((item) => item?.board_id === "taishanpi" || String(item?.device_id || "").includes("taishanpi"));
|
|
1208
|
-
if (!taishan) return "";
|
|
1209
|
-
const locatorHost = Array.isArray(taishan.locators)
|
|
1210
|
-
? taishan.locators.map((locator) => asString(locator.host)).find(Boolean)
|
|
1211
|
-
: "";
|
|
1212
|
-
return asString(taishan.host || taishan.link_local?.host || locatorHost);
|
|
1213
|
-
}
|
|
1214
|
-
|
|
1215
|
-
function emptyBoardStatusProbe() {
|
|
1216
|
-
return {
|
|
1217
|
-
ok: true,
|
|
1218
|
-
response: {
|
|
1219
|
-
ok: true,
|
|
1220
|
-
data: {
|
|
1221
|
-
result: {
|
|
1222
|
-
tcp: [],
|
|
1223
|
-
observed_at: new Date().toISOString()
|
|
1224
|
-
}
|
|
1225
|
-
}
|
|
1226
|
-
}
|
|
1227
|
-
};
|
|
1228
|
-
}
|
|
1229
|
-
|
|
1230
|
-
async function rp2350MonitorTool(toolName, capabilityId, args) {
|
|
1231
|
-
const startedAt = Date.now();
|
|
1232
|
-
const input = normalizeRp2350Input(args);
|
|
1233
|
-
const requestedLocalDeviceId = asString(input.local_device_id || input.localDeviceId);
|
|
1234
|
-
if (requestedLocalDeviceId) input.local_device_id = requestedLocalDeviceId;
|
|
1235
|
-
delete input.localDeviceId;
|
|
1236
|
-
delete input.approve;
|
|
1237
|
-
delete input.approved;
|
|
1238
|
-
const command = ["tool", "call", capabilityId, "--input-json", JSON.stringify(input)];
|
|
1239
|
-
if (boolValue(args.approve) || boolValue(args.approved)) command.push("--approve");
|
|
1240
|
-
const response = await runEmbed(command);
|
|
1241
|
-
const data = responseData(response);
|
|
1242
|
-
const localDeviceId = requestedLocalDeviceId || inferLocalDeviceIdFromToolResponse(data);
|
|
1243
|
-
const summary = data?.result?.summary_for_user || data?.summary_for_user || `${capabilityId} completed.`;
|
|
1244
|
-
const logResult = await recordMcpToolEvent(toolName, {
|
|
1245
|
-
client: "codex",
|
|
1246
|
-
mode: "local_ai",
|
|
1247
|
-
server_model_used: false,
|
|
1248
|
-
success: response?.response?.ok === true,
|
|
1249
|
-
duration_ms: Date.now() - startedAt,
|
|
1250
|
-
local_device_id: localDeviceId,
|
|
1251
|
-
input_summary: `${capabilityId} ${JSON.stringify(input).slice(0, 180)}`,
|
|
1252
|
-
output_summary: asString(summary).slice(0, 240)
|
|
1253
|
-
});
|
|
1254
|
-
return { ...response, usage: { mode: "local_ai_mcp_tool", server_model_used: false, input_tokens: 0, output_tokens: 0, local_device_id: localDeviceId || undefined, mcp_service_log: logResult } };
|
|
1255
|
-
}
|
|
1256
|
-
|
|
1257
|
-
function normalizeRp2350Input(args) {
|
|
1258
|
-
const input = compactObject({ ...args });
|
|
1259
|
-
for (const key of [
|
|
1260
|
-
"port", "baud", "timeout_ms", "pin_base", "pin_count", "sample_rate", "samples",
|
|
1261
|
-
"wait_timeout_ms", "read_timeout_ms", "pre_samples", "post_samples", "search_samples",
|
|
1262
|
-
"burst_count", "trigger_pin", "trigger_mask", "trigger_value", "capture_id",
|
|
1263
|
-
"start_sample", "end_sample", "preview_limit", "rx_pin", "data_bits", "sck_pin", "mosi_pin",
|
|
1264
|
-
"miso_pin", "cs_pin", "mode", "word_bits", "sda_pin", "scl_pin",
|
|
1265
|
-
"id", "instance", "sck", "mosi", "miso", "cs", "read_len", "tx", "rx",
|
|
1266
|
-
"sda", "scl", "pin", "gpio", "slot", "swclk", "swdio", "reset",
|
|
1267
|
-
"swclk_khz", "pulse_ms"
|
|
1268
|
-
]) {
|
|
1269
|
-
const value = input[key];
|
|
1270
|
-
if (typeof value === "string" && value.trim() && Number.isFinite(Number(value))) {
|
|
1271
|
-
input[key] = Number(value);
|
|
1272
|
-
}
|
|
1273
|
-
}
|
|
1274
|
-
for (const key of ["include_caps", "release", "trigger_level", "invert", "cs_active_high", "level", "save", "use_current_channel"]) {
|
|
1275
|
-
if (typeof input[key] === "string") {
|
|
1276
|
-
input[key] = boolValue(input[key]);
|
|
1277
|
-
}
|
|
1278
|
-
}
|
|
1279
|
-
if (typeof input.pins === "string") {
|
|
1280
|
-
input.pins = input.pins.split(/[,\s]+/g).map((item) => Number(item)).filter((item) => Number.isFinite(item));
|
|
1281
|
-
}
|
|
1282
|
-
for (const key of ["channel_names", "pin_pulls", "stimulus", "params"]) {
|
|
1283
|
-
const parsed = objectValue(input[key]);
|
|
1284
|
-
if (parsed) input[key] = parsed;
|
|
1285
|
-
else delete input[key];
|
|
1286
|
-
}
|
|
1287
|
-
return input;
|
|
1288
|
-
}
|
|
1289
|
-
|
|
1290
|
-
async function rp2350MonitorStatus(args) {
|
|
1291
|
-
return await rp2350MonitorTool("dbt_rp2350_monitor_status", "rp2350.monitor.status", args);
|
|
1292
|
-
}
|
|
1293
|
-
|
|
1294
|
-
async function rp2350Capabilities(args) {
|
|
1295
|
-
return await rp2350MonitorTool("dbt_rp2350_capabilities", "rp2350.monitor.capabilities", args);
|
|
1296
|
-
}
|
|
1297
|
-
|
|
1298
|
-
async function rp2350Operation(args) {
|
|
1299
|
-
return await rp2350MonitorTool("dbt_rp2350_operation", "rp2350.monitor.operation", args);
|
|
1300
|
-
}
|
|
1301
|
-
|
|
1302
|
-
async function rp2350GpioRead(args) {
|
|
1303
|
-
return await rp2350MonitorTool("dbt_rp2350_gpio_read", "rp2350.monitor.gpio.read", args);
|
|
1304
|
-
}
|
|
1305
|
-
|
|
1306
|
-
async function rp2350GpioWrite(args) {
|
|
1307
|
-
return await rp2350MonitorTool("dbt_rp2350_gpio_write", "rp2350.monitor.gpio.write", args);
|
|
1308
|
-
}
|
|
1309
|
-
|
|
1310
|
-
async function rp2350UartWrite(args) {
|
|
1311
|
-
return await rp2350MonitorTool("dbt_rp2350_uart_write", "rp2350.monitor.uart.write", args);
|
|
517
|
+
async function currentBoardStatus(args) {
|
|
518
|
+
return summarizeBoardStatus(await boardDebug(args));
|
|
1312
519
|
}
|
|
1313
520
|
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
}
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
async function rp2350LogicDecode(args) {
|
|
1323
|
-
return await rp2350MonitorTool("dbt_rp2350_logic_decode", "rp2350.monitor.logic.decode", args);
|
|
1324
|
-
}
|
|
1325
|
-
|
|
1326
|
-
async function rp2350SpiTransfer(args) {
|
|
1327
|
-
return await rp2350MonitorTool("dbt_rp2350_spi_transfer", "rp2350.monitor.spi.transfer", args);
|
|
1328
|
-
}
|
|
1329
|
-
|
|
1330
|
-
async function rp2350WifiManage(args) {
|
|
1331
|
-
return await rp2350MonitorTool("dbt_rp2350_wifi_manage", "rp2350.monitor.wifi.manage", args);
|
|
1332
|
-
}
|
|
1333
|
-
|
|
1334
|
-
async function rp2350ProbeDebug(args) {
|
|
1335
|
-
return await rp2350MonitorTool("dbt_rp2350_probe_debug", "rp2350.monitor.probe.debug", args);
|
|
1336
|
-
}
|
|
1337
|
-
|
|
1338
|
-
async function rp2350MonitorCommand(args) {
|
|
1339
|
-
return await rp2350MonitorTool("dbt_rp2350_monitor_command", "rp2350.monitor.command", args);
|
|
1340
|
-
}
|
|
1341
|
-
|
|
1342
|
-
function parsePorts(value) {
|
|
1343
|
-
const text = asString(value) || DEFAULT_PORTS;
|
|
1344
|
-
const ports = text
|
|
1345
|
-
.split(/[,\s]+/g)
|
|
1346
|
-
.map((item) => Number.parseInt(item, 10))
|
|
1347
|
-
.filter((item) => Number.isInteger(item) && item > 0 && item <= 65535);
|
|
1348
|
-
return ports.length > 0 ? ports : DEFAULT_PORTS.split(",").map((item) => Number.parseInt(item, 10));
|
|
1349
|
-
}
|
|
1350
|
-
|
|
1351
|
-
async function recordMcpToolEvent(toolName, details) {
|
|
1352
|
-
try {
|
|
1353
|
-
const requestId = `mcp_${toolName}_${Date.now()}_${Math.random().toString(16).slice(2)}`;
|
|
1354
|
-
const command = [
|
|
1355
|
-
"mcp", "log",
|
|
1356
|
-
"--tool", toolName,
|
|
1357
|
-
"--client", details.client || "codex",
|
|
1358
|
-
"--mode", details.mode || "local_ai",
|
|
1359
|
-
"--server-model-used", details.server_model_used ? "true" : "false",
|
|
1360
|
-
"--success", details.success ? "true" : "false",
|
|
1361
|
-
"--request-id", requestId
|
|
1362
|
-
];
|
|
1363
|
-
if (Number.isFinite(details.duration_ms)) command.push("--duration-ms", String(Math.max(0, Math.round(details.duration_ms))));
|
|
1364
|
-
if (asString(details.local_device_id)) command.push("--local-device-id", asString(details.local_device_id));
|
|
1365
|
-
if (asString(details.input_summary)) command.push("--input-summary", asString(details.input_summary));
|
|
1366
|
-
if (asString(details.output_summary)) command.push("--output-summary", asString(details.output_summary));
|
|
1367
|
-
const response = await runEmbed(command);
|
|
1368
|
-
return { recorded: response?.response?.ok === true, event_id: response?.response?.data?.event_id };
|
|
1369
|
-
} catch (error) {
|
|
1370
|
-
return compactMcpLogError(error);
|
|
1371
|
-
}
|
|
1372
|
-
}
|
|
1373
|
-
|
|
1374
|
-
function compactMcpLogError(error) {
|
|
1375
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1376
|
-
try {
|
|
1377
|
-
const parsed = JSON.parse(message);
|
|
1378
|
-
const cloudError = parsed?.stdout?.error || parsed?.response?.error || parsed?.error;
|
|
1379
|
-
if (cloudError?.code) {
|
|
1380
|
-
return {
|
|
1381
|
-
recorded: false,
|
|
1382
|
-
error_code: cloudError.code,
|
|
1383
|
-
message: cloudError.message || "MCP service log was not recorded."
|
|
1384
|
-
};
|
|
1385
|
-
}
|
|
1386
|
-
} catch {
|
|
1387
|
-
// Fall through to a concise text message.
|
|
1388
|
-
}
|
|
1389
|
-
return { recorded: false, message: message.slice(0, 240) };
|
|
1390
|
-
}
|
|
1391
|
-
|
|
1392
|
-
function localDeviceIdForTool(args) {
|
|
1393
|
-
return asString(args.local_device_id || args.localDeviceId);
|
|
1394
|
-
}
|
|
1395
|
-
|
|
1396
|
-
function inferLocalDeviceIdFromToolResponse(data) {
|
|
1397
|
-
const result = data?.result || data || {};
|
|
1398
|
-
const existing = asString(result.local_device_id || result.device_id);
|
|
1399
|
-
if (existing) return existing;
|
|
1400
|
-
const bridgeUrl = asString(result.bridge_url);
|
|
1401
|
-
if (bridgeUrl) return stableLocalDeviceId("rp2350-monitor", bridgeUrl);
|
|
1402
|
-
const host = asString(result.host);
|
|
1403
|
-
if (host) return stableLocalDeviceId("taishanpi", host);
|
|
1404
|
-
return "";
|
|
1405
|
-
}
|
|
1406
|
-
|
|
1407
|
-
function stableLocalDeviceId(kind, locator) {
|
|
1408
|
-
const hash = createHash("sha256").update(`${kind}:${locator}`).digest("hex").slice(0, 12);
|
|
1409
|
-
return `${kind}_${hash}`;
|
|
1410
|
-
}
|
|
1411
|
-
|
|
1412
|
-
function summarizeBoardStatus(scanResult, probeResult, requestedPorts = []) {
|
|
1413
|
-
const scan = responseData(scanResult) || {};
|
|
1414
|
-
const probeEnvelope = responseData(probeResult) || {};
|
|
1415
|
-
const probe = probeEnvelope.result || probeEnvelope || {};
|
|
1416
|
-
const devices = Array.isArray(scan.development_boards) ? scan.development_boards : (Array.isArray(scan.devices) ? scan.devices : []);
|
|
1417
|
-
const bridgeBoardStatus = objectValue(scan.board_status) || {};
|
|
1418
|
-
const visiblePorts = new Set((Array.isArray(requestedPorts) ? requestedPorts : []).map((port) => Number(port)).filter((port) => Number.isFinite(port)));
|
|
1419
|
-
const boardLocalDeviceIds = devices
|
|
1420
|
-
.map((item) => asString(item.local_device_id || item.device_id))
|
|
1421
|
-
.filter(Boolean);
|
|
521
|
+
function summarizeBoardStatus(result) {
|
|
522
|
+
const data = result?.response?.data || {};
|
|
523
|
+
const completion = data.completion || {};
|
|
524
|
+
const toolResults = Array.isArray(completion.tool_results) ? completion.tool_results : [];
|
|
525
|
+
const scan = toolResults.find((item) => item?.capability_id === "device.scan")?.output || {};
|
|
526
|
+
const probe = toolResults.find((item) => item?.capability_id === "device.probe")?.output || {};
|
|
527
|
+
const devices = Array.isArray(scan.devices) ? scan.devices : [];
|
|
1422
528
|
const boards = devices
|
|
1423
|
-
.
|
|
529
|
+
.filter((item) => item?.board_id || String(item?.device_id || "").includes("taishanpi"))
|
|
530
|
+
.map((item) => ({
|
|
531
|
+
device_id: item.device_id,
|
|
532
|
+
board_id: item.board_id,
|
|
533
|
+
display_name: item.display_name,
|
|
534
|
+
status: item.status,
|
|
535
|
+
locators: item.locators,
|
|
536
|
+
summary_for_user: item.summary_for_user
|
|
537
|
+
}));
|
|
1424
538
|
const tcp = (Array.isArray(probe.tcp) ? probe.tcp : []).map((item) => ({
|
|
1425
539
|
host: item.host,
|
|
1426
540
|
port: item.port,
|
|
1427
541
|
reachable: item.reachable,
|
|
1428
542
|
duration_ms: item.duration_ms
|
|
1429
543
|
}));
|
|
1430
|
-
const
|
|
1431
|
-
|
|
544
|
+
const serialPaths = Array.isArray(scan.serial) ? scan.serial.map((item) => item.path).filter(Boolean) : [];
|
|
545
|
+
const likelyBoardSerialPaths = serialPaths.filter((item) => /usbmodem|debug|wlan/i.test(item));
|
|
546
|
+
return {
|
|
547
|
+
ok: result?.ok === true && result?.response?.ok === true,
|
|
548
|
+
board_connected: boards.some((item) => item.status === "connected"),
|
|
1432
549
|
boards,
|
|
1433
|
-
local_device_ids: boardLocalDeviceIds,
|
|
1434
550
|
tcp_ports: tcp,
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
:
|
|
1438
|
-
|
|
551
|
+
serial_summary: {
|
|
552
|
+
total_paths: serialPaths.length,
|
|
553
|
+
likely_board_paths: likelyBoardSerialPaths,
|
|
554
|
+
note: "macOS exposes many serial devices twice as /dev/cu.* and /dev/tty.*; this is a count of local paths, not board count."
|
|
555
|
+
},
|
|
556
|
+
status_text: boards.length > 0
|
|
557
|
+
? `${boards[0].display_name || boards[0].board_id || "Board"} is ${boards[0].status || "detected"}.`
|
|
558
|
+
: "No supported board was detected in the local scan.",
|
|
1439
559
|
usage: {
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
560
|
+
usage_id: completion.usage_id,
|
|
561
|
+
provider: completion.provider,
|
|
562
|
+
model: completion.model,
|
|
563
|
+
input_tokens: completion.input_tokens,
|
|
564
|
+
output_tokens: completion.output_tokens
|
|
1444
565
|
},
|
|
1445
566
|
observed_at: probe.observed_at || scan.observed_at,
|
|
1446
567
|
raw_result_omitted: true,
|
|
1447
568
|
model_answer_omitted: true
|
|
1448
569
|
};
|
|
1449
|
-
Object.defineProperty(result, "query_ok", {
|
|
1450
|
-
value: scanResult?.ok === true && scanResult?.response?.ok === true && probeResult?.ok === true && probeResult?.response?.ok === true,
|
|
1451
|
-
enumerable: false
|
|
1452
|
-
});
|
|
1453
|
-
return result;
|
|
1454
|
-
}
|
|
1455
|
-
|
|
1456
|
-
function summarizeBoardDevice(item, visiblePorts) {
|
|
1457
|
-
const isRp2350 = isRp2350Device(item);
|
|
1458
|
-
const monitor = isRp2350 ? rp2350MonitorSummary(item) : undefined;
|
|
1459
|
-
const displayName = isRp2350
|
|
1460
|
-
? `${rp2350BoardDisplayName(item) || item.display_name || item.name || "RP2350"}${monitor?.running ? " (Monitor)" : ""}`
|
|
1461
|
-
: item.display_name || item.name || item.variant_id;
|
|
1462
|
-
const host = isRp2350 ? asString(item.host || item.link_local?.host) : item.host || item.link_local?.host || item.bridge_url;
|
|
1463
|
-
return {
|
|
1464
|
-
device_id: item.device_id,
|
|
1465
|
-
local_device_id: item.local_device_id,
|
|
1466
|
-
board_id: item.board_id,
|
|
1467
|
-
display_name: displayName,
|
|
1468
|
-
status: item.status || (item.connected === true ? "connected" : item.reachable === true ? "reachable" : "detected"),
|
|
1469
|
-
connected: item.connected === true || item.status === "connected",
|
|
1470
|
-
host: host || undefined,
|
|
1471
|
-
ui_url: isRp2350 ? asString(item.ui_url || item.browser_url) || undefined : undefined,
|
|
1472
|
-
browser_url: isRp2350 ? asString(item.browser_url || item.ui_url) || undefined : undefined,
|
|
1473
|
-
public_bridge_url: isRp2350 ? asString(item.public_bridge_url) || undefined : undefined,
|
|
1474
|
-
reachable: item.reachable,
|
|
1475
|
-
link_local: item.link_local,
|
|
1476
|
-
locators: isRp2350 ? filteredRp2350Locators(item.locators) : item.locators,
|
|
1477
|
-
monitor,
|
|
1478
|
-
tcp: Array.isArray(item.tcp) ? item.tcp
|
|
1479
|
-
.filter((entry) => visiblePorts.size === 0 || visiblePorts.has(Number(entry.port)))
|
|
1480
|
-
.map((entry) => ({
|
|
1481
|
-
host: entry.host,
|
|
1482
|
-
port: entry.port,
|
|
1483
|
-
reachable: entry.reachable,
|
|
1484
|
-
error: entry.error
|
|
1485
|
-
})) : undefined,
|
|
1486
|
-
identity: item.identity ? {
|
|
1487
|
-
attempted: item.identity.attempted,
|
|
1488
|
-
exit_code: item.identity.exit_code,
|
|
1489
|
-
stdout: item.identity.stdout,
|
|
1490
|
-
timed_out: item.identity.timed_out,
|
|
1491
|
-
board: isRp2350 ? rp2350BoardDisplayName(item) : undefined
|
|
1492
|
-
} : undefined,
|
|
1493
|
-
summary_for_user: item.summary_for_user
|
|
1494
|
-
};
|
|
1495
|
-
}
|
|
1496
|
-
|
|
1497
|
-
function isRp2350Device(item) {
|
|
1498
|
-
const boardId = asString(item?.board_id).toLowerCase();
|
|
1499
|
-
const kind = asString(item?.kind).toLowerCase();
|
|
1500
|
-
const id = asString(item?.local_device_id || item?.device_id).toLowerCase();
|
|
1501
|
-
return boardId === "rp2350" || boardId.includes("rp2350") || kind.includes("rp2350") || id.includes("rp2350");
|
|
1502
|
-
}
|
|
1503
|
-
|
|
1504
|
-
function rp2350BoardDisplayName(item) {
|
|
1505
|
-
const identity = objectValue(item?.identity) || {};
|
|
1506
|
-
const monitorDevice = objectValue(identity.monitor_device) || {};
|
|
1507
|
-
const details = objectValue(item?.details) || {};
|
|
1508
|
-
const snapshot = unwrapRp2350Snapshot(details.snapshot);
|
|
1509
|
-
const snapshotDevice = objectValue(snapshot.device) || {};
|
|
1510
|
-
for (const value of [
|
|
1511
|
-
item?.board_display_name,
|
|
1512
|
-
item?.variant_id,
|
|
1513
|
-
item?.board_model,
|
|
1514
|
-
monitorDevice.board,
|
|
1515
|
-
snapshotDevice.board,
|
|
1516
|
-
item?.board,
|
|
1517
|
-
item?.display_name,
|
|
1518
|
-
item?.name
|
|
1519
|
-
]) {
|
|
1520
|
-
const normalized = normalizeRp2350BoardName(value);
|
|
1521
|
-
if (normalized) return normalized;
|
|
1522
|
-
}
|
|
1523
|
-
return "";
|
|
1524
|
-
}
|
|
1525
|
-
|
|
1526
|
-
function normalizeRp2350BoardName(value) {
|
|
1527
|
-
const text = asString(value);
|
|
1528
|
-
if (!text) return "";
|
|
1529
|
-
const compact = text.toLowerCase().replace(/[\s_/-]+/g, "");
|
|
1530
|
-
if (compact.includes("coloreasypico2") || compact.includes("colorpico2")) return "ColorEasyPICO2";
|
|
1531
|
-
if (compact.includes("pico2w") || compact.includes("pico2wireless")) return "Pico 2 W";
|
|
1532
|
-
if (compact === "rp2350" || compact === "rp2350monitor") return "";
|
|
1533
|
-
return text;
|
|
1534
|
-
}
|
|
1535
|
-
|
|
1536
|
-
function rp2350MonitorSummary(item) {
|
|
1537
|
-
const identity = objectValue(item?.identity) || {};
|
|
1538
|
-
const monitorDevice = objectValue(identity.monitor_device) || {};
|
|
1539
|
-
const details = objectValue(item?.details) || {};
|
|
1540
|
-
const snapshot = unwrapRp2350Snapshot(details.snapshot);
|
|
1541
|
-
const snapshotDevice = objectValue(snapshot.device) || {};
|
|
1542
|
-
const running = item?.monitor_running === true || item?.monitor_status === "running";
|
|
1543
|
-
return compactObject({
|
|
1544
|
-
running,
|
|
1545
|
-
status: item?.monitor_status || (running ? "running" : "not_running"),
|
|
1546
|
-
feature_state: item?.feature_state,
|
|
1547
|
-
ui_url: asString(item?.ui_url || item?.browser_url),
|
|
1548
|
-
browser_url: asString(item?.browser_url || item?.ui_url),
|
|
1549
|
-
public_bridge_url: asString(item?.public_bridge_url),
|
|
1550
|
-
upstream_url: asString(item?.upstream_url || item?.monitor_bridge_url || item?.bridge_url),
|
|
1551
|
-
firmware: monitorDevice.firmware || snapshotDevice.firmware,
|
|
1552
|
-
transport: monitorDevice.transport || snapshotDevice.transport
|
|
1553
|
-
});
|
|
1554
|
-
}
|
|
1555
|
-
|
|
1556
|
-
function filteredRp2350Locators(locators) {
|
|
1557
|
-
if (!Array.isArray(locators)) return undefined;
|
|
1558
|
-
const filtered = locators.filter((locator) => asString(locator?.transport) !== "rp2350-monitor");
|
|
1559
|
-
return filtered.length > 0 ? filtered : undefined;
|
|
1560
570
|
}
|
|
1561
571
|
|
|
1562
572
|
async function updateLogo(args) {
|
|
@@ -1597,678 +607,6 @@ async function composeBootLogo(args) {
|
|
|
1597
607
|
return await runEmbed(command);
|
|
1598
608
|
}
|
|
1599
609
|
|
|
1600
|
-
function defaultActionDir(kind, explicitDir) {
|
|
1601
|
-
const outputDir = asString(explicitDir) || path.join(process.cwd(), ".embed-labs", kind, `${kind}_${Date.now()}`);
|
|
1602
|
-
mkdirSync(outputDir, { recursive: true });
|
|
1603
|
-
return outputDir;
|
|
1604
|
-
}
|
|
1605
|
-
|
|
1606
|
-
function firstExistingPath(candidates) {
|
|
1607
|
-
return candidates.map(asString).find((candidate) => candidate && existsSync(candidate)) || "";
|
|
1608
|
-
}
|
|
1609
|
-
|
|
1610
|
-
async function ensureLocalToolchain(args, boardId, mode) {
|
|
1611
|
-
const sdkCheck = await localToolchainValidate({
|
|
1612
|
-
board_id: boardId,
|
|
1613
|
-
mode,
|
|
1614
|
-
release_root: args.release_root || args.releaseRoot
|
|
1615
|
-
});
|
|
1616
|
-
const checkData = responseData(sdkCheck) || {};
|
|
1617
|
-
if (checkData.ok) {
|
|
1618
|
-
return { sdkCheck, sdkInstall: null, releaseRoot: asString(checkData.release_root) };
|
|
1619
|
-
}
|
|
1620
|
-
const sdkInstall = await localToolchainInstall({
|
|
1621
|
-
board_id: boardId,
|
|
1622
|
-
channel: args.channel,
|
|
1623
|
-
metadata_root: args.metadata_root || args.metadataRoot,
|
|
1624
|
-
install_root: args.install_root || args.installRoot,
|
|
1625
|
-
mode,
|
|
1626
|
-
force: args.force
|
|
1627
|
-
});
|
|
1628
|
-
const installData = responseData(sdkInstall) || {};
|
|
1629
|
-
return { sdkCheck, sdkInstall, releaseRoot: asString(installData.release_root) };
|
|
1630
|
-
}
|
|
1631
|
-
|
|
1632
|
-
function bootLogoBaseImageCandidates(args, releaseRoot) {
|
|
1633
|
-
return [
|
|
1634
|
-
args.base_image_path || args.base_image,
|
|
1635
|
-
releaseRoot ? path.join(releaseRoot, "boot-workspace", "out", "resource.img") : "",
|
|
1636
|
-
releaseRoot ? path.join(releaseRoot, "boot-workspace", "out", "boot.img") : "",
|
|
1637
|
-
releaseRoot ? path.join(releaseRoot, "images", "current", "boot.img") : ""
|
|
1638
|
-
].map(asString).filter(Boolean);
|
|
1639
|
-
}
|
|
1640
|
-
|
|
1641
|
-
function fileSha256Sync(filePath) {
|
|
1642
|
-
return createHash("sha256").update(readFileSync(filePath)).digest("hex");
|
|
1643
|
-
}
|
|
1644
|
-
|
|
1645
|
-
function expectedBaseShaFromPackage(packagePath) {
|
|
1646
|
-
try {
|
|
1647
|
-
const payload = JSON.parse(readFileSync(packagePath, "utf8"));
|
|
1648
|
-
return asString(payload?.local_merge?.base_image?.sha256).toLowerCase();
|
|
1649
|
-
} catch {
|
|
1650
|
-
return "";
|
|
1651
|
-
}
|
|
1652
|
-
}
|
|
1653
|
-
|
|
1654
|
-
function resolveBootLogoBaseImage(args, releaseRoot, packagePath) {
|
|
1655
|
-
const candidates = bootLogoBaseImageCandidates(args, releaseRoot).filter((candidate) => existsSync(candidate));
|
|
1656
|
-
const expectedSha = expectedBaseShaFromPackage(packagePath);
|
|
1657
|
-
if (expectedSha) {
|
|
1658
|
-
const matched = candidates.find((candidate) => fileSha256Sync(candidate) === expectedSha);
|
|
1659
|
-
if (matched) return matched;
|
|
1660
|
-
return "";
|
|
1661
|
-
}
|
|
1662
|
-
return candidates[0] || "";
|
|
1663
|
-
}
|
|
1664
|
-
|
|
1665
|
-
function compactBootLogoCompose(composeResult) {
|
|
1666
|
-
const data = responseData(composeResult) || {};
|
|
1667
|
-
return compactObject({
|
|
1668
|
-
ready_for_flash: data.ready_for_flash,
|
|
1669
|
-
replacement_applied: data.replacement_applied,
|
|
1670
|
-
operation_kind: data.operation_kind,
|
|
1671
|
-
output_path: data.output_path || data.output,
|
|
1672
|
-
manifest_path: data.manifest_path || data.manifest,
|
|
1673
|
-
local_merge_strategy: data.local_merge_strategy,
|
|
1674
|
-
requires_board_specific_patcher: data.requires_board_specific_patcher,
|
|
1675
|
-
summary_for_user: data.summary_for_user,
|
|
1676
|
-
warnings: compactWarnings(data.warnings)
|
|
1677
|
-
});
|
|
1678
|
-
}
|
|
1679
|
-
|
|
1680
|
-
function executableExists(filePath) {
|
|
1681
|
-
try {
|
|
1682
|
-
return statSync(filePath).isFile();
|
|
1683
|
-
} catch {
|
|
1684
|
-
return false;
|
|
1685
|
-
}
|
|
1686
|
-
}
|
|
1687
|
-
|
|
1688
|
-
function resolveExecutableFromPath(names) {
|
|
1689
|
-
const pathEntries = asString(process.env.PATH).split(path.delimiter).filter(Boolean);
|
|
1690
|
-
const extensions = process.platform === "win32" ? ["", ".exe", ".cmd", ".bat"] : [""];
|
|
1691
|
-
for (const name of names) {
|
|
1692
|
-
if (path.isAbsolute(name) && executableExists(name)) return name;
|
|
1693
|
-
for (const dir of pathEntries) {
|
|
1694
|
-
for (const ext of extensions) {
|
|
1695
|
-
const candidate = path.join(dir, `${name}${ext}`);
|
|
1696
|
-
if (executableExists(candidate)) return candidate;
|
|
1697
|
-
}
|
|
1698
|
-
}
|
|
1699
|
-
}
|
|
1700
|
-
return "";
|
|
1701
|
-
}
|
|
1702
|
-
|
|
1703
|
-
function resourceToolPath(releaseRoot) {
|
|
1704
|
-
const platformDirs = process.platform === "darwin"
|
|
1705
|
-
? ["mac", "darwin"]
|
|
1706
|
-
: process.platform === "win32"
|
|
1707
|
-
? ["windows", "win"]
|
|
1708
|
-
: ["linux"];
|
|
1709
|
-
const names = process.platform === "win32" ? ["resource_tool.exe", "resource_tool"] : ["resource_tool"];
|
|
1710
|
-
const candidates = [];
|
|
1711
|
-
for (const dir of platformDirs) {
|
|
1712
|
-
for (const name of names) {
|
|
1713
|
-
candidates.push(path.join(releaseRoot, "tools", dir, name));
|
|
1714
|
-
}
|
|
1715
|
-
}
|
|
1716
|
-
for (const name of names) {
|
|
1717
|
-
candidates.push(path.join(releaseRoot, "tools", name));
|
|
1718
|
-
}
|
|
1719
|
-
return firstExistingPath(candidates);
|
|
1720
|
-
}
|
|
1721
|
-
|
|
1722
|
-
function readBmpDimensions(filePath) {
|
|
1723
|
-
const bytes = readFileSync(filePath);
|
|
1724
|
-
if (bytes.length < 54 || bytes.toString("ascii", 0, 2) !== "BM") {
|
|
1725
|
-
throw new Error(`Not a Windows BMP file: ${filePath}`);
|
|
1726
|
-
}
|
|
1727
|
-
return {
|
|
1728
|
-
width: Math.abs(bytes.readInt32LE(18)),
|
|
1729
|
-
height: Math.abs(bytes.readInt32LE(22)),
|
|
1730
|
-
bits_per_pixel: bytes.readUInt16LE(28)
|
|
1731
|
-
};
|
|
1732
|
-
}
|
|
1733
|
-
|
|
1734
|
-
function normalizeBmpTopDown(filePath) {
|
|
1735
|
-
const bytes = Buffer.from(readFileSync(filePath));
|
|
1736
|
-
if (bytes.length < 54 || bytes.toString("ascii", 0, 2) !== "BM") return;
|
|
1737
|
-
const width = bytes.readInt32LE(18);
|
|
1738
|
-
const height = bytes.readInt32LE(22);
|
|
1739
|
-
const bitsPerPixel = bytes.readUInt16LE(28);
|
|
1740
|
-
if (height <= 0 || width <= 0 || bitsPerPixel <= 0) return;
|
|
1741
|
-
const pixelOffset = bytes.readUInt32LE(10);
|
|
1742
|
-
const rowSize = Math.floor((bitsPerPixel * width + 31) / 32) * 4;
|
|
1743
|
-
const pixelBytes = rowSize * height;
|
|
1744
|
-
if (pixelOffset + pixelBytes > bytes.length) return;
|
|
1745
|
-
const original = Buffer.from(bytes.subarray(pixelOffset, pixelOffset + pixelBytes));
|
|
1746
|
-
for (let row = 0; row < height; row += 1) {
|
|
1747
|
-
const sourceStart = (height - row - 1) * rowSize;
|
|
1748
|
-
original.copy(bytes, pixelOffset + row * rowSize, sourceStart, sourceStart + rowSize);
|
|
1749
|
-
}
|
|
1750
|
-
bytes.writeInt32LE(-height, 22);
|
|
1751
|
-
writeFileSync(filePath, bytes);
|
|
1752
|
-
}
|
|
1753
|
-
|
|
1754
|
-
async function convertImageToBootBmp(inputPath, outputPath, width, height) {
|
|
1755
|
-
const magick = resolveExecutableFromPath(["magick"]);
|
|
1756
|
-
if (magick) {
|
|
1757
|
-
const result = await runProcess(magick, [
|
|
1758
|
-
inputPath,
|
|
1759
|
-
"-auto-orient",
|
|
1760
|
-
"-resize", `${width}x${height}^`,
|
|
1761
|
-
"-gravity", "center",
|
|
1762
|
-
"-extent", `${width}x${height}`,
|
|
1763
|
-
"-type", "TrueColor",
|
|
1764
|
-
"-depth", "8",
|
|
1765
|
-
`BMP3:${outputPath}`
|
|
1766
|
-
]);
|
|
1767
|
-
if (result.code === 0) {
|
|
1768
|
-
normalizeBmpTopDown(outputPath);
|
|
1769
|
-
return { converter: "magick", output_path: outputPath };
|
|
1770
|
-
}
|
|
1771
|
-
}
|
|
1772
|
-
const convert = resolveExecutableFromPath(["convert"]);
|
|
1773
|
-
if (convert) {
|
|
1774
|
-
const result = await runProcess(convert, [
|
|
1775
|
-
inputPath,
|
|
1776
|
-
"-auto-orient",
|
|
1777
|
-
"-resize", `${width}x${height}^`,
|
|
1778
|
-
"-gravity", "center",
|
|
1779
|
-
"-extent", `${width}x${height}`,
|
|
1780
|
-
"-type", "TrueColor",
|
|
1781
|
-
"-depth", "8",
|
|
1782
|
-
`BMP3:${outputPath}`
|
|
1783
|
-
]);
|
|
1784
|
-
if (result.code === 0) {
|
|
1785
|
-
normalizeBmpTopDown(outputPath);
|
|
1786
|
-
return { converter: "convert", output_path: outputPath };
|
|
1787
|
-
}
|
|
1788
|
-
}
|
|
1789
|
-
const sips = resolveExecutableFromPath(["sips"]);
|
|
1790
|
-
if (sips) {
|
|
1791
|
-
const result = await runProcess(sips, [
|
|
1792
|
-
"-s", "format", "bmp",
|
|
1793
|
-
"-z", String(height), String(width),
|
|
1794
|
-
inputPath,
|
|
1795
|
-
"--out", outputPath
|
|
1796
|
-
]);
|
|
1797
|
-
if (result.code === 0) {
|
|
1798
|
-
normalizeBmpTopDown(outputPath);
|
|
1799
|
-
return { converter: "sips", output_path: outputPath };
|
|
1800
|
-
}
|
|
1801
|
-
}
|
|
1802
|
-
const ffmpeg = resolveExecutableFromPath(["ffmpeg"]);
|
|
1803
|
-
if (ffmpeg) {
|
|
1804
|
-
const result = await runProcess(ffmpeg, [
|
|
1805
|
-
"-y",
|
|
1806
|
-
"-i", inputPath,
|
|
1807
|
-
"-vf", `scale=${width}:${height}:force_original_aspect_ratio=increase,crop=${width}:${height}`,
|
|
1808
|
-
outputPath
|
|
1809
|
-
]);
|
|
1810
|
-
if (result.code === 0) {
|
|
1811
|
-
normalizeBmpTopDown(outputPath);
|
|
1812
|
-
return { converter: "ffmpeg", output_path: outputPath };
|
|
1813
|
-
}
|
|
1814
|
-
}
|
|
1815
|
-
throw new Error("No local image converter was found. Install ImageMagick, or use the SDK image converter package once published.");
|
|
1816
|
-
}
|
|
1817
|
-
|
|
1818
|
-
function readFdtTotalsize(bytes) {
|
|
1819
|
-
if (bytes.length < 8 || bytes.readUInt32BE(0) !== 0xd00dfeed) return 0;
|
|
1820
|
-
const totalSize = bytes.readUInt32BE(4);
|
|
1821
|
-
return totalSize > 0 && totalSize <= bytes.length ? totalSize : 0;
|
|
1822
|
-
}
|
|
1823
|
-
|
|
1824
|
-
function findUniqueSubBufferOffset(haystack, needle) {
|
|
1825
|
-
let found = -1;
|
|
1826
|
-
let searchFrom = 0;
|
|
1827
|
-
while (searchFrom < haystack.length) {
|
|
1828
|
-
const index = haystack.indexOf(needle, searchFrom);
|
|
1829
|
-
if (index < 0) break;
|
|
1830
|
-
if (found >= 0) return -1;
|
|
1831
|
-
found = index;
|
|
1832
|
-
searchFrom = index + 1;
|
|
1833
|
-
}
|
|
1834
|
-
return found;
|
|
1835
|
-
}
|
|
1836
|
-
|
|
1837
|
-
async function readFitIntegerProperty(imagePath, nodePath, property, workDir) {
|
|
1838
|
-
const fdtget = resolveExecutableFromPath(["fdtget"]);
|
|
1839
|
-
if (!fdtget) return undefined;
|
|
1840
|
-
const result = await runProcess(fdtget, ["-ti", imagePath, nodePath, property], { cwd: workDir });
|
|
1841
|
-
if (result.code !== 0) return undefined;
|
|
1842
|
-
const value = Number.parseInt(result.stdout.trim(), 10);
|
|
1843
|
-
return Number.isSafeInteger(value) && value >= 0 ? value : undefined;
|
|
1844
|
-
}
|
|
1845
|
-
|
|
1846
|
-
async function readFitExternalDataInfo(imagePath, nodeName, workDir) {
|
|
1847
|
-
const nodePath = `/images/${nodeName}`;
|
|
1848
|
-
const offset = await readFitIntegerProperty(imagePath, nodePath, "data-position", workDir);
|
|
1849
|
-
const size = await readFitIntegerProperty(imagePath, nodePath, "data-size", workDir);
|
|
1850
|
-
if (offset === undefined || size === undefined) return undefined;
|
|
1851
|
-
return { offset, size };
|
|
1852
|
-
}
|
|
1853
|
-
|
|
1854
|
-
function sha256Buffer(bytes) {
|
|
1855
|
-
return createHash("sha256").update(bytes).digest("hex");
|
|
1856
|
-
}
|
|
1857
|
-
|
|
1858
|
-
function writeJsonFile(filePath, payload) {
|
|
1859
|
-
mkdirSync(path.dirname(filePath), { recursive: true });
|
|
1860
|
-
writeFileSync(filePath, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
|
|
1861
|
-
}
|
|
1862
|
-
|
|
1863
|
-
async function localBootLogoCompose(args, sdk, logoPath, outputDir, outputImagePath, manifestPath) {
|
|
1864
|
-
const releaseRoot = sdk.releaseRoot;
|
|
1865
|
-
const toolPath = resourceToolPath(releaseRoot);
|
|
1866
|
-
if (!toolPath) {
|
|
1867
|
-
return {
|
|
1868
|
-
ok: false,
|
|
1869
|
-
error: {
|
|
1870
|
-
code: "resource_tool_missing",
|
|
1871
|
-
message: "本机 SDK 缺少 Rockchip resource_tool,无法在本地重打包启动 Logo 镜像。"
|
|
1872
|
-
}
|
|
1873
|
-
};
|
|
1874
|
-
}
|
|
1875
|
-
|
|
1876
|
-
const baseBootImage = firstExistingPath([
|
|
1877
|
-
args.base_image_path || args.base_image,
|
|
1878
|
-
path.join(releaseRoot, "images", "current", "boot.img"),
|
|
1879
|
-
path.join(releaseRoot, "boot-workspace", "out", "boot.img")
|
|
1880
|
-
]);
|
|
1881
|
-
if (!baseBootImage) {
|
|
1882
|
-
return {
|
|
1883
|
-
ok: false,
|
|
1884
|
-
error: {
|
|
1885
|
-
code: "base_boot_image_missing",
|
|
1886
|
-
message: "本机 SDK 中没有找到基础 boot.img,无法生成新的启动 Logo 镜像。"
|
|
1887
|
-
}
|
|
1888
|
-
};
|
|
1889
|
-
}
|
|
1890
|
-
|
|
1891
|
-
const baseBootBytes = readFileSync(baseBootImage);
|
|
1892
|
-
const headerSize = readFdtTotalsize(baseBootBytes);
|
|
1893
|
-
if (!headerSize) {
|
|
1894
|
-
return {
|
|
1895
|
-
ok: false,
|
|
1896
|
-
error: {
|
|
1897
|
-
code: "unsupported_boot_image_format",
|
|
1898
|
-
message: "当前 boot.img 不是可直接本地补丁的 FIT 镜像格式。"
|
|
1899
|
-
}
|
|
1900
|
-
};
|
|
1901
|
-
}
|
|
1902
|
-
const fitResource = await readFitExternalDataInfo(baseBootImage, "resource", outputDir);
|
|
1903
|
-
if (!fitResource || fitResource.offset + fitResource.size > baseBootBytes.length) {
|
|
1904
|
-
return {
|
|
1905
|
-
ok: false,
|
|
1906
|
-
error: {
|
|
1907
|
-
code: "fit_resource_not_found",
|
|
1908
|
-
message: "无法从 boot.img 中定位 Rockchip resource 数据段。请确认 SDK 包包含 fdtget 或等效 FIT 工具。"
|
|
1909
|
-
}
|
|
1910
|
-
};
|
|
1911
|
-
}
|
|
1912
|
-
|
|
1913
|
-
const workDir = path.join(outputDir, "local-work");
|
|
1914
|
-
rmSync(workDir, { recursive: true, force: true });
|
|
1915
|
-
mkdirSync(workDir, { recursive: true });
|
|
1916
|
-
const baseResourcePath = path.join(workDir, "base-resource.img");
|
|
1917
|
-
const patchedResourcePath = asString(args.output_resource_path || args.outputResource)
|
|
1918
|
-
|| path.join(outputDir, "boot-logo-resource.img");
|
|
1919
|
-
writeFileSync(baseResourcePath, baseBootBytes.subarray(fitResource.offset, fitResource.offset + fitResource.size));
|
|
1920
|
-
|
|
1921
|
-
const unpackResult = await runProcess(toolPath, ["--unpack", `--image=${baseResourcePath}`], { cwd: workDir });
|
|
1922
|
-
if (unpackResult.code !== 0) {
|
|
1923
|
-
return {
|
|
1924
|
-
ok: false,
|
|
1925
|
-
error: {
|
|
1926
|
-
code: "resource_unpack_failed",
|
|
1927
|
-
message: summarizePublicText(unpackResult.stderr || unpackResult.stdout) || "resource_tool unpack failed."
|
|
1928
|
-
}
|
|
1929
|
-
};
|
|
1930
|
-
}
|
|
1931
|
-
const unpackDir = path.join(workDir, "out");
|
|
1932
|
-
const logoBmpPath = path.join(unpackDir, "logo.bmp");
|
|
1933
|
-
const kernelLogoBmpPath = path.join(unpackDir, "logo_kernel.bmp");
|
|
1934
|
-
const dimensions = readBmpDimensions(logoBmpPath);
|
|
1935
|
-
const convertedLogoPath = path.join(outputDir, "logo.bmp");
|
|
1936
|
-
const convertedKernelLogoPath = path.join(outputDir, "logo_kernel.bmp");
|
|
1937
|
-
const converter = await convertImageToBootBmp(logoPath, convertedLogoPath, dimensions.width, dimensions.height);
|
|
1938
|
-
const kernelLogoSource = asString(args.kernel_logo_path || args.kernel_logo) || logoPath;
|
|
1939
|
-
const kernelConverter = await convertImageToBootBmp(kernelLogoSource, convertedKernelLogoPath, dimensions.width, dimensions.height);
|
|
1940
|
-
copyFileSync(convertedLogoPath, logoBmpPath);
|
|
1941
|
-
copyFileSync(convertedKernelLogoPath, kernelLogoBmpPath);
|
|
1942
|
-
|
|
1943
|
-
mkdirSync(path.dirname(patchedResourcePath), { recursive: true });
|
|
1944
|
-
const packResult = await runProcess(toolPath, [
|
|
1945
|
-
"--pack",
|
|
1946
|
-
`--image=${patchedResourcePath}`,
|
|
1947
|
-
"rk-kernel.dtb",
|
|
1948
|
-
"logo.bmp",
|
|
1949
|
-
"logo_kernel.bmp"
|
|
1950
|
-
], { cwd: unpackDir });
|
|
1951
|
-
if (packResult.code !== 0) {
|
|
1952
|
-
return {
|
|
1953
|
-
ok: false,
|
|
1954
|
-
error: {
|
|
1955
|
-
code: "resource_pack_failed",
|
|
1956
|
-
message: summarizePublicText(packResult.stderr || packResult.stdout) || "resource_tool pack failed."
|
|
1957
|
-
}
|
|
1958
|
-
};
|
|
1959
|
-
}
|
|
1960
|
-
|
|
1961
|
-
let patchedResourceBytes = readFileSync(patchedResourcePath);
|
|
1962
|
-
if (patchedResourceBytes.length > fitResource.size) {
|
|
1963
|
-
const extra = patchedResourceBytes.subarray(fitResource.size);
|
|
1964
|
-
if (extra.every((byte) => byte === 0)) {
|
|
1965
|
-
patchedResourceBytes = patchedResourceBytes.subarray(0, fitResource.size);
|
|
1966
|
-
writeFileSync(patchedResourcePath, patchedResourceBytes);
|
|
1967
|
-
}
|
|
1968
|
-
}
|
|
1969
|
-
if (patchedResourceBytes.length < fitResource.size) {
|
|
1970
|
-
const padded = Buffer.alloc(fitResource.size);
|
|
1971
|
-
patchedResourceBytes.copy(padded, 0);
|
|
1972
|
-
patchedResourceBytes = padded;
|
|
1973
|
-
writeFileSync(patchedResourcePath, patchedResourceBytes);
|
|
1974
|
-
}
|
|
1975
|
-
if (patchedResourceBytes.length !== fitResource.size) {
|
|
1976
|
-
return {
|
|
1977
|
-
ok: false,
|
|
1978
|
-
error: {
|
|
1979
|
-
code: "resource_size_mismatch",
|
|
1980
|
-
message: `重打包后的 resource.img 大小为 ${patchedResourceBytes.length} 字节,但 boot.img 资源槽大小为 ${fitResource.size} 字节。`
|
|
1981
|
-
}
|
|
1982
|
-
};
|
|
1983
|
-
}
|
|
1984
|
-
|
|
1985
|
-
const baseResourceBytes = baseBootBytes.subarray(fitResource.offset, fitResource.offset + fitResource.size);
|
|
1986
|
-
const baseResourceHash = Buffer.from(sha256Buffer(baseResourceBytes), "hex");
|
|
1987
|
-
const patchedResourceHash = Buffer.from(sha256Buffer(patchedResourceBytes), "hex");
|
|
1988
|
-
const hashOffset = findUniqueSubBufferOffset(baseBootBytes.subarray(0, headerSize), baseResourceHash);
|
|
1989
|
-
if (hashOffset < 0) {
|
|
1990
|
-
return {
|
|
1991
|
-
ok: false,
|
|
1992
|
-
error: {
|
|
1993
|
-
code: "fit_resource_hash_not_found",
|
|
1994
|
-
message: "无法在 boot.img FIT 头中唯一定位 resource 哈希,已停止生成,避免输出不可刷写镜像。"
|
|
1995
|
-
}
|
|
1996
|
-
};
|
|
1997
|
-
}
|
|
1998
|
-
|
|
1999
|
-
const outputBootBytes = Buffer.from(baseBootBytes);
|
|
2000
|
-
patchedResourceHash.copy(outputBootBytes, hashOffset);
|
|
2001
|
-
patchedResourceBytes.copy(outputBootBytes, fitResource.offset);
|
|
2002
|
-
mkdirSync(path.dirname(outputImagePath), { recursive: true });
|
|
2003
|
-
writeFileSync(outputImagePath, outputBootBytes);
|
|
2004
|
-
const manifest = {
|
|
2005
|
-
schema_version: "1.0",
|
|
2006
|
-
operation_kind: "image.boot_logo.local_compose",
|
|
2007
|
-
board_id: "taishanpi-1m-rk3566",
|
|
2008
|
-
logo_path: logoPath,
|
|
2009
|
-
kernel_logo_path: kernelLogoSource,
|
|
2010
|
-
base_boot_image: baseBootImage,
|
|
2011
|
-
output_boot_image: outputImagePath,
|
|
2012
|
-
output_resource_image: patchedResourcePath,
|
|
2013
|
-
dimensions,
|
|
2014
|
-
fit_resource: fitResource,
|
|
2015
|
-
fit_resource_hash_offset: hashOffset,
|
|
2016
|
-
base_boot_sha256: sha256Buffer(baseBootBytes),
|
|
2017
|
-
output_boot_sha256: sha256Buffer(outputBootBytes),
|
|
2018
|
-
base_resource_sha256: sha256Buffer(baseResourceBytes),
|
|
2019
|
-
output_resource_sha256: sha256Buffer(patchedResourceBytes),
|
|
2020
|
-
converter: converter.converter,
|
|
2021
|
-
kernel_converter: kernelConverter.converter,
|
|
2022
|
-
cross_platform: true,
|
|
2023
|
-
platform: process.platform,
|
|
2024
|
-
arch: process.arch,
|
|
2025
|
-
observed_at: new Date().toISOString()
|
|
2026
|
-
};
|
|
2027
|
-
writeJsonFile(manifestPath, manifest);
|
|
2028
|
-
return {
|
|
2029
|
-
ok: true,
|
|
2030
|
-
ready_for_flash: true,
|
|
2031
|
-
local_merge_strategy: "local_resource_repack_and_fit_patch",
|
|
2032
|
-
output_image_path: outputImagePath,
|
|
2033
|
-
output_resource_path: patchedResourcePath,
|
|
2034
|
-
manifest_path: manifestPath,
|
|
2035
|
-
base_image_path: baseBootImage,
|
|
2036
|
-
summary_for_user: "启动 Logo 已在本机完成图片转换、resource.img 重打包和 boot.img 补丁生成。",
|
|
2037
|
-
manifest
|
|
2038
|
-
};
|
|
2039
|
-
}
|
|
2040
|
-
|
|
2041
|
-
async function bootLogoUpdate(args) {
|
|
2042
|
-
const logoPath = asString(args.logo_path || args.logo);
|
|
2043
|
-
if (!logoPath) throw new Error("logo_path is required");
|
|
2044
|
-
if (!existsSync(logoPath)) {
|
|
2045
|
-
return {
|
|
2046
|
-
ok: false,
|
|
2047
|
-
action: "boot_logo_update",
|
|
2048
|
-
error: {
|
|
2049
|
-
code: "logo_not_found",
|
|
2050
|
-
message: `Logo image does not exist: ${logoPath}`
|
|
2051
|
-
}
|
|
2052
|
-
};
|
|
2053
|
-
}
|
|
2054
|
-
|
|
2055
|
-
const boardId = asString(args.board_id || args.board) || "taishanpi-1m-rk3566";
|
|
2056
|
-
const outputDir = defaultActionDir("boot-logo", args.output_dir || args.outputDir);
|
|
2057
|
-
const outputImagePath = asString(args.output_image_path || args.outputImage || args.output)
|
|
2058
|
-
|| path.join(outputDir, "boot-logo-boot.img");
|
|
2059
|
-
const manifestPath = asString(args.manifest_path || args.manifest) || path.join(outputDir, "boot-logo-compose-manifest.json");
|
|
2060
|
-
const sdk = await ensureLocalToolchain(args, boardId, "images");
|
|
2061
|
-
if (!sdk.releaseRoot) {
|
|
2062
|
-
return {
|
|
2063
|
-
ok: false,
|
|
2064
|
-
action: "boot_logo_update",
|
|
2065
|
-
error: {
|
|
2066
|
-
code: "local_sdk_missing",
|
|
2067
|
-
message: "本机没有可用的 TaishanPi images SDK,无法本地生成启动 Logo 镜像。"
|
|
2068
|
-
},
|
|
2069
|
-
sdk: compactSdkState(sdk.sdkCheck, sdk.sdkInstall)
|
|
2070
|
-
};
|
|
2071
|
-
}
|
|
2072
|
-
const composeSummary = await localBootLogoCompose(args, sdk, logoPath, outputDir, outputImagePath, manifestPath);
|
|
2073
|
-
if (!composeSummary.ok) {
|
|
2074
|
-
return {
|
|
2075
|
-
ok: false,
|
|
2076
|
-
action: "boot_logo_update",
|
|
2077
|
-
error: composeSummary.error,
|
|
2078
|
-
sdk: compactSdkState(sdk.sdkCheck, sdk.sdkInstall),
|
|
2079
|
-
output_dir: outputDir
|
|
2080
|
-
};
|
|
2081
|
-
}
|
|
2082
|
-
const approved = boolValue(args.approved) || boolValue(args.approve);
|
|
2083
|
-
const readyForFlash = composeSummary.ready_for_flash === true;
|
|
2084
|
-
const result = {
|
|
2085
|
-
ok: readyForFlash,
|
|
2086
|
-
action: "boot_logo_update",
|
|
2087
|
-
board: "TaishanPi 1M RK3566",
|
|
2088
|
-
status: readyForFlash ? (approved ? "composed_ready_for_flash" : "ready_for_approval") : "composed_not_flashable",
|
|
2089
|
-
approved,
|
|
2090
|
-
summary_for_user: readyForFlash
|
|
2091
|
-
? "启动 Logo 已在本机完成格式转换、resource.img 重打包和 boot.img 镜像生成。需要真实写入开发板时,应继续走受控的本地刷写入口。"
|
|
2092
|
-
: "启动 Logo 本地生成未完成,不能直接作为刷写产物。",
|
|
2093
|
-
sdk: compactSdkState(sdk.sdkCheck, sdk.sdkInstall),
|
|
2094
|
-
artifacts: compactObject({
|
|
2095
|
-
logo_path: logoPath,
|
|
2096
|
-
base_image_path: composeSummary.base_image_path,
|
|
2097
|
-
output_image_path: outputImagePath,
|
|
2098
|
-
output_resource_path: composeSummary.output_resource_path,
|
|
2099
|
-
manifest_path: manifestPath
|
|
2100
|
-
}),
|
|
2101
|
-
compose: composeSummary,
|
|
2102
|
-
next_step: readyForFlash
|
|
2103
|
-
? "如果用户明确批准写入开发板,后续应由同一高层工具调用 Local Bridge 的受控 boot 分区刷写能力,不能让模型手动拼命令。"
|
|
2104
|
-
: "需要修复本地 SDK 图像转换或 resource 重打包工具后再刷写。",
|
|
2105
|
-
raw_omitted: boolValue(args.include_raw) ? undefined : true
|
|
2106
|
-
};
|
|
2107
|
-
if (boolValue(args.include_raw)) {
|
|
2108
|
-
result.raw = compactObject({ compose_result: composeSummary });
|
|
2109
|
-
}
|
|
2110
|
-
return result;
|
|
2111
|
-
}
|
|
2112
|
-
|
|
2113
|
-
function shellQuote(value) {
|
|
2114
|
-
const text = asString(value);
|
|
2115
|
-
return `'${text.replaceAll("'", "'\\''")}'`;
|
|
2116
|
-
}
|
|
2117
|
-
|
|
2118
|
-
function defaultQtExampleForRequest(request) {
|
|
2119
|
-
const text = asString(request).toLowerCase();
|
|
2120
|
-
if (text.includes("modbus") || text.includes("uart") || text.includes("串口") || text.includes("协议")) {
|
|
2121
|
-
const relative = path.join("board-packs", "taishanpi", "examples", "modbus-loop-demo", "taishanpi");
|
|
2122
|
-
return {
|
|
2123
|
-
sourcePath: firstExistingPath([path.join(process.cwd(), relative), path.join(REPO_ROOT, relative)]),
|
|
2124
|
-
targetName: "modbus_loop_demo",
|
|
2125
|
-
template: "taishanpi_modbus_loop_demo"
|
|
2126
|
-
};
|
|
2127
|
-
}
|
|
2128
|
-
return {
|
|
2129
|
-
sourcePath: "",
|
|
2130
|
-
targetName: "qt_smoke",
|
|
2131
|
-
template: "qt_smoke"
|
|
2132
|
-
};
|
|
2133
|
-
}
|
|
2134
|
-
|
|
2135
|
-
function qtRuntimeCommand(remotePath) {
|
|
2136
|
-
const remote = shellQuote(remotePath);
|
|
2137
|
-
return [
|
|
2138
|
-
"export QT_ROOT=/userdata/rootfs/qt6-rk3566-llvm",
|
|
2139
|
-
"export LD_LIBRARY_PATH=$QT_ROOT/lib:$LD_LIBRARY_PATH",
|
|
2140
|
-
"export QT_PLUGIN_PATH=$QT_ROOT/plugins",
|
|
2141
|
-
"export QT_QPA_PLATFORM_PLUGIN_PATH=$QT_ROOT/plugins/platforms",
|
|
2142
|
-
"export QML2_IMPORT_PATH=$QT_ROOT/qml",
|
|
2143
|
-
"export QML_IMPORT_PATH=$QT_ROOT/qml",
|
|
2144
|
-
"export QT_QUICK_CONTROLS_STYLE=Basic",
|
|
2145
|
-
`chmod +x ${remote}`,
|
|
2146
|
-
remote
|
|
2147
|
-
].join(" && ");
|
|
2148
|
-
}
|
|
2149
|
-
|
|
2150
|
-
function artifactPathFromBuild(buildResult) {
|
|
2151
|
-
const data = responseData(buildResult) || {};
|
|
2152
|
-
return asString(data.artifact_path || data.artifactPath || data.output_path || data.executable_path);
|
|
2153
|
-
}
|
|
2154
|
-
|
|
2155
|
-
function taishanpiHostFromDeviceList(scanResult) {
|
|
2156
|
-
const data = responseData(scanResult) || {};
|
|
2157
|
-
const devices = Array.isArray(data.development_boards) ? data.development_boards : [];
|
|
2158
|
-
for (const device of devices) {
|
|
2159
|
-
if (device?.board_id !== "taishanpi") continue;
|
|
2160
|
-
const host = asString(device.host || device.link_local?.host);
|
|
2161
|
-
if (host && device.status !== "bootloader") return host;
|
|
2162
|
-
}
|
|
2163
|
-
return "";
|
|
2164
|
-
}
|
|
2165
|
-
|
|
2166
|
-
async function taishanpiQtAppWorkflow(args) {
|
|
2167
|
-
const request = asString(args.request || args.prompt);
|
|
2168
|
-
const boardId = "taishanpi-1m-rk3566";
|
|
2169
|
-
const selected = defaultQtExampleForRequest(request);
|
|
2170
|
-
const outputDir = defaultActionDir("qt-app", args.output_dir || args.outputDir);
|
|
2171
|
-
const explicitSource = asString(args.source_path || args.source);
|
|
2172
|
-
let sourcePath = explicitSource || selected.sourcePath;
|
|
2173
|
-
const projectDir = asString(args.project_dir || args.projectDir);
|
|
2174
|
-
if (!explicitSource && projectDir && selected.sourcePath) {
|
|
2175
|
-
mkdirSync(path.dirname(projectDir), { recursive: true });
|
|
2176
|
-
cpSync(selected.sourcePath, projectDir, { recursive: true, force: true });
|
|
2177
|
-
sourcePath = projectDir;
|
|
2178
|
-
}
|
|
2179
|
-
if (!sourcePath && selected.template !== "qt_smoke") {
|
|
2180
|
-
return {
|
|
2181
|
-
ok: false,
|
|
2182
|
-
action: "taishanpi_qt_app_workflow",
|
|
2183
|
-
error: {
|
|
2184
|
-
code: "qt_template_not_found",
|
|
2185
|
-
message: "未找到匹配需求的官方 Qt 示例模板。",
|
|
2186
|
-
remediation: "请在包含 board-packs 的 Embed Labs 项目目录中运行,或传入 source_path 指向已有 Qt/CMake 工程。"
|
|
2187
|
-
},
|
|
2188
|
-
template: selected.template
|
|
2189
|
-
};
|
|
2190
|
-
}
|
|
2191
|
-
const targetName = asString(args.target_name || args.targetName) || selected.targetName;
|
|
2192
|
-
const buildDir = asString(args.build_dir || args.buildDir) || path.join(outputDir, targetName || "qt-app-build");
|
|
2193
|
-
|
|
2194
|
-
const sdk = await ensureLocalToolchain(args, boardId, "qt");
|
|
2195
|
-
const buildArgs = { build_dir: buildDir, target_name: targetName, release_root: sdk.releaseRoot };
|
|
2196
|
-
if (sourcePath) buildArgs.source_path = sourcePath;
|
|
2197
|
-
const buildResult = await localQtSmoke(buildArgs);
|
|
2198
|
-
const artifactPath = artifactPathFromBuild(buildResult);
|
|
2199
|
-
|
|
2200
|
-
let deployResult;
|
|
2201
|
-
const approved = boolValue(args.approved) || boolValue(args.approve);
|
|
2202
|
-
const shouldDeploy = boolValue(args.deploy) || boolValue(args.run);
|
|
2203
|
-
if (shouldDeploy && approved) {
|
|
2204
|
-
const scan = await runEmbed(["device", "list"]);
|
|
2205
|
-
const host = asString(args.host) || taishanpiHostFromDeviceList(scan);
|
|
2206
|
-
if (!host) {
|
|
2207
|
-
deployResult = {
|
|
2208
|
-
ok: false,
|
|
2209
|
-
error: {
|
|
2210
|
-
code: "runtime_host_not_found",
|
|
2211
|
-
message: "Qt 程序已完成交叉编译,但当前未检测到可通过 SSH 部署的泰山派运行态主机。",
|
|
2212
|
-
remediation: "确认泰山派已启动初始化系统并连接到当前电脑/网络后,再调用同一工具部署。"
|
|
2213
|
-
}
|
|
2214
|
-
};
|
|
2215
|
-
} else if (!artifactPath) {
|
|
2216
|
-
deployResult = {
|
|
2217
|
-
ok: false,
|
|
2218
|
-
error: {
|
|
2219
|
-
code: "artifact_not_found",
|
|
2220
|
-
message: "Qt 构建没有返回可部署的可执行文件路径。"
|
|
2221
|
-
}
|
|
2222
|
-
};
|
|
2223
|
-
} else {
|
|
2224
|
-
const remotePath = asString(args.remote_path || args.remotePath)
|
|
2225
|
-
|| `/userdata/embed-labs/apps/${path.basename(artifactPath)}`;
|
|
2226
|
-
const command = ["deploy", "taishanpi", "--host", host, "--artifact", artifactPath, "--remote-path", remotePath, "--approve"];
|
|
2227
|
-
if (boolValue(args.run)) {
|
|
2228
|
-
command.push("--run", "--run-command", qtRuntimeCommand(remotePath));
|
|
2229
|
-
}
|
|
2230
|
-
deployResult = await runEmbed(command);
|
|
2231
|
-
}
|
|
2232
|
-
}
|
|
2233
|
-
|
|
2234
|
-
const buildData = responseData(buildResult) || {};
|
|
2235
|
-
const deployData = responseData(deployResult) || {};
|
|
2236
|
-
const result = {
|
|
2237
|
-
ok: buildResult?.response?.ok === true && (!deployResult || deployResult?.response?.ok === true || deployResult?.ok === false),
|
|
2238
|
-
action: "taishanpi_qt_app_workflow",
|
|
2239
|
-
board: "TaishanPi 1M RK3566",
|
|
2240
|
-
status: deployResult ? (deployResult?.response?.ok === true ? "built_and_deployed" : "built_deploy_pending") : "built",
|
|
2241
|
-
template: selected.template,
|
|
2242
|
-
request: request || undefined,
|
|
2243
|
-
sdk: compactSdkState(sdk.sdkCheck, sdk.sdkInstall),
|
|
2244
|
-
source_path: sourcePath || "built-in Qt smoke source",
|
|
2245
|
-
build: compactObject({
|
|
2246
|
-
build_dir: buildDir,
|
|
2247
|
-
target_name: targetName,
|
|
2248
|
-
artifact_path: artifactPath,
|
|
2249
|
-
artifact_size_bytes: buildData.artifact_size_bytes,
|
|
2250
|
-
artifact_sha256: buildData.artifact_sha256,
|
|
2251
|
-
summary_for_user: buildData.summary_for_user
|
|
2252
|
-
}),
|
|
2253
|
-
deploy: deployResult ? compactObject({
|
|
2254
|
-
ok: deployResult?.response?.ok ?? deployResult?.ok,
|
|
2255
|
-
host: deployData.host,
|
|
2256
|
-
remote_path: deployData.remote_path,
|
|
2257
|
-
ran: deployData.ran,
|
|
2258
|
-
error: deployResult?.error || deployResult?.response?.error
|
|
2259
|
-
}) : undefined,
|
|
2260
|
-
summary_for_user: deployResult?.response?.ok === true
|
|
2261
|
-
? "Qt 程序已按泰山派 Qt 运行库路径完成构建、部署并启动。"
|
|
2262
|
-
: "Qt 程序已通过本机 LLVM/Qt 工具链完成交叉编译。",
|
|
2263
|
-
next_step: deployResult ? "查看部署/运行结果;如需协议采集,再调用 RP2350 Monitor 的对应高层工具。" : "如需上板运行,用户批准后用同一工具传入 deploy=true、run=true、approved=true。",
|
|
2264
|
-
raw_omitted: boolValue(args.include_raw) ? undefined : true
|
|
2265
|
-
};
|
|
2266
|
-
if (boolValue(args.include_raw)) {
|
|
2267
|
-
result.raw = compactObject({ build_result: buildResult, deploy_result: deployResult });
|
|
2268
|
-
}
|
|
2269
|
-
return result;
|
|
2270
|
-
}
|
|
2271
|
-
|
|
2272
610
|
async function localToolchainLatest(args) {
|
|
2273
611
|
const command = ["local", "toolchain", "latest"];
|
|
2274
612
|
pushOptional(command, "board", args.board_id || args.board);
|
|
@@ -2277,24 +615,6 @@ async function localToolchainLatest(args) {
|
|
|
2277
615
|
return await runEmbed(command);
|
|
2278
616
|
}
|
|
2279
617
|
|
|
2280
|
-
async function localToolchainList(args) {
|
|
2281
|
-
const command = ["local", "toolchain", "list"];
|
|
2282
|
-
pushOptional(command, "board", args.board_id || args.board);
|
|
2283
|
-
pushOptional(command, "channel", args.channel);
|
|
2284
|
-
pushOptional(command, "metadata-root", args.metadata_root || args.metadataRoot);
|
|
2285
|
-
pushOptional(command, "install-root", args.install_root || args.installRoot);
|
|
2286
|
-
return await runEmbed(command);
|
|
2287
|
-
}
|
|
2288
|
-
|
|
2289
|
-
async function localToolchainInstalled(args) {
|
|
2290
|
-
const command = ["local", "toolchain", "installed"];
|
|
2291
|
-
pushOptional(command, "board", args.board_id || args.board);
|
|
2292
|
-
pushOptional(command, "channel", args.channel);
|
|
2293
|
-
pushOptional(command, "metadata-root", args.metadata_root || args.metadataRoot);
|
|
2294
|
-
pushOptional(command, "install-root", args.install_root || args.installRoot);
|
|
2295
|
-
return await runEmbed(command);
|
|
2296
|
-
}
|
|
2297
|
-
|
|
2298
618
|
async function localToolchainCurrent(args) {
|
|
2299
619
|
const command = ["local", "toolchain", "current"];
|
|
2300
620
|
pushOptional(command, "install-root", args.install_root || args.installRoot);
|
|
@@ -2309,373 +629,13 @@ async function localToolchainInstall(args) {
|
|
|
2309
629
|
pushOptional(command, "source-url", args.source_url || args.sourceUrl);
|
|
2310
630
|
pushOptional(command, "source-release-root", args.source_release_root || args.sourceReleaseRoot);
|
|
2311
631
|
pushOptional(command, "install-root", args.install_root || args.installRoot);
|
|
2312
|
-
pushOptional(command, "mode", args.mode);
|
|
2313
632
|
if (boolValue(args.force)) command.push("--force");
|
|
2314
633
|
return await runEmbed(command);
|
|
2315
634
|
}
|
|
2316
635
|
|
|
2317
|
-
async function localToolchainUninstall(args) {
|
|
2318
|
-
const boardId = asString(args.board_id || args.board);
|
|
2319
|
-
if (!boardId) throw new Error("board_id is required");
|
|
2320
|
-
const command = ["local", "toolchain", "uninstall", "--board", boardId];
|
|
2321
|
-
pushOptional(command, "install-root", args.install_root || args.installRoot);
|
|
2322
|
-
return await runEmbed(command);
|
|
2323
|
-
}
|
|
2324
|
-
|
|
2325
636
|
async function localToolchainValidate(args) {
|
|
2326
637
|
const command = ["local", "toolchain", "validate"];
|
|
2327
|
-
pushOptional(command, "board", args.board_id || args.board);
|
|
2328
638
|
pushOptional(command, "release-root", args.release_root || args.releaseRoot);
|
|
2329
|
-
pushOptional(command, "mode", args.mode);
|
|
2330
|
-
return await runEmbed(command);
|
|
2331
|
-
}
|
|
2332
|
-
|
|
2333
|
-
function isTaishanpiInitialTarget(value) {
|
|
2334
|
-
const text = asString(value).toLowerCase();
|
|
2335
|
-
return text.includes("taishan") || text.includes("tspi") || text.includes("rk3566") || text.includes("1m");
|
|
2336
|
-
}
|
|
2337
|
-
|
|
2338
|
-
function isRp2350InitialTarget(value) {
|
|
2339
|
-
const text = asString(value).toLowerCase();
|
|
2340
|
-
return text.includes("rp2350") || text.includes("pico") || text.includes("coloreasy");
|
|
2341
|
-
}
|
|
2342
|
-
|
|
2343
|
-
async function initialImageFlash(args) {
|
|
2344
|
-
const requestedBoard = asString(args.board_id || args.board);
|
|
2345
|
-
if (isRp2350InitialTarget(requestedBoard)) {
|
|
2346
|
-
return await rp2350InitialFirmwareFlash(args);
|
|
2347
|
-
}
|
|
2348
|
-
if (isTaishanpiInitialTarget(requestedBoard)) {
|
|
2349
|
-
return await taishanpiInitialImageFlash(args);
|
|
2350
|
-
}
|
|
2351
|
-
|
|
2352
|
-
const scan = await runEmbed(["device", "list"]);
|
|
2353
|
-
const scanData = responseData(scan) || {};
|
|
2354
|
-
const devices = Array.isArray(scanData.development_boards) ? scanData.development_boards : [];
|
|
2355
|
-
const candidates = devices
|
|
2356
|
-
.filter((item) => item?.board_id === "taishanpi" || isRp2350Device(item))
|
|
2357
|
-
.map((item) => compactObject({
|
|
2358
|
-
local_device_id: item.local_device_id || item.device_id,
|
|
2359
|
-
board_id: item.board_id,
|
|
2360
|
-
variant_id: item.variant_id,
|
|
2361
|
-
display_name: item.display_name || item.name,
|
|
2362
|
-
status: item.status
|
|
2363
|
-
}));
|
|
2364
|
-
|
|
2365
|
-
if (candidates.length === 1) {
|
|
2366
|
-
const candidate = candidates[0];
|
|
2367
|
-
const nextArgs = { ...args, local_device_id: args.local_device_id || candidate.local_device_id };
|
|
2368
|
-
if (candidate.board_id === "taishanpi") return await taishanpiInitialImageFlash(nextArgs);
|
|
2369
|
-
return await rp2350InitialFirmwareFlash({ ...nextArgs, board_id: candidate.variant_id || candidate.board_id });
|
|
2370
|
-
}
|
|
2371
|
-
|
|
2372
|
-
return {
|
|
2373
|
-
ok: false,
|
|
2374
|
-
error: {
|
|
2375
|
-
code: candidates.length > 1 ? "ambiguous_board" : "target_not_found",
|
|
2376
|
-
message: candidates.length > 1
|
|
2377
|
-
? "检测到多块可刷写开发板,请指定要刷写的开发板。"
|
|
2378
|
-
: "当前未检测到可刷写初始化镜像的支持开发板。",
|
|
2379
|
-
candidates,
|
|
2380
|
-
remediation: candidates.length > 1
|
|
2381
|
-
? "重新调用 dbt_initial_image_flash,并传入对应 local_device_id。"
|
|
2382
|
-
: "请连接开发板,或让开发板进入 Loader/Maskrom/BOOTSEL 后重新查询开发板状态。"
|
|
2383
|
-
}
|
|
2384
|
-
};
|
|
2385
|
-
}
|
|
2386
|
-
|
|
2387
|
-
function compactSdkState(sdkCheck, sdkInstall) {
|
|
2388
|
-
const checkData = responseData(sdkCheck) || {};
|
|
2389
|
-
const installData = responseData(sdkInstall) || {};
|
|
2390
|
-
const validation = objectValue(installData.validation) || {};
|
|
2391
|
-
const repairedOk = typeof installData.ok === "boolean" ? installData.ok : validation.ok;
|
|
2392
|
-
return compactObject({
|
|
2393
|
-
checked: !!sdkCheck,
|
|
2394
|
-
installed_or_repaired: !!sdkInstall,
|
|
2395
|
-
ok: sdkInstall ? repairedOk : checkData.ok,
|
|
2396
|
-
release_root: installData.release_root || validation.release_root || checkData.release_root,
|
|
2397
|
-
mode: installData.mode || validation.mode || checkData.mode,
|
|
2398
|
-
summary_for_user: installData.summary_for_user || validation.summary_for_user || checkData.summary_for_user
|
|
2399
|
-
});
|
|
2400
|
-
}
|
|
2401
|
-
|
|
2402
|
-
function compactFlashPlan(plan, boardLabel, imagePath) {
|
|
2403
|
-
const data = responseData(plan) || {};
|
|
2404
|
-
const target = data.target_device || {};
|
|
2405
|
-
return compactObject({
|
|
2406
|
-
ready: data.ready === true,
|
|
2407
|
-
board: boardLabel || data.board_id,
|
|
2408
|
-
boot_mode: data.boot_mode,
|
|
2409
|
-
local_device_id: data.local_device_id || target.local_device_id || target.device_id,
|
|
2410
|
-
target: target.display_name || target.name,
|
|
2411
|
-
target_status: target.status,
|
|
2412
|
-
image_dir: data.image_dir,
|
|
2413
|
-
image_dir_source: data.image_dir_source,
|
|
2414
|
-
artifact_path: imagePath,
|
|
2415
|
-
approval_required: data.approval_required === true,
|
|
2416
|
-
destructive: data.destructive === true,
|
|
2417
|
-
step_count: Array.isArray(data.steps) ? data.steps.length : undefined,
|
|
2418
|
-
partitions: Array.isArray(data.partitions)
|
|
2419
|
-
? data.partitions.map((item) => item.partition).filter(Boolean)
|
|
2420
|
-
: undefined,
|
|
2421
|
-
warnings: compactWarnings(data.warnings),
|
|
2422
|
-
errors: data.errors
|
|
2423
|
-
});
|
|
2424
|
-
}
|
|
2425
|
-
|
|
2426
|
-
function compactWarnings(warnings) {
|
|
2427
|
-
if (!Array.isArray(warnings) || warnings.length === 0) return undefined;
|
|
2428
|
-
return warnings.map((item) => compactObject({
|
|
2429
|
-
code: item.code,
|
|
2430
|
-
message: item.message,
|
|
2431
|
-
partitions: Array.isArray(item.partitions) ? item.partitions.map((part) => part.partition).filter(Boolean) : undefined
|
|
2432
|
-
}));
|
|
2433
|
-
}
|
|
2434
|
-
|
|
2435
|
-
function localJobIdFromRun(response) {
|
|
2436
|
-
const data = responseData(response) || {};
|
|
2437
|
-
return asString(data.local_job_id || data.job_id);
|
|
2438
|
-
}
|
|
2439
|
-
|
|
2440
|
-
async function waitForLocalJob(jobId, timeoutMs = 180000) {
|
|
2441
|
-
const deadline = Date.now() + Math.max(5000, timeoutMs);
|
|
2442
|
-
let lastStatus = null;
|
|
2443
|
-
while (Date.now() < deadline) {
|
|
2444
|
-
lastStatus = await runEmbed(["task", "status", jobId]);
|
|
2445
|
-
const data = responseData(lastStatus) || {};
|
|
2446
|
-
const status = asString(data.status);
|
|
2447
|
-
if (["completed", "failed", "cancelled"].includes(status)) {
|
|
2448
|
-
return lastStatus;
|
|
2449
|
-
}
|
|
2450
|
-
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2451
|
-
}
|
|
2452
|
-
return lastStatus || { ok: false, response: { ok: false, error: { code: "job_status_timeout", message: "Timed out waiting for local flash job." } } };
|
|
2453
|
-
}
|
|
2454
|
-
|
|
2455
|
-
function compactFlashRunResult(runResult, finalStatus) {
|
|
2456
|
-
const started = responseData(runResult) || {};
|
|
2457
|
-
const finalData = responseData(finalStatus) || {};
|
|
2458
|
-
const data = Object.keys(finalData).length > 0 ? finalData : started;
|
|
2459
|
-
return compactObject({
|
|
2460
|
-
local_job_id: data.local_job_id || started.local_job_id,
|
|
2461
|
-
status: data.status || started.status,
|
|
2462
|
-
started_at: data.started_at || started.started_at,
|
|
2463
|
-
updated_at: data.updated_at || started.updated_at,
|
|
2464
|
-
completed_at: data.completed_at,
|
|
2465
|
-
error: data.error,
|
|
2466
|
-
logs: Array.isArray(data.logs) ? data.logs.slice(-8) : undefined
|
|
2467
|
-
});
|
|
2468
|
-
}
|
|
2469
|
-
|
|
2470
|
-
function compactInitialFlashResult({ boardLabel, sdkCheck, sdkInstall, imageDir, artifactPath, plan, runResult, finalStatus, approved, includeRaw }) {
|
|
2471
|
-
const planSummary = compactFlashPlan(plan, boardLabel, artifactPath);
|
|
2472
|
-
const runSummary = runResult ? compactFlashRunResult(runResult, finalStatus) : undefined;
|
|
2473
|
-
const status = runSummary?.status || (planSummary.ready ? "ready_for_approval" : "not_ready");
|
|
2474
|
-
const summary = runSummary
|
|
2475
|
-
? (runSummary.status === "completed"
|
|
2476
|
-
? `${boardLabel} 初始化镜像刷写已完成。`
|
|
2477
|
-
: runSummary.status === "failed"
|
|
2478
|
-
? `${boardLabel} 初始化镜像刷写失败,请查看 error 和最近日志。`
|
|
2479
|
-
: `${boardLabel} 初始化镜像刷写任务已启动,当前状态:${runSummary.status || "running"}。`)
|
|
2480
|
-
: (planSummary.ready
|
|
2481
|
-
? `${boardLabel} 初始化镜像刷写计划已就绪,确认后可直接执行。`
|
|
2482
|
-
: `${boardLabel} 初始化镜像刷写计划未就绪。`);
|
|
2483
|
-
const result = {
|
|
2484
|
-
ok: !runSummary?.error && planSummary.ready === true,
|
|
2485
|
-
action: "initial_image_flash",
|
|
2486
|
-
board: boardLabel,
|
|
2487
|
-
status,
|
|
2488
|
-
approved: approved === true,
|
|
2489
|
-
summary_for_user: summary,
|
|
2490
|
-
sdk: compactSdkState(sdkCheck, sdkInstall),
|
|
2491
|
-
plan: planSummary,
|
|
2492
|
-
run: runSummary,
|
|
2493
|
-
next_step: runSummary
|
|
2494
|
-
? (runSummary.status === "completed" ? "可以重新查询开发板状态,确认系统已启动。" : "继续查询该 local_job_id 的任务状态。")
|
|
2495
|
-
: (planSummary.ready ? "用户确认刷写后,调用同一工具并传入 approved=true。" : "根据 errors/warnings 修复后重试。"),
|
|
2496
|
-
raw_omitted: includeRaw ? undefined : true
|
|
2497
|
-
};
|
|
2498
|
-
if (includeRaw) {
|
|
2499
|
-
result.raw = compactObject({ sdk_check: sdkCheck, sdk_install: sdkInstall, flash_plan: plan, flash_result: runResult, final_status: finalStatus });
|
|
2500
|
-
}
|
|
2501
|
-
return result;
|
|
2502
|
-
}
|
|
2503
|
-
|
|
2504
|
-
async function taishanpiInitialImageFlash(args) {
|
|
2505
|
-
const sdkBoardId = asString(args.board_id || args.board) || "taishanpi-1m-rk3566";
|
|
2506
|
-
const explicitImageDir = asString(args.image_dir || args.imageDir);
|
|
2507
|
-
let sdkCheck = null;
|
|
2508
|
-
let sdkInstall = null;
|
|
2509
|
-
let releaseRoot = "";
|
|
2510
|
-
if (!explicitImageDir) {
|
|
2511
|
-
sdkCheck = await localToolchainValidate({ board_id: sdkBoardId, mode: "images" });
|
|
2512
|
-
const checkData = responseData(sdkCheck) || {};
|
|
2513
|
-
if (checkData.ok) {
|
|
2514
|
-
releaseRoot = asString(checkData.release_root);
|
|
2515
|
-
} else {
|
|
2516
|
-
const installArgs = {
|
|
2517
|
-
board_id: sdkBoardId,
|
|
2518
|
-
channel: args.channel,
|
|
2519
|
-
metadata_root: args.metadata_root || args.metadataRoot,
|
|
2520
|
-
install_root: args.install_root || args.installRoot,
|
|
2521
|
-
mode: "images",
|
|
2522
|
-
force: args.force
|
|
2523
|
-
};
|
|
2524
|
-
sdkInstall = await localToolchainInstall(installArgs);
|
|
2525
|
-
const installedData = responseData(sdkInstall) || {};
|
|
2526
|
-
releaseRoot = asString(installedData.release_root);
|
|
2527
|
-
}
|
|
2528
|
-
}
|
|
2529
|
-
const imageDir = explicitImageDir || (releaseRoot ? path.join(releaseRoot, "images", "current") : "");
|
|
2530
|
-
const localDeviceId = asString(args.local_device_id || args.localDeviceId || args.device_id || args.deviceId);
|
|
2531
|
-
const planCommand = ["flash", "plan", "--board", "taishanpi"];
|
|
2532
|
-
if (imageDir) pushOptional(planCommand, "image-dir", imageDir);
|
|
2533
|
-
if (localDeviceId) pushOptional(planCommand, "local-device-id", localDeviceId);
|
|
2534
|
-
const plan = await runEmbed(planCommand);
|
|
2535
|
-
const approved = boolValue(args.approved) || boolValue(args.approve);
|
|
2536
|
-
if (!approved) {
|
|
2537
|
-
return compactInitialFlashResult({
|
|
2538
|
-
boardLabel: "TaishanPi 1M RK3566",
|
|
2539
|
-
sdkCheck,
|
|
2540
|
-
sdkInstall,
|
|
2541
|
-
imageDir,
|
|
2542
|
-
plan,
|
|
2543
|
-
approved,
|
|
2544
|
-
includeRaw: boolValue(args.include_raw)
|
|
2545
|
-
});
|
|
2546
|
-
}
|
|
2547
|
-
const runCommand = ["flash", "run", "--board", "taishanpi", "--approve"];
|
|
2548
|
-
if (imageDir) pushOptional(runCommand, "image-dir", imageDir);
|
|
2549
|
-
if (localDeviceId) pushOptional(runCommand, "local-device-id", localDeviceId);
|
|
2550
|
-
const flashed = await runEmbed(runCommand);
|
|
2551
|
-
const wait = args.wait === undefined ? true : boolValue(args.wait);
|
|
2552
|
-
const jobId = localJobIdFromRun(flashed);
|
|
2553
|
-
const finalStatus = wait && jobId ? await waitForLocalJob(jobId, Number(args.timeout_ms) || 180000) : undefined;
|
|
2554
|
-
return compactInitialFlashResult({
|
|
2555
|
-
boardLabel: "TaishanPi 1M RK3566",
|
|
2556
|
-
sdkCheck,
|
|
2557
|
-
sdkInstall,
|
|
2558
|
-
imageDir,
|
|
2559
|
-
plan,
|
|
2560
|
-
runResult: flashed,
|
|
2561
|
-
finalStatus,
|
|
2562
|
-
approved,
|
|
2563
|
-
includeRaw: boolValue(args.include_raw)
|
|
2564
|
-
});
|
|
2565
|
-
}
|
|
2566
|
-
|
|
2567
|
-
async function rp2350MonitorFirmwareFlash(args) {
|
|
2568
|
-
const boardId = asString(args.board_id || args.board) || "pico2w-rp2350-monitor";
|
|
2569
|
-
const installArgs = {
|
|
2570
|
-
board_id: boardId,
|
|
2571
|
-
channel: args.channel,
|
|
2572
|
-
metadata_root: args.metadata_root || args.metadataRoot,
|
|
2573
|
-
install_root: args.install_root || args.installRoot,
|
|
2574
|
-
mode: "firmware",
|
|
2575
|
-
force: args.force
|
|
2576
|
-
};
|
|
2577
|
-
const installed = await localToolchainInstall(installArgs);
|
|
2578
|
-
const installedData = responseData(installed) || {};
|
|
2579
|
-
const releaseRoot = asString(installedData.release_root);
|
|
2580
|
-
const artifactPath = asString(args.artifact_path || args.artifact)
|
|
2581
|
-
|| (releaseRoot ? path.join(releaseRoot, "toolkit-runtime", "rp2350-monitor", "firmware", "rp2350_monitor.uf2") : "");
|
|
2582
|
-
if (!artifactPath) {
|
|
2583
|
-
throw new Error("RP2350 Monitor firmware UF2 could not be resolved after local toolchain install.");
|
|
2584
|
-
}
|
|
2585
|
-
const plan = await runEmbed(["flash", "plan", "--board", "rp2350", "--artifact", artifactPath]);
|
|
2586
|
-
const approved = boolValue(args.approved) || boolValue(args.approve);
|
|
2587
|
-
if (!approved) {
|
|
2588
|
-
const planData = responseData(plan) || plan;
|
|
2589
|
-
const automaticBootsel = !!planData.automatic_bootsel
|
|
2590
|
-
|| (Array.isArray(planData.steps) && planData.steps.some((step) => step && step.kind === "picotool_load_force_usb"));
|
|
2591
|
-
return {
|
|
2592
|
-
ok: true,
|
|
2593
|
-
firmware_install: installed,
|
|
2594
|
-
artifact_path: artifactPath,
|
|
2595
|
-
flash_plan: plan,
|
|
2596
|
-
next_step: automaticBootsel
|
|
2597
|
-
? "Flash plan is ready. After user approval, call this tool again with approved=true."
|
|
2598
|
-
: "Flash plan needs a visible UF2 target volume before it can run."
|
|
2599
|
-
};
|
|
2600
|
-
}
|
|
2601
|
-
const flashed = await runEmbed(["flash", "run", "--board", "rp2350", "--artifact", artifactPath, "--approve"]);
|
|
2602
|
-
return {
|
|
2603
|
-
ok: true,
|
|
2604
|
-
firmware_install: installed,
|
|
2605
|
-
artifact_path: artifactPath,
|
|
2606
|
-
flash_plan: plan,
|
|
2607
|
-
flash_result: flashed
|
|
2608
|
-
};
|
|
2609
|
-
}
|
|
2610
|
-
|
|
2611
|
-
function rp2350InitialFirmwarePath(boardId, releaseRoot) {
|
|
2612
|
-
const normalized = boardId.toLowerCase();
|
|
2613
|
-
const boardDir = normalized.includes("coloreasy") ? "coloreasypico2" : "pico2w";
|
|
2614
|
-
return releaseRoot ? path.join(releaseRoot, "rp2350-initial-firmware", boardDir, "initial.uf2") : "";
|
|
2615
|
-
}
|
|
2616
|
-
|
|
2617
|
-
async function rp2350InitialFirmwareFlash(args) {
|
|
2618
|
-
const boardId = asString(args.board_id || args.board) || "pico2w-rp2350-monitor";
|
|
2619
|
-
const installArgs = {
|
|
2620
|
-
board_id: boardId,
|
|
2621
|
-
channel: args.channel,
|
|
2622
|
-
metadata_root: args.metadata_root || args.metadataRoot,
|
|
2623
|
-
install_root: args.install_root || args.installRoot,
|
|
2624
|
-
mode: "firmware",
|
|
2625
|
-
force: args.force
|
|
2626
|
-
};
|
|
2627
|
-
const installed = await localToolchainInstall(installArgs);
|
|
2628
|
-
const installedData = responseData(installed) || {};
|
|
2629
|
-
const releaseRoot = asString(installedData.release_root);
|
|
2630
|
-
const artifactPath = asString(args.artifact_path || args.artifact) || rp2350InitialFirmwarePath(boardId, releaseRoot);
|
|
2631
|
-
if (!artifactPath) {
|
|
2632
|
-
throw new Error("RP2350 initialization firmware UF2 could not be resolved after local toolchain install.");
|
|
2633
|
-
}
|
|
2634
|
-
const plan = await runEmbed(["flash", "plan", "--board", "rp2350", "--artifact", artifactPath]);
|
|
2635
|
-
const approved = boolValue(args.approved) || boolValue(args.approve);
|
|
2636
|
-
if (!approved) {
|
|
2637
|
-
const planData = responseData(plan) || plan;
|
|
2638
|
-
const automaticBootsel = !!planData.automatic_bootsel
|
|
2639
|
-
|| (Array.isArray(planData.steps) && planData.steps.some((step) => step && step.kind === "picotool_load_force_usb"));
|
|
2640
|
-
return {
|
|
2641
|
-
ok: true,
|
|
2642
|
-
firmware_install: installed,
|
|
2643
|
-
artifact_path: artifactPath,
|
|
2644
|
-
flash_plan: plan,
|
|
2645
|
-
next_step: automaticBootsel
|
|
2646
|
-
? "Initialization flash plan is ready. After user approval, call this tool again with approved=true."
|
|
2647
|
-
: "Initialization flash plan needs a visible UF2 target volume before it can run."
|
|
2648
|
-
};
|
|
2649
|
-
}
|
|
2650
|
-
const flashed = await runEmbed(["flash", "run", "--board", "rp2350", "--artifact", artifactPath, "--approve"]);
|
|
2651
|
-
return {
|
|
2652
|
-
ok: true,
|
|
2653
|
-
firmware_install: installed,
|
|
2654
|
-
artifact_path: artifactPath,
|
|
2655
|
-
flash_plan: plan,
|
|
2656
|
-
flash_result: flashed
|
|
2657
|
-
};
|
|
2658
|
-
}
|
|
2659
|
-
|
|
2660
|
-
async function pluginUpdateCheck(args) {
|
|
2661
|
-
const command = ["plugin", "update", "check"];
|
|
2662
|
-
pushOptional(command, "release-url", args.release_url || args.releaseUrl);
|
|
2663
|
-
pushOptional(command, "target", args.target);
|
|
2664
|
-
pushOptional(command, "codex-target", args.codex_target || args.codexTarget);
|
|
2665
|
-
pushOptional(command, "opencode-target", args.opencode_target || args.opencodeTarget);
|
|
2666
|
-
return await runEmbed(command);
|
|
2667
|
-
}
|
|
2668
|
-
|
|
2669
|
-
async function pluginUpdate(args) {
|
|
2670
|
-
const target = asString(args.target_plugin || args.plugin || "all").toLowerCase();
|
|
2671
|
-
if (!["codex", "opencode", "all"].includes(target)) {
|
|
2672
|
-
throw new Error("target_plugin must be codex, opencode, or all");
|
|
2673
|
-
}
|
|
2674
|
-
const command = ["plugin", "update", target];
|
|
2675
|
-
pushOptional(command, "release-url", args.release_url || args.releaseUrl);
|
|
2676
|
-
pushOptional(command, "target", args.target);
|
|
2677
|
-
pushOptional(command, "codex-target", args.codex_target || args.codexTarget);
|
|
2678
|
-
pushOptional(command, "opencode-target", args.opencode_target || args.opencodeTarget);
|
|
2679
639
|
return await runEmbed(command);
|
|
2680
640
|
}
|
|
2681
641
|
|
|
@@ -2701,61 +661,6 @@ async function localQtSmoke(args) {
|
|
|
2701
661
|
return await runEmbed(command);
|
|
2702
662
|
}
|
|
2703
663
|
|
|
2704
|
-
function serverWorkspaceDisabledResult() {
|
|
2705
|
-
return {
|
|
2706
|
-
ok: false,
|
|
2707
|
-
error: {
|
|
2708
|
-
code: "server_workspaces_disabled",
|
|
2709
|
-
message: "Embed Labs now uses the user's local SDK/toolchain environment for source edits, builds, and board operation. Server-side user workspaces, Docker builds, and image generation are not exposed by the plugin.",
|
|
2710
|
-
remediation: "Use dbt_local_toolchain_install, dbt_local_toolchain_validate, dbt_local_compile, dbt_local_qt_smoke, and Local Bridge board tools. Cloud API remains available for account login, device binding, board knowledge, plugin releases, MCP service logs, and SDK downloads."
|
|
2711
|
-
}
|
|
2712
|
-
};
|
|
2713
|
-
}
|
|
2714
|
-
|
|
2715
|
-
function isServerWorkspaceToolName(name) {
|
|
2716
|
-
return [
|
|
2717
|
-
"dbt_workspace_provision",
|
|
2718
|
-
"dbt_application_generate",
|
|
2719
|
-
"dbt_source_list",
|
|
2720
|
-
"dbt_source_get",
|
|
2721
|
-
"dbt_source_search",
|
|
2722
|
-
"dbt_source_put",
|
|
2723
|
-
"dbt_source_patch",
|
|
2724
|
-
"dbt_application_compile",
|
|
2725
|
-
"dbt_image_generate",
|
|
2726
|
-
"dbt_workspace_release"
|
|
2727
|
-
].includes(name);
|
|
2728
|
-
}
|
|
2729
|
-
|
|
2730
|
-
function isServerWorkspaceAction(action) {
|
|
2731
|
-
return [
|
|
2732
|
-
"provision",
|
|
2733
|
-
"workspace-provision",
|
|
2734
|
-
"source-list",
|
|
2735
|
-
"list-source",
|
|
2736
|
-
"source-get",
|
|
2737
|
-
"source-read",
|
|
2738
|
-
"read-source",
|
|
2739
|
-
"source-search",
|
|
2740
|
-
"search-source",
|
|
2741
|
-
"source-put",
|
|
2742
|
-
"source-write",
|
|
2743
|
-
"write-source",
|
|
2744
|
-
"source-patch",
|
|
2745
|
-
"patch-source",
|
|
2746
|
-
"app",
|
|
2747
|
-
"application",
|
|
2748
|
-
"application-generate",
|
|
2749
|
-
"build-run",
|
|
2750
|
-
"compile",
|
|
2751
|
-
"application-compile",
|
|
2752
|
-
"image",
|
|
2753
|
-
"image-generate",
|
|
2754
|
-
"release",
|
|
2755
|
-
"workspace-release"
|
|
2756
|
-
].includes(action);
|
|
2757
|
-
}
|
|
2758
|
-
|
|
2759
664
|
async function workspaceProvision(args) {
|
|
2760
665
|
const account = asString(args.account_id || args.account);
|
|
2761
666
|
const project = asString(args.project_id || args.project);
|
|
@@ -2917,96 +822,58 @@ async function workspaceRelease(args) {
|
|
|
2917
822
|
}
|
|
2918
823
|
|
|
2919
824
|
async function callTool(name, args = {}) {
|
|
2920
|
-
if (name === "
|
|
2921
|
-
if (name === "dbt_plugin_update_check") return await pluginUpdateCheck(args);
|
|
2922
|
-
if (name === "dbt_plugin_update") return await pluginUpdate(args);
|
|
2923
|
-
if (name === "dbt_supported_boards") return await supportedBoards(args);
|
|
2924
|
-
if (name === "dbt_local_toolchain_list") return await localToolchainList(args);
|
|
2925
|
-
if (name === "dbt_local_toolchain_installed") return await localToolchainInstalled(args);
|
|
2926
|
-
if (name === "dbt_local_toolchain_latest") return await localToolchainLatest(args);
|
|
2927
|
-
if (name === "dbt_local_toolchain_current") return await localToolchainCurrent(args);
|
|
2928
|
-
const authStatus = await pluginAuthStatus();
|
|
2929
|
-
if (!authStatus.ok) return authStatus;
|
|
2930
|
-
if (name === "dbt_current_board_status" || name === "dbtstatus") return await currentBoardStatus(args, authStatus);
|
|
2931
|
-
if (name === "dbt_initial_image_flash") return await initialImageFlash(args);
|
|
2932
|
-
if (name === "dbt_rp2350_monitor_status") return await rp2350MonitorStatus(args);
|
|
2933
|
-
if (name === "dbt_rp2350_capabilities") return await rp2350Capabilities(args);
|
|
2934
|
-
if (name === "dbt_rp2350_operation") return await rp2350Operation(args);
|
|
2935
|
-
if (name === "dbt_rp2350_gpio_read") return await rp2350GpioRead(args);
|
|
2936
|
-
if (name === "dbt_rp2350_gpio_write") return await rp2350GpioWrite(args);
|
|
2937
|
-
if (name === "dbt_rp2350_uart_write") return await rp2350UartWrite(args);
|
|
2938
|
-
if (name === "dbt_rp2350_i2c_transfer") return await rp2350I2cTransfer(args);
|
|
2939
|
-
if (name === "dbt_rp2350_logic_capture") return await rp2350LogicCapture(args);
|
|
2940
|
-
if (name === "dbt_rp2350_logic_decode") return await rp2350LogicDecode(args);
|
|
2941
|
-
if (name === "dbt_rp2350_spi_transfer") return await rp2350SpiTransfer(args);
|
|
2942
|
-
if (name === "dbt_rp2350_wifi_manage") return await rp2350WifiManage(args);
|
|
2943
|
-
if (name === "dbt_rp2350_probe_debug") return await rp2350ProbeDebug(args);
|
|
2944
|
-
if (name === "dbt_rp2350_monitor_command") return await rp2350MonitorCommand(args);
|
|
825
|
+
if (name === "dbt_current_board_status" || name === "dbtstatus") return await currentBoardStatus(args);
|
|
2945
826
|
if (name === "dbt_cloud_status") return await cloudStatus(args);
|
|
2946
|
-
if (name === "
|
|
2947
|
-
if (
|
|
827
|
+
if (name === "dbt_workspace_provision") return await workspaceProvision(args);
|
|
828
|
+
if (name === "dbt_application_generate") return await applicationGenerate(args);
|
|
829
|
+
if (name === "dbt_source_list") return await sourceList(args);
|
|
830
|
+
if (name === "dbt_source_get") return await sourceGet(args);
|
|
831
|
+
if (name === "dbt_source_search") return await sourceSearch(args);
|
|
832
|
+
if (name === "dbt_source_put") return await sourcePut(args);
|
|
833
|
+
if (name === "dbt_source_patch") return await sourcePatch(args);
|
|
834
|
+
if (name === "dbt_application_compile") return await applicationCompile(args);
|
|
2948
835
|
if (name === "dbt_task_status") return await taskStatus(args);
|
|
2949
836
|
if (name === "dbt_task_log_download") return await taskLogDownload(args);
|
|
837
|
+
if (name === "dbt_image_generate") return await imageGenerate(args);
|
|
2950
838
|
if (name === "dbt_artifact_download") return await artifactDownload(args);
|
|
2951
839
|
if (name === "dbt_board_debug") return await boardDebug(args);
|
|
2952
|
-
if (name === "dbt_boot_logo_update") return await bootLogoUpdate(args);
|
|
2953
840
|
if (name === "dbt_update_logo") return await updateLogo(args);
|
|
2954
841
|
if (name === "dbt_compose_boot_logo") return await composeBootLogo(args);
|
|
842
|
+
if (name === "dbt_local_toolchain_latest") return await localToolchainLatest(args);
|
|
843
|
+
if (name === "dbt_local_toolchain_current") return await localToolchainCurrent(args);
|
|
2955
844
|
if (name === "dbt_local_toolchain_install") return await localToolchainInstall(args);
|
|
2956
|
-
if (name === "dbt_local_toolchain_uninstall") return await localToolchainUninstall(args);
|
|
2957
845
|
if (name === "dbt_local_toolchain_validate") return await localToolchainValidate(args);
|
|
2958
|
-
if (name === "dbt_taishanpi_initial_image_flash") return await taishanpiInitialImageFlash(args);
|
|
2959
|
-
if (name === "dbt_rp2350_monitor_firmware_flash") return await rp2350MonitorFirmwareFlash(args);
|
|
2960
|
-
if (name === "dbt_rp2350_initial_firmware_flash") return await rp2350InitialFirmwareFlash(args);
|
|
2961
|
-
if (name === "dbt_plugin_update_check") return await pluginUpdateCheck(args);
|
|
2962
|
-
if (name === "dbt_plugin_update") return await pluginUpdate(args);
|
|
2963
846
|
if (name === "dbt_local_compile") return await localCompile(args);
|
|
2964
847
|
if (name === "dbt_local_qt_smoke") return await localQtSmoke(args);
|
|
2965
|
-
if (name === "
|
|
848
|
+
if (name === "dbt_workspace_release") return await workspaceRelease(args);
|
|
2966
849
|
if (name === "dbttool") {
|
|
2967
850
|
const action = asString(args.action).toLowerCase().replaceAll("_", "-");
|
|
2968
851
|
let payload = {};
|
|
2969
852
|
if (asString(args.arguments_json)) payload = JSON.parse(args.arguments_json);
|
|
2970
853
|
if (asString(args.request) && !payload.prompt) payload.prompt = asString(args.request);
|
|
2971
|
-
if (["supported-boards", "boards", "board-list", "registry-list"].includes(action)) return await supportedBoards(payload);
|
|
2972
854
|
if (["status", "cloud-status", "methods", "knowledge", "capabilities"].includes(action)) return await cloudStatus(payload);
|
|
2973
|
-
if (["board-status", "
|
|
2974
|
-
if (["
|
|
2975
|
-
if (["rp2350-operation", "rpmon-operation"].includes(action)) return await rp2350Operation(payload);
|
|
2976
|
-
if (["rp2350-monitor-status", "rp2350-status", "rpmon-status"].includes(action)) return await rp2350MonitorStatus(payload);
|
|
2977
|
-
if (["rp2350-gpio-read", "rpmon-gpio-read", "gpio-read"].includes(action)) return await rp2350GpioRead(payload);
|
|
2978
|
-
if (["rp2350-gpio-write", "rpmon-gpio-write", "gpio-write"].includes(action)) return await rp2350GpioWrite(payload);
|
|
2979
|
-
if (["rp2350-uart-write", "rpmon-uart-write", "uart-write"].includes(action)) return await rp2350UartWrite(payload);
|
|
2980
|
-
if (["rp2350-i2c-transfer", "rpmon-i2c-transfer", "i2c-transfer"].includes(action)) return await rp2350I2cTransfer(payload);
|
|
2981
|
-
if (["rp2350-logic-capture", "rpmon-logic-capture", "logic-capture"].includes(action)) return await rp2350LogicCapture(payload);
|
|
2982
|
-
if (["rp2350-logic-decode", "rpmon-logic-decode", "logic-decode"].includes(action)) return await rp2350LogicDecode(payload);
|
|
2983
|
-
if (["rp2350-spi-transfer", "rpmon-spi-transfer", "spi-transfer", "spi-test"].includes(action)) return await rp2350SpiTransfer(payload);
|
|
2984
|
-
if (["rp2350-wifi", "rpmon-wifi", "wifi-manage"].includes(action)) return await rp2350WifiManage(payload);
|
|
2985
|
-
if (["rp2350-probe-debug", "rpmon-probe-debug", "probe-debug"].includes(action)) return await rp2350ProbeDebug(payload);
|
|
2986
|
-
if (["rp2350-monitor-command", "rpmon-command"].includes(action)) return await rp2350MonitorCommand(payload);
|
|
2987
|
-
if (["initial-image-flash", "flash-initial-image", "initial-flash", "flash-initial", "init-flash"].includes(action)) return await initialImageFlash(payload);
|
|
2988
|
-
if (["debug", "agent-run", "deploy"].includes(action)) return await boardDebug(payload);
|
|
2989
|
-
if (["boot-logo-update", "update-boot-logo", "update-logo", "boot-logo", "logo", "boot-logo-modify"].includes(action)) return await bootLogoUpdate(payload);
|
|
855
|
+
if (["board-status", "debug", "agent-run", "deploy"].includes(action)) return await boardDebug(payload);
|
|
856
|
+
if (["update-logo", "boot-logo", "logo", "boot-logo-modify"].includes(action)) return await updateLogo(payload);
|
|
2990
857
|
if (["compose-boot-logo", "boot-logo-compose", "compose-logo"].includes(action)) return await composeBootLogo(payload);
|
|
2991
|
-
if (["local-toolchain-list", "toolchain-list", "list-toolchains", "board-environments", "local-environments"].includes(action)) return await localToolchainList(payload);
|
|
2992
|
-
if (["local-toolchain-installed", "toolchain-installed", "installed-toolchains", "installed-board-environments"].includes(action)) return await localToolchainInstalled(payload);
|
|
2993
858
|
if (["local-toolchain-latest", "toolchain-latest", "latest-toolchain"].includes(action)) return await localToolchainLatest(payload);
|
|
2994
859
|
if (["local-toolchain-current", "toolchain-current", "current-toolchain"].includes(action)) return await localToolchainCurrent(payload);
|
|
2995
860
|
if (["local-toolchain-install", "toolchain-install", "install-toolchain"].includes(action)) return await localToolchainInstall(payload);
|
|
2996
|
-
if (["local-toolchain-uninstall", "toolchain-uninstall", "uninstall-toolchain", "remove-toolchain"].includes(action)) return await localToolchainUninstall(payload);
|
|
2997
861
|
if (["local-toolchain", "local-toolchain-validate", "toolchain-validate"].includes(action)) return await localToolchainValidate(payload);
|
|
2998
|
-
if (["taishanpi-initial-image-flash", "taishanpi-initial-flash", "taishanpi-flash-initial", "rk3566-initial-flash"].includes(action)) return await taishanpiInitialImageFlash(payload);
|
|
2999
|
-
if (["rp2350-monitor-firmware-flash", "rp2350-firmware-flash", "pico-monitor-flash"].includes(action)) return await rp2350MonitorFirmwareFlash(payload);
|
|
3000
|
-
if (["rp2350-initial-firmware-flash", "rp2350-initial-flash", "pico-initial-flash", "pico-initial-firmware-flash"].includes(action)) return await rp2350InitialFirmwareFlash(payload);
|
|
3001
|
-
if (["plugin-update-check", "check-plugin-update", "update-check"].includes(action)) return await pluginUpdateCheck(payload);
|
|
3002
|
-
if (["plugin-update", "update-plugin", "upgrade-plugin"].includes(action)) return await pluginUpdate(payload);
|
|
3003
862
|
if (["local-compile", "compile-local"].includes(action)) return await localCompile(payload);
|
|
3004
863
|
if (["local-qt-smoke", "qt-smoke", "local-qt-build"].includes(action)) return await localQtSmoke(payload);
|
|
3005
|
-
if (["
|
|
3006
|
-
if (
|
|
864
|
+
if (["provision", "workspace-provision"].includes(action)) return await workspaceProvision(payload);
|
|
865
|
+
if (["source-list", "list-source"].includes(action)) return await sourceList(payload);
|
|
866
|
+
if (["source-get", "source-read", "read-source"].includes(action)) return await sourceGet(payload);
|
|
867
|
+
if (["source-search", "search-source"].includes(action)) return await sourceSearch(payload);
|
|
868
|
+
if (["source-put", "source-write", "write-source"].includes(action)) return await sourcePut(payload);
|
|
869
|
+
if (["source-patch", "patch-source"].includes(action)) return await sourcePatch(payload);
|
|
870
|
+
if (["app", "application", "application-generate", "build-run"].includes(action)) return await applicationGenerate(payload);
|
|
871
|
+
if (["compile", "application-compile"].includes(action)) return await applicationCompile(payload);
|
|
3007
872
|
if (["task-status", "status-task"].includes(action)) return await taskStatus(payload);
|
|
3008
873
|
if (["task-log", "task-log-download", "log-download"].includes(action)) return await taskLogDownload(payload);
|
|
874
|
+
if (["image", "image-generate"].includes(action)) return await imageGenerate(payload);
|
|
3009
875
|
if (["download", "artifact-download"].includes(action)) return await artifactDownload(payload);
|
|
876
|
+
if (["release", "workspace-release"].includes(action)) return await workspaceRelease(payload);
|
|
3010
877
|
return { ok: false, error_code: "unsupported_dbt_action" };
|
|
3011
878
|
}
|
|
3012
879
|
throw new Error(`Unknown tool: ${name}`);
|
|
@@ -3036,7 +903,7 @@ async function handleRpc(message) {
|
|
|
3036
903
|
result: {
|
|
3037
904
|
protocolVersion: "2024-11-05",
|
|
3038
905
|
capabilities: { tools: {} },
|
|
3039
|
-
serverInfo:
|
|
906
|
+
serverInfo: { name: "embed-labs", version: "0.2.2" }
|
|
3040
907
|
}
|
|
3041
908
|
});
|
|
3042
909
|
return;
|