@dadado/agent-kit-cli 4.8.2 → 4.8.4
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/dashboard/dashboard.html +459 -77
- package/dashboard/lib/guards.mjs +19 -1
- package/dashboard/lib/semantic-model.mjs +38 -23
- package/dashboard/lib/triage-heading.mjs +20 -0
- package/dashboard/start-broadcast.mjs +23 -23
- package/dashboard/start.mjs +22 -28
- package/dist/index.js +176 -24
- package/package.json +1 -1
package/dashboard/lib/guards.mjs
CHANGED
|
@@ -29,6 +29,22 @@ export const DEFAULT_PORT_BASE = 3333;
|
|
|
29
29
|
*/
|
|
30
30
|
export const DEFAULT_PORT_RANGE = 256;
|
|
31
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Escape a path/string for embedding in a Perl double-quoted literal.
|
|
34
|
+
* Scoped npm package paths contain `@` (e.g. `node_modules/@dadado/...`);
|
|
35
|
+
* unescaped `@name` is array interpolation in Perl and strips the scope segment,
|
|
36
|
+
* so macOS detach-start via `perl -e` cannot exec `dashboard/serve.mjs`.
|
|
37
|
+
* @param {string} value
|
|
38
|
+
* @returns {string}
|
|
39
|
+
*/
|
|
40
|
+
export function escapePerlDoubleQuoted(value) {
|
|
41
|
+
return String(value)
|
|
42
|
+
.replace(/\\/g, "\\\\")
|
|
43
|
+
.replace(/"/g, '\\"')
|
|
44
|
+
.replace(/\$/g, "\\$")
|
|
45
|
+
.replace(/@/g, "\\@");
|
|
46
|
+
}
|
|
47
|
+
|
|
32
48
|
/**
|
|
33
49
|
* Resolve the repository root Mission Control should snapshot.
|
|
34
50
|
* @param {NodeJS.ProcessEnv | Record<string, string | undefined>} [env]
|
|
@@ -141,7 +157,9 @@ export function sameRepoRoot(a, b) {
|
|
|
141
157
|
export function resolveMissionControlPort({ repoRoot, envPort, probe, opts = {} }) {
|
|
142
158
|
const root = resolve(String(repoRoot || "").trim() || ".");
|
|
143
159
|
const raw =
|
|
144
|
-
envPort != null && String(envPort).trim() !== ""
|
|
160
|
+
envPort != null && String(envPort).trim() !== ""
|
|
161
|
+
? Number.parseInt(String(envPort), 10)
|
|
162
|
+
: Number.NaN;
|
|
145
163
|
|
|
146
164
|
if (Number.isFinite(raw) && raw > 0) {
|
|
147
165
|
const info = probe(raw);
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
// Pure Mission Control view-model helpers (testable; no fs/git I/O).
|
|
3
3
|
|
|
4
4
|
import { truncateStr } from "./guards.mjs";
|
|
5
|
+
import { TRIAGE_HEADING_RE, hasTriageHeading } from "./triage-heading.mjs";
|
|
6
|
+
|
|
7
|
+
/** Durable triage heading SoT (shared with CLI `monitors --untriaged`). */
|
|
8
|
+
export { TRIAGE_HEADING_RE, hasTriageHeading };
|
|
5
9
|
|
|
6
10
|
export const MAX_ACTIVITY = 28;
|
|
7
11
|
export const MAX_ATTENTION = 15;
|
|
@@ -62,15 +66,6 @@ export const MAX_CHECKLIST_NOTES = 15;
|
|
|
62
66
|
* External review reports are `.cursor/memory/plan-monitor-<slug>.md`. */
|
|
63
67
|
export const EXTERNAL_REPORT_FILE_RE = /^plan-monitor-(.+)\.md$/;
|
|
64
68
|
|
|
65
|
-
/**
|
|
66
|
-
* A heading the triage step leaves behind in the report itself. Confirmed
|
|
67
|
-
* against the local reports: `## Triage note - residual (A) verified` and
|
|
68
|
-
* `## Follow-up plan - hitl_ask_questions_residuals_2026_07_20.plan.md`.
|
|
69
|
-
* `/plan-review-triage` must write one of these for every outcome, including
|
|
70
|
-
* Ack and stop, so Field Report can clear the untriaged row.
|
|
71
|
-
*/
|
|
72
|
-
export const TRIAGE_HEADING_RE = /^#{2,6}\s+.*\b(triage|follow-?up plan|residuals plan)\b/im;
|
|
73
|
-
|
|
74
69
|
/**
|
|
75
70
|
* Local Field Report dismissals store (IDs only). Valid attention ids that
|
|
76
71
|
* `/field-report-resolve` may append: External reviews, agent prompts, and
|
|
@@ -576,21 +571,23 @@ export function buildFlightLogWarnings(handoff, opts = {}) {
|
|
|
576
571
|
|
|
577
572
|
/**
|
|
578
573
|
* Bounded untriaged external-review rows for Flight Log quiet state.
|
|
579
|
-
* Filters
|
|
580
|
-
* or bulk FR CTAs).
|
|
581
|
-
*
|
|
582
|
-
*
|
|
574
|
+
* Filters to `kind === "report"` with a non-empty `sourcePath` (no cadence,
|
|
575
|
+
* prompts, readiness, or bulk FR CTAs). Prefer calling with
|
|
576
|
+
* `buildExternalReportItems(...)` output so the quiet lane is not starved by
|
|
577
|
+
* the shared `buildAttentionItems` cap. Callers must not mix these rows into a
|
|
578
|
+
* non-quiet Gaps/Warnings stack.
|
|
579
|
+
* @param {object[]|null|undefined} reportOrAttentionItems
|
|
583
580
|
* @param {{ limit?: number }} [opts]
|
|
584
581
|
* @returns {object[]}
|
|
585
582
|
*/
|
|
586
|
-
export function listFlightLogQuietOpenTriages(
|
|
583
|
+
export function listFlightLogQuietOpenTriages(reportOrAttentionItems, opts = {}) {
|
|
587
584
|
const limit =
|
|
588
585
|
typeof opts.limit === "number" && opts.limit > 0
|
|
589
586
|
? Math.floor(opts.limit)
|
|
590
587
|
: FLIGHT_LOG_QUIET_OPEN_TRIAGES_CAP;
|
|
591
|
-
if (!Array.isArray(
|
|
588
|
+
if (!Array.isArray(reportOrAttentionItems) || reportOrAttentionItems.length === 0) return [];
|
|
592
589
|
const out = [];
|
|
593
|
-
for (const item of
|
|
590
|
+
for (const item of reportOrAttentionItems) {
|
|
594
591
|
if (!item || item.kind !== "report") continue;
|
|
595
592
|
if (typeof item.sourcePath !== "string" || !item.sourcePath.trim()) continue;
|
|
596
593
|
out.push(item);
|
|
@@ -1369,26 +1366,33 @@ export function normalizeHandoffGaps(raw) {
|
|
|
1369
1366
|
|
|
1370
1367
|
/**
|
|
1371
1368
|
* Classify a Flight Log Gaps/Warning body for palette chrome.
|
|
1369
|
+
* Runs heuristics on whitespace-collapsed text **before** display truncation so
|
|
1370
|
+
* long Gaps whose only warning/prompt/advice keyword sits past MAX_SEMANTIC_LABEL
|
|
1371
|
+
* still match the inline dashboard.html classifier (which does not truncate).
|
|
1372
1372
|
* @param {string | null | undefined} text
|
|
1373
1373
|
* @param {{ lane?: 'gaps' | 'warning' }} [opts]
|
|
1374
1374
|
* @returns {FlightLogMessageKind}
|
|
1375
1375
|
*/
|
|
1376
1376
|
export function classifyFlightLogMessageKind(text, opts = {}) {
|
|
1377
1377
|
if (opts.lane === "warning") return "warning";
|
|
1378
|
-
|
|
1379
|
-
|
|
1378
|
+
if (!text || typeof text !== "string") return "ok";
|
|
1379
|
+
const collapsed = text.replace(/\s+/g, " ").trim();
|
|
1380
|
+
if (!collapsed) return "ok";
|
|
1381
|
+
if (/^(none|n\/a)$/i.test(collapsed)) return "ok";
|
|
1382
|
+
if (/^(none|n\/a)\s*[.:,;\/(\-–—…]/i.test(collapsed)) return "ok";
|
|
1383
|
+
if (/^([-–—.…]|empty|no gaps?|cleared|all clear|ok)$/i.test(collapsed)) return "ok";
|
|
1380
1384
|
if (
|
|
1381
|
-
/\bAPI\s*\/\s*usage\s+limit\b|\bAPI\s+usage\s+limit\b|\bSTOPPED:\s*API\b/i.test(
|
|
1382
|
-
/\b(hard.?stop|quota\s+pause)\b/i.test(
|
|
1385
|
+
/\bAPI\s*\/\s*usage\s+limit\b|\bAPI\s+usage\s+limit\b|\bSTOPPED:\s*API\b/i.test(collapsed) ||
|
|
1386
|
+
/\b(hard.?stop|quota\s+pause)\b/i.test(collapsed)
|
|
1383
1387
|
) {
|
|
1384
1388
|
return "warning";
|
|
1385
1389
|
}
|
|
1386
1390
|
if (
|
|
1387
|
-
/\b(confirm|ask questions|hitl|\bpaste\b|choose\b|approve\b|operator yes)\b/i.test(
|
|
1391
|
+
/\b(confirm|ask questions|hitl|\bpaste\b|choose\b|approve\b|operator yes)\b/i.test(collapsed)
|
|
1388
1392
|
) {
|
|
1389
1393
|
return "prompt";
|
|
1390
1394
|
}
|
|
1391
|
-
if (/\b(tip:|advice:|consider\b|recommends?\b|recommended\b|prefer\b)/i.test(
|
|
1395
|
+
if (/\b(tip:|advice:|consider\b|recommends?\b|recommended\b|prefer\b)/i.test(collapsed)) {
|
|
1392
1396
|
return "advice";
|
|
1393
1397
|
}
|
|
1394
1398
|
return "residual";
|
|
@@ -3544,6 +3548,15 @@ export function buildMissionControlView({
|
|
|
3544
3548
|
// Field Report attention inbox left the Flight Log card; builders stay
|
|
3545
3549
|
// exported for /field-report-resolve + cadence scripts (ADR keep).
|
|
3546
3550
|
// Quiet Gaps+Warnings: bounded report rows may surface on Flight Log.
|
|
3551
|
+
// Build quiet lane from external reports directly (not capped attention) so
|
|
3552
|
+
// prompt/readiness pressure cannot starve Reviews awaiting triage to All clear.
|
|
3553
|
+
const dismissedForQuiet = new Set(
|
|
3554
|
+
(dismissedIds || []).filter((id) => typeof id === "string" && id.length > 0),
|
|
3555
|
+
);
|
|
3556
|
+
const quietReportItems = buildExternalReportItems(externalReports, plans, {
|
|
3557
|
+
handoff,
|
|
3558
|
+
archivedPlanFiles,
|
|
3559
|
+
}).filter((item) => item && !dismissedForQuiet.has(item.id));
|
|
3547
3560
|
const attention = buildAttentionItems({
|
|
3548
3561
|
plans,
|
|
3549
3562
|
handoff,
|
|
@@ -3556,7 +3569,7 @@ export function buildMissionControlView({
|
|
|
3556
3569
|
cadenceLedger,
|
|
3557
3570
|
cadenceConfig,
|
|
3558
3571
|
});
|
|
3559
|
-
flightLog.quietOpenTriages = listFlightLogQuietOpenTriages(
|
|
3572
|
+
flightLog.quietOpenTriages = listFlightLogQuietOpenTriages(quietReportItems);
|
|
3560
3573
|
// Deprecated: attention owns Field Report rows. Kept empty so older panel
|
|
3561
3574
|
// code that still reads the field does not double-render.
|
|
3562
3575
|
const checklistNotes = [];
|
|
@@ -3571,6 +3584,8 @@ export function buildMissionControlView({
|
|
|
3571
3584
|
plans: classifiedPlans,
|
|
3572
3585
|
// Crew Monitor hero display cap (SoT for dashboard.html; no HTML literal).
|
|
3573
3586
|
monitorFeedCap: MONITOR_FEED_CAP,
|
|
3587
|
+
// Quiet open-triage fallback cap for dashboard.html attention mirror.
|
|
3588
|
+
flightLogQuietOpenTriagesCap: FLIGHT_LOG_QUIET_OPEN_TRIAGES_CAP,
|
|
3574
3589
|
// /run-plan-all queue slice (null outside queue mode). Copy-only data:
|
|
3575
3590
|
// display order and roles; the panel never writes the queue back.
|
|
3576
3591
|
runQueue: buildRunQueueView(handoff),
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable triage headings written by `/plan-review-triage` (L0).
|
|
3
|
+
*
|
|
4
|
+
* Shared SoT for Mission Control (`isReportTriaged`) and
|
|
5
|
+
* `agent-kit monitors --untriaged`. Match ONLY these heading titles, not tick
|
|
6
|
+
* headings that merely name a `triage-*` to-do id (hyphens are word boundaries,
|
|
7
|
+
* so `\btriage\b` falsely matched those).
|
|
8
|
+
*
|
|
9
|
+
* Allowed forms (case-insensitive; optional suffix after the title):
|
|
10
|
+
* ## Triage note
|
|
11
|
+
* ## Follow-up plan (also "Followup plan")
|
|
12
|
+
* ## Residuals plan
|
|
13
|
+
*/
|
|
14
|
+
export const TRIAGE_HEADING_RE =
|
|
15
|
+
/^#{2,6}\s+(?:Triage note|Follow-?up plan|Residuals plan)\b/im;
|
|
16
|
+
|
|
17
|
+
/** True when markdown carries a durable triage heading. */
|
|
18
|
+
export function hasTriageHeading(text) {
|
|
19
|
+
return TRIAGE_HEADING_RE.test(String(text ?? ""));
|
|
20
|
+
}
|
|
@@ -7,13 +7,14 @@
|
|
|
7
7
|
* prints LAN URL(s) with token. Does not weaken loopback `/dashboard`.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import { execFileSync, execSync, spawn } from "node:child_process";
|
|
11
11
|
import { existsSync, openSync } from "node:fs";
|
|
12
|
+
import { platform } from "node:os";
|
|
12
13
|
import { dirname, join } from "node:path";
|
|
13
14
|
import { fileURLToPath } from "node:url";
|
|
14
|
-
import { platform } from "node:os";
|
|
15
15
|
import {
|
|
16
16
|
BROADCAST_TOKEN_ENV,
|
|
17
|
+
escapePerlDoubleQuoted,
|
|
17
18
|
generateBroadcastToken,
|
|
18
19
|
isLoopbackBindHost,
|
|
19
20
|
isValidBroadcastToken,
|
|
@@ -26,7 +27,7 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
|
26
27
|
const ROOT = join(__dirname, "..");
|
|
27
28
|
const SERVE = join(__dirname, "serve.mjs");
|
|
28
29
|
const LOG = process.env.MISSION_CONTROL_LOG || "/tmp/mission-control-broadcast.log";
|
|
29
|
-
const PORT = parseInt(process.env.PORT || "3333", 10);
|
|
30
|
+
const PORT = Number.parseInt(process.env.PORT || "3333", 10);
|
|
30
31
|
const READY_TIMEOUT_MS = 20_000;
|
|
31
32
|
const READY_POLL_MS = 250;
|
|
32
33
|
|
|
@@ -57,11 +58,10 @@ function urlsForProbe(token) {
|
|
|
57
58
|
|
|
58
59
|
function probeHttp(url) {
|
|
59
60
|
try {
|
|
60
|
-
const code = execFileSync(
|
|
61
|
-
"
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
).trim();
|
|
61
|
+
const code = execFileSync("curl", ["-sf", "-o", "/dev/null", "-w", "%{http_code}", url], {
|
|
62
|
+
encoding: "utf8",
|
|
63
|
+
timeout: 3000,
|
|
64
|
+
}).trim();
|
|
65
65
|
return code === "200";
|
|
66
66
|
} catch {
|
|
67
67
|
return false;
|
|
@@ -70,11 +70,10 @@ function probeHttp(url) {
|
|
|
70
70
|
|
|
71
71
|
function listeningPids() {
|
|
72
72
|
try {
|
|
73
|
-
const out = execFileSync(
|
|
74
|
-
"
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
).trim();
|
|
73
|
+
const out = execFileSync("lsof", ["-nP", `-iTCP:${PORT}`, "-sTCP:LISTEN", "-t"], {
|
|
74
|
+
encoding: "utf8",
|
|
75
|
+
timeout: 3000,
|
|
76
|
+
}).trim();
|
|
78
77
|
return out ? out.split(/\n+/).filter(Boolean) : [];
|
|
79
78
|
} catch {
|
|
80
79
|
return [];
|
|
@@ -107,12 +106,13 @@ function detachStart(env) {
|
|
|
107
106
|
return;
|
|
108
107
|
}
|
|
109
108
|
|
|
110
|
-
|
|
111
|
-
const
|
|
112
|
-
const
|
|
113
|
-
const
|
|
114
|
-
const
|
|
115
|
-
const
|
|
109
|
+
// Escape @/$ so scoped package paths (node_modules/@scope/...) survive Perl qq.
|
|
110
|
+
const rootEsc = escapePerlDoubleQuoted(ROOT);
|
|
111
|
+
const serveEsc = escapePerlDoubleQuoted(SERVE);
|
|
112
|
+
const logEsc = escapePerlDoubleQuoted(LOG);
|
|
113
|
+
const hostEsc = escapePerlDoubleQuoted(String(env.HOST));
|
|
114
|
+
const tokenEsc = escapePerlDoubleQuoted(String(env[BROADCAST_TOKEN_ENV]));
|
|
115
|
+
const portEsc = escapePerlDoubleQuoted(String(PORT));
|
|
116
116
|
const perl = [
|
|
117
117
|
"use POSIX qw(setsid);",
|
|
118
118
|
"exit if fork;",
|
|
@@ -169,7 +169,9 @@ function openBrowser(url) {
|
|
|
169
169
|
async function main() {
|
|
170
170
|
const { env, host, token } = resolveBroadcastEnv();
|
|
171
171
|
if (isLoopbackBindHost(host)) {
|
|
172
|
-
console.error(
|
|
172
|
+
console.error(
|
|
173
|
+
"Broadcast refused: bind host resolved to loopback. Set HOST to a non-loopback address.",
|
|
174
|
+
);
|
|
173
175
|
process.exit(1);
|
|
174
176
|
}
|
|
175
177
|
|
|
@@ -193,9 +195,7 @@ async function main() {
|
|
|
193
195
|
detachStart(env);
|
|
194
196
|
const ready = await waitReady(urls);
|
|
195
197
|
if (!ready) {
|
|
196
|
-
console.error(
|
|
197
|
-
`Mission Control broadcast did not answer within ${READY_TIMEOUT_MS}ms.`,
|
|
198
|
-
);
|
|
198
|
+
console.error(`Mission Control broadcast did not answer within ${READY_TIMEOUT_MS}ms.`);
|
|
199
199
|
console.error(`Check the log: ${LOG}`);
|
|
200
200
|
process.exit(1);
|
|
201
201
|
}
|
package/dashboard/start.mjs
CHANGED
|
@@ -15,17 +15,18 @@
|
|
|
15
15
|
* Foreground serve for debugging remains: `npm run start:dashboard`.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
import {
|
|
18
|
+
import { execFileSync, execSync, spawn } from "node:child_process";
|
|
19
19
|
import { existsSync, openSync } from "node:fs";
|
|
20
|
+
import { platform } from "node:os";
|
|
20
21
|
import { dirname, join, resolve } from "node:path";
|
|
21
22
|
import { fileURLToPath } from "node:url";
|
|
22
|
-
import { platform } from "node:os";
|
|
23
23
|
import {
|
|
24
24
|
REPO_ROOT_ENV,
|
|
25
|
-
|
|
25
|
+
escapePerlDoubleQuoted,
|
|
26
|
+
repoRootLogId,
|
|
26
27
|
resolveMissionControlPort,
|
|
28
|
+
resolveSnapshotRepoRoot,
|
|
27
29
|
sameRepoRoot,
|
|
28
|
-
repoRootLogId,
|
|
29
30
|
} from "./lib/guards.mjs";
|
|
30
31
|
|
|
31
32
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -50,18 +51,15 @@ function setPort(port) {
|
|
|
50
51
|
PORT = port;
|
|
51
52
|
URL = `http://${DISPLAY_HOST}:${PORT}/`;
|
|
52
53
|
DATA_URL = `http://${DISPLAY_HOST}:${PORT}/dashboard-data.json`;
|
|
53
|
-
LOG =
|
|
54
|
-
process.env.MISSION_CONTROL_LOG ||
|
|
55
|
-
`/tmp/mission-control-${repoRootLogId(ROOT)}.log`;
|
|
54
|
+
LOG = process.env.MISSION_CONTROL_LOG || `/tmp/mission-control-${repoRootLogId(ROOT)}.log`;
|
|
56
55
|
}
|
|
57
56
|
|
|
58
57
|
function probeHttp(url = URL) {
|
|
59
58
|
try {
|
|
60
|
-
const code = execFileSync(
|
|
61
|
-
"
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
).trim();
|
|
59
|
+
const code = execFileSync("curl", ["-sf", "-o", "/dev/null", "-w", "%{http_code}", url], {
|
|
60
|
+
encoding: "utf8",
|
|
61
|
+
timeout: 3000,
|
|
62
|
+
}).trim();
|
|
65
63
|
return code === "200";
|
|
66
64
|
} catch {
|
|
67
65
|
return false;
|
|
@@ -70,11 +68,10 @@ function probeHttp(url = URL) {
|
|
|
70
68
|
|
|
71
69
|
function listeningPids(port = PORT) {
|
|
72
70
|
try {
|
|
73
|
-
const out = execFileSync(
|
|
74
|
-
"
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
).trim();
|
|
71
|
+
const out = execFileSync("lsof", ["-nP", `-iTCP:${port}`, "-sTCP:LISTEN", "-t"], {
|
|
72
|
+
encoding: "utf8",
|
|
73
|
+
timeout: 3000,
|
|
74
|
+
}).trim();
|
|
78
75
|
return out ? out.split(/\n+/).filter(Boolean) : [];
|
|
79
76
|
} catch {
|
|
80
77
|
return [];
|
|
@@ -153,11 +150,12 @@ function detachStart() {
|
|
|
153
150
|
}
|
|
154
151
|
|
|
155
152
|
// macOS and other hosts without setsid: Perl double-fork + setsid().
|
|
156
|
-
|
|
157
|
-
const
|
|
158
|
-
const
|
|
159
|
-
const
|
|
160
|
-
const
|
|
153
|
+
// Escape @/$ so scoped package paths (node_modules/@scope/...) survive Perl qq.
|
|
154
|
+
const rootEsc = escapePerlDoubleQuoted(KIT_ROOT);
|
|
155
|
+
const serveEsc = escapePerlDoubleQuoted(SERVE);
|
|
156
|
+
const logEsc = escapePerlDoubleQuoted(LOG);
|
|
157
|
+
const portEsc = escapePerlDoubleQuoted(String(PORT));
|
|
158
|
+
const snapEsc = escapePerlDoubleQuoted(ROOT);
|
|
161
159
|
const perl = [
|
|
162
160
|
"use POSIX qw(setsid);",
|
|
163
161
|
"exit if fork;",
|
|
@@ -258,9 +256,7 @@ async function ensureServer() {
|
|
|
258
256
|
detachStart();
|
|
259
257
|
const ready = await waitReady();
|
|
260
258
|
if (!ready) {
|
|
261
|
-
console.error(
|
|
262
|
-
`Mission Control did not answer ${URL} within ${READY_TIMEOUT_MS}ms.`,
|
|
263
|
-
);
|
|
259
|
+
console.error(`Mission Control did not answer ${URL} within ${READY_TIMEOUT_MS}ms.`);
|
|
264
260
|
console.error(`Check the log: ${LOG}`);
|
|
265
261
|
process.exit(1);
|
|
266
262
|
}
|
|
@@ -280,9 +276,7 @@ async function main() {
|
|
|
280
276
|
"Opened in the default browser. In Cursor, Simple Browser or /dashboard also works.",
|
|
281
277
|
);
|
|
282
278
|
} else {
|
|
283
|
-
console.log(
|
|
284
|
-
"Open that URL in a browser (Cursor: Simple Browser, or run /dashboard in chat).",
|
|
285
|
-
);
|
|
279
|
+
console.log("Open that URL in a browser (Cursor: Simple Browser, or run /dashboard in chat).");
|
|
286
280
|
}
|
|
287
281
|
}
|
|
288
282
|
|