@jossuealcala/madre 1.0.1 → 1.0.2
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/ADDITIONAL-TERMS.md +93 -0
- package/CHANGELOG.en.md +1945 -0
- package/CHANGELOG.md +34 -1
- package/CONTRIBUTING.md +1 -1
- package/LICENSE +661 -202
- package/NOTICE +26 -14
- package/README.en.md +245 -0
- package/README.md +19 -10
- package/TRADEMARKS.en.md +50 -0
- package/TRADEMARKS.md +33 -16
- package/bin/madre.mjs +2 -0
- package/docs/INTERNALS.md +6 -0
- package/docs/REFERENCE.md +11 -2
- package/docs/en/INTERNALS.md +103 -0
- package/docs/en/REFERENCE.md +339 -0
- package/docs/en/SDK.md +306 -0
- package/docs/en/training/README.md +59 -0
- package/docs/training/README.md +20 -8
- package/docs/training/train.sh +2 -1
- package/package.json +11 -3
- package/public/abduction.js +2 -0
- package/public/app.js +1549 -409
- package/public/attribution.js +71 -0
- package/public/brands.js +3 -1
- package/public/editor.js +2 -0
- package/public/es.js +620 -400
- package/public/i18n.js +25 -1
- package/public/index.html +13 -10
- package/public/inquiry.js +2 -0
- package/public/loader.js +2 -0
- package/public/macroverso.js +2 -0
- package/public/nostromo-engine.js +2 -0
- package/public/resay.js +6 -0
- package/public/review-ask.js +2 -0
- package/public/routing.js +23 -1
- package/public/salas-mac.css +21 -4
- package/public/salas-mac.html +5 -1
- package/public/salas-mac.js +53 -28
- package/public/salas.css +2 -0
- package/public/salas.html +1 -0
- package/public/salas.js +4 -2
- package/public/styles.css +957 -18
- package/public/syntax.js +2 -0
- package/public/touch.js +2 -0
- package/public/tour-chapters.js +2 -0
- package/public/tour.js +37 -0
- package/public/troubleshooting.js +18 -16
- package/src/adapters/claude.mjs +2 -0
- package/src/adapters/codex.mjs +2 -0
- package/src/adapters/gemini.mjs +3 -1
- package/src/adapters/madre.mjs +3 -1
- package/src/adapters/opencode.mjs +7 -3
- package/src/adapters/process.mjs +2 -0
- package/src/asking.mjs +2 -0
- package/src/auth-probe.mjs +3 -1
- package/src/bridge/devices.mjs +2 -0
- package/src/bridge/handoff.mjs +2 -0
- package/src/bridge/host.mjs +2 -0
- package/src/bridge/network.mjs +2 -0
- package/src/bridge/policy.mjs +3 -1
- package/src/bridge/qr.mjs +2 -0
- package/src/bridge/server.mjs +2 -0
- package/src/capabilities.mjs +2 -0
- package/src/chats.mjs +2 -0
- package/src/checkpoint.mjs +2 -0
- package/src/choices.mjs +2 -0
- package/src/cli-args.mjs +2 -0
- package/src/cold.mjs +2 -0
- package/src/commands.mjs +2 -0
- package/src/config.mjs +2 -0
- package/src/conversation-context.mjs +15 -1
- package/src/credentials.mjs +32 -3
- package/src/dataset.mjs +2 -0
- package/src/directives.mjs +2 -0
- package/src/distiller.mjs +2 -0
- package/src/embeddings.mjs +2 -0
- package/src/enlaces.mjs +6 -4
- package/src/event-store.mjs +2 -0
- package/src/exam.mjs +6 -4
- package/src/extensions.mjs +2 -0
- package/src/eyecat-watch.mjs +2 -0
- package/src/eyecat.mjs +2 -0
- package/src/file-ops.mjs +2 -0
- package/src/files.mjs +32 -0
- package/src/github.mjs +2 -0
- package/src/heartbeat.mjs +2 -0
- package/src/i18n.mjs +15 -1
- package/src/image-studio.mjs +2 -0
- package/src/json-file.mjs +2 -0
- package/src/launch.mjs +2 -0
- package/src/lease.mjs +5 -1
- package/src/lobby-work.mjs +2 -0
- package/src/local-ai.mjs +2 -0
- package/src/looks.mjs +2 -0
- package/src/maturity.mjs +3 -1
- package/src/mcp/connect.mjs +345 -0
- package/src/mcp/github-server.mjs +2 -0
- package/src/mcp/image-server.mjs +2 -0
- package/src/mcp/memory-server.mjs +2 -0
- package/src/mcp/proxy.mjs +153 -0
- package/src/mcp/smtp-server.mjs +2 -0
- package/src/memory-dashboard.mjs +2 -0
- package/src/memory-tools.mjs +2 -0
- package/src/memory.mjs +2 -0
- package/src/models.mjs +45 -3
- package/src/modules/ahp.mjs +2 -0
- package/src/modules/ash-policy.mjs +2 -0
- package/src/modules/ash.mjs +3 -1
- package/src/modules/bishop-plan.mjs +5 -3
- package/src/modules/bishop.mjs +13 -11
- package/src/modules/choices.mjs +2 -0
- package/src/modules/correo.mjs +2 -0
- package/src/modules/enlaces.mjs +2 -0
- package/src/modules/git-pulse.mjs +2 -0
- package/src/modules/github.mjs +2 -0
- package/src/modules/helpers.mjs +2 -0
- package/src/modules/image-studio.mjs +3 -1
- package/src/modules/index.mjs +4 -1
- package/src/modules/intents-calibration.mjs +2 -0
- package/src/modules/intents-desk.mjs +4 -2
- package/src/modules/intents-observe.mjs +2 -0
- package/src/modules/intents-policy.mjs +4 -1
- package/src/modules/intents-try.mjs +2 -0
- package/src/modules/intents.mjs +8 -6
- package/src/modules/mcp-connect.mjs +293 -0
- package/src/modules/ollama.mjs +3 -1
- package/src/modules/playwright.mjs +25 -10
- package/src/modules/ripley.mjs +2 -0
- package/src/modules/runs.mjs +5 -3
- package/src/modules/sdk.mjs +2 -0
- package/src/modules/updates.mjs +2 -0
- package/src/mother.mjs +2 -0
- package/src/ollama.mjs +2 -0
- package/src/outbound.mjs +7 -5
- package/src/override.mjs +2 -0
- package/src/ports.mjs +2 -0
- package/src/prices.mjs +2 -0
- package/src/privacy.mjs +2 -0
- package/src/quota-monitor.mjs +2 -0
- package/src/quota-sources.mjs +2 -0
- package/src/request-guard.mjs +2 -0
- package/src/review-ask.mjs +2 -0
- package/src/room/archivist.mjs +2 -0
- package/src/room/attachments.mjs +2 -0
- package/src/room/budget.mjs +2 -0
- package/src/room/context.mjs +2 -0
- package/src/room/control.mjs +7 -1
- package/src/room/copilot.mjs +76 -0
- package/src/room/economy.mjs +2 -0
- package/src/room/escalation.mjs +2 -0
- package/src/room/ghost.mjs +2 -0
- package/src/room/guard.mjs +2 -0
- package/src/room/prompt.mjs +13 -4
- package/src/room/returns.mjs +82 -0
- package/src/room/runs.mjs +4 -0
- package/src/room/vectors.mjs +2 -0
- package/src/room.mjs +209 -31
- package/src/router.mjs +2 -0
- package/src/runs.mjs +2 -0
- package/src/runtime-detection.mjs +2 -0
- package/src/salas.mjs +2 -0
- package/src/sentinel-errors.mjs +2 -0
- package/src/server-sentinel.mjs +2 -0
- package/src/server.mjs +141 -34
- package/src/setup.mjs +2 -0
- package/src/smtp.mjs +2 -0
- package/src/tour.mjs +2 -0
- package/src/updates.mjs +2 -0
- package/src/usage-sentinel.mjs +2 -0
- package/src/vault.mjs +2 -0
- package/src/verdict.mjs +3 -1
- package/src/whats-new.mjs +2 -0
package/docs/en/SDK.md
ADDED
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
# MADRE SDK · write your own modules
|
|
2
|
+
|
|
3
|
+
A MADRE module is **one file**. No build, no dependencies, no signing up anywhere. You copy it into a folder, reload the modules from «Create your module» in MODULES, and it shows up on the board with its switch. You can write it by hand or ask an AI for it, with this document as context.
|
|
4
|
+
|
|
5
|
+
## Where it lives
|
|
6
|
+
|
|
7
|
+
| Folder | Scope |
|
|
8
|
+
|---|---|
|
|
9
|
+
| `~/.pulse/modules/*.mjs` | All your rooms |
|
|
10
|
+
| `<proyecto>/.madre/modules/*.mjs` | That project only |
|
|
11
|
+
|
|
12
|
+
No agent can write there: `.madre/` is a forbidden zone in CONTROL and AIRLOCK, and `~/.pulse` is outside every project. A module gets into MADRE only because you put it there.
|
|
13
|
+
|
|
14
|
+
## The file
|
|
15
|
+
|
|
16
|
+
The `export default` is a plain object. MADRE wraps it with `defineModule`. There are two complete
|
|
17
|
+
examples, and both work as is:
|
|
18
|
+
|
|
19
|
+
| | what it teaches |
|
|
20
|
+
|---|---|
|
|
21
|
+
| [`hello-module.mjs`](../sdk/hello-module.mjs) | the minimum: a switch, one setting, a `/hello` command |
|
|
22
|
+
| [`connector-module.mjs`](../sdk/connector-module.mjs) | a real connector: key in the vault, declared destination, and **a single file that is the module page when imported and the MCP server when executed** |
|
|
23
|
+
|
|
24
|
+
Start with the first one. If your module talks to something outside this computer, copy the
|
|
25
|
+
second: that shape can't be guessed, and the obvious way to write it fails **silently** (see
|
|
26
|
+
"A file that is also a server", below).
|
|
27
|
+
|
|
28
|
+
```js
|
|
29
|
+
export default {
|
|
30
|
+
id: 'hello', // kebab-case
|
|
31
|
+
name: 'HELLO',
|
|
32
|
+
summary: 'Qué hace, en una frase.',
|
|
33
|
+
settings: { enabled: false, greeting: 'hola' },
|
|
34
|
+
async status(ctx) { return { status: { installed: Boolean(ctx.settings.enabled), detail: ctx.settings.enabled ? 'on' : 'off' } }; },
|
|
35
|
+
slash: [{ name: 'hello', usage: '/hello [name]', summary: 'Saluda.', async execute(ctx, args) { return { ok: true, title: 'HELLO', text: `${ctx.settings.greeting}, ${args[0] ?? 'crew'}` }; } }],
|
|
36
|
+
};
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**Your module does not import packages.** Not MADRE, not the SDK, nothing from npm. Before installing it, MADRE copies your file to a separate temporary folder and loads it there —that way it checks what it is without running it where it lives— and that folder has no `node_modules`: an `import` by package name doesn't resolve and the installation fails.
|
|
40
|
+
|
|
41
|
+
**Node builtins do work**, always: `node:fs`, `node:url`, `node:tls`, `node:child_process`. That's enough to speak any protocol. What there isn't is anything to install.
|
|
42
|
+
|
|
43
|
+
If you need `defineModule` —to compute something before defining the module, for example— **MADRE hands it to you**: export a function and you receive it as an argument.
|
|
44
|
+
|
|
45
|
+
```js
|
|
46
|
+
export default ({ defineModule }) => defineModule({
|
|
47
|
+
id: 'mi-modulo',
|
|
48
|
+
name: 'MI MÓDULO',
|
|
49
|
+
// …lo que hayas calculado arriba
|
|
50
|
+
});
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
> The modules that ship with MADRE do write `import { defineModule } from './sdk.mjs'`, because they live inside the package. **If you ask an agent to write one for you, it will read those files and copy that line** — and it won't work. Tell it to export a plain object, or the function above.
|
|
54
|
+
|
|
55
|
+
## What a module can declare
|
|
56
|
+
|
|
57
|
+
This table is **the complete contract**: 36 fields, not one more. Any other field you write, MADRE
|
|
58
|
+
tells you about at install time ("I didn't understand these fields") instead of silently ignoring it. A test in
|
|
59
|
+
the suite compares this table against the code, so if they ever drift apart, it's a failure.
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
| Field | What it is |
|
|
63
|
+
|---|---|
|
|
64
|
+
| `id`, `name`, `vendor`, `summary`, `creates`, `requires` | Its page in MODULES. `summary` is one sentence: what it does. `creates` and `requires` are short lines, one idea each |
|
|
65
|
+
| `version` | Yours, and it starts at `1.0.0`. If you don't declare it, the page shows the file's date |
|
|
66
|
+
| `tracks` | If your module wraps something from outside, its version isn't yours: it's that thing's. Declare `{ name, npm }` or `{ name, github }` and return the version found in `status(ctx)` as `runs: [{ name, version }]`. MADRE shows that version and looks for a newer one on its own, once a day, with the page's ↻ button to check now |
|
|
67
|
+
| `updatePlan(ctx, { latest })` | How that new version is brought to this computer: `{ command, args, display, note, after }`. MADRE shows `display` and runs nothing until the human has read it; the output lands in the room line by line. With no command, return `{ command: null, note, download }` and the page sends you to download it |
|
|
68
|
+
| `updates` | An https `{ url }` where you publish **your own module**. With that, its page gets a button that fetches the file, verifies it the same way as an installation, and replaces it if it passes. If you installed it from a file, MADRE remembers which one and you don't need to declare anything: edit it and ask for a new copy |
|
|
69
|
+
| `settings` | Default values. They live in `~/.pulse/config.json` under `modules.<idEnCamelCase>`; `enabled` is the switch |
|
|
70
|
+
| `status(ctx)` | What the card shows: `{ status: { installed, detail }, preflight: { ok, problems }, install: { display } }`. `install` is returned from here; it is not a top-level field of the module |
|
|
71
|
+
| `status(ctx)` → `runs` | What your module manages that isn't MADRE: `[{ name, version, target }]`. `version` is what you found on this computer (`null` if it isn't there), `target` is what you would install. If it matches `tracks.name`, that is the page's version |
|
|
72
|
+
| `toggle(ctx, payload)` | Replaces the default switch; `confirm: 'texto'` asks for confirmation before turning on |
|
|
73
|
+
| `onToggle(ctx, enabled)` | Reacts to the switch |
|
|
74
|
+
| `slash` | `/name` commands that run on the server with your `ctx` and return `{ ok, title, text }`. The room shows them as a card and the agents read them |
|
|
75
|
+
| `toolsForTurn(ctx, turn)` | MCP servers for an agent's turn: `[{ name, command, args, env, tools, sends, brief }]`. MADRE attaches them to the CLI in its isolated run and describes `brief` to the agent. `turn` carries the agent, the mode, the lease, the folder, the port, and `turn.turn`: the id of the reply that turn will write. If your server calls back to the room on behalf of the turn, send that id; `ctx.room.runningTurn(id)` tells you whether it is still running and for which agent |
|
|
76
|
+
| `toolsForTurn` → `sends` | Which of your tools **send something outside**. The core withdraws them below `#4` AIRLOCK: the model isn't even told they exist. See below |
|
|
77
|
+
| `reaches` | Where your module reaches, in the same four answers MADRE gives about its own. See below |
|
|
78
|
+
| `secrets` | The keys you need, declared instead of drawn: `[{ name, label, note, where }]`. MADRE draws the field, keeps it in its vault under your id, and gives it back to you through `ctx.vault`. See below |
|
|
79
|
+
| `routes` | Your own HTTP routes: `{ method, path, handler(ctx, { payload, params, url, request, viaPhone }) }` → `{ status, body }`. `viaPhone` is `true` when the request comes from a paired phone: deny it yourself if your route belongs to the Mac |
|
|
80
|
+
| `onEvent(ctx, event)` | Every ledger event of every live conversation in the room, while your module is on. `ctx.room` and `ctx.chat` are that conversation's, and `ctx.record` writes into it. It runs separately: whatever it takes long on or fails at doesn't stop the room. As always, a module doesn't fabricate messages or turns |
|
|
81
|
+
| `controls` | Your module's settings, declared instead of drawn: `[{ key, label, type: 'select' \| 'switch' \| 'text', options, choices, note, invert }]`. MADRE draws them on the page and saves them in your block of `config.json`. In a `select`, `choices: [{ value, label }]` says which phrase the person reads for each value that gets saved (if you give it, `options` comes from there); without it, the person reads the value as is |
|
|
82
|
+
| `onSettings(ctx, settings)` | Tells you when the human changed one of your `controls`, in case something live needs to know |
|
|
83
|
+
| `conditions` | Entries for the MU/TH/UR catalog, with a remedy per platform |
|
|
84
|
+
| `kind` | `'builtin'` (the default) changes how MADRE behaves and lives in `config.json`; `'installer'` writes into the project with a command the human confirms before it runs |
|
|
85
|
+
| `configKey` | Your block in `config.json`. By default it is your `id` in camelCase; declare it only if you want a different one |
|
|
86
|
+
| `card` | Which card MADRE draws. A module of yours leaves the default value: the named cards are the ones MADRE ships drawn for its own |
|
|
87
|
+
| `models` | The models your module chooses between, if it wraps something that has them. They show up on the page |
|
|
88
|
+
| `package` | The npm package that installs what your module manages, if there is one |
|
|
89
|
+
| `confirm` | A text. If it is there, turning on the switch asks for confirmation and doesn't turn on until the human gives it |
|
|
90
|
+
| `toggledEvent`, `toggledBody` | What gets added to the `extension.toggled` event and to the notice the room sees when your module is turned on or off |
|
|
91
|
+
| `preflight(ctx)`, `detect(ctx)`, `installCommand(ctx)` | **Legacy**, from when a module could only be an installer. They still work; for something new, return `preflight` from `status(ctx)` as above |
|
|
92
|
+
| `describe(ctx)` | MADRE builds it from everything above. **Don't declare it**: it is on the list because the object carries it, not because you have to write it |
|
|
93
|
+
|
|
94
|
+
### Can it connect to *X*?
|
|
95
|
+
|
|
96
|
+
That doesn't depend on MADRE. A module can do whatever a Node process can do on your
|
|
97
|
+
computer: speak TLS, start a server, store a key. **It depends on how that service
|
|
98
|
+
lets a human hand over their own credential**, and that falls into three tiers:
|
|
99
|
+
|
|
100
|
+
| | what the service asks for | does it work on install? |
|
|
101
|
+
|---|---|---|
|
|
102
|
+
| **A** | a static key in minutes: personal token, API key, app password. No app to register, no review | **yes** |
|
|
103
|
+
| **B** | that each user register their own app (OAuth without restricted permissions) | yes, but signing up stops being "paste this" — and you would have to write OAuth |
|
|
104
|
+
| **C** | review, security assessment, or verified publisher | **no**: each user would have to be their own developer |
|
|
105
|
+
|
|
106
|
+
Almost everything useful is in **A**: GitHub, GitLab, Linear, Notion (internal integration), Jira,
|
|
107
|
+
Slack, Discord, Telegram, Stripe, Shopify, Cloudflare, Odoo, any database, and any
|
|
108
|
+
API that accepts an `Authorization: Bearer`. In **B**: Google Calendar and Drive, Microsoft Graph,
|
|
109
|
+
Mercado Libre. In **C**: Gmail for reading, all of Meta, banking.
|
|
110
|
+
|
|
111
|
+
**The rule**, if you don't want to read the table: it's worth it when the credential can be obtained in less
|
|
112
|
+
than five minutes without being a developer, can be revoked on its own, and what your module brings in or sends out fits in one
|
|
113
|
+
line of the page. The long catalog, and why, is in
|
|
114
|
+
[§6 of `CONECTORES.md`](https://github.com/jossuealcacao-exe/madre/blob/main/docs/en/CONNECTORS.md#6--qu%C3%A9-alcanza-un-m%C3%B3dulo-y-de-qui%C3%A9n-es-el-muro), in the repository.
|
|
115
|
+
|
|
116
|
+
### A file that is also a server
|
|
117
|
+
|
|
118
|
+
`toolsForTurn` returns `args` that point to a file, and you only have one. You don't need two:
|
|
119
|
+
**yours is the module page when MADRE imports it and the MCP server when MADRE executes it.** It knows
|
|
120
|
+
which of the two it is at the end of the file.
|
|
121
|
+
|
|
122
|
+
```js
|
|
123
|
+
import { realpath } from 'node:fs/promises';
|
|
124
|
+
import { fileURLToPath } from 'node:url';
|
|
125
|
+
const SELF = fileURLToPath(import.meta.url);
|
|
126
|
+
|
|
127
|
+
// …export default { …, toolsForTurn: () => [{ command: process.execPath, args: [SELF], … }] }
|
|
128
|
+
|
|
129
|
+
const real = async (p) => (p ? realpath(p).catch(() => p) : null);
|
|
130
|
+
if ((await real(process.argv[1])) === (await real(SELF))) serve();
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**Resolve both paths before comparing them.** `process.argv[1] === SELF` looks the same and almost
|
|
134
|
+
always is: when MADRE launches it, it passes the same `SELF` that Node's loader already resolved.
|
|
135
|
+
But as soon as **you run the file yourself by hand to test it** —`node ~/.pulse/modules/tuyo.mjs`, or
|
|
136
|
+
any path that goes through `/tmp`, which on macOS is a link to `/private/tmp`— `argv[1]` keeps
|
|
137
|
+
what you typed and `SELF` doesn't. The comparison comes out false, `serve()` never runs, the process starts,
|
|
138
|
+
answers nothing and **exits successfully**. It doesn't print an error because, on paper, there wasn't one.
|
|
139
|
+
Two lines save you that afternoon.
|
|
140
|
+
|
|
141
|
+
### If your module talks to an outside service
|
|
142
|
+
|
|
143
|
+
Declare where it reaches. `src/outbound.mjs` is not a log of outgoing traffic: it is a contract, and
|
|
144
|
+
it patches global `fetch` so that no module can get around it. A connector that doesn't declare its
|
|
145
|
+
destination shows up in the log as "went to an address nothing here declares" — true, and
|
|
146
|
+
useless to someone trying to tell your module apart from a leak.
|
|
147
|
+
|
|
148
|
+
```js
|
|
149
|
+
reaches: [{
|
|
150
|
+
id: 'gmail',
|
|
151
|
+
host: 'gmail.googleapis.com',
|
|
152
|
+
to: 'Google · Gmail, con tu cuenta',
|
|
153
|
+
what: 'El asunto y el cuerpo del correo que el agente redactó.',
|
|
154
|
+
when: 'cuando un agente usa la herramienta de envío',
|
|
155
|
+
where: 'MODULES → CORREO',
|
|
156
|
+
}]
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
The four answers are the same ones MADRE gives about its own, and for the same reason: whoever
|
|
160
|
+
reads the log deserves to know what travels, when, where to, and where it's turned off. Your declaration is
|
|
161
|
+
stored **separately** from the core's and marked with your `id`, because who promised what is part
|
|
162
|
+
of the promise. It is rebuilt on every load: uninstalling your module takes its destination with it.
|
|
163
|
+
|
|
164
|
+
If you point your module at another server at runtime, declare again with the **same
|
|
165
|
+
`id`**: that way the entry is replaced instead of leaving the log answering for an address
|
|
166
|
+
you no longer use.
|
|
167
|
+
|
|
168
|
+
### If your module needs a key
|
|
169
|
+
|
|
170
|
+
Declare them and don't ask for them yourself. MADRE draws a password field on the `KEYS` floor of your
|
|
171
|
+
page, accepts the value **only from this computer** (`127.0.0.1`), stores it in
|
|
172
|
+
`~/.pulse/credentials/<tu-id>.json` with `0600` permissions inside a `0700` directory, and notes
|
|
173
|
+
in the ledger that one was saved and **with what name and what length, never the value**.
|
|
174
|
+
|
|
175
|
+
```js
|
|
176
|
+
secrets: [{
|
|
177
|
+
name: 'app-password',
|
|
178
|
+
label: 'APP PASSWORD',
|
|
179
|
+
note: 'No es la contraseña de tu cuenta. Una de aplicación, que puedes revocar sola.',
|
|
180
|
+
where: 'https://myaccount.google.com/apppasswords', // enlace a dónde sacarla
|
|
181
|
+
}]
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
And you read it through `ctx.vault`, which comes scoped to your module:
|
|
185
|
+
|
|
186
|
+
```js
|
|
187
|
+
await ctx.vault.get('app-password') // el valor, solo para ti
|
|
188
|
+
await ctx.vault.held() // [{ module, name, bytes }] — nombres y tamaños, nunca valores
|
|
189
|
+
await ctx.vault.keep(name, value) // guardar
|
|
190
|
+
await ctx.vault.forget(name) // olvidar; sin nombre, todas las tuyas
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Your id is sealed inside the SDK, so asking for another module's secret is not something you can
|
|
194
|
+
*write*. The page only shows that one is stored and how many characters it has.
|
|
195
|
+
|
|
196
|
+
**The value goes to the environment of the process you start, and nowhere else.** Not to the prompt, not
|
|
197
|
+
to the `brief`, not to a tool result, not to the ledger.
|
|
198
|
+
|
|
199
|
+
### If your module sends something
|
|
200
|
+
|
|
201
|
+
Reading can be undone: in the worst case you brought in something that wasn't useful. **Sending can't.** An email, a
|
|
202
|
+
message, a record in someone else's system: that goes out under the name of whoever opened the room
|
|
203
|
+
and doesn't come back.
|
|
204
|
+
|
|
205
|
+
That's why sending lives in `#4` AIRLOCK, where the rule already requires the agent to say in one line
|
|
206
|
+
what goes out and where before it goes out. **The core enforces it, not your good will:**
|
|
207
|
+
|
|
208
|
+
```js
|
|
209
|
+
tools: ['buscar_correo', 'mandar_correo'],
|
|
210
|
+
sends: ['mandar_correo'], // por debajo de #4 esta se retira de la lista
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
If *all* of a server's tools send, the whole server doesn't travel below
|
|
214
|
+
`#4`. If some don't send, the server travels with the list trimmed and a `brief` that says why
|
|
215
|
+
it comes short. And whatever you decide to check, check it **in your server and not in the `brief`**: a
|
|
216
|
+
prompt is a suggestion.
|
|
217
|
+
|
|
218
|
+
**Can it connect to *X*?** The answer doesn't depend on MADRE but on how that service lets
|
|
219
|
+
a human hand over their own key, and it is mapped in three tiers —with a catalog— in
|
|
220
|
+
[§6 of `CONECTORES.md`](https://github.com/jossuealcacao-exe/madre/blob/main/docs/en/CONNECTORS.md#6--qu%C3%A9-alcanza-un-m%C3%B3dulo-y-de-qui%C3%A9n-es-el-muro), in the repository.
|
|
221
|
+
Read it before writing anything: it saves you discovering halfway through that the service requires an annual
|
|
222
|
+
security review to let you in.
|
|
223
|
+
|
|
224
|
+
The whole design of the connectors is in
|
|
225
|
+
[`CONECTORES.md`](https://github.com/jossuealcacao-exe/madre/blob/main/docs/en/CONNECTORS.md), in the repository. A complete connector to read from beginning
|
|
226
|
+
to end —CORREO, which sends email over SMTP without a single dependency— is in
|
|
227
|
+
`src/modules/correo.mjs` and takes ten minutes to read. It doesn't appear in MODULES yet: we're
|
|
228
|
+
finishing it, and in the meantime it can only be turned on deliberately with `PULSE_LABS=correo`.
|
|
229
|
+
|
|
230
|
+
## Publishing it
|
|
231
|
+
|
|
232
|
+
The page of a module of yours has **GET A NEWER FILE**: MADRE fetches the file —from the `url` you declared or from the file you installed it from—, verifies it in a separate copy, and only replaces the installed one if it loads, follows the house rules, and states a different version. Verifying installs nothing.
|
|
233
|
+
|
|
234
|
+
And in the MODULES header there is **+ ADD A MODULE** to install someone else's `.mjs`. It goes through the same door as everything else. Said plainly, because it is what it is: a module runs **inside MADRE, with the permissions of whoever opens it**. MADRE checks that it loads and that it doesn't leave its pen —its own id, routes only under `/api/x/<id>/`, never on top of a MADRE module—; what the code *intends* is something nobody but you can check.
|
|
235
|
+
|
|
236
|
+
## The module page
|
|
237
|
+
|
|
238
|
+
You don't draw anything: you declare, and MADRE builds your module the same way as its own. In MODULES each module is a card on the board and, when you tap it, a page with the same blocks as SETTINGS. That's why yours reads the same as the ones MADRE ships.
|
|
239
|
+
|
|
240
|
+
**The board card** has an icon, your `name`, a line with your `summary`, and its switch if it is a module that gets turned on. A module of yours carries the «Yours» tag.
|
|
241
|
+
|
|
242
|
+
**The page**, in this order:
|
|
243
|
+
|
|
244
|
+
| Block | What it shows | Where it comes from |
|
|
245
|
+
|---|---|---|
|
|
246
|
+
| What it does | your sentence, the «Active» switch with what your module says about itself, and the version with «Look for a newer one» | `summary`, `status(ctx).status.detail`, `vendor`, `version` or `tracks` |
|
|
247
|
+
| Settings | one row per control: its name on the left, the selector, switch, or field on the right; it saves when you change it | `controls` |
|
|
248
|
+
| Keys | one field per secret, what is stored (name and length, never the value), and the option to forget it | `secrets` |
|
|
249
|
+
| What it touches | each thing it writes or needs, with its label | `creates`, `requires` |
|
|
250
|
+
| Commands | what it adds to the text box | `commands` or `slash` |
|
|
251
|
+
| Yours | get a newer file and remove it | the file you installed, `updates` |
|
|
252
|
+
|
|
253
|
+
A module that installs into the project instead of being turned on keeps its install card, with the command in view before anything runs, inside the «What it does» block. The figures at the top of the page and the dedicated window belong to MADRE's modules that keep a reading (Ash, Suggested effort, Ollama); you don't need to declare anything for them.
|
|
254
|
+
|
|
255
|
+
## The `ctx`
|
|
256
|
+
|
|
257
|
+
```
|
|
258
|
+
ctx = {
|
|
259
|
+
projectRoot, stateRoot, // el proyecto y ~/.pulse
|
|
260
|
+
config, settings, // config.json completo y tus ajustes con defaults
|
|
261
|
+
env, agents, room, // agentes detectados; la sala (room.capabilities(), room.record(...))
|
|
262
|
+
readConfig(), updateConfig(patch),
|
|
263
|
+
record(type, payload), // escribe un evento en el ledger; tipo "algo.algo", nunca message.* ni agent.*
|
|
264
|
+
services: { imageKey, ollama, … }, // lo que el servidor ofrece
|
|
265
|
+
vault: { get, held, keep, forget } // tus secretos, y solo los tuyos
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
`turn`, in `toolsForTurn`: `{ agent, mode, lease, scratchDir, port, roomDir }`. `mode` goes from 0 to 4; in 0 (GHOST) nothing is saved, so decide whether you want to hand over tools there.
|
|
270
|
+
|
|
271
|
+
## House rules
|
|
272
|
+
|
|
273
|
+
- **Nothing leaves the machine on its own.** If your module talks to a service, say so in `summary` and in `requires`, and do it only when the human asks for it.
|
|
274
|
+
- **Don't store credentials.** Use the sessions that already live on the machine.
|
|
275
|
+
- **Don't write into the project** from a module; that's what the agents' modes are for. A module that installs something into the project is `kind: 'installer'` and shows the command before running it.
|
|
276
|
+
- **Fail softly.** An exception in `toolsForTurn` hands over nothing; in a command, the card states the error. Never break an agent's turn.
|
|
277
|
+
- **MADRE's voice.** Short uppercase labels, notes that say what doesn't leave the machine, `MU/TH/UR › ` when you speak in a notice.
|
|
278
|
+
|
|
279
|
+
## Let your AI build it, in the room
|
|
280
|
+
|
|
281
|
+
Type `/module <lo que debe hacer>` in the composer. It is the explicit entry point to the guided flow: if `#2 CREATE` isn't active, MADRE asks you to choose it and keeps your request intact. With `#2` active, it keeps the agent you chose and hands it the SDK contract and the example. The agent must write **a single file** named `<id>.module.mjs` in its scratch folder; it cannot install it on its own.
|
|
282
|
+
|
|
283
|
+
MADRE recognizes that name and puts a card in the room: `INSTALL FOR EVERY ROOM` or `INSTALL FOR THIS PROJECT`. Read it, decide, one click. MADRE validates it in a copy, saves it as `<id>.mjs` in the folder you chose, and it appears in MODULES with the «Yours» tag. You can also ask for the same thing in natural language while you're in `#2` or higher; `/module` makes the format explicit and doesn't grant permissions on its own.
|
|
284
|
+
|
|
285
|
+
Agents never write to `~/.pulse/modules` or `.madre/modules`: they propose, you install.
|
|
286
|
+
|
|
287
|
+
A module of yours carries the «Yours» tag and is removed from its page with «Remove» —also from «Create your module», in the list of yours—. MADRE confirms the exact file, deletes that file and its origin record for updates, and withdraws it from the room without restarting it. It doesn't delete files the module created or its saved settings. The modules that ship with MADRE don't show that button: they aren't removed, they're turned off.
|
|
288
|
+
|
|
289
|
+
## What a module cannot touch
|
|
290
|
+
|
|
291
|
+
A module runs inside MADRE's process with your permissions: install only what you've read. MADRE, for its part, protects its core like this:
|
|
292
|
+
|
|
293
|
+
- Its HTTP routes live under `/api/x/<id>/`; a module with routes outside that doesn't load, so none can impersonate `/api/state`, `/api/messages`, or any route of MADRE's own.
|
|
294
|
+
- It cannot use the `id` of a built-in module or of another one already loaded.
|
|
295
|
+
- `record()` rejects the reserved event types (`message.*`, `agent.*`); it cannot fabricate turns or messages.
|
|
296
|
+
- It receives nobody's credentials; like MADRE, it uses the sessions that already live on the machine.
|
|
297
|
+
- An error in `toolsForTurn` hands over nothing; in a command, the card states the error; at load time, the board warns how many didn't load and «Create your module» says which file and why. None of that stops the room.
|
|
298
|
+
|
|
299
|
+
## Testing it
|
|
300
|
+
|
|
301
|
+
1. Copy the file to `~/.pulse/modules/`.
|
|
302
|
+
2. In the room, MODULES → «Create your module» → «Reload modules». If the file has an error, it tells you right there with the exact line.
|
|
303
|
+
3. Turn it on with its switch. Type `/tu-comando` in the composer.
|
|
304
|
+
4. Change the file and reload again: no need to restart the room.
|
|
305
|
+
|
|
306
|
+
With an AI: give it this document and `hello-module.mjs`, describe what you want, and ask it for a single `.mjs` file with `export default { … }`. MADRE does the rest.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# MADRE AI · train the project's model
|
|
2
|
+
|
|
3
|
+
Roadmap 2c. The project exports what it remembers as chat pairs; you train a small model with them on your machine; Ollama serves it; `@madre` uses it as soon as it exists. All local.
|
|
4
|
+
|
|
5
|
+
## 1 · Export
|
|
6
|
+
|
|
7
|
+
From **Settings › What it remembers › Train your own AI › Export** (when it finishes it tells you which folder it used), or in the terminal:
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
npx @jossuealcala/madre dataset # in the project folder
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
It writes `~/.pulse/rooms/<project>/dataset/{train.jsonl, valid.jsonl, manifest.json}`. Each line is `{"messages":[{role:"system"},{role:"user"},{role:"assistant"}]}`: the project's real answers, redacted (no paths, names, emails or keys), plus the summarized notes as recall pairs. Messages sent in #0 INCOGNITO are never there.
|
|
14
|
+
|
|
15
|
+
How much you need: with fewer than ~200 pairs the model learns the tone and little more; from ~1000 on it starts to be worth it. Export every so often; the split is deterministic, so lines don't switch sides.
|
|
16
|
+
|
|
17
|
+
## 2 · Train (Apple Silicon, mlx-lm)
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
python3 -m venv ~/.madre-train && source ~/.madre-train/bin/activate
|
|
21
|
+
pip install mlx-lm
|
|
22
|
+
cd ~/.pulse/rooms/<project>
|
|
23
|
+
mlx_lm.lora --model Qwen/Qwen2.5-1.5B-Instruct --train --data dataset --iters 600 --batch-size 2 --learning-rate 1e-5 --adapter-path adapters
|
|
24
|
+
mlx_lm.lora --model Qwen/Qwen2.5-1.5B-Instruct --adapter-path adapters --data dataset --test
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Linux with NVIDIA (experimental)
|
|
28
|
+
|
|
29
|
+
MLX publishes a CUDA package for Linux (CUDA 12, compute capability 7.5 or higher, driver 550.54.14 or higher, glibc 2.35 or newer, Python 3.10 or newer): <https://ml-explore.github.io/mlx/build/html/install.html>. mlx-lm only documents Apple silicon, so test it first:
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
python3 -m venv ~/.madre-train && source ~/.madre-train/bin/activate
|
|
33
|
+
pip install "mlx[cuda12]" mlx-lm
|
|
34
|
+
python3 -c "import mlx.core as mx; print('CUDA:', mx.cuda.is_available())" # must say True
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
If it says `True`, the training commands above (from `cd ~/.pulse/rooms/<project>`) and steps 3 and 4 are the same. If not, there's no tested path on Linux yet.
|
|
38
|
+
|
|
39
|
+
[`train.sh`](../../training/train.sh) in `docs/training/` does the same with checks. On 16 GB use the 1.5B; with 32 GB or more, `Qwen/Qwen2.5-3B-Instruct` or `Qwen/Qwen2.5-Coder-7B-Instruct` with `--batch-size 1`. A round of 600 iterations takes 10 to 40 minutes.
|
|
40
|
+
|
|
41
|
+
## 3 · Serve in Ollama
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
mlx_lm.fuse --model Qwen/Qwen2.5-1.5B-Instruct --adapter-path adapters --save-path fused
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Ollama reads GGUF. For Llama and Mistral `mlx_lm.fuse` accepts `--export-gguf`; for Qwen convert with llama.cpp:
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
git clone --depth 1 https://github.com/ggml-org/llama.cpp && pip install -r llama.cpp/requirements.txt
|
|
51
|
+
python3 llama.cpp/convert_hf_to_gguf.py fused --outfile madre.gguf --outtype q8_0
|
|
52
|
+
cp <MADRE folder>/docs/training/Modelfile . && ollama create madre-<project> -f Modelfile
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The name matters: `madre-<project>` in lowercase, with hyphens. At the next **RECHECK** in Modules › Local AI, `@madre` switches to that model and What it remembers says so. The `Modelfile` comes in `docs/training/` of the MADRE repository and of the npm package.
|
|
56
|
+
|
|
57
|
+
## 4 · Evaluate before trusting
|
|
58
|
+
|
|
59
|
+
Before promoting a new model, ask `@madre` the ten things the project did decide and the five it never discussed. It should get the first ones right and say "the project never discussed it" for the second ones. If it makes things up, go back to the base model (`ollama rm madre-<project>`) and train with more pairs or fewer iterations.
|
package/docs/training/README.md
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
# MADRE AI · entrenar el modelo del proyecto
|
|
2
2
|
|
|
3
|
-
Roadmap 2c.
|
|
3
|
+
Roadmap 2c. El proyecto exporta lo que recuerda como pares de chat; tú entrenas un modelo pequeño con ellos en tu máquina; Ollama lo sirve; `@madre` lo usa en cuanto existe. Todo local.
|
|
4
4
|
|
|
5
5
|
## 1 · Exportar
|
|
6
6
|
|
|
7
|
-
Desde
|
|
7
|
+
Desde **Ajustes › Lo que recuerda › Entrena tu propia IA › Exportar** (al terminar te dice en qué carpeta quedó), o en terminal:
|
|
8
8
|
|
|
9
9
|
```
|
|
10
|
-
madre dataset
|
|
10
|
+
npx @jossuealcala/madre dataset # en la carpeta del proyecto
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
Escribe `~/.pulse/rooms/<
|
|
13
|
+
Escribe `~/.pulse/rooms/<proyecto>/dataset/{train.jsonl, valid.jsonl, manifest.json}`. Cada línea es `{"messages":[{role:"system"},{role:"user"},{role:"assistant"}]}`: las respuestas reales del proyecto, redactados (sin rutas, nombres, correos ni claves), más las notas resumidas como pares de recuerdo. Lo que se envió en #0 INCÓGNITO nunca está.
|
|
14
14
|
|
|
15
15
|
Cuánto hace falta: con menos de ~200 pares el modelo aprende el tono y poco más; a partir de ~1000 empieza a valer. Exporta cada tanto; el split es determinista, así que las líneas no cambian de lado.
|
|
16
16
|
|
|
@@ -19,11 +19,23 @@ Cuánto hace falta: con menos de ~200 pares el modelo aprende el tono y poco má
|
|
|
19
19
|
```
|
|
20
20
|
python3 -m venv ~/.madre-train && source ~/.madre-train/bin/activate
|
|
21
21
|
pip install mlx-lm
|
|
22
|
-
cd ~/.pulse/rooms/<
|
|
22
|
+
cd ~/.pulse/rooms/<proyecto>
|
|
23
23
|
mlx_lm.lora --model Qwen/Qwen2.5-1.5B-Instruct --train --data dataset --iters 600 --batch-size 2 --learning-rate 1e-5 --adapter-path adapters
|
|
24
24
|
mlx_lm.lora --model Qwen/Qwen2.5-1.5B-Instruct --adapter-path adapters --data dataset --test
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
+
### Linux con NVIDIA (experimental)
|
|
28
|
+
|
|
29
|
+
MLX publica un paquete para CUDA en Linux (CUDA 12, capacidad de cómputo 7.5 o mayor, controlador 550.54.14 o mayor, glibc 2.35 o mayor, Python 3.10 o mayor): <https://ml-explore.github.io/mlx/build/html/install.html>. mlx-lm solo documenta Apple silicon, así que pruébalo primero:
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
python3 -m venv ~/.madre-train && source ~/.madre-train/bin/activate
|
|
33
|
+
pip install "mlx[cuda12]" mlx-lm
|
|
34
|
+
python3 -c "import mlx.core as mx; print('CUDA:', mx.cuda.is_available())" # debe decir True
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Si dice `True`, los comandos de entrenamiento de arriba (desde `cd ~/.pulse/rooms/<proyecto>`) y los pasos 3 y 4 son los mismos. Si no, no hay ruta probada en Linux todavía.
|
|
38
|
+
|
|
27
39
|
`train.sh` en esta carpeta hace lo mismo con comprobaciones. En 16 GB usa el 1.5B; con 32 GB o más, `Qwen/Qwen2.5-3B-Instruct` o `Qwen/Qwen2.5-Coder-7B-Instruct` con `--batch-size 1`. Una ronda de 600 iteraciones tarda de 10 a 40 minutos.
|
|
28
40
|
|
|
29
41
|
## 3 · Servir en Ollama
|
|
@@ -37,11 +49,11 @@ Ollama lee GGUF. Para Llama y Mistral `mlx_lm.fuse` acepta `--export-gguf`; para
|
|
|
37
49
|
```
|
|
38
50
|
git clone --depth 1 https://github.com/ggml-org/llama.cpp && pip install -r llama.cpp/requirements.txt
|
|
39
51
|
python3 llama.cpp/convert_hf_to_gguf.py fused --outfile madre.gguf --outtype q8_0
|
|
40
|
-
cp docs/training/Modelfile . && ollama create madre-<proyecto> -f Modelfile
|
|
52
|
+
cp <carpeta de MADRE>/docs/training/Modelfile . && ollama create madre-<proyecto> -f Modelfile
|
|
41
53
|
```
|
|
42
54
|
|
|
43
|
-
El nombre importa: `madre-<proyecto>` en minúsculas, con guiones. Al siguiente
|
|
55
|
+
El nombre importa: `madre-<proyecto>` en minúsculas, con guiones. Al siguiente **REVISAR OTRA VEZ** en Módulos › IA local, `@madre` cambia a ese modelo y Lo que recuerda lo dice. El `Modelfile` viene en `docs/training/` del repositorio de MADRE y del paquete de npm.
|
|
44
56
|
|
|
45
57
|
## 4 · Evaluar antes de confiar
|
|
46
58
|
|
|
47
|
-
Antes de promover un modelo nuevo, pregúntale a `@madre` las diez cosas que
|
|
59
|
+
Antes de promover un modelo nuevo, pregúntale a `@madre` las diez cosas que el proyecto sí decidió y las cinco que nunca discutió. Debe acertar las primeras y decir "el proyecto nunca lo discutió" en las segundas. Si inventa, vuelve al modelo base (`ollama rm madre-<proyecto>`) y entrena con más pares o menos iteraciones.
|
package/docs/training/train.sh
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# MADRE AI · one LoRA round on this room's dataset with mlx-lm (Apple Silicon
|
|
2
|
+
# MADRE AI · one LoRA round on this room's dataset with mlx-lm (Apple Silicon; Linux with an
|
|
3
|
+
# NVIDIA card through mlx[cuda12] is experimental: mlx-lm only promises Apple chips).
|
|
3
4
|
# docs/training/train.sh <room-dir> [base-model] [iters]
|
|
4
5
|
set -euo pipefail
|
|
5
6
|
ROOM="${1:?room dir, e.g. ~/.pulse/rooms/<sala>}"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jossuealcala/madre",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "MADRE · one local room where the AI coding agents already on your machine (Codex, Claude Code, Gemini CLI, OpenCode) work on your project together, share one memory you can see and explore, and answer to you. Read-only by default, with per-message permission modes and checkpoints you can undo. Nothing leaves your machine on its own.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-agents",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"url": "https://jossuealcala.com/en/"
|
|
37
37
|
},
|
|
38
38
|
"type": "module",
|
|
39
|
-
"license": "
|
|
39
|
+
"license": "AGPL-3.0-only",
|
|
40
40
|
"files": [
|
|
41
41
|
"bin",
|
|
42
42
|
"src",
|
|
@@ -54,7 +54,15 @@
|
|
|
54
54
|
"docs/INTERNALS.md",
|
|
55
55
|
"docs/REFERENCE.md",
|
|
56
56
|
"SECURITY.md",
|
|
57
|
-
"CONTRIBUTING.md"
|
|
57
|
+
"CONTRIBUTING.md",
|
|
58
|
+
"README.en.md",
|
|
59
|
+
"CHANGELOG.en.md",
|
|
60
|
+
"TRADEMARKS.en.md",
|
|
61
|
+
"ADDITIONAL-TERMS.md",
|
|
62
|
+
"docs/en/REFERENCE.md",
|
|
63
|
+
"docs/en/SDK.md",
|
|
64
|
+
"docs/en/INTERNALS.md",
|
|
65
|
+
"docs/en/training"
|
|
58
66
|
],
|
|
59
67
|
"bin": {
|
|
60
68
|
"madre": "./bin/madre.mjs",
|
package/public/abduction.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-only
|
|
2
|
+
// MADRE · © 2026 Jossue Alcalá · GNU AGPL v3 with additional terms (Section 7): ADDITIONAL-TERMS.md
|
|
1
3
|
// ABDUCTION — the one joke in the room, and the only thing here that is not work.
|
|
2
4
|
//
|
|
3
5
|
// Type MADRE into the box, alone, and send it. Not @madre: that is the local agent and it has a
|