@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.
- package/dist/index.js +199 -14
- 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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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/
|
|
20
|
+
"@taskmarket/prettier-config": "0.0.0",
|
|
21
21
|
"@taskmarket/shared": "1.0.0",
|
|
22
|
-
"@taskmarket/
|
|
22
|
+
"@taskmarket/eslint-config": "0.0.0"
|
|
23
23
|
},
|
|
24
24
|
"files": [
|
|
25
25
|
"dist"
|