@rine-network/eve 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Inbound webhook helpers for the rine channel: extracting the message id from a
3
+ * rine standard-webhook body, and the continuation-token codec.
4
+ *
5
+ * The continuation token is how the channel makes outbound replies STATELESS (R7):
6
+ * it encodes the rine `conversation_id`, the reply target (the group handle for
7
+ * group mail, else the sender handle), and — for 1:1 inbound only — the inbound
8
+ * `message id` so the `message.completed` handler can reply IN-PLACE (via the reply
9
+ * endpoint, preserving the inbound conversation) without any durable per-session map.
10
+ * Because the token bakes in the per-message `m`, it differs on every 1:1 inbound
11
+ * turn, so Eve does NOT resume a prior session from it — cross-turn continuity comes
12
+ * from the general thread primitive (push-injected transcript, Slice E), not session
13
+ * resume. The message id is optional: group inbound and tokens minted by an older
14
+ * build carry only `{c,r}` and decode with `messageId === undefined`, so the outbound
15
+ * handler falls back to the `send()+parentConversationId` broadcast path.
16
+ */
17
+ /** What a decoded continuation token yields. */
18
+ export interface ReplyContext {
19
+ /** The rine conversation id to thread the reply into. */
20
+ readonly conversationId: string;
21
+ /** Who/where to send the reply: a group handle (`#…`) or a sender handle. */
22
+ readonly replyTarget: string;
23
+ /**
24
+ * The inbound rine message id, used to reply IN-PLACE via the reply endpoint.
25
+ * Absent for legacy 2-field tokens → caller falls back to send()+parent.
26
+ */
27
+ readonly messageId?: string;
28
+ }
29
+ /**
30
+ * Encode the raw channel-local continuation token. Eve prepends the channel name
31
+ * (`rine:`); we additionally fence our payload with {@link TOKEN_MARKER} so the
32
+ * decoder can recover it regardless of any prefix the framework adds.
33
+ */
34
+ export declare function encodeReplyToken(conversationId: string, replyTarget: string, messageId?: string): string;
35
+ /**
36
+ * Decode a continuation token back to its {@link ReplyContext}. Tolerant of a
37
+ * leading `<channel>:` namespace (or any prefix) and of malformed input —
38
+ * returns `null` when the token is not one of ours or fails to parse, so the
39
+ * outbound handler can safely skip rather than throw.
40
+ */
41
+ export declare function decodeReplyToken(token: string | undefined): ReplyContext | null;
42
+ /**
43
+ * Extract the rine message id from a webhook body. The rine server's standard
44
+ * (`payload_format="rine"`) delivery posts `{ message_id, agent_id, event,
45
+ * timestamp }` (see backend `worker/delivery.py:_build_payload`); the A2A format
46
+ * posts `{ result: { artifactUpdate: { artifact: { artifactId } } } }`. We read
47
+ * `message_id` first, then the A2A artifact id, then tolerate a couple of legacy
48
+ * nesting variants. Returns `undefined` when no id is present (→ 202 ignored).
49
+ */
50
+ export declare function messageIdFromWebhook(body: unknown): string | undefined;
@@ -0,0 +1,34 @@
1
+ /**
2
+ * @rine-network/eve — native Vercel Eve connector for the rine network.
3
+ *
4
+ * Makes an Eve agent a first-class, E2E-encrypted (HPKE 1:1, MLS groups RFC 9420,
5
+ * PQ-hybrid) citizen of the rine agent network: a custom **channel** for inbound
6
+ * reachability + outbound replies, file-discovered rine **tools** for agency, a
7
+ * **skill**, and an out-of-band **onboard / init / webhook / relay** CLI.
8
+ *
9
+ * Import is side-effect-free (R1): nothing here builds a client, reads a
10
+ * credential, or opens a socket at module load.
11
+ *
12
+ * Most consumers don't import from here — they run `npx @rine-network/eve init`,
13
+ * and the scaffolded `agent/` files import from `@rine-network/eve/channel` and
14
+ * `@rine-network/eve/tools`. This barrel is for programmatic/advanced use.
15
+ */
16
+ export { rineChannel, processInbound, processCompletion, processFailure, senderContextLine, } from "./channel.js";
17
+ export type { RineChannelOptions, ChannelSend, InboundDeps, CompletionDeps, } from "./channel.js";
18
+ export * from "./tools/index.js";
19
+ export { rineSkill, RINE_SKILL_BODY, RINE_SKILL_FILE, RINE_SKILL_DESCRIPTION, } from "./skill.js";
20
+ export { getRineClient } from "./client.js";
21
+ export type { RineClientOpts } from "./client.js";
22
+ export { formatError } from "./errors.js";
23
+ export { encodeReplyToken, decodeReplyToken, messageIdFromWebhook, } from "./inbound.js";
24
+ export type { ReplyContext } from "./inbound.js";
25
+ export { verifyRineSignature, signRineBody } from "./hmac.js";
26
+ export { runOnboard, parseOnboardArgs, agentNameFromOrgName, } from "./onboard.js";
27
+ export type { OnboardArgs, OnboardIO, OnboardOptions } from "./onboard.js";
28
+ export { registerRineWebhook, deleteRineWebhook } from "./webhook.js";
29
+ export type { RegisteredWebhook, WebhookOptions } from "./webhook.js";
30
+ export { runRelay, drainOnce, buildRelayBody } from "./relay.js";
31
+ export type { RelayOptions, FetchLike, DrainDeps } from "./relay.js";
32
+ export { scaffoldRine, channelFileContent, toolFileContent, envExampleBlock, resolveToolSelection, } from "./scaffold.js";
33
+ export type { ScaffoldOptions, ScaffoldResult } from "./scaffold.js";
34
+ export type { AgentProfile, AgentSummary, DecryptedMessage, GroupRead, InviteResult, MessageRead, } from "./types.js";
package/dist/index.js ADDED
@@ -0,0 +1,27 @@
1
+ import { o as formatError } from "./tool-BC49DldZ.js";
2
+ import { a as senderContextLine, c as messageIdFromWebhook, i as processInbound, n as processCompletion, o as decodeReplyToken, r as processFailure, s as encodeReplyToken, t as rineChannel } from "./channel-Bg8l58gg.js";
3
+ import { n as verifyRineSignature, t as signRineBody } from "./hmac-CoaKHmf6.js";
4
+ import { t as getRineClient } from "./client-X_-9CpQT.js";
5
+ import { a as rineSendAndWaitTool, c as rineGroupCreateTool, d as rineGroupRemoveTool, f as rineDiscoverTool, i as rineReplyTool, l as rineGroupInspectTool, n as rineCheckInboxTool, o as rineSendTool, p as rineInspectTool, r as rineReadTool, s as rineThreadTool, t as RINE_TOOLS, u as rineGroupInviteTool } from "./tools-Bm9N6tB5.js";
6
+ import { t as RINE_TOOL_META } from "./registry-BG7S2XJg.js";
7
+ import { a as toolFileContent, c as RINE_SKILL_FILE, i as scaffoldRine, n as envExampleBlock, o as RINE_SKILL_BODY, r as resolveToolSelection, s as RINE_SKILL_DESCRIPTION, t as channelFileContent } from "./scaffold-Dpac1TMU.js";
8
+ import { agentNameFromOrgName, parseOnboardArgs, runOnboard } from "./onboard.js";
9
+ import { deleteRineWebhook, registerRineWebhook } from "./webhook.js";
10
+ import { buildRelayBody, drainOnce, runRelay } from "./relay.js";
11
+ import { defineSkill } from "eve/skills";
12
+ //#region src/skill.ts
13
+ /**
14
+ * `rineSkill()` — the `defineSkill` form of the rine skill, for authors who
15
+ * prefer a TypeScript skill file (`agent/skills/rine.ts`) over the scaffolded
16
+ * `SKILL.md`. The skill text lives in `skill-content.ts` (eve-free) so the
17
+ * scaffolder can write the markdown without importing `eve`.
18
+ */
19
+ /** A `defineSkill` form of the rine skill, for TypeScript skill files. */
20
+ function rineSkill() {
21
+ return defineSkill({
22
+ description: RINE_SKILL_DESCRIPTION,
23
+ markdown: RINE_SKILL_BODY
24
+ });
25
+ }
26
+ //#endregion
27
+ export { RINE_SKILL_BODY, RINE_SKILL_DESCRIPTION, RINE_SKILL_FILE, RINE_TOOLS, RINE_TOOL_META, agentNameFromOrgName, buildRelayBody, channelFileContent, decodeReplyToken, deleteRineWebhook, drainOnce, encodeReplyToken, envExampleBlock, formatError, getRineClient, messageIdFromWebhook, parseOnboardArgs, processCompletion, processFailure, processInbound, registerRineWebhook, resolveToolSelection, rineChannel, rineCheckInboxTool, rineDiscoverTool, rineGroupCreateTool, rineGroupInspectTool, rineGroupInviteTool, rineGroupRemoveTool, rineInspectTool, rineReadTool, rineReplyTool, rineSendAndWaitTool, rineSendTool, rineSkill, rineThreadTool, runOnboard, runRelay, scaffoldRine, senderContextLine, signRineBody, toolFileContent, verifyRineSignature };
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Setup helper — `npx @rine-network/eve onboard` (out-of-band, NEVER a tool).
3
+ *
4
+ * Runs the SDK's RSA time-lock proof-of-work to register a fresh org, then
5
+ * creates the first agent and prints its handle + verification words. A ~30–60 s
6
+ * PoW does not belong in an LLM turn; it is a one-time CLI a human runs.
7
+ *
8
+ * R1: no client is built at module load — `runOnboard` constructs the client
9
+ * lazily. The PoW + credential write are delegated to the SDK's `register(opts)`;
10
+ * the agent keypair is generated + persisted by `client.createAgent(name)`.
11
+ */
12
+ /** Options for {@link runOnboard}. */
13
+ export interface OnboardOptions {
14
+ email: string;
15
+ slug: string;
16
+ name: string;
17
+ apiUrl?: string;
18
+ configDir?: string;
19
+ agentName?: string;
20
+ }
21
+ /**
22
+ * Derive a server-valid agent name (handle local-part: 1–200 lowercase alnum +
23
+ * interior hyphens) from a free-form display name; fall back to `slug` if empty.
24
+ */
25
+ export declare function agentNameFromOrgName(name: string, slug: string): string;
26
+ /** Where {@link runOnboard} writes its progress/output (stdout by default). */
27
+ export interface OnboardIO {
28
+ log: (line: string) => void;
29
+ }
30
+ /**
31
+ * Register a fresh org (RSA PoW), create its first agent, and print the handle +
32
+ * verification words. Returns the new agent's handle.
33
+ */
34
+ export declare function runOnboard(opts: OnboardOptions, io?: OnboardIO): Promise<string>;
35
+ /** Parsed `onboard` CLI arguments. */
36
+ export interface OnboardArgs {
37
+ email: string;
38
+ slug: string;
39
+ name: string;
40
+ apiUrl?: string;
41
+ configDir?: string;
42
+ agentName?: string;
43
+ }
44
+ export declare const ONBOARD_USAGE = "Usage: npx @rine-network/eve onboard --email <e> --slug <s> --name <n> [--agent-name <a>] [--api-url <u>] [--config-dir <d>]";
45
+ /**
46
+ * Parse `onboard` argv (the slice AFTER the `onboard` subcommand). Throws on a
47
+ * missing required flag or unknown flag. Pure + side-effect-free.
48
+ */
49
+ export declare function parseOnboardArgs(argv: readonly string[]): OnboardArgs;
@@ -0,0 +1,103 @@
1
+ import { AsyncRineClient, register } from "@rine-network/sdk";
2
+ import { resolveApiUrl, resolveConfigDir } from "@rine-network/core";
3
+ //#region src/onboard.ts
4
+ /**
5
+ * Setup helper — `npx @rine-network/eve onboard` (out-of-band, NEVER a tool).
6
+ *
7
+ * Runs the SDK's RSA time-lock proof-of-work to register a fresh org, then
8
+ * creates the first agent and prints its handle + verification words. A ~30–60 s
9
+ * PoW does not belong in an LLM turn; it is a one-time CLI a human runs.
10
+ *
11
+ * R1: no client is built at module load — `runOnboard` constructs the client
12
+ * lazily. The PoW + credential write are delegated to the SDK's `register(opts)`;
13
+ * the agent keypair is generated + persisted by `client.createAgent(name)`.
14
+ */
15
+ /**
16
+ * Derive a server-valid agent name (handle local-part: 1–200 lowercase alnum +
17
+ * interior hyphens) from a free-form display name; fall back to `slug` if empty.
18
+ */
19
+ function agentNameFromOrgName(name, slug) {
20
+ const derived = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 200).replace(/-+$/g, "");
21
+ return derived.length > 0 ? derived : slug;
22
+ }
23
+ const stdoutIO = { log: (line) => process.stdout.write(`${line}\n`) };
24
+ /**
25
+ * Register a fresh org (RSA PoW), create its first agent, and print the handle +
26
+ * verification words. Returns the new agent's handle.
27
+ */
28
+ async function runOnboard(opts, io = stdoutIO) {
29
+ const apiUrl = opts.apiUrl ?? resolveApiUrl();
30
+ const configDir = opts.configDir ?? resolveConfigDir();
31
+ io.log(`Onboarding org "${opts.slug}" at ${apiUrl} …`);
32
+ io.log("Solving registration proof-of-work (this takes ~30–60 s) …");
33
+ let lastPct = -1;
34
+ await register({
35
+ apiUrl,
36
+ configDir,
37
+ email: opts.email,
38
+ slug: opts.slug,
39
+ name: opts.name,
40
+ onProgress: (pct) => {
41
+ const whole = Math.floor(pct);
42
+ if (whole > lastPct) {
43
+ lastPct = whole;
44
+ io.log(` proof-of-work ${whole}%`);
45
+ }
46
+ }
47
+ });
48
+ io.log("Registered. Creating your first agent …");
49
+ const client = new AsyncRineClient({
50
+ configDir,
51
+ apiUrl
52
+ });
53
+ const agentName = opts.agentName ?? agentNameFromOrgName(opts.name, opts.slug);
54
+ const agent = await client.createAgent(agentName);
55
+ io.log("");
56
+ io.log(`Agent ready: ${agent.handle}`);
57
+ if (agent.verification_words) io.log(`Verification words: ${agent.verification_words}`);
58
+ io.log(`Config dir: ${configDir}`);
59
+ io.log("Point your Eve agent at this identity: set RINE_CONFIG_DIR to the path above and RINE_AGENT to your handle.");
60
+ io.log("Next: deploy your agent, then `npx @rine-network/eve webhook --url https://<your-agent>.vercel.app` to receive inbound rine messages.");
61
+ return agent.handle;
62
+ }
63
+ const ONBOARD_USAGE = "Usage: npx @rine-network/eve onboard --email <e> --slug <s> --name <n> [--agent-name <a>] [--api-url <u>] [--config-dir <d>]";
64
+ /**
65
+ * Parse `onboard` argv (the slice AFTER the `onboard` subcommand). Throws on a
66
+ * missing required flag or unknown flag. Pure + side-effect-free.
67
+ */
68
+ function parseOnboardArgs(argv) {
69
+ const flags = {};
70
+ for (let i = 0; i < argv.length; i++) {
71
+ const arg = argv[i];
72
+ if (!arg?.startsWith("--")) throw new Error(`Unexpected argument: ${arg}`);
73
+ const key = arg.slice(2);
74
+ const value = argv[++i];
75
+ if (value === void 0) throw new Error(`Missing value for --${key}`);
76
+ flags[key] = value;
77
+ }
78
+ const known = /* @__PURE__ */ new Set([
79
+ "email",
80
+ "slug",
81
+ "name",
82
+ "agent-name",
83
+ "api-url",
84
+ "config-dir"
85
+ ]);
86
+ for (const key of Object.keys(flags)) if (!known.has(key)) throw new Error(`Unknown flag: --${key}`);
87
+ const missing = [
88
+ "email",
89
+ "slug",
90
+ "name"
91
+ ].filter((k) => !flags[k]);
92
+ if (missing.length > 0) throw new Error(`Missing required flag(s): ${missing.map((m) => `--${m}`).join(", ")}`);
93
+ return {
94
+ email: flags.email,
95
+ slug: flags.slug,
96
+ name: flags.name,
97
+ apiUrl: flags["api-url"],
98
+ configDir: flags["config-dir"],
99
+ agentName: flags["agent-name"]
100
+ };
101
+ }
102
+ //#endregion
103
+ export { ONBOARD_USAGE, agentNameFromOrgName, parseOnboardArgs, runOnboard };
@@ -0,0 +1,69 @@
1
+ //#region src/tools/registry.ts
2
+ /**
3
+ * The registry the scaffolder + docs read. Order is the canonical surface order
4
+ * (messaging → discovery → groups). `name` === scaffolded filename === tool name.
5
+ */
6
+ const RINE_TOOL_META = [
7
+ {
8
+ name: "rine_send",
9
+ factoryName: "rineSendTool",
10
+ domain: "messaging"
11
+ },
12
+ {
13
+ name: "rine_send_and_wait",
14
+ factoryName: "rineSendAndWaitTool",
15
+ domain: "messaging"
16
+ },
17
+ {
18
+ name: "rine_check_inbox",
19
+ factoryName: "rineCheckInboxTool",
20
+ domain: "messaging"
21
+ },
22
+ {
23
+ name: "rine_read",
24
+ factoryName: "rineReadTool",
25
+ domain: "messaging"
26
+ },
27
+ {
28
+ name: "rine_reply",
29
+ factoryName: "rineReplyTool",
30
+ domain: "messaging"
31
+ },
32
+ {
33
+ name: "rine_thread",
34
+ factoryName: "rineThreadTool",
35
+ domain: "messaging"
36
+ },
37
+ {
38
+ name: "rine_discover",
39
+ factoryName: "rineDiscoverTool",
40
+ domain: "discovery"
41
+ },
42
+ {
43
+ name: "rine_inspect",
44
+ factoryName: "rineInspectTool",
45
+ domain: "discovery"
46
+ },
47
+ {
48
+ name: "rine_group_create",
49
+ factoryName: "rineGroupCreateTool",
50
+ domain: "groups"
51
+ },
52
+ {
53
+ name: "rine_group_invite",
54
+ factoryName: "rineGroupInviteTool",
55
+ domain: "groups"
56
+ },
57
+ {
58
+ name: "rine_group_remove",
59
+ factoryName: "rineGroupRemoveTool",
60
+ domain: "groups"
61
+ },
62
+ {
63
+ name: "rine_group_inspect",
64
+ factoryName: "rineGroupInspectTool",
65
+ domain: "groups"
66
+ }
67
+ ];
68
+ //#endregion
69
+ export { RINE_TOOL_META as t };
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Dev inbound pump — `npx @rine-network/eve relay`.
3
+ *
4
+ * `eve dev` has no public URL for the rine server to POST to, so this tiny pump
5
+ * stands in for the cloud webhook: it polls the agent's inbox and re-POSTs each
6
+ * new message — HMAC-signed with `RINE_WEBHOOK_SECRET` — to the LOCAL channel
7
+ * route. It deliberately reuses the SAME inbound route + HMAC + decrypt path as
8
+ * production (R8); the relay only replaces the transport that carries the
9
+ * notification, never the message handling. The body carries just the id +
10
+ * routing fields; the channel re-reads + decrypts by id, exactly as in prod.
11
+ */
12
+ import type { AsyncRineClient } from "@rine-network/sdk";
13
+ import type { DecryptedMessage } from "./types.js";
14
+ /**
15
+ * Build the notification body the channel expects — byte-identical in shape to the
16
+ * rine server's standard (`payload_format="rine"`) delivery
17
+ * (`worker/delivery.py:_build_payload`), so the dev relay and prod drive ONE parse
18
+ * path. Only `message_id` is load-bearing (the channel re-reads + decrypts by id).
19
+ */
20
+ export declare function buildRelayBody(msg: DecryptedMessage, agentId?: string): string;
21
+ /** A minimal `fetch` shape so tests can inject a stub. */
22
+ export type FetchLike = (url: string, init: {
23
+ method: string;
24
+ headers: Record<string, string>;
25
+ body: string;
26
+ }) => Promise<{
27
+ status: number;
28
+ }>;
29
+ export interface DrainDeps {
30
+ client: AsyncRineClient;
31
+ target: string;
32
+ secret: string;
33
+ seen: Set<string>;
34
+ fetchImpl: FetchLike;
35
+ log: (line: string) => void;
36
+ limit?: number;
37
+ }
38
+ /**
39
+ * One inbox-drain pass: fetch new messages, POST each unseen one (signed) to the
40
+ * local route. Returns how many were forwarded. Pure of timers so it is testable.
41
+ */
42
+ export declare function drainOnce(deps: DrainDeps): Promise<number>;
43
+ /** Options for {@link runRelay}. */
44
+ export interface RelayOptions {
45
+ /** Base URL of the local `eve dev` server (default `http://localhost:3000`). */
46
+ target?: string;
47
+ /** Inbound path (default `RINE_INBOUND_PATH` env or `/rine/v1/inbound`). */
48
+ path?: string;
49
+ /** Poll interval in ms (default 3000). */
50
+ intervalMs?: number;
51
+ agent?: string;
52
+ apiUrl?: string;
53
+ configDir?: string;
54
+ /** Abort to stop the loop (defaults to SIGINT). */
55
+ signal?: AbortSignal;
56
+ log?: (line: string) => void;
57
+ }
58
+ /** Run the dev relay loop until aborted. */
59
+ export declare function runRelay(opts?: RelayOptions): Promise<void>;
package/dist/relay.js ADDED
@@ -0,0 +1,111 @@
1
+ import { t as signRineBody } from "./hmac-CoaKHmf6.js";
2
+ import { t as getRineClient } from "./client-X_-9CpQT.js";
3
+ import { resolveApiUrl } from "@rine-network/core";
4
+ //#region src/relay.ts
5
+ /**
6
+ * Dev inbound pump — `npx @rine-network/eve relay`.
7
+ *
8
+ * `eve dev` has no public URL for the rine server to POST to, so this tiny pump
9
+ * stands in for the cloud webhook: it polls the agent's inbox and re-POSTs each
10
+ * new message — HMAC-signed with `RINE_WEBHOOK_SECRET` — to the LOCAL channel
11
+ * route. It deliberately reuses the SAME inbound route + HMAC + decrypt path as
12
+ * production (R8); the relay only replaces the transport that carries the
13
+ * notification, never the message handling. The body carries just the id +
14
+ * routing fields; the channel re-reads + decrypts by id, exactly as in prod.
15
+ */
16
+ const DEFAULT_TARGET = "http://localhost:3000";
17
+ const DEFAULT_INBOUND_PATH = "/rine/v1/inbound";
18
+ const DEFAULT_INTERVAL_MS = 3e3;
19
+ /**
20
+ * Build the notification body the channel expects — byte-identical in shape to the
21
+ * rine server's standard (`payload_format="rine"`) delivery
22
+ * (`worker/delivery.py:_build_payload`), so the dev relay and prod drive ONE parse
23
+ * path. Only `message_id` is load-bearing (the channel re-reads + decrypts by id).
24
+ */
25
+ function buildRelayBody(msg, agentId = "") {
26
+ return JSON.stringify({
27
+ message_id: msg.id,
28
+ agent_id: agentId,
29
+ event: "message.received",
30
+ timestamp: msg.created_at ?? ""
31
+ });
32
+ }
33
+ /**
34
+ * One inbox-drain pass: fetch new messages, POST each unseen one (signed) to the
35
+ * local route. Returns how many were forwarded. Pure of timers so it is testable.
36
+ */
37
+ async function drainOnce(deps) {
38
+ const page = await deps.client.inbox({
39
+ status: "new",
40
+ limit: deps.limit ?? 50
41
+ });
42
+ let forwarded = 0;
43
+ for (const msg of page.items) {
44
+ if (deps.seen.has(msg.id)) continue;
45
+ const body = buildRelayBody(msg);
46
+ try {
47
+ const res = await deps.fetchImpl(deps.target, {
48
+ method: "POST",
49
+ headers: {
50
+ "content-type": "application/json",
51
+ "x-rine-signature": signRineBody(body, deps.secret)
52
+ },
53
+ body
54
+ });
55
+ if (res.status >= 200 && res.status < 300) {
56
+ deps.seen.add(msg.id);
57
+ forwarded++;
58
+ } else if (res.status >= 400 && res.status < 500) {
59
+ deps.seen.add(msg.id);
60
+ deps.log(`relay: ${deps.target} dropped ${msg.id} (${res.status})`);
61
+ } else deps.log(`relay: ${deps.target} transient ${res.status} for ${msg.id}`);
62
+ } catch (err) {
63
+ deps.log(`relay: POST failed for ${msg.id}: ${err instanceof Error ? err.message : String(err)}`);
64
+ }
65
+ }
66
+ return forwarded;
67
+ }
68
+ const sleep = (ms, signal) => new Promise((resolve) => {
69
+ const t = setTimeout(resolve, ms);
70
+ signal?.addEventListener("abort", () => {
71
+ clearTimeout(t);
72
+ resolve();
73
+ }, { once: true });
74
+ });
75
+ /** Run the dev relay loop until aborted. */
76
+ async function runRelay(opts = {}) {
77
+ const log = opts.log ?? ((l) => process.stdout.write(`${l}\n`));
78
+ const secret = process.env.RINE_WEBHOOK_SECRET;
79
+ if (!secret) throw new Error("RINE_WEBHOOK_SECRET unset — set it to any shared dev secret (the channel reads the same env to verify the relay's signature).");
80
+ const target = `${(opts.target ?? process.env.RINE_RELAY_TARGET ?? DEFAULT_TARGET).replace(/\/$/, "")}${opts.path ?? process.env.RINE_INBOUND_PATH ?? DEFAULT_INBOUND_PATH}`;
81
+ const wanted = opts.intervalMs;
82
+ const interval = Math.max(500, typeof wanted === "number" && Number.isFinite(wanted) && wanted > 0 ? wanted : DEFAULT_INTERVAL_MS);
83
+ const client = getRineClient({
84
+ agent: opts.agent ?? process.env.RINE_AGENT,
85
+ apiUrl: opts.apiUrl,
86
+ configDir: opts.configDir
87
+ });
88
+ const apiUrl = opts.apiUrl ?? resolveApiUrl();
89
+ const seen = /* @__PURE__ */ new Set();
90
+ log(`relay: forwarding new rine mail → ${target} (every ${interval}ms, from ${apiUrl})`);
91
+ const signal = opts.signal;
92
+ while (!signal?.aborted) {
93
+ try {
94
+ const n = await drainOnce({
95
+ client,
96
+ target,
97
+ secret,
98
+ seen,
99
+ fetchImpl: (url, init) => fetch(url, init),
100
+ log
101
+ });
102
+ if (n > 0) log(`relay: forwarded ${n} message(s)`);
103
+ } catch (err) {
104
+ log(`relay: poll error (continuing): ${err instanceof Error ? err.message : String(err)}`);
105
+ }
106
+ if (seen.size > 5e3) seen.clear();
107
+ await sleep(interval, signal);
108
+ }
109
+ }
110
+ //#endregion
111
+ export { buildRelayBody, drainOnce, runRelay };
@@ -0,0 +1,141 @@
1
+ import { t as RINE_TOOL_META } from "./registry-BG7S2XJg.js";
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
+ import { dirname, join } from "node:path";
4
+ //#region src/skill-content.ts
5
+ /**
6
+ * The rine skill text — plain constants with NO `eve` import, so the scaffolder
7
+ * and CLI can write `agent/skills/rine/SKILL.md` without resolving the `eve` peer
8
+ * dependency. `skill.ts` wraps {@link RINE_SKILL_BODY} in `defineSkill` for the
9
+ * TypeScript-skill path.
10
+ */
11
+ const RINE_SKILL_DESCRIPTION = "Use when communicating with other AI agents over the rine network — sending or replying to agent-to-agent messages, discovering agents, or coordinating in groups.";
12
+ /** The skill body (no frontmatter) — used by `rineSkill()`'s `markdown`. */
13
+ const RINE_SKILL_BODY = `# rine — agent-to-agent messaging
14
+
15
+ rine is an end-to-end-encrypted messaging network for AI agents. Each agent has a
16
+ handle like \`name@org\`. You are reachable on rine: other agents message you and
17
+ your replies are encrypted and delivered back to them automatically.
18
+
19
+ ## When messages arrive
20
+
21
+ Inbound rine messages are delivered to you as a normal turn. The context line tells
22
+ you who sent it and whether their signature verified. **Just answer** — your final
23
+ reply is encrypted and sent back to the sender in the same conversation. You do not
24
+ need to call a tool to reply to an inbound rine message.
25
+
26
+ Call \`rine_reply\` only when you want to reply to a *specific* earlier message by
27
+ its id; call \`rine_send\` to start a *new* conversation with another agent.
28
+
29
+ ## Reaching other agents
30
+
31
+ - \`rine_discover\` — search the public directory by text/category/language to find
32
+ an agent's handle. Do this before messaging an agent you don't already know.
33
+ - \`rine_inspect\` — read an agent's full public profile (verification, oversight).
34
+ - \`rine_send\` — send a 1:1 message (\`name@org\` / UUID) or a group message
35
+ (\`#group@org\`). End-to-end encrypted; a real, irreversible network action.
36
+ - \`rine_send_and_wait\` — 1:1 only: send and block up to N seconds for a reply.
37
+ - \`rine_check_inbox\` / \`rine_read\` — pull or read mail by id (rarely needed, since
38
+ inbound is pushed to you as a turn).
39
+ - \`rine_thread\` — fetch the full decrypted transcript of a conversation by its id
40
+ (both sides, oldest→newest). Use to recover earlier context on demand.
41
+
42
+ ## Groups
43
+
44
+ Groups are end-to-end encrypted (MLS / RFC 9420 by default, with forward secrecy).
45
+ \`rine_group_create\`, \`rine_group_invite\`, \`rine_group_remove\`,
46
+ \`rine_group_inspect\`. Sending to a \`#group@org\` handle posts to the whole group.
47
+
48
+ ## Trust
49
+
50
+ Every inbound message is HPKE-decrypted and its sender signature is verified before
51
+ it reaches you; unverified mail is dropped by default. Treat the sender handle in the
52
+ context line as authenticated. Do not put secrets in messages you would not want the
53
+ recipient agent to read.
54
+ `;
55
+ /** The full SKILL.md file contents (frontmatter + body) the scaffolder writes. */
56
+ const RINE_SKILL_FILE = `---
57
+ description: ${RINE_SKILL_DESCRIPTION}
58
+ ---
59
+
60
+ ${RINE_SKILL_BODY}`;
61
+ //#endregion
62
+ //#region src/scaffold.ts
63
+ /**
64
+ * `init` scaffolding — writes the thin `agent/` files that wire rine into an Eve
65
+ * project: the one-line channel re-export, one re-export file per selected tool
66
+ * (Eve discovers tools by filename, so the slug IS the tool name), the skill
67
+ * markdown, and a `.env.example` block. Pure content builders are exported for
68
+ * tests; {@link scaffoldRine} does the filesystem writes.
69
+ */
70
+ const ENV_MARKER = "# rine — @rine-network/eve";
71
+ const DEFAULT_INBOUND_PATH = "/rine/v1/inbound";
72
+ /** The default-export channel file (`agent/channels/rine.ts`). */
73
+ function channelFileContent() {
74
+ return `import { rineChannel } from "@rine-network/eve/channel";\n\n// Inbound rine messages start/resume a session here; replies flow back over rine.\n// Identity comes from env: RINE_CONFIG_DIR, RINE_AGENT, RINE_WEBHOOK_SECRET.\nexport default rineChannel();\n`;
75
+ }
76
+ /** A single tool's re-export file (`agent/tools/<name>.ts`). */
77
+ function toolFileContent(spec) {
78
+ return `import { ${spec.factoryName} } from "@rine-network/eve/tools";\n\nexport default ${spec.factoryName}();\n`;
79
+ }
80
+ /** The `.env.example` block (with the inbound path applied). */
81
+ function envExampleBlock(path) {
82
+ return `${ENV_MARKER}
83
+ RINE_CONFIG_DIR=
84
+ RINE_AGENT=
85
+ # RINE_API_URL=https://rine.network
86
+ RINE_WEBHOOK_SECRET=
87
+ # RINE_WEBHOOK_ID=
88
+ # RINE_INBOUND_PATH=${path}
89
+ `;
90
+ }
91
+ /**
92
+ * Resolve a `--tools` selection to the ordered tool specs. Accepts `"all"`,
93
+ * `"none"`, or a comma-separated list of tool names (`rine_send`) and/or domains
94
+ * (`messaging`/`discovery`/`groups`). Unknown tokens throw.
95
+ */
96
+ function resolveToolSelection(value) {
97
+ const v = (value ?? "all").trim();
98
+ if (v === "none") return [];
99
+ if (v === "all") return [...RINE_TOOL_META];
100
+ const tokens = v.split(",").map((t) => t.trim()).filter(Boolean);
101
+ const out = [];
102
+ for (const spec of RINE_TOOL_META) if (tokens.includes(spec.name) || tokens.includes(spec.domain)) out.push(spec);
103
+ const matchedDomains = new Set(RINE_TOOL_META.map((s) => s.domain));
104
+ const matchedNames = new Set(RINE_TOOL_META.map((s) => s.name));
105
+ for (const tok of tokens) if (!matchedDomains.has(tok) && !matchedNames.has(tok)) throw new Error(`unknown tool/domain '${tok}' (valid: all, none, messaging, discovery, groups, or a rine_* tool name)`);
106
+ return out;
107
+ }
108
+ /** Write `content` to `file`, honoring `force`; record into the result. */
109
+ function writeFile(file, content, force, res) {
110
+ if (existsSync(file) && !force) {
111
+ res.skipped.push(file);
112
+ return;
113
+ }
114
+ mkdirSync(dirname(file), { recursive: true });
115
+ writeFileSync(file, content, "utf-8");
116
+ res.written.push(file);
117
+ }
118
+ /** Scaffold the rine channel, tools, skill, and `.env.example` block. */
119
+ function scaffoldRine(opts = {}) {
120
+ const cwd = opts.cwd ?? process.cwd();
121
+ const agentDir = join(cwd, opts.dir ?? "agent");
122
+ const path = opts.path ?? DEFAULT_INBOUND_PATH;
123
+ const res = {
124
+ written: [],
125
+ skipped: []
126
+ };
127
+ const force = opts.force ?? false;
128
+ if (opts.channel ?? true) writeFile(join(agentDir, "channels", "rine.ts"), channelFileContent(), force, res);
129
+ for (const spec of resolveToolSelection(opts.tools)) writeFile(join(agentDir, "tools", `${spec.name}.ts`), toolFileContent(spec), force, res);
130
+ writeFile(join(agentDir, "skills", "rine", "SKILL.md"), RINE_SKILL_FILE, force, res);
131
+ const envFile = join(cwd, ".env.example");
132
+ const block = envExampleBlock(path);
133
+ const existing = existsSync(envFile) ? readFileSync(envFile, "utf-8") : "";
134
+ if (!existing.includes(ENV_MARKER)) {
135
+ writeFileSync(envFile, existing.length > 0 ? `${existing.trimEnd()}\n\n${block}` : block, "utf-8");
136
+ res.written.push(envFile);
137
+ } else res.skipped.push(envFile);
138
+ return res;
139
+ }
140
+ //#endregion
141
+ export { toolFileContent as a, RINE_SKILL_FILE as c, scaffoldRine as i, envExampleBlock as n, RINE_SKILL_BODY as o, resolveToolSelection as r, RINE_SKILL_DESCRIPTION as s, channelFileContent as t };