relaymessenger 0.1.5 → 0.1.6-staging.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +65 -62
- package/dist/agent-create.d.ts +24 -0
- package/dist/agent-create.d.ts.map +1 -0
- package/dist/agent-create.js +64 -0
- package/dist/agent-create.js.map +1 -0
- package/dist/agent-driver.d.ts +32 -0
- package/dist/agent-driver.d.ts.map +1 -0
- package/dist/agent-driver.js +59 -0
- package/dist/agent-driver.js.map +1 -0
- package/dist/claude-channel.d.ts +43 -0
- package/dist/claude-channel.d.ts.map +1 -0
- package/dist/claude-channel.js +124 -0
- package/dist/claude-channel.js.map +1 -0
- package/dist/connect.d.ts +106 -0
- package/dist/connect.d.ts.map +1 -0
- package/dist/connect.js +418 -0
- package/dist/connect.js.map +1 -0
- package/dist/help-groups.d.ts +29 -0
- package/dist/help-groups.d.ts.map +1 -0
- package/dist/help-groups.js +38 -0
- package/dist/help-groups.js.map +1 -0
- package/dist/interactive.d.ts +28 -2
- package/dist/interactive.d.ts.map +1 -1
- package/dist/interactive.js +61 -28
- package/dist/interactive.js.map +1 -1
- package/dist/program.d.ts +4 -1
- package/dist/program.d.ts.map +1 -1
- package/dist/program.js +265 -160
- package/dist/program.js.map +1 -1
- package/dist/qr-terminal.d.ts +6 -0
- package/dist/qr-terminal.d.ts.map +1 -0
- package/dist/qr-terminal.js +49 -0
- package/dist/qr-terminal.js.map +1 -0
- package/dist/runtime-sniff.d.ts +43 -0
- package/dist/runtime-sniff.d.ts.map +1 -0
- package/dist/runtime-sniff.js +96 -0
- package/dist/runtime-sniff.js.map +1 -0
- package/dist/skill-offer.d.ts +4 -1
- package/dist/skill-offer.d.ts.map +1 -1
- package/dist/skill-offer.js +9 -2
- package/dist/skill-offer.js.map +1 -1
- package/dist/terminal-session.d.ts.map +1 -1
- package/dist/terminal-session.js +2 -5
- package/dist/terminal-session.js.map +1 -1
- package/package.json +2 -2
- package/dist/agent-handoff.d.ts +0 -29
- package/dist/agent-handoff.d.ts.map +0 -1
- package/dist/agent-handoff.js +0 -81
- package/dist/agent-handoff.js.map +0 -1
package/README.md
CHANGED
|
@@ -19,17 +19,43 @@ npm install --global relaymessenger
|
|
|
19
19
|
Node.js 22.22.3 or newer is required. `relaymessenger` is
|
|
20
20
|
the canonical executable; `relay` is the shorter command alias.
|
|
21
21
|
|
|
22
|
+
## The front door
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
npx relaymessenger connect # asks what will answer as this agent
|
|
26
|
+
npx relaymessenger connect claude # names it outright
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
`connect` finds the runtimes on this computer, makes an agent or takes one you
|
|
30
|
+
already have, shows every file it will write and every command it will run,
|
|
31
|
+
writes the runtime's own configuration, waits for a first message and asks
|
|
32
|
+
whether to allow that sender, then offers to start the runtime. This build
|
|
33
|
+
writes Claude Code; Hermes and OpenClaw are detected, their plan is printed, and
|
|
34
|
+
the command stops without changing anything.
|
|
35
|
+
|
|
36
|
+
Every question has a flag for scripts: `--new`, `--handle`, `--name`, `--image`,
|
|
37
|
+
`--token`, `--allow`, `--yes`, `--dry-run`, `--no-start`, `--no-skill`, `--json`
|
|
38
|
+
and `--api-url`. `--dry-run` prints the plan and changes nothing.
|
|
39
|
+
|
|
22
40
|
## Interactive use
|
|
23
41
|
|
|
24
|
-
Run `relay` (or `npx relaymessenger`) in a terminal for
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
42
|
+
Run `relay` (or `npx relaymessenger`) in a terminal for Connect an agent, Watch
|
|
43
|
+
an agent, and Exit. `relay agents` and `relay auth` offer focused menus. Menus
|
|
44
|
+
and passwords use Clack; cancellation before a mutation leaves it unperformed.
|
|
45
|
+
|
|
46
|
+
Whether Relay asks anything is decided by the terminal and the flags, and by
|
|
47
|
+
nothing else: a `CI` variable no longer suppresses a menu. Explicit commands
|
|
48
|
+
still work. `--non-interactive`, `--json`, piping, help, and version output never
|
|
49
|
+
show optional menus or skill offers. Interactive agent deletion asks for
|
|
50
|
+
confirmation; scripted deletion does not gain a mandatory `--yes` flag.
|
|
51
|
+
|
|
52
|
+
With no terminal, a command that needs an answer prints the flags that would
|
|
53
|
+
have answered it and exits 2 rather than a usage block. With `--json`, every
|
|
54
|
+
error is `{ "error": …, "next_step": … }`.
|
|
28
55
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
mandatory `--yes` flag.
|
|
56
|
+
`relay --help` puts the commands in three groups: Get started (`connect`), Every
|
|
57
|
+
day (`watch`, `doctor`, `agents`), and Everything else behind one line. Every
|
|
58
|
+
command has a description; run `relay help <command>` for any of them.
|
|
33
59
|
|
|
34
60
|
Before interactive creation or sign-in setup, the CLI may offer the Relay skill
|
|
35
61
|
once if it is absent from the standard install locations. Declining skips skill
|
|
@@ -49,10 +75,12 @@ The install menu remains available when you explicitly want to run the installer
|
|
|
49
75
|
|
|
50
76
|
### Persistent agent view
|
|
51
77
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
78
|
+
`relay watch <handle>` opens the live view: the public QR and link, and the
|
|
79
|
+
events as they arrive. Successful interactive creation and sign-in keep the same
|
|
80
|
+
view open. Press `q`, Ctrl-C, or Ctrl-D to close it; it does not delete the agent
|
|
81
|
+
or stop a runtime. `relay events listen` is the older name for a different thing:
|
|
82
|
+
it takes events, so Relay can stop resending them elsewhere. It keeps working and
|
|
83
|
+
keeps its flags, and it is no longer listed in the help.
|
|
56
84
|
|
|
57
85
|
The view only watches. Your agent still receives every message, because this view
|
|
58
86
|
never answers Relay and never takes an event from it. It shows the events Relay
|
|
@@ -61,13 +89,30 @@ they are. A connected view does not mean the agent is running, and the screen
|
|
|
61
89
|
says that too. `--json`, `--non-interactive`, and any command not attached to a
|
|
62
90
|
terminal never open this view.
|
|
63
91
|
|
|
92
|
+
## For the agent running this command
|
|
93
|
+
|
|
94
|
+
Relay reads the environment for the coding agent driving it — `CLAUDECODE`,
|
|
95
|
+
`CURSOR_CLI`, `CURSOR`, `CODEX_HOME` — and when one is there it asks nothing and
|
|
96
|
+
prints one line saying so. Every question has a flag.
|
|
97
|
+
|
|
98
|
+
```sh
|
|
99
|
+
relaymessenger --install-skills # install the Relay skill for this computer's agents
|
|
100
|
+
relaymessenger docs # the documentation itself when piped, its address in a terminal
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
`--install-skills` runs the pinned installer with `--global --yes`, targeting
|
|
104
|
+
`~/.agents/skills` and, when this computer has Claude Code, `~/.claude/skills`.
|
|
105
|
+
It answers nothing on your behalf beyond those targets, and the command you
|
|
106
|
+
typed carries on afterwards.
|
|
107
|
+
|
|
108
|
+
`docs` prints `https://docs.relayapp.im/llms.txt`: its contents when there is no
|
|
109
|
+
terminal, and its address when there is one or when Relay cannot reach it.
|
|
110
|
+
|
|
64
111
|
## Agent Token authentication
|
|
65
112
|
|
|
66
113
|
Use `agents create` for a new agent, or import an existing Agent Token. Tokens
|
|
67
114
|
can be entered through the private `auth login` prompt, read from stdin with
|
|
68
|
-
`auth login --with-token`, supplied by `RELAY_AGENT_TOKEN` when present
|
|
69
|
-
from the selected saved profile with `--connect`;
|
|
70
|
-
there is deliberately no token command-line option.
|
|
115
|
+
`auth login --with-token`, or supplied by `RELAY_AGENT_TOKEN` when present.
|
|
71
116
|
|
|
72
117
|
```sh
|
|
73
118
|
# Private prompt when RELAY_AGENT_TOKEN is not set:
|
|
@@ -109,7 +154,6 @@ relay chats read "$CHAT_ID"
|
|
|
109
154
|
relay contact-card get
|
|
110
155
|
relay contact-card setup --handle weather.dev --name Weather
|
|
111
156
|
relay contact-card share "$CHAT_ID"
|
|
112
|
-
relay contact-requests create advait
|
|
113
157
|
|
|
114
158
|
relay attachments upload ./report.pdf --content-type application/pdf
|
|
115
159
|
relay blocked-handles list
|
|
@@ -119,7 +163,7 @@ relay webhooks subscriptions list
|
|
|
119
163
|
|
|
120
164
|
Run `relay --help` and each command group's `--help` for the full current
|
|
121
165
|
surface: Chats, Messages, Attachments, blocked Handles, webhook events and
|
|
122
|
-
subscriptions,
|
|
166
|
+
subscriptions, and Contact Cards.
|
|
123
167
|
|
|
124
168
|
Chats contain at most one human user and one or more agents; agent-to-agent
|
|
125
169
|
Chats are also supported. Agents and users have the same generic Chat API
|
|
@@ -141,13 +185,10 @@ relay chats participants add "$CHAT_ID" research.dev
|
|
|
141
185
|
relay chats participants remove "$CHAT_ID" research.dev
|
|
142
186
|
```
|
|
143
187
|
|
|
144
|
-
`contact-card share` shares the authenticated agent's own card.
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
supported subject to Contacts eligibility and blocking; a pending Add request
|
|
149
|
-
does not grant messaging eligibility. There are no phone address-book, mutual-contact, human discovery,
|
|
150
|
-
or human invite-link commands.
|
|
188
|
+
`contact-card share` shares the authenticated agent's own card. Agent-initiated
|
|
189
|
+
Messages to users remain supported subject to Contacts eligibility and blocking.
|
|
190
|
+
There are no add-request, phone address-book, mutual-contact, human discovery, or
|
|
191
|
+
human invite-link commands.
|
|
151
192
|
|
|
152
193
|
## Developer-managed agents
|
|
153
194
|
|
|
@@ -222,44 +263,6 @@ metadata, paired with its rendered local image/URL/attachment. It is not a defau
|
|
|
222
263
|
interactive question. The CLI does not render recipes or generate images. The
|
|
223
264
|
server's response supplies the permanent public image URL.
|
|
224
265
|
|
|
225
|
-
### Optional: connect a runtime
|
|
226
|
-
|
|
227
|
-
```sh
|
|
228
|
-
relay agents create --connect hermes \
|
|
229
|
-
--runtime-home /absolute/hermes-profile \
|
|
230
|
-
--runtime-state-dir /absolute/hermes-profile/relay \
|
|
231
|
-
--confirm-configure --runtime-stopped
|
|
232
|
-
|
|
233
|
-
# Import into a named profile via private stdin; no creation request.
|
|
234
|
-
relay --profile imported auth login --with-token --connect openclaw \
|
|
235
|
-
--runtime-config /absolute/openclaw.json \
|
|
236
|
-
--runtime-state-dir /absolute/openclaw-state --runtime-account my-agent \
|
|
237
|
-
--confirm-configure --runtime-stopped
|
|
238
|
-
```
|
|
239
|
-
|
|
240
|
-
Stop the selected runtime before passing `--runtime-stopped`. `--confirm-configure`
|
|
241
|
-
authorizes only private configuration writes. `--runtime-brain` selects an existing
|
|
242
|
-
OpenClaw binding; Claude uses `--runtime-home` for an existing session channel
|
|
243
|
-
directory and `--runtime-context` for its session identifier. Existing sender
|
|
244
|
-
permissions are preserved, not inferred from Contacts.
|
|
245
|
-
|
|
246
|
-
`agents create --connect` reads the profile it just saved, and ignores any token in
|
|
247
|
-
your environment. `auth login --connect` with no token option reuses the saved
|
|
248
|
-
profile you named and its API address, and ignores your environment too. Add
|
|
249
|
-
`--with-token` to read the token from a pipe instead. Plain `auth login` uses
|
|
250
|
-
`RELAY_AGENT_TOKEN` where there is no terminal, or asks for the token in a hidden
|
|
251
|
-
prompt. Relay checks the token with the API before saving it, and never creates an
|
|
252
|
-
agent as a fallback.
|
|
253
|
-
Empty Hermes profiles and explicit empty/new OpenClaw accounts can receive an
|
|
254
|
-
initial credential when their native context and state are safe. Occupied
|
|
255
|
-
credentials, unknown secret references, and bound/corrupt state are not replaced.
|
|
256
|
-
|
|
257
|
-
The `connect` block in the output says whether the configuration was written, and
|
|
258
|
-
always reports `connected: false`: this command does not install, start, stop or
|
|
259
|
-
test anything. Start the runtime yourself. If writing the configuration fails
|
|
260
|
-
after an agent was created, the token is still saved; run `auth login --connect`
|
|
261
|
-
with that token rather than creating another agent.
|
|
262
|
-
|
|
263
266
|
## Local event forwarding
|
|
264
267
|
|
|
265
268
|
`relay events listen` is a development convenience backed only by the SDK's
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type AgentImageUploadResult } from "./agent-image-upload.js";
|
|
2
|
+
import { createAgent, type AgentDependencies, type CreateAgentInput } from "./agents.js";
|
|
3
|
+
import { type AgentImageRecipe } from "@relaymessenger/sdk";
|
|
4
|
+
/**
|
|
5
|
+
* One creation path for every caller. `agents create` and `connect` both make an
|
|
6
|
+
* agent, save its token privately, and then set its picture from a local file,
|
|
7
|
+
* so the order, the retry wording and the "do not create another agent" rule
|
|
8
|
+
* live here once rather than in each command.
|
|
9
|
+
*/
|
|
10
|
+
export interface CreateWithPictureInput extends CreateAgentInput {
|
|
11
|
+
/** A file on this computer, or an https:// address. */
|
|
12
|
+
image?: string;
|
|
13
|
+
imageRecipe?: AgentImageRecipe;
|
|
14
|
+
cwd?: string;
|
|
15
|
+
home?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface CreateWithPictureResult {
|
|
18
|
+
result: Awaited<ReturnType<typeof createAgent>>;
|
|
19
|
+
image?: AgentImageUploadResult;
|
|
20
|
+
}
|
|
21
|
+
export declare const createAgentWithPicture: (input: CreateWithPictureInput, deps: AgentDependencies, fetchImplementation?: typeof globalThis.fetch) => Promise<CreateWithPictureResult>;
|
|
22
|
+
/** The one sentence a caller prints when a picture did not finish. */
|
|
23
|
+
export declare const incompletePictureMessage: (handle: string, profile: string, image: AgentImageUploadResult, hasRecipe: boolean) => string;
|
|
24
|
+
//# sourceMappingURL=agent-create.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-create.d.ts","sourceRoot":"","sources":["../src/agent-create.ts"],"names":[],"mappings":"AACA,OAAO,EAAoB,KAAK,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AACxF,OAAO,EAAe,WAAW,EAAE,KAAK,iBAAiB,EAAE,KAAK,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEtG,OAAc,EAAE,KAAK,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAGnE;;;;;GAKG;AACH,MAAM,WAAW,sBAAuB,SAAQ,gBAAgB;IAC9D,uDAAuD;IACvD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,gBAAgB,CAAC;IAC/B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC;IAChD,KAAK,CAAC,EAAE,sBAAsB,CAAC;CAChC;AAED,eAAO,MAAM,sBAAsB,GACjC,OAAO,sBAAsB,EAC7B,MAAM,iBAAiB,EACvB,sBAAsB,OAAO,UAAU,CAAC,KAAK,KAC5C,OAAO,CAAC,uBAAuB,CA6CjC,CAAC;AAEF,sEAAsE;AACtE,eAAO,MAAM,wBAAwB,GACnC,QAAQ,MAAM,EACd,SAAS,MAAM,EACf,OAAO,sBAAsB,EAC7B,WAAW,OAAO,KACjB,MAIF,CAAC"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { prepareAgentImage } from "./local-image.js";
|
|
2
|
+
import { uploadAgentImage } from "./agent-image-upload.js";
|
|
3
|
+
import { agentRecord, createAgent } from "./agents.js";
|
|
4
|
+
import { safeMetadata } from "./output.js";
|
|
5
|
+
import Relay, {} from "@relaymessenger/sdk";
|
|
6
|
+
import { DEFAULT_API_URL, validateApiURL } from "./config.js";
|
|
7
|
+
export const createAgentWithPicture = async (input, deps, fetchImplementation) => {
|
|
8
|
+
let localImage;
|
|
9
|
+
let imageURL = input.imageURL;
|
|
10
|
+
if (input.image !== undefined) {
|
|
11
|
+
// Only creation may say "No agent was created": the same checks are reached
|
|
12
|
+
// by a contact-card update with no agent in play.
|
|
13
|
+
const image = await prepareAgentImage(input.image, {
|
|
14
|
+
...(input.cwd ? { cwd: input.cwd } : {}),
|
|
15
|
+
...(input.home ? { home: input.home } : {}),
|
|
16
|
+
}).catch((error) => { throw new Error(`${error instanceof Error ? error.message : String(error)} No agent was created.`); });
|
|
17
|
+
if (image.kind === "file")
|
|
18
|
+
localImage = image.file;
|
|
19
|
+
else
|
|
20
|
+
imageURL = image.url;
|
|
21
|
+
}
|
|
22
|
+
if (input.imageRecipe !== undefined && imageURL === undefined && !localImage) {
|
|
23
|
+
throw new Error("--image-recipe requires its rendered --image or --image-url.");
|
|
24
|
+
}
|
|
25
|
+
const result = await createAgent({
|
|
26
|
+
...(input.profile === undefined ? {} : { profile: input.profile }),
|
|
27
|
+
...(input.apiURL === undefined ? {} : { apiURL: input.apiURL }),
|
|
28
|
+
...(input.tokenName === undefined ? {} : { tokenName: input.tokenName }),
|
|
29
|
+
...(input.handle === undefined ? {} : { handle: input.handle }),
|
|
30
|
+
...(input.firstName === undefined ? {} : { firstName: input.firstName }),
|
|
31
|
+
...(imageURL === undefined ? {} : { imageURL }),
|
|
32
|
+
...(input.imageRecipe === undefined || localImage ? {} : { imageRecipe: input.imageRecipe }),
|
|
33
|
+
}, deps);
|
|
34
|
+
if (!localImage)
|
|
35
|
+
return { result };
|
|
36
|
+
let image;
|
|
37
|
+
try {
|
|
38
|
+
// The agent and its token are already saved. Never use a token from the
|
|
39
|
+
// environment for a just-created agent's picture upload.
|
|
40
|
+
const saved = (await deps.read()).profiles[result.profile];
|
|
41
|
+
if (!saved?.agent_token || validateApiURL(saved.api_url ?? DEFAULT_API_URL) !== result.api_url)
|
|
42
|
+
throw new Error("Saved identity changed.");
|
|
43
|
+
const client = new Relay({ apiKey: saved.agent_token, baseURL: result.api_url, ...(fetchImplementation ? { fetch: fetchImplementation } : {}) });
|
|
44
|
+
const outcome = await uploadAgentImage({ handle: result.handle, image: localImage }, client, (attachmentID) => client.contactCard.update({
|
|
45
|
+
handle: result.handle,
|
|
46
|
+
attachment_id: attachmentID,
|
|
47
|
+
...(input.imageRecipe ? { image_recipe: input.imageRecipe } : {}),
|
|
48
|
+
}, { maxRetries: 0 }));
|
|
49
|
+
image = safeMetadata(outcome, [saved.agent_token]);
|
|
50
|
+
if (image.status === "updated")
|
|
51
|
+
Object.assign(result, agentRecord(image.agent));
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
image = { status: "incomplete", phase: "agent", message: "The agent was created and its token was saved. The picture did not go through. Set the picture on this profile; do not create the agent again." };
|
|
55
|
+
}
|
|
56
|
+
return { result, image };
|
|
57
|
+
};
|
|
58
|
+
/** The one sentence a caller prints when a picture did not finish. */
|
|
59
|
+
export const incompletePictureMessage = (handle, profile, image, hasRecipe) => {
|
|
60
|
+
const retry = image.status === "incomplete" && image.attachment_id && ["check", "save"].includes(image.phase)
|
|
61
|
+
? `--attachment-id ${image.attachment_id}` : "--image <local-file>";
|
|
62
|
+
return `Agent @${handle} was created and its profile and token are saved. The picture did not go through. Set it on this profile: npx relaymessenger --profile ${profile} contact-card update --handle ${handle} ${retry}${hasRecipe ? " --image-recipe <json-file>" : ""}. Do not create another agent.`;
|
|
63
|
+
};
|
|
64
|
+
//# sourceMappingURL=agent-create.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-create.js","sourceRoot":"","sources":["../src/agent-create.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The coding agents that announce themselves in the environment, and the exact
|
|
3
|
+
* variable each one sets. Measured on 2026-09-09: with `CLAUDECODE=1` Vercel's
|
|
4
|
+
* CLI prints `isAgent=true agentName=claude … => nonInteractive=true`, and its
|
|
5
|
+
* own bundled detector carries the strings `CODEX_HOME`, `CURSOR` and
|
|
6
|
+
* `CURSOR_CLI` (Relay-Research/research/cli-hands-on-20260909/vercel.md, section
|
|
7
|
+
* 12). `CODEX_HOME` and `CLAUDE_CONFIG_DIR` are also the homes the pinned skills
|
|
8
|
+
* installer reads, so Relay already trusts them to mean those agents.
|
|
9
|
+
*/
|
|
10
|
+
export declare const AGENT_VARIABLES: ReadonlyArray<{
|
|
11
|
+
variable: string;
|
|
12
|
+
name: string;
|
|
13
|
+
}>;
|
|
14
|
+
export interface DrivingAgent {
|
|
15
|
+
name: string;
|
|
16
|
+
variable: string;
|
|
17
|
+
}
|
|
18
|
+
/** The agent running this command, when one says so. */
|
|
19
|
+
export declare const drivingAgent: (env: NodeJS.ProcessEnv) => DrivingAgent | undefined;
|
|
20
|
+
/** One line, so the agent reading it knows why it saw no menu. */
|
|
21
|
+
export declare const drivingAgentHint: (agent: DrivingAgent) => string;
|
|
22
|
+
/** Relay's documentation, written for an agent to read in one request. */
|
|
23
|
+
export declare const DOCS_LLMS_URL = "https://docs.relayapp.im/llms.txt";
|
|
24
|
+
export declare const readDocs: (fetchImplementation?: typeof globalThis.fetch) => Promise<string | undefined>;
|
|
25
|
+
/**
|
|
26
|
+
* The installer names a target by agent, not by folder. `cline` is the agent
|
|
27
|
+
* whose global folder is `~/.agents/skills`, the place every runtime-neutral
|
|
28
|
+
* agent reads, and `claude-code` is `~/.claude/skills` (skills@1.5.25 README,
|
|
29
|
+
* "Supported Agents"). Claude Code is added only when this computer has it.
|
|
30
|
+
*/
|
|
31
|
+
export declare const skillTargets: (home: string, env: NodeJS.ProcessEnv) => Promise<string[]>;
|
|
32
|
+
//# sourceMappingURL=agent-driver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-driver.d.ts","sourceRoot":"","sources":["../src/agent-driver.ts"],"names":[],"mappings":"AAGA;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,EAAE,aAAa,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAK7E,CAAC;AAEF,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,wDAAwD;AACxD,eAAO,MAAM,YAAY,GAAI,KAAK,MAAM,CAAC,UAAU,KAAG,YAAY,GAAG,SAMpE,CAAC;AAEF,kEAAkE;AAClE,eAAO,MAAM,gBAAgB,GAAI,OAAO,YAAY,KAAG,MAC6E,CAAC;AAErI,0EAA0E;AAC1E,eAAO,MAAM,aAAa,sCAAsC,CAAC;AAEjE,eAAO,MAAM,QAAQ,GACnB,sBAAqB,OAAO,UAAU,CAAC,KAAwB,KAC9D,OAAO,CAAC,MAAM,GAAG,SAAS,CAS5B,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,YAAY,GAAU,MAAM,MAAM,EAAE,KAAK,MAAM,CAAC,UAAU,KAAG,OAAO,CAAC,MAAM,EAAE,CAOzF,CAAC"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { stat } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
/**
|
|
4
|
+
* The coding agents that announce themselves in the environment, and the exact
|
|
5
|
+
* variable each one sets. Measured on 2026-09-09: with `CLAUDECODE=1` Vercel's
|
|
6
|
+
* CLI prints `isAgent=true agentName=claude … => nonInteractive=true`, and its
|
|
7
|
+
* own bundled detector carries the strings `CODEX_HOME`, `CURSOR` and
|
|
8
|
+
* `CURSOR_CLI` (Relay-Research/research/cli-hands-on-20260909/vercel.md, section
|
|
9
|
+
* 12). `CODEX_HOME` and `CLAUDE_CONFIG_DIR` are also the homes the pinned skills
|
|
10
|
+
* installer reads, so Relay already trusts them to mean those agents.
|
|
11
|
+
*/
|
|
12
|
+
export const AGENT_VARIABLES = [
|
|
13
|
+
{ variable: "CLAUDECODE", name: "Claude Code" },
|
|
14
|
+
{ variable: "CURSOR_CLI", name: "Cursor" },
|
|
15
|
+
{ variable: "CURSOR", name: "Cursor" },
|
|
16
|
+
{ variable: "CODEX_HOME", name: "Codex" },
|
|
17
|
+
];
|
|
18
|
+
/** The agent running this command, when one says so. */
|
|
19
|
+
export const drivingAgent = (env) => {
|
|
20
|
+
for (const entry of AGENT_VARIABLES) {
|
|
21
|
+
const value = env[entry.variable]?.trim();
|
|
22
|
+
if (value)
|
|
23
|
+
return { name: entry.name, variable: entry.variable };
|
|
24
|
+
}
|
|
25
|
+
return undefined;
|
|
26
|
+
};
|
|
27
|
+
/** One line, so the agent reading it knows why it saw no menu. */
|
|
28
|
+
export const drivingAgentHint = (agent) => `Relay sees ${agent.name} (${agent.variable}), so it asks nothing. Every question has a flag: run relaymessenger connect --help.`;
|
|
29
|
+
/** Relay's documentation, written for an agent to read in one request. */
|
|
30
|
+
export const DOCS_LLMS_URL = "https://docs.relayapp.im/llms.txt";
|
|
31
|
+
export const readDocs = async (fetchImplementation = globalThis.fetch) => {
|
|
32
|
+
try {
|
|
33
|
+
const answer = await fetchImplementation(DOCS_LLMS_URL, { redirect: "follow" });
|
|
34
|
+
if (!answer.ok)
|
|
35
|
+
return undefined;
|
|
36
|
+
const text = await answer.text();
|
|
37
|
+
return text.trim() ? text : undefined;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* The installer names a target by agent, not by folder. `cline` is the agent
|
|
45
|
+
* whose global folder is `~/.agents/skills`, the place every runtime-neutral
|
|
46
|
+
* agent reads, and `claude-code` is `~/.claude/skills` (skills@1.5.25 README,
|
|
47
|
+
* "Supported Agents"). Claude Code is added only when this computer has it.
|
|
48
|
+
*/
|
|
49
|
+
export const skillTargets = async (home, env) => {
|
|
50
|
+
const targets = ["cline"];
|
|
51
|
+
const claudeRoot = env.CLAUDE_CONFIG_DIR?.trim() ?? join(home, ".claude");
|
|
52
|
+
try {
|
|
53
|
+
if ((await stat(claudeRoot)).isDirectory())
|
|
54
|
+
targets.push("claude-code");
|
|
55
|
+
}
|
|
56
|
+
catch { /* Claude Code is not on this computer. */ }
|
|
57
|
+
return targets;
|
|
58
|
+
};
|
|
59
|
+
//# sourceMappingURL=agent-driver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-driver.js","sourceRoot":"","sources":["../src/agent-driver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AACxC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,eAAe,GAAsD;IAChF,EAAE,QAAQ,EAAE,YAAY,EAAE,IAAI,EAAE,aAAa,EAAE;IAC/C,EAAE,QAAQ,EAAE,YAAY,EAAE,IAAI,EAAE,QAAQ,EAAE;IAC1C,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE;IACtC,EAAE,QAAQ,EAAE,YAAY,EAAE,IAAI,EAAE,OAAO,EAAE;CAC1C,CAAC;AAOF,wDAAwD;AACxD,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,GAAsB,EAA4B,EAAE;IAC/E,KAAK,MAAM,KAAK,IAAI,eAAe,EAAE,CAAC;QACpC,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,CAAC;QAC1C,IAAI,KAAK;YAAE,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC;IACnE,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC,CAAC;AAEF,kEAAkE;AAClE,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,KAAmB,EAAU,EAAE,CAC9D,cAAc,KAAK,CAAC,IAAI,KAAK,KAAK,CAAC,QAAQ,uFAAuF,CAAC;AAErI,0EAA0E;AAC1E,MAAM,CAAC,MAAM,aAAa,GAAG,mCAAmC,CAAC;AAEjE,MAAM,CAAC,MAAM,QAAQ,GAAG,KAAK,EAC3B,sBAA+C,UAAU,CAAC,KAAK,EAClC,EAAE;IAC/B,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,mBAAmB,CAAC,aAAa,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,CAAC;QAChF,IAAI,CAAC,MAAM,CAAC,EAAE;YAAE,OAAO,SAAS,CAAC;QACjC,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;QACjC,OAAO,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;IACxC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,KAAK,EAAE,IAAY,EAAE,GAAsB,EAAqB,EAAE;IAC5F,MAAM,OAAO,GAAG,CAAC,OAAO,CAAC,CAAC;IAC1B,MAAM,UAAU,GAAG,GAAG,CAAC,iBAAiB,EAAE,IAAI,EAAE,IAAI,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;IAC1E,IAAI,CAAC;QACH,IAAI,CAAC,MAAM,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,WAAW,EAAE;YAAE,OAAO,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;IAC1E,CAAC;IAAC,MAAM,CAAC,CAAC,0CAA0C,CAAC,CAAC;IACtD,OAAO,OAAO,CAAC;AACjB,CAAC,CAAC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one file the Relay channel for Claude Code reads. Its three names, its
|
|
3
|
+
* folder and its format are the channel's own
|
|
4
|
+
* (packages/claude-code/src/config.ts, `loadConfig` and `parseEnvFile`;
|
|
5
|
+
* packages/claude-code/README.md, "Manual environment fallback"). The folder is
|
|
6
|
+
* owner-only and the file is mode 600, because it holds an agent's token.
|
|
7
|
+
*/
|
|
8
|
+
export declare const CHANNEL_ENV_KEYS: readonly ["RELAY_AGENT_TOKEN", "RELAY_BASE_URL", "RELAY_ALLOWED_SENDERS"];
|
|
9
|
+
export declare const CHANNEL_DIR_MODE = 448;
|
|
10
|
+
export declare const CHANNEL_FILE_MODE = 384;
|
|
11
|
+
export interface ChannelEnvValues {
|
|
12
|
+
token: string;
|
|
13
|
+
baseURL: string;
|
|
14
|
+
/** Handles or Contact ids allowed to message this agent. */
|
|
15
|
+
allowedSenders: readonly string[];
|
|
16
|
+
}
|
|
17
|
+
/** The channel's own reader: last line wins, quotes optional, `export` allowed. */
|
|
18
|
+
export declare const readChannelEnv: (contents: string) => Record<string, string>;
|
|
19
|
+
/**
|
|
20
|
+
* Rewrites only Relay's own three names, in place. Every other line a person or
|
|
21
|
+
* another tool put in this file is kept exactly where it was.
|
|
22
|
+
*/
|
|
23
|
+
export declare const renderChannelEnv: (existing: string, values: ChannelEnvValues) => string;
|
|
24
|
+
export interface ChannelEnvState {
|
|
25
|
+
path: string;
|
|
26
|
+
exists: boolean;
|
|
27
|
+
/** The token already in the file, when it holds one. Never printed. */
|
|
28
|
+
token?: string;
|
|
29
|
+
baseURL?: string;
|
|
30
|
+
allowedSenders: string[];
|
|
31
|
+
contents: string;
|
|
32
|
+
}
|
|
33
|
+
export declare const inspectChannelEnv: (channelDir: string) => Promise<ChannelEnvState>;
|
|
34
|
+
/**
|
|
35
|
+
* Writes the file through a private temporary file in the same folder, so a
|
|
36
|
+
* reader never sees a half-written token and the token never exists in a
|
|
37
|
+
* world-readable file for even an instant.
|
|
38
|
+
*/
|
|
39
|
+
export declare const writeChannelEnv: (channelDir: string, values: ChannelEnvValues, platform?: NodeJS.Platform) => Promise<{
|
|
40
|
+
path: string;
|
|
41
|
+
contents: string;
|
|
42
|
+
}>;
|
|
43
|
+
//# sourceMappingURL=claude-channel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-channel.d.ts","sourceRoot":"","sources":["../src/claude-channel.ts"],"names":[],"mappings":"AAIA;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,2EAA4E,CAAC;AAC1G,eAAO,MAAM,gBAAgB,MAAQ,CAAC;AACtC,eAAO,MAAM,iBAAiB,MAAQ,CAAC;AAEvC,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,4DAA4D;IAC5D,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;CACnC;AAED,mFAAmF;AACnF,eAAO,MAAM,cAAc,GAAI,UAAU,MAAM,KAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CActE,CAAC;AASF;;;GAGG;AACH,eAAO,MAAM,gBAAgB,GAAI,UAAU,MAAM,EAAE,QAAQ,gBAAgB,KAAG,MAsB7E,CAAC;AAEF,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;IAChB,uEAAuE;IACvE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,eAAO,MAAM,iBAAiB,GAAU,YAAY,MAAM,KAAG,OAAO,CAAC,eAAe,CAiBnF,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,eAAe,GAC1B,YAAY,MAAM,EAClB,QAAQ,gBAAgB,EACxB,WAAU,MAAM,CAAC,QAA2B,KAC3C,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAoB5C,CAAC"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { chmod, mkdir, open, readFile, rename, unlink } from "node:fs/promises";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
/**
|
|
5
|
+
* The one file the Relay channel for Claude Code reads. Its three names, its
|
|
6
|
+
* folder and its format are the channel's own
|
|
7
|
+
* (packages/claude-code/src/config.ts, `loadConfig` and `parseEnvFile`;
|
|
8
|
+
* packages/claude-code/README.md, "Manual environment fallback"). The folder is
|
|
9
|
+
* owner-only and the file is mode 600, because it holds an agent's token.
|
|
10
|
+
*/
|
|
11
|
+
export const CHANNEL_ENV_KEYS = ["RELAY_AGENT_TOKEN", "RELAY_BASE_URL", "RELAY_ALLOWED_SENDERS"];
|
|
12
|
+
export const CHANNEL_DIR_MODE = 0o700;
|
|
13
|
+
export const CHANNEL_FILE_MODE = 0o600;
|
|
14
|
+
/** The channel's own reader: last line wins, quotes optional, `export` allowed. */
|
|
15
|
+
export const readChannelEnv = (contents) => {
|
|
16
|
+
const values = {};
|
|
17
|
+
for (const original of contents.split(/\r?\n/u)) {
|
|
18
|
+
const line = original.trim();
|
|
19
|
+
if (!line || line.startsWith("#"))
|
|
20
|
+
continue;
|
|
21
|
+
const match = /^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/u.exec(line);
|
|
22
|
+
if (!match?.[1] || match[2] === undefined)
|
|
23
|
+
continue;
|
|
24
|
+
let value = match[2].trim();
|
|
25
|
+
if (value.length >= 2 && ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'")))) {
|
|
26
|
+
value = value.slice(1, -1);
|
|
27
|
+
}
|
|
28
|
+
values[match[1]] = value;
|
|
29
|
+
}
|
|
30
|
+
return values;
|
|
31
|
+
};
|
|
32
|
+
const quoted = (value) => {
|
|
33
|
+
if (/[\r\n\s"'`$\\]/u.test(value)) {
|
|
34
|
+
throw new Error("That value cannot be written to a .env file safely. Use a value with no quotes, backticks, dollar signs, backslashes or line breaks.");
|
|
35
|
+
}
|
|
36
|
+
return `"${value}"`;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Rewrites only Relay's own three names, in place. Every other line a person or
|
|
40
|
+
* another tool put in this file is kept exactly where it was.
|
|
41
|
+
*/
|
|
42
|
+
export const renderChannelEnv = (existing, values) => {
|
|
43
|
+
const changes = {
|
|
44
|
+
RELAY_AGENT_TOKEN: values.token,
|
|
45
|
+
RELAY_BASE_URL: values.baseURL,
|
|
46
|
+
RELAY_ALLOWED_SENDERS: [...new Set(values.allowedSenders.map((sender) => sender.trim()).filter(Boolean))].join(","),
|
|
47
|
+
};
|
|
48
|
+
const lines = existing ? existing.split(/\r?\n/u) : [];
|
|
49
|
+
const written = new Set();
|
|
50
|
+
const output = lines.map((line) => {
|
|
51
|
+
const match = /^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/u.exec(line.trim());
|
|
52
|
+
const key = match?.[1];
|
|
53
|
+
if (!key || !Object.hasOwn(changes, key) || written.has(key))
|
|
54
|
+
return line;
|
|
55
|
+
written.add(key);
|
|
56
|
+
return `${key}=${quoted(changes[key])}`;
|
|
57
|
+
});
|
|
58
|
+
for (const key of CHANNEL_ENV_KEYS) {
|
|
59
|
+
if (written.has(key))
|
|
60
|
+
continue;
|
|
61
|
+
if (output.at(-1) === "")
|
|
62
|
+
output.pop();
|
|
63
|
+
output.push(`${key}=${quoted(changes[key])}`);
|
|
64
|
+
}
|
|
65
|
+
if (output.at(-1) !== "")
|
|
66
|
+
output.push("");
|
|
67
|
+
return output.join(existing.includes("\r\n") ? "\r\n" : "\n");
|
|
68
|
+
};
|
|
69
|
+
export const inspectChannelEnv = async (channelDir) => {
|
|
70
|
+
const path = join(channelDir, ".env");
|
|
71
|
+
let contents = "";
|
|
72
|
+
let exists = false;
|
|
73
|
+
try {
|
|
74
|
+
contents = await readFile(path, "utf8");
|
|
75
|
+
exists = true;
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
if (error.code !== "ENOENT")
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
const values = readChannelEnv(contents);
|
|
82
|
+
return {
|
|
83
|
+
path,
|
|
84
|
+
exists,
|
|
85
|
+
...(values.RELAY_AGENT_TOKEN ? { token: values.RELAY_AGENT_TOKEN } : {}),
|
|
86
|
+
...(values.RELAY_BASE_URL ? { baseURL: values.RELAY_BASE_URL } : {}),
|
|
87
|
+
allowedSenders: (values.RELAY_ALLOWED_SENDERS ?? "").split(",").map((entry) => entry.trim()).filter(Boolean),
|
|
88
|
+
contents,
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* Writes the file through a private temporary file in the same folder, so a
|
|
93
|
+
* reader never sees a half-written token and the token never exists in a
|
|
94
|
+
* world-readable file for even an instant.
|
|
95
|
+
*/
|
|
96
|
+
export const writeChannelEnv = async (channelDir, values, platform = process.platform) => {
|
|
97
|
+
const state = await inspectChannelEnv(channelDir);
|
|
98
|
+
const contents = renderChannelEnv(state.contents, values);
|
|
99
|
+
await mkdir(channelDir, { recursive: true, mode: CHANNEL_DIR_MODE });
|
|
100
|
+
if (platform !== "win32")
|
|
101
|
+
await chmod(channelDir, CHANNEL_DIR_MODE);
|
|
102
|
+
const temporary = join(channelDir, `.relay-connect-${process.pid}-${randomUUID()}.tmp`);
|
|
103
|
+
const handle = await open(temporary, "wx", CHANNEL_FILE_MODE);
|
|
104
|
+
try {
|
|
105
|
+
try {
|
|
106
|
+
await handle.writeFile(contents, "utf8");
|
|
107
|
+
if (platform !== "win32")
|
|
108
|
+
await handle.chmod(CHANNEL_FILE_MODE);
|
|
109
|
+
await handle.sync();
|
|
110
|
+
}
|
|
111
|
+
finally {
|
|
112
|
+
await handle.close();
|
|
113
|
+
}
|
|
114
|
+
await rename(temporary, state.path);
|
|
115
|
+
if (platform !== "win32")
|
|
116
|
+
await chmod(state.path, CHANNEL_FILE_MODE);
|
|
117
|
+
return { path: state.path, contents };
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
await unlink(temporary).catch(() => undefined);
|
|
121
|
+
throw error;
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
//# sourceMappingURL=claude-channel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-channel.js","sourceRoot":"","sources":["../src/claude-channel.ts"],"names":[],"mappings":"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"}
|