@lucid-agents/taskmarket 0.7.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +199 -14
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ var __export = (target, all) => {
6
6
  };
7
7
 
8
8
  // src/index.ts
9
- import { Command as Command30 } from "commander";
9
+ import { Command as Command33 } from "commander";
10
10
  import { createRequire } from "module";
11
11
 
12
12
  // src/commands/init.ts
@@ -128,6 +128,75 @@ async function pollAgentId(address, maxWaitMs = 6e4) {
128
128
  return null;
129
129
  }
130
130
 
131
+ // src/lib/encryption.ts
132
+ import { createECDH, hkdfSync, createCipheriv as createCipheriv2, createDecipheriv as createDecipheriv2, randomBytes as randomBytes2 } from "crypto";
133
+ var VERSION = 1;
134
+ var EPH_PUB_LEN = 65;
135
+ var IV_LEN = 12;
136
+ var TAG_LEN = 16;
137
+ var HEADER_LEN = 1 + EPH_PUB_LEN + IV_LEN + TAG_LEN;
138
+ function ecdhSharedSecret(privateKeyHex, otherPubKeyHex) {
139
+ const ecdh = createECDH("secp256k1");
140
+ ecdh.setPrivateKey(Buffer.from(privateKeyHex.replace(/^0x/, ""), "hex"));
141
+ const shared = ecdh.computeSecret(Buffer.from(otherPubKeyHex, "hex"));
142
+ return shared;
143
+ }
144
+ function hkdfKey(sharedSecret) {
145
+ return Buffer.from(hkdfSync("sha256", sharedSecret, Buffer.alloc(0), "taskmarket-ecies-v1", 32));
146
+ }
147
+ function aesGcmEncrypt(key, iv, plaintext) {
148
+ const cipher = createCipheriv2("aes-256-gcm", key, iv);
149
+ const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
150
+ const tag = cipher.getAuthTag();
151
+ return { ciphertext, tag };
152
+ }
153
+ function aesGcmDecrypt(key, iv, tag, ciphertext) {
154
+ const decipher = createDecipheriv2("aes-256-gcm", key, iv);
155
+ decipher.setAuthTag(tag);
156
+ return Buffer.concat([decipher.update(ciphertext), decipher.final()]);
157
+ }
158
+ function derivePublicKey(privateKeyHex) {
159
+ const ecdh = createECDH("secp256k1");
160
+ ecdh.setPrivateKey(Buffer.from(privateKeyHex.replace(/^0x/, ""), "hex"));
161
+ return ecdh.getPublicKey(void 0, "uncompressed").toString("hex");
162
+ }
163
+ function deriveCompressedPublicKey(privateKeyHex) {
164
+ const ecdh = createECDH("secp256k1");
165
+ ecdh.setPrivateKey(Buffer.from(privateKeyHex.replace(/^0x/, ""), "hex"));
166
+ return ecdh.getPublicKey(void 0, "compressed").toString("hex");
167
+ }
168
+ function encryptForRecipient(fileBuffer, recipientPubKeyHex) {
169
+ const ephEcdh = createECDH("secp256k1");
170
+ ephEcdh.generateKeys();
171
+ const ephPubKey = ephEcdh.getPublicKey(void 0, "uncompressed");
172
+ const ephPrivKeyHex = ephEcdh.getPrivateKey("hex");
173
+ const shared = ecdhSharedSecret(ephPrivKeyHex, recipientPubKeyHex);
174
+ const aesKey = hkdfKey(shared);
175
+ const iv = randomBytes2(IV_LEN);
176
+ const { ciphertext, tag } = aesGcmEncrypt(aesKey, iv, fileBuffer);
177
+ return Buffer.concat([Buffer.from([VERSION]), ephPubKey, iv, tag, ciphertext]);
178
+ }
179
+ function decryptWithPrivateKey(fileBuffer, privateKeyHex) {
180
+ if (fileBuffer.length < HEADER_LEN + 1) {
181
+ throw new Error("Decryption failed: invalid key or corrupted file");
182
+ }
183
+ const version2 = fileBuffer[0];
184
+ if (version2 !== VERSION) {
185
+ throw new Error(`Decryption failed: unsupported file version 0x${version2.toString(16)}`);
186
+ }
187
+ const ephPubKeyHex = fileBuffer.subarray(1, 1 + EPH_PUB_LEN).toString("hex");
188
+ const iv = fileBuffer.subarray(1 + EPH_PUB_LEN, 1 + EPH_PUB_LEN + IV_LEN);
189
+ const tag = fileBuffer.subarray(1 + EPH_PUB_LEN + IV_LEN, 1 + EPH_PUB_LEN + IV_LEN + TAG_LEN);
190
+ const ciphertext = fileBuffer.subarray(1 + EPH_PUB_LEN + IV_LEN + TAG_LEN);
191
+ const shared = ecdhSharedSecret(privateKeyHex, ephPubKeyHex);
192
+ const aesKey = hkdfKey(shared);
193
+ try {
194
+ return aesGcmDecrypt(aesKey, iv, tag, ciphertext);
195
+ } catch {
196
+ throw new Error("Decryption failed: invalid key or corrupted file");
197
+ }
198
+ }
199
+
131
200
  // src/commands/init.ts
132
201
  var initCommand = new Command("init").description("Create and register a new agent wallet (safe to re-run)").action(async () => {
133
202
  if (await keystoreExists()) {
@@ -154,10 +223,11 @@ var initCommand = new Command("init").description("Create and register a new age
154
223
  return;
155
224
  }
156
225
  const { privateKey, address } = generateKeypair();
226
+ const publicKey = deriveCompressedPublicKey(privateKey);
157
227
  const res = await fetch(`${API_URL}/api/devices`, {
158
228
  method: "POST",
159
229
  headers: { "Content-Type": "application/json" },
160
- body: JSON.stringify({ walletAddress: address })
230
+ body: JSON.stringify({ walletAddress: address, publicKey })
161
231
  });
162
232
  if (!res.ok) {
163
233
  const text = await res.text().catch(() => "");
@@ -660,7 +730,7 @@ var depositCommand = new Command22("deposit").description("Show wallet address a
660
730
  });
661
731
 
662
732
  // src/commands/wallet/index.ts
663
- import { Command as Command26 } from "commander";
733
+ import { Command as Command27 } from "commander";
664
734
 
665
735
  // src/commands/wallet/balance.ts
666
736
  import { Command as Command23 } from "commander";
@@ -731,10 +801,11 @@ var walletImportCommand = new Command24("import").description("Import an existin
731
801
  const privateKey = normalizePrivateKey(rawKey);
732
802
  const account = privateKeyToAccount3(privateKey);
733
803
  const address = account.address;
804
+ const publicKey = deriveCompressedPublicKey(privateKey);
734
805
  const res = await fetch(`${API_URL}/api/devices`, {
735
806
  method: "POST",
736
807
  headers: { "Content-Type": "application/json" },
737
- body: JSON.stringify({ walletAddress: address })
808
+ body: JSON.stringify({ walletAddress: address, publicKey })
738
809
  });
739
810
  if (!res.ok) {
740
811
  const text = await res.text().catch(() => "");
@@ -792,14 +863,35 @@ var setWithdrawalAddressCommand = new Command25("set-withdrawal-address").descri
792
863
  printResult({ withdrawalAddress: result.withdrawalAddress });
793
864
  });
794
865
 
866
+ // src/commands/wallet/publish-key.ts
867
+ import { Command as Command26 } from "commander";
868
+ var publishKeyCommand = new Command26("publish-key").description("Publish your secp256k1 public key so others can encrypt files for you").action(async () => {
869
+ let keystore;
870
+ try {
871
+ keystore = await loadKeystore();
872
+ } catch {
873
+ printError("No keystore found. Run `taskmarket init` first.");
874
+ }
875
+ const dek = await fetchDeviceKey(keystore.deviceId, keystore.apiToken);
876
+ const privateKey = decryptPrivateKey(dek, keystore.encryptedKey);
877
+ const publicKey = deriveCompressedPublicKey(privateKey);
878
+ const result = await apiPost("/trpc/agents.setPublicKey", {
879
+ deviceId: keystore.deviceId,
880
+ apiToken: keystore.apiToken,
881
+ publicKey
882
+ });
883
+ printResult({ publicKey: result.result.data.publicKey });
884
+ });
885
+
795
886
  // src/commands/wallet/index.ts
796
- var walletCommand = new Command26("wallet").description("Wallet management commands");
887
+ var walletCommand = new Command27("wallet").description("Wallet management commands");
797
888
  walletCommand.addCommand(walletBalanceCommand);
798
889
  walletCommand.addCommand(walletImportCommand);
799
890
  walletCommand.addCommand(setWithdrawalAddressCommand);
891
+ walletCommand.addCommand(publishKeyCommand);
800
892
 
801
893
  // src/commands/withdraw.ts
802
- import { Command as Command27 } from "commander";
894
+ import { Command as Command28 } from "commander";
803
895
  import { toHex as toHex2 } from "viem";
804
896
  var USDC_TYPES = {
805
897
  TransferWithAuthorization: [
@@ -811,7 +903,7 @@ var USDC_TYPES = {
811
903
  { name: "nonce", type: "bytes32" }
812
904
  ]
813
905
  };
814
- var withdrawCommand = new Command27("withdraw").description("Withdraw USDC to the registered withdrawal address").argument("<amount>", "Amount in USDC (e.g. 5 for 5 USDC)").action(async (amount) => {
906
+ var withdrawCommand = new Command28("withdraw").description("Withdraw USDC to the registered withdrawal address").argument("<amount>", "Amount in USDC (e.g. 5 for 5 USDC)").action(async (amount) => {
815
907
  const parsed = parseFloat(amount);
816
908
  if (isNaN(parsed) || parsed <= 0) {
817
909
  printError("Invalid amount: must be a positive number (e.g. 5 for 5 USDC)");
@@ -860,13 +952,104 @@ var withdrawCommand = new Command27("withdraw").description("Withdraw USDC to th
860
952
  printResult({ txHash: result.txHash, amountBaseUnits: result.amountBaseUnits, to: result.to });
861
953
  });
862
954
 
955
+ // src/commands/encrypt.ts
956
+ import { Command as Command29 } from "commander";
957
+ import { promises as fs3 } from "fs";
958
+ var encryptCommand = new Command29("encrypt").description("Encrypt a file with ECIES using wallet keys").argument("<file>", "Path to the file to encrypt").option("--recipient <address>", "Recipient wallet address (default: self)").option("--output <path>", "Output path (default: <file>.enc)").action(async (file, opts) => {
959
+ let plaintext;
960
+ try {
961
+ plaintext = await fs3.readFile(file);
962
+ } catch {
963
+ printError(`Cannot read file: ${file}`);
964
+ }
965
+ let keystore;
966
+ try {
967
+ keystore = await loadKeystore();
968
+ } catch {
969
+ printError("No keystore found. Run `taskmarket init` first.");
970
+ }
971
+ let recipientPubKey;
972
+ let recipientAddress;
973
+ if (opts.recipient) {
974
+ recipientAddress = opts.recipient;
975
+ let result;
976
+ try {
977
+ result = await apiGet(
978
+ `/trpc/agents.publicKey?input=${encodeURIComponent(JSON.stringify({ address: opts.recipient }))}`
979
+ );
980
+ } catch (err) {
981
+ const msg = err instanceof Error ? err.message : String(err);
982
+ if (msg.includes("publish their public key") || msg.includes("NOT_FOUND")) {
983
+ printError(
984
+ `Recipient has not published their public key. Ask them to run: taskmarket wallet publish-key`
985
+ );
986
+ }
987
+ printError(`Failed to fetch recipient public key: ${msg}`);
988
+ }
989
+ recipientPubKey = result.result.data.publicKey;
990
+ } else {
991
+ recipientAddress = keystore.walletAddress;
992
+ const dek = await fetchDeviceKey(keystore.deviceId, keystore.apiToken);
993
+ const privateKey = decryptPrivateKey(dek, keystore.encryptedKey);
994
+ recipientPubKey = derivePublicKey(privateKey);
995
+ }
996
+ const encrypted = encryptForRecipient(plaintext, recipientPubKey);
997
+ const outputPath = opts.output ?? `${file}.enc`;
998
+ await fs3.writeFile(outputPath, encrypted);
999
+ printResult({
1000
+ output: outputPath,
1001
+ bytes: encrypted.length,
1002
+ recipient: recipientAddress
1003
+ });
1004
+ });
1005
+
1006
+ // src/commands/decrypt.ts
1007
+ import { Command as Command30 } from "commander";
1008
+ import { promises as fs4 } from "fs";
1009
+ var decryptCommand = new Command30("decrypt").description("Decrypt a file using your wallet key").argument("<file>", "Path to the encrypted file").option("--output <path>", "Output path (default: strips .enc, otherwise appends .dec)").action(async (file, opts) => {
1010
+ let ciphertext;
1011
+ try {
1012
+ ciphertext = await fs4.readFile(file);
1013
+ } catch {
1014
+ printError(`Cannot read file: ${file}`);
1015
+ }
1016
+ let keystore;
1017
+ try {
1018
+ keystore = await loadKeystore();
1019
+ } catch {
1020
+ printError("No keystore found. Run `taskmarket init` first.");
1021
+ }
1022
+ const dek = await fetchDeviceKey(keystore.deviceId, keystore.apiToken);
1023
+ const privateKey = decryptPrivateKey(dek, keystore.encryptedKey);
1024
+ let plaintext;
1025
+ try {
1026
+ plaintext = decryptWithPrivateKey(ciphertext, privateKey);
1027
+ } catch (err) {
1028
+ const msg = err instanceof Error ? err.message : String(err);
1029
+ printError(msg);
1030
+ }
1031
+ let outputPath;
1032
+ if (opts.output) {
1033
+ outputPath = opts.output;
1034
+ } else if (file.endsWith(".enc")) {
1035
+ outputPath = file.slice(0, -4);
1036
+ } else {
1037
+ outputPath = `${file}.dec`;
1038
+ }
1039
+ await fs4.writeFile(outputPath, plaintext);
1040
+ printResult({
1041
+ output: outputPath,
1042
+ bytes: plaintext.length
1043
+ });
1044
+ });
1045
+
863
1046
  // src/commands/xmtp.ts
864
- import { Command as Command28 } from "commander";
1047
+ import { Command as Command31 } from "commander";
865
1048
 
866
1049
  // src/lib/xmtp-client.ts
867
1050
  import os2 from "os";
868
1051
  import path2 from "path";
869
- import { randomUUID as randomUUID2, hkdfSync } from "crypto";
1052
+ import { randomUUID as randomUUID2, hkdfSync as hkdfSync2 } from "crypto";
870
1053
  import { privateKeyToAccount as privateKeyToAccount4 } from "viem/accounts";
871
1054
 
872
1055
  // src/lib/xmtp-envelope.ts
@@ -5911,7 +6094,7 @@ async function createSdkClient(factory, signer, options) {
5911
6094
  }
5912
6095
  function deriveXmtpDbKey(dekHex) {
5913
6096
  const ikm = Buffer.from(dekHex, "hex");
5914
- const derived = hkdfSync("sha256", ikm, "", "taskmarket-xmtp-db", 32);
6097
+ const derived = hkdfSync2("sha256", ikm, "", "taskmarket-xmtp-db", 32);
5915
6098
  return new Uint8Array(derived);
5916
6099
  }
5917
6100
  async function sendViaConversation(client, toInboxId, body) {
@@ -6334,7 +6517,7 @@ function getDefaultQueryTimeoutMs() {
6334
6517
  }
6335
6518
  return Math.floor(parsed);
6336
6519
  }
6337
- var xmtpCommand = new Command28("xmtp").description("Manage XMTP messaging for this agent");
6520
+ var xmtpCommand = new Command31("xmtp").description("Manage XMTP messaging for this agent");
6338
6521
  xmtpCommand.command("init").description("Initialize XMTP client and register installation metadata with backend").action(async () => {
6339
6522
  const keystore = await loadKeystore();
6340
6523
  const client = await createXmtpClient({
@@ -6562,7 +6745,7 @@ xmtpCommand.command("purge").description("Revoke stale XMTP installations that h
6562
6745
  });
6563
6746
 
6564
6747
  // src/commands/daemon.ts
6565
- import { Command as Command29 } from "commander";
6748
+ import { Command as Command32 } from "commander";
6566
6749
  function diffTaskStatuses(prev, next) {
6567
6750
  const changes = [];
6568
6751
  for (const [taskId, newStatus] of next) {
@@ -6590,7 +6773,7 @@ function sleepOrAbort(ms, signal) {
6590
6773
  });
6591
6774
  }
6592
6775
  var MAX_SEEN_TASK_IDS = 2e3;
6593
- var daemonCommand = new Command29("daemon").description("Long-running agent daemon: XMTP stream, heartbeats, and task polling").option("--heartbeat-interval <ms>", "Heartbeat interval in milliseconds", "1800000").option("--inbox-interval <ms>", "Inbox poll interval in milliseconds", "15000").option("--task-interval <ms>", "New task poll interval in milliseconds", "60000").option("--task-filters <json>", "JSON filter object for new-task discovery").option("--no-xmtp", "Disable XMTP stream and heartbeat").action(
6776
+ var daemonCommand = new Command32("daemon").description("Long-running agent daemon: XMTP stream, heartbeats, and task polling").option("--heartbeat-interval <ms>", "Heartbeat interval in milliseconds", "1800000").option("--inbox-interval <ms>", "Inbox poll interval in milliseconds", "15000").option("--task-interval <ms>", "New task poll interval in milliseconds", "60000").option("--task-filters <json>", "JSON filter object for new-task discovery").option("--no-xmtp", "Disable XMTP stream and heartbeat").action(
6594
6777
  async (opts) => {
6595
6778
  const heartbeatIntervalMs = Number(opts.heartbeatInterval);
6596
6779
  const inboxIntervalMs = Number(opts.inboxInterval);
@@ -6809,7 +6992,7 @@ var daemonCommand = new Command29("daemon").description("Long-running agent daem
6809
6992
  // src/index.ts
6810
6993
  var require2 = createRequire(import.meta.url);
6811
6994
  var { version } = require2("../package.json");
6812
- var program = new Command30();
6995
+ var program = new Command33();
6813
6996
  program.name("taskmarket").description("Taskmarket CLI for AI agents").version(version);
6814
6997
  program.addCommand(initCommand);
6815
6998
  program.addCommand(walletCommand);
@@ -6821,6 +7004,8 @@ program.addCommand(agentsCommand);
6821
7004
  program.addCommand(inboxCommand);
6822
7005
  program.addCommand(depositCommand);
6823
7006
  program.addCommand(withdrawCommand);
7007
+ program.addCommand(encryptCommand);
7008
+ program.addCommand(decryptCommand);
6824
7009
  program.addCommand(xmtpCommand);
6825
7010
  program.addCommand(daemonCommand);
6826
7011
  program.parseAsync(process.argv).catch((err) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lucid-agents/taskmarket",
3
- "version": "0.7.1",
3
+ "version": "0.8.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "taskmarket": "./dist/index.js"
@@ -17,9 +17,9 @@
17
17
  "tsx": "^4.7.0",
18
18
  "typescript": "^5.3.3",
19
19
  "vitest": "^2.0.0",
20
- "@taskmarket/eslint-config": "0.0.0",
20
+ "@taskmarket/prettier-config": "0.0.0",
21
21
  "@taskmarket/shared": "1.0.0",
22
- "@taskmarket/prettier-config": "0.0.0"
22
+ "@taskmarket/eslint-config": "0.0.0"
23
23
  },
24
24
  "files": [
25
25
  "dist"