@jossuealcala/madre 0.2.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/CHANGELOG.md +38 -0
- package/LICENSE +202 -0
- package/NOTICE +5 -0
- package/README.md +281 -0
- package/bin/madre.mjs +134 -0
- package/docs/madre-banner.svg +25 -0
- package/package.json +63 -0
- package/public/app.js +4031 -0
- package/public/brands.js +59 -0
- package/public/index.html +231 -0
- package/public/styles.css +976 -0
- package/public/troubleshooting.js +460 -0
- package/src/adapters/claude.mjs +105 -0
- package/src/adapters/codex.mjs +84 -0
- package/src/adapters/gemini.mjs +335 -0
- package/src/adapters/opencode.mjs +148 -0
- package/src/adapters/process.mjs +132 -0
- package/src/ashcode.mjs +64 -0
- package/src/auth-probe.mjs +145 -0
- package/src/capabilities.mjs +126 -0
- package/src/checkpoint.mjs +106 -0
- package/src/cli-args.mjs +38 -0
- package/src/commands.mjs +98 -0
- package/src/config.mjs +59 -0
- package/src/conversation-context.mjs +59 -0
- package/src/directives.mjs +70 -0
- package/src/distiller.mjs +78 -0
- package/src/embeddings.mjs +80 -0
- package/src/event-store.mjs +152 -0
- package/src/extensions.mjs +263 -0
- package/src/files.mjs +177 -0
- package/src/image-studio.mjs +25 -0
- package/src/lease.mjs +83 -0
- package/src/mcp/image-server.mjs +169 -0
- package/src/mcp/memory-server.mjs +221 -0
- package/src/memory-tools.mjs +34 -0
- package/src/memory.mjs +543 -0
- package/src/models.mjs +78 -0
- package/src/mother.mjs +163 -0
- package/src/quota-monitor.mjs +119 -0
- package/src/quota-sources.mjs +161 -0
- package/src/room.mjs +1159 -0
- package/src/router.mjs +13 -0
- package/src/runtime-detection.mjs +85 -0
- package/src/server.mjs +693 -0
- package/src/setup.mjs +192 -0
- package/src/usage-sentinel.mjs +65 -0
package/bin/madre.mjs
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
|
+
import { stat } from 'node:fs/promises';
|
|
5
|
+
import { startPulse } from '../src/server.mjs';
|
|
6
|
+
import { detectAgents } from '../src/runtime-detection.mjs';
|
|
7
|
+
import { probeAll } from '../src/auth-probe.mjs';
|
|
8
|
+
import { applyConfigToEnv, loadConfig } from '../src/config.mjs';
|
|
9
|
+
import { isOnline, runSetup } from '../src/setup.mjs';
|
|
10
|
+
import { parseArgs } from '../src/cli-args.mjs';
|
|
11
|
+
|
|
12
|
+
const cli = parseArgs(process.argv.slice(2));
|
|
13
|
+
const { command } = cli;
|
|
14
|
+
const option = (name, fallback) => cli.option(name.replace(/^--/, ''), fallback);
|
|
15
|
+
const has = (name) => cli.has(name.replace(/^--/, ''));
|
|
16
|
+
|
|
17
|
+
// With an explicit --port a busy port is an error the user asked for. Without
|
|
18
|
+
// one, MOTHER walks up to the next free port so a second room just opens.
|
|
19
|
+
async function openRoom(options) {
|
|
20
|
+
const explicit = cli.explicit('port');
|
|
21
|
+
let port = options.port;
|
|
22
|
+
for (let attempt = 0; attempt < 20; attempt += 1) {
|
|
23
|
+
try {
|
|
24
|
+
return await startPulse({ ...options, port });
|
|
25
|
+
} catch (error) {
|
|
26
|
+
if (error.code !== 'EADDRINUSE') throw error;
|
|
27
|
+
if (explicit) {
|
|
28
|
+
console.error(`\n MOTHER › port ${port} is already in use. Another MADRE may be open there; try --port ${port + 1} or omit --port to pick one automatically.\n`);
|
|
29
|
+
process.exit(2);
|
|
30
|
+
}
|
|
31
|
+
console.error(` MOTHER › port ${port} in use, trying ${port + 1}.`);
|
|
32
|
+
port += 1;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
console.error(`\n MOTHER › no free port between ${options.port} and ${port}. Pass --port explicitly.\n`);
|
|
36
|
+
process.exit(2);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const projectRoot = resolve(option('--project', process.cwd()));
|
|
40
|
+
const [nodeMajor, nodeMinor] = process.versions.node.split('.').map(Number);
|
|
41
|
+
if (nodeMajor < 22 || (nodeMajor === 22 && nodeMinor < 5)) {
|
|
42
|
+
console.error(`\n MOTHER › MADRE needs Node 22.5 or newer (found ${process.version}): the room's memory runs on node:sqlite.\n`);
|
|
43
|
+
process.exit(2);
|
|
44
|
+
}
|
|
45
|
+
const stateRoot = process.env.PULSE_HOME;
|
|
46
|
+
if (command !== 'help') {
|
|
47
|
+
const stats = await stat(projectRoot).catch(() => null);
|
|
48
|
+
if (!stats?.isDirectory()) {
|
|
49
|
+
console.error(`\n MOTHER › project folder not found: ${projectRoot}\n Pass an existing directory with --project, or run MADRE from inside the project.\n`);
|
|
50
|
+
process.exit(2);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
applyConfigToEnv(await loadConfig(stateRoot));
|
|
54
|
+
|
|
55
|
+
if (command === 'doctor' && (has('--catalog') || has('--conditions'))) {
|
|
56
|
+
// The same knowledge base MU/TH/UR uses in the room, printed for this platform.
|
|
57
|
+
const { CONDITIONS, fixesFor, searchConditions, PLATFORMS } = await import('../public/troubleshooting.js');
|
|
58
|
+
const query = option('--catalog', '') || option('--conditions', '') || '';
|
|
59
|
+
const platform = PLATFORMS[process.platform] ? process.platform : 'linux';
|
|
60
|
+
const list = query && !query.startsWith('--') ? searchConditions(query) : CONDITIONS;
|
|
61
|
+
console.log(`\nMU/TH/UR · KNOWN CONDITIONS · ${list.length} OF ${CONDITIONS.length} · ${PLATFORMS[platform].label.toUpperCase()} / ${PLATFORMS[platform].shell.toUpperCase()}\n`);
|
|
62
|
+
for (const condition of list) {
|
|
63
|
+
console.log(`▌ ${condition.title}`);
|
|
64
|
+
console.log(` ${condition.id} · ${condition.severity}${condition.agent ? ` · @${condition.agent}` : ''}`);
|
|
65
|
+
console.log(` ${condition.diagnosis}`);
|
|
66
|
+
console.log(` → ${condition.remedy}`);
|
|
67
|
+
const fixes = fixesFor(condition, platform);
|
|
68
|
+
if (fixes.length) console.log(fixes.map((line) => ` ${line}`).join('\n'));
|
|
69
|
+
if (condition.perAgent) for (const [id, byPlatform] of Object.entries(condition.perAgent)) { const lines = byPlatform[platform] ?? byPlatform.darwin ?? []; if (lines.length) console.log(` # @${id}\n${lines.map((line) => ` ${line}`).join('\n')}`); }
|
|
70
|
+
console.log('');
|
|
71
|
+
}
|
|
72
|
+
process.exitCode = 0;
|
|
73
|
+
} else if (command === 'doctor') {
|
|
74
|
+
const agents = await detectAgents();
|
|
75
|
+
const probes = await probeAll(agents);
|
|
76
|
+
const report = agents.map((agent) => ({ ...agent, session: probes[agent.id] }));
|
|
77
|
+
const result = {
|
|
78
|
+
ok: report.some((agent) => isOnline(agent, agent.session)),
|
|
79
|
+
node: process.version,
|
|
80
|
+
project: projectRoot,
|
|
81
|
+
agents: report,
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
if (has('--json')) {
|
|
85
|
+
console.log(JSON.stringify(result, null, 2));
|
|
86
|
+
} else {
|
|
87
|
+
console.log('\nMADRE doctor\n');
|
|
88
|
+
for (const agent of report) {
|
|
89
|
+
const mark = agent.ready ? 'ready' : agent.detected ? 'detected' : 'not found';
|
|
90
|
+
const session = agent.detected ? ` · ${agent.session.state}${agent.session.detail ? ` (${agent.session.detail})` : ''}` : '';
|
|
91
|
+
console.log(` ${agent.label.padEnd(10)} ${mark}${agent.version ? ` · ${agent.version}` : ''}${session}`);
|
|
92
|
+
}
|
|
93
|
+
console.log(`\n Project ${result.project}`);
|
|
94
|
+
console.log(result.ok ? '\nReady to start. Known conditions and fixes: `madre doctor --catalog [query]`.\n' : '\nNo agent is online. Run `madre setup`. Known conditions and fixes: `madre doctor --catalog`.\n');
|
|
95
|
+
}
|
|
96
|
+
process.exitCode = result.ok ? 0 : 1;
|
|
97
|
+
} else if (command === 'setup') {
|
|
98
|
+
const { action, online } = await runSetup({ projectRoot, stateRoot });
|
|
99
|
+
if (action === 'start') {
|
|
100
|
+
await openRoom({ port: Number(option('--port', '4317')), projectRoot, openBrowser: !has('--no-open') });
|
|
101
|
+
} else {
|
|
102
|
+
process.exitCode = action === 'report' && !online ? 1 : 0;
|
|
103
|
+
}
|
|
104
|
+
} else if (command === 'start') {
|
|
105
|
+
const port = Number(option('--port', '4317'));
|
|
106
|
+
const noOpen = has('--no-open');
|
|
107
|
+
// First contact: in a terminal with nobody online, MOTHER walks the user
|
|
108
|
+
// through configuring an agent before the room opens.
|
|
109
|
+
if (process.stdin.isTTY && process.stdout.isTTY && !has('--no-setup')) {
|
|
110
|
+
const agents = await detectAgents();
|
|
111
|
+
const probes = await probeAll(agents);
|
|
112
|
+
if (!agents.some((agent) => isOnline(agent, probes[agent.id]))) {
|
|
113
|
+
const { action } = await runSetup({ projectRoot, stateRoot });
|
|
114
|
+
if (action !== 'start') process.exit(0);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
await openRoom({ port, projectRoot, openBrowser: !noOpen });
|
|
118
|
+
} else if (command === 'help') {
|
|
119
|
+
console.log(`MADRE
|
|
120
|
+
|
|
121
|
+
madre start [--project PATH] [--port 4317] [--no-open] [--no-setup]
|
|
122
|
+
Without --port, a busy 4317 falls through to the next free port.
|
|
123
|
+
madre setup [--project PATH] [--port 4317] [--no-open]
|
|
124
|
+
madre doctor [--project PATH] [--json]
|
|
125
|
+
|
|
126
|
+
start Opens the project room. With nobody online, runs setup first.
|
|
127
|
+
setup Detects the agents on this computer, shows who has a session,
|
|
128
|
+
and runs each agent's own sign-in from here.
|
|
129
|
+
doctor Prints the same diagnosis without the wizard.
|
|
130
|
+
`);
|
|
131
|
+
} else {
|
|
132
|
+
console.error(`Unknown command: ${command}`);
|
|
133
|
+
process.exitCode = 1;
|
|
134
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="300" viewBox="0 0 1200 300" role="img" aria-label="MADRE · MU/TH/UR 6000 · INTERFACE 2037">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="bg" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#050605"/><stop offset="1" stop-color="#0b0d0b"/></linearGradient>
|
|
4
|
+
<pattern id="scan" width="4" height="4" patternUnits="userSpaceOnUse"><rect width="4" height="1" fill="#9bff66" fill-opacity="0.06"/></pattern>
|
|
5
|
+
<filter id="glow" x="-20%" y="-50%" width="140%" height="200%"><feGaussianBlur stdDeviation="4" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge></filter>
|
|
6
|
+
<filter id="glowSoft" x="-20%" y="-50%" width="140%" height="200%"><feGaussianBlur stdDeviation="10"/></filter>
|
|
7
|
+
</defs>
|
|
8
|
+
<rect width="1200" height="300" fill="url(#bg)"/>
|
|
9
|
+
<rect width="1200" height="300" fill="url(#scan)"/>
|
|
10
|
+
<rect x="0.5" y="0.5" width="1199" height="299" fill="none" stroke="#9bff66" stroke-opacity="0.18"/>
|
|
11
|
+
<g transform="translate(90 150)" stroke="#9bff66" fill="none" stroke-width="5" stroke-linecap="round" stroke-linejoin="round">
|
|
12
|
+
<path d="M0 0 H140 L176 0 L208 -70 L248 70 L284 0 L312 0 L330 -36 L348 0 H460" stroke-opacity="0.25" filter="url(#glowSoft)" stroke-width="14"/>
|
|
13
|
+
<path d="M0 0 H140 L176 0 L208 -70 L248 70 L284 0 L312 0 L330 -36 L348 0 H460" filter="url(#glow)"/>
|
|
14
|
+
<circle cx="520" cy="0" r="12" fill="#9bff66" stroke="none" filter="url(#glow)"/>
|
|
15
|
+
</g>
|
|
16
|
+
<g font-family="SF Mono, Menlo, Consolas, monospace" fill="#9bff66">
|
|
17
|
+
<text x="690" y="150" font-size="84" font-weight="700" letter-spacing="14" filter="url(#glow)">MADRE</text>
|
|
18
|
+
<text x="692" y="194" font-size="15" letter-spacing="6" fill-opacity="0.75">MU/TH/UR 6000 · INTERFACE 2037</text>
|
|
19
|
+
<text x="692" y="224" font-size="13" letter-spacing="3" fill-opacity="0.55">ONE ROOM · FOUR AGENTS · ONE MEMORY</text>
|
|
20
|
+
</g>
|
|
21
|
+
<g font-family="SF Mono, Menlo, Consolas, monospace" font-size="11" letter-spacing="4" fill="#9bff66" fill-opacity="0.45">
|
|
22
|
+
<text x="90" y="262">CODEX · CLAUDE CODE · GEMINI CLI · OPENCODE</text>
|
|
23
|
+
<text x="1110" y="262" text-anchor="end">PULSE CHANNEL · LOCAL FIRST</text>
|
|
24
|
+
</g>
|
|
25
|
+
</svg>
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@jossuealcala/madre",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "MADRE: one local room where the AI coding agents already installed on your machine (Codex, Claude Code, Gemini CLI, OpenCode) work on a project together over the PULSE channel: read-only by default, per-message permission modes up to a checkpointed CONTROL, a shared memory every agent recalls and queries, and NOSTROMO to browse it.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"ai-agents",
|
|
7
|
+
"codex",
|
|
8
|
+
"claude-code",
|
|
9
|
+
"gemini-cli",
|
|
10
|
+
"opencode",
|
|
11
|
+
"multi-agent",
|
|
12
|
+
"local-first",
|
|
13
|
+
"handoff",
|
|
14
|
+
"madre",
|
|
15
|
+
"mother",
|
|
16
|
+
"mu-th-ur",
|
|
17
|
+
"memory",
|
|
18
|
+
"sqlite",
|
|
19
|
+
"mcp",
|
|
20
|
+
"pulse"
|
|
21
|
+
],
|
|
22
|
+
"homepage": "https://github.com/jossuealcacao-exe/madre#readme",
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "git+https://github.com/jossuealcacao-exe/madre.git"
|
|
26
|
+
},
|
|
27
|
+
"bugs": {
|
|
28
|
+
"url": "https://github.com/jossuealcacao-exe/madre/issues"
|
|
29
|
+
},
|
|
30
|
+
"author": {
|
|
31
|
+
"name": "Jossué Alcalá",
|
|
32
|
+
"url": "https://jossuealcala.com/en/"
|
|
33
|
+
},
|
|
34
|
+
"type": "module",
|
|
35
|
+
"license": "Apache-2.0",
|
|
36
|
+
"files": [
|
|
37
|
+
"bin",
|
|
38
|
+
"src",
|
|
39
|
+
"public",
|
|
40
|
+
"README.md",
|
|
41
|
+
"CHANGELOG.md",
|
|
42
|
+
"LICENSE",
|
|
43
|
+
"NOTICE",
|
|
44
|
+
"docs/madre-banner.svg"
|
|
45
|
+
],
|
|
46
|
+
"bin": {
|
|
47
|
+
"madre": "./bin/madre.mjs",
|
|
48
|
+
"pulse": "./bin/madre.mjs"
|
|
49
|
+
},
|
|
50
|
+
"engines": {
|
|
51
|
+
"node": ">=22.5.0"
|
|
52
|
+
},
|
|
53
|
+
"scripts": {
|
|
54
|
+
"start": "node ./bin/madre.mjs start",
|
|
55
|
+
"doctor": "node ./bin/madre.mjs doctor",
|
|
56
|
+
"test": "node --test",
|
|
57
|
+
"check": "npm test && node ./bin/madre.mjs doctor --json",
|
|
58
|
+
"pack:check": "node ./scripts/pack-check.mjs"
|
|
59
|
+
},
|
|
60
|
+
"publishConfig": {
|
|
61
|
+
"access": "public"
|
|
62
|
+
}
|
|
63
|
+
}
|