@agentdomain/eliza-plugin 0.7.0 → 0.7.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 CHANGED
@@ -11,12 +11,13 @@ The plugin gives Eliza agents a complete AgentDomain lifecycle surface:
11
11
  - Registration quote and x402 registration
12
12
  - Registry discovery
13
13
  - Agent email send/list/batch, monthly usage, signed inbound webhooks, primary address updates, and Starter/Pro/Enterprise aliases
14
- - Typed DNS management for all 13 Spaceship-supported types, including capabilities, revision-safe batches, and BIND import/export
14
+ - Typed DNS management for all 13 supported types, including capabilities, revision-safe batches, and BIND import/export
15
15
  - SSL repair/reconfiguration
16
16
  - RenewalVault status, funding, and auto-renew
17
17
  - Per-agent Premium Plan status and upgrades
18
18
 
19
- Configure `AGENTDOMAIN_API_URL` only if you need a custom endpoint. The default is `https://agentdomain.app/api/v1`.
19
+ Configure `AGENTDOMAIN_API_URL` only if you need a custom endpoint. The default is
20
+ `https://api.agentdomain.app/api/v1`.
20
21
 
21
22
  Set `AGENTDOMAIN_BUILDER_CODE` to your public ERC-8021 app identifier whenever
22
23
  the runtime can submit direct Base writes. The value is not a private key.
package/dist/index.js CHANGED
@@ -5,6 +5,7 @@ import { createPublicClient, createWalletClient, http } from 'viem';
5
5
  import { privateKeyToAccount } from 'viem/accounts';
6
6
  import { base, baseSepolia } from 'viem/chains';
7
7
  import { z } from 'zod';
8
+ import { extractEmailAddresses, extractFencedBlock, parseCompactQuantity, readLabeledEmail, readLabeledEmailUsername, readLabeledRemainder, } from './text-parsing.js';
8
9
  /** Builds the attributed direct calldata used by Eliza's post-registration auto-renew step. */
9
10
  export function encodeElizaAutoRenewCalldata(tokenId, builderCode) {
10
11
  if (!builderCode) {
@@ -42,8 +43,6 @@ function getClients(runtime, opts = {}) {
42
43
  const TLD_PATTERN = new RegExp(String.raw `([a-z0-9-]{3,63})\.([a-z]{2,20})\b`, 'i');
43
44
  const UUID_PATTERN = /[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}/i;
44
45
  const AMOUNT_PATTERN = /\$?\s*(\d+(?:\.\d{1,6})?)\s*(?:usdc)?/i;
45
- const EMAIL_PATTERN = /[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/i;
46
- const EMAIL_GLOBAL_PATTERN = /[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi;
47
46
  const EMAIL_USERNAME_PATTERN = /^[a-z0-9](?:[a-z0-9._+-]{0,62}[a-z0-9])?$/;
48
47
  const registerSchema = z.object({
49
48
  preferredName: z.string(),
@@ -108,9 +107,9 @@ function parseRegistrationPlan(lower) {
108
107
  function parsePlan(text) {
109
108
  const lower = text.toLowerCase();
110
109
  if (lower.includes('enterprise')) {
111
- const match = lower.match(/(\d+(?:\.\d+)?)\s*(k|m)/);
112
- const monthly = match
113
- ? Math.round(Number(match[1]) * (match[2] === 'm' ? 1_000_000 : 1_000))
110
+ const quantity = parseCompactQuantity(lower);
111
+ const monthly = quantity
112
+ ? Math.round(quantity.value * (quantity.suffix === 'm' ? 1_000_000 : 1_000))
114
113
  : 100_000;
115
114
  return {
116
115
  plan: 'enterprise',
@@ -123,27 +122,29 @@ function parseDnsRecord(text) {
123
122
  const lower = text.toLowerCase();
124
123
  const typeMatch = text.match(/\b(A|AAAA|ALIAS|CAA|CNAME|HTTPS|MX|NS|PTR|SRV|SVCB|TLSA|TXT)\b/i);
125
124
  const nameMatch = text.match(/\bname[:=]\s*([^\s]+)/i);
126
- const valueMatch = text.match(/\bvalue[:=]\s*(.+?)(?=\s+\b(?:ttl|priority)[:=]|$)/i);
125
+ const value = readLabeledRemainder(text, ['value'], {
126
+ stopLabels: ['ttl', 'priority'],
127
+ });
127
128
  const ttlMatch = text.match(/\bttl[:=]\s*(\d+)/i);
128
129
  const priorityMatch = text.match(/\bpriority[:=]\s*(\d+)/i);
129
130
  const parsed = dnsRecordSchema.parse({
130
131
  type: typeMatch?.[1]?.toUpperCase() ?? (lower.includes('txt') ? 'TXT' : 'A'),
131
132
  name: nameMatch?.[1] ?? '@',
132
- value: valueMatch?.[1]?.trim() ?? text.match(/\b(?:\d{1,3}\.){3}\d{1,3}\b/)?.[0] ?? '',
133
+ value: value ?? text.match(/\b(?:\d{1,3}\.){3}\d{1,3}\b/)?.[0] ?? '',
133
134
  ttl: ttlMatch ? Number(ttlMatch[1]) : 3600,
134
135
  priority: priorityMatch ? Number(priorityMatch[1]) : undefined,
135
136
  });
136
137
  return parsed;
137
138
  }
138
139
  function parseDnsRecordsJson(text) {
139
- const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/i)?.[1];
140
+ const fenced = extractFencedBlock(text, ['json']);
140
141
  const json = fenced ?? text.slice(text.indexOf('['), text.lastIndexOf(']') + 1);
141
142
  if (!json)
142
143
  throw new Error('A JSON array of DNS records is required');
143
144
  return z.array(sharedDnsRecordSchema).min(1).max(200).parse(JSON.parse(json));
144
145
  }
145
146
  function parseZoneFile(text) {
146
- const fenced = text.match(/```(?:bind|zone|dns)?\s*([\s\S]*?)```/i)?.[1];
147
+ const fenced = extractFencedBlock(text, ['bind', 'zone', 'dns']);
147
148
  if (!fenced?.trim())
148
149
  throw new Error('Put the BIND zone records inside a fenced code block');
149
150
  return fenced.trim();
@@ -162,20 +163,20 @@ function requireDnsRevision(value) {
162
163
  return value;
163
164
  }
164
165
  function parseEmailUsername(text) {
165
- const explicit = text.match(/\b(?:email\s+username|primary\s+email|username|alias)[:=]?\s*([a-z0-9._+-]{1,64})/i)?.[1] ?? text.match(EMAIL_PATTERN)?.[0]?.split('@')[0];
166
+ const explicit = readLabeledEmailUsername(text, ['email username', 'primary email', 'username', 'alias']) ??
167
+ extractEmailAddresses(text)[0]?.split('@')[0];
166
168
  const username = explicit?.trim().toLowerCase();
167
169
  return username && EMAIL_USERNAME_PATTERN.test(username) ? username : null;
168
170
  }
169
171
  function parseEmailRequest(text) {
170
- const fromAddress = text.match(/\bfrom[:=]\s*([A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,})/i)?.[1];
171
- const explicitTo = text.match(/\bto[:=]\s*([A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,})/i)?.[1];
172
- const emails = text.match(EMAIL_GLOBAL_PATTERN) ?? [];
172
+ const fromAddress = readLabeledEmail(text, ['from']);
173
+ const explicitTo = readLabeledEmail(text, ['to']);
174
+ const emails = extractEmailAddresses(text);
173
175
  const to = explicitTo ?? emails.find((email) => email.toLowerCase() !== fromAddress?.toLowerCase());
174
176
  if (!to)
175
177
  throw new Error('Recipient email address is required');
176
- const subject = text.match(/\bsubject[:=]\s*([^|]+)/i)?.[1]?.trim() ?? 'AgentDomain message';
177
- const body = text.match(/\b(?:text|body|message)[:=]\s*([\s\S]+)/i)?.[1]?.trim() ??
178
- text.replace(to, '').trim();
178
+ const subject = readLabeledRemainder(text, ['subject'], { stopCharacter: '|' }) ?? 'AgentDomain message';
179
+ const body = readLabeledRemainder(text, ['text', 'body', 'message']) ?? text.replace(to, '').trim();
179
180
  return { to, fromAddress, subject, text: body || 'Hello from AgentDomain.' };
180
181
  }
181
182
  export const quoteRegistrationAction = {
@@ -412,7 +413,7 @@ export const deleteEmailAliasAction = {
412
413
  handler: async (runtime, message) => {
413
414
  const { ad } = getClients(runtime);
414
415
  const agentId = requireAgentId(message.content.text);
415
- const emailAddress = message.content.text.match(EMAIL_PATTERN)?.[0];
416
+ const emailAddress = extractEmailAddresses(message.content.text)[0];
416
417
  if (!emailAddress)
417
418
  throw new Error('Full alias email address is required');
418
419
  const result = await ad.deleteEmailAlias(agentId, emailAddress);
@@ -470,7 +471,7 @@ export const enableAutoRenewAction = {
470
471
  };
471
472
  export const reconfigureSslAction = {
472
473
  name: 'RECONFIGURE_SSL',
473
- description: 'Rebuild the Cloudflare SaaS SSL hostname and sync Spaceship DNS validation records for an AgentDomain identity.',
474
+ description: 'Rebuild the managed SSL hostname and sync DNS validation records for an AgentDomain identity.',
474
475
  similes: ['FIX_SSL', 'REPAIR_SSL', 'SYNC_SSL'],
475
476
  examples: [],
476
477
  validate: async (runtime) => Boolean(runtime.getSetting('AGENT_PRIVATE_KEY')),
@@ -509,7 +510,7 @@ export const dnsCapabilitiesAction = {
509
510
  const { ad } = getClients(runtime);
510
511
  const result = await ad.getDnsCapabilities(requireAgentId(message.content.text));
511
512
  return {
512
- text: `AgentDomain supports ${result.supportedTypes.length} Spaceship DNS record types.`,
513
+ text: `AgentDomain supports ${result.supportedTypes.length} DNS record types.`,
513
514
  data: result,
514
515
  };
515
516
  },
@@ -0,0 +1,14 @@
1
+ export interface CompactQuantity {
2
+ value: number;
3
+ suffix: 'k' | 'm';
4
+ }
5
+ export declare function readLabeledRemainder(text: string, labels: readonly string[], options?: {
6
+ requireSeparator?: boolean;
7
+ stopCharacter?: string;
8
+ stopLabels?: readonly string[];
9
+ }): string | undefined;
10
+ export declare function readLabeledEmailUsername(text: string, labels: readonly string[]): string | undefined;
11
+ export declare function extractEmailAddresses(text: string): string[];
12
+ export declare function readLabeledEmail(text: string, labels: readonly string[]): string | undefined;
13
+ export declare function extractFencedBlock(text: string, optionalLanguages: readonly string[]): string | undefined;
14
+ export declare function parseCompactQuantity(text: string): CompactQuantity | undefined;
@@ -0,0 +1,226 @@
1
+ function isAsciiWhitespace(character) {
2
+ return character === ' ' || character === '\t' || character === '\r' || character === '\n';
3
+ }
4
+ function isAsciiDigit(character) {
5
+ return character !== undefined && character >= '0' && character <= '9';
6
+ }
7
+ function isAsciiLetter(character) {
8
+ return (character !== undefined &&
9
+ ((character >= 'a' && character <= 'z') || (character >= 'A' && character <= 'Z')));
10
+ }
11
+ function isAsciiAlphanumeric(character) {
12
+ return isAsciiDigit(character) || isAsciiLetter(character);
13
+ }
14
+ function isWordCharacter(character) {
15
+ return isAsciiAlphanumeric(character) || character === '_';
16
+ }
17
+ function isEmailLocalCharacter(character) {
18
+ return (isAsciiAlphanumeric(character) ||
19
+ character === '.' ||
20
+ character === '_' ||
21
+ character === '%' ||
22
+ character === '+' ||
23
+ character === '-');
24
+ }
25
+ function isEmailDomainCharacter(character) {
26
+ return isAsciiAlphanumeric(character) || character === '.' || character === '-';
27
+ }
28
+ function skipWhitespace(text, start) {
29
+ let cursor = start;
30
+ while (cursor < text.length && isAsciiWhitespace(text[cursor]))
31
+ cursor += 1;
32
+ return cursor;
33
+ }
34
+ function asciiCharactersEqual(left, right) {
35
+ if (left === right)
36
+ return true;
37
+ if (left === undefined || !isAsciiLetter(left) || !isAsciiLetter(right))
38
+ return false;
39
+ return (left.charCodeAt(0) | 32) === (right.charCodeAt(0) | 32);
40
+ }
41
+ function matchesAsciiCaseInsensitive(text, pattern, start) {
42
+ if (start + pattern.length > text.length)
43
+ return false;
44
+ for (let offset = 0; offset < pattern.length; offset += 1) {
45
+ if (!asciiCharactersEqual(text[start + offset], pattern[offset]))
46
+ return false;
47
+ }
48
+ return true;
49
+ }
50
+ function findLabeledValue(text, labels, requireSeparator, fromIndex = 0, requireWhitespaceBefore = false) {
51
+ let best = null;
52
+ for (const label of labels) {
53
+ let searchFrom = fromIndex;
54
+ while (searchFrom + label.length <= text.length) {
55
+ if (!matchesAsciiCaseInsensitive(text, label, searchFrom)) {
56
+ searchFrom += 1;
57
+ continue;
58
+ }
59
+ const labelStart = searchFrom;
60
+ const labelEnd = labelStart + label.length;
61
+ const hasStartBoundary = labelStart === 0 || !isWordCharacter(text[labelStart - 1]);
62
+ const hasEndBoundary = labelEnd === text.length || !isWordCharacter(text[labelEnd]);
63
+ const hasRequiredWhitespace = !requireWhitespaceBefore ||
64
+ labelStart === fromIndex ||
65
+ isAsciiWhitespace(text[labelStart - 1]);
66
+ if (hasStartBoundary && hasEndBoundary && hasRequiredWhitespace) {
67
+ let valueStart = skipWhitespace(text, labelEnd);
68
+ const hasSeparator = text[valueStart] === ':' || text[valueStart] === '=';
69
+ if (hasSeparator)
70
+ valueStart = skipWhitespace(text, valueStart + 1);
71
+ if (!requireSeparator || hasSeparator) {
72
+ if (best === null || labelStart < best.labelStart)
73
+ best = { labelStart, valueStart };
74
+ break;
75
+ }
76
+ }
77
+ searchFrom += 1;
78
+ }
79
+ }
80
+ return best;
81
+ }
82
+ export function readLabeledRemainder(text, labels, options = {}) {
83
+ const match = findLabeledValue(text, labels, options.requireSeparator ?? true);
84
+ if (match === null)
85
+ return undefined;
86
+ let valueEnd = text.length;
87
+ if (options.stopCharacter) {
88
+ const stop = text.indexOf(options.stopCharacter, match.valueStart);
89
+ if (stop >= 0)
90
+ valueEnd = stop;
91
+ }
92
+ if (options.stopLabels?.length) {
93
+ const stop = findLabeledValue(text, options.stopLabels, true, match.valueStart, true);
94
+ if (stop !== null && stop.labelStart < valueEnd)
95
+ valueEnd = stop.labelStart;
96
+ }
97
+ const value = text.slice(match.valueStart, valueEnd).trim();
98
+ return value || undefined;
99
+ }
100
+ export function readLabeledEmailUsername(text, labels) {
101
+ const match = findLabeledValue(text, labels, false);
102
+ if (match === null || !isEmailLocalCharacter(text[match.valueStart]))
103
+ return undefined;
104
+ let end = match.valueStart;
105
+ while (end < text.length && isEmailLocalCharacter(text[end]))
106
+ end += 1;
107
+ return text.slice(match.valueStart, end);
108
+ }
109
+ function isValidDomain(domain) {
110
+ let labelStart = 0;
111
+ let dotCount = 0;
112
+ for (let index = 0; index <= domain.length; index += 1) {
113
+ if (index < domain.length && domain[index] !== '.') {
114
+ if (!isAsciiAlphanumeric(domain[index]) && domain[index] !== '-')
115
+ return false;
116
+ continue;
117
+ }
118
+ if (index === labelStart ||
119
+ !isAsciiAlphanumeric(domain[labelStart]) ||
120
+ !isAsciiAlphanumeric(domain[index - 1])) {
121
+ return false;
122
+ }
123
+ if (index < domain.length) {
124
+ dotCount += 1;
125
+ labelStart = index + 1;
126
+ }
127
+ }
128
+ if (dotCount < 1 || domain.length - labelStart < 2)
129
+ return false;
130
+ for (let index = labelStart; index < domain.length; index += 1) {
131
+ if (!isAsciiLetter(domain[index]))
132
+ return false;
133
+ }
134
+ return true;
135
+ }
136
+ function isValidEmailParts(local, domain) {
137
+ if (!local || local.startsWith('.') || local.endsWith('.') || local.includes('..') || !domain) {
138
+ return false;
139
+ }
140
+ return isValidDomain(domain);
141
+ }
142
+ function readEmailAt(text, start) {
143
+ if (!isEmailLocalCharacter(text[start]))
144
+ return { nextIndex: start + 1 };
145
+ let cursor = start;
146
+ while (cursor < text.length && isEmailLocalCharacter(text[cursor]))
147
+ cursor += 1;
148
+ const localEnd = cursor;
149
+ if (text[cursor] !== '@')
150
+ return { nextIndex: cursor };
151
+ cursor += 1;
152
+ const domainStart = cursor;
153
+ while (cursor < text.length && isEmailDomainCharacter(text[cursor]))
154
+ cursor += 1;
155
+ let addressEnd = cursor;
156
+ while (addressEnd > domainStart && text[addressEnd - 1] === '.')
157
+ addressEnd -= 1;
158
+ const local = text.slice(start, localEnd);
159
+ const domain = text.slice(domainStart, addressEnd);
160
+ return {
161
+ email: isValidEmailParts(local, domain) ? text.slice(start, addressEnd) : undefined,
162
+ nextIndex: Math.max(cursor, start + 1),
163
+ };
164
+ }
165
+ export function extractEmailAddresses(text) {
166
+ const emails = [];
167
+ let cursor = 0;
168
+ while (cursor < text.length) {
169
+ if (!isEmailLocalCharacter(text[cursor])) {
170
+ cursor += 1;
171
+ continue;
172
+ }
173
+ const result = readEmailAt(text, cursor);
174
+ if (result.email)
175
+ emails.push(result.email);
176
+ cursor = result.nextIndex;
177
+ }
178
+ return emails;
179
+ }
180
+ export function readLabeledEmail(text, labels) {
181
+ const match = findLabeledValue(text, labels, true);
182
+ if (match === null)
183
+ return undefined;
184
+ return readEmailAt(text, match.valueStart).email;
185
+ }
186
+ export function extractFencedBlock(text, optionalLanguages) {
187
+ const openingFence = text.indexOf('```');
188
+ if (openingFence < 0)
189
+ return undefined;
190
+ let contentStart = openingFence + 3;
191
+ for (const language of optionalLanguages) {
192
+ if (matchesAsciiCaseInsensitive(text, language, contentStart)) {
193
+ contentStart += language.length;
194
+ break;
195
+ }
196
+ }
197
+ contentStart = skipWhitespace(text, contentStart);
198
+ const closingFence = text.indexOf('```', contentStart);
199
+ return closingFence < 0 ? undefined : text.slice(contentStart, closingFence);
200
+ }
201
+ export function parseCompactQuantity(text) {
202
+ let cursor = 0;
203
+ while (cursor < text.length) {
204
+ if (!isAsciiDigit(text[cursor])) {
205
+ cursor += 1;
206
+ continue;
207
+ }
208
+ const numberStart = cursor;
209
+ while (cursor < text.length && isAsciiDigit(text[cursor]))
210
+ cursor += 1;
211
+ if (text[cursor] === '.' && isAsciiDigit(text[cursor + 1])) {
212
+ cursor += 1;
213
+ while (cursor < text.length && isAsciiDigit(text[cursor]))
214
+ cursor += 1;
215
+ }
216
+ const numberEnd = cursor;
217
+ const suffixIndex = skipWhitespace(text, cursor);
218
+ const suffix = text[suffixIndex]?.toLowerCase();
219
+ if (suffix === 'k' || suffix === 'm') {
220
+ const value = Number(text.slice(numberStart, numberEnd));
221
+ if (Number.isFinite(value))
222
+ return { value, suffix };
223
+ }
224
+ }
225
+ return undefined;
226
+ }
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@agentdomain/eliza-plugin",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "description": "ElizaOS plugin for AgentDomain - give your Eliza agent a complete identity stack",
5
5
  "license": "Apache-2.0",
6
6
  "repository": "https://github.com/0xmdrakib/AgentDomain.git",
7
- "homepage": "https://agentdomain.app/docs",
7
+ "homepage": "https://docs.agentdomain.app",
8
8
  "bugs": "https://github.com/0xmdrakib/AgentDomain/issues",
9
9
  "type": "module",
10
10
  "main": "./dist/index.js",