mellos-mapping 0.14.0 → 0.16.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/README.md +5 -3
- package/README.zh-CN.md +4 -2
- package/dist/server.mjs +13 -11
- package/dist/watch.mjs +165 -58
- package/package.json +1 -1
- package/scripts/open-pane.mjs +41 -7
package/README.md
CHANGED
|
@@ -128,9 +128,11 @@ the script after updating the plugin.
|
|
|
128
128
|
To watch the live pane beside a Codex session on Windows, run
|
|
129
129
|
`node <plugin root>/scripts/open-pane.mjs <project dir>` — it splits the
|
|
130
130
|
terminal window hosting the session (or falls back to a dedicated
|
|
131
|
-
"mellos-mapping" window; `--window` picks that on purpose).
|
|
132
|
-
|
|
133
|
-
|
|
131
|
+
"mellos-mapping" window; `--window` picks that on purpose). Add
|
|
132
|
+
`--page <slug>` to open on a particular page — and with a pane already open,
|
|
133
|
+
rerunning with `--page` retargets it instead of opening another. Elsewhere
|
|
134
|
+
run `node <plugin root>/dist/watch.mjs` (same `--page` flag) from the
|
|
135
|
+
project directory in a second terminal (or any terminal split).
|
|
134
136
|
|
|
135
137
|
## Any MCP client
|
|
136
138
|
|
package/README.zh-CN.md
CHANGED
|
@@ -118,8 +118,10 @@ node ~/.codex/plugins/cache/mellos-mapping/mellos-mapping/<版本>/scripts/codex
|
|
|
118
118
|
要在 Codex 会话旁边看实况面板,Windows 上运行
|
|
119
119
|
`node <插件根>/scripts/open-pane.mjs <项目目录>`——它会在承载本会话的
|
|
120
120
|
终端窗口里分屏(识别不到就确定性地开到专属的 "mellos-mapping" 窗口;
|
|
121
|
-
`--window`
|
|
122
|
-
|
|
121
|
+
`--window` 则是主动选择专属窗口)。加 `--page <slug>` 指定打开哪一页;
|
|
122
|
+
面板已经开着时,带 `--page` 重跑一次不会再开新面板,而是让现有面板
|
|
123
|
+
切到那一页。其他环境在项目目录下的第二个终端(或任意分屏)运行
|
|
124
|
+
`node <插件根>/dist/watch.mjs`(同样支持 `--page`)。
|
|
123
125
|
|
|
124
126
|
## 任意 MCP 客户端
|
|
125
127
|
|
package/dist/server.mjs
CHANGED
|
@@ -21390,7 +21390,7 @@ function removeLayer(map, id) {
|
|
|
21390
21390
|
|
|
21391
21391
|
// src/render/render.ts
|
|
21392
21392
|
var ZOOM_MIN = -4;
|
|
21393
|
-
var ZOOM_MAX =
|
|
21393
|
+
var ZOOM_MAX = 2;
|
|
21394
21394
|
var ZOOM_DEFAULT = 0;
|
|
21395
21395
|
function clampZoom(n) {
|
|
21396
21396
|
return Math.max(ZOOM_MIN, Math.min(ZOOM_MAX, Math.round(n)));
|
|
@@ -21668,10 +21668,14 @@ function neutralSkin(unicode) {
|
|
|
21668
21668
|
var BOX_H = 3;
|
|
21669
21669
|
var BOX_GAP = 2;
|
|
21670
21670
|
var LEFT_MARGIN = 2;
|
|
21671
|
+
var DETAIL_BUDGET = { innerMin: 22, innerMax: 32, noteRows: 3 };
|
|
21672
|
+
var DETAIL_PLUS_BUDGET = { innerMin: 30, innerMax: 48, noteRows: 12 };
|
|
21671
21673
|
function zoomGeometry(zoom) {
|
|
21672
21674
|
switch (zoom) {
|
|
21675
|
+
case 2:
|
|
21676
|
+
return { mode: "detail", scale: 1, pad: 1, boxGap: BOX_GAP, breathe: 1, titleGap: 1, barGap: 1, bandCounts: false, detail: DETAIL_PLUS_BUDGET };
|
|
21673
21677
|
case 1:
|
|
21674
|
-
return { mode: "detail", scale: 1, pad: 1, boxGap: BOX_GAP, breathe: 1, titleGap: 1, barGap: 1, bandCounts: false };
|
|
21678
|
+
return { mode: "detail", scale: 1, pad: 1, boxGap: BOX_GAP, breathe: 1, titleGap: 1, barGap: 1, bandCounts: false, detail: DETAIL_BUDGET };
|
|
21675
21679
|
case 0:
|
|
21676
21680
|
return { mode: "boxes", scale: 1, pad: 1, boxGap: BOX_GAP, breathe: 1, titleGap: 1, barGap: 1, bandCounts: false };
|
|
21677
21681
|
case -1:
|
|
@@ -21684,9 +21688,6 @@ function zoomGeometry(zoom) {
|
|
|
21684
21688
|
return { mode: "constellation", scale: 0, pad: 0, boxGap: BOX_GAP, breathe: 0, titleGap: 0, barGap: 1, bandCounts: true };
|
|
21685
21689
|
}
|
|
21686
21690
|
}
|
|
21687
|
-
var DETAIL_INNER_MIN = 22;
|
|
21688
|
-
var DETAIL_INNER_MAX = 32;
|
|
21689
|
-
var DETAIL_NOTE_ROWS = 3;
|
|
21690
21691
|
var LABEL_BUDGET_MIN = 4;
|
|
21691
21692
|
function boxSpec(node, geo, unicode, neutral) {
|
|
21692
21693
|
const glyph = node.kind !== void 0 ? kindGlyph(node.kind, unicode) : void 0;
|
|
@@ -21696,14 +21697,15 @@ function boxSpec(node, geo, unicode, neutral) {
|
|
|
21696
21697
|
if (geo.mode === "constellation") {
|
|
21697
21698
|
return { w: 3, h: 1, label: "", pad: 0, borderless: true, extra: [] };
|
|
21698
21699
|
}
|
|
21699
|
-
if (geo.mode === "detail") {
|
|
21700
|
-
const
|
|
21700
|
+
if (geo.mode === "detail" && geo.detail !== void 0) {
|
|
21701
|
+
const budget2 = geo.detail;
|
|
21702
|
+
const innerW = Math.min(Math.max(displayWidth(text2) + badgeW + 4, budget2.innerMin), budget2.innerMax);
|
|
21701
21703
|
const extra = [];
|
|
21702
21704
|
if (node.evidence !== void 0) extra.push({ text: fitWidth(` ${node.evidence}`, innerW), style: "faint" });
|
|
21703
21705
|
if (node.detail !== void 0) {
|
|
21704
21706
|
const wrapped = wrapWidth(node.detail, innerW - 2);
|
|
21705
|
-
for (let i = 0; i < Math.min(wrapped.length,
|
|
21706
|
-
const cut = i ===
|
|
21707
|
+
for (let i = 0; i < Math.min(wrapped.length, budget2.noteRows); i++) {
|
|
21708
|
+
const cut = i === budget2.noteRows - 1 && wrapped.length > budget2.noteRows;
|
|
21707
21709
|
extra.push({ text: ` ${cut ? fitWidth(wrapped[i] + "\u2026", innerW - 2) : wrapped[i]}`, style: "none" });
|
|
21708
21710
|
}
|
|
21709
21711
|
}
|
|
@@ -22131,7 +22133,7 @@ function drawBox(canvas, box, opts, neutral, focused = false) {
|
|
|
22131
22133
|
}
|
|
22132
22134
|
|
|
22133
22135
|
// src/store/store.ts
|
|
22134
|
-
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
22136
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
22135
22137
|
import { basename, dirname, join } from "node:path";
|
|
22136
22138
|
var STATE_FILE_VERSION = 1;
|
|
22137
22139
|
var STATE_FILE_RELATIVE_PATH = join(".claude", "mellos-mapping.json");
|
|
@@ -22513,7 +22515,7 @@ function summarize(map) {
|
|
|
22513
22515
|
|
|
22514
22516
|
// src/server/server.ts
|
|
22515
22517
|
var SERVER_NAME = "mellos-mapping";
|
|
22516
|
-
var SERVER_VERSION = "0.
|
|
22518
|
+
var SERVER_VERSION = "0.16.0";
|
|
22517
22519
|
var ID = external_exports.string().regex(/^[a-z0-9][a-z0-9-]{0,63}$/, "lowercase letters, digits and dashes, 1-64 chars").describe("stable kebab-case identifier");
|
|
22518
22520
|
var PAGE = ID.optional().describe(
|
|
22519
22521
|
"page (parallel map) this call targets; omit for the default page. One effort = one page: start a NEW effort on its own page named after the effort, so concurrent sessions never write over each other and the pane can switch between pages."
|
package/dist/watch.mjs
CHANGED
|
@@ -201,13 +201,15 @@ function updateNode(map, input) {
|
|
|
201
201
|
|
|
202
202
|
// src/render/render.ts
|
|
203
203
|
var ZOOM_MIN = -4;
|
|
204
|
-
var ZOOM_MAX =
|
|
204
|
+
var ZOOM_MAX = 2;
|
|
205
205
|
var ZOOM_DEFAULT = 0;
|
|
206
206
|
function clampZoom(n) {
|
|
207
207
|
return Math.max(ZOOM_MIN, Math.min(ZOOM_MAX, Math.round(n)));
|
|
208
208
|
}
|
|
209
209
|
function zoomLabel(zoom) {
|
|
210
210
|
switch (zoom) {
|
|
211
|
+
case 2:
|
|
212
|
+
return "detail+";
|
|
211
213
|
case 1:
|
|
212
214
|
return "detail";
|
|
213
215
|
case 0:
|
|
@@ -495,10 +497,14 @@ function neutralSkin(unicode) {
|
|
|
495
497
|
var BOX_H = 3;
|
|
496
498
|
var BOX_GAP = 2;
|
|
497
499
|
var LEFT_MARGIN = 2;
|
|
500
|
+
var DETAIL_BUDGET = { innerMin: 22, innerMax: 32, noteRows: 3 };
|
|
501
|
+
var DETAIL_PLUS_BUDGET = { innerMin: 30, innerMax: 48, noteRows: 12 };
|
|
498
502
|
function zoomGeometry(zoom) {
|
|
499
503
|
switch (zoom) {
|
|
504
|
+
case 2:
|
|
505
|
+
return { mode: "detail", scale: 1, pad: 1, boxGap: BOX_GAP, breathe: 1, titleGap: 1, barGap: 1, bandCounts: false, detail: DETAIL_PLUS_BUDGET };
|
|
500
506
|
case 1:
|
|
501
|
-
return { mode: "detail", scale: 1, pad: 1, boxGap: BOX_GAP, breathe: 1, titleGap: 1, barGap: 1, bandCounts: false };
|
|
507
|
+
return { mode: "detail", scale: 1, pad: 1, boxGap: BOX_GAP, breathe: 1, titleGap: 1, barGap: 1, bandCounts: false, detail: DETAIL_BUDGET };
|
|
502
508
|
case 0:
|
|
503
509
|
return { mode: "boxes", scale: 1, pad: 1, boxGap: BOX_GAP, breathe: 1, titleGap: 1, barGap: 1, bandCounts: false };
|
|
504
510
|
case -1:
|
|
@@ -511,9 +517,6 @@ function zoomGeometry(zoom) {
|
|
|
511
517
|
return { mode: "constellation", scale: 0, pad: 0, boxGap: BOX_GAP, breathe: 0, titleGap: 0, barGap: 1, bandCounts: true };
|
|
512
518
|
}
|
|
513
519
|
}
|
|
514
|
-
var DETAIL_INNER_MIN = 22;
|
|
515
|
-
var DETAIL_INNER_MAX = 32;
|
|
516
|
-
var DETAIL_NOTE_ROWS = 3;
|
|
517
520
|
var LABEL_BUDGET_MIN = 4;
|
|
518
521
|
function boxSpec(node, geo, unicode, neutral) {
|
|
519
522
|
const glyph = node.kind !== void 0 ? kindGlyph(node.kind, unicode) : void 0;
|
|
@@ -523,14 +526,15 @@ function boxSpec(node, geo, unicode, neutral) {
|
|
|
523
526
|
if (geo.mode === "constellation") {
|
|
524
527
|
return { w: 3, h: 1, label: "", pad: 0, borderless: true, extra: [] };
|
|
525
528
|
}
|
|
526
|
-
if (geo.mode === "detail") {
|
|
527
|
-
const
|
|
529
|
+
if (geo.mode === "detail" && geo.detail !== void 0) {
|
|
530
|
+
const budget2 = geo.detail;
|
|
531
|
+
const innerW = Math.min(Math.max(displayWidth(text) + badgeW + 4, budget2.innerMin), budget2.innerMax);
|
|
528
532
|
const extra = [];
|
|
529
533
|
if (node.evidence !== void 0) extra.push({ text: fitWidth(` ${node.evidence}`, innerW), style: "faint" });
|
|
530
534
|
if (node.detail !== void 0) {
|
|
531
535
|
const wrapped = wrapWidth(node.detail, innerW - 2);
|
|
532
|
-
for (let i = 0; i < Math.min(wrapped.length,
|
|
533
|
-
const cut = i ===
|
|
536
|
+
for (let i = 0; i < Math.min(wrapped.length, budget2.noteRows); i++) {
|
|
537
|
+
const cut = i === budget2.noteRows - 1 && wrapped.length > budget2.noteRows;
|
|
534
538
|
extra.push({ text: ` ${cut ? fitWidth(wrapped[i] + "\u2026", innerW - 2) : wrapped[i]}`, style: "none" });
|
|
535
539
|
}
|
|
536
540
|
}
|
|
@@ -963,11 +967,14 @@ function drawBox(canvas, box, opts, neutral, focused = false) {
|
|
|
963
967
|
}
|
|
964
968
|
|
|
965
969
|
// src/store/store.ts
|
|
966
|
-
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
970
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
967
971
|
import { basename, dirname, join } from "node:path";
|
|
968
972
|
var STATE_FILE_VERSION = 1;
|
|
969
973
|
var STATE_FILE_RELATIVE_PATH = join(".claude", "mellos-mapping.json");
|
|
970
974
|
var PAGES_DIR_NAME = "mellos-mapping.pages";
|
|
975
|
+
function makePageId(raw) {
|
|
976
|
+
return ID_RULE.test(raw) ? ok(raw) : err({ kind: "invalid-id", raw, rule: ID_RULE_TEXT });
|
|
977
|
+
}
|
|
971
978
|
function pageFilePath(defaultFile, page) {
|
|
972
979
|
return page === void 0 ? defaultFile : join(dirname(defaultFile), PAGES_DIR_NAME, `${page}.json`);
|
|
973
980
|
}
|
|
@@ -989,6 +996,35 @@ function listPageFiles(defaultFile) {
|
|
|
989
996
|
}
|
|
990
997
|
return out;
|
|
991
998
|
}
|
|
999
|
+
var FOCUS_FILE_NAME = "mellos-mapping.focus";
|
|
1000
|
+
function focusFilePath(defaultFile) {
|
|
1001
|
+
return join(dirname(defaultFile), FOCUS_FILE_NAME);
|
|
1002
|
+
}
|
|
1003
|
+
function takeFocusRequest(defaultFile) {
|
|
1004
|
+
const path = focusFilePath(defaultFile);
|
|
1005
|
+
let raw;
|
|
1006
|
+
try {
|
|
1007
|
+
raw = readFileSync(path, "utf8");
|
|
1008
|
+
} catch {
|
|
1009
|
+
return void 0;
|
|
1010
|
+
}
|
|
1011
|
+
try {
|
|
1012
|
+
rmSync(path, { force: true });
|
|
1013
|
+
} catch {
|
|
1014
|
+
}
|
|
1015
|
+
let parsed;
|
|
1016
|
+
try {
|
|
1017
|
+
parsed = JSON.parse(raw);
|
|
1018
|
+
} catch {
|
|
1019
|
+
return void 0;
|
|
1020
|
+
}
|
|
1021
|
+
if (typeof parsed !== "object" || parsed === null) return void 0;
|
|
1022
|
+
const page = parsed.page;
|
|
1023
|
+
if (page === void 0 || page === null) return { page: void 0 };
|
|
1024
|
+
if (typeof page !== "string") return void 0;
|
|
1025
|
+
const id = makePageId(page);
|
|
1026
|
+
return id.ok ? { page: id.value } : void 0;
|
|
1027
|
+
}
|
|
992
1028
|
function describeStoreError(e) {
|
|
993
1029
|
switch (e.kind) {
|
|
994
1030
|
case "not-found":
|
|
@@ -1175,7 +1211,7 @@ var KEY_PAN = {
|
|
|
1175
1211
|
function mouseEvent(code, x, y, final) {
|
|
1176
1212
|
if (code & WHEEL) {
|
|
1177
1213
|
const down = (code & 1) !== 0;
|
|
1178
|
-
return code & SHIFT ? { kind: "pan", dx: 0, dy: (down ? 1 : -1) * WHEEL_V_STEP } : { kind: "zoom", delta: down ? -1 : 1 };
|
|
1214
|
+
return code & SHIFT ? { kind: "pan", dx: 0, dy: (down ? 1 : -1) * WHEEL_V_STEP } : { kind: "zoom", delta: down ? -1 : 1, at: { x, y } };
|
|
1179
1215
|
}
|
|
1180
1216
|
const buttons = code & BUTTON_BITS;
|
|
1181
1217
|
if (final === "m") return buttons === 0 ? { kind: "mouse-up", x, y } : void 0;
|
|
@@ -1237,11 +1273,17 @@ function parseArgs(argv, cwd) {
|
|
|
1237
1273
|
let unicode = true;
|
|
1238
1274
|
let color = true;
|
|
1239
1275
|
let mouse = true;
|
|
1276
|
+
let page;
|
|
1240
1277
|
for (let i = 0; i < argv.length; i++) {
|
|
1241
1278
|
switch (argv[i]) {
|
|
1242
1279
|
case "--file":
|
|
1243
1280
|
file = argv[++i] ?? file;
|
|
1244
1281
|
break;
|
|
1282
|
+
case "--page": {
|
|
1283
|
+
const parsed = makePageId(argv[++i] ?? "");
|
|
1284
|
+
if (parsed.ok) page = parsed.value;
|
|
1285
|
+
break;
|
|
1286
|
+
}
|
|
1245
1287
|
case "--interval":
|
|
1246
1288
|
intervalMs = Math.max(50, Number(argv[++i]) || intervalMs);
|
|
1247
1289
|
break;
|
|
@@ -1258,7 +1300,19 @@ function parseArgs(argv, cwd) {
|
|
|
1258
1300
|
break;
|
|
1259
1301
|
}
|
|
1260
1302
|
}
|
|
1261
|
-
return { file, intervalMs, unicode, color, mouse };
|
|
1303
|
+
return { file, intervalMs, unicode, color, mouse, page };
|
|
1304
|
+
}
|
|
1305
|
+
function mostRecentPageFile(files, mtimeOf) {
|
|
1306
|
+
let best;
|
|
1307
|
+
let bestMtime = -Infinity;
|
|
1308
|
+
for (const file of files) {
|
|
1309
|
+
const mtime = mtimeOf(file);
|
|
1310
|
+
if (mtime !== void 0 && mtime > bestMtime) {
|
|
1311
|
+
best = file;
|
|
1312
|
+
bestMtime = mtime;
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
return best ?? files[0];
|
|
1262
1316
|
}
|
|
1263
1317
|
var HIDE_CURSOR = "\x1B[?25l";
|
|
1264
1318
|
var SHOW_CURSOR = "\x1B[?25h";
|
|
@@ -1271,6 +1325,9 @@ var RESET = "\x1B[0m";
|
|
|
1271
1325
|
var PANEL_CONTENT_ROWS = 6;
|
|
1272
1326
|
var PANEL_ROWS_MIN = 2;
|
|
1273
1327
|
var MAP_ROWS_MIN = 4;
|
|
1328
|
+
function usableColumns(cols) {
|
|
1329
|
+
return Math.max(1, cols - 1);
|
|
1330
|
+
}
|
|
1274
1331
|
function clampPanelRows(wanted, totalRows, tabRows) {
|
|
1275
1332
|
const largest = totalRows - tabRows - MAP_ROWS_MIN - 2;
|
|
1276
1333
|
return Math.max(PANEL_ROWS_MIN, Math.min(wanted, largest));
|
|
@@ -1302,27 +1359,46 @@ function anchorOffsets(anchor, offset, before, after) {
|
|
|
1302
1359
|
y: before.h > 0 ? Math.round(offset.y * after.h / before.h) : 0
|
|
1303
1360
|
};
|
|
1304
1361
|
}
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
for (const [index, tab] of tabs.entries()) {
|
|
1309
|
-
const room = width - (col - 1);
|
|
1310
|
-
if (room <= 3) break;
|
|
1362
|
+
var TAB_INDICATOR_W = 3;
|
|
1363
|
+
function pageTabRow(tabs, width, unicode, scroll = 0) {
|
|
1364
|
+
const texts = tabs.map((tab) => {
|
|
1311
1365
|
const marker = tab.active ? unicode ? "\u25CF" : "*" : unicode ? "\u25CB" : "o";
|
|
1312
1366
|
const glyph = STATUS_GLYPH[tab.status][unicode ? 0 : 1];
|
|
1313
|
-
|
|
1367
|
+
return tab.neutral === true ? ` ${marker} ${tab.title} ` : ` ${marker} ${glyph} ${tab.title} `;
|
|
1368
|
+
});
|
|
1369
|
+
const sgrOf = (tab) => tab.neutral === true ? tab.active ? "1" : tab.fresh ? "36" : "90" : tab.active ? `${STATUS_SGR[tab.status]};1` : tab.fresh ? STATUS_SGR[tab.status] : "90";
|
|
1370
|
+
const widths = texts.map(displayWidth);
|
|
1371
|
+
const count = tabs.length;
|
|
1372
|
+
let lo = 0;
|
|
1373
|
+
let hi = count - 1;
|
|
1374
|
+
if (widths.reduce((a, b) => a + b, 0) > width) {
|
|
1375
|
+
lo = Math.max(0, Math.min(scroll, count - 1));
|
|
1376
|
+
hi = lo;
|
|
1377
|
+
const cost = (l, h) => widths.slice(l, h + 1).reduce((a, b) => a + b, 0) + (l > 0 ? TAB_INDICATOR_W : 0) + (h < count - 1 ? TAB_INDICATOR_W : 0);
|
|
1378
|
+
while (hi + 1 < count && cost(lo, hi + 1) <= width) hi++;
|
|
1379
|
+
}
|
|
1380
|
+
const segments = [];
|
|
1381
|
+
let col = 1;
|
|
1382
|
+
const push = (text, sgr, action) => {
|
|
1314
1383
|
const w = displayWidth(text);
|
|
1315
|
-
segments.push({
|
|
1316
|
-
text,
|
|
1317
|
-
sgr: tab.neutral === true ? tab.active ? "1" : tab.fresh ? "36" : "90" : tab.active ? `${STATUS_SGR[tab.status]};1` : tab.fresh ? STATUS_SGR[tab.status] : "90",
|
|
1318
|
-
lo: col,
|
|
1319
|
-
hi: col + w - 1,
|
|
1320
|
-
index
|
|
1321
|
-
});
|
|
1384
|
+
segments.push({ text, sgr, lo: col, hi: col + w - 1, action });
|
|
1322
1385
|
col += w;
|
|
1386
|
+
};
|
|
1387
|
+
if (lo > 0) push(unicode ? " \u2039 " : " < ", "90", { kind: "scroll", delta: -1 });
|
|
1388
|
+
const tail = hi < count - 1 ? TAB_INDICATOR_W : 0;
|
|
1389
|
+
for (let i = lo; i <= hi; i++) {
|
|
1390
|
+
push(fitWidth(texts[i], Math.max(1, width - (col - 1) - tail)), sgrOf(tabs[i]), { kind: "switch", index: i });
|
|
1323
1391
|
}
|
|
1392
|
+
if (hi < count - 1) push(unicode ? " \u203A " : " > ", "90", { kind: "scroll", delta: 1 });
|
|
1324
1393
|
return segments;
|
|
1325
1394
|
}
|
|
1395
|
+
function tabScrollFor(tabs, width, unicode, scroll, index) {
|
|
1396
|
+
if (index <= scroll) return Math.max(0, index);
|
|
1397
|
+
const visibleAt = (s2) => pageTabRow(tabs, width, unicode, s2).some((seg) => seg.action.kind === "switch" && seg.action.index === index);
|
|
1398
|
+
let s = Math.max(0, Math.min(scroll, tabs.length - 1));
|
|
1399
|
+
while (s < index && !visibleAt(s)) s++;
|
|
1400
|
+
return s;
|
|
1401
|
+
}
|
|
1326
1402
|
function topLevelFiles(defaultFile, files, mapOf) {
|
|
1327
1403
|
const refs = /* @__PURE__ */ new Set();
|
|
1328
1404
|
for (const m of mapOf.values()) {
|
|
@@ -1601,7 +1677,9 @@ function main() {
|
|
|
1601
1677
|
const pageViews = /* @__PURE__ */ new Map();
|
|
1602
1678
|
let activeFile;
|
|
1603
1679
|
let firstScan = true;
|
|
1680
|
+
let pendingFocusFile = cfg.page === void 0 ? void 0 : pageFilePath(cfg.file, cfg.page);
|
|
1604
1681
|
let lastTabSegments = [];
|
|
1682
|
+
let tabScroll = 0;
|
|
1605
1683
|
let offsetX = 0;
|
|
1606
1684
|
let offsetY = 0;
|
|
1607
1685
|
let zoom = ZOOM_DEFAULT;
|
|
@@ -1629,13 +1707,24 @@ function main() {
|
|
|
1629
1707
|
parent = diveOrigin(cfg.file, activeFile, pageFiles, maps())?.parent;
|
|
1630
1708
|
}
|
|
1631
1709
|
if (parent !== void 0 && parent !== activeFile) {
|
|
1632
|
-
|
|
1710
|
+
handSwitch(parent);
|
|
1633
1711
|
return true;
|
|
1634
1712
|
}
|
|
1635
1713
|
return false;
|
|
1636
1714
|
};
|
|
1715
|
+
const viewWidth = () => usableColumns(process.stdout.columns ?? 100);
|
|
1637
1716
|
const viewHeight = () => Math.max(1, (process.stdout.rows ?? 30) - (1 + panelContentRows) - 1 - tabRows());
|
|
1638
1717
|
const dividerY = () => tabRows() + viewHeight() + 1;
|
|
1718
|
+
const pageTabsOf = (files) => files.map((f) => {
|
|
1719
|
+
const m = pageData.get(f)?.map;
|
|
1720
|
+
return {
|
|
1721
|
+
title: m?.title ?? (pageIdOfFile(cfg.file, f) ?? "main"),
|
|
1722
|
+
status: m !== void 0 ? mapStatus(m) : "planned",
|
|
1723
|
+
active: f === activeFile,
|
|
1724
|
+
fresh: pageData.get(f)?.fresh ?? false,
|
|
1725
|
+
neutral: m !== void 0 && isNeutralKind(m)
|
|
1726
|
+
};
|
|
1727
|
+
});
|
|
1639
1728
|
const switchPage = (file) => {
|
|
1640
1729
|
if (activeFile !== void 0) pageViews.set(activeFile, { offsetX, offsetY, zoom, selectedId });
|
|
1641
1730
|
activeFile = file;
|
|
@@ -1649,10 +1738,18 @@ function main() {
|
|
|
1649
1738
|
if (entry !== void 0 && entry.fresh) pageData.set(file, { ...entry, fresh: false });
|
|
1650
1739
|
map = entry?.map;
|
|
1651
1740
|
notice = map === void 0 ? `waiting for ${file} ...` : "";
|
|
1741
|
+
const top = topFiles();
|
|
1742
|
+
const tabIndex = top.indexOf(file);
|
|
1743
|
+
if (tabIndex >= 0) tabScroll = tabScrollFor(pageTabsOf(top), viewWidth(), cfg.unicode, tabScroll, tabIndex);
|
|
1744
|
+
};
|
|
1745
|
+
const handSwitch = (file) => {
|
|
1746
|
+
pendingFocusFile = void 0;
|
|
1747
|
+
switchPage(file);
|
|
1652
1748
|
};
|
|
1653
1749
|
const hitTest = (termX, termY) => {
|
|
1654
1750
|
const sx = termX - 1;
|
|
1655
1751
|
const sy = termY - 1 - tabRows();
|
|
1752
|
+
if (sx < 0 || sx >= viewWidth()) return void 0;
|
|
1656
1753
|
if (sy < 0 || sy >= viewHeight()) return void 0;
|
|
1657
1754
|
const cx = sx + offsetX;
|
|
1658
1755
|
const cy = sy + offsetY;
|
|
@@ -1667,6 +1764,7 @@ function main() {
|
|
|
1667
1764
|
process.on("SIGTERM", restore);
|
|
1668
1765
|
const paint = () => {
|
|
1669
1766
|
const cols = process.stdout.columns ?? 100;
|
|
1767
|
+
const viewW = viewWidth();
|
|
1670
1768
|
panelContentRows = clampPanelRows(panelContentRows, process.stdout.rows ?? 30, tabRows());
|
|
1671
1769
|
const viewH = viewHeight();
|
|
1672
1770
|
const focus = hoverId ?? selectedId;
|
|
@@ -1677,9 +1775,9 @@ function main() {
|
|
|
1677
1775
|
const windowed = renderMapWindow(
|
|
1678
1776
|
map,
|
|
1679
1777
|
{ color: cfg.color, unicode: cfg.unicode, spinnerFrame, focus, zoom },
|
|
1680
|
-
{ x: offsetX, y: offsetY, width:
|
|
1778
|
+
{ x: offsetX, y: offsetY, width: viewW, height: viewH }
|
|
1681
1779
|
);
|
|
1682
|
-
const maxX = Math.max(0, windowed.contentWidth -
|
|
1780
|
+
const maxX = Math.max(0, windowed.contentWidth - viewW);
|
|
1683
1781
|
const maxY = Math.max(0, windowed.contentHeight - viewH);
|
|
1684
1782
|
if (offsetX > maxX || offsetY > maxY || offsetX < 0 || offsetY < 0) {
|
|
1685
1783
|
offsetX = Math.min(Math.max(0, offsetX), maxX);
|
|
@@ -1693,10 +1791,10 @@ function main() {
|
|
|
1693
1791
|
lastContent = { w: windowed.contentWidth, h: windowed.contentHeight };
|
|
1694
1792
|
if (offsetX !== 0 || offsetY !== 0) panned = ` (+${offsetX},+${offsetY})`;
|
|
1695
1793
|
} else {
|
|
1696
|
-
body = (interactive ? splashFrame(notice, splashTick,
|
|
1794
|
+
body = (interactive ? splashFrame(notice, splashTick, viewW, viewH, cfg.unicode, cfg.color) : void 0) ?? [fitWidth(notice, viewW)];
|
|
1697
1795
|
}
|
|
1698
1796
|
if (notice !== "" && map !== void 0) {
|
|
1699
|
-
body[body.length - 1] = fitWidth(` ${notice}`,
|
|
1797
|
+
body[body.length - 1] = fitWidth(` ${notice}`, viewW);
|
|
1700
1798
|
}
|
|
1701
1799
|
const panelWidth = Math.max(10, cols - 2);
|
|
1702
1800
|
let panel;
|
|
@@ -1708,9 +1806,9 @@ function main() {
|
|
|
1708
1806
|
panel = mapPanel(map, cfg.unicode, panelWidth, panelContentRows);
|
|
1709
1807
|
}
|
|
1710
1808
|
const grip = cfg.unicode ? " \u22EF " : " ~ ";
|
|
1711
|
-
const bar = (cfg.unicode ? "\u2500" : "-").repeat(
|
|
1712
|
-
const gripAt = Math.max(0, Math.floor((
|
|
1713
|
-
const separator =
|
|
1809
|
+
const bar = (cfg.unicode ? "\u2500" : "-").repeat(viewW);
|
|
1810
|
+
const gripAt = Math.max(0, Math.floor((viewW - grip.length) / 2));
|
|
1811
|
+
const separator = viewW > grip.length + 2 ? bar.slice(0, gripAt) + grip + bar.slice(gripAt + grip.length) : bar;
|
|
1714
1812
|
const panelRows = [
|
|
1715
1813
|
cfg.color ? `\x1B[90m${separator}${RESET}` : separator,
|
|
1716
1814
|
...panel.map(
|
|
@@ -1726,29 +1824,19 @@ function main() {
|
|
|
1726
1824
|
const parentTitle = parentFile !== void 0 ? pageData.get(parentFile)?.map?.title ?? (pageIdOfFile(cfg.file, parentFile) ?? "main") : "main";
|
|
1727
1825
|
const nodeLabel = scanned?.label ?? map?.title ?? "";
|
|
1728
1826
|
const crumbHead = ` ${cfg.unicode ? "\u232B" : "<"} ${parentTitle} ${cfg.unicode ? "\u25B8" : ">"} `;
|
|
1729
|
-
const head = { text: crumbHead, sgr: "90", lo: 1, hi: displayWidth(crumbHead),
|
|
1730
|
-
const tailText = fitWidth(`${nodeLabel} `, Math.max(1,
|
|
1827
|
+
const head = { text: crumbHead, sgr: "90", lo: 1, hi: displayWidth(crumbHead), action: { kind: "back" } };
|
|
1828
|
+
const tailText = fitWidth(`${nodeLabel} `, Math.max(1, viewW - displayWidth(crumbHead)));
|
|
1731
1829
|
const tail = {
|
|
1732
1830
|
text: tailText,
|
|
1733
1831
|
sgr: "1",
|
|
1734
1832
|
lo: head.hi + 1,
|
|
1735
1833
|
hi: head.hi + displayWidth(tailText),
|
|
1736
|
-
|
|
1834
|
+
action: { kind: "back" }
|
|
1737
1835
|
};
|
|
1738
1836
|
lastTabSegments = [head, tail];
|
|
1739
1837
|
tabLine = lastTabSegments.map((s) => cfg.color && s.sgr !== "" ? `\x1B[${s.sgr}m${s.text}${RESET}` : s.text).join("");
|
|
1740
1838
|
} else if (tabRows() > 0) {
|
|
1741
|
-
const
|
|
1742
|
-
const m = pageData.get(f)?.map;
|
|
1743
|
-
return {
|
|
1744
|
-
title: m?.title ?? (pageIdOfFile(cfg.file, f) ?? "main"),
|
|
1745
|
-
status: m !== void 0 ? mapStatus(m) : "planned",
|
|
1746
|
-
active: f === activeFile,
|
|
1747
|
-
fresh: pageData.get(f)?.fresh ?? false,
|
|
1748
|
-
neutral: m !== void 0 && isNeutralKind(m)
|
|
1749
|
-
};
|
|
1750
|
-
});
|
|
1751
|
-
const segments = pageTabRow(tabs, cols, cfg.unicode);
|
|
1839
|
+
const segments = pageTabRow(pageTabsOf(lastTabFiles), viewW, cfg.unicode, tabScroll);
|
|
1752
1840
|
lastTabSegments = segments;
|
|
1753
1841
|
tabLine = segments.map((s) => cfg.color && s.sgr !== "" ? `\x1B[${s.sgr}m${s.text}${RESET}` : s.text).join("");
|
|
1754
1842
|
} else {
|
|
@@ -1756,7 +1844,7 @@ function main() {
|
|
|
1756
1844
|
}
|
|
1757
1845
|
const zoomTag = `${cfg.unicode ? "\u2295" : "zoom"} ${zoomLabel(zoom)}`;
|
|
1758
1846
|
const hint = !interactive ? cfg.file : (flash !== void 0 ? `${flash.text} \xB7 ` : "") + `${zoomTag} \xB7 wheel zoom \xB7 ` + (pannable ? "drag pan \xB7 " : "") + "hover/click \xB7 0 reset \xB7 q quit";
|
|
1759
|
-
const footerText = fitWidth(` ${hint}${panned}`,
|
|
1847
|
+
const footerText = fitWidth(` ${hint}${panned}`, viewW);
|
|
1760
1848
|
const footer = cfg.color ? `\x1B[90m${footerText}${RESET}` : footerText;
|
|
1761
1849
|
let frame = HOME;
|
|
1762
1850
|
if (tabLine !== void 0) frame += tabLine + ERASE_LINE_END + "\n";
|
|
@@ -1813,8 +1901,18 @@ function main() {
|
|
|
1813
1901
|
if (file === activeFile) notice = describeStoreError(loaded.error);
|
|
1814
1902
|
}
|
|
1815
1903
|
}
|
|
1904
|
+
const request = takeFocusRequest(cfg.file);
|
|
1905
|
+
if (request !== void 0 && !(firstScan && pendingFocusFile !== void 0)) {
|
|
1906
|
+
pendingFocusFile = pageFilePath(cfg.file, request.page);
|
|
1907
|
+
}
|
|
1908
|
+
if (pendingFocusFile !== void 0 && pageFiles.includes(pendingFocusFile)) {
|
|
1909
|
+
if (pendingFocusFile !== activeFile) switchPage(pendingFocusFile);
|
|
1910
|
+
pendingFocusFile = void 0;
|
|
1911
|
+
}
|
|
1816
1912
|
firstScan = false;
|
|
1817
|
-
if (activeFile === void 0 || !pageFiles.includes(activeFile))
|
|
1913
|
+
if (activeFile === void 0 || !pageFiles.includes(activeFile)) {
|
|
1914
|
+
switchPage(mostRecentPageFile(pageFiles, (f) => pageData.get(f)?.mtimeMs));
|
|
1915
|
+
}
|
|
1818
1916
|
if ([...pageData.values()].some((p) => p.map?.nodes.some((n) => n.status === "in-progress"))) spinnerFrame++;
|
|
1819
1917
|
if (flash !== void 0 && Date.now() > flash.until) flash = void 0;
|
|
1820
1918
|
paint();
|
|
@@ -1849,10 +1947,14 @@ function main() {
|
|
|
1849
1947
|
dirty = true;
|
|
1850
1948
|
break;
|
|
1851
1949
|
case "zoom": {
|
|
1950
|
+
if (event.at !== void 0 && event.at.y === 1 && tabRows() > 0 && !inSubmap()) {
|
|
1951
|
+
tabScroll = Math.max(0, Math.min(tabScroll + (event.delta === 1 ? -1 : 1), topFiles().length - 1));
|
|
1952
|
+
dirty = true;
|
|
1953
|
+
break;
|
|
1954
|
+
}
|
|
1852
1955
|
const next = clampZoom(zoom + event.delta);
|
|
1853
1956
|
if (next === zoom || map === void 0) break;
|
|
1854
|
-
const
|
|
1855
|
-
const anchorId = hoverId ?? selectedId ?? nearestHit(lastHits, offsetX + cols / 2, offsetY + viewHeight() / 2)?.id;
|
|
1957
|
+
const anchorId = hoverId ?? selectedId ?? nearestHit(lastHits, offsetX + viewWidth() / 2, offsetY + viewHeight() / 2)?.id;
|
|
1856
1958
|
const before = lastHits.find((h) => h.id === anchorId);
|
|
1857
1959
|
zoom = next;
|
|
1858
1960
|
const sized = renderMapWindow(
|
|
@@ -1916,11 +2018,13 @@ function main() {
|
|
|
1916
2018
|
if (press && !press.moved) {
|
|
1917
2019
|
const tabHit = tabRows() > 0 && event.y === 1 ? lastTabSegments.find((s) => event.x >= s.lo && event.x <= s.hi) : void 0;
|
|
1918
2020
|
if (tabHit !== void 0) {
|
|
1919
|
-
if (tabHit.
|
|
2021
|
+
if (tabHit.action.kind === "back") {
|
|
1920
2022
|
climbBack();
|
|
2023
|
+
} else if (tabHit.action.kind === "scroll") {
|
|
2024
|
+
tabScroll = Math.max(0, Math.min(tabScroll + tabHit.action.delta, lastTabFiles.length - 1));
|
|
1921
2025
|
} else {
|
|
1922
|
-
const target = lastTabFiles[tabHit.index];
|
|
1923
|
-
if (target !== void 0 && target !== activeFile)
|
|
2026
|
+
const target = lastTabFiles[tabHit.action.index];
|
|
2027
|
+
if (target !== void 0 && target !== activeFile) handSwitch(target);
|
|
1924
2028
|
}
|
|
1925
2029
|
} else {
|
|
1926
2030
|
const id = hitTest(event.x, event.y);
|
|
@@ -1931,7 +2035,7 @@ function main() {
|
|
|
1931
2035
|
const target = pageFilePath(cfg.file, submap);
|
|
1932
2036
|
if (pageFiles.includes(target) && target !== activeFile) {
|
|
1933
2037
|
diveStack.push(activeFile);
|
|
1934
|
-
|
|
2038
|
+
handSwitch(target);
|
|
1935
2039
|
} else if (!pageFiles.includes(target)) {
|
|
1936
2040
|
flash = { text: `submap "${submap}" has no page yet`, until: now + 2500 };
|
|
1937
2041
|
}
|
|
@@ -1955,7 +2059,7 @@ function main() {
|
|
|
1955
2059
|
const step = event.kind === "next-page" ? 1 : -1;
|
|
1956
2060
|
const target = top[(current + step + top.length) % top.length];
|
|
1957
2061
|
if (target !== activeFile) {
|
|
1958
|
-
|
|
2062
|
+
handSwitch(target);
|
|
1959
2063
|
dirty = true;
|
|
1960
2064
|
}
|
|
1961
2065
|
}
|
|
@@ -1964,7 +2068,7 @@ function main() {
|
|
|
1964
2068
|
case "page": {
|
|
1965
2069
|
const target = topFiles()[event.index];
|
|
1966
2070
|
if (target !== void 0 && target !== activeFile) {
|
|
1967
|
-
|
|
2071
|
+
handSwitch(target);
|
|
1968
2072
|
dirty = true;
|
|
1969
2073
|
}
|
|
1970
2074
|
break;
|
|
@@ -2010,6 +2114,7 @@ export {
|
|
|
2010
2114
|
launchedAsEntry,
|
|
2011
2115
|
liveRipples,
|
|
2012
2116
|
mapPanel,
|
|
2117
|
+
mostRecentPageFile,
|
|
2013
2118
|
nearestHit,
|
|
2014
2119
|
nodePanel,
|
|
2015
2120
|
pageTabRow,
|
|
@@ -2017,7 +2122,9 @@ export {
|
|
|
2017
2122
|
parseArgs,
|
|
2018
2123
|
splashArt,
|
|
2019
2124
|
splashFrame,
|
|
2125
|
+
tabScrollFor,
|
|
2020
2126
|
topLevelFiles,
|
|
2127
|
+
usableColumns,
|
|
2021
2128
|
waveAt,
|
|
2022
2129
|
waveHash,
|
|
2023
2130
|
waveLevel,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mellos-mapping",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"mcpName": "io.github.GuangminJu/mellos-mapping",
|
|
5
5
|
"description": "A live layered dependency map for bottom-up development — MCP server + terminal pane. Ghost the design first, then light nodes up from the bottom as they are built and verified.",
|
|
6
6
|
"type": "module",
|
package/scripts/open-pane.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Open the live map pane in the RIGHT Windows Terminal window.
|
|
4
4
|
*
|
|
5
|
-
* node scripts/open-pane.mjs <project-dir> [--window] [--ascii] [--force]
|
|
5
|
+
* node scripts/open-pane.mjs <project-dir> [--page <slug>] [--window] [--ascii] [--force]
|
|
6
6
|
*
|
|
7
7
|
* Why this exists: the agent's shell runs on a hidden console (no WT_SESSION),
|
|
8
8
|
* so a bare `wt -w 0 sp` targets the MOST RECENTLY USED terminal window — with
|
|
@@ -33,18 +33,38 @@
|
|
|
33
33
|
* Known race, accepted: if the user focuses a DIFFERENT terminal window in
|
|
34
34
|
* the ~1s between our focus-verify and wt reading its MRU state, the split
|
|
35
35
|
* can still land there. The window is at least one the user is actively in.
|
|
36
|
+
*
|
|
37
|
+
* --page <slug> opens the map ON that page (the effort under discussion, not
|
|
38
|
+
* whatever page the store lists first). With a watcher already running it
|
|
39
|
+
* writes the one-shot focus file instead — the existing pane retargets within
|
|
40
|
+
* a poll tick — so re-running with --page is also how you steer an open pane.
|
|
36
41
|
*/
|
|
37
42
|
import { spawnSync } from 'node:child_process';
|
|
38
|
-
import { existsSync } from 'node:fs';
|
|
43
|
+
import { existsSync, mkdirSync, renameSync, writeFileSync } from 'node:fs';
|
|
39
44
|
import { dirname, join, resolve } from 'node:path';
|
|
40
45
|
import { fileURLToPath } from 'node:url';
|
|
41
46
|
|
|
47
|
+
const USAGE = 'usage: node scripts/open-pane.mjs <project-dir> [--page <slug>] [--window] [--ascii] [--force]';
|
|
48
|
+
|
|
42
49
|
const argv = process.argv.slice(2);
|
|
43
|
-
const flags = new Set(
|
|
44
|
-
const positional =
|
|
50
|
+
const flags = new Set();
|
|
51
|
+
const positional = [];
|
|
52
|
+
let pageSlug;
|
|
53
|
+
for (let i = 0; i < argv.length; i++) {
|
|
54
|
+
const a = argv[i];
|
|
55
|
+
if (a === '--page') pageSlug = argv[++i];
|
|
56
|
+
else if (a.startsWith('--')) flags.add(a);
|
|
57
|
+
else positional.push(a);
|
|
58
|
+
}
|
|
45
59
|
|
|
46
60
|
if (positional.length !== 1) {
|
|
47
|
-
console.error(
|
|
61
|
+
console.error(USAGE);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
// Mirrors ID_RULE in src/domain/types.ts — this script runs standalone and
|
|
65
|
+
// cannot import the TypeScript sources.
|
|
66
|
+
if (pageSlug !== undefined && !/^[a-z0-9][a-z0-9-]{0,63}$/.test(pageSlug)) {
|
|
67
|
+
console.error(`--page needs a kebab-case slug (got "${pageSlug}")\n${USAGE}`);
|
|
48
68
|
process.exit(1);
|
|
49
69
|
}
|
|
50
70
|
if (process.platform !== 'win32') {
|
|
@@ -203,6 +223,7 @@ Write-Output "FOCUS=$(if ($focused) { 1 } else { 0 })"
|
|
|
203
223
|
function paneCommand() {
|
|
204
224
|
const cmd = ['--title', 'mellos map', '-d', projectDir, 'node', watchPath, '--file', mapFile];
|
|
205
225
|
if (flags.has('--ascii')) cmd.push('--ascii');
|
|
226
|
+
if (pageSlug !== undefined) cmd.push('--page', pageSlug);
|
|
206
227
|
return cmd;
|
|
207
228
|
}
|
|
208
229
|
|
|
@@ -221,8 +242,21 @@ function openDedicatedWindow(reason) {
|
|
|
221
242
|
}
|
|
222
243
|
|
|
223
244
|
if (!flags.has('--force') && watcherAlreadyRunning()) {
|
|
224
|
-
|
|
225
|
-
|
|
245
|
+
if (pageSlug !== undefined) {
|
|
246
|
+
// One-shot focus request (see takeFocusRequest in src/store/store.ts):
|
|
247
|
+
// the running watcher consumes and deletes it within a poll tick. Temp +
|
|
248
|
+
// rename because the watcher polls: a torn read would be swept as junk,
|
|
249
|
+
// silently losing the request.
|
|
250
|
+
mkdirSync(dirname(mapFile), { recursive: true });
|
|
251
|
+
const focusFile = join(dirname(mapFile), 'mellos-mapping.focus');
|
|
252
|
+
writeFileSync(`${focusFile}.tmp`, JSON.stringify({ page: pageSlug }));
|
|
253
|
+
renameSync(`${focusFile}.tmp`, focusFile);
|
|
254
|
+
console.log(`MMAP_PANE already-open refocused=${pageSlug}`);
|
|
255
|
+
console.log(`A watcher for ${mapFile} is already running — asked it to show page "${pageSlug}".`);
|
|
256
|
+
} else {
|
|
257
|
+
console.log('MMAP_PANE already-open');
|
|
258
|
+
console.log(`A watcher for ${mapFile} is already running — not opening another pane (use --force to override).`);
|
|
259
|
+
}
|
|
226
260
|
process.exit(0);
|
|
227
261
|
}
|
|
228
262
|
|