kitowall 2.3.0 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +23 -2
- package/dist/core/workshop.js +247 -9
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -131,8 +131,12 @@ Commands:
|
|
|
131
131
|
we library List downloaded workshop items
|
|
132
132
|
we scan-steam Detect Steam workshop folders and list downloaded ids
|
|
133
133
|
we sync-steam Sync local Steam Workshop 431960 items into Kitsune downloads
|
|
134
|
-
we app-status Detect if Wallpaper Engine
|
|
134
|
+
we app-status Detect if Wallpaper Engine and scene engine are installed
|
|
135
135
|
we active Show current livewallpaper authority/lock state
|
|
136
|
+
we apply <id> --monitor <name> [--backend auto|mpvpaper|linux-wallpaperengine]
|
|
137
|
+
Apply live wallpaper on one monitor (auto resolves by wallpaper type)
|
|
138
|
+
we apply --map DP-1:<id1>,HDMI-A-1:<id2> [--backend auto|mpvpaper|linux-wallpaperengine]
|
|
139
|
+
Apply wallpapers in batch by monitor map
|
|
136
140
|
we stop [--monitor <name> | --all] Stop livewallpaper instances and restore previous services
|
|
137
141
|
we coexist enter|exit|status Temporarily stop/restore wallpaper rotation services
|
|
138
142
|
check [--namespace <ns>] [--json] Quick system check (no changes)
|
|
@@ -425,6 +429,23 @@ async function main() {
|
|
|
425
429
|
console.log(JSON.stringify(out, null, 2));
|
|
426
430
|
return;
|
|
427
431
|
}
|
|
432
|
+
if (action === 'apply') {
|
|
433
|
+
const mapValue = cleanOpt(getOptionValue(args, '--map'));
|
|
434
|
+
const backend = cleanOpt(getOptionValue(args, '--backend')) ?? 'auto';
|
|
435
|
+
if (mapValue) {
|
|
436
|
+
const out = await (0, workshop_1.workshopApplyMap)({ map: mapValue, backend });
|
|
437
|
+
console.log(JSON.stringify(out, null, 2));
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
const id = cleanOpt(args[2] ?? null);
|
|
441
|
+
const monitor = cleanOpt(getOptionValue(args, '--monitor'));
|
|
442
|
+
if (!id || !monitor) {
|
|
443
|
+
throw new Error('Usage: we apply <id> --monitor <name> [--backend auto|mpvpaper|linux-wallpaperengine] OR we apply --map DP-1:<id1>,HDMI-A-1:<id2>');
|
|
444
|
+
}
|
|
445
|
+
const out = await (0, workshop_1.workshopApply)({ id, monitor, backend });
|
|
446
|
+
console.log(JSON.stringify(out, null, 2));
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
428
449
|
if (action === 'stop') {
|
|
429
450
|
const monitor = cleanOpt(getOptionValue(args, '--monitor'));
|
|
430
451
|
const out = await (0, workshop_1.workshopStop)({
|
|
@@ -453,7 +474,7 @@ async function main() {
|
|
|
453
474
|
}
|
|
454
475
|
throw new Error('Usage: we coexist <enter|exit|status>');
|
|
455
476
|
}
|
|
456
|
-
throw new Error('Usage: we <config|search|details|download|job|jobs|library|scan-steam|sync-steam|app-status|active|stop|run-job|coexist> ...');
|
|
477
|
+
throw new Error('Usage: we <config|search|details|download|job|jobs|library|scan-steam|sync-steam|app-status|active|apply|stop|run-job|coexist> ...');
|
|
457
478
|
}
|
|
458
479
|
// Regular commands (need config/state)
|
|
459
480
|
const config = (0, config_1.loadConfig)();
|
package/dist/core/workshop.js
CHANGED
|
@@ -7,6 +7,7 @@ exports.setWorkshopApiKey = setWorkshopApiKey;
|
|
|
7
7
|
exports.workshopGetSteamRoots = workshopGetSteamRoots;
|
|
8
8
|
exports.workshopSetSteamRoots = workshopSetSteamRoots;
|
|
9
9
|
exports.workshopWallpaperEngineStatus = workshopWallpaperEngineStatus;
|
|
10
|
+
exports.workshopSceneEngineStatus = workshopSceneEngineStatus;
|
|
10
11
|
exports.workshopScanSteamDownloads = workshopScanSteamDownloads;
|
|
11
12
|
exports.workshopSyncSteamDownloads = workshopSyncSteamDownloads;
|
|
12
13
|
exports.workshopSearch = workshopSearch;
|
|
@@ -22,6 +23,8 @@ exports.workshopRunJob = workshopRunJob;
|
|
|
22
23
|
exports.workshopGetJob = workshopGetJob;
|
|
23
24
|
exports.workshopListJobs = workshopListJobs;
|
|
24
25
|
exports.workshopLibrary = workshopLibrary;
|
|
26
|
+
exports.workshopApply = workshopApply;
|
|
27
|
+
exports.workshopApplyMap = workshopApplyMap;
|
|
25
28
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
26
29
|
const node_os_1 = __importDefault(require("node:os"));
|
|
27
30
|
const node_path_1 = __importDefault(require("node:path"));
|
|
@@ -43,6 +46,32 @@ function clean(input) {
|
|
|
43
46
|
const v = input.trim();
|
|
44
47
|
return v.length > 0 ? v : undefined;
|
|
45
48
|
}
|
|
49
|
+
function isExecutable(filePath) {
|
|
50
|
+
try {
|
|
51
|
+
node_fs_1.default.accessSync(filePath, node_fs_1.default.constants.X_OK);
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function resolveOnPath(binary) {
|
|
59
|
+
const bin = clean(binary);
|
|
60
|
+
if (!bin)
|
|
61
|
+
return undefined;
|
|
62
|
+
if (bin.includes('/')) {
|
|
63
|
+
const resolved = node_path_1.default.resolve(bin);
|
|
64
|
+
return node_fs_1.default.existsSync(resolved) && isExecutable(resolved) ? resolved : undefined;
|
|
65
|
+
}
|
|
66
|
+
const pathEnv = clean(process.env.PATH) ?? '';
|
|
67
|
+
const parts = pathEnv.split(':').filter(Boolean);
|
|
68
|
+
for (const p of parts) {
|
|
69
|
+
const candidate = node_path_1.default.join(p, bin);
|
|
70
|
+
if (node_fs_1.default.existsSync(candidate) && isExecutable(candidate))
|
|
71
|
+
return candidate;
|
|
72
|
+
}
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
46
75
|
function getWePaths() {
|
|
47
76
|
const root = node_path_1.default.join(node_os_1.default.homedir(), '.local', 'share', 'kitsune', 'we');
|
|
48
77
|
return {
|
|
@@ -375,19 +404,46 @@ function detectAudioReactiveFromText(text) {
|
|
|
375
404
|
function readProjectInfo(dir) {
|
|
376
405
|
const projectPath = node_path_1.default.join(dir, 'project.json');
|
|
377
406
|
if (!node_fs_1.default.existsSync(projectPath)) {
|
|
378
|
-
return { type: 'unknown', audioReactive: false };
|
|
407
|
+
return { type: 'unknown', audioReactive: false, entry: undefined };
|
|
379
408
|
}
|
|
380
409
|
try {
|
|
381
410
|
const raw = node_fs_1.default.readFileSync(projectPath, 'utf8');
|
|
382
411
|
const json = JSON.parse(raw);
|
|
383
412
|
const type = normalizeWallpaperType(json.type);
|
|
384
413
|
const title = clean(json.title) ?? '';
|
|
414
|
+
const fileEntry = clean(json.file);
|
|
415
|
+
const entry = fileEntry ? node_path_1.default.join(dir, fileEntry) : undefined;
|
|
385
416
|
const audioReactive = detectAudioReactiveFromText(`${title}\n${raw}`);
|
|
386
|
-
return { type, audioReactive };
|
|
417
|
+
return { type, audioReactive, entry };
|
|
387
418
|
}
|
|
388
419
|
catch {
|
|
389
|
-
return { type: 'unknown', audioReactive: false };
|
|
420
|
+
return { type: 'unknown', audioReactive: false, entry: undefined };
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
function inferVideoEntry(dir) {
|
|
424
|
+
if (!node_fs_1.default.existsSync(dir))
|
|
425
|
+
return undefined;
|
|
426
|
+
const files = node_fs_1.default.readdirSync(dir, { withFileTypes: true });
|
|
427
|
+
const preferred = ['.mp4', '.webm', '.gif', '.mkv', '.avi', '.mov'];
|
|
428
|
+
for (const ext of preferred) {
|
|
429
|
+
const match = files.find((entry) => entry.isFile() && entry.name.toLowerCase().endsWith(ext));
|
|
430
|
+
if (match)
|
|
431
|
+
return node_path_1.default.join(dir, match.name);
|
|
432
|
+
}
|
|
433
|
+
return undefined;
|
|
434
|
+
}
|
|
435
|
+
function detectTypeFromEntry(entry) {
|
|
436
|
+
const e = clean(entry)?.toLowerCase();
|
|
437
|
+
if (!e)
|
|
438
|
+
return 'unknown';
|
|
439
|
+
if (e.endsWith('.mp4') || e.endsWith('.webm') || e.endsWith('.gif') || e.endsWith('.mkv') || e.endsWith('.avi') || e.endsWith('.mov')) {
|
|
440
|
+
return 'video';
|
|
390
441
|
}
|
|
442
|
+
if (e.endsWith('index.html') || e.endsWith('.html'))
|
|
443
|
+
return 'web';
|
|
444
|
+
if (e.endsWith('scene.json') || e.endsWith('gifscene.json'))
|
|
445
|
+
return 'scene';
|
|
446
|
+
return 'unknown';
|
|
391
447
|
}
|
|
392
448
|
function readLibraryFoldersVdf(vdfPath) {
|
|
393
449
|
if (!node_fs_1.default.existsSync(vdfPath))
|
|
@@ -503,13 +559,37 @@ function workshopWallpaperEngineStatus() {
|
|
|
503
559
|
if (node_fs_1.default.existsSync(manifest))
|
|
504
560
|
manifests.push(manifest);
|
|
505
561
|
}
|
|
562
|
+
const engine = workshopSceneEngineStatus();
|
|
506
563
|
return {
|
|
507
564
|
ok: true,
|
|
508
565
|
installed: manifests.length > 0,
|
|
509
566
|
manifests,
|
|
510
|
-
steamapps
|
|
567
|
+
steamapps,
|
|
568
|
+
engine
|
|
511
569
|
};
|
|
512
570
|
}
|
|
571
|
+
function workshopSceneEngineStatus() {
|
|
572
|
+
const configured = clean(process.env.KITOWALL_SCENE_ENGINE_CMD);
|
|
573
|
+
const candidates = configured
|
|
574
|
+
? [configured]
|
|
575
|
+
: ['linux-wallpaperengine', 'linux-wallpaperengine-cli', 'wallpaper-engine'];
|
|
576
|
+
for (const cmd of candidates) {
|
|
577
|
+
const resolved = resolveOnPath(cmd);
|
|
578
|
+
if (!resolved)
|
|
579
|
+
continue;
|
|
580
|
+
const verOut = (0, node_child_process_1.spawnSync)(resolved, ['--version'], { encoding: 'utf8', timeout: 2500 });
|
|
581
|
+
const stdout = clean(verOut.stdout) ?? '';
|
|
582
|
+
const stderr = clean(verOut.stderr) ?? '';
|
|
583
|
+
const version = (stdout.split('\n')[0] || stderr.split('\n')[0] || '').trim();
|
|
584
|
+
return {
|
|
585
|
+
installed: true,
|
|
586
|
+
path: resolved,
|
|
587
|
+
version: version || undefined,
|
|
588
|
+
command: cmd
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
return { installed: false, command: configured ?? 'linux-wallpaperengine' };
|
|
592
|
+
}
|
|
513
593
|
function findPreviewCandidate(dir) {
|
|
514
594
|
if (!node_fs_1.default.existsSync(dir))
|
|
515
595
|
return undefined;
|
|
@@ -602,17 +682,19 @@ function workshopSyncSteamDownloads() {
|
|
|
602
682
|
author_name: 'Steam Workshop',
|
|
603
683
|
time_updated: Math.floor(Date.now() / 1000),
|
|
604
684
|
wallpaper_type: info.type,
|
|
605
|
-
audio_reactive: info.audioReactive
|
|
685
|
+
audio_reactive: info.audioReactive,
|
|
686
|
+
entry: info.entry
|
|
606
687
|
});
|
|
607
688
|
}
|
|
608
689
|
else {
|
|
609
690
|
try {
|
|
610
691
|
const current = JSON.parse(node_fs_1.default.readFileSync(metadataFile, 'utf8'));
|
|
611
|
-
if (!current.wallpaper_type || current.wallpaper_type === 'unknown' || current.audio_reactive === undefined) {
|
|
692
|
+
if (!current.wallpaper_type || current.wallpaper_type === 'unknown' || current.audio_reactive === undefined || !current.entry) {
|
|
612
693
|
writeMetaFile({
|
|
613
694
|
...current,
|
|
614
695
|
wallpaper_type: current.wallpaper_type ?? info.type,
|
|
615
|
-
audio_reactive: current.audio_reactive ?? info.audioReactive
|
|
696
|
+
audio_reactive: current.audio_reactive ?? info.audioReactive,
|
|
697
|
+
entry: current.entry ?? info.entry
|
|
616
698
|
});
|
|
617
699
|
}
|
|
618
700
|
}
|
|
@@ -1026,9 +1108,10 @@ async function workshopCoexistenceStatus() {
|
|
|
1026
1108
|
return { ok: true, snapshot, current };
|
|
1027
1109
|
}
|
|
1028
1110
|
async function workshopStop(options) {
|
|
1029
|
-
|
|
1111
|
+
let state = readActiveState();
|
|
1030
1112
|
const hadActive = !!state;
|
|
1031
1113
|
const stopped = [];
|
|
1114
|
+
let shouldRestore = false;
|
|
1032
1115
|
if (state?.instances) {
|
|
1033
1116
|
if (options?.monitor) {
|
|
1034
1117
|
const inst = state.instances[options.monitor];
|
|
@@ -1040,6 +1123,7 @@ async function workshopStop(options) {
|
|
|
1040
1123
|
}
|
|
1041
1124
|
else {
|
|
1042
1125
|
clearActiveState();
|
|
1126
|
+
shouldRestore = true;
|
|
1043
1127
|
}
|
|
1044
1128
|
}
|
|
1045
1129
|
else {
|
|
@@ -1048,12 +1132,14 @@ async function workshopStop(options) {
|
|
|
1048
1132
|
stopped.push(monitor);
|
|
1049
1133
|
}
|
|
1050
1134
|
clearActiveState();
|
|
1135
|
+
shouldRestore = true;
|
|
1051
1136
|
}
|
|
1052
1137
|
}
|
|
1053
1138
|
else if (options?.all) {
|
|
1054
1139
|
clearActiveState();
|
|
1140
|
+
shouldRestore = true;
|
|
1055
1141
|
}
|
|
1056
|
-
const coexist = await workshopCoexistenceExit();
|
|
1142
|
+
const coexist = shouldRestore ? await workshopCoexistenceExit() : { ok: true, restored: [] };
|
|
1057
1143
|
return {
|
|
1058
1144
|
ok: true,
|
|
1059
1145
|
stopped_instances: stopped,
|
|
@@ -1165,6 +1251,9 @@ function workshopLibrary() {
|
|
|
1165
1251
|
if (meta.audio_reactive === undefined) {
|
|
1166
1252
|
meta.audio_reactive = info.audioReactive;
|
|
1167
1253
|
}
|
|
1254
|
+
if (!meta.entry) {
|
|
1255
|
+
meta.entry = info.entry;
|
|
1256
|
+
}
|
|
1168
1257
|
}
|
|
1169
1258
|
items.push({
|
|
1170
1259
|
id,
|
|
@@ -1176,3 +1265,152 @@ function workshopLibrary() {
|
|
|
1176
1265
|
items.sort((a, b) => a.id.localeCompare(b.id));
|
|
1177
1266
|
return { root: paths.downloads, items };
|
|
1178
1267
|
}
|
|
1268
|
+
function spawnMpvpaper(monitor, entry) {
|
|
1269
|
+
return new Promise((resolve, reject) => {
|
|
1270
|
+
const child = (0, node_child_process_1.spawn)('mpvpaper', ['-o', 'no-audio --loop-file=inf', monitor, entry], {
|
|
1271
|
+
detached: true,
|
|
1272
|
+
stdio: 'ignore',
|
|
1273
|
+
env: process.env
|
|
1274
|
+
});
|
|
1275
|
+
let settled = false;
|
|
1276
|
+
const done = (fn) => {
|
|
1277
|
+
if (settled)
|
|
1278
|
+
return;
|
|
1279
|
+
settled = true;
|
|
1280
|
+
fn();
|
|
1281
|
+
};
|
|
1282
|
+
child.once('error', (err) => done(() => reject(err)));
|
|
1283
|
+
setTimeout(() => {
|
|
1284
|
+
done(() => {
|
|
1285
|
+
child.unref();
|
|
1286
|
+
resolve(child.pid ?? 0);
|
|
1287
|
+
});
|
|
1288
|
+
}, 180);
|
|
1289
|
+
});
|
|
1290
|
+
}
|
|
1291
|
+
function spawnSceneEngine(monitor, wallpaperDir) {
|
|
1292
|
+
const engine = workshopSceneEngineStatus();
|
|
1293
|
+
if (!engine.installed || !engine.path) {
|
|
1294
|
+
throw new Error('Scene engine not installed. Install linux-wallpaperengine first.');
|
|
1295
|
+
}
|
|
1296
|
+
const enginePath = engine.path;
|
|
1297
|
+
return new Promise((resolve, reject) => {
|
|
1298
|
+
const child = (0, node_child_process_1.spawn)(enginePath, ['--screen-root', monitor, '--bg', wallpaperDir], {
|
|
1299
|
+
detached: true,
|
|
1300
|
+
stdio: 'ignore',
|
|
1301
|
+
env: process.env
|
|
1302
|
+
});
|
|
1303
|
+
let settled = false;
|
|
1304
|
+
const done = (fn) => {
|
|
1305
|
+
if (settled)
|
|
1306
|
+
return;
|
|
1307
|
+
settled = true;
|
|
1308
|
+
fn();
|
|
1309
|
+
};
|
|
1310
|
+
child.once('error', (err) => done(() => reject(err)));
|
|
1311
|
+
setTimeout(() => {
|
|
1312
|
+
done(() => {
|
|
1313
|
+
child.unref();
|
|
1314
|
+
resolve(child.pid ?? 0);
|
|
1315
|
+
});
|
|
1316
|
+
}, 220);
|
|
1317
|
+
});
|
|
1318
|
+
}
|
|
1319
|
+
async function workshopApply(input) {
|
|
1320
|
+
const id = clean(input.id);
|
|
1321
|
+
const monitor = clean(input.monitor);
|
|
1322
|
+
const requestedBackend = clean(input.backend) ?? 'auto';
|
|
1323
|
+
if (!id)
|
|
1324
|
+
throw new Error('id is required');
|
|
1325
|
+
if (!monitor)
|
|
1326
|
+
throw new Error('monitor is required');
|
|
1327
|
+
const paths = getWePaths();
|
|
1328
|
+
ensureWePaths(paths);
|
|
1329
|
+
const dir = node_path_1.default.join(paths.downloads, id);
|
|
1330
|
+
if (!node_fs_1.default.existsSync(dir)) {
|
|
1331
|
+
throw new Error(`Wallpaper not found in downloads: ${id}`);
|
|
1332
|
+
}
|
|
1333
|
+
const project = readProjectInfo(dir);
|
|
1334
|
+
const inferredEntry = project.entry && node_fs_1.default.existsSync(project.entry) ? project.entry : inferVideoEntry(dir);
|
|
1335
|
+
const type = project.type !== 'unknown'
|
|
1336
|
+
? project.type
|
|
1337
|
+
: detectTypeFromEntry(inferredEntry ?? node_path_1.default.join(dir, 'scene.json'));
|
|
1338
|
+
if (type === 'web' || type === 'application' || type === 'unknown') {
|
|
1339
|
+
throw new Error(`Unsupported wallpaper type for apply: ${type}. Supported: video, scene.`);
|
|
1340
|
+
}
|
|
1341
|
+
let state = readActiveState();
|
|
1342
|
+
if (!state) {
|
|
1343
|
+
const coexist = await workshopCoexistenceEnter();
|
|
1344
|
+
state = {
|
|
1345
|
+
mode: 'livewallpaper',
|
|
1346
|
+
started_at: now(),
|
|
1347
|
+
snapshot_id: coexist.snapshot_id,
|
|
1348
|
+
instances: {}
|
|
1349
|
+
};
|
|
1350
|
+
}
|
|
1351
|
+
const current = state.instances[monitor];
|
|
1352
|
+
if (current?.pid) {
|
|
1353
|
+
killPid(current.pid);
|
|
1354
|
+
}
|
|
1355
|
+
let backend;
|
|
1356
|
+
let pid = 0;
|
|
1357
|
+
if (type === 'video') {
|
|
1358
|
+
if (!(requestedBackend === 'auto' || requestedBackend === 'mpvpaper')) {
|
|
1359
|
+
throw new Error(`Invalid backend for video wallpaper: ${requestedBackend}`);
|
|
1360
|
+
}
|
|
1361
|
+
if (!inferredEntry || !node_fs_1.default.existsSync(inferredEntry)) {
|
|
1362
|
+
throw new Error(`Video entry not found for wallpaper: ${id}`);
|
|
1363
|
+
}
|
|
1364
|
+
backend = 'mpvpaper';
|
|
1365
|
+
pid = await spawnMpvpaper(monitor, inferredEntry).catch((err) => {
|
|
1366
|
+
throw new Error(`Failed to launch mpvpaper: ${err instanceof Error ? err.message : String(err)}`);
|
|
1367
|
+
});
|
|
1368
|
+
}
|
|
1369
|
+
else if (type === 'scene') {
|
|
1370
|
+
if (!(requestedBackend === 'auto' || requestedBackend === 'linux-wallpaperengine')) {
|
|
1371
|
+
throw new Error(`Invalid backend for scene wallpaper: ${requestedBackend}`);
|
|
1372
|
+
}
|
|
1373
|
+
backend = 'linux-wallpaperengine';
|
|
1374
|
+
pid = await spawnSceneEngine(monitor, dir).catch((err) => {
|
|
1375
|
+
throw new Error(`Failed to launch scene engine: ${err instanceof Error ? err.message : String(err)}`);
|
|
1376
|
+
});
|
|
1377
|
+
}
|
|
1378
|
+
else {
|
|
1379
|
+
throw new Error(`Unsupported wallpaper type for apply: ${type}`);
|
|
1380
|
+
}
|
|
1381
|
+
if (!pid) {
|
|
1382
|
+
throw new Error(`${backend} started without pid`);
|
|
1383
|
+
}
|
|
1384
|
+
state.instances[monitor] = {
|
|
1385
|
+
id,
|
|
1386
|
+
pid,
|
|
1387
|
+
backend,
|
|
1388
|
+
type
|
|
1389
|
+
};
|
|
1390
|
+
writeActiveState(state);
|
|
1391
|
+
return { ok: true, applied: true, monitor, id, backend, pid, state };
|
|
1392
|
+
}
|
|
1393
|
+
async function workshopApplyMap(input) {
|
|
1394
|
+
const raw = clean(input.map);
|
|
1395
|
+
if (!raw)
|
|
1396
|
+
throw new Error('map is required');
|
|
1397
|
+
const entries = raw.split(',').map(v => v.trim()).filter(Boolean);
|
|
1398
|
+
if (entries.length === 0)
|
|
1399
|
+
throw new Error('map has no entries');
|
|
1400
|
+
const applied = [];
|
|
1401
|
+
for (const pair of entries) {
|
|
1402
|
+
const idx = pair.indexOf(':');
|
|
1403
|
+
if (idx <= 0 || idx === pair.length - 1) {
|
|
1404
|
+
throw new Error(`Invalid map entry: ${pair}. Expected <monitor>:<id>`);
|
|
1405
|
+
}
|
|
1406
|
+
const monitor = pair.slice(0, idx).trim();
|
|
1407
|
+
const id = pair.slice(idx + 1).trim();
|
|
1408
|
+
if (!monitor || !id) {
|
|
1409
|
+
throw new Error(`Invalid map entry: ${pair}. Expected <monitor>:<id>`);
|
|
1410
|
+
}
|
|
1411
|
+
const out = await workshopApply({ id, monitor, backend: input.backend });
|
|
1412
|
+
applied.push({ monitor: out.monitor, id: out.id, backend: out.backend, pid: out.pid });
|
|
1413
|
+
}
|
|
1414
|
+
const state = readActiveState() ?? undefined;
|
|
1415
|
+
return { ok: true, applied, state };
|
|
1416
|
+
}
|