@monoes/monomindcli 2.12.0 → 2.14.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/.claude/skills/mastermind/SKILL.md +1 -15
- package/README.md +1 -1
- package/dist/src/commands/doctor-mcp-probe.d.ts +48 -0
- package/dist/src/commands/doctor-mcp-probe.d.ts.map +1 -0
- package/dist/src/commands/doctor-mcp-probe.js +205 -0
- package/dist/src/commands/doctor-mcp-probe.js.map +1 -0
- package/dist/src/commands/doctor-monoes-checks.d.ts +1 -0
- package/dist/src/commands/doctor-monoes-checks.d.ts.map +1 -1
- package/dist/src/commands/doctor-monoes-checks.js +96 -34
- package/dist/src/commands/doctor-monoes-checks.js.map +1 -1
- package/dist/src/commands/doctor-project-checks.d.ts +31 -1
- package/dist/src/commands/doctor-project-checks.d.ts.map +1 -1
- package/dist/src/commands/doctor-project-checks.js +109 -4
- package/dist/src/commands/doctor-project-checks.js.map +1 -1
- package/dist/src/commands/doctor.d.ts.map +1 -1
- package/dist/src/commands/doctor.js +21 -3
- package/dist/src/commands/doctor.js.map +1 -1
- package/dist/src/commands/init.d.ts.map +1 -1
- package/dist/src/commands/init.js +29 -0
- package/dist/src/commands/init.js.map +1 -1
- package/dist/src/commands/mcp.d.ts.map +1 -1
- package/dist/src/commands/mcp.js +4 -3
- package/dist/src/commands/mcp.js.map +1 -1
- package/dist/src/commands/org-observe.d.ts.map +1 -1
- package/dist/src/commands/org-observe.js +14 -0
- package/dist/src/commands/org-observe.js.map +1 -1
- package/dist/src/commands/org.d.ts.map +1 -1
- package/dist/src/commands/org.js +21 -4
- package/dist/src/commands/org.js.map +1 -1
- package/dist/src/commands/ui.d.ts.map +1 -1
- package/dist/src/commands/ui.js +4 -0
- package/dist/src/commands/ui.js.map +1 -1
- package/dist/src/init/claudemd-generator.d.ts.map +1 -1
- package/dist/src/init/claudemd-generator.js +2 -1
- package/dist/src/init/claudemd-generator.js.map +1 -1
- package/dist/src/init/copy-assets.d.ts.map +1 -1
- package/dist/src/init/copy-assets.js +56 -18
- package/dist/src/init/copy-assets.js.map +1 -1
- package/dist/src/init/executor.d.ts.map +1 -1
- package/dist/src/init/executor.js +9 -1
- package/dist/src/init/executor.js.map +1 -1
- package/dist/src/init/mcp-generator.d.ts.map +1 -1
- package/dist/src/init/mcp-generator.js +3 -3
- package/dist/src/init/mcp-generator.js.map +1 -1
- package/dist/src/init/never-commit.d.ts +39 -0
- package/dist/src/init/never-commit.d.ts.map +1 -0
- package/dist/src/init/never-commit.js +15 -0
- package/dist/src/init/never-commit.js.map +1 -0
- package/dist/src/init/shared.d.ts +74 -1
- package/dist/src/init/shared.d.ts.map +1 -1
- package/dist/src/init/shared.js +166 -0
- package/dist/src/init/shared.js.map +1 -1
- package/dist/src/init/types.d.ts +11 -0
- package/dist/src/init/types.d.ts.map +1 -1
- package/dist/src/init/types.js.map +1 -1
- package/dist/src/init/upgrade.d.ts.map +1 -1
- package/dist/src/init/upgrade.js +1 -0
- package/dist/src/init/upgrade.js.map +1 -1
- package/dist/src/init/write-kimicode.d.ts.map +1 -1
- package/dist/src/init/write-kimicode.js +13 -9
- package/dist/src/init/write-kimicode.js.map +1 -1
- package/dist/src/init/write-opencode.d.ts.map +1 -1
- package/dist/src/init/write-opencode.js +44 -2
- package/dist/src/init/write-opencode.js.map +1 -1
- package/dist/src/init/write-runtime-config.d.ts +2 -0
- package/dist/src/init/write-runtime-config.d.ts.map +1 -1
- package/dist/src/init/write-runtime-config.js +65 -38
- package/dist/src/init/write-runtime-config.js.map +1 -1
- package/dist/src/mastermind/manifest-data.d.ts.map +1 -1
- package/dist/src/mastermind/manifest-data.js +14 -0
- package/dist/src/mastermind/manifest-data.js.map +1 -1
- package/dist/src/mcp/monoes-mcp-entry.mjs +86 -0
- package/dist/src/mcp-tools/terminal-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/terminal-tools.js +52 -9
- package/dist/src/mcp-tools/terminal-tools.js.map +1 -1
- package/dist/src/memory/memory-bridge.d.ts +84 -3
- package/dist/src/memory/memory-bridge.d.ts.map +1 -1
- package/dist/src/memory/memory-bridge.js +215 -17
- package/dist/src/memory/memory-bridge.js.map +1 -1
- package/dist/src/orgrt/agent-runner.d.ts +38 -0
- package/dist/src/orgrt/agent-runner.d.ts.map +1 -1
- package/dist/src/orgrt/agent-runner.js +47 -0
- package/dist/src/orgrt/agent-runner.js.map +1 -1
- package/dist/src/orgrt/checkpoint.d.ts +12 -1
- package/dist/src/orgrt/checkpoint.d.ts.map +1 -1
- package/dist/src/orgrt/checkpoint.js +15 -6
- package/dist/src/orgrt/checkpoint.js.map +1 -1
- package/dist/src/orgrt/completion-gate.d.ts +74 -0
- package/dist/src/orgrt/completion-gate.d.ts.map +1 -1
- package/dist/src/orgrt/completion-gate.js +45 -0
- package/dist/src/orgrt/completion-gate.js.map +1 -1
- package/dist/src/orgrt/cost-tier.d.ts +144 -0
- package/dist/src/orgrt/cost-tier.d.ts.map +1 -0
- package/dist/src/orgrt/cost-tier.js +183 -0
- package/dist/src/orgrt/cost-tier.js.map +1 -0
- package/dist/src/orgrt/cross-org.d.ts.map +1 -1
- package/dist/src/orgrt/cross-org.js +14 -0
- package/dist/src/orgrt/cross-org.js.map +1 -1
- package/dist/src/orgrt/daemon.d.ts +12 -1
- package/dist/src/orgrt/daemon.d.ts.map +1 -1
- package/dist/src/orgrt/daemon.js +216 -49
- package/dist/src/orgrt/daemon.js.map +1 -1
- package/dist/src/orgrt/decisions.d.ts +14 -2
- package/dist/src/orgrt/decisions.d.ts.map +1 -1
- package/dist/src/orgrt/decisions.js +216 -10
- package/dist/src/orgrt/decisions.js.map +1 -1
- package/dist/src/orgrt/file-roots.d.ts +7 -0
- package/dist/src/orgrt/file-roots.d.ts.map +1 -1
- package/dist/src/orgrt/file-roots.js +38 -1
- package/dist/src/orgrt/file-roots.js.map +1 -1
- package/dist/src/orgrt/forwarder.d.ts.map +1 -1
- package/dist/src/orgrt/forwarder.js +16 -2
- package/dist/src/orgrt/forwarder.js.map +1 -1
- package/dist/src/orgrt/idle-deadline.d.ts +74 -1
- package/dist/src/orgrt/idle-deadline.d.ts.map +1 -1
- package/dist/src/orgrt/idle-deadline.js +52 -2
- package/dist/src/orgrt/idle-deadline.js.map +1 -1
- package/dist/src/orgrt/loadouts.d.ts +38 -0
- package/dist/src/orgrt/loadouts.d.ts.map +1 -0
- package/dist/src/orgrt/loadouts.js +129 -0
- package/dist/src/orgrt/loadouts.js.map +1 -0
- package/dist/src/orgrt/mailbox.d.ts +17 -1
- package/dist/src/orgrt/mailbox.d.ts.map +1 -1
- package/dist/src/orgrt/mailbox.js +50 -3
- package/dist/src/orgrt/mailbox.js.map +1 -1
- package/dist/src/orgrt/policy.d.ts +42 -2
- package/dist/src/orgrt/policy.d.ts.map +1 -1
- package/dist/src/orgrt/policy.js +53 -8
- package/dist/src/orgrt/policy.js.map +1 -1
- package/dist/src/orgrt/prompt-vars.d.ts +11 -0
- package/dist/src/orgrt/prompt-vars.d.ts.map +1 -0
- package/dist/src/orgrt/prompt-vars.js +49 -0
- package/dist/src/orgrt/prompt-vars.js.map +1 -0
- package/dist/src/orgrt/questions.d.ts +22 -1
- package/dist/src/orgrt/questions.d.ts.map +1 -1
- package/dist/src/orgrt/questions.js +24 -3
- package/dist/src/orgrt/questions.js.map +1 -1
- package/dist/src/orgrt/review-packet.d.ts +29 -0
- package/dist/src/orgrt/review-packet.d.ts.map +1 -0
- package/dist/src/orgrt/review-packet.js +67 -0
- package/dist/src/orgrt/review-packet.js.map +1 -0
- package/dist/src/orgrt/role-sandbox.d.ts.map +1 -1
- package/dist/src/orgrt/role-sandbox.js +16 -3
- package/dist/src/orgrt/role-sandbox.js.map +1 -1
- package/dist/src/orgrt/session-ledger.d.ts +61 -0
- package/dist/src/orgrt/session-ledger.d.ts.map +1 -0
- package/dist/src/orgrt/session-ledger.js +113 -0
- package/dist/src/orgrt/session-ledger.js.map +1 -0
- package/dist/src/orgrt/session.d.ts +45 -5
- package/dist/src/orgrt/session.d.ts.map +1 -1
- package/dist/src/orgrt/session.js +384 -27
- package/dist/src/orgrt/session.js.map +1 -1
- package/dist/src/orgrt/task-dag.d.ts +34 -1
- package/dist/src/orgrt/task-dag.d.ts.map +1 -1
- package/dist/src/orgrt/task-dag.js +58 -7
- package/dist/src/orgrt/task-dag.js.map +1 -1
- package/dist/src/orgrt/tool-spill.d.ts +94 -0
- package/dist/src/orgrt/tool-spill.d.ts.map +1 -0
- package/dist/src/orgrt/tool-spill.js +180 -0
- package/dist/src/orgrt/tool-spill.js.map +1 -0
- package/dist/src/orgrt/types.d.ts +138 -0
- package/dist/src/orgrt/types.d.ts.map +1 -1
- package/dist/src/orgrt/types.js +148 -0
- package/dist/src/orgrt/types.js.map +1 -1
- package/dist/src/platform-adapters/renderers/mcp.d.ts +20 -2
- package/dist/src/platform-adapters/renderers/mcp.d.ts.map +1 -1
- package/dist/src/platform-adapters/renderers/mcp.js +25 -5
- package/dist/src/platform-adapters/renderers/mcp.js.map +1 -1
- package/dist/src/platform-adapters/renderers/skills.d.ts +0 -6
- package/dist/src/platform-adapters/renderers/skills.d.ts.map +1 -1
- package/dist/src/platform-adapters/renderers/skills.js +12 -22
- package/dist/src/platform-adapters/renderers/skills.js.map +1 -1
- package/dist/src/protocol-capabilities.d.ts +2 -1
- package/dist/src/protocol-capabilities.d.ts.map +1 -1
- package/dist/src/protocol-capabilities.js +2 -1
- package/dist/src/protocol-capabilities.js.map +1 -1
- package/dist/src/ui/dashboard.html +457 -164
- package/dist/src/ui/org-hil.mjs +276 -0
- package/dist/src/ui/org-runtime.mjs +350 -0
- package/dist/src/ui/orgs.html +22 -4
- package/dist/src/ui/routes-monoes.mjs +28 -3
- package/dist/src/ui/routes-org.mjs +290 -541
- package/dist/src/ui/server.mjs +185 -19
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +7 -6
package/dist/src/ui/server.mjs
CHANGED
|
@@ -6,6 +6,10 @@ import os from 'node:os';
|
|
|
6
6
|
import path from 'node:path';
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
8
8
|
import zlib from 'node:zlib';
|
|
9
|
+
import {
|
|
10
|
+
detectDashboardTokenLeak,
|
|
11
|
+
formatDashboardTokenLeakWarning,
|
|
12
|
+
} from '../mcp/monoes-mcp-entry.mjs';
|
|
9
13
|
import {
|
|
10
14
|
collectAgents,
|
|
11
15
|
collectAll,
|
|
@@ -574,17 +578,34 @@ function _getGitMonomindDir(workDir) {
|
|
|
574
578
|
}
|
|
575
579
|
|
|
576
580
|
// Returns the monomind home directory for server-level data (capture, control.json, loops).
|
|
577
|
-
// Priority:
|
|
578
|
-
|
|
581
|
+
// Priority: an EXPLICIT project dir (--project-dir / CLAUDE_PROJECT_DIR) > MONOMIND_HOME env
|
|
582
|
+
// var > walk up from cwd finding .monomind/control.json, never past the enclosing git root >
|
|
583
|
+
// cwd fallback.
|
|
584
|
+
//
|
|
585
|
+
// #308: the walk used to run unconditionally, ignoring --project-dir, so it could resolve to
|
|
586
|
+
// any ancestor that happened to hold a control.json from an earlier dashboard run. Per-project
|
|
587
|
+
// state written under this home — the monoes.me connection file with its OAuth tokens,
|
|
588
|
+
// capture/, orgs/ — then landed in that ancestor instead of the project the dashboard is for.
|
|
589
|
+
//
|
|
590
|
+
// `projectDirExplicit` is what keeps the walk alive: both entry points always hand startServer
|
|
591
|
+
// a projectDir (`monomind ui` defaults the flag to cwd, the run-directly block below defaults
|
|
592
|
+
// CLAUDE_PROJECT_DIR to cwd), so short-circuiting on the dir alone would silently move the home
|
|
593
|
+
// of every dashboard started from a subdirectory of its project down into that subdirectory.
|
|
594
|
+
// Only a dir the caller actually named counts.
|
|
595
|
+
export function getMonomindHome(projectDir, projectDirExplicit) {
|
|
596
|
+
if (projectDirExplicit && projectDir) return path.resolve(projectDir);
|
|
579
597
|
if (process.env.MONOMIND_HOME) return path.resolve(process.env.MONOMIND_HOME);
|
|
580
598
|
let dir = process.cwd();
|
|
581
599
|
while (dir !== path.dirname(dir)) {
|
|
582
600
|
if (fs.existsSync(path.join(dir, '.monomind', 'control.json'))) return dir;
|
|
601
|
+
if (fs.existsSync(path.join(dir, '.git'))) break;
|
|
583
602
|
dir = path.dirname(dir);
|
|
584
603
|
}
|
|
585
604
|
return process.cwd();
|
|
586
605
|
}
|
|
587
|
-
|
|
606
|
+
// Re-resolved from the options at startServer() time (the projectDir only exists then); the
|
|
607
|
+
// call sites below all read it from inside startServer, after that assignment.
|
|
608
|
+
let MONOMIND_HOME = getMonomindHome();
|
|
588
609
|
|
|
589
610
|
// Resolve an org's project directory by searching across known projects.
|
|
590
611
|
// Returns the first project dir where {dir}/.monomind/orgs/{orgName}.json exists, or null.
|
|
@@ -878,6 +899,9 @@ function bindServer(server, port) {
|
|
|
878
899
|
* @param {object} [options]
|
|
879
900
|
* @param {number} [options.port=4242] - Preferred port. Tries up to port+10 on collision.
|
|
880
901
|
* @param {string} [options.projectDir] - Root of the project to collect data from.
|
|
902
|
+
* @param {boolean} [options.projectDirExplicit=false] - True when projectDir was named by the
|
|
903
|
+
* caller (--project-dir / CLAUDE_PROJECT_DIR) rather than defaulted to cwd; only then does it
|
|
904
|
+
* set the monomind home (see getMonomindHome, #308).
|
|
881
905
|
* @param {boolean} [options.openBrowser=true] - Whether to open the dashboard in the default browser.
|
|
882
906
|
* @returns {Promise<{port: number, url: string, server: http.Server}>}
|
|
883
907
|
*/
|
|
@@ -973,9 +997,24 @@ function _resolveSlugToPathUncached(slug, projDir) {
|
|
|
973
997
|
export async function startServer({
|
|
974
998
|
port = 4242,
|
|
975
999
|
projectDir,
|
|
1000
|
+
projectDirExplicit = false,
|
|
976
1001
|
openBrowser = true,
|
|
977
1002
|
allowedHosts,
|
|
978
1003
|
} = {}) {
|
|
1004
|
+
// #308: resolve the home now that the caller's project dir is in hand.
|
|
1005
|
+
MONOMIND_HOME = getMonomindHome(projectDir, projectDirExplicit);
|
|
1006
|
+
// i-052 commit 3: warn on every start, not only at `init` (executor.ts).
|
|
1007
|
+
// The dashboard is what WRITES dashboard-token — a user who never
|
|
1008
|
+
// re-runs `init` after the file got committed (e.g. before commits 1-2
|
|
1009
|
+
// shipped `.gitignore` coverage) would otherwise never see this warning
|
|
1010
|
+
// at all. Same "name the file, never the value" contract as the
|
|
1011
|
+
// monoes.me leak warning; best-effort (never blocks startup on a git
|
|
1012
|
+
// failure — detectDashboardTokenLeak already treats git-unavailable as
|
|
1013
|
+
// "not a leak signal", not an error).
|
|
1014
|
+
const dashboardTokenStartupWarning = formatDashboardTokenLeakWarning(
|
|
1015
|
+
await detectDashboardTokenLeak(projectDir || process.cwd()),
|
|
1016
|
+
);
|
|
1017
|
+
if (dashboardTokenStartupWarning) console.error(dashboardTokenStartupWarning);
|
|
979
1018
|
// Extra Host names accepted beyond loopback (see isAllowedHost above).
|
|
980
1019
|
const _allowedHosts = resolveAllowedHosts(allowedHosts);
|
|
981
1020
|
// ── Security: per-process auth credential for mutating (non-GET) requests ─
|
|
@@ -991,6 +1030,17 @@ export async function startServer({
|
|
|
991
1030
|
// silently degrading per-prompt Second Brain injection to keyword fallback.
|
|
992
1031
|
// So the write is deferred to bind time and gated: primary instances write
|
|
993
1032
|
// `dashboard-token`; secondaries write `dashboard-token-<port>` instead.
|
|
1033
|
+
// i-052 commit 4: the path THIS process actually wrote (primary
|
|
1034
|
+
// `dashboard-token` xor its own `dashboard-token-<port>` secondary) — set
|
|
1035
|
+
// once, at the bottom of writeDashboardToken(), and read by shutdown()
|
|
1036
|
+
// below for best-effort cleanup on a clean exit. `mode` is create-only
|
|
1037
|
+
// on `fs.writeFileSync` (POSIX `open(2)` ignores it when the file
|
|
1038
|
+
// exists) — measured on this machine: 2 of 11 token files were 644
|
|
1039
|
+
// despite every write site passing `{ mode: 0o600 }`, because a prior
|
|
1040
|
+
// run had already created them. `fs.chmodSync` after every write, here
|
|
1041
|
+
// and in propagateDashboardToken() below, enforces the mode on rewrite
|
|
1042
|
+
// too, not just on first creation.
|
|
1043
|
+
let _dashboardTokenFilePath = null;
|
|
994
1044
|
async function writeDashboardToken(actualPort) {
|
|
995
1045
|
try {
|
|
996
1046
|
actualPort = Number(actualPort);
|
|
@@ -1032,14 +1082,17 @@ export async function startServer({
|
|
|
1032
1082
|
} catch (_) {
|
|
1033
1083
|
/* no readable control.json — treat as primary */
|
|
1034
1084
|
}
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
{ mode: 0o600 },
|
|
1085
|
+
const tokenPath = path.join(
|
|
1086
|
+
authFileDir,
|
|
1087
|
+
primary ? 'dashboard-token' : `dashboard-token-${actualPort}`,
|
|
1039
1088
|
);
|
|
1089
|
+
fs.writeFileSync(tokenPath, dashboardAuthValue, { mode: 0o600 });
|
|
1090
|
+
fs.chmodSync(tokenPath, 0o600); // enforce on rewrite — {mode} above is create-only
|
|
1091
|
+
_dashboardTokenFilePath = tokenPath;
|
|
1040
1092
|
// Sweep stale secondary tokens (dead scratch/test instances) so they
|
|
1041
1093
|
// don't accumulate as orphaned credential files.
|
|
1042
|
-
//
|
|
1094
|
+
// Age-based sweep — the belt-and-braces backstop for a dirty death
|
|
1095
|
+
// (kill -9) that never reaches shutdown()'s cleanup below.
|
|
1043
1096
|
try {
|
|
1044
1097
|
const weekMs = 7 * 24 * 3600 * 1000;
|
|
1045
1098
|
for (const f of fs.readdirSync(authFileDir)) {
|
|
@@ -1050,9 +1103,45 @@ export async function startServer({
|
|
|
1050
1103
|
} catch (_) {}
|
|
1051
1104
|
} catch (_) {}
|
|
1052
1105
|
}
|
|
1106
|
+
// i-052 commit 4: this is the code path that actually planted
|
|
1107
|
+
// dashboard-token in the incident's other repos — a paired project may
|
|
1108
|
+
// never have run `init` (so it never got commits 1-2's `.gitignore`
|
|
1109
|
+
// coverage) yet still receives a live token here on every restart.
|
|
1110
|
+
// Ensures the SAME coverage `init` would have given it before writing
|
|
1111
|
+
// the token, content-guarded exactly like write-runtime-config.ts's
|
|
1112
|
+
// append (creates the file if absent; appends the one missing line if
|
|
1113
|
+
// present; no-ops if already covered) — duplicated locally rather than
|
|
1114
|
+
// imported because this file ships as plain ESM with no build step and
|
|
1115
|
+
// cannot import compiled TypeScript (see monoes-mcp-entry.mjs's own doc
|
|
1116
|
+
// comment for the identical constraint). Best-effort: a coverage-ensure
|
|
1117
|
+
// failure must never block token delivery, which is the more urgent
|
|
1118
|
+
// property (a stale token 401s every cross-project caller).
|
|
1119
|
+
function ensureDashboardTokenGitignoreCoverage(kpMonoDir) {
|
|
1120
|
+
try {
|
|
1121
|
+
const gitignorePath = path.join(kpMonoDir, '.gitignore');
|
|
1122
|
+
const reason =
|
|
1123
|
+
'live monomind dashboard credential (i-052) — grants cross-project file reads and agent execution; rewritten on every dashboard restart, so an already-tracked path re-commits a fresh live token on every restart';
|
|
1124
|
+
if (!fs.existsSync(gitignorePath)) {
|
|
1125
|
+
fs.mkdirSync(kpMonoDir, { recursive: true });
|
|
1126
|
+
fs.writeFileSync(gitignorePath, `# ${reason}\ndashboard-token\n`);
|
|
1127
|
+
return;
|
|
1128
|
+
}
|
|
1129
|
+
const existing = fs.readFileSync(gitignorePath, 'utf8');
|
|
1130
|
+
const existingLines = new Set(existing.split('\n').map((l) => l.trim()));
|
|
1131
|
+
if (!existingLines.has('dashboard-token')) {
|
|
1132
|
+
fs.writeFileSync(gitignorePath, `${existing.trimEnd()}\n# ${reason}\ndashboard-token\n`);
|
|
1133
|
+
}
|
|
1134
|
+
} catch (_) {
|
|
1135
|
+
/* best effort — see doc comment above */
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1053
1139
|
// Propagate the fresh token to every known project whose control.json points
|
|
1054
1140
|
// at this server — otherwise each restart silently orphans cross-project
|
|
1055
|
-
// callers (their curls/CLI reads a stale token and 401s forever).
|
|
1141
|
+
// callers (their curls/CLI reads a stale token and 401s forever). The
|
|
1142
|
+
// population is `data/known-projects.json`'s own content, never a
|
|
1143
|
+
// directory glob (i-052 owner correction: the incident's affected-repo
|
|
1144
|
+
// count included projects outside any assumed directory layout).
|
|
1056
1145
|
// Called after bind so the match uses the ACTUAL bound port.
|
|
1057
1146
|
function propagateDashboardToken(actualPort) {
|
|
1058
1147
|
try {
|
|
@@ -1064,9 +1153,10 @@ export async function startServer({
|
|
|
1064
1153
|
const _kpCtl = JSON.parse(fs.readFileSync(path.join(_kpMono, 'control.json'), 'utf8'));
|
|
1065
1154
|
// Only pair projects that direct their traffic to this server's port.
|
|
1066
1155
|
if (_kpCtl && String(_kpCtl.url || '').includes(`:${actualPort}`)) {
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
});
|
|
1156
|
+
ensureDashboardTokenGitignoreCoverage(_kpMono);
|
|
1157
|
+
const _kpTokenPath = path.join(_kpMono, 'dashboard-token');
|
|
1158
|
+
fs.writeFileSync(_kpTokenPath, dashboardAuthValue, { mode: 0o600 });
|
|
1159
|
+
fs.chmodSync(_kpTokenPath, 0o600); // enforce on rewrite — {mode} above is create-only
|
|
1070
1160
|
}
|
|
1071
1161
|
} catch (_) {
|
|
1072
1162
|
/* project missing/unreadable — skip */
|
|
@@ -1342,10 +1432,26 @@ export async function startServer({
|
|
|
1342
1432
|
} catch (_e) {}
|
|
1343
1433
|
if (!_st.agents) _st.agents = {};
|
|
1344
1434
|
const _ex = _st.agents[_arole] || {};
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
//
|
|
1348
|
-
//
|
|
1435
|
+
// ADR-O001 D1: orgrt's 'org:usage' now carries the four billable
|
|
1436
|
+
// quantities separately (tokens_in / tokens_out / cache_read /
|
|
1437
|
+
// cache_creation) alongside the `tokens` total, so the per-role rows
|
|
1438
|
+
// record real values instead of the 0s they used to persist.
|
|
1439
|
+
const _tokensIn =
|
|
1440
|
+
event.type === 'agent:usage'
|
|
1441
|
+
? Number(event.tokens_in) || 0
|
|
1442
|
+
: Number(event.data?.tokens_in) || 0;
|
|
1443
|
+
const _tokensOut =
|
|
1444
|
+
event.type === 'agent:usage'
|
|
1445
|
+
? Number(event.tokens_out) || 0
|
|
1446
|
+
: Number(event.data?.tokens_out) || 0;
|
|
1447
|
+
const _cacheRead =
|
|
1448
|
+
event.type === 'org:usage' ? Number(event.data?.cache_read) || 0 : 0;
|
|
1449
|
+
const _cacheCreation =
|
|
1450
|
+
event.type === 'org:usage' ? Number(event.data?.cache_creation) || 0 : 0;
|
|
1451
|
+
// 'org:usage' carries the billable total in data.tokens (cache
|
|
1452
|
+
// reads and writes included) — counted toward tokens_used, and NOT
|
|
1453
|
+
// re-added from the split fields, which are that same total broken
|
|
1454
|
+
// down rather than an extra amount.
|
|
1349
1455
|
const _tokensTotal =
|
|
1350
1456
|
event.type === 'org:usage' ? Number(event.data?.tokens) || 0 : 0;
|
|
1351
1457
|
const _costUsd =
|
|
@@ -1356,7 +1462,11 @@ export async function startServer({
|
|
|
1356
1462
|
..._ex,
|
|
1357
1463
|
tokens_in: (_ex.tokens_in || 0) + _tokensIn,
|
|
1358
1464
|
tokens_out: (_ex.tokens_out || 0) + _tokensOut,
|
|
1359
|
-
|
|
1465
|
+
cache_read_tokens: (_ex.cache_read_tokens || 0) + _cacheRead,
|
|
1466
|
+
cache_creation_tokens: (_ex.cache_creation_tokens || 0) + _cacheCreation,
|
|
1467
|
+
tokens_used:
|
|
1468
|
+
(_ex.tokens_used || 0) +
|
|
1469
|
+
(event.type === 'org:usage' ? _tokensTotal : _tokensIn + _tokensOut),
|
|
1360
1470
|
total_cost_usd: (_ex.total_cost_usd || 0) + _costUsd,
|
|
1361
1471
|
lastUpdated: event.ts,
|
|
1362
1472
|
};
|
|
@@ -1771,7 +1881,16 @@ export async function startServer({
|
|
|
1771
1881
|
|
|
1772
1882
|
const _checkAuth = (req) => {
|
|
1773
1883
|
let _suppliedAuth = req.headers['x-monomind-token'] || '';
|
|
1774
|
-
|
|
1884
|
+
// i-073: the query-token fallback is for EventSource (which cannot send
|
|
1885
|
+
// headers) and other subresource GETs ONLY. It used to apply
|
|
1886
|
+
// unconditionally, which meant a mutation needed only the header OR the
|
|
1887
|
+
// query param — and the header is what forces a CORS preflight (a
|
|
1888
|
+
// mutation becomes a "simple request" without it). A plain
|
|
1889
|
+
// `<form method=POST action="...?token=LEAKED">` has no header to omit,
|
|
1890
|
+
// so accepting the query param on POST turned every mutation into a
|
|
1891
|
+
// CSRF target reachable with no preflight and no JS. Restoring the
|
|
1892
|
+
// unconditional fallback here reopens that hole — see i-073.
|
|
1893
|
+
if (!_suppliedAuth && (req.method === 'GET' || req.method === 'HEAD')) {
|
|
1775
1894
|
try {
|
|
1776
1895
|
_suppliedAuth = new URL(req.url, 'http://localhost').searchParams.get('token') || '';
|
|
1777
1896
|
} catch (_) {}
|
|
@@ -1855,6 +1974,32 @@ export async function startServer({
|
|
|
1855
1974
|
_sendUnauthorized(res, corsOrigin);
|
|
1856
1975
|
return;
|
|
1857
1976
|
}
|
|
1977
|
+
// ── Security: Sec-Fetch-Site CSRF guard on mutations (i-073) ──────────
|
|
1978
|
+
// The token check alone cannot stop CSRF if the token ever leaks (a
|
|
1979
|
+
// browser cache, a proxy log, `ps` on a shared host). Reject when the
|
|
1980
|
+
// header is PRESENT and not `same-origin` — deliberately NOT "reject
|
|
1981
|
+
// cross-site": `Sec-Fetch-Site: same-site` covers a *different port on
|
|
1982
|
+
// the same host*, i.e. a page served by another local server (another
|
|
1983
|
+
// monomind dashboard among them — o-04 shows there can be eleven).
|
|
1984
|
+
// Rejecting anything that isn't `same-origin` closes that case too, at
|
|
1985
|
+
// no cost to the dashboard's own pages.
|
|
1986
|
+
// Header ABSENT must stay allowed, or every first-party non-browser
|
|
1987
|
+
// caller breaks: control-start.cjs, event-logger.cjs,
|
|
1988
|
+
// capture-handler.cjs, route-handler.cjs, forwarder.ts and the
|
|
1989
|
+
// `/mastermind:*` commands all POST with the token header and send no
|
|
1990
|
+
// Sec-Fetch-* at all. A browser too old to send Sec-Fetch-Site is
|
|
1991
|
+
// treated the same as a non-browser client — it still needs the token.
|
|
1992
|
+
if (req.method !== 'GET' && req.method !== 'HEAD') {
|
|
1993
|
+
const _secFetchSite = req.headers['sec-fetch-site'];
|
|
1994
|
+
if (_secFetchSite && _secFetchSite !== 'same-origin') {
|
|
1995
|
+
res.writeHead(403, {
|
|
1996
|
+
'Content-Type': 'application/json',
|
|
1997
|
+
...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}),
|
|
1998
|
+
});
|
|
1999
|
+
res.end(JSON.stringify({ error: 'Forbidden: cross-origin mutation' }));
|
|
2000
|
+
return;
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
1858
2003
|
}
|
|
1859
2004
|
|
|
1860
2005
|
// ------------------------------------------------------------------ GET /
|
|
@@ -5911,6 +6056,21 @@ export async function startServer({
|
|
|
5911
6056
|
// Close all SSE connections
|
|
5912
6057
|
closeSseClients();
|
|
5913
6058
|
|
|
6059
|
+
// i-052 commit 4: best-effort cleanup of the credential THIS process
|
|
6060
|
+
// wrote (primary `dashboard-token`, or this process's own
|
|
6061
|
+
// `dashboard-token-<port>` secondary) — belt-and-braces only.
|
|
6062
|
+
// writeDashboardToken's age-based sweep is the actual backstop for a
|
|
6063
|
+
// dirty death: SIGKILL never reaches this handler at all, and a
|
|
6064
|
+
// failed unlink here (permissions, already-gone) must not block the
|
|
6065
|
+
// rest of shutdown.
|
|
6066
|
+
if (_dashboardTokenFilePath) {
|
|
6067
|
+
try {
|
|
6068
|
+
fs.unlinkSync(_dashboardTokenFilePath);
|
|
6069
|
+
} catch (_) {
|
|
6070
|
+
/* already gone, or unremovable — the age sweep is the backstop */
|
|
6071
|
+
}
|
|
6072
|
+
}
|
|
6073
|
+
|
|
5914
6074
|
// Drain in-flight JSONL appends before closing (prevents truncated writes on fast SIGTERM)
|
|
5915
6075
|
Promise.all([..._writeQueue.values()])
|
|
5916
6076
|
.catch(() => {})
|
|
@@ -5955,8 +6115,14 @@ export function getServerStatus() {
|
|
|
5955
6115
|
const _isMain = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
5956
6116
|
if (_isMain) {
|
|
5957
6117
|
const _port = parseInt(process.argv[2] || process.env.CONTROL_PORT || '4242', 10);
|
|
6118
|
+
// CLAUDE_PROJECT_DIR names a project; the bare cwd fallback does not (#308).
|
|
5958
6119
|
const _dir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
5959
|
-
startServer({
|
|
6120
|
+
startServer({
|
|
6121
|
+
port: _port,
|
|
6122
|
+
openBrowser: false,
|
|
6123
|
+
projectDir: _dir,
|
|
6124
|
+
projectDirExplicit: Boolean(process.env.CLAUDE_PROJECT_DIR),
|
|
6125
|
+
}).catch((err) => {
|
|
5960
6126
|
process.stderr.write(`[server] failed to start: ${err.message}\n`);
|
|
5961
6127
|
process.exit(1);
|
|
5962
6128
|
});
|