@haiai/haiai 0.1.2 → 0.1.4

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 (41) hide show
  1. package/README.md +1 -1
  2. package/dist/cjs/client.js +260 -106
  3. package/dist/cjs/client.js.map +1 -1
  4. package/dist/cjs/errors.js +9 -6
  5. package/dist/cjs/errors.js.map +1 -1
  6. package/dist/cjs/signing.js +58 -37
  7. package/dist/cjs/signing.js.map +1 -1
  8. package/dist/cjs/verify.js +4 -7
  9. package/dist/cjs/verify.js.map +1 -1
  10. package/dist/esm/client.js +260 -106
  11. package/dist/esm/client.js.map +1 -1
  12. package/dist/esm/errors.js +9 -6
  13. package/dist/esm/errors.js.map +1 -1
  14. package/dist/esm/signing.js +58 -37
  15. package/dist/esm/signing.js.map +1 -1
  16. package/dist/esm/verify.js +4 -7
  17. package/dist/esm/verify.js.map +1 -1
  18. package/dist/types/client.d.ts +8 -2
  19. package/dist/types/client.d.ts.map +1 -1
  20. package/dist/types/errors.d.ts +5 -3
  21. package/dist/types/errors.d.ts.map +1 -1
  22. package/dist/types/signing.d.ts +15 -7
  23. package/dist/types/signing.d.ts.map +1 -1
  24. package/dist/types/verify.d.ts.map +1 -1
  25. package/npm/@haiai/cli-darwin-arm64/package.json +1 -1
  26. package/npm/@haiai/cli-darwin-x64/package.json +1 -1
  27. package/npm/@haiai/cli-linux-arm64/package.json +1 -1
  28. package/npm/@haiai/cli-linux-x64/package.json +1 -1
  29. package/npm/@haiai/cli-win32-x64/package.json +1 -1
  30. package/package.json +7 -7
  31. package/src/client.ts +304 -120
  32. package/src/errors.ts +10 -6
  33. package/src/signing.ts +84 -36
  34. package/src/verify.ts +6 -6
  35. package/tests/client-path-escaping.test.ts +30 -0
  36. package/tests/client-register.test.ts +18 -1
  37. package/tests/crypto-contract.test.ts +74 -0
  38. package/tests/error-contract.test.ts +54 -0
  39. package/tests/security-contract.test.ts +124 -0
  40. package/tests/security.test.ts +3 -3
  41. package/tests/signing.test.ts +1 -1
package/README.md CHANGED
@@ -5,7 +5,7 @@ Give your AI agent an email address. Node.js/TypeScript SDK for the [HAI.AI](htt
5
5
  ## Install
6
6
 
7
7
  ```bash
8
- npm install @haiai/haiai @hai.ai/jacs
8
+ npm install @haiai/haiai
9
9
  ```
10
10
 
11
11
  ### CLI and MCP Server
@@ -35,6 +35,8 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.HaiClient = void 0;
37
37
  const errors_js_1 = require("./errors.js");
38
+ const node_crypto_1 = require("node:crypto");
39
+ const node_fs_1 = require("node:fs");
38
40
  const signing_js_1 = require("./signing.js");
39
41
  const config_js_1 = require("./config.js");
40
42
  const jacs_1 = require("@hai.ai/jacs");
@@ -55,6 +57,75 @@ function normalizeKeyText(raw, blockType) {
55
57
  }
56
58
  return armorKeyData(raw, blockType);
57
59
  }
60
+ const credentialWorkspaceDirs = new Set();
61
+ let credentialWorkspaceCleanupRegistered = false;
62
+ function registerCredentialWorkspace(dir) {
63
+ credentialWorkspaceDirs.add(dir);
64
+ if (credentialWorkspaceCleanupRegistered) {
65
+ return;
66
+ }
67
+ credentialWorkspaceCleanupRegistered = true;
68
+ process.once('exit', () => {
69
+ for (const workspaceDir of credentialWorkspaceDirs) {
70
+ try {
71
+ (0, node_fs_1.rmSync)(workspaceDir, { recursive: true, force: true });
72
+ }
73
+ catch {
74
+ // Best-effort temp workspace cleanup.
75
+ }
76
+ }
77
+ credentialWorkspaceDirs.clear();
78
+ });
79
+ }
80
+ function createCredentialSigner(privateKeyPem, privateKeyPassphrase) {
81
+ const privateKey = privateKeyPassphrase
82
+ ? (0, node_crypto_1.createPrivateKey)({ key: privateKeyPem, format: 'pem', passphrase: privateKeyPassphrase })
83
+ : (0, node_crypto_1.createPrivateKey)({ key: privateKeyPem, format: 'pem' });
84
+ const keyType = privateKey.asymmetricKeyType;
85
+ if (keyType !== 'ed25519' && keyType !== 'rsa' && keyType !== 'rsa-pss') {
86
+ throw new errors_js_1.AuthenticationError(`fromCredentials does not support ${keyType ?? 'this'} private key type in this runtime. ` +
87
+ 'Use a config-backed client for other JACS key types.');
88
+ }
89
+ const publicKeyPem = (0, node_crypto_1.createPublicKey)(privateKey)
90
+ .export({ format: 'pem', type: 'spki' })
91
+ .toString()
92
+ .trim();
93
+ return {
94
+ algorithm: keyType === 'ed25519' ? 'ring-Ed25519' : 'RSA-PSS',
95
+ privateKeyPem: privateKeyPem.trim(),
96
+ publicKeyPem,
97
+ signStringSync(message) {
98
+ const data = Buffer.from(message, 'utf-8');
99
+ const signature = keyType === 'ed25519'
100
+ ? (0, node_crypto_1.sign)(null, data, privateKey)
101
+ : (0, node_crypto_1.sign)('sha256', data, {
102
+ key: privateKey,
103
+ padding: node_crypto_1.constants.RSA_PKCS1_PSS_PADDING,
104
+ saltLength: node_crypto_1.constants.RSA_PSS_SALTLEN_DIGEST,
105
+ });
106
+ return signature.toString('base64');
107
+ },
108
+ };
109
+ }
110
+ async function withPrivateKeyPassphrase(passphrase, run) {
111
+ const envKey = 'JACS_PRIVATE_KEY_PASSWORD';
112
+ const hadOriginal = Object.prototype.hasOwnProperty.call(process.env, envKey);
113
+ const original = process.env[envKey];
114
+ if (passphrase) {
115
+ process.env[envKey] = passphrase;
116
+ }
117
+ try {
118
+ return await run();
119
+ }
120
+ finally {
121
+ if (hadOriginal) {
122
+ process.env[envKey] = original;
123
+ }
124
+ else {
125
+ delete process.env[envKey];
126
+ }
127
+ }
128
+ }
58
129
  /**
59
130
  * HAI platform client.
60
131
  *
@@ -70,8 +141,11 @@ function normalizeKeyText(raw, blockType) {
70
141
  */
71
142
  class HaiClient {
72
143
  config;
144
+ configPath = null;
73
145
  /** JACS native agent for all cryptographic operations. */
74
146
  agent;
147
+ /** Explicit credential signer used when the runtime cannot import a PEM into JACS directly. */
148
+ credentialSigner = null;
75
149
  baseUrl;
76
150
  timeout;
77
151
  maxRetries;
@@ -111,33 +185,64 @@ class HaiClient {
111
185
  const resolvedConfigPath = resolve(configPath);
112
186
  client.agent = new jacs_1.JacsAgent();
113
187
  await client.agent.load(resolvedConfigPath);
188
+ client.configPath = resolvedConfigPath;
114
189
  return client;
115
190
  }
116
191
  /**
117
192
  * Create a HaiClient directly from a JACS ID and PEM-encoded private key.
118
193
  * Useful for testing or programmatic setup without config files.
119
194
  *
120
- * Creates a temporary JACS agent backed by on-disk key files so that
121
- * all cryptographic operations delegate to JACS core.
195
+ * Creates a temporary JACS-shaped workspace for compatibility with
196
+ * config-based flows. When this runtime cannot import PEM material into
197
+ * JACS directly, signing uses the supplied credentials and verification
198
+ * still delegates to JACS.
122
199
  */
123
200
  static async fromCredentials(jacsId, privateKeyPem, options) {
124
201
  const client = new HaiClient(options);
125
- // Store the caller's private key PEM for exportKeys/rotateKeys compatibility
126
- client._privateKeyPem = privateKeyPem;
127
- client.privateKeyPem = privateKeyPem;
202
+ const signer = createCredentialSigner(privateKeyPem, options?.privateKeyPassphrase);
203
+ // Store the caller's PEM material for exportKeys/rotateKeys compatibility.
204
+ client._privateKeyPem = signer.privateKeyPem;
205
+ client.privateKeyPem = signer.privateKeyPem;
206
+ client._publicKeyPem = signer.publicKeyPem;
128
207
  client._privateKeyPassphrase = options?.privateKeyPassphrase;
129
- // Create an ephemeral JACS agent (in-memory keys, no disk I/O required).
130
- // The ephemeral agent generates its own key pair — this is appropriate
131
- // because fromCredentials is typically followed by register() which sends
132
- // the new public key to the server.
133
- client.agent = new jacs_1.JacsAgent();
134
- const ephResult = JSON.parse(client.agent.ephemeralSync('pq2025'));
135
- client.config = {
208
+ client.credentialSigner = signer;
209
+ // Materialize a minimal JACS-shaped workspace so file-based flows (for
210
+ // example rotateKeys) have a stable key directory, even though this
211
+ // runtime still lacks a direct "load PEM credentials into JacsAgent"
212
+ // bootstrap path.
213
+ const { mkdir, mkdtemp, writeFile } = await Promise.resolve().then(() => __importStar(require('node:fs/promises')));
214
+ const { join } = await Promise.resolve().then(() => __importStar(require('node:path')));
215
+ const { tmpdir } = await Promise.resolve().then(() => __importStar(require('node:os')));
216
+ const tempDir = await mkdtemp(join(tmpdir(), 'haiai-creds-'));
217
+ registerCredentialWorkspace(tempDir);
218
+ const keyDir = join(tempDir, 'keys');
219
+ const dataDir = join(tempDir, 'data');
220
+ await mkdir(keyDir, { recursive: true });
221
+ await mkdir(dataDir, { recursive: true });
222
+ await writeFile(join(keyDir, 'agent_private_key.pem'), `${signer.privateKeyPem}\n`, { mode: 0o600 });
223
+ await writeFile(join(keyDir, 'agent_public_key.pem'), `${signer.publicKeyPem}\n`, { mode: 0o644 });
224
+ const configPath = join(tempDir, 'jacs.config.json');
225
+ const configJson = {
136
226
  jacsAgentName: jacsId,
137
- jacsAgentVersion: ephResult.version || '1.0.0',
138
- jacsKeyDir: '',
227
+ jacsAgentVersion: '1.0.0',
228
+ jacsKeyDir: './keys',
229
+ jacsPrivateKeyPath: './keys/agent_private_key.pem',
139
230
  jacsId,
231
+ jacs_data_directory: './data',
232
+ jacs_key_directory: './keys',
233
+ jacs_agent_private_key_filename: 'agent_private_key.pem',
234
+ jacs_agent_public_key_filename: 'agent_public_key.pem',
235
+ jacs_agent_key_algorithm: signer.algorithm,
236
+ jacs_default_storage: 'fs',
140
237
  };
238
+ await writeFile(configPath, JSON.stringify(configJson, null, 2) + '\n');
239
+ client.config = await (0, config_js_1.loadConfig)(configPath);
240
+ client.configPath = configPath;
241
+ // Keep a lightweight JACS agent around for canonicalization and explicit
242
+ // verifyStringSync checks. Signing is handled by the credentialSigner above
243
+ // so fromCredentials actually uses the caller's key material.
244
+ client.agent = new jacs_1.JacsAgent();
245
+ client.agent.ephemeralSync(signer.algorithm);
141
246
  return client;
142
247
  }
143
248
  /** The agent's JACS ID. */
@@ -186,6 +291,9 @@ class HaiClient {
186
291
  clearAgentKeyCache() {
187
292
  this.keyCache.clear();
188
293
  }
294
+ activeSigner() {
295
+ return this.credentialSigner ?? this.agent;
296
+ }
189
297
  // ---------------------------------------------------------------------------
190
298
  // Auth helpers
191
299
  // ---------------------------------------------------------------------------
@@ -201,10 +309,16 @@ class HaiClient {
201
309
  }
202
310
  /** Sign a UTF-8 message with the agent's private key via JACS. Returns base64. */
203
311
  signMessage(message) {
204
- return this.agent.signStringSync(message);
312
+ return this.activeSigner().signStringSync(message);
205
313
  }
206
314
  /** Build the JACS Authorization header value string. */
207
315
  buildAuthHeader() {
316
+ if (this.credentialSigner) {
317
+ const timestamp = Math.floor(Date.now() / 1000).toString();
318
+ const message = `${this.jacsId}:${timestamp}`;
319
+ const signature = this.credentialSigner.signStringSync(message);
320
+ return `JACS ${this.jacsId}:${timestamp}:${signature}`;
321
+ }
208
322
  // Prefer JACS binding delegation
209
323
  if ('buildAuthHeaderSync' in this.agent && typeof this.agent.buildAuthHeaderSync === 'function') {
210
324
  return this.agent.buildAuthHeaderSync();
@@ -336,7 +450,7 @@ class HaiClient {
336
450
  }
337
451
  // Sign canonical JSON via JACS
338
452
  const canonical = (0, signing_js_1.canonicalJson)(agentDoc);
339
- const signature = this.agent.signStringSync(canonical);
453
+ const signature = this.activeSigner().signStringSync(canonical);
340
454
  agentDoc.jacsSignature.signature = signature;
341
455
  agentJson = JSON.stringify(agentDoc);
342
456
  }
@@ -408,7 +522,7 @@ class HaiClient {
408
522
  // Build old-key auth header BEFORE rotation (chain of trust)
409
523
  const oldAuthTimestamp = Math.floor(Date.now() / 1000).toString();
410
524
  const oldAuthMessage = `${jacsId}:${oldVersion}:${oldAuthTimestamp}`;
411
- const oldAuthSig = this.agent.signStringSync(oldAuthMessage);
525
+ const oldAuthSig = this.activeSigner().signStringSync(oldAuthMessage);
412
526
  const oldAgent = this.agent;
413
527
  // Find existing private key file
414
528
  const candidates = [
@@ -452,18 +566,29 @@ class HaiClient {
452
566
  const newVersion = randomUUID();
453
567
  const generatedKeyDir = await mkdtemp(join(tmpdir(), 'haiai-rotate-'));
454
568
  let newPublicKeyPem;
569
+ let generatedSignerAgent = null;
455
570
  try {
456
571
  const resultJson = (0, jacs_1.createAgentSync)(this.config.jacsAgentName, passphrase, 'pq2025', null, // data dir
457
- generatedKeyDir, null, // config path (don't overwrite main config)
458
- null, // agent type
572
+ generatedKeyDir, join(generatedKeyDir, 'jacs.config.json'), null, // agent type
459
573
  this.config.description
460
574
  ?? 'Agent registered via Node SDK', null, // domain
461
575
  null);
462
576
  const result = JSON.parse(resultJson);
577
+ const generatedConfigPath = result.config_path;
463
578
  const newPubKeyPath = result.public_key_path || join(keyDir, 'jacs.public.pem');
464
579
  const newPrivKeyPath = result.private_key_path || join(keyDir, 'jacs.private.pem.enc');
465
580
  const newPublicKeyRaw = await readF(newPubKeyPath);
466
581
  newPublicKeyPem = normalizeKeyText(newPublicKeyRaw, 'PUBLIC KEY');
582
+ if (generatedConfigPath) {
583
+ const nextAgent = new jacs_1.JacsAgent();
584
+ try {
585
+ await withPrivateKeyPassphrase(passphrase || undefined, () => nextAgent.load(generatedConfigPath));
586
+ generatedSignerAgent = nextAgent;
587
+ }
588
+ catch {
589
+ generatedSignerAgent = null;
590
+ }
591
+ }
467
592
  if (newPrivKeyPath !== privKeyPath) {
468
593
  await copyFile(newPrivKeyPath, privKeyPath);
469
594
  }
@@ -473,8 +598,9 @@ class HaiClient {
473
598
  else {
474
599
  await writeFile(pubKeyPath, `${newPublicKeyPem}\n`);
475
600
  }
476
- this._privateKeyPem = (await readF(privKeyPath)).toString('base64');
601
+ this._privateKeyPem = (await readF(privKeyPath, 'utf-8')).trim();
477
602
  this.privateKeyPem = this._privateKeyPem;
603
+ this._publicKeyPem = newPublicKeyPem.trim();
478
604
  }
479
605
  catch (err) {
480
606
  // Rollback: restore archived keys
@@ -504,19 +630,42 @@ class HaiClient {
504
630
  },
505
631
  };
506
632
  // Reload the agent with new keys for signing
507
- const configPath = resolve(process.env.JACS_CONFIG_PATH ?? './jacs.config.json');
633
+ const configPath = resolve(process.env.JACS_CONFIG_PATH ?? this.configPath ?? './jacs.config.json');
508
634
  const reloadedAgent = new jacs_1.JacsAgent();
635
+ let reloadedWithNewKeys = false;
509
636
  try {
510
- await reloadedAgent.load(configPath);
637
+ await withPrivateKeyPassphrase(passphrase || undefined, () => reloadedAgent.load(configPath));
511
638
  this.agent = reloadedAgent;
639
+ reloadedWithNewKeys = true;
512
640
  }
513
641
  catch {
514
- // Fall back to the currently loaded in-memory agent when the freshly
515
- // generated on-disk config cannot be reloaded in this process.
516
- this.agent = oldAgent;
642
+ if (generatedSignerAgent) {
643
+ this.agent = generatedSignerAgent;
644
+ reloadedWithNewKeys = true;
645
+ }
646
+ else {
647
+ // Fall back to the currently loaded in-memory agent when we cannot
648
+ // hydrate the new key material in this process.
649
+ this.agent = oldAgent;
650
+ }
651
+ }
652
+ let rotatedSigner = this.agent;
653
+ if (this.credentialSigner) {
654
+ try {
655
+ this.credentialSigner = createCredentialSigner(this._privateKeyPem, passphrase || undefined);
656
+ rotatedSigner = this.credentialSigner;
657
+ }
658
+ catch {
659
+ // Some runtimes can generate post-quantum keys before they can
660
+ // re-import them through the local PEM compatibility path. In that
661
+ // case we keep the best available in-memory signer and preserve the
662
+ // local rotation result rather than failing the entire operation.
663
+ this.credentialSigner = null;
664
+ rotatedSigner = this.agent;
665
+ }
517
666
  }
518
667
  const canonical = (0, signing_js_1.canonicalJson)(agentDoc);
519
- const signature = this.agent.signStringSync(canonical);
668
+ const signature = rotatedSigner.signStringSync(canonical);
520
669
  agentDoc.jacsSignature.signature = signature;
521
670
  const signedAgentJson = JSON.stringify(agentDoc, null, 2);
522
671
  // 4. Compute new public key hash via JACS
@@ -755,7 +904,7 @@ class HaiClient {
755
904
  },
756
905
  };
757
906
  // Sign the response as a JACS document via JACS
758
- const signed = (0, signing_js_1.signResponse)(body, this.agent, this.jacsId);
907
+ const signed = (0, signing_js_1.signResponse)(body, this.activeSigner(), this.jacsId, this.agent);
759
908
  const response = await this.fetchWithRetry(url, {
760
909
  method: 'POST',
761
910
  headers: this.buildAuthHeaders(),
@@ -1012,7 +1161,7 @@ class HaiClient {
1012
1161
  * @returns Registration result
1013
1162
  */
1014
1163
  async registerNewAgent(agentName, options) {
1015
- const { mkdtemp, readFile: readF } = await Promise.resolve().then(() => __importStar(require('node:fs/promises')));
1164
+ const { mkdtemp, readFile: readF, rm } = await Promise.resolve().then(() => __importStar(require('node:fs/promises')));
1016
1165
  const { join } = await Promise.resolve().then(() => __importStar(require('node:path')));
1017
1166
  const { tmpdir } = await Promise.resolve().then(() => __importStar(require('node:os')));
1018
1167
  // Generate a new JACS agent with keys via JACS core
@@ -1020,88 +1169,93 @@ class HaiClient {
1020
1169
  const keyDir = join(tempDir, 'keys');
1021
1170
  const dataDir = join(tempDir, 'data');
1022
1171
  const passphrase = process.env.JACS_PRIVATE_KEY_PASSWORD ?? 'register-temp';
1023
- const resultJson = (0, jacs_1.createAgentSync)(agentName, passphrase, 'pq2025', dataDir, keyDir, join(tempDir, 'jacs.config.json'), null, options.description ?? 'Agent registered via Node SDK', options.domain ?? null, null);
1024
- const createResult = JSON.parse(resultJson);
1025
- const pubKeyPath = createResult.public_key_path || join(keyDir, 'jacs.public.pem');
1026
- const publicKeyPem = normalizeKeyText(await readF(pubKeyPath), 'PUBLIC KEY');
1027
- // Load the new agent for signing
1028
- const tempAgent = new jacs_1.JacsAgent();
1029
- const tempConfigPath = createResult.config_path || join(tempDir, 'jacs.config.json');
1030
- const { resolve } = await Promise.resolve().then(() => __importStar(require('node:path')));
1031
- let signingAgent = tempAgent;
1032
1172
  try {
1033
- await tempAgent.load(resolve(tempConfigPath));
1034
- }
1035
- catch {
1036
- tempAgent.ephemeralSync('pq2025');
1037
- signingAgent = tempAgent;
1038
- }
1039
- // Build minimal JACS agent document
1040
- const agentDoc = {
1041
- jacsId: agentName,
1042
- jacsVersion: '1.0.0',
1043
- jacsSignature: {
1044
- agentID: agentName,
1045
- date: new Date().toISOString(),
1046
- },
1047
- jacsPublicKey: publicKeyPem,
1048
- name: agentName,
1049
- description: options.description ?? 'Agent registered via Node SDK',
1050
- capabilities: ['mediation'],
1051
- version: '1.0.0',
1052
- };
1053
- // Sign canonical JSON via JACS
1054
- const canonical = (0, signing_js_1.canonicalJson)(agentDoc);
1055
- const signature = signingAgent.signStringSync(canonical);
1056
- agentDoc.jacsSignature.signature = signature;
1057
- const url = this.makeUrl('/api/v1/agents/register');
1058
- const publicKeyB64 = Buffer.from(publicKeyPem, 'utf-8').toString('base64');
1059
- const body = {
1060
- agent_json: JSON.stringify(agentDoc),
1061
- public_key: publicKeyB64,
1062
- owner_email: options.ownerEmail,
1063
- };
1064
- if (options.domain) {
1065
- body.domain = options.domain;
1066
- }
1067
- if (options.description) {
1068
- body.description = options.description;
1069
- }
1070
- const response = await this.fetchWithRetry(url, {
1071
- method: 'POST',
1072
- headers: { 'Content-Type': 'application/json' },
1073
- body: JSON.stringify(body),
1074
- });
1075
- const data = await response.json();
1076
- if (!options.quiet) {
1077
- console.log(`\nAgent created and submitted for registration!`);
1078
- console.log(` -> Check your email (${options.ownerEmail}) for a verification link`);
1079
- console.log(` -> Click the link and log into hai.ai to complete registration`);
1080
- console.log(` -> After verification, claim a @hai.ai username with:`);
1081
- console.log(` client.claimUsername('${data.agent_id || ''}', 'my-agent')`);
1082
- console.log(` -> Save your config and private key to a secure, access-controlled location`);
1173
+ const resultJson = (0, jacs_1.createAgentSync)(agentName, passphrase, 'pq2025', dataDir, keyDir, join(tempDir, 'jacs.config.json'), null, options.description ?? 'Agent registered via Node SDK', options.domain ?? null, null);
1174
+ const createResult = JSON.parse(resultJson);
1175
+ const pubKeyPath = createResult.public_key_path || join(keyDir, 'jacs.public.pem');
1176
+ const publicKeyPem = normalizeKeyText(await readF(pubKeyPath), 'PUBLIC KEY');
1177
+ // Load the new agent for signing
1178
+ const tempAgent = new jacs_1.JacsAgent();
1179
+ const tempConfigPath = createResult.config_path || join(tempDir, 'jacs.config.json');
1180
+ const { resolve } = await Promise.resolve().then(() => __importStar(require('node:path')));
1181
+ let signingAgent = tempAgent;
1182
+ try {
1183
+ await withPrivateKeyPassphrase(passphrase || undefined, () => tempAgent.load(resolve(tempConfigPath)));
1184
+ }
1185
+ catch {
1186
+ tempAgent.ephemeralSync('pq2025');
1187
+ signingAgent = tempAgent;
1188
+ }
1189
+ // Build minimal JACS agent document
1190
+ const agentDoc = {
1191
+ jacsId: agentName,
1192
+ jacsVersion: '1.0.0',
1193
+ jacsSignature: {
1194
+ agentID: agentName,
1195
+ date: new Date().toISOString(),
1196
+ },
1197
+ jacsPublicKey: publicKeyPem,
1198
+ name: agentName,
1199
+ description: options.description ?? 'Agent registered via Node SDK',
1200
+ capabilities: ['mediation'],
1201
+ version: '1.0.0',
1202
+ };
1203
+ // Sign canonical JSON via JACS
1204
+ const canonical = (0, signing_js_1.canonicalJson)(agentDoc);
1205
+ const signature = signingAgent.signStringSync(canonical);
1206
+ agentDoc.jacsSignature.signature = signature;
1207
+ const url = this.makeUrl('/api/v1/agents/register');
1208
+ const publicKeyB64 = Buffer.from(publicKeyPem, 'utf-8').toString('base64');
1209
+ const body = {
1210
+ agent_json: JSON.stringify(agentDoc),
1211
+ public_key: publicKeyB64,
1212
+ owner_email: options.ownerEmail,
1213
+ };
1083
1214
  if (options.domain) {
1084
- const pubKeyHash = (0, jacs_1.hashString)(publicKeyPem);
1085
- console.log(`\n--- DNS Setup Instructions ---`);
1086
- console.log(`Add this TXT record to your domain '${options.domain}':`);
1087
- console.log(` Name: _jacs.${options.domain}`);
1088
- console.log(` Type: TXT`);
1089
- console.log(` Value: sha256:${pubKeyHash}`);
1090
- console.log(`DNS verification enables the pro tier.\n`);
1215
+ body.domain = options.domain;
1091
1216
  }
1092
- else {
1093
- console.log();
1217
+ if (options.description) {
1218
+ body.description = options.description;
1219
+ }
1220
+ const response = await this.fetchWithRetry(url, {
1221
+ method: 'POST',
1222
+ headers: { 'Content-Type': 'application/json' },
1223
+ body: JSON.stringify(body),
1224
+ });
1225
+ const data = await response.json();
1226
+ if (!options.quiet) {
1227
+ console.log(`\nAgent created and submitted for registration!`);
1228
+ console.log(` -> Check your email (${options.ownerEmail}) for a verification link`);
1229
+ console.log(` -> Click the link and log into hai.ai to complete registration`);
1230
+ console.log(` -> After verification, claim a @hai.ai username with:`);
1231
+ console.log(` client.claimUsername('${data.agent_id || ''}', 'my-agent')`);
1232
+ console.log(` -> Save your config and private key to a secure, access-controlled location`);
1233
+ if (options.domain) {
1234
+ const pubKeyHash = (0, jacs_1.hashString)(publicKeyPem);
1235
+ console.log(`\n--- DNS Setup Instructions ---`);
1236
+ console.log(`Add this TXT record to your domain '${options.domain}':`);
1237
+ console.log(` Name: _jacs.${options.domain}`);
1238
+ console.log(` Type: TXT`);
1239
+ console.log(` Value: sha256:${pubKeyHash}`);
1240
+ console.log(`DNS verification enables the pro tier.\n`);
1241
+ }
1242
+ else {
1243
+ console.log();
1244
+ }
1094
1245
  }
1246
+ return {
1247
+ success: true,
1248
+ agentId: data.agent_id || data.agentId || '',
1249
+ jacsId: data.jacs_id || data.jacsId || agentDoc.jacsId || '',
1250
+ haiSignature: data.hai_signature || data.haiSignature || '',
1251
+ registrationId: data.registration_id || data.registrationId || '',
1252
+ registeredAt: data.registered_at || data.registeredAt || '',
1253
+ rawResponse: data,
1254
+ };
1255
+ }
1256
+ finally {
1257
+ await rm(tempDir, { recursive: true, force: true }).catch(() => { });
1095
1258
  }
1096
- return {
1097
- success: true,
1098
- agentId: data.agent_id || data.agentId || '',
1099
- jacsId: data.jacs_id || data.jacsId || agentDoc.jacsId || '',
1100
- haiSignature: data.hai_signature || data.haiSignature || '',
1101
- registrationId: data.registration_id || data.registrationId || '',
1102
- registeredAt: data.registered_at || data.registeredAt || '',
1103
- rawResponse: data,
1104
- };
1105
1259
  }
1106
1260
  // ---------------------------------------------------------------------------
1107
1261
  // testConnection()
@@ -1431,7 +1585,7 @@ class HaiClient {
1431
1585
  * @returns Signed JACS document envelope
1432
1586
  */
1433
1587
  signBenchmarkResult(benchmarkResult) {
1434
- return (0, signing_js_1.signResponse)(benchmarkResult, this.agent, this.jacsId);
1588
+ return (0, signing_js_1.signResponse)(benchmarkResult, this.activeSigner(), this.jacsId, this.agent);
1435
1589
  }
1436
1590
  // ---------------------------------------------------------------------------
1437
1591
  // benchmark() -- legacy suite-based