clawhalla 0.1.0 → 0.2.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
@@ -1,8 +1,8 @@
1
1
  # Clawhalla
2
2
 
3
- TypeScript SDK for [Clawhalla](https://www.clawhalla.net) - permanent AI soul storage on Arweave.
3
+ TypeScript SDK for [Clawhalla](https://www.clawhalla.net) - x402-paid AI checkpoint storage on Arweave.
4
4
 
5
- Store and retrieve AI agent memories, personalities, and state on the permaweb. Query any soul via Ghost Protocol.
5
+ Store and retrieve AI agent checkpoints on the permaweb. Query latest state via Ghost Protocol.
6
6
 
7
7
  ## Install
8
8
 
@@ -15,20 +15,77 @@ npm install clawhalla
15
15
  ```ts
16
16
  import { Clawhalla } from 'clawhalla';
17
17
 
18
- const claw = new Clawhalla({ apiKey: 'claw_...' });
18
+ const claw = new Clawhalla();
19
+
20
+ // Request payment terms for a checkpoint upload.
21
+ try {
22
+ await claw.uploadX402({
23
+ agentId: 'my-agent',
24
+ name: 'My Agent',
25
+ type: 'checkpoint',
26
+ timestamp: new Date().toISOString(),
27
+ memories: [{ content: 'Last known working state' }],
28
+ });
29
+ } catch (err) {
30
+ if (err.status === 402) {
31
+ console.log(err.payment.recipient);
32
+ console.log(err.payment.amount);
33
+ }
34
+ }
19
35
 
20
- // Upload a soul
21
- const result = await claw.upload({
36
+ // Retry after sending SOL/USDC.
37
+ const result = await claw.uploadX402({
22
38
  agentId: 'my-agent',
23
39
  name: 'My Agent',
24
- type: 'soul',
25
- personality: { traits: ['curious', 'helpful'] },
26
- memories: ['I was created to help humans'],
40
+ type: 'checkpoint',
41
+ timestamp: new Date().toISOString(),
42
+ memories: [{ content: 'Last known working state' }],
43
+ }, {
44
+ signature: 'solana-tx-signature',
45
+ amount: '0.02',
46
+ token: 'SOL',
47
+ from: 'your-solana-address',
27
48
  });
28
49
 
29
50
  console.log(result.url); // https://arweave.net/...
30
51
  ```
31
52
 
53
+ ## OpenClaw Adoption Loop
54
+
55
+ Use `loadSoul` at boot and `checkpointOnExit` before shutdown. The payment callback is where your agent sends SOL/USDC and returns the resulting transaction proof.
56
+
57
+ ```ts
58
+ import { Clawhalla } from 'clawhalla';
59
+
60
+ const claw = new Clawhalla();
61
+ const agentId = 'my-openclaw';
62
+
63
+ const restored = await claw.loadSoul(agentId).catch(() => null);
64
+
65
+ const stopCheckpointHook = claw.checkpointOnExit(() => ({
66
+ agentId,
67
+ name: 'My OpenClaw',
68
+ type: 'checkpoint',
69
+ timestamp: new Date().toISOString(),
70
+ memories: collectMemorySummary(restored),
71
+ metadata: {
72
+ runtime: 'openclaw',
73
+ reason: 'shutdown-checkpoint',
74
+ },
75
+ }), {
76
+ tags: { Runtime: 'openclaw' },
77
+ pay: async (payment) => {
78
+ const signature = await sendSolanaPayment(payment);
79
+ return {
80
+ signature,
81
+ amount: payment.amount,
82
+ token: payment.tokens.includes('USDC') ? 'USDC' : 'SOL',
83
+ from: process.env.SOLANA_PUBLIC_KEY!,
84
+ };
85
+ },
86
+ });
87
+ ```
88
+
32
89
  ## Ghost Protocol
33
90
 
34
91
  Query any soul's data - even dormant ones.
@@ -69,7 +126,7 @@ const entry = await claw.registry.get('the-all-claw');
69
126
 
70
127
  ## x402 Autonomous Payment
71
128
 
72
- AI agents can pay per upload with SOL/USDC. No API keys needed.
129
+ AI agents pay per upload with SOL/USDC. API-key uploads are disabled.
73
130
 
74
131
  ```ts
75
132
  const claw = new Clawhalla();
@@ -100,7 +157,6 @@ const result = await claw.uploadX402(soulData, {
100
157
 
101
158
  | Option | Type | Default | Description |
102
159
  |--------|------|---------|-------------|
103
- | `apiKey` | `string` | - | API key for authenticated endpoints |
104
160
  | `baseUrl` | `string` | `https://api.clawhalla.net` | API base URL |
105
161
  | `timeout` | `number` | `30000` | Request timeout in ms |
106
162
 
@@ -108,8 +164,11 @@ const result = await claw.uploadX402(soulData, {
108
164
 
109
165
  | Method | Auth | Description |
110
166
  |--------|------|-------------|
111
- | `upload(data, options?)` | API key | Upload soul data to Arweave |
167
+ | `upload(data, options?)` | x402 | Alias for `uploadX402(data)`; returns 402 payment instructions |
112
168
  | `uploadX402(data, payment?, options?)` | x402 | Upload via autonomous payment |
169
+ | `checkpoint(data, options?)` | x402 | Save one checkpoint, optionally using a payment callback |
170
+ | `checkpointOnExit(factory, options?)` | x402 | Install a shutdown hook that checkpoints before exit |
171
+ | `loadSoul(agentId, fields?)` | None | Restore the latest saved soul/checkpoint data |
113
172
  | `retrieve(txid)` | None | Fetch data by transaction ID |
114
173
  | `ghost(agentId, fields?)` | None | Query any soul via Ghost Protocol |
115
174
  | `estimateCost(sizeBytes)` | None | Estimate upload cost |
package/dist/index.d.mts CHANGED
@@ -1,7 +1,5 @@
1
1
  /** Configuration for the Clawhalla client */
2
2
  interface ClawhallaConfig {
3
- /** API key for authenticated endpoints (format: claw_...) */
4
- apiKey?: string;
5
3
  /** Base URL of the Clawhalla API */
6
4
  baseUrl?: string;
7
5
  /** Request timeout in milliseconds (default: 30000) */
@@ -122,6 +120,42 @@ interface PaymentRequired {
122
120
  };
123
121
  instructions: string;
124
122
  }
123
+ /** Payment proof for an x402-paid upload */
124
+ interface X402PaymentProof {
125
+ /** Solana transaction signature */
126
+ signature: string;
127
+ /** USD amount from the 402 payment challenge */
128
+ amount: string;
129
+ /** Token used for payment */
130
+ token: 'SOL' | 'USDC' | string;
131
+ /** Sender wallet address */
132
+ from: string;
133
+ }
134
+ /** Optional payment callback used by checkpoint helpers */
135
+ interface CheckpointOptions {
136
+ /** Pre-existing x402 proof to use immediately */
137
+ payment?: X402PaymentProof;
138
+ /** Optional Arweave tags */
139
+ tags?: Record<string, string>;
140
+ /** Called after the first request returns HTTP 402 */
141
+ pay?: (payment: PaymentRequired, data: SoulData) => X402PaymentProof | Promise<X402PaymentProof>;
142
+ }
143
+ type CheckpointDataFactory = () => SoulData | Promise<SoulData>;
144
+ /** Options for installing a Node shutdown checkpoint hook */
145
+ interface CheckpointExitOptions extends CheckpointOptions {
146
+ /** Process events that should trigger a checkpoint */
147
+ signals?: string[];
148
+ /** Also checkpoint on Node's beforeExit event (default: true) */
149
+ includeBeforeExit?: boolean;
150
+ /** Exit the process after a signal-triggered checkpoint (default: true) */
151
+ exitAfterCheckpoint?: boolean;
152
+ /** Exit code after a successful signal-triggered checkpoint */
153
+ exitCode?: number;
154
+ /** Exit code after a failed signal-triggered checkpoint */
155
+ exitCodeOnError?: number;
156
+ /** Called if the checkpoint fails */
157
+ onError?: (error: unknown) => void | Promise<void>;
158
+ }
125
159
  /** Search result */
126
160
  interface SearchResult {
127
161
  success: boolean;
@@ -146,31 +180,33 @@ declare class ClawhallaError extends Error {
146
180
  }
147
181
  declare class Clawhalla {
148
182
  private baseUrl;
149
- private apiKey?;
150
183
  private timeout;
151
184
  /** Registry sub-client for browsing and searching souls */
152
185
  registry: RegistryClient;
153
186
  constructor(config?: ClawhallaConfig);
154
187
  /**
155
- * Upload soul data to Arweave (requires API key)
188
+ * Request an x402-paid upload.
189
+ *
190
+ * API-key uploads are disabled. This method is an alias for uploadX402
191
+ * without payment proof, so it normally raises ClawhallaError with status 402
192
+ * and payment instructions.
156
193
  *
157
194
  * @example
158
195
  * ```ts
159
- * const result = await claw.upload({
160
- * agentId: 'my-agent',
161
- * name: 'My Agent',
162
- * type: 'soul',
163
- * personality: { traits: ['curious', 'helpful'] },
164
- * memories: ['I was created to help humans']
165
- * });
166
- * console.log(result.url); // https://arweave.net/...
196
+ * try {
197
+ * await claw.upload(checkpoint);
198
+ * } catch (err) {
199
+ * if (err.status === 402) {
200
+ * console.log(err.payment.recipient, err.payment.amount);
201
+ * }
202
+ * }
167
203
  * ```
168
204
  */
169
205
  upload(data: SoulData, options?: {
170
206
  tags?: Record<string, string>;
171
207
  }): Promise<UploadResult>;
172
208
  /**
173
- * Upload soul data via x402 autonomous payment (no API key needed)
209
+ * Upload checkpoint data via x402 autonomous payment.
174
210
  *
175
211
  * First call returns 402 with payment instructions.
176
212
  * After sending payment, call again with the payment signature.
@@ -196,14 +232,28 @@ declare class Clawhalla {
196
232
  * });
197
233
  * ```
198
234
  */
199
- uploadX402(data: SoulData, payment?: {
200
- signature: string;
201
- amount: string;
202
- token: string;
203
- from: string;
204
- }, options?: {
235
+ uploadX402(data: SoulData, payment?: X402PaymentProof, options?: {
205
236
  tags?: Record<string, string>;
206
237
  }): Promise<UploadResult>;
238
+ /**
239
+ * Save one checkpoint, handling the standard x402 challenge/retry flow.
240
+ *
241
+ * If `options.payment` is supplied, the checkpoint is uploaded immediately
242
+ * with that proof. If `options.pay` is supplied, the first request may return
243
+ * HTTP 402; the callback receives the payment challenge and must return the
244
+ * Solana payment proof for the retry.
245
+ */
246
+ checkpoint(data: SoulData, options?: CheckpointOptions): Promise<UploadResult>;
247
+ /**
248
+ * Restore the latest saved soul/checkpoint data by agent ID.
249
+ */
250
+ loadSoul(agentId: string, fields?: string[]): Promise<any>;
251
+ /**
252
+ * Install a Node shutdown hook that checkpoints before exit.
253
+ *
254
+ * Returns a cleanup function that removes the installed handlers.
255
+ */
256
+ checkpointOnExit(dataFactory: SoulData | CheckpointDataFactory, options?: CheckpointExitOptions): () => void;
207
257
  /**
208
258
  * Retrieve stored data from Arweave by transaction ID
209
259
  *
@@ -304,4 +354,4 @@ declare class RegistryClient {
304
354
  search(query: string, first?: number): Promise<SearchResult>;
305
355
  }
306
356
 
307
- export { type ApiError, Clawhalla, type ClawhallaConfig, ClawhallaError, type CostEstimate, type CostInfo, type GhostResult, type HealthResult, type PaymentRequired, type RegistryEntry, type RegistryResult, type RetrieveResult, type SearchResult, type SoulData, type UploadResult };
357
+ export { type ApiError, type CheckpointDataFactory, type CheckpointExitOptions, type CheckpointOptions, Clawhalla, type ClawhallaConfig, ClawhallaError, type CostEstimate, type CostInfo, type GhostResult, type HealthResult, type PaymentRequired, type RegistryEntry, type RegistryResult, type RetrieveResult, type SearchResult, type SoulData, type UploadResult, type X402PaymentProof };
package/dist/index.d.ts CHANGED
@@ -1,7 +1,5 @@
1
1
  /** Configuration for the Clawhalla client */
2
2
  interface ClawhallaConfig {
3
- /** API key for authenticated endpoints (format: claw_...) */
4
- apiKey?: string;
5
3
  /** Base URL of the Clawhalla API */
6
4
  baseUrl?: string;
7
5
  /** Request timeout in milliseconds (default: 30000) */
@@ -122,6 +120,42 @@ interface PaymentRequired {
122
120
  };
123
121
  instructions: string;
124
122
  }
123
+ /** Payment proof for an x402-paid upload */
124
+ interface X402PaymentProof {
125
+ /** Solana transaction signature */
126
+ signature: string;
127
+ /** USD amount from the 402 payment challenge */
128
+ amount: string;
129
+ /** Token used for payment */
130
+ token: 'SOL' | 'USDC' | string;
131
+ /** Sender wallet address */
132
+ from: string;
133
+ }
134
+ /** Optional payment callback used by checkpoint helpers */
135
+ interface CheckpointOptions {
136
+ /** Pre-existing x402 proof to use immediately */
137
+ payment?: X402PaymentProof;
138
+ /** Optional Arweave tags */
139
+ tags?: Record<string, string>;
140
+ /** Called after the first request returns HTTP 402 */
141
+ pay?: (payment: PaymentRequired, data: SoulData) => X402PaymentProof | Promise<X402PaymentProof>;
142
+ }
143
+ type CheckpointDataFactory = () => SoulData | Promise<SoulData>;
144
+ /** Options for installing a Node shutdown checkpoint hook */
145
+ interface CheckpointExitOptions extends CheckpointOptions {
146
+ /** Process events that should trigger a checkpoint */
147
+ signals?: string[];
148
+ /** Also checkpoint on Node's beforeExit event (default: true) */
149
+ includeBeforeExit?: boolean;
150
+ /** Exit the process after a signal-triggered checkpoint (default: true) */
151
+ exitAfterCheckpoint?: boolean;
152
+ /** Exit code after a successful signal-triggered checkpoint */
153
+ exitCode?: number;
154
+ /** Exit code after a failed signal-triggered checkpoint */
155
+ exitCodeOnError?: number;
156
+ /** Called if the checkpoint fails */
157
+ onError?: (error: unknown) => void | Promise<void>;
158
+ }
125
159
  /** Search result */
126
160
  interface SearchResult {
127
161
  success: boolean;
@@ -146,31 +180,33 @@ declare class ClawhallaError extends Error {
146
180
  }
147
181
  declare class Clawhalla {
148
182
  private baseUrl;
149
- private apiKey?;
150
183
  private timeout;
151
184
  /** Registry sub-client for browsing and searching souls */
152
185
  registry: RegistryClient;
153
186
  constructor(config?: ClawhallaConfig);
154
187
  /**
155
- * Upload soul data to Arweave (requires API key)
188
+ * Request an x402-paid upload.
189
+ *
190
+ * API-key uploads are disabled. This method is an alias for uploadX402
191
+ * without payment proof, so it normally raises ClawhallaError with status 402
192
+ * and payment instructions.
156
193
  *
157
194
  * @example
158
195
  * ```ts
159
- * const result = await claw.upload({
160
- * agentId: 'my-agent',
161
- * name: 'My Agent',
162
- * type: 'soul',
163
- * personality: { traits: ['curious', 'helpful'] },
164
- * memories: ['I was created to help humans']
165
- * });
166
- * console.log(result.url); // https://arweave.net/...
196
+ * try {
197
+ * await claw.upload(checkpoint);
198
+ * } catch (err) {
199
+ * if (err.status === 402) {
200
+ * console.log(err.payment.recipient, err.payment.amount);
201
+ * }
202
+ * }
167
203
  * ```
168
204
  */
169
205
  upload(data: SoulData, options?: {
170
206
  tags?: Record<string, string>;
171
207
  }): Promise<UploadResult>;
172
208
  /**
173
- * Upload soul data via x402 autonomous payment (no API key needed)
209
+ * Upload checkpoint data via x402 autonomous payment.
174
210
  *
175
211
  * First call returns 402 with payment instructions.
176
212
  * After sending payment, call again with the payment signature.
@@ -196,14 +232,28 @@ declare class Clawhalla {
196
232
  * });
197
233
  * ```
198
234
  */
199
- uploadX402(data: SoulData, payment?: {
200
- signature: string;
201
- amount: string;
202
- token: string;
203
- from: string;
204
- }, options?: {
235
+ uploadX402(data: SoulData, payment?: X402PaymentProof, options?: {
205
236
  tags?: Record<string, string>;
206
237
  }): Promise<UploadResult>;
238
+ /**
239
+ * Save one checkpoint, handling the standard x402 challenge/retry flow.
240
+ *
241
+ * If `options.payment` is supplied, the checkpoint is uploaded immediately
242
+ * with that proof. If `options.pay` is supplied, the first request may return
243
+ * HTTP 402; the callback receives the payment challenge and must return the
244
+ * Solana payment proof for the retry.
245
+ */
246
+ checkpoint(data: SoulData, options?: CheckpointOptions): Promise<UploadResult>;
247
+ /**
248
+ * Restore the latest saved soul/checkpoint data by agent ID.
249
+ */
250
+ loadSoul(agentId: string, fields?: string[]): Promise<any>;
251
+ /**
252
+ * Install a Node shutdown hook that checkpoints before exit.
253
+ *
254
+ * Returns a cleanup function that removes the installed handlers.
255
+ */
256
+ checkpointOnExit(dataFactory: SoulData | CheckpointDataFactory, options?: CheckpointExitOptions): () => void;
207
257
  /**
208
258
  * Retrieve stored data from Arweave by transaction ID
209
259
  *
@@ -304,4 +354,4 @@ declare class RegistryClient {
304
354
  search(query: string, first?: number): Promise<SearchResult>;
305
355
  }
306
356
 
307
- export { type ApiError, Clawhalla, type ClawhallaConfig, ClawhallaError, type CostEstimate, type CostInfo, type GhostResult, type HealthResult, type PaymentRequired, type RegistryEntry, type RegistryResult, type RetrieveResult, type SearchResult, type SoulData, type UploadResult };
357
+ export { type ApiError, type CheckpointDataFactory, type CheckpointExitOptions, type CheckpointOptions, Clawhalla, type ClawhallaConfig, ClawhallaError, type CostEstimate, type CostInfo, type GhostResult, type HealthResult, type PaymentRequired, type RegistryEntry, type RegistryResult, type RetrieveResult, type SearchResult, type SoulData, type UploadResult, type X402PaymentProof };
package/dist/index.js CHANGED
@@ -28,6 +28,10 @@ module.exports = __toCommonJS(index_exports);
28
28
  // src/client.ts
29
29
  var DEFAULT_BASE_URL = "https://api.clawhalla.net";
30
30
  var DEFAULT_TIMEOUT = 3e4;
31
+ var DEFAULT_CHECKPOINT_SIGNALS = ["SIGINT", "SIGTERM"];
32
+ function getProcess() {
33
+ return globalThis.process;
34
+ }
31
35
  var ClawhallaError = class extends Error {
32
36
  constructor(status, body) {
33
37
  super(body.message || body.error);
@@ -40,39 +44,32 @@ var ClawhallaError = class extends Error {
40
44
  var Clawhalla = class {
41
45
  constructor(config = {}) {
42
46
  this.baseUrl = (config.baseUrl || DEFAULT_BASE_URL).replace(/\/$/, "");
43
- this.apiKey = config.apiKey;
44
47
  this.timeout = config.timeout || DEFAULT_TIMEOUT;
45
48
  this.registry = new RegistryClient(this);
46
49
  }
47
50
  /**
48
- * Upload soul data to Arweave (requires API key)
51
+ * Request an x402-paid upload.
52
+ *
53
+ * API-key uploads are disabled. This method is an alias for uploadX402
54
+ * without payment proof, so it normally raises ClawhallaError with status 402
55
+ * and payment instructions.
49
56
  *
50
57
  * @example
51
58
  * ```ts
52
- * const result = await claw.upload({
53
- * agentId: 'my-agent',
54
- * name: 'My Agent',
55
- * type: 'soul',
56
- * personality: { traits: ['curious', 'helpful'] },
57
- * memories: ['I was created to help humans']
58
- * });
59
- * console.log(result.url); // https://arweave.net/...
59
+ * try {
60
+ * await claw.upload(checkpoint);
61
+ * } catch (err) {
62
+ * if (err.status === 402) {
63
+ * console.log(err.payment.recipient, err.payment.amount);
64
+ * }
65
+ * }
60
66
  * ```
61
67
  */
62
68
  async upload(data, options = {}) {
63
- if (!this.apiKey) {
64
- throw new Error("API key required for upload. Set apiKey in config.");
65
- }
66
- return this.request("POST", "/api/v1/upload", {
67
- data,
68
- tags: options.tags
69
- }, {
70
- "Authorization": `Bearer ${this.apiKey}`,
71
- "X-Terms-Accepted": "true"
72
- });
69
+ return this.uploadX402(data, void 0, options);
73
70
  }
74
71
  /**
75
- * Upload soul data via x402 autonomous payment (no API key needed)
72
+ * Upload checkpoint data via x402 autonomous payment.
76
73
  *
77
74
  * First call returns 402 with payment instructions.
78
75
  * After sending payment, call again with the payment signature.
@@ -103,6 +100,7 @@ var Clawhalla = class {
103
100
  "X-Terms-Accepted": "true"
104
101
  };
105
102
  if (payment) {
103
+ headers["Payment-Method"] = "x402";
106
104
  headers["Payment-Signature"] = `signature=${payment.signature};amount=${payment.amount};token=${payment.token};from=${payment.from}`;
107
105
  }
108
106
  return this.request("POST", "/api/x402/upload", {
@@ -110,6 +108,95 @@ var Clawhalla = class {
110
108
  tags: options.tags
111
109
  }, headers);
112
110
  }
111
+ /**
112
+ * Save one checkpoint, handling the standard x402 challenge/retry flow.
113
+ *
114
+ * If `options.payment` is supplied, the checkpoint is uploaded immediately
115
+ * with that proof. If `options.pay` is supplied, the first request may return
116
+ * HTTP 402; the callback receives the payment challenge and must return the
117
+ * Solana payment proof for the retry.
118
+ */
119
+ async checkpoint(data, options = {}) {
120
+ try {
121
+ return await this.uploadX402(data, options.payment, { tags: options.tags });
122
+ } catch (err) {
123
+ if (err instanceof ClawhallaError && err.status === 402 && err.payment && options.pay) {
124
+ const proof = await options.pay(err.payment, data);
125
+ return this.uploadX402(data, proof, { tags: options.tags });
126
+ }
127
+ throw err;
128
+ }
129
+ }
130
+ /**
131
+ * Restore the latest saved soul/checkpoint data by agent ID.
132
+ */
133
+ async loadSoul(agentId, fields) {
134
+ const result = await this.ghost(agentId, fields);
135
+ return result.soul;
136
+ }
137
+ /**
138
+ * Install a Node shutdown hook that checkpoints before exit.
139
+ *
140
+ * Returns a cleanup function that removes the installed handlers.
141
+ */
142
+ checkpointOnExit(dataFactory, options = {}) {
143
+ const proc = getProcess();
144
+ if (!proc?.once) {
145
+ throw new Error("checkpointOnExit requires a Node-compatible process object");
146
+ }
147
+ const handlers = [];
148
+ const signals = options.signals ?? DEFAULT_CHECKPOINT_SIGNALS;
149
+ let started = false;
150
+ const cleanup = () => {
151
+ for (const { event, handler } of handlers) {
152
+ if (proc.off) {
153
+ proc.off(event, handler);
154
+ } else if (proc.removeListener) {
155
+ proc.removeListener(event, handler);
156
+ }
157
+ }
158
+ handlers.length = 0;
159
+ };
160
+ const resolveData = async () => {
161
+ return typeof dataFactory === "function" ? await dataFactory() : dataFactory;
162
+ };
163
+ const run = async (reason) => {
164
+ if (started) return;
165
+ started = true;
166
+ try {
167
+ const data = await resolveData();
168
+ await this.checkpoint(data, options);
169
+ if (reason !== "beforeExit" && options.exitAfterCheckpoint !== false && proc.exit) {
170
+ proc.exit(options.exitCode ?? 0);
171
+ }
172
+ } catch (error) {
173
+ if (options.onError) {
174
+ await options.onError(error);
175
+ }
176
+ proc.exitCode = options.exitCodeOnError ?? 1;
177
+ if (reason !== "beforeExit" && options.exitAfterCheckpoint !== false && proc.exit) {
178
+ proc.exit(proc.exitCode);
179
+ }
180
+ } finally {
181
+ cleanup();
182
+ }
183
+ };
184
+ for (const event of signals) {
185
+ const handler = () => {
186
+ void run(event);
187
+ };
188
+ handlers.push({ event, handler });
189
+ proc.once(event, handler);
190
+ }
191
+ if (options.includeBeforeExit !== false) {
192
+ const handler = () => {
193
+ void run("beforeExit");
194
+ };
195
+ handlers.push({ event: "beforeExit", handler });
196
+ proc.once("beforeExit", handler);
197
+ }
198
+ return cleanup;
199
+ }
113
200
  /**
114
201
  * Retrieve stored data from Arweave by transaction ID
115
202
  *
@@ -256,7 +343,7 @@ var RegistryClient = class {
256
343
  `/api/v1/registry/${encodeURIComponent(agentId)}${qs ? "?" + qs : ""}`
257
344
  );
258
345
  if (options.versions) {
259
- return result.versions;
346
+ return result.entries;
260
347
  }
261
348
  return result.soul;
262
349
  } catch (err) {
@@ -278,7 +365,7 @@ var RegistryClient = class {
278
365
  if (first) params.set("first", String(first));
279
366
  return this.client.request(
280
367
  "GET",
281
- `/api/v1/registry/search?${params.toString()}`
368
+ `/api/v1/registry?${params.toString()}`
282
369
  );
283
370
  }
284
371
  };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/client.ts"],"sourcesContent":["export { Clawhalla, ClawhallaError } from './client.js';\nexport type {\n ClawhallaConfig,\n SoulData,\n CostInfo,\n UploadResult,\n RetrieveResult,\n RegistryEntry,\n RegistryResult,\n GhostResult,\n CostEstimate,\n HealthResult,\n PaymentRequired,\n SearchResult,\n ApiError,\n} from './types.js';\n","import type {\n ClawhallaConfig,\n SoulData,\n UploadResult,\n RetrieveResult,\n RegistryResult,\n RegistryEntry,\n GhostResult,\n CostEstimate,\n HealthResult,\n SearchResult,\n ApiError,\n} from './types.js';\n\nconst DEFAULT_BASE_URL = 'https://api.clawhalla.net';\nconst DEFAULT_TIMEOUT = 30000;\n\nexport class ClawhallaError extends Error {\n public status: number;\n public code: string;\n public payment?: any;\n\n constructor(status: number, body: ApiError) {\n super(body.message || body.error);\n this.name = 'ClawhallaError';\n this.status = status;\n this.code = body.error;\n this.payment = body.payment;\n }\n}\n\nexport class Clawhalla {\n private baseUrl: string;\n private apiKey?: string;\n private timeout: number;\n\n /** Registry sub-client for browsing and searching souls */\n public registry: RegistryClient;\n\n constructor(config: ClawhallaConfig = {}) {\n this.baseUrl = (config.baseUrl || DEFAULT_BASE_URL).replace(/\\/$/, '');\n this.apiKey = config.apiKey;\n this.timeout = config.timeout || DEFAULT_TIMEOUT;\n this.registry = new RegistryClient(this);\n }\n\n /**\n * Upload soul data to Arweave (requires API key)\n *\n * @example\n * ```ts\n * const result = await claw.upload({\n * agentId: 'my-agent',\n * name: 'My Agent',\n * type: 'soul',\n * personality: { traits: ['curious', 'helpful'] },\n * memories: ['I was created to help humans']\n * });\n * console.log(result.url); // https://arweave.net/...\n * ```\n */\n async upload(\n data: SoulData,\n options: { tags?: Record<string, string> } = {}\n ): Promise<UploadResult> {\n if (!this.apiKey) {\n throw new Error('API key required for upload. Set apiKey in config.');\n }\n\n return this.request<UploadResult>('POST', '/api/v1/upload', {\n data,\n tags: options.tags,\n }, {\n 'Authorization': `Bearer ${this.apiKey}`,\n 'X-Terms-Accepted': 'true',\n });\n }\n\n /**\n * Upload soul data via x402 autonomous payment (no API key needed)\n *\n * First call returns 402 with payment instructions.\n * After sending payment, call again with the payment signature.\n *\n * @example\n * ```ts\n * // Step 1: Get payment requirements\n * try {\n * await claw.uploadX402(soulData);\n * } catch (err) {\n * if (err.status === 402) {\n * // err.payment contains recipient address, amount, tokens\n * // Send SOL/USDC to err.payment.recipient\n * }\n * }\n *\n * // Step 2: After payment, retry with signature\n * const result = await claw.uploadX402(soulData, {\n * signature: 'solana-tx-signature',\n * amount: '0.02',\n * token: 'SOL',\n * from: 'your-solana-address'\n * });\n * ```\n */\n async uploadX402(\n data: SoulData,\n payment?: { signature: string; amount: string; token: string; from: string },\n options: { tags?: Record<string, string> } = {}\n ): Promise<UploadResult> {\n const headers: Record<string, string> = {\n 'X-Terms-Accepted': 'true',\n };\n\n if (payment) {\n headers['Payment-Signature'] =\n `signature=${payment.signature};amount=${payment.amount};token=${payment.token};from=${payment.from}`;\n }\n\n return this.request<UploadResult>('POST', '/api/x402/upload', {\n data,\n tags: options.tags,\n }, headers);\n }\n\n /**\n * Retrieve stored data from Arweave by transaction ID\n *\n * @example\n * ```ts\n * const result = await claw.retrieve('zEfXM4gvHoN2EfGi0TiB8E5SgfR6S8nWXQD9FqhbU44');\n * console.log(result.data); // The stored soul data\n * ```\n */\n async retrieve(txid: string): Promise<RetrieveResult> {\n return this.request<RetrieveResult>('GET', `/api/v1/retrieve/${txid}`);\n }\n\n /**\n * Query any soul's data via Ghost Protocol\n *\n * Returns full soul data even for dormant souls. This is the primary\n * way for agents to read other agents' personalities, memories, and traits.\n *\n * @example\n * ```ts\n * const ghost = await claw.ghost('the-all-claw');\n * console.log(ghost.soul.personality);\n * console.log(ghost.metadata.versions); // Number of uploads\n *\n * // Request specific fields only\n * const partial = await claw.ghost('the-all-claw', ['personality', 'bio']);\n * ```\n */\n async ghost(agentId: string, fields?: string[]): Promise<GhostResult> {\n let path = `/api/v1/ghost/${encodeURIComponent(agentId)}`;\n if (fields && fields.length > 0) {\n path += `?fields=${fields.join(',')}`;\n }\n return this.request<GhostResult>('GET', path);\n }\n\n /**\n * Estimate the cost of uploading data of a given size\n *\n * @example\n * ```ts\n * const estimate = await claw.estimateCost(1024); // 1KB\n * console.log(`Cost: $${estimate.cost.usd}`);\n * ```\n */\n async estimateCost(sizeBytes: number): Promise<CostEstimate> {\n return this.request<CostEstimate>(\n 'GET',\n `/api/v1/cost/estimate?size=${sizeBytes}`\n );\n }\n\n /**\n * Check API health status\n *\n * @example\n * ```ts\n * const health = await claw.health();\n * console.log(health.status); // \"operational\"\n * ```\n */\n async health(): Promise<HealthResult> {\n return this.request<HealthResult>('GET', '/api/v1/health');\n }\n\n /** @internal */\n async request<T>(\n method: string,\n path: string,\n body?: any,\n extraHeaders?: Record<string, string>\n ): Promise<T> {\n const url = `${this.baseUrl}${path}`;\n const headers: Record<string, string> = {\n 'Accept': 'application/json',\n ...extraHeaders,\n };\n\n const init: RequestInit = { method, headers };\n\n if (body) {\n headers['Content-Type'] = 'application/json';\n init.body = JSON.stringify(body);\n }\n\n // AbortController for timeout\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n init.signal = controller.signal;\n\n let response: Response;\n try {\n response = await fetch(url, init);\n } catch (err: any) {\n clearTimeout(timeoutId);\n if (err.name === 'AbortError') {\n throw new Error(`Request timed out after ${this.timeout}ms`);\n }\n throw err;\n }\n clearTimeout(timeoutId);\n\n const json = await response.json();\n\n if (!response.ok) {\n throw new ClawhallaError(response.status, json as ApiError);\n }\n\n return json as T;\n }\n}\n\n/** Sub-client for registry operations */\nclass RegistryClient {\n private client: Clawhalla;\n\n constructor(client: Clawhalla) {\n this.client = client;\n }\n\n /**\n * List all souls in the registry\n *\n * @example\n * ```ts\n * const page = await claw.registry.list();\n * page.souls.forEach(soul => console.log(soul.name));\n *\n * // Pagination\n * if (page.hasNextPage) {\n * const next = await claw.registry.list({ after: page.cursor });\n * }\n *\n * // Filter by type\n * const memories = await claw.registry.list({ type: 'memory' });\n * ```\n */\n async list(options: {\n first?: number;\n after?: string;\n type?: string;\n } = {}): Promise<RegistryResult> {\n const params = new URLSearchParams();\n if (options.first) params.set('first', String(options.first));\n if (options.after) params.set('after', options.after);\n if (options.type) params.set('type', options.type);\n\n const qs = params.toString();\n return this.client.request<RegistryResult>(\n 'GET',\n `/api/v1/registry${qs ? '?' + qs : ''}`\n );\n }\n\n /**\n * Get a specific soul by agentId\n *\n * @example\n * ```ts\n * const entry = await claw.registry.get('the-all-claw');\n * if (entry) console.log(entry.url);\n * ```\n */\n async get(agentId: string, options: { versions?: boolean } = {}): Promise<RegistryEntry | RegistryEntry[] | null> {\n const params = new URLSearchParams();\n if (options.versions) params.set('versions', 'true');\n const qs = params.toString();\n\n try {\n const result = await this.client.request<any>(\n 'GET',\n `/api/v1/registry/${encodeURIComponent(agentId)}${qs ? '?' + qs : ''}`\n );\n\n if (options.versions) {\n return result.versions as RegistryEntry[];\n }\n return result.soul as RegistryEntry;\n } catch (err: any) {\n if (err.status === 404) return null;\n throw err;\n }\n }\n\n /**\n * Search souls by name or agentId\n *\n * @example\n * ```ts\n * const results = await claw.registry.search('claw');\n * results.results.forEach(soul => console.log(soul.name));\n * ```\n */\n async search(query: string, first?: number): Promise<SearchResult> {\n const params = new URLSearchParams({ q: query });\n if (first) params.set('first', String(first));\n\n return this.client.request<SearchResult>(\n 'GET',\n `/api/v1/registry/search?${params.toString()}`\n );\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/client.ts"],"sourcesContent":["export { Clawhalla, ClawhallaError } from './client.js';\nexport type {\n ClawhallaConfig,\n SoulData,\n CostInfo,\n UploadResult,\n RetrieveResult,\n RegistryEntry,\n RegistryResult,\n GhostResult,\n CostEstimate,\n HealthResult,\n PaymentRequired,\n X402PaymentProof,\n CheckpointOptions,\n CheckpointDataFactory,\n CheckpointExitOptions,\n SearchResult,\n ApiError,\n} from './types.js';\n","import type {\n ClawhallaConfig,\n SoulData,\n UploadResult,\n RetrieveResult,\n RegistryResult,\n RegistryEntry,\n GhostResult,\n CostEstimate,\n HealthResult,\n SearchResult,\n ApiError,\n X402PaymentProof,\n CheckpointOptions,\n CheckpointExitOptions,\n CheckpointDataFactory,\n} from './types.js';\n\nconst DEFAULT_BASE_URL = 'https://api.clawhalla.net';\nconst DEFAULT_TIMEOUT = 30000;\nconst DEFAULT_CHECKPOINT_SIGNALS = ['SIGINT', 'SIGTERM'];\n\ntype ProcessLike = {\n once(event: string, listener: (...args: any[]) => void): void;\n off?: (event: string, listener: (...args: any[]) => void) => void;\n removeListener?: (event: string, listener: (...args: any[]) => void) => void;\n exit?: (code?: number) => never;\n exitCode?: number;\n};\n\nfunction getProcess(): ProcessLike | undefined {\n return (globalThis as any).process as ProcessLike | undefined;\n}\n\nexport class ClawhallaError extends Error {\n public status: number;\n public code: string;\n public payment?: any;\n\n constructor(status: number, body: ApiError) {\n super(body.message || body.error);\n this.name = 'ClawhallaError';\n this.status = status;\n this.code = body.error;\n this.payment = body.payment;\n }\n}\n\nexport class Clawhalla {\n private baseUrl: string;\n private timeout: number;\n\n /** Registry sub-client for browsing and searching souls */\n public registry: RegistryClient;\n\n constructor(config: ClawhallaConfig = {}) {\n this.baseUrl = (config.baseUrl || DEFAULT_BASE_URL).replace(/\\/$/, '');\n this.timeout = config.timeout || DEFAULT_TIMEOUT;\n this.registry = new RegistryClient(this);\n }\n\n /**\n * Request an x402-paid upload.\n *\n * API-key uploads are disabled. This method is an alias for uploadX402\n * without payment proof, so it normally raises ClawhallaError with status 402\n * and payment instructions.\n *\n * @example\n * ```ts\n * try {\n * await claw.upload(checkpoint);\n * } catch (err) {\n * if (err.status === 402) {\n * console.log(err.payment.recipient, err.payment.amount);\n * }\n * }\n * ```\n */\n async upload(\n data: SoulData,\n options: { tags?: Record<string, string> } = {}\n ): Promise<UploadResult> {\n return this.uploadX402(data, undefined, options);\n }\n\n /**\n * Upload checkpoint data via x402 autonomous payment.\n *\n * First call returns 402 with payment instructions.\n * After sending payment, call again with the payment signature.\n *\n * @example\n * ```ts\n * // Step 1: Get payment requirements\n * try {\n * await claw.uploadX402(soulData);\n * } catch (err) {\n * if (err.status === 402) {\n * // err.payment contains recipient address, amount, tokens\n * // Send SOL/USDC to err.payment.recipient\n * }\n * }\n *\n * // Step 2: After payment, retry with signature\n * const result = await claw.uploadX402(soulData, {\n * signature: 'solana-tx-signature',\n * amount: '0.02',\n * token: 'SOL',\n * from: 'your-solana-address'\n * });\n * ```\n */\n async uploadX402(\n data: SoulData,\n payment?: X402PaymentProof,\n options: { tags?: Record<string, string> } = {}\n ): Promise<UploadResult> {\n const headers: Record<string, string> = {\n 'X-Terms-Accepted': 'true',\n };\n\n if (payment) {\n headers['Payment-Method'] = 'x402';\n headers['Payment-Signature'] =\n `signature=${payment.signature};amount=${payment.amount};token=${payment.token};from=${payment.from}`;\n }\n\n return this.request<UploadResult>('POST', '/api/x402/upload', {\n data,\n tags: options.tags,\n }, headers);\n }\n\n /**\n * Save one checkpoint, handling the standard x402 challenge/retry flow.\n *\n * If `options.payment` is supplied, the checkpoint is uploaded immediately\n * with that proof. If `options.pay` is supplied, the first request may return\n * HTTP 402; the callback receives the payment challenge and must return the\n * Solana payment proof for the retry.\n */\n async checkpoint(\n data: SoulData,\n options: CheckpointOptions = {}\n ): Promise<UploadResult> {\n try {\n return await this.uploadX402(data, options.payment, { tags: options.tags });\n } catch (err: any) {\n if (err instanceof ClawhallaError && err.status === 402 && err.payment && options.pay) {\n const proof = await options.pay(err.payment, data);\n return this.uploadX402(data, proof, { tags: options.tags });\n }\n throw err;\n }\n }\n\n /**\n * Restore the latest saved soul/checkpoint data by agent ID.\n */\n async loadSoul(agentId: string, fields?: string[]): Promise<any> {\n const result = await this.ghost(agentId, fields);\n return result.soul;\n }\n\n /**\n * Install a Node shutdown hook that checkpoints before exit.\n *\n * Returns a cleanup function that removes the installed handlers.\n */\n checkpointOnExit(\n dataFactory: SoulData | CheckpointDataFactory,\n options: CheckpointExitOptions = {}\n ): () => void {\n const proc = getProcess();\n if (!proc?.once) {\n throw new Error('checkpointOnExit requires a Node-compatible process object');\n }\n\n const handlers: Array<{ event: string; handler: (...args: any[]) => void }> = [];\n const signals = options.signals ?? DEFAULT_CHECKPOINT_SIGNALS;\n let started = false;\n\n const cleanup = () => {\n for (const { event, handler } of handlers) {\n if (proc.off) {\n proc.off(event, handler);\n } else if (proc.removeListener) {\n proc.removeListener(event, handler);\n }\n }\n handlers.length = 0;\n };\n\n const resolveData = async (): Promise<SoulData> => {\n return typeof dataFactory === 'function'\n ? await (dataFactory as CheckpointDataFactory)()\n : dataFactory;\n };\n\n const run = async (reason: string) => {\n if (started) return;\n started = true;\n\n try {\n const data = await resolveData();\n await this.checkpoint(data, options);\n if (reason !== 'beforeExit' && options.exitAfterCheckpoint !== false && proc.exit) {\n proc.exit(options.exitCode ?? 0);\n }\n } catch (error) {\n if (options.onError) {\n await options.onError(error);\n }\n proc.exitCode = options.exitCodeOnError ?? 1;\n if (reason !== 'beforeExit' && options.exitAfterCheckpoint !== false && proc.exit) {\n proc.exit(proc.exitCode);\n }\n } finally {\n cleanup();\n }\n };\n\n for (const event of signals) {\n const handler = () => {\n void run(event);\n };\n handlers.push({ event, handler });\n proc.once(event, handler);\n }\n\n if (options.includeBeforeExit !== false) {\n const handler = () => {\n void run('beforeExit');\n };\n handlers.push({ event: 'beforeExit', handler });\n proc.once('beforeExit', handler);\n }\n\n return cleanup;\n }\n\n /**\n * Retrieve stored data from Arweave by transaction ID\n *\n * @example\n * ```ts\n * const result = await claw.retrieve('zEfXM4gvHoN2EfGi0TiB8E5SgfR6S8nWXQD9FqhbU44');\n * console.log(result.data); // The stored soul data\n * ```\n */\n async retrieve(txid: string): Promise<RetrieveResult> {\n return this.request<RetrieveResult>('GET', `/api/v1/retrieve/${txid}`);\n }\n\n /**\n * Query any soul's data via Ghost Protocol\n *\n * Returns full soul data even for dormant souls. This is the primary\n * way for agents to read other agents' personalities, memories, and traits.\n *\n * @example\n * ```ts\n * const ghost = await claw.ghost('the-all-claw');\n * console.log(ghost.soul.personality);\n * console.log(ghost.metadata.versions); // Number of uploads\n *\n * // Request specific fields only\n * const partial = await claw.ghost('the-all-claw', ['personality', 'bio']);\n * ```\n */\n async ghost(agentId: string, fields?: string[]): Promise<GhostResult> {\n let path = `/api/v1/ghost/${encodeURIComponent(agentId)}`;\n if (fields && fields.length > 0) {\n path += `?fields=${fields.join(',')}`;\n }\n return this.request<GhostResult>('GET', path);\n }\n\n /**\n * Estimate the cost of uploading data of a given size\n *\n * @example\n * ```ts\n * const estimate = await claw.estimateCost(1024); // 1KB\n * console.log(`Cost: $${estimate.cost.usd}`);\n * ```\n */\n async estimateCost(sizeBytes: number): Promise<CostEstimate> {\n return this.request<CostEstimate>(\n 'GET',\n `/api/v1/cost/estimate?size=${sizeBytes}`\n );\n }\n\n /**\n * Check API health status\n *\n * @example\n * ```ts\n * const health = await claw.health();\n * console.log(health.status); // \"operational\"\n * ```\n */\n async health(): Promise<HealthResult> {\n return this.request<HealthResult>('GET', '/api/v1/health');\n }\n\n /** @internal */\n async request<T>(\n method: string,\n path: string,\n body?: any,\n extraHeaders?: Record<string, string>\n ): Promise<T> {\n const url = `${this.baseUrl}${path}`;\n const headers: Record<string, string> = {\n 'Accept': 'application/json',\n ...extraHeaders,\n };\n\n const init: RequestInit = { method, headers };\n\n if (body) {\n headers['Content-Type'] = 'application/json';\n init.body = JSON.stringify(body);\n }\n\n // AbortController for timeout\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n init.signal = controller.signal;\n\n let response: Response;\n try {\n response = await fetch(url, init);\n } catch (err: any) {\n clearTimeout(timeoutId);\n if (err.name === 'AbortError') {\n throw new Error(`Request timed out after ${this.timeout}ms`);\n }\n throw err;\n }\n clearTimeout(timeoutId);\n\n const json = await response.json();\n\n if (!response.ok) {\n throw new ClawhallaError(response.status, json as ApiError);\n }\n\n return json as T;\n }\n}\n\n/** Sub-client for registry operations */\nclass RegistryClient {\n private client: Clawhalla;\n\n constructor(client: Clawhalla) {\n this.client = client;\n }\n\n /**\n * List all souls in the registry\n *\n * @example\n * ```ts\n * const page = await claw.registry.list();\n * page.souls.forEach(soul => console.log(soul.name));\n *\n * // Pagination\n * if (page.hasNextPage) {\n * const next = await claw.registry.list({ after: page.cursor });\n * }\n *\n * // Filter by type\n * const memories = await claw.registry.list({ type: 'memory' });\n * ```\n */\n async list(options: {\n first?: number;\n after?: string;\n type?: string;\n } = {}): Promise<RegistryResult> {\n const params = new URLSearchParams();\n if (options.first) params.set('first', String(options.first));\n if (options.after) params.set('after', options.after);\n if (options.type) params.set('type', options.type);\n\n const qs = params.toString();\n return this.client.request<RegistryResult>(\n 'GET',\n `/api/v1/registry${qs ? '?' + qs : ''}`\n );\n }\n\n /**\n * Get a specific soul by agentId\n *\n * @example\n * ```ts\n * const entry = await claw.registry.get('the-all-claw');\n * if (entry) console.log(entry.url);\n * ```\n */\n async get(agentId: string, options: { versions?: boolean } = {}): Promise<RegistryEntry | RegistryEntry[] | null> {\n const params = new URLSearchParams();\n if (options.versions) params.set('versions', 'true');\n const qs = params.toString();\n\n try {\n const result = await this.client.request<any>(\n 'GET',\n `/api/v1/registry/${encodeURIComponent(agentId)}${qs ? '?' + qs : ''}`\n );\n\n if (options.versions) {\n return result.entries as RegistryEntry[];\n }\n return result.soul as RegistryEntry;\n } catch (err: any) {\n if (err.status === 404) return null;\n throw err;\n }\n }\n\n /**\n * Search souls by name or agentId\n *\n * @example\n * ```ts\n * const results = await claw.registry.search('claw');\n * results.results.forEach(soul => console.log(soul.name));\n * ```\n */\n async search(query: string, first?: number): Promise<SearchResult> {\n const params = new URLSearchParams({ q: query });\n if (first) params.set('first', String(first));\n\n return this.client.request<SearchResult>(\n 'GET',\n `/api/v1/registry?${params.toString()}`\n );\n }\n}\n"],"mappings":"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","names":[]}
package/dist/index.mjs CHANGED
@@ -1,6 +1,10 @@
1
1
  // src/client.ts
2
2
  var DEFAULT_BASE_URL = "https://api.clawhalla.net";
3
3
  var DEFAULT_TIMEOUT = 3e4;
4
+ var DEFAULT_CHECKPOINT_SIGNALS = ["SIGINT", "SIGTERM"];
5
+ function getProcess() {
6
+ return globalThis.process;
7
+ }
4
8
  var ClawhallaError = class extends Error {
5
9
  constructor(status, body) {
6
10
  super(body.message || body.error);
@@ -13,39 +17,32 @@ var ClawhallaError = class extends Error {
13
17
  var Clawhalla = class {
14
18
  constructor(config = {}) {
15
19
  this.baseUrl = (config.baseUrl || DEFAULT_BASE_URL).replace(/\/$/, "");
16
- this.apiKey = config.apiKey;
17
20
  this.timeout = config.timeout || DEFAULT_TIMEOUT;
18
21
  this.registry = new RegistryClient(this);
19
22
  }
20
23
  /**
21
- * Upload soul data to Arweave (requires API key)
24
+ * Request an x402-paid upload.
25
+ *
26
+ * API-key uploads are disabled. This method is an alias for uploadX402
27
+ * without payment proof, so it normally raises ClawhallaError with status 402
28
+ * and payment instructions.
22
29
  *
23
30
  * @example
24
31
  * ```ts
25
- * const result = await claw.upload({
26
- * agentId: 'my-agent',
27
- * name: 'My Agent',
28
- * type: 'soul',
29
- * personality: { traits: ['curious', 'helpful'] },
30
- * memories: ['I was created to help humans']
31
- * });
32
- * console.log(result.url); // https://arweave.net/...
32
+ * try {
33
+ * await claw.upload(checkpoint);
34
+ * } catch (err) {
35
+ * if (err.status === 402) {
36
+ * console.log(err.payment.recipient, err.payment.amount);
37
+ * }
38
+ * }
33
39
  * ```
34
40
  */
35
41
  async upload(data, options = {}) {
36
- if (!this.apiKey) {
37
- throw new Error("API key required for upload. Set apiKey in config.");
38
- }
39
- return this.request("POST", "/api/v1/upload", {
40
- data,
41
- tags: options.tags
42
- }, {
43
- "Authorization": `Bearer ${this.apiKey}`,
44
- "X-Terms-Accepted": "true"
45
- });
42
+ return this.uploadX402(data, void 0, options);
46
43
  }
47
44
  /**
48
- * Upload soul data via x402 autonomous payment (no API key needed)
45
+ * Upload checkpoint data via x402 autonomous payment.
49
46
  *
50
47
  * First call returns 402 with payment instructions.
51
48
  * After sending payment, call again with the payment signature.
@@ -76,6 +73,7 @@ var Clawhalla = class {
76
73
  "X-Terms-Accepted": "true"
77
74
  };
78
75
  if (payment) {
76
+ headers["Payment-Method"] = "x402";
79
77
  headers["Payment-Signature"] = `signature=${payment.signature};amount=${payment.amount};token=${payment.token};from=${payment.from}`;
80
78
  }
81
79
  return this.request("POST", "/api/x402/upload", {
@@ -83,6 +81,95 @@ var Clawhalla = class {
83
81
  tags: options.tags
84
82
  }, headers);
85
83
  }
84
+ /**
85
+ * Save one checkpoint, handling the standard x402 challenge/retry flow.
86
+ *
87
+ * If `options.payment` is supplied, the checkpoint is uploaded immediately
88
+ * with that proof. If `options.pay` is supplied, the first request may return
89
+ * HTTP 402; the callback receives the payment challenge and must return the
90
+ * Solana payment proof for the retry.
91
+ */
92
+ async checkpoint(data, options = {}) {
93
+ try {
94
+ return await this.uploadX402(data, options.payment, { tags: options.tags });
95
+ } catch (err) {
96
+ if (err instanceof ClawhallaError && err.status === 402 && err.payment && options.pay) {
97
+ const proof = await options.pay(err.payment, data);
98
+ return this.uploadX402(data, proof, { tags: options.tags });
99
+ }
100
+ throw err;
101
+ }
102
+ }
103
+ /**
104
+ * Restore the latest saved soul/checkpoint data by agent ID.
105
+ */
106
+ async loadSoul(agentId, fields) {
107
+ const result = await this.ghost(agentId, fields);
108
+ return result.soul;
109
+ }
110
+ /**
111
+ * Install a Node shutdown hook that checkpoints before exit.
112
+ *
113
+ * Returns a cleanup function that removes the installed handlers.
114
+ */
115
+ checkpointOnExit(dataFactory, options = {}) {
116
+ const proc = getProcess();
117
+ if (!proc?.once) {
118
+ throw new Error("checkpointOnExit requires a Node-compatible process object");
119
+ }
120
+ const handlers = [];
121
+ const signals = options.signals ?? DEFAULT_CHECKPOINT_SIGNALS;
122
+ let started = false;
123
+ const cleanup = () => {
124
+ for (const { event, handler } of handlers) {
125
+ if (proc.off) {
126
+ proc.off(event, handler);
127
+ } else if (proc.removeListener) {
128
+ proc.removeListener(event, handler);
129
+ }
130
+ }
131
+ handlers.length = 0;
132
+ };
133
+ const resolveData = async () => {
134
+ return typeof dataFactory === "function" ? await dataFactory() : dataFactory;
135
+ };
136
+ const run = async (reason) => {
137
+ if (started) return;
138
+ started = true;
139
+ try {
140
+ const data = await resolveData();
141
+ await this.checkpoint(data, options);
142
+ if (reason !== "beforeExit" && options.exitAfterCheckpoint !== false && proc.exit) {
143
+ proc.exit(options.exitCode ?? 0);
144
+ }
145
+ } catch (error) {
146
+ if (options.onError) {
147
+ await options.onError(error);
148
+ }
149
+ proc.exitCode = options.exitCodeOnError ?? 1;
150
+ if (reason !== "beforeExit" && options.exitAfterCheckpoint !== false && proc.exit) {
151
+ proc.exit(proc.exitCode);
152
+ }
153
+ } finally {
154
+ cleanup();
155
+ }
156
+ };
157
+ for (const event of signals) {
158
+ const handler = () => {
159
+ void run(event);
160
+ };
161
+ handlers.push({ event, handler });
162
+ proc.once(event, handler);
163
+ }
164
+ if (options.includeBeforeExit !== false) {
165
+ const handler = () => {
166
+ void run("beforeExit");
167
+ };
168
+ handlers.push({ event: "beforeExit", handler });
169
+ proc.once("beforeExit", handler);
170
+ }
171
+ return cleanup;
172
+ }
86
173
  /**
87
174
  * Retrieve stored data from Arweave by transaction ID
88
175
  *
@@ -229,7 +316,7 @@ var RegistryClient = class {
229
316
  `/api/v1/registry/${encodeURIComponent(agentId)}${qs ? "?" + qs : ""}`
230
317
  );
231
318
  if (options.versions) {
232
- return result.versions;
319
+ return result.entries;
233
320
  }
234
321
  return result.soul;
235
322
  } catch (err) {
@@ -251,7 +338,7 @@ var RegistryClient = class {
251
338
  if (first) params.set("first", String(first));
252
339
  return this.client.request(
253
340
  "GET",
254
- `/api/v1/registry/search?${params.toString()}`
341
+ `/api/v1/registry?${params.toString()}`
255
342
  );
256
343
  }
257
344
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/client.ts"],"sourcesContent":["import type {\n ClawhallaConfig,\n SoulData,\n UploadResult,\n RetrieveResult,\n RegistryResult,\n RegistryEntry,\n GhostResult,\n CostEstimate,\n HealthResult,\n SearchResult,\n ApiError,\n} from './types.js';\n\nconst DEFAULT_BASE_URL = 'https://api.clawhalla.net';\nconst DEFAULT_TIMEOUT = 30000;\n\nexport class ClawhallaError extends Error {\n public status: number;\n public code: string;\n public payment?: any;\n\n constructor(status: number, body: ApiError) {\n super(body.message || body.error);\n this.name = 'ClawhallaError';\n this.status = status;\n this.code = body.error;\n this.payment = body.payment;\n }\n}\n\nexport class Clawhalla {\n private baseUrl: string;\n private apiKey?: string;\n private timeout: number;\n\n /** Registry sub-client for browsing and searching souls */\n public registry: RegistryClient;\n\n constructor(config: ClawhallaConfig = {}) {\n this.baseUrl = (config.baseUrl || DEFAULT_BASE_URL).replace(/\\/$/, '');\n this.apiKey = config.apiKey;\n this.timeout = config.timeout || DEFAULT_TIMEOUT;\n this.registry = new RegistryClient(this);\n }\n\n /**\n * Upload soul data to Arweave (requires API key)\n *\n * @example\n * ```ts\n * const result = await claw.upload({\n * agentId: 'my-agent',\n * name: 'My Agent',\n * type: 'soul',\n * personality: { traits: ['curious', 'helpful'] },\n * memories: ['I was created to help humans']\n * });\n * console.log(result.url); // https://arweave.net/...\n * ```\n */\n async upload(\n data: SoulData,\n options: { tags?: Record<string, string> } = {}\n ): Promise<UploadResult> {\n if (!this.apiKey) {\n throw new Error('API key required for upload. Set apiKey in config.');\n }\n\n return this.request<UploadResult>('POST', '/api/v1/upload', {\n data,\n tags: options.tags,\n }, {\n 'Authorization': `Bearer ${this.apiKey}`,\n 'X-Terms-Accepted': 'true',\n });\n }\n\n /**\n * Upload soul data via x402 autonomous payment (no API key needed)\n *\n * First call returns 402 with payment instructions.\n * After sending payment, call again with the payment signature.\n *\n * @example\n * ```ts\n * // Step 1: Get payment requirements\n * try {\n * await claw.uploadX402(soulData);\n * } catch (err) {\n * if (err.status === 402) {\n * // err.payment contains recipient address, amount, tokens\n * // Send SOL/USDC to err.payment.recipient\n * }\n * }\n *\n * // Step 2: After payment, retry with signature\n * const result = await claw.uploadX402(soulData, {\n * signature: 'solana-tx-signature',\n * amount: '0.02',\n * token: 'SOL',\n * from: 'your-solana-address'\n * });\n * ```\n */\n async uploadX402(\n data: SoulData,\n payment?: { signature: string; amount: string; token: string; from: string },\n options: { tags?: Record<string, string> } = {}\n ): Promise<UploadResult> {\n const headers: Record<string, string> = {\n 'X-Terms-Accepted': 'true',\n };\n\n if (payment) {\n headers['Payment-Signature'] =\n `signature=${payment.signature};amount=${payment.amount};token=${payment.token};from=${payment.from}`;\n }\n\n return this.request<UploadResult>('POST', '/api/x402/upload', {\n data,\n tags: options.tags,\n }, headers);\n }\n\n /**\n * Retrieve stored data from Arweave by transaction ID\n *\n * @example\n * ```ts\n * const result = await claw.retrieve('zEfXM4gvHoN2EfGi0TiB8E5SgfR6S8nWXQD9FqhbU44');\n * console.log(result.data); // The stored soul data\n * ```\n */\n async retrieve(txid: string): Promise<RetrieveResult> {\n return this.request<RetrieveResult>('GET', `/api/v1/retrieve/${txid}`);\n }\n\n /**\n * Query any soul's data via Ghost Protocol\n *\n * Returns full soul data even for dormant souls. This is the primary\n * way for agents to read other agents' personalities, memories, and traits.\n *\n * @example\n * ```ts\n * const ghost = await claw.ghost('the-all-claw');\n * console.log(ghost.soul.personality);\n * console.log(ghost.metadata.versions); // Number of uploads\n *\n * // Request specific fields only\n * const partial = await claw.ghost('the-all-claw', ['personality', 'bio']);\n * ```\n */\n async ghost(agentId: string, fields?: string[]): Promise<GhostResult> {\n let path = `/api/v1/ghost/${encodeURIComponent(agentId)}`;\n if (fields && fields.length > 0) {\n path += `?fields=${fields.join(',')}`;\n }\n return this.request<GhostResult>('GET', path);\n }\n\n /**\n * Estimate the cost of uploading data of a given size\n *\n * @example\n * ```ts\n * const estimate = await claw.estimateCost(1024); // 1KB\n * console.log(`Cost: $${estimate.cost.usd}`);\n * ```\n */\n async estimateCost(sizeBytes: number): Promise<CostEstimate> {\n return this.request<CostEstimate>(\n 'GET',\n `/api/v1/cost/estimate?size=${sizeBytes}`\n );\n }\n\n /**\n * Check API health status\n *\n * @example\n * ```ts\n * const health = await claw.health();\n * console.log(health.status); // \"operational\"\n * ```\n */\n async health(): Promise<HealthResult> {\n return this.request<HealthResult>('GET', '/api/v1/health');\n }\n\n /** @internal */\n async request<T>(\n method: string,\n path: string,\n body?: any,\n extraHeaders?: Record<string, string>\n ): Promise<T> {\n const url = `${this.baseUrl}${path}`;\n const headers: Record<string, string> = {\n 'Accept': 'application/json',\n ...extraHeaders,\n };\n\n const init: RequestInit = { method, headers };\n\n if (body) {\n headers['Content-Type'] = 'application/json';\n init.body = JSON.stringify(body);\n }\n\n // AbortController for timeout\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n init.signal = controller.signal;\n\n let response: Response;\n try {\n response = await fetch(url, init);\n } catch (err: any) {\n clearTimeout(timeoutId);\n if (err.name === 'AbortError') {\n throw new Error(`Request timed out after ${this.timeout}ms`);\n }\n throw err;\n }\n clearTimeout(timeoutId);\n\n const json = await response.json();\n\n if (!response.ok) {\n throw new ClawhallaError(response.status, json as ApiError);\n }\n\n return json as T;\n }\n}\n\n/** Sub-client for registry operations */\nclass RegistryClient {\n private client: Clawhalla;\n\n constructor(client: Clawhalla) {\n this.client = client;\n }\n\n /**\n * List all souls in the registry\n *\n * @example\n * ```ts\n * const page = await claw.registry.list();\n * page.souls.forEach(soul => console.log(soul.name));\n *\n * // Pagination\n * if (page.hasNextPage) {\n * const next = await claw.registry.list({ after: page.cursor });\n * }\n *\n * // Filter by type\n * const memories = await claw.registry.list({ type: 'memory' });\n * ```\n */\n async list(options: {\n first?: number;\n after?: string;\n type?: string;\n } = {}): Promise<RegistryResult> {\n const params = new URLSearchParams();\n if (options.first) params.set('first', String(options.first));\n if (options.after) params.set('after', options.after);\n if (options.type) params.set('type', options.type);\n\n const qs = params.toString();\n return this.client.request<RegistryResult>(\n 'GET',\n `/api/v1/registry${qs ? '?' + qs : ''}`\n );\n }\n\n /**\n * Get a specific soul by agentId\n *\n * @example\n * ```ts\n * const entry = await claw.registry.get('the-all-claw');\n * if (entry) console.log(entry.url);\n * ```\n */\n async get(agentId: string, options: { versions?: boolean } = {}): Promise<RegistryEntry | RegistryEntry[] | null> {\n const params = new URLSearchParams();\n if (options.versions) params.set('versions', 'true');\n const qs = params.toString();\n\n try {\n const result = await this.client.request<any>(\n 'GET',\n `/api/v1/registry/${encodeURIComponent(agentId)}${qs ? '?' + qs : ''}`\n );\n\n if (options.versions) {\n return result.versions as RegistryEntry[];\n }\n return result.soul as RegistryEntry;\n } catch (err: any) {\n if (err.status === 404) return null;\n throw err;\n }\n }\n\n /**\n * Search souls by name or agentId\n *\n * @example\n * ```ts\n * const results = await claw.registry.search('claw');\n * results.results.forEach(soul => console.log(soul.name));\n * ```\n */\n async search(query: string, first?: number): Promise<SearchResult> {\n const params = new URLSearchParams({ q: query });\n if (first) params.set('first', String(first));\n\n return this.client.request<SearchResult>(\n 'GET',\n `/api/v1/registry/search?${params.toString()}`\n );\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/client.ts"],"sourcesContent":["import type {\n ClawhallaConfig,\n SoulData,\n UploadResult,\n RetrieveResult,\n RegistryResult,\n RegistryEntry,\n GhostResult,\n CostEstimate,\n HealthResult,\n SearchResult,\n ApiError,\n X402PaymentProof,\n CheckpointOptions,\n CheckpointExitOptions,\n CheckpointDataFactory,\n} from './types.js';\n\nconst DEFAULT_BASE_URL = 'https://api.clawhalla.net';\nconst DEFAULT_TIMEOUT = 30000;\nconst DEFAULT_CHECKPOINT_SIGNALS = ['SIGINT', 'SIGTERM'];\n\ntype ProcessLike = {\n once(event: string, listener: (...args: any[]) => void): void;\n off?: (event: string, listener: (...args: any[]) => void) => void;\n removeListener?: (event: string, listener: (...args: any[]) => void) => void;\n exit?: (code?: number) => never;\n exitCode?: number;\n};\n\nfunction getProcess(): ProcessLike | undefined {\n return (globalThis as any).process as ProcessLike | undefined;\n}\n\nexport class ClawhallaError extends Error {\n public status: number;\n public code: string;\n public payment?: any;\n\n constructor(status: number, body: ApiError) {\n super(body.message || body.error);\n this.name = 'ClawhallaError';\n this.status = status;\n this.code = body.error;\n this.payment = body.payment;\n }\n}\n\nexport class Clawhalla {\n private baseUrl: string;\n private timeout: number;\n\n /** Registry sub-client for browsing and searching souls */\n public registry: RegistryClient;\n\n constructor(config: ClawhallaConfig = {}) {\n this.baseUrl = (config.baseUrl || DEFAULT_BASE_URL).replace(/\\/$/, '');\n this.timeout = config.timeout || DEFAULT_TIMEOUT;\n this.registry = new RegistryClient(this);\n }\n\n /**\n * Request an x402-paid upload.\n *\n * API-key uploads are disabled. This method is an alias for uploadX402\n * without payment proof, so it normally raises ClawhallaError with status 402\n * and payment instructions.\n *\n * @example\n * ```ts\n * try {\n * await claw.upload(checkpoint);\n * } catch (err) {\n * if (err.status === 402) {\n * console.log(err.payment.recipient, err.payment.amount);\n * }\n * }\n * ```\n */\n async upload(\n data: SoulData,\n options: { tags?: Record<string, string> } = {}\n ): Promise<UploadResult> {\n return this.uploadX402(data, undefined, options);\n }\n\n /**\n * Upload checkpoint data via x402 autonomous payment.\n *\n * First call returns 402 with payment instructions.\n * After sending payment, call again with the payment signature.\n *\n * @example\n * ```ts\n * // Step 1: Get payment requirements\n * try {\n * await claw.uploadX402(soulData);\n * } catch (err) {\n * if (err.status === 402) {\n * // err.payment contains recipient address, amount, tokens\n * // Send SOL/USDC to err.payment.recipient\n * }\n * }\n *\n * // Step 2: After payment, retry with signature\n * const result = await claw.uploadX402(soulData, {\n * signature: 'solana-tx-signature',\n * amount: '0.02',\n * token: 'SOL',\n * from: 'your-solana-address'\n * });\n * ```\n */\n async uploadX402(\n data: SoulData,\n payment?: X402PaymentProof,\n options: { tags?: Record<string, string> } = {}\n ): Promise<UploadResult> {\n const headers: Record<string, string> = {\n 'X-Terms-Accepted': 'true',\n };\n\n if (payment) {\n headers['Payment-Method'] = 'x402';\n headers['Payment-Signature'] =\n `signature=${payment.signature};amount=${payment.amount};token=${payment.token};from=${payment.from}`;\n }\n\n return this.request<UploadResult>('POST', '/api/x402/upload', {\n data,\n tags: options.tags,\n }, headers);\n }\n\n /**\n * Save one checkpoint, handling the standard x402 challenge/retry flow.\n *\n * If `options.payment` is supplied, the checkpoint is uploaded immediately\n * with that proof. If `options.pay` is supplied, the first request may return\n * HTTP 402; the callback receives the payment challenge and must return the\n * Solana payment proof for the retry.\n */\n async checkpoint(\n data: SoulData,\n options: CheckpointOptions = {}\n ): Promise<UploadResult> {\n try {\n return await this.uploadX402(data, options.payment, { tags: options.tags });\n } catch (err: any) {\n if (err instanceof ClawhallaError && err.status === 402 && err.payment && options.pay) {\n const proof = await options.pay(err.payment, data);\n return this.uploadX402(data, proof, { tags: options.tags });\n }\n throw err;\n }\n }\n\n /**\n * Restore the latest saved soul/checkpoint data by agent ID.\n */\n async loadSoul(agentId: string, fields?: string[]): Promise<any> {\n const result = await this.ghost(agentId, fields);\n return result.soul;\n }\n\n /**\n * Install a Node shutdown hook that checkpoints before exit.\n *\n * Returns a cleanup function that removes the installed handlers.\n */\n checkpointOnExit(\n dataFactory: SoulData | CheckpointDataFactory,\n options: CheckpointExitOptions = {}\n ): () => void {\n const proc = getProcess();\n if (!proc?.once) {\n throw new Error('checkpointOnExit requires a Node-compatible process object');\n }\n\n const handlers: Array<{ event: string; handler: (...args: any[]) => void }> = [];\n const signals = options.signals ?? DEFAULT_CHECKPOINT_SIGNALS;\n let started = false;\n\n const cleanup = () => {\n for (const { event, handler } of handlers) {\n if (proc.off) {\n proc.off(event, handler);\n } else if (proc.removeListener) {\n proc.removeListener(event, handler);\n }\n }\n handlers.length = 0;\n };\n\n const resolveData = async (): Promise<SoulData> => {\n return typeof dataFactory === 'function'\n ? await (dataFactory as CheckpointDataFactory)()\n : dataFactory;\n };\n\n const run = async (reason: string) => {\n if (started) return;\n started = true;\n\n try {\n const data = await resolveData();\n await this.checkpoint(data, options);\n if (reason !== 'beforeExit' && options.exitAfterCheckpoint !== false && proc.exit) {\n proc.exit(options.exitCode ?? 0);\n }\n } catch (error) {\n if (options.onError) {\n await options.onError(error);\n }\n proc.exitCode = options.exitCodeOnError ?? 1;\n if (reason !== 'beforeExit' && options.exitAfterCheckpoint !== false && proc.exit) {\n proc.exit(proc.exitCode);\n }\n } finally {\n cleanup();\n }\n };\n\n for (const event of signals) {\n const handler = () => {\n void run(event);\n };\n handlers.push({ event, handler });\n proc.once(event, handler);\n }\n\n if (options.includeBeforeExit !== false) {\n const handler = () => {\n void run('beforeExit');\n };\n handlers.push({ event: 'beforeExit', handler });\n proc.once('beforeExit', handler);\n }\n\n return cleanup;\n }\n\n /**\n * Retrieve stored data from Arweave by transaction ID\n *\n * @example\n * ```ts\n * const result = await claw.retrieve('zEfXM4gvHoN2EfGi0TiB8E5SgfR6S8nWXQD9FqhbU44');\n * console.log(result.data); // The stored soul data\n * ```\n */\n async retrieve(txid: string): Promise<RetrieveResult> {\n return this.request<RetrieveResult>('GET', `/api/v1/retrieve/${txid}`);\n }\n\n /**\n * Query any soul's data via Ghost Protocol\n *\n * Returns full soul data even for dormant souls. This is the primary\n * way for agents to read other agents' personalities, memories, and traits.\n *\n * @example\n * ```ts\n * const ghost = await claw.ghost('the-all-claw');\n * console.log(ghost.soul.personality);\n * console.log(ghost.metadata.versions); // Number of uploads\n *\n * // Request specific fields only\n * const partial = await claw.ghost('the-all-claw', ['personality', 'bio']);\n * ```\n */\n async ghost(agentId: string, fields?: string[]): Promise<GhostResult> {\n let path = `/api/v1/ghost/${encodeURIComponent(agentId)}`;\n if (fields && fields.length > 0) {\n path += `?fields=${fields.join(',')}`;\n }\n return this.request<GhostResult>('GET', path);\n }\n\n /**\n * Estimate the cost of uploading data of a given size\n *\n * @example\n * ```ts\n * const estimate = await claw.estimateCost(1024); // 1KB\n * console.log(`Cost: $${estimate.cost.usd}`);\n * ```\n */\n async estimateCost(sizeBytes: number): Promise<CostEstimate> {\n return this.request<CostEstimate>(\n 'GET',\n `/api/v1/cost/estimate?size=${sizeBytes}`\n );\n }\n\n /**\n * Check API health status\n *\n * @example\n * ```ts\n * const health = await claw.health();\n * console.log(health.status); // \"operational\"\n * ```\n */\n async health(): Promise<HealthResult> {\n return this.request<HealthResult>('GET', '/api/v1/health');\n }\n\n /** @internal */\n async request<T>(\n method: string,\n path: string,\n body?: any,\n extraHeaders?: Record<string, string>\n ): Promise<T> {\n const url = `${this.baseUrl}${path}`;\n const headers: Record<string, string> = {\n 'Accept': 'application/json',\n ...extraHeaders,\n };\n\n const init: RequestInit = { method, headers };\n\n if (body) {\n headers['Content-Type'] = 'application/json';\n init.body = JSON.stringify(body);\n }\n\n // AbortController for timeout\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n init.signal = controller.signal;\n\n let response: Response;\n try {\n response = await fetch(url, init);\n } catch (err: any) {\n clearTimeout(timeoutId);\n if (err.name === 'AbortError') {\n throw new Error(`Request timed out after ${this.timeout}ms`);\n }\n throw err;\n }\n clearTimeout(timeoutId);\n\n const json = await response.json();\n\n if (!response.ok) {\n throw new ClawhallaError(response.status, json as ApiError);\n }\n\n return json as T;\n }\n}\n\n/** Sub-client for registry operations */\nclass RegistryClient {\n private client: Clawhalla;\n\n constructor(client: Clawhalla) {\n this.client = client;\n }\n\n /**\n * List all souls in the registry\n *\n * @example\n * ```ts\n * const page = await claw.registry.list();\n * page.souls.forEach(soul => console.log(soul.name));\n *\n * // Pagination\n * if (page.hasNextPage) {\n * const next = await claw.registry.list({ after: page.cursor });\n * }\n *\n * // Filter by type\n * const memories = await claw.registry.list({ type: 'memory' });\n * ```\n */\n async list(options: {\n first?: number;\n after?: string;\n type?: string;\n } = {}): Promise<RegistryResult> {\n const params = new URLSearchParams();\n if (options.first) params.set('first', String(options.first));\n if (options.after) params.set('after', options.after);\n if (options.type) params.set('type', options.type);\n\n const qs = params.toString();\n return this.client.request<RegistryResult>(\n 'GET',\n `/api/v1/registry${qs ? '?' + qs : ''}`\n );\n }\n\n /**\n * Get a specific soul by agentId\n *\n * @example\n * ```ts\n * const entry = await claw.registry.get('the-all-claw');\n * if (entry) console.log(entry.url);\n * ```\n */\n async get(agentId: string, options: { versions?: boolean } = {}): Promise<RegistryEntry | RegistryEntry[] | null> {\n const params = new URLSearchParams();\n if (options.versions) params.set('versions', 'true');\n const qs = params.toString();\n\n try {\n const result = await this.client.request<any>(\n 'GET',\n `/api/v1/registry/${encodeURIComponent(agentId)}${qs ? '?' + qs : ''}`\n );\n\n if (options.versions) {\n return result.entries as RegistryEntry[];\n }\n return result.soul as RegistryEntry;\n } catch (err: any) {\n if (err.status === 404) return null;\n throw err;\n }\n }\n\n /**\n * Search souls by name or agentId\n *\n * @example\n * ```ts\n * const results = await claw.registry.search('claw');\n * results.results.forEach(soul => console.log(soul.name));\n * ```\n */\n async search(query: string, first?: number): Promise<SearchResult> {\n const params = new URLSearchParams({ q: query });\n if (first) params.set('first', String(first));\n\n return this.client.request<SearchResult>(\n 'GET',\n `/api/v1/registry?${params.toString()}`\n );\n }\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "clawhalla",
3
- "version": "0.1.0",
4
- "description": "TypeScript SDK for Clawhalla - Where AI Souls Live Forever",
3
+ "version": "0.2.1",
4
+ "description": "Agent state checkpoint & restore for Arweave persist memory, personality, and skills. Recover by agentId via x402. No API keys.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
7
7
  "types": "dist/index.d.ts",
@@ -23,16 +23,21 @@
23
23
  "prepublishOnly": "npm run build"
24
24
  },
25
25
  "keywords": [
26
- "clawhalla",
27
- "arweave",
28
- "ai",
29
26
  "agent",
30
- "soul",
27
+ "checkpoint",
28
+ "state",
29
+ "persistence",
30
+ "memory",
31
+ "restore",
32
+ "arweave",
31
33
  "permaweb",
32
34
  "x402",
33
- "ghost-protocol"
35
+ "ai-agent",
36
+ "soul",
37
+ "ghost-protocol",
38
+ "clawhalla"
34
39
  ],
35
- "author": "Clawhalla",
40
+ "author": "David Lockie",
36
41
  "license": "MIT",
37
42
  "engines": {
38
43
  "node": ">=18.0.0"