@exayard/cli 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.
- package/README.md +75 -0
- package/dist/bin.d.ts +1 -0
- package/dist/bin.js +707 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.js +612 -0
- package/package.json +33 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Individual subcommand implementations for the exayard CLI.
|
|
3
|
+
*
|
|
4
|
+
* Each command takes the parsed args + an `output` sink (defaults to console.log
|
|
5
|
+
* / console.error) so the dispatch layer can swap in a JSON-mode writer for
|
|
6
|
+
* `--json` consumers. Every command returns an exit code so `bin.ts` can
|
|
7
|
+
* propagate a stable exit status up to the shell.
|
|
8
|
+
*/
|
|
9
|
+
interface CommandContext {
|
|
10
|
+
flags: Record<string, string | boolean | undefined>;
|
|
11
|
+
positional: string[];
|
|
12
|
+
json: boolean;
|
|
13
|
+
write: (line: string) => void;
|
|
14
|
+
writeErr: (line: string) => void;
|
|
15
|
+
}
|
|
16
|
+
declare const COMMANDS: Record<string, (ctx: CommandContext) => Promise<number>>;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Credential storage for the Exayard CLI.
|
|
20
|
+
*
|
|
21
|
+
* Written to ~/.exayard/config.json with mode 600 (readable only by the
|
|
22
|
+
* owner) — same pattern `gh`, `stripe`, and `vercel` use. Env vars take
|
|
23
|
+
* precedence so CI runs don't need to persist anything to disk.
|
|
24
|
+
*
|
|
25
|
+
* The file is intentionally tiny: api key + last-used org. Richer state
|
|
26
|
+
* (saved commands, history, etc.) can be added without changing the
|
|
27
|
+
* precedence rules.
|
|
28
|
+
*/
|
|
29
|
+
interface ExayardConfig {
|
|
30
|
+
apiKey?: string;
|
|
31
|
+
organizationId?: string;
|
|
32
|
+
baseUrl?: string;
|
|
33
|
+
}
|
|
34
|
+
declare const readConfig: () => ExayardConfig;
|
|
35
|
+
declare const writeConfig: (config: ExayardConfig) => void;
|
|
36
|
+
declare const clearConfig: () => void;
|
|
37
|
+
|
|
38
|
+
export { COMMANDS, type ExayardConfig, clearConfig, readConfig, writeConfig };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,612 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
9
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
10
|
+
};
|
|
11
|
+
var __export = (target, all) => {
|
|
12
|
+
for (var name in all)
|
|
13
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
14
|
+
};
|
|
15
|
+
var __copyProps = (to, from, except, desc) => {
|
|
16
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
17
|
+
for (let key of __getOwnPropNames(from))
|
|
18
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
19
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
20
|
+
}
|
|
21
|
+
return to;
|
|
22
|
+
};
|
|
23
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
24
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
25
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
26
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
27
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
28
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
29
|
+
mod
|
|
30
|
+
));
|
|
31
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
32
|
+
|
|
33
|
+
// ../sdk/dist/error.js
|
|
34
|
+
var require_error = __commonJS({
|
|
35
|
+
"../sdk/dist/error.js"(exports2) {
|
|
36
|
+
"use strict";
|
|
37
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
38
|
+
exports2.ExayardError = void 0;
|
|
39
|
+
var ExayardError = class extends Error {
|
|
40
|
+
type;
|
|
41
|
+
title;
|
|
42
|
+
status;
|
|
43
|
+
detail;
|
|
44
|
+
instance;
|
|
45
|
+
code;
|
|
46
|
+
param;
|
|
47
|
+
docUrl;
|
|
48
|
+
requestId;
|
|
49
|
+
constructor(body) {
|
|
50
|
+
const title = body.title ?? "error";
|
|
51
|
+
const status = typeof body.status === "number" ? body.status : 0;
|
|
52
|
+
const code = body.code ?? `http_${status || "unknown"}`;
|
|
53
|
+
const detail = body.detail ?? "";
|
|
54
|
+
const message = detail ? `${title} (${code}): ${detail}` : `HTTP ${status || "unknown"}: ${title}`;
|
|
55
|
+
super(message);
|
|
56
|
+
this.name = "ExayardError";
|
|
57
|
+
this.type = body.type ?? "about:blank";
|
|
58
|
+
this.title = title;
|
|
59
|
+
this.status = status;
|
|
60
|
+
this.detail = detail;
|
|
61
|
+
this.instance = body.instance;
|
|
62
|
+
this.code = code;
|
|
63
|
+
this.param = body.param;
|
|
64
|
+
this.docUrl = body.doc_url;
|
|
65
|
+
this.requestId = body.request_id;
|
|
66
|
+
}
|
|
67
|
+
// Convenience guards for the most common branching points.
|
|
68
|
+
isRateLimited() {
|
|
69
|
+
return this.code === "rate_limited" || this.status === 429;
|
|
70
|
+
}
|
|
71
|
+
isUnauthenticated() {
|
|
72
|
+
return this.code === "unauthenticated" || this.status === 401;
|
|
73
|
+
}
|
|
74
|
+
isNotFound() {
|
|
75
|
+
return this.code === "not_found" || this.status === 404;
|
|
76
|
+
}
|
|
77
|
+
isInsufficientScope() {
|
|
78
|
+
return this.code === "insufficient_scope" || this.status === 403;
|
|
79
|
+
}
|
|
80
|
+
isIdempotencyConflict() {
|
|
81
|
+
return this.code === "idempotency_key_reused";
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
exports2.ExayardError = ExayardError;
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// ../sdk/dist/webhooks.js
|
|
89
|
+
var require_webhooks = __commonJS({
|
|
90
|
+
"../sdk/dist/webhooks.js"(exports2) {
|
|
91
|
+
"use strict";
|
|
92
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
93
|
+
exports2.constructWebhookEvent = exports2.WebhookSignatureError = void 0;
|
|
94
|
+
var WebhookSignatureError2 = class extends Error {
|
|
95
|
+
code;
|
|
96
|
+
constructor(code, message) {
|
|
97
|
+
super(message);
|
|
98
|
+
this.name = "WebhookSignatureError";
|
|
99
|
+
this.code = code;
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
exports2.WebhookSignatureError = WebhookSignatureError2;
|
|
103
|
+
var REPLAY_WINDOW_SECONDS = 5 * 60;
|
|
104
|
+
var parseSignatureHeader = (header) => {
|
|
105
|
+
const parts = header.split(",").map((s) => s.trim());
|
|
106
|
+
let t;
|
|
107
|
+
let v1;
|
|
108
|
+
for (const part of parts) {
|
|
109
|
+
const eq = part.indexOf("=");
|
|
110
|
+
if (eq === -1)
|
|
111
|
+
continue;
|
|
112
|
+
const k = part.slice(0, eq);
|
|
113
|
+
const v = part.slice(eq + 1);
|
|
114
|
+
if (k === "t")
|
|
115
|
+
t = parseInt(v, 10);
|
|
116
|
+
if (k === "v1")
|
|
117
|
+
v1 = v;
|
|
118
|
+
}
|
|
119
|
+
if (t === void 0 || Number.isNaN(t) || !v1) {
|
|
120
|
+
throw new WebhookSignatureError2("malformed_header", `Exayard-Signature must be "t=<unix>,v1=<digest>" \u2014 got "${header}".`);
|
|
121
|
+
}
|
|
122
|
+
return { t, v1 };
|
|
123
|
+
};
|
|
124
|
+
var hmacSha256Base64 = async (secret, payload) => {
|
|
125
|
+
const key = await crypto.subtle.importKey("raw", new TextEncoder().encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
126
|
+
const digest = await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(payload));
|
|
127
|
+
const bytes = new Uint8Array(digest);
|
|
128
|
+
let str = "";
|
|
129
|
+
for (let i = 0; i < bytes.length; i++)
|
|
130
|
+
str += String.fromCharCode(bytes[i]);
|
|
131
|
+
return btoa(str);
|
|
132
|
+
};
|
|
133
|
+
var safeCompare = (a, b) => {
|
|
134
|
+
if (a.length !== b.length)
|
|
135
|
+
return false;
|
|
136
|
+
let result = 0;
|
|
137
|
+
for (let i = 0; i < a.length; i++)
|
|
138
|
+
result |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
139
|
+
return result === 0;
|
|
140
|
+
};
|
|
141
|
+
var constructWebhookEvent2 = async (rawBody, signatureHeader, secret, options = {}) => {
|
|
142
|
+
const { t, v1 } = parseSignatureHeader(signatureHeader);
|
|
143
|
+
const nowSec = Math.floor((options.now ?? Date.now()) / 1e3);
|
|
144
|
+
if (Math.abs(nowSec - t) > REPLAY_WINDOW_SECONDS) {
|
|
145
|
+
throw new WebhookSignatureError2("replay_window_exceeded", `Signature timestamp ${t} is outside the ${REPLAY_WINDOW_SECONDS}-second window (now=${nowSec}).`);
|
|
146
|
+
}
|
|
147
|
+
const expected = await hmacSha256Base64(secret, `${t}.${rawBody}`);
|
|
148
|
+
if (!safeCompare(expected, v1)) {
|
|
149
|
+
throw new WebhookSignatureError2("invalid_signature", "HMAC digest mismatch. Check the endpoint secret.");
|
|
150
|
+
}
|
|
151
|
+
return JSON.parse(rawBody);
|
|
152
|
+
};
|
|
153
|
+
exports2.constructWebhookEvent = constructWebhookEvent2;
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// ../sdk/dist/client.js
|
|
158
|
+
var require_client = __commonJS({
|
|
159
|
+
"../sdk/dist/client.js"(exports2) {
|
|
160
|
+
"use strict";
|
|
161
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
162
|
+
exports2.Exayard = void 0;
|
|
163
|
+
var error_1 = require_error();
|
|
164
|
+
var webhooks_1 = require_webhooks();
|
|
165
|
+
var UNSAFE_METHODS = /* @__PURE__ */ new Set(["POST", "PATCH", "PUT", "DELETE"]);
|
|
166
|
+
var randomIdempotencyKey = () => {
|
|
167
|
+
if (typeof crypto !== "undefined" && "randomUUID" in crypto) {
|
|
168
|
+
return `idem_${crypto.randomUUID().replace(/-/g, "")}`;
|
|
169
|
+
}
|
|
170
|
+
return `idem_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 10)}`;
|
|
171
|
+
};
|
|
172
|
+
var Exayard2 = class {
|
|
173
|
+
apiKey;
|
|
174
|
+
bearerToken;
|
|
175
|
+
baseUrl;
|
|
176
|
+
timeoutMs;
|
|
177
|
+
fetchImpl;
|
|
178
|
+
constructor(opts = {}) {
|
|
179
|
+
if (!opts.apiKey && !opts.bearerToken) {
|
|
180
|
+
throw new Error("Exayard: pass `apiKey` or `bearerToken` \u2014 see https://developers.exayard.com/api-reference.");
|
|
181
|
+
}
|
|
182
|
+
this.apiKey = opts.apiKey;
|
|
183
|
+
this.bearerToken = opts.bearerToken;
|
|
184
|
+
this.baseUrl = (opts.baseUrl ?? "https://api.exayard.com/v1").replace(/\/$/, "");
|
|
185
|
+
this.timeoutMs = opts.timeoutMs ?? 6e4;
|
|
186
|
+
this.fetchImpl = opts.fetch ?? fetch;
|
|
187
|
+
}
|
|
188
|
+
// Internal request primitive. Public resource methods compose this.
|
|
189
|
+
async request(opts) {
|
|
190
|
+
const url = new URL(`${this.baseUrl}${opts.path}`);
|
|
191
|
+
if (opts.query) {
|
|
192
|
+
for (const [k, v] of Object.entries(opts.query)) {
|
|
193
|
+
if (v !== void 0)
|
|
194
|
+
url.searchParams.set(k, String(v));
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
const headers = {
|
|
198
|
+
Accept: "application/json",
|
|
199
|
+
Authorization: `Bearer ${this.apiKey ?? this.bearerToken}`
|
|
200
|
+
};
|
|
201
|
+
if (opts.body !== void 0)
|
|
202
|
+
headers["Content-Type"] = "application/json";
|
|
203
|
+
const autoIdem = opts.autoIdempotencyKey ?? UNSAFE_METHODS.has(opts.method);
|
|
204
|
+
if (opts.idempotencyKey) {
|
|
205
|
+
headers["Idempotency-Key"] = opts.idempotencyKey;
|
|
206
|
+
} else if (autoIdem) {
|
|
207
|
+
headers["Idempotency-Key"] = randomIdempotencyKey();
|
|
208
|
+
}
|
|
209
|
+
const ac = new AbortController();
|
|
210
|
+
const timeout = setTimeout(() => ac.abort(), this.timeoutMs);
|
|
211
|
+
let res;
|
|
212
|
+
try {
|
|
213
|
+
res = await this.fetchImpl(url.toString(), {
|
|
214
|
+
method: opts.method,
|
|
215
|
+
headers,
|
|
216
|
+
body: opts.body !== void 0 ? JSON.stringify(opts.body) : void 0,
|
|
217
|
+
signal: ac.signal
|
|
218
|
+
});
|
|
219
|
+
} finally {
|
|
220
|
+
clearTimeout(timeout);
|
|
221
|
+
}
|
|
222
|
+
if (!res.ok) {
|
|
223
|
+
const ct = res.headers.get("Content-Type") ?? "";
|
|
224
|
+
if (ct.includes("problem+json") || ct.includes("application/json")) {
|
|
225
|
+
const body = await res.json();
|
|
226
|
+
throw new error_1.ExayardError(body);
|
|
227
|
+
}
|
|
228
|
+
const text = await res.text().catch(() => "");
|
|
229
|
+
throw new error_1.ExayardError({
|
|
230
|
+
type: "https://developers.exayard.com/concepts/errors#transport_error",
|
|
231
|
+
title: "Transport Error",
|
|
232
|
+
status: res.status,
|
|
233
|
+
detail: text.slice(0, 500) || `HTTP ${res.status}`,
|
|
234
|
+
code: "transport_error",
|
|
235
|
+
doc_url: "https://developers.exayard.com/concepts/errors#transport_error",
|
|
236
|
+
request_id: res.headers.get("X-Request-Id") ?? void 0
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
if (res.status === 204)
|
|
240
|
+
return void 0;
|
|
241
|
+
return await res.json();
|
|
242
|
+
}
|
|
243
|
+
// ---------------------------------------------------------------------------
|
|
244
|
+
// Resource surface — a curated subset covering the flows agents and integrations
|
|
245
|
+
// most often hit. Full OpenAPI coverage can be layered on via generated code
|
|
246
|
+
// without changing the public shape of this client.
|
|
247
|
+
// ---------------------------------------------------------------------------
|
|
248
|
+
me = {
|
|
249
|
+
get: () => this.request({ method: "GET", path: "/me" })
|
|
250
|
+
};
|
|
251
|
+
projects = {
|
|
252
|
+
// GET /v1/projects returns a raw array (OpenAPI schema z.array(project));
|
|
253
|
+
// there is no { items, next_cursor } envelope and the endpoint accepts only
|
|
254
|
+
// organizationId/status/search (no limit/cursor pagination yet).
|
|
255
|
+
list: (query) => this.request({ method: "GET", path: "/projects", query }),
|
|
256
|
+
get: (id, query) => this.request({ method: "GET", path: `/projects/${id}`, query }),
|
|
257
|
+
create: (body, opts = {}) => this.request({ method: "POST", path: "/projects", body, idempotencyKey: opts.idempotencyKey }),
|
|
258
|
+
export: (id, query) => this.request({ method: "GET", path: `/projects/${id}/export`, query }),
|
|
259
|
+
archive: (id, query) => this.request({ method: "POST", path: `/projects/${id}/archive`, query })
|
|
260
|
+
};
|
|
261
|
+
help = {
|
|
262
|
+
search: (body) => this.request({ method: "POST", path: "/help/search", body, autoIdempotencyKey: false })
|
|
263
|
+
};
|
|
264
|
+
webhooks = {
|
|
265
|
+
listEndpoints: (query) => this.request({ method: "GET", path: "/webhook_endpoints", query }),
|
|
266
|
+
createEndpoint: (body) => this.request({ method: "POST", path: "/webhook_endpoints", body }),
|
|
267
|
+
deleteEndpoint: (id, query) => this.request({ method: "DELETE", path: `/webhook_endpoints/${id}`, query }),
|
|
268
|
+
listDeliveries: (id, query) => this.request({ method: "GET", path: `/webhook_endpoints/${id}/deliveries`, query }),
|
|
269
|
+
/**
|
|
270
|
+
* Parse + verify an inbound webhook delivery. Throws WebhookSignatureError
|
|
271
|
+
* on any signature failure — always catch and return 400 to let us retry.
|
|
272
|
+
*/
|
|
273
|
+
constructEvent: webhooks_1.constructWebhookEvent
|
|
274
|
+
};
|
|
275
|
+
};
|
|
276
|
+
exports2.Exayard = Exayard2;
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
// ../sdk/dist/index.js
|
|
281
|
+
var require_dist = __commonJS({
|
|
282
|
+
"../sdk/dist/index.js"(exports2) {
|
|
283
|
+
"use strict";
|
|
284
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
285
|
+
exports2.WebhookSignatureError = exports2.constructWebhookEvent = exports2.ExayardError = exports2.Exayard = void 0;
|
|
286
|
+
var client_1 = require_client();
|
|
287
|
+
Object.defineProperty(exports2, "Exayard", { enumerable: true, get: function() {
|
|
288
|
+
return client_1.Exayard;
|
|
289
|
+
} });
|
|
290
|
+
var error_1 = require_error();
|
|
291
|
+
Object.defineProperty(exports2, "ExayardError", { enumerable: true, get: function() {
|
|
292
|
+
return error_1.ExayardError;
|
|
293
|
+
} });
|
|
294
|
+
var webhooks_1 = require_webhooks();
|
|
295
|
+
Object.defineProperty(exports2, "constructWebhookEvent", { enumerable: true, get: function() {
|
|
296
|
+
return webhooks_1.constructWebhookEvent;
|
|
297
|
+
} });
|
|
298
|
+
Object.defineProperty(exports2, "WebhookSignatureError", { enumerable: true, get: function() {
|
|
299
|
+
return webhooks_1.WebhookSignatureError;
|
|
300
|
+
} });
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
// src/index.ts
|
|
305
|
+
var index_exports = {};
|
|
306
|
+
__export(index_exports, {
|
|
307
|
+
COMMANDS: () => COMMANDS,
|
|
308
|
+
clearConfig: () => clearConfig,
|
|
309
|
+
readConfig: () => readConfig,
|
|
310
|
+
writeConfig: () => writeConfig
|
|
311
|
+
});
|
|
312
|
+
module.exports = __toCommonJS(index_exports);
|
|
313
|
+
|
|
314
|
+
// src/commands.ts
|
|
315
|
+
var import_sdk = __toESM(require_dist());
|
|
316
|
+
|
|
317
|
+
// src/config.ts
|
|
318
|
+
var import_node_fs = require("fs");
|
|
319
|
+
var import_node_os = require("os");
|
|
320
|
+
var import_node_path = require("path");
|
|
321
|
+
var configPath = () => (0, import_node_path.join)((0, import_node_os.homedir)(), ".exayard", "config.json");
|
|
322
|
+
var readConfig = () => {
|
|
323
|
+
const fromEnv = {
|
|
324
|
+
apiKey: process.env.EXAYARD_API_KEY,
|
|
325
|
+
organizationId: process.env.EXAYARD_ORG_ID,
|
|
326
|
+
baseUrl: process.env.EXAYARD_BASE_URL
|
|
327
|
+
};
|
|
328
|
+
const path = configPath();
|
|
329
|
+
if (!(0, import_node_fs.existsSync)(path)) return fromEnv;
|
|
330
|
+
try {
|
|
331
|
+
const raw = JSON.parse((0, import_node_fs.readFileSync)(path, "utf8"));
|
|
332
|
+
return { ...raw, ...Object.fromEntries(Object.entries(fromEnv).filter(([, v]) => v !== void 0 && v !== "")) };
|
|
333
|
+
} catch {
|
|
334
|
+
return fromEnv;
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
var writeConfig = (config) => {
|
|
338
|
+
const path = configPath();
|
|
339
|
+
(0, import_node_fs.mkdirSync)((0, import_node_path.dirname)(path), { recursive: true });
|
|
340
|
+
(0, import_node_fs.writeFileSync)(path, JSON.stringify(config, null, 2));
|
|
341
|
+
try {
|
|
342
|
+
(0, import_node_fs.chmodSync)(path, 384);
|
|
343
|
+
} catch {
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
var clearConfig = () => {
|
|
347
|
+
try {
|
|
348
|
+
(0, import_node_fs.writeFileSync)(configPath(), JSON.stringify({}));
|
|
349
|
+
} catch {
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
// src/commands.ts
|
|
354
|
+
var makeClient = (ctx) => {
|
|
355
|
+
const cfg = readConfig();
|
|
356
|
+
const apiKey = (typeof ctx.flags["api-key"] === "string" ? ctx.flags["api-key"] : void 0) ?? cfg.apiKey;
|
|
357
|
+
if (!apiKey) {
|
|
358
|
+
throw new Error("No API key. Run `exayard login` or set EXAYARD_API_KEY.");
|
|
359
|
+
}
|
|
360
|
+
return new import_sdk.Exayard({ apiKey, baseUrl: cfg.baseUrl });
|
|
361
|
+
};
|
|
362
|
+
var renderProject = (p) => {
|
|
363
|
+
const id = String(p._id ?? p.id ?? "???");
|
|
364
|
+
const name = String(p.name ?? "");
|
|
365
|
+
const status = String(p.status ?? "");
|
|
366
|
+
return `${id} ${status.padEnd(10)} ${name}`;
|
|
367
|
+
};
|
|
368
|
+
var loginCommand = async (ctx) => {
|
|
369
|
+
const pasted = typeof ctx.flags["api-key"] === "string" ? ctx.flags["api-key"] : void 0;
|
|
370
|
+
if (!pasted) {
|
|
371
|
+
ctx.writeErr("Paste an API key from https://exayard.com/settings/api-keys:");
|
|
372
|
+
ctx.writeErr(" exayard login --api-key sk_live_...");
|
|
373
|
+
return 2;
|
|
374
|
+
}
|
|
375
|
+
const orgId = typeof ctx.flags.org === "string" ? ctx.flags.org : void 0;
|
|
376
|
+
const baseUrl = (typeof ctx.flags["base-url"] === "string" ? ctx.flags["base-url"] : void 0) ?? readConfig().baseUrl;
|
|
377
|
+
writeConfig({ apiKey: pasted, organizationId: orgId, baseUrl });
|
|
378
|
+
const exa = new import_sdk.Exayard({ apiKey: pasted, baseUrl });
|
|
379
|
+
await exa.me.get();
|
|
380
|
+
ctx.write(ctx.json ? JSON.stringify({ ok: true }) : "Logged in. Token saved to ~/.exayard/config.json.");
|
|
381
|
+
return 0;
|
|
382
|
+
};
|
|
383
|
+
var projectsListCommand = async (ctx) => {
|
|
384
|
+
const exa = makeClient(ctx);
|
|
385
|
+
const cfg = readConfig();
|
|
386
|
+
const organizationId = (typeof ctx.flags.org === "string" ? ctx.flags.org : void 0) ?? cfg.organizationId;
|
|
387
|
+
if (!organizationId) {
|
|
388
|
+
ctx.writeErr("--org <organizationId> required (or set EXAYARD_ORG_ID).");
|
|
389
|
+
return 2;
|
|
390
|
+
}
|
|
391
|
+
const res = await exa.projects.list({ organizationId });
|
|
392
|
+
if (ctx.json) {
|
|
393
|
+
ctx.write(JSON.stringify(res, null, 2));
|
|
394
|
+
} else {
|
|
395
|
+
for (const item of res) {
|
|
396
|
+
ctx.write(renderProject(item));
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
return 0;
|
|
400
|
+
};
|
|
401
|
+
var webhooksListenCommand = async (ctx) => {
|
|
402
|
+
if (process.env.EXAYARD_EXPERIMENTAL !== "1") {
|
|
403
|
+
ctx.writeErr(
|
|
404
|
+
[
|
|
405
|
+
"exayard listen is not yet implemented.",
|
|
406
|
+
"",
|
|
407
|
+
"The tunnel implementation is still in progress. To preview the command",
|
|
408
|
+
"surface, set EXAYARD_EXPERIMENTAL=1 \u2014 but note the tunnel itself will not",
|
|
409
|
+
"forward events yet.",
|
|
410
|
+
"",
|
|
411
|
+
"For now, register a webhook endpoint pointing at a public tunnel",
|
|
412
|
+
"(ngrok, cloudflared) and verify signatures locally with:",
|
|
413
|
+
"",
|
|
414
|
+
' echo "$RAW_BODY" | exayard webhooks verify --signature "$EXAYARD_SIGNATURE" --secret whsec_...'
|
|
415
|
+
].join("\n")
|
|
416
|
+
);
|
|
417
|
+
return 2;
|
|
418
|
+
}
|
|
419
|
+
const forwardTo = typeof ctx.flags["forward-to"] === "string" ? ctx.flags["forward-to"] : void 0;
|
|
420
|
+
if (!forwardTo) {
|
|
421
|
+
ctx.writeErr("Usage: exayard listen --forward-to http://localhost:3000/webhook [--secret whsec_...]");
|
|
422
|
+
return 2;
|
|
423
|
+
}
|
|
424
|
+
ctx.writeErr(
|
|
425
|
+
[
|
|
426
|
+
`exayard listen \u2192 ${forwardTo}`,
|
|
427
|
+
"",
|
|
428
|
+
"[experimental] Tunnel implementation pending; command surface is stable but",
|
|
429
|
+
"no events will be forwarded. Track progress at https://developers.exayard.com."
|
|
430
|
+
].join("\n")
|
|
431
|
+
);
|
|
432
|
+
return 0;
|
|
433
|
+
};
|
|
434
|
+
var webhooksVerifyCommand = async (ctx) => {
|
|
435
|
+
const signature = typeof ctx.flags.signature === "string" ? ctx.flags.signature : void 0;
|
|
436
|
+
const secret = typeof ctx.flags.secret === "string" ? ctx.flags.secret : process.env.EXAYARD_WEBHOOK_SECRET;
|
|
437
|
+
if (!signature || !secret) {
|
|
438
|
+
ctx.writeErr('Usage: cat body.json | exayard webhooks verify --signature "t=...,v1=..." --secret whsec_...');
|
|
439
|
+
return 2;
|
|
440
|
+
}
|
|
441
|
+
const body = await readAllStdin();
|
|
442
|
+
try {
|
|
443
|
+
const event = await (0, import_sdk.constructWebhookEvent)(body, signature, secret);
|
|
444
|
+
ctx.write(ctx.json ? JSON.stringify(event, null, 2) : `verified: ${event.type} (${event.id})`);
|
|
445
|
+
return 0;
|
|
446
|
+
} catch (err) {
|
|
447
|
+
if (err instanceof import_sdk.WebhookSignatureError) {
|
|
448
|
+
ctx.writeErr(`Signature invalid (${err.code}): ${err.message}`);
|
|
449
|
+
} else {
|
|
450
|
+
ctx.writeErr(`Verify failed: ${err.message}`);
|
|
451
|
+
}
|
|
452
|
+
return 1;
|
|
453
|
+
}
|
|
454
|
+
};
|
|
455
|
+
var readAllStdin = async () => {
|
|
456
|
+
if (process.stdin.isTTY) return "";
|
|
457
|
+
const chunks = [];
|
|
458
|
+
return await new Promise((resolve, reject) => {
|
|
459
|
+
process.stdin.on("data", (chunk) => chunks.push(chunk));
|
|
460
|
+
process.stdin.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
|
|
461
|
+
process.stdin.on("error", reject);
|
|
462
|
+
});
|
|
463
|
+
};
|
|
464
|
+
var helpSearchCommand = async (ctx) => {
|
|
465
|
+
const query = ctx.positional.join(" ").trim();
|
|
466
|
+
if (!query) {
|
|
467
|
+
ctx.writeErr('Usage: exayard help-search "<query>"');
|
|
468
|
+
return 2;
|
|
469
|
+
}
|
|
470
|
+
const exa = makeClient(ctx);
|
|
471
|
+
const res = await exa.help.search({ query, limit: Number(ctx.flags.limit) || void 0 });
|
|
472
|
+
if (ctx.json) {
|
|
473
|
+
ctx.write(JSON.stringify(res, null, 2));
|
|
474
|
+
} else {
|
|
475
|
+
for (const r of res.results) ctx.write(`${r.url}
|
|
476
|
+
${r.title} \u2014 ${r.description}`);
|
|
477
|
+
}
|
|
478
|
+
return 0;
|
|
479
|
+
};
|
|
480
|
+
var resolveOrgId = (ctx) => {
|
|
481
|
+
const fromFlag = typeof ctx.flags.org === "string" ? ctx.flags.org : void 0;
|
|
482
|
+
return fromFlag ?? readConfig().organizationId ?? null;
|
|
483
|
+
};
|
|
484
|
+
var projectsCreateCommand = async (ctx) => {
|
|
485
|
+
const name = typeof ctx.flags.name === "string" ? ctx.flags.name : ctx.positional[0];
|
|
486
|
+
if (!name) {
|
|
487
|
+
ctx.writeErr('Usage: exayard projects create --name "<project name>" [--org org_...]');
|
|
488
|
+
return 2;
|
|
489
|
+
}
|
|
490
|
+
const organizationId = resolveOrgId(ctx);
|
|
491
|
+
if (!organizationId) {
|
|
492
|
+
ctx.writeErr("--org <organizationId> required (or set EXAYARD_ORG_ID).");
|
|
493
|
+
return 2;
|
|
494
|
+
}
|
|
495
|
+
const exa = makeClient(ctx);
|
|
496
|
+
const res = await exa.projects.create({ organizationId, name });
|
|
497
|
+
ctx.write(ctx.json ? JSON.stringify(res, null, 2) : `created ${res._id ?? res.id} ${name}`);
|
|
498
|
+
return 0;
|
|
499
|
+
};
|
|
500
|
+
var projectsArchiveCommand = async (ctx) => {
|
|
501
|
+
const id = typeof ctx.flags.id === "string" ? ctx.flags.id : ctx.positional[0];
|
|
502
|
+
if (!id) {
|
|
503
|
+
ctx.writeErr("Usage: exayard projects archive <project_id> [--org org_...]");
|
|
504
|
+
return 2;
|
|
505
|
+
}
|
|
506
|
+
const organizationId = resolveOrgId(ctx);
|
|
507
|
+
if (!organizationId) {
|
|
508
|
+
ctx.writeErr("--org <organizationId> required (or set EXAYARD_ORG_ID).");
|
|
509
|
+
return 2;
|
|
510
|
+
}
|
|
511
|
+
const exa = makeClient(ctx);
|
|
512
|
+
await exa.projects.archive(id, { organizationId });
|
|
513
|
+
ctx.write(ctx.json ? JSON.stringify({ ok: true, archived: id }) : `archived ${id}`);
|
|
514
|
+
return 0;
|
|
515
|
+
};
|
|
516
|
+
var webhooksListCommand = async (ctx) => {
|
|
517
|
+
const organizationId = resolveOrgId(ctx);
|
|
518
|
+
if (!organizationId) {
|
|
519
|
+
ctx.writeErr("--org <organizationId> required (or set EXAYARD_ORG_ID).");
|
|
520
|
+
return 2;
|
|
521
|
+
}
|
|
522
|
+
const exa = makeClient(ctx);
|
|
523
|
+
const res = await exa.webhooks.listEndpoints({ organizationId });
|
|
524
|
+
if (ctx.json) {
|
|
525
|
+
ctx.write(JSON.stringify(res, null, 2));
|
|
526
|
+
} else {
|
|
527
|
+
for (const ep of res) {
|
|
528
|
+
const id = String(ep.id ?? ep._id ?? "???");
|
|
529
|
+
const url = String(ep.url ?? "");
|
|
530
|
+
const events = Array.isArray(ep.events) ? ep.events.join(",") : "";
|
|
531
|
+
ctx.write(`${id} ${url} ${events}`);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
return 0;
|
|
535
|
+
};
|
|
536
|
+
var webhooksCreateCommand = async (ctx) => {
|
|
537
|
+
const url = typeof ctx.flags.url === "string" ? ctx.flags.url : void 0;
|
|
538
|
+
const eventsFlag = typeof ctx.flags.events === "string" ? ctx.flags.events : void 0;
|
|
539
|
+
const description = typeof ctx.flags.description === "string" ? ctx.flags.description : void 0;
|
|
540
|
+
if (!url || !eventsFlag) {
|
|
541
|
+
ctx.writeErr('Usage: exayard webhooks create --url <https://...> --events "project.created,bid.generated" [--description "..."]');
|
|
542
|
+
return 2;
|
|
543
|
+
}
|
|
544
|
+
const organizationId = resolveOrgId(ctx);
|
|
545
|
+
if (!organizationId) {
|
|
546
|
+
ctx.writeErr("--org <organizationId> required (or set EXAYARD_ORG_ID).");
|
|
547
|
+
return 2;
|
|
548
|
+
}
|
|
549
|
+
const events = eventsFlag.split(",").map((s) => s.trim()).filter(Boolean);
|
|
550
|
+
const exa = makeClient(ctx);
|
|
551
|
+
const res = await exa.webhooks.createEndpoint({ organizationId, url, events, description });
|
|
552
|
+
if (ctx.json) {
|
|
553
|
+
ctx.write(JSON.stringify(res, null, 2));
|
|
554
|
+
} else {
|
|
555
|
+
ctx.write(`created ${res.id} ${url}`);
|
|
556
|
+
ctx.writeErr(`secret: ${res.secret} (shown once \u2014 save it now)`);
|
|
557
|
+
}
|
|
558
|
+
return 0;
|
|
559
|
+
};
|
|
560
|
+
var webhooksDeleteCommand = async (ctx) => {
|
|
561
|
+
const id = typeof ctx.flags.id === "string" ? ctx.flags.id : ctx.positional[0];
|
|
562
|
+
if (!id) {
|
|
563
|
+
ctx.writeErr("Usage: exayard webhooks delete <endpoint_id> [--org org_...]");
|
|
564
|
+
return 2;
|
|
565
|
+
}
|
|
566
|
+
const organizationId = resolveOrgId(ctx);
|
|
567
|
+
if (!organizationId) {
|
|
568
|
+
ctx.writeErr("--org <organizationId> required (or set EXAYARD_ORG_ID).");
|
|
569
|
+
return 2;
|
|
570
|
+
}
|
|
571
|
+
const exa = makeClient(ctx);
|
|
572
|
+
await exa.webhooks.deleteEndpoint(id, { organizationId });
|
|
573
|
+
ctx.write(ctx.json ? JSON.stringify({ ok: true, deleted: id }) : `deleted ${id}`);
|
|
574
|
+
return 0;
|
|
575
|
+
};
|
|
576
|
+
var initCommand = async (ctx) => {
|
|
577
|
+
const { existsSync: existsSync2, writeFileSync: writeFileSync2 } = await import("fs");
|
|
578
|
+
const force = Boolean(ctx.flags.force);
|
|
579
|
+
if (existsSync2("agents.md") && !force) {
|
|
580
|
+
ctx.writeErr("agents.md already exists \u2014 pass --force to overwrite");
|
|
581
|
+
return 1;
|
|
582
|
+
}
|
|
583
|
+
const res = await fetch("https://exayard.com/agents.md");
|
|
584
|
+
if (!res.ok) {
|
|
585
|
+
ctx.writeErr(`Failed to fetch agents.md: ${res.status}`);
|
|
586
|
+
return 1;
|
|
587
|
+
}
|
|
588
|
+
const body = await res.text();
|
|
589
|
+
writeFileSync2("agents.md", body);
|
|
590
|
+
ctx.write(ctx.json ? JSON.stringify({ wrote: "agents.md", bytes: body.length }) : "Wrote agents.md");
|
|
591
|
+
return 0;
|
|
592
|
+
};
|
|
593
|
+
var COMMANDS = {
|
|
594
|
+
login: loginCommand,
|
|
595
|
+
"projects:list": projectsListCommand,
|
|
596
|
+
"projects:create": projectsCreateCommand,
|
|
597
|
+
"projects:archive": projectsArchiveCommand,
|
|
598
|
+
"webhooks:list": webhooksListCommand,
|
|
599
|
+
"webhooks:create": webhooksCreateCommand,
|
|
600
|
+
"webhooks:delete": webhooksDeleteCommand,
|
|
601
|
+
listen: webhooksListenCommand,
|
|
602
|
+
"webhooks:verify": webhooksVerifyCommand,
|
|
603
|
+
"help-search": helpSearchCommand,
|
|
604
|
+
init: initCommand
|
|
605
|
+
};
|
|
606
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
607
|
+
0 && (module.exports = {
|
|
608
|
+
COMMANDS,
|
|
609
|
+
clearConfig,
|
|
610
|
+
readConfig,
|
|
611
|
+
writeConfig
|
|
612
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@exayard/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Official CLI for the Exayard API",
|
|
5
|
+
"bin": {
|
|
6
|
+
"exayard": "./dist/bin.js"
|
|
7
|
+
},
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"require": "./dist/index.js",
|
|
14
|
+
"default": "./dist/index.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"README.md"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsup && chmod +x ./dist/bin.js",
|
|
23
|
+
"prepublishOnly": "yarn build",
|
|
24
|
+
"typecheck": "tsc --noEmit"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@exayard/config-typescript": "*",
|
|
28
|
+
"@exayard/sdk": "workspace:*",
|
|
29
|
+
"@types/node": "22.19.0",
|
|
30
|
+
"tsup": "^8.4.0",
|
|
31
|
+
"typescript": "5.9.3"
|
|
32
|
+
}
|
|
33
|
+
}
|