openvibers/openvibe.codes Commits

OpenVibe Link: the new name in pages, docs and messages (#326)

OP OpenVibers committed 5 days ago · 170e6ec777 · 32 files, +48 −48

* OpenVibe Link: the new name in pages, docs and messages

OpenVibe Node is now OpenVibe Link. Pages, emails, notifications, docs, READMEs, error messages and tool descriptions say OpenVibe Link (or Link), and installer links point at openvibe.link. Code identifiers, the openvibe-node command, paths, contract ids and the repository name are unchanged.

* Codes setup test: the installer is at openvibe.link/install

* Migrations: keep applied files byte-identical (comments unchanged)

* OpenVibe Link: the last user-facing Node phrases

Changes

ddiff --git a/README.md b/README.md
iindex dab817c..434976b 100644
---- a/README.md
++++ b/README.md
@@@ -20,7 +20,7 @@ One place to send a coding task to the agent that fits it — by what the task n
| The harness | `openvibe-codes` ([bin/openvibe-codes.js](bin/openvibe-codes.js), [harness/](harness/)) | Runs on a person's own machine with their CLIs and keys; nothing goes through OpenVibe. See **Use the harness** below. |
| Home | `/` | What the harness is, the agents in the catalog, how a task finds its agent, Improve OpenVibe, and an honest table of what works today and what comes next. |
| Get started | `/start` | Install, the agents and what each needs, the commands, permission levels, hand-offs, the event format and using it from code. |
--| Run on your machine | `/run`, `/run/tasks/:id` ([server/http/run.js](server/http/run.js)) | A signed-in person starts a coding task in any browser and their own machine runs it: an OpenVibe Run job (class `code`, artifact `[email protected]`, `trust: user-owned`) that Codes starts for them in their project (project delegation: `X-OV-Subject`, `X-OV-Project`; [server/domain/run-client.js](server/domain/run-client.js)). Run places it only on a machine of theirs (added on openvibe.bot/machines), whose OpenVibe Node runs `openvibe-codes job` ([harness/job.js](harness/job.js)) in its job sandbox: clone, run the agents with hand-offs, hand back the summary, the changed files and the patch, and, when asked and the machine holds `GITHUB_TOKEN`, push a new branch (never the one it started from) and open a pull request. The page streams the job's lines (Run's job stream through Codes) and keeps the result (`run_tasks`, migration 0003). Each task is its owner's; account export and deletion cover them (ADR-033, [server/account-data.js](server/account-data.js)). The task page shows the **timeline of attempts** above the agents' **session** (each attempt with its why: "Codex hit its limit, so Command Code took over with what it had done") and the session with openvibe-shared/agent-session.js: steps grouped, each step's command, change and output (live while it runs), helpers nested, hand-offs and the plan; after the end, the session from `codes_session_lines` (migration 0007): the lines are kept as the stream arrives, back-filled from the machine's final log, and replayed at `GET /run/tasks/:id/session.json?after=` (the owner, or a service with `codes.task.read` and `X-OV-Subject`); tasks from before keep the old `log`. Run's `run.job.*` events finalize a task at `POST /internal/events` ([server/events-consumer.js](server/events-consumer.js), no polling but a 10-minute fallback), and every state change is published as `codes.task.queued|running|succeeded|failed|cancelled` from an outbox ([server/events/outbox.js](server/events/outbox.js), contracts 0.232.0). |
++| Run on your machine | `/run`, `/run/tasks/:id` ([server/http/run.js](server/http/run.js)) | A signed-in person starts a coding task in any browser and their own machine runs it: an OpenVibe Run job (class `code`, artifact `[email protected]`, `trust: user-owned`) that Codes starts for them in their project (project delegation: `X-OV-Subject`, `X-OV-Project`; [server/domain/run-client.js](server/domain/run-client.js)). Run places it only on a machine of theirs (added on openvibe.bot/machines), whose OpenVibe Link runs `openvibe-codes job` ([harness/job.js](harness/job.js)) in its job sandbox: clone, run the agents with hand-offs, hand back the summary, the changed files and the patch, and, when asked and the machine holds `GITHUB_TOKEN`, push a new branch (never the one it started from) and open a pull request. The page streams the job's lines (Run's job stream through Codes) and keeps the result (`run_tasks`, migration 0003). Each task is its owner's; account export and deletion cover them (ADR-033, [server/account-data.js](server/account-data.js)). The task page shows the **timeline of attempts** above the agents' **session** (each attempt with its why: "Codex hit its limit, so Command Code took over with what it had done") and the session with openvibe-shared/agent-session.js: steps grouped, each step's command, change and output (live while it runs), helpers nested, hand-offs and the plan; after the end, the session from `codes_session_lines` (migration 0007): the lines are kept as the stream arrives, back-filled from the machine's final log, and replayed at `GET /run/tasks/:id/session.json?after=` (the owner, or a service with `codes.task.read` and `X-OV-Subject`); tasks from before keep the old `log`. Run's `run.job.*` events finalize a task at `POST /internal/events` ([server/events-consumer.js](server/events-consumer.js), no polling but a 10-minute fallback), and every state change is published as `codes.task.queued|running|succeeded|failed|cancelled` from an outbox ([server/events/outbox.js](server/events/outbox.js), contracts 0.232.0). |
| Knowledge | `/knowledge`, `GET/POST /api/v1/knowledge` ([server/http/knowledge.js](server/http/knowledge.js), [server/domain/knowledge.js](server/domain/knowledge.js)) | Workspace knowledge (Know K5): when a run ends, Codes captures what it taught — the agent's `Learned:` lines, a failure with its reason, the command that failed and the commands the repository uses — redacts secrets and stores it as a private row with its source run (`codes_knowledge`, migration 0025). The next run's job carries the relevant rows in its prompt before the brief, for every harness ([harness/job.js](harness/job.js) `agentPrompt`), so a pin bump, a test command or a trap is recalled without re-reading files. The page shows every row with its run, and edits, pins, shares (with a Network project's team) and deletes it; private by default. The same rows are exported and erased with the account ([server/account-data.js](server/account-data.js)); `ov-know`'s `knowledge_search`/`knowledge_add` ([know/knowledge.js](https://github.com/OpenVibers/openvibe-agents)) answer them for the person's own agents. |
| Skills hub | `/skills`, `/api/v1/skills`, `/api/v1/me/skills` ([server/http/skills-pages.js](server/http/skills-pages.js), [server/domain/skills.js](server/domain/skills.js)) | Skills, rules, MCP servers, commands and hooks kept per person and per project, versioned (`codes_skills`, `codes_skill_versions`, migration 0028): every save is an immutable version, a rollback copies an old version into a new one, and the body is rendered through the markup gate. Sharing is `openvibe-sdk/share`'s four audiences (`public`, `link`, `people`, `private`); only `public` is in the gallery, and the share sheet is the SDK's at `/api/v1/skill/:id/share`. A person adds a record to their sync, and `openvibe-node skills sync` writes it into Claude Code, Codex, OpenCode and Command Code inside one marked block, with a dry run, a diff and an undo. |
| The agents | `/harnesses` | Every harness in [`server/data/harness-offers.json`](server/data/harness-offers.json) (validated at boot as `platform.harness-offer@1`, each model as `platform.agent-offer@1`) with its provider, address kind, what it can do, price and limits, and its models with context limits and token prices. No local path is ever shown. |
ddiff --git a/STATUS.json b/STATUS.json
iindex a60e1e4..f47cc53 100644
---- a/STATUS.json
++++ b/STATUS.json
@@@ -20,7 +20,7 @@
"openvibe-codes on a person's machine (bin/openvibe-codes.js, harness/): agents, route, run, runs, show, resume; installs from the release tag",
"openvibe-codes task (bin/task.js): a coding task on openvibe.codes as the person — start from a brief (with --name, --tier, --permission review|edit|full), list running/needs/done, follow the event stream, continue, stop",
"The conductor (C9b): a task whose single attempt is a long-lived Claude Code session on a machine the person chooses, with their own plan login; each message from the web or the phone is the next turn of that session, and its work is handed to the other agents through openvibe-codes mcp (codes_task_start, list, show, continue, stop, fleet, ship status, approve), with a risky call approved or denied from the phone",
-- "The fleet (C9c): what each agent on the person's machines has left — installed, signed in, and every limit window with when it frees — reported by each machine's Node with its heartbeat and kept here (labels, numbers and times only); the router skips an agent whose window is full or that is not signed in, prefers a flat-price plan with room, and leaves out a pay-per-token agent whose balance is nearly gone",
++ "The fleet (C9c): what each agent on the person's machines has left — installed, signed in, and every limit window with when it frees — reported by each machine's Link with its heartbeat and kept here (labels, numbers and times only); the router skips an agent whose window is full or that is not signed in, prefers a flat-price plan with room, and leaves out a pay-per-token agent whose balance is nearly gone",
"A task with no agent named is auto: the router reads the fleet and picks, and a task no agent may take says why and needs a person; a finished or unfinished task tells the person once (Settings: notify_done, notify_needs), and a pull request's CI, merge and deploy are on the task page",
"Adapters for Claude Code, Codex, OpenCode, Command Code and Aider (their CLIs) and for the DeepSeek API and any OpenAI-compatible server (the harness's own agent loop with list/read/grep/write/edit/run tools, confined to the working directory)",
"One event stream for every agent, in Claude Code's stream-json shape, ending with exactly one result; keys and tokens redacted",
@@@ -54,12 +54,12 @@
"Improve OpenVibe",
"community documents",
"redirects for the moved developer console",
-- "run a coding task on your own machine from any browser (/run; openvibe-codes job through OpenVibe Run and the person's own OpenVibe Node)",
++ "run a coding task on your own machine from any browser (/run; openvibe-codes job through OpenVibe Run and the person's own OpenVibe Link)",
"user JSON API for signed-in people to list, start, read and cancel their own tasks",
"proof of a finished task: its tests, screenshots, diff and a second-model review, and the outcome in plain words",
"the conductor (C9b): one Claude Code session on the person's own machine, driven from the browser or the phone, with its approvals",
"the job runner (CLI 0.6.0): each turn at the effort a person picks, in each agent's own words, and a conversation's next turn starting from the work before it (the last diff applied with git apply --3way, and the conversation so far), on any agent",
-- "the job runner (CLI 0.6.1): \"edit\" means edits on the person's own computer (OpenCode and Command Code run unattended only in a Node sandbox or for full; Aider suggests no shell commands there), and a private repository clones with a token the machine holds (GITHUB_TOKEN, GITLAB_TOKEN, OPENVIBE_GIT_CREDENTIALS) sent as a header, never in the URL or .git/config",
++ "the job runner (CLI 0.6.1): \"edit\" means edits on the person's own computer (OpenCode and Command Code run unattended only in a Link sandbox or for full; Aider suggests no shell commands there), and a private repository clones with a token the machine holds (GITHUB_TOKEN, GITLAB_TOKEN, OPENVIBE_GIT_CREDENTIALS) sent as a header, never in the URL or .git/config",
"The guided setup (/setup, owner 2026-11-03): one page of five steps read live: your machine (paired, online), your agents (installed, signed in, usage; the install and sign-in commands for the ones missing), your AI keys on Your AI (optional), OpenVibe Git (optional), your choices (default agent and machine, the conductor's fallbacks, safety); the home says how far along until it is done; every agent picker (Settings, the setup, the composer) says what each agent is ready for, drawn by Shared's select (data-state, data-badge, data-desc, a setup link)",
"The conductor's failover (CLI 0.7.0, owner 2026-11-03): Settings → Conductor picks up to three fallback agents (Codex, OpenCode) with a model each and a switch point (90% of a usage window by default); before each turn the conductor's own agent and the fallbacks are read against the machine's usage windows, the turn runs on the first with room and returns to the primary once its window frees; each agent resumes its own session and the one taking over is told what happened while it was away; the conductor's page shows each agent's usage and which takes the next turn",
"OpenVibe Git through the person's own account (/git, CLI 0.6.2): link once (the forge's OAuth2 consent with PKCE, tokens sealed with CODES_SEAL_KEY), make a repository in your name and start a chat on it; a task on a forge repository takes the person's credential line to their machine for git only (never the agent), and the branch and its pull request open on OpenVibe Git"
ddiff --git a/bin/mcp.js b/bin/mcp.js
iindex 0eaaf9f..3f2dd3d 100644
---- a/bin/mcp.js
++++ b/bin/mcp.js
@@@ -97,7 +97,7 @@ const TOOLS = [
},
{
name: 'codes_fleet',
-- description: 'The coding agents on the owner\'s machines: installed, signed in, and every limit window with when it frees. Each Node reports its fleet with its heartbeat; this machine is read directly from the CLIs\' own files. Read-only.',
++ description: 'The coding agents on the owner\'s machines: installed, signed in, and every limit window with when it frees. Each Link reports its fleet with its heartbeat; this machine is read directly from the CLIs\' own files. Read-only.',
inputSchema: { type: 'object', properties: {}, additionalProperties: true },
},
{
@@@ -203,7 +203,7 @@ async function stopTask(args) {
return text({ id: t.id, state: t.state, outcome: t.outcome ? t.outcome.state : null });
}
--/** This machine's own reading: the CLIs' files, the same answer the Node sends with its heartbeat. */
++/** This machine's own reading: the CLIs' files, the same answer the Link sends with its heartbeat. */
function here() {
const rows = probeLogins({ home: os.homedir() });
return {
ddiff --git a/bin/openvibe-codes.js b/bin/openvibe-codes.js
iindex 927682c..9b7eb23 100755
---- a/bin/openvibe-codes.js
++++ b/bin/openvibe-codes.js
@@@ -36,7 +36,7 @@ const USAGE = `openvibe-codes ${VERSION} — code with any agent, from your term
openvibe-codes show <run id> [--json] one run's attempts and result (--json: every event)
openvibe-codes resume <run id> "follow-up" continue a run's last session with its agent
openvibe-codes job one task as an OpenVibe Run job (JSON input on stdin; the
-- OpenVibe Node's job worker runs it on your own machine)
++ OpenVibe Link's job worker runs it on your own machine)
openvibe-codes task start|list|show|continue|stop coding tasks on openvibe.codes, as you, on one of
your machines (openvibe-codes task --help)
openvibe-codes mcp the Codes MCP tools for a Claude Code session: tasks, the fleet,
ddiff --git a/docs/adr/050-codes-harness-fabric.md b/docs/adr/050-codes-harness-fabric.md
iindex 921e909..deb7bcb 100644
---- a/docs/adr/050-codes-harness-fabric.md
++++ b/docs/adr/050-codes-harness-fabric.md
@@@ -58,7 +58,7 @@ first `docs/adr/` — the plan (§8) lists **ADR-050 Codes Harness Fabric** amon
- **The product half of T16 does not exist in Codes yet.** There is no workspace, editor, terminal, task feed, diff,
review verdict, cost, or branch/PR code in `server/` — the strings do not appear. T16's product line is
"repository browser, workspace, editor, terminal, task feed, tests, diff, harness explanation, review verdict, cost,
-- branch/PR flow — usable hosted and on a Node (T14) for local harnesses", and T16's dependency line is
++ branch/PR flow — usable hosted and on a Link (T14) for local harnesses", and T16's dependency line is
"T1 (Fabric offers), T2 (projects/grants), T5 (metering/Vibes), T17 (Actor for planning), T14 (Run/Node for
execution)". The plan does not use the phrase "forge model"; the repository/branch/PR authority it describes is
Forgejo/GitHub under T16, and the project/grant authority is T2.
ddiff --git a/engine/README.md b/engine/README.md
iindex 20197a5..87fb476 100644
---- a/engine/README.md
++++ b/engine/README.md
@@@ -45,7 +45,7 @@ normaliser (`transcript.js`) into job lines (`joblines.js`), and a budget stop b
### `prompt.js` — the prompt every job gets
The git rule, the house rules, the lessons, the repo digest (`repomap.js`), then the brief. The lessons come from the
--shared Know store's own `recall()` (the portal's `know/lessons.js`; on a Node its path is configuration —
++shared Know store's own `recall()` (the portal's `know/lessons.js`; on a Link its path is configuration —
`CODES_KNOW_LESSONS` — never a hard-coded home). A store that cannot be read gives no lessons, not an error.
### `logins.js` — what this machine has
ddiff --git a/engine/logins.js b/engine/logins.js
iindex ec1b85d..954e952 100644
---- a/engine/logins.js
++++ b/engine/logins.js
@@@ -4,7 +4,7 @@
* What this machine has: each coding CLI, whether it is installed, whether it is signed in, and the limit windows it
* can read.
*
-- * The plan logins are the owner's own files (the Node's worker copies the ones the owner names into the job's private
++ * The plan logins are the owner's own files (the Link's worker copies the ones the owner names into the job's private
* home, read-only, for that job only). This module reads them the way the portal's probes do:
*
* installed the CLI is on PATH
ddiff --git a/engine/prompt.js b/engine/prompt.js
iindex 671ee73..7b0bf7e 100644
---- a/engine/prompt.js
++++ b/engine/prompt.js
@@@ -24,7 +24,7 @@ function fragment(name) {
/**
* The lessons a job gets: what earlier jobs learned here, through the shared Know store's own `recall()`
-- * (the portal's know/lessons.js; on a Node its path comes from the configuration, never a hard-coded home).
++ * (the portal's know/lessons.js; on a Link its path comes from the configuration, never a hard-coded home).
*
* The store is optional and off by default. A path that cannot be required, a module without recall(), or a recall
* that throws gives no lessons, never an error: a job must not fail because the memory is unreachable.
@@@ -55,7 +55,7 @@ function lessons({ file, repo, brief, env = process.env } = {}) {
* @param {string} opts.brief what the task is
* @param {string} opts.dir the folder the job works in
* @param {object} [opts.env] the environment (CODES_KNOW_LESSONS, or the older OPENVIBE_CODES_LESSONS)
-- * @param {string} [opts.know] the Know module's path (the Node's config wins over the environment)
++ * @param {string} [opts.know] the Know module's path (the Link's config wins over the environment)
* @returns {{text: string, parts: object[]}} the prompt and the parts it is made of
*/
function assemble({ brief, dir, env = process.env, know } = {}) {
ddiff --git a/harness/job.js b/harness/job.js
iindex f0c9544..0c9b80a 100644
---- a/harness/job.js
++++ b/harness/job.js
@@@ -1,14 +1,14 @@
'use strict';
/**
-- * `openvibe-codes job`: one coding task as an OpenVibe Run job on the person's own machine (plan T16; the Node's job
++ * `openvibe-codes job`: one coding task as an OpenVibe Run job on the person's own machine (plan T16; the Link's job
* worker runs it in its sandbox as artifact `[email protected]`, JOB_PROTOCOL below). The job's input is one JSON
* line on stdin:
*
* { repo, ref?, prompt, agent?, model?, permission?: 'edit'|'full', push?: { branch?, pull_request? } }
*
* It clones `repo` (https only) into a scratch directory, runs the harness there with hand-offs, and prints one compact
-- * JSON line per event on stdout (what the page shows live). The result goes to fd 3 (the Node's result channel), or as
++ * JSON line per event on stdout (what the page shows live). The result goes to fd 3 (the Link's result channel), or as
* a last `{ t: 'result_doc' }` line when fd 3 is not open: what the agents said, the changed files and the patch, and,
* when `push` is asked and the machine holds GITHUB_TOKEN, the new branch it pushed (never the base branch) and its
* pull request. Nothing here ever prints a token.
@@@ -22,7 +22,7 @@ const { collectProof } = require('../engine/proof');
const JOB_PROTOCOL = '1.0.0';
/** The agents a conductor's turn may run on: every harness Codes offers. */
// Every harness can lead a conductor; the three that resume a session and take the Codes MCP tools are
--// the failover chain's own (server/domain/conductor-failover.js). The job's contract with the Node (stdin, stdout
++// the failover chain's own (server/domain/conductor-failover.js). The job's contract with the Link (stdin, stdout
// lines, fd 3) is unchanged by 1.1's lines.
const CONDUCTOR_AGENTS = ['claude-code', 'claude-other', 'codex', 'opencode', 'command-code', 'minimax-code', 'aider', 'deepseek', 'openvibe-ai', 'openai-compatible'];
const LINES = '1.1'; // 1.1: lines carry step ids, inputs, results, progress, helpers; the result keeps `log`
@@@ -115,7 +115,7 @@ function cloneProblem(message, repo) {
if (!/could not read Username|Authentication failed|terminal prompts disabled|403|401|not found/i.test(m)) return m;
let host = 'its host';
try { host = new URL(String(repo)).host; } catch { /* keep the words */ }
-- return `${m} — if the repository is private, give this machine's Codes runner a token for ${host} (GITHUB_TOKEN, GITLAB_TOKEN, or an OPENVIBE_GIT_CREDENTIALS line https://you:token@${host}) in the Node's config.json env, then restart the Node`;
++ return `${m} — if the repository is private, give this machine's Codes runner a token for ${host} (GITHUB_TOKEN, GITLAB_TOKEN, or an OPENVIBE_GIT_CREDENTIALS line https://you:token@${host}) in the Link's config.json env, then restart the Node`;
}
function scrub(s) {
@@@ -734,7 +734,7 @@ async function runJob({ input, harness, emit, writeResult, env = process.env, tm
prompt: agentPrompt(job), task: 'edit', agent: job.agent, model: job.model, effort: job.effort || null, cwd: work,
permission: job.permission, handoff: !job.agent, maxAttempts: job.kind === 'conductor' ? 1 : 3,
// A job on the person's Node runs in its sandbox (own namespaces, a read-only root, no home folder, only
-- // what the job was given); the Node sets OPENVIBE_JOB_ID there. Only inside it may an agent with no
++ // what the job was given); the Link sets OPENVIBE_JOB_ID there. Only inside it may an agent with no
// edits-only mode take an "edit" task unattended (harness/index.js unfitFor).
sandboxed: !!env.OPENVIBE_JOB_ID,
// A task carried on in the same conversation resumes its session (RESUME in the environment, or the job's
@@@ -807,7 +807,7 @@ async function runJob({ input, harness, emit, writeResult, env = process.env, tm
return result;
}
--/** The Node's result channel is fd 3; outside a job the result is the last stdout line. */
++/** The Link's result channel is fd 3; outside a job the result is the last stdout line. */
function resultWriter(emit) {
return (doc) => {
const text = JSON.stringify(doc);
ddiff --git a/server/config.js b/server/config.js
iindex 5918768..d761921 100644
---- a/server/config.js
++++ b/server/config.js
@@@ -165,7 +165,7 @@ function load(env = process.env) {
// The tokens one attempt may spend, when the task does not say (a run token's budget, Codes C3).
tokens: int(env.CODES_TASK_TOKENS, 0),
},
-- // Know (the shared lessons store, Know K): on a Node this is the portal's know/lessons.js, never a hard-coded
++ // Know (the shared lessons store, Know K): on a Link this is the portal's know/lessons.js, never a hard-coded
// home. The path is configuration; a store that cannot be read gives no lessons, never an error.
know: { lessons: String(env.CODES_KNOW_LESSONS || env.OPENVIBE_CODES_LESSONS || '').trim() },
// The task's timeline (stage C2): how many automatic continues one task may have before it needs a person.
ddiff --git a/server/domain/conductor.js b/server/domain/conductor.js
iindex 2c50ce0..77222d5 100644
---- a/server/domain/conductor.js
++++ b/server/domain/conductor.js
@@@ -547,7 +547,7 @@ function createConductor({ store, config = {}, log = console, run, attempts, tas
const session = ran.session || (agent === (task.agent || PRIMARY_AGENT) && !turn.agent ? (attempt && attempt.session) : null) || null;
let note = turn.switch_note || null;
if (agent !== asked) {
-- note = `${note ? `${note} ` : ''}Your machine ran ${failover.nameOf(agent)} instead of ${failover.nameOf(asked)}: its Codes runner is older than the switch. Update OpenVibe Node on it to let the conductor switch agents.`.slice(0, 1000);
++ note = `${note ? `${note} ` : ''}Your machine ran ${failover.nameOf(agent)} instead of ${failover.nameOf(asked)}: its Codes runner is older than the switch. Update OpenVibe Link on it to let the conductor switch agents.`.slice(0, 1000);
}
await db.exec(
`UPDATE codes_conductor_messages SET state = $2, result = $3::jsonb, error = $4, session = $5, finished_at = $6::timestamptz,
ddiff --git a/server/domain/fleet.js b/server/domain/fleet.js
iindex bb659f8..0d19f3e 100644
---- a/server/domain/fleet.js
++++ b/server/domain/fleet.js
@@@ -4,10 +4,10 @@
* The fleet (Codes C9c): what each of a person's machines says about its coding agents right now — installed, signed
* in, and every limit window with when it frees.
*
-- * The machine reports it with its heartbeat to OpenVibe.Bot (OpenVibe.Node `logins`), and Bot answers it for the
++ * The machine reports it with its heartbeat to OpenVibe.Bot (OpenVibe Link `logins`), and Bot answers it for the
* machine's owner at GET /api/v1/machines with the person's own token. This module reads that answer, keeps the latest
* per machine in codes_machine_fleet, and answers the Agents page, the router and the MCP fleet tool from it. It stores
-- * no key, no token and no account name: the Node reports labels, numbers and times only.
++ * no key, no token and no account name: the Link reports labels, numbers and times only.
*
* createFleet({ config, store, fetchImpl, log, now })
* refresh(subject, token) read Bot now and replace the stored rows → { ok, machines } or { ok: false, problem }
@@@ -121,7 +121,7 @@ function createFleet({ config = {}, store, fetchImpl = globalThis.fetch, log = c
/**
* Read Bot now and replace what Codes holds for each machine whose answer names a fleet. A machine that is gone from
-- * the answer is gone here too; a machine that answers no fleet keeps its last reading (the Node may be older).
++ * the answer is gone here too; a machine that answers no fleet keeps its last reading (the Link may be older).
*/
async function refresh(subject, token) {
if (!USER.test(String(subject || '')) || !token) return { ok: false, problem: { status: 401, detail: 'sign in again to see your machines' } };
ddiff --git a/server/domain/machines.js b/server/domain/machines.js
iindex c5d2d35..593cf0b 100644
---- a/server/domain/machines.js
++++ b/server/domain/machines.js
@@@ -3,7 +3,7 @@
/**
* A person's machines, as OpenVibe.Network knows them (Codes C4b).
*
-- * A machine is paired by redeeming a one-time code on OpenVibe.Network (docs: OpenVibe.Node README and
++ * A machine is paired by redeeming a one-time code on OpenVibe.Network (docs: OpenVibe Link README and
* internal/link/network.go): openvibe.bot/machines mints `pair_…` with a code that lasts ten minutes, and the machine
* redeems it -> `openvibe-node pair --network <URL> --pairing pair_… --code ABCD-1234`. The result is a node principal
* (`nod_…`) owned by the person; that principal, its status and when it was last seen are what this module reads.
ddiff --git a/server/domain/skills.js b/server/domain/skills.js
iindex 91dd074..729b259 100644
---- a/server/domain/skills.js
++++ b/server/domain/skills.js
@@@ -287,7 +287,7 @@ function createSkills({ store, sharing, log = console }) {
await db.exec('DELETE FROM codes_skill_assignments WHERE subject = $1 AND project = $2 AND skill_id = $3', [subject, scope, id]);
}
-- /** What the Node syncs: the person's picks, plus this project's picks, at their newest versions. */
++ /** What the Link syncs: the person's picks, plus this project's picks, at their newest versions. */
async function manifest({ subject, project = '' }) {
const scope = String(project || '');
const rows = await db.many(`SELECT ${COLUMNS} ${FROM}
ddiff --git a/server/http/actor-limits.js b/server/http/actor-limits.js
iindex 3335034..bf91c1a 100644
---- a/server/http/actor-limits.js
++++ b/server/http/actor-limits.js
@@@ -47,7 +47,7 @@ const BUDGETS = {
// The skills hub (Compute plan §5): save a version or share a record now and then; a script editing a library
// may do a few a minute, not a flood.
'codes.skill.write': { minute: 20, hour: 300 },
-- // Adding a skill to the sync is how the Node is set up; the first run adds several, then almost never.
++ // Adding a skill to the sync is how the Link is set up; the first run adds several, then almost never.
'codes.skill.assign': { minute: 30, hour: 600 },
};
ddiff --git a/server/http/c4.js b/server/http/c4.js
iindex dd91795..c4092f7 100644
---- a/server/http/c4.js
++++ b/server/http/c4.js
@@@ -7,7 +7,7 @@
* / the composer with the person's saved repositories and machines (last used first), running work
* /tasks, /tasks/:id the person's tasks; a task's Live, Changes, Proof and Pull request panels are Codes' own
* /agents the person's connections: their provider keys (they live in OpenVibe.AI), added, tested, removed
-- * /machines their paired Node machines, their state, and Node's pairing flow with a one-time code
++ * /machines their paired Link machines, their state, and Node's pairing flow with a one-time code
* /usage what Codes knows: runs, minutes and tokens per day (no prices: Billing is Billing's)
* /memory what the person's own assistants know about them (the notes live in OpenVibe.AI's store)
* /settings their choices, remembered per person (codes_user_settings)
@@@ -753,7 +753,7 @@ ${before ? `<h3 class="c4-subhead">Saved in Codes before</h3><ul class="c4-rows"
<p class="c4-sub">One command, one code</p>
<ol class="c4-steps">
<li><b>Get a one-time code</b> on <a href="https://openvibe.bot/machines">openvibe.bot/machines</a>. It lasts ten minutes and works once.</li>
--<li><b>Run the installer on the machine</b>, with the code it gives you: <code class="c4-code-inline">curl -fsSL https://node.openvibe.network/install | sh -s -- --worker</code></li>
++<li><b>Run the installer on the machine</b>, with the code it gives you: <code class="c4-code-inline">curl -fsSL https://openvibe.link/install | sh -s -- --worker</code></li>
<li><b>It pairs itself</b>, then appears here as Online. A machine runs your jobs and nobody else's.</li>
</ol>
<form class="c4-form c4-form-inline" method="get" action="/machines">
@@@ -761,7 +761,7 @@ ${before ? `<h3 class="c4-subhead">Saved in Codes before</h3><ul class="c4-rows"
<button class="c4-button" type="submit">Show the command</button>
</form>
${badCode ? note('a pairing code is 8 letters and digits, like ABCD-1234', 'bad') : ''}
--${code ? `<div class="c4-pair-command"><p class="c4-kv"><span>On the machine</span><code class="c4-code-inline">sudo openvibe-node pair ${esc(code)} --network https://openvibe.network</code></p><p class="c4-kv"><span>Or with the installer</span><code class="c4-code-inline">curl -fsSL https://node.openvibe.network/install | sh -s -- --network https://openvibe.network --pairing <span class="c4-note-inline">pair_… from openvibe.bot</span> --code ${esc(code)} --worker</code></p><p class="c4-hint">The code is the one openvibe.bot gave you for this machine; Codes never mints it. Reload this page once the installer says it is paired.</p></div>` : ''}
++${code ? `<div class="c4-pair-command"><p class="c4-kv"><span>On the machine</span><code class="c4-code-inline">sudo openvibe-node pair ${esc(code)} --network https://openvibe.network</code></p><p class="c4-kv"><span>Or with the installer</span><code class="c4-code-inline">curl -fsSL https://openvibe.link/install | sh -s -- --network https://openvibe.network --pairing <span class="c4-note-inline">pair_… from openvibe.bot</span> --code ${esc(code)} --worker</code></p><p class="c4-hint">The code is the one openvibe.bot gave you for this machine; Codes never mints it. Reload this page once the installer says it is paired.</p></div>` : ''}
</section>`;
const content = `${header('Machines', 'Your own computers, paired with your account.')}${viewer(req) ? `${flash(req)}<section class="c4-card"><div class="c4-card-head"><h2>Your machines</h2></div><p class="c4-sub">${(mine.machines || []).length} paired</p><p class="c4-hint"><a href="https://openvibe.network/machines">All your machines are on openvibe.network/machines</a>.</p>${mine.ok ? (list ? `<ul class="c4-rows">${list}</ul>` : '<div class="c4-emptyline">No paired computers yet. Pair one below and it shows up here.</div>') : note(`Your machines could not be read. ${esc((mine.problem && mine.problem.detail) || 'Sign in again.')}`, 'bad')}</section>${pairCard}` : signIn(req)}`;
page(req, res, 'Machines', wrap(content));
@@@ -826,7 +826,7 @@ ${report.days.length ? `<div class="table-wrap" tabindex="0" role="region" aria-
<p class="c4-hint">When your conductor's agent nears a usage limit (its 5-hour or weekly window), the next turn runs on the first fallback below that has room, and returns once the window frees. Each agent keeps its own session; the one taking over is told what happened while it was away.</p>
<div class="c4-fallbacks">${[0, 1, 2].map(fallbackRow).join('')}</div>
<div class="c4-form-grid"><label>Switch at <select name="conductor_switch_pct">${[70, 80, 85, 90, 95].map((v) => `<option value="${v}"${switchPct === v ? ' selected' : ''}>${v}% of a window</option>`).join('')}</select></label></div>
--<p class="c4-hint">Codex and OpenCode have no approval prompt: on "Ask first" they work with edits in the folder only. Your machine needs OpenVibe Node with Codes 0.7 or newer to switch.</p>` : '';
++<p class="c4-hint">Codex and OpenCode have no approval prompt: on "Ask first" they work with edits in the folder only. Your machine needs OpenVibe Link with Codes 0.7 or newer to switch.</p>` : '';
const content = `${header('Settings', 'Your choices, remembered for your account.')}${viewer(req) ? `${flash(req)}<p class="c4-hint c4-setup-link">New here, or something not working? <a href="/setup">The guided setup</a> checks your machine, agents and keys step by step.</p><section class="c4-card"><form class="c4-form" method="post" action="/settings">
<div class="c4-form-grid">
<label>Default agent <select name="default_agent"><option value="">Auto</option>${agentOptions}</select></label>
ddiff --git a/server/http/memory.js b/server/http/memory.js
iindex ce9915e..a5335f8 100644
---- a/server/http/memory.js
++++ b/server/http/memory.js
@@@ -30,7 +30,7 @@ const express = require('express');
const { asyncRouter } = require('./router');
const { esc } = require('../render/html');
--/** The adapter ids the Node reads and the services that write, and the names a person knows them by. */
++/** The adapter ids the Link reads and the services that write, and the names a person knows them by. */
const SOURCE_NAMES = {
'claude-code': 'Claude Code', agents: 'AGENTS.md', gemini: 'Gemini CLI', cursor: 'Cursor',
copilot: 'GitHub Copilot', windsurf: 'Windsurf', cline: 'Cline', roo: 'Roo Code', kiro: 'Kiro',
@@@ -314,7 +314,7 @@ function createMemoryRoutes(ctx, kit) {
if (!rows.length) {
return head + '<div class="c4-emptyline">Nothing here yet. On your computer, run '
+ '<code class="c4-code-inline">openvibe-node memory upload</code>: it shows what it found and asks before sending anything. '
-- + '<a href="https://github.com/OpenVibers/OpenVibe.Node/blob/main/docs/memory.md#the-upload">The Node docs</a></div></section>';
++ + '<a href="https://github.com/OpenVibers/OpenVibe.Node/blob/main/docs/memory.md#the-upload">The Link docs</a></div></section>';
}
const sourceRow = (row) => {
const fresh = row.seen && Date.now() - row.seen < SEVEN_DAYS;
ddiff --git a/server/http/pages.js b/server/http/pages.js
iindex c18b17b..a8dd45a 100644
---- a/server/http/pages.js
++++ b/server/http/pages.js
@@@ -104,7 +104,7 @@ Added test/date.test.js: three cases, all pass.
const statusTable = table(['Piece', 'State'], [
[html`<a href="/start">The harness on your machine</a> (<code>openvibe-codes</code>)`, `Live, ${VERSION}: runs a task on any of the eight agents with your CLIs and keys, streams one event format, keeps every run to show or resume.`],
['Hand-offs between agents', 'Live: a failed, rate-limited or silent agent hands the task to the next one with a note on what was done.'],
-- [html`<a href="/run">Run on your machine</a>: start a task in any browser, your phone included; your own computer runs it`, 'Live: add your machine on openvibe.bot/machines (one command), then watch the task live here and get the patch, a branch or a pull request. It runs only on your own machines, in the Node\'s job sandbox.'],
++ [html`<a href="/run">Run on your machine</a>: start a task in any browser, your phone included; your own computer runs it`, 'Live: add your machine on openvibe.bot/machines (one command), then watch the task live here and get the patch, a branch or a pull request. It runs only on your own machines, in Link\'s job sandbox.'],
[html`<a href="/harnesses">The agent catalog</a>`, 'Live: every harness and model with its capabilities, prices and limits.'],
[html`<code>GET /api/v1/harnesses</code>, <code>POST /api/v1/harnesses/route</code>`, 'Live: public, no key needed; per-address and per-caller limits.'],
[html`<a href="/improve">Improve OpenVibe</a>: a repository, a change on a branch with its test, a pull request that CI checks and a maintainer reviews`, 'Live: pick a repository, describe the change, and start a guided run on your machine.'],
ddiff --git a/server/http/run.js b/server/http/run.js
iindex 247dd2a..13f08e0 100644
---- a/server/http/run.js
++++ b/server/http/run.js
@@@ -139,7 +139,7 @@ ${error ? html`<p class="run-notice run-notice-error" role="alert">${error}</p>`
title: 'Run on your machine', index: true,
body: html`<h1>Run a coding task on your own machine</h1>
<p class="lead">Start a task from any browser, your phone included, and your own computer does the work: the agents and keys you already have, in a sandbox, with nothing to host. You watch it live here and get the changes back as a patch, a branch or a pull request.</p>
--<ol class="run-steps"><li>Add your computer on <a href="${MACHINES_URL}">openvibe.bot/machines</a>: one command, which installs OpenVibe Node with its job worker.</li><li>Sign in here and describe the task.</li><li>Your machine clones the repository, runs the agent and sends the result back.</li></ol>
++<ol class="run-steps"><li>Add your computer on <a href="${MACHINES_URL}">openvibe.bot/machines</a>: one command, which installs OpenVibe Link with its job worker.</li><li>Sign in here and describe the task.</li><li>Your machine clones the repository, runs the agent and sends the result back.</li></ol>
<p><a class="run-btn run-btn-primary" href="/auth/login?next=%2Frun">Sign in to start</a></p>`,
});
}
ddiff --git a/server/http/setup.js b/server/http/setup.js
iindex ddab753..f2369e0 100644
---- a/server/http/setup.js
++++ b/server/http/setup.js
@@@ -84,7 +84,7 @@ function createSetupRoutes(ctx, kit) {
const problem = d.machines.ok === false && d.machines.problem ? note(esc(d.machines.problem.detail || 'Your machines could not be read.'), 'bad') : '';
const how = `<ol class="c4-setup-how">
<li><b>Get a one-time code</b> on <a href="https://openvibe.bot/machines" rel="noopener">openvibe.bot/machines</a>. It lasts ten minutes.</li>
--<li><b>Run the installer on that computer</b> and give it the code:${command('curl -fsSL https://node.openvibe.network/install | sh -s -- --worker', 'On the machine')}</li>
++<li><b>Run the installer on that computer</b> and give it the code:${command('curl -fsSL https://openvibe.link/install | sh -s -- --worker', 'On the machine')}</li>
<li><b>It pairs itself</b> and shows up here as Online. It runs your work and nobody else's.</li></ol>`;
return `${problem}${rows ? `<ul class="c4-rows">${rows}</ul>` : ''}${list.length ? `<details class="c4-setup-more"><summary>Pair another machine</summary>${how}</details>` : how}
<div class="c4-setup-actions"><a class="c4-button${list.length ? ' c4-button-light' : ''}" href="/machines#pair">${list.length ? 'Manage machines' : 'I have a code'}</a><a class="c4-opill" href="/setup#step-machine">Check again</a></div>`;
ddiff --git a/server/http/skills-api.js b/server/http/skills-api.js
iindex c40662c..5210a18 100644
---- a/server/http/skills-api.js
++++ b/server/http/skills-api.js
@@@ -18,7 +18,7 @@
* POST /api/v1/me/skills/:id add a record to the sync
* POST /api/v1/me/skills/:id/remove remove it
*
-- * The Node sync authenticates with the person's access token (Authorization: Bearer), which the same SSO middleware
++ * The Link sync authenticates with the person's access token (Authorization: Bearer), which the same SSO middleware
* verifies as a browser session. Only public records are in the gallery; the others answer 404 to everyone who is
* not allowed to read them.
*/
ddiff --git a/server/http/skills-pages.js b/server/http/skills-pages.js
iindex a091089..8ad811e 100644
---- a/server/http/skills-pages.js
++++ b/server/http/skills-pages.js
@@@ -127,7 +127,7 @@ ${table(['Version', 'When', 'Audience', 'Note'], versions.map((v) => [
v.audience,
v.note || '—',
]))}
--<p class="muted small">A rollback copies the chosen version into a new one, so history is never rewritten. The Node records the version it wrote.</p></section>
++<p class="muted small">A rollback copies the chosen version into a new one, so history is never rewritten. The Link records the version it wrote.</p></section>
${controls}`,
}, status);
}
ddiff --git a/test/c4b.test.js b/test/c4b.test.js
iindex aadb1b6..e1b6437 100644
---- a/test/c4b.test.js
++++ b/test/c4b.test.js
@@@ -5,7 +5,7 @@
* - Home's saved pickers: the repositories a person used here, last used first, and their paired machines
* - a task's Changes, Proof and Pull request panels are Codes' own, from run_tasks and codes_task_proof
* - Agents: the person's keys, added, tested and removed from the page (the key itself never comes back)
-- * - Machines: the person's paired Node machines, their state, and Node's pairing flow with a one-time code
++ * - Machines: the person's paired Link machines, their state, and Node's pairing flow with a one-time code
* - Settings: remembered per person
* - Usage: runs, minutes and tokens per day, and no prices Codes does not know
*/
ddiff --git a/test/codes-auto-routing.test.js b/test/codes-auto-routing.test.js
iindex 83c8c67..c799b78 100644
---- a/test/codes-auto-routing.test.js
++++ b/test/codes-auto-routing.test.js
@@@ -39,7 +39,7 @@ const RESET_MONTH = new Date(Date.now() + 2.5 * 86400000).toISOString();
/** The fleet Ada's machines reported with their last heartbeats. */
const window = (name, pct, resetsAt) => JSON.stringify([{ name, pct, resets_at: resetsAt }]);
// The machine, as OpenVibe.Bot answers it: the device id Run pins a job to, the Network id the machine is known
-- // by, and the fleet its Node last reported with its heartbeat.
++ // by, and the fleet its Link last reported with its heartbeat.
t.bot.addMachine({
id: 'dev_01J8Z3Q4R5S6T7V8W9X0Y1Z2N1', node_id: NODE, name: 'ada-laptop', online: true, fleet_at: new Date().toISOString(),
fleet: [
ddiff --git a/test/conductor-failover.test.js b/test/conductor-failover.test.js
iindex 2874cd4..c4194d9 100644
---- a/test/conductor-failover.test.js
++++ b/test/conductor-failover.test.js
@@@ -185,7 +185,7 @@ const HOUR = 3600_000;
await endTurn(taskId, { agent: 'claude-code', session: 'sess_claude_2' });
const m = (await messagesOf(taskId))[2];
assert.strictEqual(m.agent, 'claude-code');
-- assert.match(m.switch_note, /Update OpenVibe Node/);
++ assert.match(m.switch_note, /Update OpenVibe Link/);
report(10, 30);
await refresh();
await post(`/api/v1/conductor/${taskId}/messages`, { text: 'status?' });
ddiff --git a/test/engine-adapters.test.js b/test/engine-adapters.test.js
iindex 03b3daf..25c5e08 100644
---- a/test/engine-adapters.test.js
++++ b/test/engine-adapters.test.js
@@@ -294,7 +294,7 @@ function dry(adapter, ctx = {}) {
// A job that works in one repository gets that repository's digest; a folder that holds many gets none.
require('node:child_process').execFileSync('git', ['init', '-q'], { cwd: dir });
fs.writeFileSync(path.join(dir, 'README.md'), '# Demo\n\nA demo repository.\n');
-- // On a Node the lessons come from the Know store's module (know/lessons.js), its path from the config.
++ // On a Link the lessons come from the Know store's module (know/lessons.js), its path from the config.
const knowFile = path.join(dir, 'know.js');
fs.writeFileSync(knowFile, "module.exports = { recall: () => '## What earlier jobs learned here (dated)\\n- 2026-10-30 · demo — the tests need the database' };\n");
const built = assemble({ brief: 'fix the test', dir, env: { CODES_KNOW_LESSONS: knowFile } });
@@@ -304,7 +304,7 @@ function dry(adapter, ctx = {}) {
assert.ok(built.text.indexOf('House rules') < built.text.indexOf('learned'));
assert.ok(built.text.indexOf('learned') < built.text.indexOf('demo repository'));
assert.ok(built.text.trimEnd().endsWith('fix the test'), 'the brief is last');
-- // The Node's config path wins over the environment, and a store that cannot be read is skipped, not an error.
++ // The Link's config path wins over the environment, and a store that cannot be read is skipped, not an error.
const fromConfig = assemble({ brief: 'x', dir, know: knowFile, env: { CODES_KNOW_LESSONS: path.join(dir, 'nope.js') } });
assert.ok(fromConfig.parts.some((p) => p.name === 'lessons'), 'the config path is read');
const none = assemble({ brief: 'x', dir, env: { CODES_KNOW_LESSONS: path.join(dir, 'nope.js') } });
ddiff --git a/test/engine-prompt.test.js b/test/engine-prompt.test.js
iindex ed3a229..9845741 100644
---- a/test/engine-prompt.test.js
++++ b/test/engine-prompt.test.js
@@@ -32,7 +32,7 @@ const { assemble, lessons, fragment } = require('../engine/prompt');
assert.ok(got.trimEnd().endsWith('the database'), 'and its lesson is kept');
assert.deepStrictEqual(JSON.parse(fs.readFileSync(path.join(dir, 'seen.json'), 'utf8')),
{ brief: 'fix the database test', repo: 'demo', k: 5 }, 'the store gets the brief, the repo and the cap');
-- // The environment is how a Node passes the config path when the caller does not.
++ // The environment is how a Link passes the config path when the caller does not.
assert.equal(lessons({ brief: 'x', env: { CODES_KNOW_LESSONS: file } }), got);
fs.rmSync(dir, { recursive: true, force: true });
});
ddiff --git a/test/harness-run.test.js b/test/harness-run.test.js
iindex d7389ae..d4ab020 100644
---- a/test/harness-run.test.js
++++ b/test/harness-run.test.js
@@@ -43,7 +43,7 @@ const results = (events) => events.filter((e) => e.type === 'result');
for (const [agent, word] of [['claude-code', 'claude'], ['codex', 'codex'], ['opencode', 'opencode'], ['command-code', 'cmd']]) {
await check(`${agent}: the agent's output becomes the shared events, with one result`, async () => {
-- // OpenCode and Command Code take an "edit" run only inside a sandbox (test/harness-sandbox.test.js): run as a Node job.
++ // OpenCode and Command Code take an "edit" run only inside a sandbox (test/harness-sandbox.test.js): run as a Link job.
const events = await collect(harness().run({ prompt: 'fix a.js', agent, cwd: f.work, handoff: false, sandboxed: true }));
assert.strictEqual(events[0].type, 'system');
assert.strictEqual(events[0].subtype, 'init');
ddiff --git a/test/harness-sandbox.test.js b/test/harness-sandbox.test.js
iindex d52454c..5d175a8 100644
---- a/test/harness-sandbox.test.js
++++ b/test/harness-sandbox.test.js
@@@ -42,7 +42,7 @@ const collect = async (run) => { const out = []; for await (const e of run) out.
assert.strictEqual(cli.opencode.editsOnly, false);
assert.strictEqual(cli.commandCode.editsOnly, false);
assert.ok(!(await argvOf(cli.opencode, { permission: 'edit' })).includes('--auto'), 'edit on the person’s computer: no --auto');
-- assert.ok((await argvOf(cli.opencode, { permission: 'edit', sandboxed: true })).includes('--auto'), 'edit in a Node sandbox: --auto');
++ assert.ok((await argvOf(cli.opencode, { permission: 'edit', sandboxed: true })).includes('--auto'), 'edit in a Link sandbox: --auto');
assert.ok((await argvOf(cli.opencode, { permission: 'full' })).includes('--auto'), 'full: --auto');
assert.ok(!(await argvOf(cli.opencode, { permission: 'read', sandboxed: true })).includes('--auto'), 'read: never');
assert.ok(!(await argvOf(cli.commandCode, { permission: 'edit' })).includes('--yolo'), 'Command Code: no --yolo for edit outside a sandbox');
ddiff --git a/test/mcp.test.js b/test/mcp.test.js
iindex 673a4db..adbfd84 100644
---- a/test/mcp.test.js
++++ b/test/mcp.test.js
@@@ -179,7 +179,7 @@ async function fakeGitHub() {
assert.strictEqual(typeof a.installed, 'boolean');
assert.ok(Array.isArray(a.windows));
}
-- // Codes answers with every machine's stored report: this machine is in Bot's list, but its Node has not
++ // Codes answers with every machine's stored report: this machine is in Bot's list, but its Link has not
// reported a fleet yet, so there is nothing stored and nothing is invented.
assert.ok(Array.isArray(out.machines), 'the stored fleet of every machine');
assert.strictEqual(out.problem, undefined, 'Bot answered, so there is no problem beside the fleet');
ddiff --git a/test/setup.test.js b/test/setup.test.js
iindex baa5f8b..4c50621 100644
---- a/test/setup.test.js
++++ b/test/setup.test.js
@@@ -35,7 +35,7 @@ const DEVICE = 'dev_01J8Z3Q4R5S6T7V8W9X0Y1Z2S1';
for (const id of ['machine', 'agents', 'keys', 'accounts', 'choices']) assert.match(res.text, new RegExp(`id="step-${id}"`));
assert.match(res.text, /0 of 5 done/);
assert.match(res.text, /class="c4-card c4-setup-step is-next" id="step-machine"/, 'the machine is next');
-- assert.match(res.text, /curl -fsSL https:\/\/node\.openvibe\.network\/install \| sh -s -- --worker/);
++ assert.match(res.text, /curl -fsSL https:\/\/openvibe\.link\/install \| sh -s -- --worker/);
assert.match(res.text, /npm install -g @openai\/codex/, 'an agent that is not there says how to install it');
assert.match(res.text, /href="\/css\/setup\.css|css\/setup\.css/, 'the page brings its own stylesheet');
assert.match(res.text, /js\/setup\.js/, 'and its own script (the Copy buttons), which no other page pays for');
ddiff --git a/test/skills.test.js b/test/skills.test.js
iindex c43b9cf..c149717 100644
---- a/test/skills.test.js
++++ b/test/skills.test.js
@@@ -6,7 +6,7 @@
* - a new record is private and is not in the gallery;
* - public is listed and readable anonymously, link is readable by id but not listed, private answers 404;
* - saving a version keeps the old one, and a rollback copies it into a new version (history is never rewritten);
-- * - the Node's sync manifest is exactly the person's picks, at their newest version;
++ * - the Link's sync manifest is exactly the person's picks, at their newest version;
* - the page renders a skill body through the markup gate, never as HTML.
*/
const assert = require('assert');