@jossuealcala/madre 0.3.3 → 0.4.1
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 +497 -3
- package/CONTRIBUTING.md +3 -1
- package/README.md +68 -186
- package/SECURITY.md +2 -1
- package/bin/madre.mjs +56 -13
- package/docs/INTERNALS.md +16 -0
- package/docs/REFERENCE.md +249 -0
- package/docs/SDK.md +121 -0
- package/docs/room.png +0 -0
- package/docs/sdk/hello-module.mjs +51 -0
- package/package.json +9 -1
- package/public/app.js +3979 -867
- package/public/es.js +2258 -0
- package/public/i18n.js +66 -0
- package/public/index.html +96 -15
- package/public/inquiry.js +220 -0
- package/public/resay.js +77 -0
- package/public/styles.css +622 -65
- package/public/troubleshooting.js +255 -46
- package/src/adapters/claude.mjs +2 -1
- package/src/adapters/codex.mjs +2 -1
- package/src/adapters/gemini.mjs +6 -5
- package/src/adapters/opencode.mjs +2 -1
- package/src/adapters/process.mjs +79 -20
- package/src/asking.mjs +128 -0
- package/src/auth-probe.mjs +58 -1
- package/src/chats.mjs +193 -0
- package/src/checkpoint.mjs +1 -1
- package/src/cold.mjs +56 -0
- package/src/commands.mjs +6 -0
- package/src/conversation-context.mjs +35 -3
- package/src/credentials.mjs +145 -0
- package/src/dataset.mjs +56 -4
- package/src/distiller.mjs +12 -5
- package/src/event-store.mjs +14 -8
- package/src/exam.mjs +240 -0
- package/src/extensions.mjs +3 -2
- package/src/eyecat-watch.mjs +100 -0
- package/src/eyecat.mjs +169 -0
- package/src/i18n.mjs +47 -0
- package/src/image-studio.mjs +2 -0
- package/src/launch.mjs +61 -0
- package/src/maturity.mjs +94 -0
- package/src/mcp/image-server.mjs +36 -3
- package/src/mcp/memory-server.mjs +1 -1
- package/src/memory.mjs +325 -17
- package/src/modules/ahp.mjs +9 -7
- package/src/modules/ash.mjs +36 -0
- package/src/modules/git-pulse.mjs +5 -3
- package/src/modules/helpers.mjs +31 -0
- package/src/modules/image-studio.mjs +10 -4
- package/src/modules/index.mjs +141 -9
- package/src/modules/ollama.mjs +66 -10
- package/src/modules/playwright.mjs +44 -23
- package/src/modules/ripley.mjs +5 -3
- package/src/modules/sdk.mjs +93 -2
- package/src/modules/updates.mjs +81 -0
- package/src/ollama.mjs +5 -2
- package/src/outbound.mjs +297 -0
- package/src/privacy.mjs +54 -7
- package/src/room/context.mjs +4 -4
- package/src/room/economy.mjs +161 -0
- package/src/room/prompt.mjs +118 -46
- package/src/room.mjs +443 -44
- package/src/runtime-detection.mjs +27 -8
- package/src/sentinel-errors.mjs +19 -1
- package/src/server.mjs +709 -71
- package/src/setup.mjs +1 -1
- package/src/updates.mjs +4 -2
- package/src/usage-sentinel.mjs +13 -8
- package/src/verdict.mjs +74 -0
- package/src/ashcode.mjs +0 -64
- package/src/modules/ashcode.mjs +0 -28
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { constants } from 'node:fs';
|
|
2
2
|
import { access } from 'node:fs/promises';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
3
4
|
import { delimiter, join } from 'node:path';
|
|
4
5
|
import { execFile } from 'node:child_process';
|
|
5
6
|
import { promisify } from 'node:util';
|
|
@@ -46,13 +47,24 @@ async function executable(path) {
|
|
|
46
47
|
}
|
|
47
48
|
}
|
|
48
49
|
|
|
49
|
-
|
|
50
|
+
// On Windows a CLI on PATH is `name.cmd`, `name.exe` or `name.ps1`: npm installs a .cmd shim.
|
|
51
|
+
// PATHEXT is the system's own list of what counts as runnable, and the file has no execute bit.
|
|
52
|
+
const extensions = (env = process.env) => (process.platform === 'win32'
|
|
53
|
+
? (env.PATHEXT ?? '.COM;.EXE;.BAT;.CMD').split(';').filter(Boolean).map((extension) => extension.toLowerCase())
|
|
54
|
+
: ['']);
|
|
55
|
+
|
|
56
|
+
export async function findExecutable(candidates, envPath = process.env.PATH ?? '', env = process.env) {
|
|
57
|
+
const suffixes = extensions(env);
|
|
58
|
+
const runnable = async (path) => (process.platform === 'win32' ? access(path).then(() => true, () => false) : executable(path));
|
|
50
59
|
for (const candidate of candidates) {
|
|
51
|
-
if (candidate.includes('/')
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
60
|
+
if (candidate.includes('/') || candidate.includes('\\')) {
|
|
61
|
+
for (const suffix of candidate.includes('.') ? [''] : suffixes) if (await runnable(`${candidate}${suffix}`)) return `${candidate}${suffix}`;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
for (const directory of envPath.split(delimiter).filter(Boolean)) {
|
|
65
|
+
for (const suffix of suffixes) {
|
|
66
|
+
const path = join(directory, `${candidate}${suffix}`);
|
|
67
|
+
if (await runnable(path)) return path;
|
|
56
68
|
}
|
|
57
69
|
}
|
|
58
70
|
}
|
|
@@ -68,9 +80,16 @@ async function readVersion(path, args) {
|
|
|
68
80
|
}
|
|
69
81
|
}
|
|
70
82
|
|
|
71
|
-
|
|
83
|
+
// Where MADRE puts a CLI when the system folders are not writable: its own prefix, no
|
|
84
|
+
// administrator, nothing outside ~/.pulse. Detection looks here as well as along PATH, so an
|
|
85
|
+
// agent installed this way is found without the human touching their PATH.
|
|
86
|
+
export const toolsPrefix = (env = process.env) => join(env.PULSE_HOME ?? join(homedir(), '.pulse'), 'tools');
|
|
87
|
+
export const toolsBin = (env = process.env) => (process.platform === 'win32' ? toolsPrefix(env) : join(toolsPrefix(env), 'bin'));
|
|
88
|
+
|
|
89
|
+
export async function detectAgents({ env = process.env } = {}) {
|
|
90
|
+
const searchPath = [env.PATH ?? '', toolsBin(env)].filter(Boolean).join(delimiter);
|
|
72
91
|
return Promise.all(definitions.map(async (definition) => {
|
|
73
|
-
const path = await findExecutable(definition.candidates);
|
|
92
|
+
const path = await findExecutable(definition.candidates, searchPath);
|
|
74
93
|
const detected = Boolean(path);
|
|
75
94
|
return {
|
|
76
95
|
id: definition.id,
|
package/src/sentinel-errors.mjs
CHANGED
|
@@ -10,6 +10,12 @@
|
|
|
10
10
|
import { createHash } from 'node:crypto';
|
|
11
11
|
import { homedir, platform, arch, release } from 'node:os';
|
|
12
12
|
import { diagnose } from '../public/troubleshooting.js';
|
|
13
|
+
import { language } from './i18n.mjs';
|
|
14
|
+
|
|
15
|
+
// The product's own house. The collector lives under it too, so the line a human reads in the
|
|
16
|
+
// outbound log names the product instead of a personal endpoint on someone's cloud account.
|
|
17
|
+
export const HOME_SITE = 'https://madre.run';
|
|
18
|
+
export const REPORT_FORM = { es: `${HOME_SITE}/reportar`, en: `${HOME_SITE}/report` };
|
|
13
19
|
|
|
14
20
|
export const REPORT_WINDOW_MS = 24 * 3600 * 1000;
|
|
15
21
|
const MAX_ERROR_CHARS = 4000;
|
|
@@ -71,7 +77,7 @@ export class ErrorSentinel {
|
|
|
71
77
|
this.#save = save;
|
|
72
78
|
}
|
|
73
79
|
|
|
74
|
-
settings() { return { ...this.#settings, canSend: Boolean(this.#settings.reportUrl), repo: repoFromPackage(this.#pkg) }; }
|
|
80
|
+
settings() { return { ...this.#settings, canSend: Boolean(this.#settings.reportUrl), repo: repoFromPackage(this.#pkg), home: HOME_SITE }; }
|
|
75
81
|
async setSettings(patch = {}) {
|
|
76
82
|
if (typeof patch.autoReport === 'boolean') this.#settings.autoReport = patch.autoReport;
|
|
77
83
|
if (typeof patch.reportUrl === 'string') this.#settings.reportUrl = patch.reportUrl.trim();
|
|
@@ -174,6 +180,18 @@ export class ErrorSentinel {
|
|
|
174
180
|
return `https://github.com/${repo}/issues/new?${new URLSearchParams({ title, body, labels: 'sentinel' })}`;
|
|
175
181
|
}
|
|
176
182
|
|
|
183
|
+
// Where a human goes to say something. GitHub is the better home for a report — it is
|
|
184
|
+
// searchable, it deduplicates, and the answer helps whoever hits it next — but it asks for an
|
|
185
|
+
// account first, and somebody who arrived from a post in Spanish will not make one to say "it
|
|
186
|
+
// does not detect Codex". They get the form; the other road stays one click away, both ways.
|
|
187
|
+
formUrl({ about = '' } = {}) {
|
|
188
|
+
const base = REPORT_FORM[language()] ?? REPORT_FORM.en;
|
|
189
|
+
const env = this.environment();
|
|
190
|
+
const query = new URLSearchParams({ v: env.madre, node: env.node, os: env.platform });
|
|
191
|
+
if (about) query.set('about', about.slice(0, 120));
|
|
192
|
+
return `${base}?${query}`;
|
|
193
|
+
}
|
|
194
|
+
|
|
177
195
|
feedbackUrl({ about = '' } = {}) {
|
|
178
196
|
const repo = repoFromPackage(this.#pkg);
|
|
179
197
|
if (!repo) return null;
|