@agentisend/sdk-node 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.
Files changed (50) hide show
  1. package/README.md +97 -0
  2. package/dist/cli/args.d.ts +24 -0
  3. package/dist/cli/args.js +71 -0
  4. package/dist/cli/args.js.map +1 -0
  5. package/dist/cli/commands.d.ts +29 -0
  6. package/dist/cli/commands.js +147 -0
  7. package/dist/cli/commands.js.map +1 -0
  8. package/dist/cli/config.d.ts +31 -0
  9. package/dist/cli/config.js +73 -0
  10. package/dist/cli/config.js.map +1 -0
  11. package/dist/cli/doctor.d.ts +36 -0
  12. package/dist/cli/doctor.js +161 -0
  13. package/dist/cli/doctor.js.map +1 -0
  14. package/dist/cli/listen.d.ts +28 -0
  15. package/dist/cli/listen.js +140 -0
  16. package/dist/cli/listen.js.map +1 -0
  17. package/dist/cli/main.d.ts +14 -0
  18. package/dist/cli/main.js +369 -0
  19. package/dist/cli/main.js.map +1 -0
  20. package/dist/cli/output.d.ts +40 -0
  21. package/dist/cli/output.js +35 -0
  22. package/dist/cli/output.js.map +1 -0
  23. package/dist/cli/scheduled-at.d.ts +19 -0
  24. package/dist/cli/scheduled-at.js +108 -0
  25. package/dist/cli/scheduled-at.js.map +1 -0
  26. package/dist/cli.d.ts +2 -0
  27. package/dist/cli.js +17 -0
  28. package/dist/cli.js.map +1 -0
  29. package/dist/client.d.ts +426 -0
  30. package/dist/client.js +542 -0
  31. package/dist/client.js.map +1 -0
  32. package/dist/compat-resend.d.ts +194 -0
  33. package/dist/compat-resend.js +233 -0
  34. package/dist/compat-resend.js.map +1 -0
  35. package/dist/errors.d.ts +82 -0
  36. package/dist/errors.js +92 -0
  37. package/dist/errors.js.map +1 -0
  38. package/dist/index.d.ts +10 -0
  39. package/dist/index.js +9 -0
  40. package/dist/index.js.map +1 -0
  41. package/dist/types.d.ts +499 -0
  42. package/dist/types.js +9 -0
  43. package/dist/types.js.map +1 -0
  44. package/dist/version.d.ts +8 -0
  45. package/dist/version.js +9 -0
  46. package/dist/version.js.map +1 -0
  47. package/dist/webhook-signing.d.ts +64 -0
  48. package/dist/webhook-signing.js +125 -0
  49. package/dist/webhook-signing.js.map +1 -0
  50. package/package.json +65 -0
@@ -0,0 +1,161 @@
1
+ function messageOf(err) {
2
+ const anyErr = err;
3
+ return anyErr.message ?? String(err);
4
+ }
5
+ export async function runDoctor(input) {
6
+ const checks = [];
7
+ if (!input.client) {
8
+ checks.push({
9
+ name: 'credentials',
10
+ ok: false,
11
+ detail: `No API key for profile "${input.profile}".`,
12
+ fix: `Set AGENTISEND_API_KEY, or save one with: agentisend profile set ${input.profile} --api-key as_… (stored in ${input.configPath}).`,
13
+ });
14
+ return {
15
+ ok: false,
16
+ base_url: input.baseUrl,
17
+ profile: input.profile,
18
+ key_source: input.keySource,
19
+ checks,
20
+ };
21
+ }
22
+ checks.push({
23
+ name: 'credentials',
24
+ ok: true,
25
+ detail: `Key from ${input.keySource}, profile "${input.profile}".`,
26
+ });
27
+ // 1. Can we reach it at all? /status needs no auth, so a failure here is
28
+ // the network or the URL, never the key — worth separating.
29
+ try {
30
+ const status = await input.client.status();
31
+ checks.push({
32
+ name: 'reachable',
33
+ ok: true,
34
+ detail: `${input.baseUrl} responded (${JSON.stringify(status).slice(0, 60)}).`,
35
+ });
36
+ }
37
+ catch (err) {
38
+ checks.push({
39
+ name: 'reachable',
40
+ ok: false,
41
+ detail: `Could not reach ${input.baseUrl}: ${messageOf(err)}`,
42
+ fix: 'Check the base URL and your network. Nothing below could be tested.',
43
+ });
44
+ return {
45
+ ok: false,
46
+ base_url: input.baseUrl,
47
+ profile: input.profile,
48
+ key_source: input.keySource,
49
+ checks,
50
+ };
51
+ }
52
+ // 2. Does the key authenticate, and is it stopped?
53
+ try {
54
+ const keys = await input.client.limits.list();
55
+ const paused = keys.data.filter((row) => row.paused);
56
+ checks.push({
57
+ name: 'key',
58
+ ok: true,
59
+ detail: `Authenticated. ${String(keys.data.length)} key(s) on this account.`,
60
+ });
61
+ checks.push({
62
+ name: 'kill switch',
63
+ ok: paused.length === 0,
64
+ detail: paused.length === 0
65
+ ? 'No key is paused.'
66
+ : `${String(paused.length)} key(s) paused: ${paused.map((row) => row.api_key_id).join(', ')}.`,
67
+ ...(paused.length === 0
68
+ ? {}
69
+ : {
70
+ fix: 'A person resumes a key: POST /limits/keys/:id/resume, or the Agents screen in the console.',
71
+ }),
72
+ });
73
+ const withBudget = keys.data.filter((row) => row.budget_per_period !== null);
74
+ const exhausted = withBudget.filter((row) => (row.budget_per_period ?? 0) - row.consumed_in_period <= 0);
75
+ checks.push({
76
+ name: 'budget',
77
+ ok: exhausted.length === 0,
78
+ detail: withBudget.length === 0
79
+ ? 'No key has a budget set — sending is metered but not capped.'
80
+ : `${String(withBudget.length)} key(s) budgeted; ${String(exhausted.length)} exhausted.`,
81
+ ...(exhausted.length === 0
82
+ ? {}
83
+ : { fix: 'Raise the budget (PATCH /limits/keys/:id) or wait for the period to reset.' }),
84
+ });
85
+ }
86
+ catch (err) {
87
+ const code = err.code;
88
+ checks.push({
89
+ name: 'key',
90
+ ok: false,
91
+ detail: `The key was refused: ${messageOf(err)}`,
92
+ fix: code === 'invalid_api_key'
93
+ ? 'The key is wrong or revoked. Create another with POST /api-keys.'
94
+ : 'Check the key has limits:read — doctor reads budgets and the kill switch.',
95
+ });
96
+ }
97
+ // 3. Can anything actually be sent from here?
98
+ try {
99
+ const domains = await input.client.domains.list();
100
+ const verified = domains.data.filter((row) => row.status === 'verified');
101
+ checks.push({
102
+ name: 'domains',
103
+ ok: verified.length > 0,
104
+ detail: verified.length > 0
105
+ ? `Can send from: ${verified.map((row) => row.name).join(', ')}.`
106
+ : `No verified domain (${String(domains.data.length)} registered). Every send will be refused.`,
107
+ ...(verified.length > 0
108
+ ? {}
109
+ : {
110
+ fix: 'Publish the DNS records from GET /domains/:id, then POST /domains/:id/verify.',
111
+ }),
112
+ });
113
+ }
114
+ catch (err) {
115
+ checks.push({
116
+ name: 'domains',
117
+ ok: false,
118
+ detail: `Could not read domains: ${messageOf(err)}`,
119
+ fix: 'The key needs domains:read to answer this.',
120
+ });
121
+ }
122
+ // 4. Standing, because a throttled account explains slow sends that look
123
+ // like a bug in the caller's code.
124
+ try {
125
+ const standing = await input.client.trust.standing();
126
+ checks.push({
127
+ name: 'trust',
128
+ ok: standing.state === 'ok' || standing.state === 'warning',
129
+ detail: `Standing: ${standing.state}${standing.reasons.length > 0 ? ` (${standing.reasons.join('; ')})` : ''}.`,
130
+ ...(standing.state === 'ok'
131
+ ? {}
132
+ : { fix: 'GET /trust/standing explains each reason. POST /trust/appeal asks a human to look.' }),
133
+ });
134
+ }
135
+ catch {
136
+ // trust:read is optional for a sending-only key; not having it is not a
137
+ // fault, so this check simply does not appear.
138
+ }
139
+ return {
140
+ ok: checks.every((check) => check.ok),
141
+ base_url: input.baseUrl,
142
+ profile: input.profile,
143
+ key_source: input.keySource,
144
+ checks,
145
+ };
146
+ }
147
+ export function renderDoctor(report) {
148
+ const lines = [
149
+ `Profile: ${report.profile} · Key: ${report.key_source} · API: ${report.base_url}`,
150
+ '',
151
+ ];
152
+ for (const check of report.checks) {
153
+ lines.push(`${check.ok ? 'ok ' : 'FAIL'} ${check.name}: ${check.detail}`);
154
+ if (check.fix)
155
+ lines.push(` → ${check.fix}`);
156
+ }
157
+ lines.push('');
158
+ lines.push(report.ok ? 'Everything checks out.' : 'Something above needs attention.');
159
+ return lines.join('\n');
160
+ }
161
+ //# sourceMappingURL=doctor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/cli/doctor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ /** Headers a delivery carries that the receiving app must see unchanged. */
2
+ export declare const FORWARDED_HEADERS: string[];
3
+ export interface ListenEvent {
4
+ verified: boolean;
5
+ event_id: string | null;
6
+ event_type: string | null;
7
+ forwarded: boolean;
8
+ /** Status the receiving app returned, when we got that far. */
9
+ forward_status?: number;
10
+ reason?: string;
11
+ }
12
+ export interface ListenOptions {
13
+ secret: string;
14
+ /** Where to forward verified deliveries. Omit to verify and report only. */
15
+ forwardTo?: string | undefined;
16
+ port?: number;
17
+ host?: string;
18
+ /** Injectable for tests; defaults to global fetch. */
19
+ fetchImpl?: typeof fetch;
20
+ onEvent?: (event: ListenEvent) => void;
21
+ /** Injectable clock for the signature tolerance window. */
22
+ nowSeconds?: () => number;
23
+ }
24
+ export interface RunningListener {
25
+ port: number;
26
+ close: () => Promise<void>;
27
+ }
28
+ export declare function startWebhookListener(options: ListenOptions): Promise<RunningListener>;
@@ -0,0 +1,140 @@
1
+ /**
2
+ * M4.16 — `agentisend webhooks listen --forward-to http://localhost:3000/hook`.
3
+ *
4
+ * The one webhook feature developers name, and what it is for is the loop:
5
+ * point a tunnel (or the API itself, in dev) at this listener, and it verifies
6
+ * every delivery against the endpoint secret and forwards the ones that pass to
7
+ * the app being written — with the SAME headers and the SAME bytes, so the
8
+ * app's own verification sees exactly what production will send it.
9
+ *
10
+ * Two rules make it worth trusting:
11
+ * 1. It forwards the RAW body, byte for byte. A parse-and-re-serialise round
12
+ * trip changes key order and whitespace, which changes the HMAC, and the
13
+ * app then fails to verify a delivery that was in fact genuine.
14
+ * 2. A delivery that does NOT verify is refused and never forwarded. A local
15
+ * relay that forwards anything is a way to inject events into an app that
16
+ * believes signatures protect it.
17
+ */
18
+ import { createServer } from 'node:http';
19
+ import { EVENT_ID_HEADER, EVENT_TYPE_HEADER, REPLAY_HEADER, SIGNATURE_HEADER, SVIX_ID_HEADER, SVIX_SIGNATURE_HEADER, SVIX_TIMESTAMP_HEADER, TIMESTAMP_HEADER, verifyWebhookSignature, } from '../webhook-signing.js';
20
+ /** Headers a delivery carries that the receiving app must see unchanged. */
21
+ export const FORWARDED_HEADERS = [
22
+ SIGNATURE_HEADER,
23
+ TIMESTAMP_HEADER,
24
+ EVENT_ID_HEADER,
25
+ EVENT_TYPE_HEADER,
26
+ REPLAY_HEADER,
27
+ SVIX_ID_HEADER,
28
+ SVIX_TIMESTAMP_HEADER,
29
+ SVIX_SIGNATURE_HEADER,
30
+ 'content-type',
31
+ ];
32
+ function readBody(req) {
33
+ return new Promise((resolve, reject) => {
34
+ const chunks = [];
35
+ req.on('data', (chunk) => chunks.push(chunk));
36
+ req.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
37
+ req.on('error', reject);
38
+ });
39
+ }
40
+ function headerOf(req, name) {
41
+ const value = req.headers[name];
42
+ if (Array.isArray(value))
43
+ return value[0] ?? null;
44
+ return value ?? null;
45
+ }
46
+ export async function startWebhookListener(options) {
47
+ const fetchImpl = options.fetchImpl ?? fetch;
48
+ const handler = async (req, res) => {
49
+ if (req.method !== 'POST') {
50
+ res.writeHead(405, { 'content-type': 'application/json' });
51
+ res.end(JSON.stringify({ error: 'Only POST deliveries are accepted here.' }));
52
+ return;
53
+ }
54
+ const body = await readBody(req);
55
+ const signature = headerOf(req, SIGNATURE_HEADER);
56
+ const eventId = headerOf(req, EVENT_ID_HEADER);
57
+ const eventType = headerOf(req, EVENT_TYPE_HEADER);
58
+ const verified = signature !== null &&
59
+ verifyWebhookSignature(options.secret, signature, body, {
60
+ ...(options.nowSeconds ? { nowSeconds: options.nowSeconds() } : {}),
61
+ });
62
+ if (!verified) {
63
+ // Refused, not forwarded. The app behind this must never receive
64
+ // something this listener could not prove came from AgentiSend.
65
+ options.onEvent?.({
66
+ verified: false,
67
+ event_id: eventId,
68
+ event_type: eventType,
69
+ forwarded: false,
70
+ reason: signature === null ? 'no signature header' : 'signature did not verify',
71
+ });
72
+ res.writeHead(400, { 'content-type': 'application/json' });
73
+ res.end(JSON.stringify({
74
+ error: {
75
+ code: 'invalid_signature',
76
+ message: 'This delivery did not verify against the endpoint secret, so it was dropped.',
77
+ fix: 'Check that --secret is the secret for THIS endpoint. It is shown once, when the endpoint is created.',
78
+ },
79
+ }));
80
+ return;
81
+ }
82
+ if (!options.forwardTo) {
83
+ options.onEvent?.({ verified: true, event_id: eventId, event_type: eventType, forwarded: false });
84
+ res.writeHead(200, { 'content-type': 'application/json' });
85
+ res.end(JSON.stringify({ verified: true }));
86
+ return;
87
+ }
88
+ // Same headers, same bytes: what the app verifies here is what it will
89
+ // verify in production.
90
+ const headers = {};
91
+ for (const name of FORWARDED_HEADERS) {
92
+ const value = headerOf(req, name);
93
+ if (value !== null)
94
+ headers[name] = value;
95
+ }
96
+ try {
97
+ const forwarded = await fetchImpl(options.forwardTo, { method: 'POST', headers, body });
98
+ options.onEvent?.({
99
+ verified: true,
100
+ event_id: eventId,
101
+ event_type: eventType,
102
+ forwarded: true,
103
+ forward_status: forwarded.status,
104
+ });
105
+ res.writeHead(200, { 'content-type': 'application/json' });
106
+ res.end(JSON.stringify({ verified: true, forwarded_status: forwarded.status }));
107
+ }
108
+ catch (err) {
109
+ const detail = err instanceof Error ? err.message : String(err);
110
+ options.onEvent?.({
111
+ verified: true,
112
+ event_id: eventId,
113
+ event_type: eventType,
114
+ forwarded: false,
115
+ reason: detail,
116
+ });
117
+ // 500 back to AgentiSend so the delivery is retried rather than
118
+ // recorded as delivered while the local app was down.
119
+ res.writeHead(500, { 'content-type': 'application/json' });
120
+ res.end(JSON.stringify({ error: { code: 'forward_failed', message: detail } }));
121
+ }
122
+ };
123
+ const server = createServer((req, res) => {
124
+ void handler(req, res).catch(() => {
125
+ res.writeHead(500).end();
126
+ });
127
+ });
128
+ await new Promise((resolve) => {
129
+ server.listen(options.port ?? 0, options.host ?? '127.0.0.1', resolve);
130
+ });
131
+ const address = server.address();
132
+ const port = typeof address === 'object' && address !== null ? address.port : 0;
133
+ return {
134
+ port,
135
+ close: () => new Promise((resolve) => {
136
+ server.close(() => resolve());
137
+ }),
138
+ };
139
+ }
140
+ //# sourceMappingURL=listen.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"listen.js","sourceRoot":"","sources":["../../src/cli/listen.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import { AgentiSend } from '../client.js';
2
+ export interface RunContext {
3
+ argv: string[];
4
+ env?: NodeJS.ProcessEnv;
5
+ stdout?: (line: string) => void;
6
+ stderr?: (line: string) => void;
7
+ isTty?: boolean;
8
+ /** Injected in tests; the binary passes nothing and gets the real one. */
9
+ clientFactory?: (apiKey: string, baseUrl?: string) => AgentiSend;
10
+ readStdin?: () => string;
11
+ /** Test hook: keeps `receiving listen` from looping forever. */
12
+ now?: () => Date;
13
+ }
14
+ export declare function run(ctx: RunContext): Promise<number>;