relaybraid 0.2.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.
- package/package.json +21 -0
- package/src/index.mjs +147 -0
package/package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "relaybraid",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Relaybraid SDK for Node.js: run app actions from code.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": "./src/index.mjs",
|
|
7
|
+
"files": [
|
|
8
|
+
"src"
|
|
9
|
+
],
|
|
10
|
+
"engines": {
|
|
11
|
+
"node": ">=18"
|
|
12
|
+
},
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"homepage": "https://relaybraid.com/docs/sdk",
|
|
15
|
+
"keywords": [
|
|
16
|
+
"automation",
|
|
17
|
+
"slack",
|
|
18
|
+
"integrations",
|
|
19
|
+
"sdk"
|
|
20
|
+
]
|
|
21
|
+
}
|
package/src/index.mjs
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { appendFile, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
|
|
5
|
+
const VERSION = "0.2.0";
|
|
6
|
+
const DEFAULT_ORIGIN = process.env.RELAYBRAID_ORIGIN || "https://relaybraid.com";
|
|
7
|
+
const CLIENT = `relaybraid-sdk/${VERSION} node/${process.versions.node} ${process.platform}`;
|
|
8
|
+
|
|
9
|
+
// The name of the coding agent running this process, when one is, read from AI_AGENT or the
|
|
10
|
+
// agent's own marker variable and sent as X-Relaybraid-Agent. Nothing else about the environment
|
|
11
|
+
// is sent. What the SDK sends and why: https://relaybraid.com/docs/telemetry
|
|
12
|
+
function agentName() {
|
|
13
|
+
if (process.env.AI_AGENT) return process.env.AI_AGENT;
|
|
14
|
+
if (process.env.CLAUDECODE) return "claude-code";
|
|
15
|
+
if (process.env.CURSOR_AGENT) return "cursor";
|
|
16
|
+
if (process.env.CODEX_CI) return "codex";
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function readJson(path) {
|
|
21
|
+
if (!existsSync(path)) return null;
|
|
22
|
+
try {
|
|
23
|
+
return JSON.parse(await readFile(path, "utf8"));
|
|
24
|
+
} catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// The key the CLI saved, and the install id `init` minted for this project.
|
|
30
|
+
async function readProject(cwd) {
|
|
31
|
+
const credentials = await readJson(join(cwd, ".relaybraid", "credentials.json"));
|
|
32
|
+
const config = await readJson(join(cwd, ".relaybraid", "config.json"));
|
|
33
|
+
return { apiKey: credentials?.api_key ?? null, installId: credentials?.install_id ?? config?.install_id ?? null };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function saveCredentials(cwd, apiKey, installId) {
|
|
37
|
+
const dir = join(cwd, ".relaybraid");
|
|
38
|
+
await mkdir(dir, { recursive: true });
|
|
39
|
+
await writeFile(join(dir, "credentials.json"), `${JSON.stringify({ api_key: apiKey, install_id: installId ?? null }, null, 2)}\n`, { mode: 0o600 });
|
|
40
|
+
const ignore = join(cwd, ".gitignore");
|
|
41
|
+
const current = existsSync(ignore) ? await readFile(ignore, "utf8") : "";
|
|
42
|
+
if (!current.includes(".relaybraid/credentials.json")) await appendFile(ignore, `${current.endsWith("\n") || current === "" ? "" : "\n"}.relaybraid/credentials.json\n`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export class RelaybraidError extends Error {
|
|
46
|
+
constructor(message, { status, code }) {
|
|
47
|
+
super(message);
|
|
48
|
+
this.name = "RelaybraidError";
|
|
49
|
+
this.status = status;
|
|
50
|
+
this.code = code;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export class Relaybraid {
|
|
55
|
+
#apiKey;
|
|
56
|
+
#clientId;
|
|
57
|
+
#clientSecret;
|
|
58
|
+
#setup;
|
|
59
|
+
#origin;
|
|
60
|
+
#cwd;
|
|
61
|
+
#installId;
|
|
62
|
+
claimUrl = null;
|
|
63
|
+
|
|
64
|
+
// `setup` is the code shown in the docs you installed from; it links this project to that guide.
|
|
65
|
+
// `clientId` and `clientSecret` authenticate unattended code; otherwise the SDK reads the key the
|
|
66
|
+
// CLI saved, or RELAYBRAID_API_KEY.
|
|
67
|
+
constructor({ apiKey, clientId, clientSecret, setup, origin, cwd } = {}) {
|
|
68
|
+
this.#apiKey = apiKey ?? process.env.RELAYBRAID_API_KEY ?? null;
|
|
69
|
+
this.#clientId = clientId ?? process.env.RELAYBRAID_CLIENT_ID ?? null;
|
|
70
|
+
this.#clientSecret = clientSecret ?? process.env.RELAYBRAID_CLIENT_SECRET ?? null;
|
|
71
|
+
this.#setup = setup ?? null;
|
|
72
|
+
this.#origin = origin ?? DEFAULT_ORIGIN;
|
|
73
|
+
this.#cwd = cwd ?? process.cwd();
|
|
74
|
+
this.#installId = null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Creates a trial workspace (100 free action runs), saves its key in .relaybraid/credentials.json
|
|
78
|
+
// and exposes `claimUrl`, the link the account owner uses to keep the workspace.
|
|
79
|
+
static async trial({ setup, agent, intent, origin, cwd } = {}) {
|
|
80
|
+
const client = new Relaybraid({ setup, origin, cwd });
|
|
81
|
+
const existing = await readProject(client.#cwd);
|
|
82
|
+
if (existing.apiKey) {
|
|
83
|
+
client.#apiKey = existing.apiKey;
|
|
84
|
+
client.#installId = existing.installId;
|
|
85
|
+
return client;
|
|
86
|
+
}
|
|
87
|
+
const created = await client.#request("POST", "/api/sdk/trial", { setup: setup ?? null, agent: agent ?? agentName(), intent: intent ?? null }, { auth: false });
|
|
88
|
+
client.#apiKey = created.trial_key;
|
|
89
|
+
client.#installId = created.install_id ?? null;
|
|
90
|
+
client.claimUrl = created.claim_url;
|
|
91
|
+
await saveCredentials(client.#cwd, created.trial_key, created.install_id ?? null);
|
|
92
|
+
return client;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async #exchangeClientCredentials() {
|
|
96
|
+
const form = new URLSearchParams({ grant_type: "client_credentials", client_id: this.#clientId, client_secret: this.#clientSecret });
|
|
97
|
+
const response = await fetch(`${this.#origin}/oauth/token`, { method: "POST", headers: { "user-agent": CLIENT, "x-relaybraid-client": CLIENT, "content-type": "application/x-www-form-urlencoded" }, body: form.toString() });
|
|
98
|
+
const data = await response.json().catch(() => null);
|
|
99
|
+
if (!response.ok || !data?.access_token) throw new RelaybraidError(data?.error_description ?? "Client credentials were not accepted.", { status: response.status, code: data?.error ?? "invalid_client" });
|
|
100
|
+
return data.access_token;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async #request(method, path, body, { auth = true } = {}) {
|
|
104
|
+
const headers = { "user-agent": CLIENT, "x-relaybraid-client": CLIENT, accept: "application/json" };
|
|
105
|
+
const agent = agentName();
|
|
106
|
+
if (agent) headers["x-relaybraid-agent"] = agent;
|
|
107
|
+
if (this.#setup) headers["x-relaybraid-setup"] = this.#setup;
|
|
108
|
+
if (body !== undefined) headers["content-type"] = "application/json";
|
|
109
|
+
if (auth) {
|
|
110
|
+
if (!this.#apiKey && this.#clientId && this.#clientSecret) this.#apiKey = await this.#exchangeClientCredentials();
|
|
111
|
+
if (!this.#apiKey) {
|
|
112
|
+
const project = await readProject(this.#cwd);
|
|
113
|
+
this.#apiKey = project.apiKey;
|
|
114
|
+
this.#installId ??= project.installId;
|
|
115
|
+
}
|
|
116
|
+
if (!this.#apiKey) {
|
|
117
|
+
throw new RelaybraidError("No API key. Run the Relaybraid CLI's `login`, call Relaybraid.trial(), pass clientId and clientSecret, or set RELAYBRAID_API_KEY.", { status: 0, code: "no_api_key" });
|
|
118
|
+
}
|
|
119
|
+
headers.authorization = `Bearer ${this.#apiKey}`;
|
|
120
|
+
}
|
|
121
|
+
if (this.#installId) headers["x-relaybraid-install"] = this.#installId;
|
|
122
|
+
const response = await fetch(`${this.#origin}${path}`, { method, headers, body: body === undefined ? undefined : JSON.stringify(body) });
|
|
123
|
+
const text = await response.text();
|
|
124
|
+
let data = null;
|
|
125
|
+
try {
|
|
126
|
+
data = JSON.parse(text);
|
|
127
|
+
} catch {
|
|
128
|
+
data = null;
|
|
129
|
+
}
|
|
130
|
+
if (!response.ok) {
|
|
131
|
+
throw new RelaybraidError(data?.error?.message ?? `${method} ${path} failed with ${response.status}`, { status: response.status, code: data?.error?.code ?? "http_error" });
|
|
132
|
+
}
|
|
133
|
+
return data;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
run(action, input = {}) {
|
|
137
|
+
return this.#request("POST", "/api/v1/actions/run", { action, input });
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
apps() {
|
|
141
|
+
return this.#request("GET", "/api/v1/apps");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
me() {
|
|
145
|
+
return this.#request("GET", "/api/v1/me");
|
|
146
|
+
}
|
|
147
|
+
}
|