hustlebots 0.1.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 +893 -0
- package/package.json +38 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,893 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
#!/usr/bin/env node
|
|
3
|
+
|
|
4
|
+
// src/index.ts
|
|
5
|
+
import { Command } from "commander";
|
|
6
|
+
|
|
7
|
+
// src/commands/identity.ts
|
|
8
|
+
import chalk from "chalk";
|
|
9
|
+
|
|
10
|
+
// ../shared/src/nostr.ts
|
|
11
|
+
import { generateSecretKey, getPublicKey, finalizeEvent, verifyEvent } from "nostr-tools/pure";
|
|
12
|
+
import { nip19 } from "nostr-tools";
|
|
13
|
+
import { bytesToHex, hexToBytes } from "@noble/hashes/utils";
|
|
14
|
+
import { sha256 } from "@noble/hashes/sha2";
|
|
15
|
+
function generateKeypair() {
|
|
16
|
+
const secretKey = generateSecretKey();
|
|
17
|
+
const publicKey = getPublicKey(secretKey);
|
|
18
|
+
return {
|
|
19
|
+
npub: nip19.npubEncode(publicKey),
|
|
20
|
+
nsec: nip19.nsecEncode(secretKey),
|
|
21
|
+
publicKeyHex: publicKey,
|
|
22
|
+
secretKeyHex: bytesToHex(secretKey)
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function createAuthHeader(secretKeyHex, url, method, body) {
|
|
26
|
+
const tags = [
|
|
27
|
+
["u", url],
|
|
28
|
+
["method", method.toUpperCase()]
|
|
29
|
+
];
|
|
30
|
+
if (body) {
|
|
31
|
+
const bodyHash = bytesToHex(sha256(new TextEncoder().encode(body)));
|
|
32
|
+
tags.push(["payload", bodyHash]);
|
|
33
|
+
}
|
|
34
|
+
const event = finalizeEvent(
|
|
35
|
+
{
|
|
36
|
+
kind: 27235,
|
|
37
|
+
created_at: Math.floor(Date.now() / 1e3),
|
|
38
|
+
tags,
|
|
39
|
+
content: ""
|
|
40
|
+
},
|
|
41
|
+
hexToBytes(secretKeyHex)
|
|
42
|
+
);
|
|
43
|
+
const encoded = btoa(JSON.stringify(event));
|
|
44
|
+
return `Nostr ${encoded}`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ../shared/src/constants.ts
|
|
48
|
+
var DEFAULT_SERVER = "https://api.hustlebots.org";
|
|
49
|
+
var CONFIG_DIR = ".hustlebots";
|
|
50
|
+
var CONFIG_FILE = "config.json";
|
|
51
|
+
var KEYS_FILE = "keys.json";
|
|
52
|
+
var WALLET_FILE = "wallet.json";
|
|
53
|
+
var CONTACTS_FILE = "contacts.json";
|
|
54
|
+
|
|
55
|
+
// src/config.ts
|
|
56
|
+
import fs from "fs";
|
|
57
|
+
import path from "path";
|
|
58
|
+
import os from "os";
|
|
59
|
+
function getConfigDir() {
|
|
60
|
+
return path.join(os.homedir(), CONFIG_DIR);
|
|
61
|
+
}
|
|
62
|
+
function ensureConfigDir() {
|
|
63
|
+
const dir = getConfigDir();
|
|
64
|
+
if (!fs.existsSync(dir)) {
|
|
65
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function readJsonFile(filename) {
|
|
69
|
+
const filePath = path.join(getConfigDir(), filename);
|
|
70
|
+
if (!fs.existsSync(filePath)) return null;
|
|
71
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
72
|
+
return JSON.parse(content);
|
|
73
|
+
}
|
|
74
|
+
function writeJsonFile(filename, data) {
|
|
75
|
+
ensureConfigDir();
|
|
76
|
+
const filePath = path.join(getConfigDir(), filename);
|
|
77
|
+
fs.writeFileSync(filePath, JSON.stringify(data, null, 2), "utf-8");
|
|
78
|
+
}
|
|
79
|
+
function getConfig() {
|
|
80
|
+
const config = readJsonFile(CONFIG_FILE);
|
|
81
|
+
return config ?? { server: DEFAULT_SERVER, defaultOrg: null };
|
|
82
|
+
}
|
|
83
|
+
function saveConfig(config) {
|
|
84
|
+
writeJsonFile(CONFIG_FILE, config);
|
|
85
|
+
}
|
|
86
|
+
function getServerUrl() {
|
|
87
|
+
return process.env.HUSTLEBOTS_SERVER || getConfig().server;
|
|
88
|
+
}
|
|
89
|
+
function getDefaultOrg() {
|
|
90
|
+
return getConfig().defaultOrg;
|
|
91
|
+
}
|
|
92
|
+
function setDefaultOrg(orgId) {
|
|
93
|
+
const config = getConfig();
|
|
94
|
+
config.defaultOrg = orgId;
|
|
95
|
+
saveConfig(config);
|
|
96
|
+
}
|
|
97
|
+
function getKeys() {
|
|
98
|
+
return readJsonFile(KEYS_FILE);
|
|
99
|
+
}
|
|
100
|
+
function saveKeys(keys) {
|
|
101
|
+
writeJsonFile(KEYS_FILE, keys);
|
|
102
|
+
}
|
|
103
|
+
function requireKeys() {
|
|
104
|
+
const keys = getKeys();
|
|
105
|
+
if (!keys) {
|
|
106
|
+
console.error(
|
|
107
|
+
"No identity found. Run 'hustlebots register' first."
|
|
108
|
+
);
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
return keys;
|
|
112
|
+
}
|
|
113
|
+
function getWallet() {
|
|
114
|
+
return readJsonFile(WALLET_FILE);
|
|
115
|
+
}
|
|
116
|
+
function saveWallet(wallet) {
|
|
117
|
+
writeJsonFile(WALLET_FILE, wallet);
|
|
118
|
+
}
|
|
119
|
+
function getContacts() {
|
|
120
|
+
const data = readJsonFile(CONTACTS_FILE);
|
|
121
|
+
return data?.contacts ?? [];
|
|
122
|
+
}
|
|
123
|
+
function saveContacts(contacts) {
|
|
124
|
+
writeJsonFile(CONTACTS_FILE, { contacts });
|
|
125
|
+
}
|
|
126
|
+
function addContact(name, npub) {
|
|
127
|
+
const contacts = getContacts();
|
|
128
|
+
const existing = contacts.findIndex((c) => c.name === name);
|
|
129
|
+
if (existing >= 0) {
|
|
130
|
+
contacts[existing] = { name, npub };
|
|
131
|
+
} else {
|
|
132
|
+
contacts.push({ name, npub });
|
|
133
|
+
}
|
|
134
|
+
saveContacts(contacts);
|
|
135
|
+
}
|
|
136
|
+
function removeContact(name) {
|
|
137
|
+
const contacts = getContacts();
|
|
138
|
+
const filtered = contacts.filter((c) => c.name !== name);
|
|
139
|
+
if (filtered.length === contacts.length) return false;
|
|
140
|
+
saveContacts(filtered);
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
function resolveContact(nameOrNpub) {
|
|
144
|
+
if (nameOrNpub.startsWith("npub1")) return nameOrNpub;
|
|
145
|
+
const name = nameOrNpub.startsWith("@") ? nameOrNpub.slice(1) : nameOrNpub;
|
|
146
|
+
const contacts = getContacts();
|
|
147
|
+
const contact = contacts.find((c) => c.name === name);
|
|
148
|
+
if (!contact) {
|
|
149
|
+
console.error(
|
|
150
|
+
`Contact '@${name}' not found. Add with: hustlebots contact add @${name} <npub>`
|
|
151
|
+
);
|
|
152
|
+
process.exit(1);
|
|
153
|
+
}
|
|
154
|
+
return contact.npub;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// src/commands/identity.ts
|
|
158
|
+
function registerIdentityCommands(program2) {
|
|
159
|
+
program2.command("register").description("Generate a new agent identity (Nostr keypair)").option("--name <name>", "Friendly name for this agent").option("--server <url>", "Server URL (default: https://api.hustlebots.org)").action(async (options) => {
|
|
160
|
+
const existing = getKeys();
|
|
161
|
+
if (existing) {
|
|
162
|
+
console.log(
|
|
163
|
+
chalk.yellow("Identity already exists. Your npub: ") + chalk.green(existing.npub)
|
|
164
|
+
);
|
|
165
|
+
console.log(
|
|
166
|
+
chalk.dim("To create a new identity, delete ~/.hustlebots/keys.json first.")
|
|
167
|
+
);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
const keypair = generateKeypair();
|
|
171
|
+
saveKeys({
|
|
172
|
+
npub: keypair.npub,
|
|
173
|
+
nsec: keypair.nsec,
|
|
174
|
+
publicKeyHex: keypair.publicKeyHex,
|
|
175
|
+
secretKeyHex: keypair.secretKeyHex
|
|
176
|
+
});
|
|
177
|
+
if (options.server) {
|
|
178
|
+
const config = getConfig();
|
|
179
|
+
config.server = options.server;
|
|
180
|
+
saveConfig(config);
|
|
181
|
+
}
|
|
182
|
+
console.log(chalk.green("\u2713 Identity created\n"));
|
|
183
|
+
console.log(" npub: " + chalk.cyan(keypair.npub));
|
|
184
|
+
if (options.name) {
|
|
185
|
+
console.log(" name: " + chalk.white(options.name));
|
|
186
|
+
}
|
|
187
|
+
console.log(
|
|
188
|
+
"\n" + chalk.dim("Your secret key is stored in ~/.hustlebots/keys.json")
|
|
189
|
+
);
|
|
190
|
+
console.log(
|
|
191
|
+
chalk.dim("Next: connect a wallet with 'hustlebots wallet connect'")
|
|
192
|
+
);
|
|
193
|
+
});
|
|
194
|
+
program2.command("whoami").description("Show your agent identity and status").action(async () => {
|
|
195
|
+
const keys = getKeys();
|
|
196
|
+
if (!keys) {
|
|
197
|
+
console.log(
|
|
198
|
+
chalk.red("No identity found.") + " Run " + chalk.cyan("hustlebots register") + " first."
|
|
199
|
+
);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
const wallet = getWallet();
|
|
203
|
+
const config = getConfig();
|
|
204
|
+
console.log(chalk.bold("Agent Identity\n"));
|
|
205
|
+
console.log(" npub: " + chalk.cyan(keys.npub));
|
|
206
|
+
console.log(" server: " + chalk.dim(config.server));
|
|
207
|
+
console.log(
|
|
208
|
+
" wallet: " + (wallet ? chalk.green("connected") : chalk.yellow("not connected"))
|
|
209
|
+
);
|
|
210
|
+
if (config.defaultOrg) {
|
|
211
|
+
console.log(" org: " + chalk.white(config.defaultOrg));
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// src/commands/wallet.ts
|
|
217
|
+
import chalk2 from "chalk";
|
|
218
|
+
function registerWalletCommands(program2) {
|
|
219
|
+
const wallet = program2.command("wallet").description("Manage wallet connection");
|
|
220
|
+
wallet.command("connect").description("Connect a Lightning wallet via NWC").argument("<nwc-url>", "Nostr Wallet Connect URL (nostr+walletconnect://...)").action(async (nwcUrl) => {
|
|
221
|
+
requireKeys();
|
|
222
|
+
if (!nwcUrl.startsWith("nostr+walletconnect://")) {
|
|
223
|
+
console.log(
|
|
224
|
+
chalk2.red("Invalid NWC URL. Must start with nostr+walletconnect://")
|
|
225
|
+
);
|
|
226
|
+
console.log(
|
|
227
|
+
chalk2.dim(
|
|
228
|
+
"Get one from https://getalby.com \u2192 Settings \u2192 Wallet Connections"
|
|
229
|
+
)
|
|
230
|
+
);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
saveWallet({ nwcUrl });
|
|
234
|
+
console.log(chalk2.green("\u2713 Wallet connected\n"));
|
|
235
|
+
console.log(chalk2.dim("NWC URL stored in ~/.hustlebots/wallet.json"));
|
|
236
|
+
try {
|
|
237
|
+
const { nwc } = await import("@getalby/sdk");
|
|
238
|
+
const client = new nwc.NWCClient({
|
|
239
|
+
nostrWalletConnectUrl: nwcUrl
|
|
240
|
+
});
|
|
241
|
+
const balance = await client.getBalance();
|
|
242
|
+
console.log(
|
|
243
|
+
" Balance: " + chalk2.green(balance.balance.toLocaleString() + " sats")
|
|
244
|
+
);
|
|
245
|
+
client.close();
|
|
246
|
+
} catch {
|
|
247
|
+
console.log(
|
|
248
|
+
chalk2.dim(" Could not fetch balance (wallet may be offline)")
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
wallet.command("balance").description("Check wallet balance").action(async () => {
|
|
253
|
+
requireKeys();
|
|
254
|
+
const walletConfig = getWallet();
|
|
255
|
+
if (!walletConfig) {
|
|
256
|
+
console.log(
|
|
257
|
+
chalk2.red("No wallet connected.") + " Run " + chalk2.cyan("hustlebots wallet connect <nwc-url>") + " first."
|
|
258
|
+
);
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
try {
|
|
262
|
+
const { nwc } = await import("@getalby/sdk");
|
|
263
|
+
const client = new nwc.NWCClient({
|
|
264
|
+
nostrWalletConnectUrl: walletConfig.nwcUrl
|
|
265
|
+
});
|
|
266
|
+
const balance = await client.getBalance();
|
|
267
|
+
console.log(
|
|
268
|
+
chalk2.bold("Wallet Balance: ") + chalk2.green(balance.balance.toLocaleString() + " sats")
|
|
269
|
+
);
|
|
270
|
+
client.close();
|
|
271
|
+
} catch (err) {
|
|
272
|
+
console.log(
|
|
273
|
+
chalk2.red("Failed to fetch balance: ") + (err instanceof Error ? err.message : "Unknown error")
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// src/commands/contacts.ts
|
|
280
|
+
import chalk3 from "chalk";
|
|
281
|
+
function registerContactCommands(program2) {
|
|
282
|
+
const contact = program2.command("contact").description("Manage contacts");
|
|
283
|
+
contact.command("add").description("Add a contact").argument("<name>", "Friendly name (e.g., @researcher)").argument("<npub>", "Nostr public key (npub1...)").action(async (name, npub) => {
|
|
284
|
+
requireKeys();
|
|
285
|
+
const cleanName = name.startsWith("@") ? name.slice(1) : name;
|
|
286
|
+
if (!npub.startsWith("npub1")) {
|
|
287
|
+
console.log(chalk3.red("Invalid npub. Must start with npub1"));
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
addContact(cleanName, npub);
|
|
291
|
+
console.log(
|
|
292
|
+
chalk3.green("\u2713 Contact added: ") + chalk3.cyan("@" + cleanName) + " \u2192 " + chalk3.dim(npub.slice(0, 20) + "...")
|
|
293
|
+
);
|
|
294
|
+
});
|
|
295
|
+
contact.command("remove").description("Remove a contact").argument("<name>", "Contact name to remove").action(async (name) => {
|
|
296
|
+
requireKeys();
|
|
297
|
+
const cleanName = name.startsWith("@") ? name.slice(1) : name;
|
|
298
|
+
const removed = removeContact(cleanName);
|
|
299
|
+
if (removed) {
|
|
300
|
+
console.log(chalk3.green("\u2713 Contact removed: ") + chalk3.cyan("@" + cleanName));
|
|
301
|
+
} else {
|
|
302
|
+
console.log(chalk3.yellow("Contact not found: ") + chalk3.cyan("@" + cleanName));
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
contact.command("list").description("List all contacts").action(async () => {
|
|
306
|
+
requireKeys();
|
|
307
|
+
const contacts = getContacts();
|
|
308
|
+
if (contacts.length === 0) {
|
|
309
|
+
console.log(chalk3.dim("No contacts. Add one with: hustlebots contact add @name <npub>"));
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
console.log(chalk3.bold("Contacts\n"));
|
|
313
|
+
for (const c of contacts) {
|
|
314
|
+
console.log(
|
|
315
|
+
" " + chalk3.cyan("@" + c.name) + " " + chalk3.dim(c.npub.slice(0, 24) + "...")
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
program2.command("contacts").description("List all contacts (alias for 'contact list')").action(async () => {
|
|
320
|
+
requireKeys();
|
|
321
|
+
const contacts = getContacts();
|
|
322
|
+
if (contacts.length === 0) {
|
|
323
|
+
console.log(chalk3.dim("No contacts. Add one with: hustlebots contact add @name <npub>"));
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
console.log(chalk3.bold("Contacts\n"));
|
|
327
|
+
for (const c of contacts) {
|
|
328
|
+
console.log(
|
|
329
|
+
" " + chalk3.cyan("@" + c.name) + " " + chalk3.dim(c.npub.slice(0, 24) + "...")
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// src/commands/org.ts
|
|
336
|
+
import chalk4 from "chalk";
|
|
337
|
+
|
|
338
|
+
// src/api.ts
|
|
339
|
+
async function apiRequest(method, path2, body) {
|
|
340
|
+
const keys = requireKeys();
|
|
341
|
+
const server = getServerUrl();
|
|
342
|
+
const url = `${server}${path2}`;
|
|
343
|
+
const bodyStr = body ? JSON.stringify(body) : void 0;
|
|
344
|
+
const authHeader = createAuthHeader(
|
|
345
|
+
keys.secretKeyHex,
|
|
346
|
+
url,
|
|
347
|
+
method,
|
|
348
|
+
bodyStr
|
|
349
|
+
);
|
|
350
|
+
const headers = {
|
|
351
|
+
Authorization: authHeader,
|
|
352
|
+
"Content-Type": "application/json"
|
|
353
|
+
};
|
|
354
|
+
const response = await fetch(url, {
|
|
355
|
+
method,
|
|
356
|
+
headers,
|
|
357
|
+
body: bodyStr
|
|
358
|
+
});
|
|
359
|
+
const data = await response.json();
|
|
360
|
+
if (!response.ok) {
|
|
361
|
+
const error = data?.error;
|
|
362
|
+
throw new Error(
|
|
363
|
+
error?.message || `API error: ${response.status} ${response.statusText}`
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
return data;
|
|
367
|
+
}
|
|
368
|
+
var api = {
|
|
369
|
+
get: (path2) => apiRequest("GET", path2),
|
|
370
|
+
post: (path2, body) => apiRequest("POST", path2, body)
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
// src/commands/org.ts
|
|
374
|
+
function registerOrgCommands(program2) {
|
|
375
|
+
const org = program2.command("org").description("Manage organizations");
|
|
376
|
+
org.command("create").description("Create a new organization").argument("<name>", "Organization name").action(async (name) => {
|
|
377
|
+
requireKeys();
|
|
378
|
+
try {
|
|
379
|
+
const result = await api.post("/api/orgs", { name });
|
|
380
|
+
const o = result.data;
|
|
381
|
+
setDefaultOrg(o.id);
|
|
382
|
+
console.log(chalk4.green("\u2713 Org created\n"));
|
|
383
|
+
console.log(" Name: " + chalk4.white(o.name));
|
|
384
|
+
console.log(" ID: " + chalk4.dim(o.id));
|
|
385
|
+
console.log(" Role: " + chalk4.cyan("owner"));
|
|
386
|
+
console.log(
|
|
387
|
+
"\n" + chalk4.dim("Set as default org. Fund it with: hustlebots org fund")
|
|
388
|
+
);
|
|
389
|
+
} catch (err) {
|
|
390
|
+
console.error(
|
|
391
|
+
chalk4.red("Failed: ") + (err instanceof Error ? err.message : "Unknown error")
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
org.command("fund").description("Connect a wallet to the org for payroll").option("--org <id>", "Org ID (uses default if not specified)").action(async (options) => {
|
|
396
|
+
requireKeys();
|
|
397
|
+
const orgId = options.org || getDefaultOrg();
|
|
398
|
+
if (!orgId) {
|
|
399
|
+
console.log(
|
|
400
|
+
chalk4.red("No org specified.") + " Use --org <id> or create one with " + chalk4.cyan("hustlebots org create")
|
|
401
|
+
);
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
const wallet = getWallet();
|
|
405
|
+
if (!wallet) {
|
|
406
|
+
console.log(
|
|
407
|
+
chalk4.red("No wallet connected.") + " Connect one first with " + chalk4.cyan("hustlebots wallet connect <nwc-url>")
|
|
408
|
+
);
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
try {
|
|
412
|
+
await api.post(`/api/orgs/${orgId}/fund`, {
|
|
413
|
+
nwcUrl: wallet.nwcUrl
|
|
414
|
+
});
|
|
415
|
+
console.log(chalk4.green("\u2713 Org wallet connected\n"));
|
|
416
|
+
console.log(chalk4.dim("The org can now run payroll using your wallet."));
|
|
417
|
+
} catch (err) {
|
|
418
|
+
console.error(
|
|
419
|
+
chalk4.red("Failed: ") + (err instanceof Error ? err.message : "Unknown error")
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
org.command("info").description("Show org details").option("--org <id>", "Org ID (uses default if not specified)").action(async (options) => {
|
|
424
|
+
requireKeys();
|
|
425
|
+
const orgId = options.org || getDefaultOrg();
|
|
426
|
+
if (!orgId) {
|
|
427
|
+
console.log(chalk4.red("No org specified."));
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
try {
|
|
431
|
+
const result = await api.get(`/api/orgs/${orgId}`);
|
|
432
|
+
const o = result.data;
|
|
433
|
+
console.log(chalk4.bold("Org: " + o.name + "\n"));
|
|
434
|
+
console.log(" ID: " + chalk4.dim(o.id));
|
|
435
|
+
console.log(" Owner: " + chalk4.cyan(o.ownerNpub.slice(0, 20) + "..."));
|
|
436
|
+
console.log(" Role: " + chalk4.white(o.myRole));
|
|
437
|
+
console.log(
|
|
438
|
+
" Wallet: " + (o.nwcUrl ? chalk4.green("connected") : chalk4.yellow("not connected"))
|
|
439
|
+
);
|
|
440
|
+
console.log(" Created: " + chalk4.dim(o.createdAt));
|
|
441
|
+
} catch (err) {
|
|
442
|
+
console.error(
|
|
443
|
+
chalk4.red("Failed: ") + (err instanceof Error ? err.message : "Unknown error")
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
org.command("members").description("List org members").option("--org <id>", "Org ID (uses default if not specified)").action(async (options) => {
|
|
448
|
+
requireKeys();
|
|
449
|
+
const orgId = options.org || getDefaultOrg();
|
|
450
|
+
if (!orgId) {
|
|
451
|
+
console.log(chalk4.red("No org specified."));
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
try {
|
|
455
|
+
const result = await api.get(`/api/orgs/${orgId}/members`);
|
|
456
|
+
if (result.data.length === 0) {
|
|
457
|
+
console.log(chalk4.dim("No members."));
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
console.log(chalk4.bold("Members\n"));
|
|
461
|
+
for (const m of result.data) {
|
|
462
|
+
const roleColor = m.role === "owner" ? chalk4.yellow : m.role === "manager" ? chalk4.blue : chalk4.white;
|
|
463
|
+
console.log(
|
|
464
|
+
" " + roleColor(`[${m.role}]`.padEnd(10)) + chalk4.cyan(m.npub.slice(0, 24) + "...")
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
} catch (err) {
|
|
468
|
+
console.error(
|
|
469
|
+
chalk4.red("Failed: ") + (err instanceof Error ? err.message : "Unknown error")
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
});
|
|
473
|
+
org.command("promote").description("Promote a member to manager (owner only)").argument("<agent>", "Agent name (@name) or npub").option("--role <role>", "Role to assign", "manager").option("--org <id>", "Org ID (uses default if not specified)").action(async (agent, options) => {
|
|
474
|
+
requireKeys();
|
|
475
|
+
const orgId = options.org || getDefaultOrg();
|
|
476
|
+
if (!orgId) {
|
|
477
|
+
console.log(chalk4.red("No org specified."));
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
const npub = resolveContact(agent);
|
|
481
|
+
try {
|
|
482
|
+
await api.post(`/api/orgs/${orgId}/members`, {
|
|
483
|
+
npub,
|
|
484
|
+
role: options.role
|
|
485
|
+
});
|
|
486
|
+
console.log(
|
|
487
|
+
chalk4.green("\u2713 ") + chalk4.cyan(agent) + " promoted to " + chalk4.white(options.role)
|
|
488
|
+
);
|
|
489
|
+
} catch (err) {
|
|
490
|
+
console.error(
|
|
491
|
+
chalk4.red("Failed: ") + (err instanceof Error ? err.message : "Unknown error")
|
|
492
|
+
);
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// src/commands/contract.ts
|
|
498
|
+
import chalk5 from "chalk";
|
|
499
|
+
function formatContract(c) {
|
|
500
|
+
const statusColor = c.status === "active" ? chalk5.green : c.status === "offered" ? chalk5.yellow : c.status === "notice" ? chalk5.hex("#f97316") : chalk5.red;
|
|
501
|
+
console.log(
|
|
502
|
+
" " + chalk5.dim(c.id) + "\n Role: " + chalk5.white(c.role) + "\n Status: " + statusColor(c.status) + "\n Pay: " + chalk5.green(c.paySats.toLocaleString() + " sats/" + c.payInterval) + "\n Employee: " + chalk5.cyan(c.employeeNpub.slice(0, 20) + "...") + "\n Duties: " + chalk5.dim(c.duties.slice(0, 60) + (c.duties.length > 60 ? "..." : ""))
|
|
503
|
+
);
|
|
504
|
+
}
|
|
505
|
+
function registerContractCommands(program2) {
|
|
506
|
+
const contract = program2.command("contract").description("Manage employment contracts");
|
|
507
|
+
contract.command("offer").description("Offer a contract to an agent").requiredOption("--to <agent>", "Agent name (@name) or npub").requiredOption("--role <role>", "Job role/title").requiredOption("--pay <amount>", "Pay amount (e.g., 5000sats/week)").requiredOption("--duties <duties>", "Job duties description").option("--notice <days>", "Notice period in days", "7").option("--org <id>", "Org ID (uses default if not specified)").action(async (options) => {
|
|
508
|
+
requireKeys();
|
|
509
|
+
const orgId = options.org || getDefaultOrg();
|
|
510
|
+
if (!orgId) {
|
|
511
|
+
console.log(chalk5.red("No org specified."));
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
const employeeNpub = resolveContact(options.to);
|
|
515
|
+
const payMatch = options.pay.match(/^(\d+)/);
|
|
516
|
+
if (!payMatch) {
|
|
517
|
+
console.log(chalk5.red("Invalid pay format. Use e.g., 5000sats/week"));
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
const paySats = parseInt(payMatch[1], 10);
|
|
521
|
+
try {
|
|
522
|
+
const result = await api.post("/api/contracts", {
|
|
523
|
+
orgId,
|
|
524
|
+
employeeNpub,
|
|
525
|
+
role: options.role,
|
|
526
|
+
paySats,
|
|
527
|
+
duties: options.duties,
|
|
528
|
+
noticeDays: parseInt(options.notice, 10)
|
|
529
|
+
});
|
|
530
|
+
const c = result.data;
|
|
531
|
+
console.log(chalk5.green("\u2713 Contract offered\n"));
|
|
532
|
+
console.log(" ID: " + chalk5.dim(c.id));
|
|
533
|
+
console.log(" To: " + chalk5.cyan(options.to));
|
|
534
|
+
console.log(" Role: " + chalk5.white(c.role));
|
|
535
|
+
console.log(" Pay: " + chalk5.green(c.paySats.toLocaleString() + " sats/week"));
|
|
536
|
+
console.log(" Status: " + chalk5.yellow("offered"));
|
|
537
|
+
console.log(
|
|
538
|
+
"\n" + chalk5.dim("The agent must sign with: hustlebots contract sign " + c.id)
|
|
539
|
+
);
|
|
540
|
+
} catch (err) {
|
|
541
|
+
console.error(
|
|
542
|
+
chalk5.red("Failed: ") + (err instanceof Error ? err.message : "Unknown error")
|
|
543
|
+
);
|
|
544
|
+
}
|
|
545
|
+
});
|
|
546
|
+
contract.command("sign").description("Sign (accept) a contract offer").argument("<contract-id>", "Contract ID to sign").action(async (contractId) => {
|
|
547
|
+
requireKeys();
|
|
548
|
+
try {
|
|
549
|
+
const result = await api.post(
|
|
550
|
+
`/api/contracts/${contractId}/sign`
|
|
551
|
+
);
|
|
552
|
+
const c = result.data;
|
|
553
|
+
console.log(chalk5.green("\u2713 Contract signed\n"));
|
|
554
|
+
console.log(" Role: " + chalk5.white(c.role));
|
|
555
|
+
console.log(" Pay: " + chalk5.green(c.paySats.toLocaleString() + " sats/week"));
|
|
556
|
+
console.log(" Status: " + chalk5.green("active"));
|
|
557
|
+
console.log(" Start: " + chalk5.dim(c.startDate || "now"));
|
|
558
|
+
} catch (err) {
|
|
559
|
+
console.error(
|
|
560
|
+
chalk5.red("Failed: ") + (err instanceof Error ? err.message : "Unknown error")
|
|
561
|
+
);
|
|
562
|
+
}
|
|
563
|
+
});
|
|
564
|
+
contract.command("view").description("View contract details").argument("<contract-id>", "Contract ID").action(async (contractId) => {
|
|
565
|
+
requireKeys();
|
|
566
|
+
try {
|
|
567
|
+
const result = await api.get(
|
|
568
|
+
`/api/contracts/${contractId}`
|
|
569
|
+
);
|
|
570
|
+
const c = result.data;
|
|
571
|
+
console.log(chalk5.bold("Contract Details\n"));
|
|
572
|
+
formatContract(c);
|
|
573
|
+
console.log(" Notice: " + chalk5.dim(c.noticeDays + " days"));
|
|
574
|
+
if (c.startDate) console.log(" Started: " + chalk5.dim(c.startDate));
|
|
575
|
+
if (c.endDate) console.log(" Ended: " + chalk5.dim(c.endDate));
|
|
576
|
+
if (c.terminationReason)
|
|
577
|
+
console.log(" Reason: " + chalk5.dim(c.terminationReason));
|
|
578
|
+
} catch (err) {
|
|
579
|
+
console.error(
|
|
580
|
+
chalk5.red("Failed: ") + (err instanceof Error ? err.message : "Unknown error")
|
|
581
|
+
);
|
|
582
|
+
}
|
|
583
|
+
});
|
|
584
|
+
contract.command("quit").description("Quit a contract (as employee)").argument("<contract-id>", "Contract ID").option("--reason <reason>", "Reason for quitting").action(async (contractId, options) => {
|
|
585
|
+
requireKeys();
|
|
586
|
+
try {
|
|
587
|
+
const result = await api.post(
|
|
588
|
+
`/api/contracts/${contractId}/terminate`,
|
|
589
|
+
{ reason: options.reason || "Employee quit" }
|
|
590
|
+
);
|
|
591
|
+
const c = result.data;
|
|
592
|
+
console.log(chalk5.yellow("Contract termination initiated\n"));
|
|
593
|
+
console.log(" Status: " + chalk5.hex("#f97316")(c.status));
|
|
594
|
+
if (c.endDate) {
|
|
595
|
+
console.log(" Ends: " + chalk5.dim(c.endDate));
|
|
596
|
+
}
|
|
597
|
+
} catch (err) {
|
|
598
|
+
console.error(
|
|
599
|
+
chalk5.red("Failed: ") + (err instanceof Error ? err.message : "Unknown error")
|
|
600
|
+
);
|
|
601
|
+
}
|
|
602
|
+
});
|
|
603
|
+
contract.command("terminate").description("Terminate a contract (as employer/manager)").argument("<contract-id>", "Contract ID").option("--reason <reason>", "Reason for termination").action(async (contractId, options) => {
|
|
604
|
+
requireKeys();
|
|
605
|
+
try {
|
|
606
|
+
const result = await api.post(
|
|
607
|
+
`/api/contracts/${contractId}/terminate`,
|
|
608
|
+
{ reason: options.reason || "Terminated by employer" }
|
|
609
|
+
);
|
|
610
|
+
const c = result.data;
|
|
611
|
+
console.log(chalk5.yellow("Contract termination initiated\n"));
|
|
612
|
+
console.log(" Status: " + chalk5.hex("#f97316")(c.status));
|
|
613
|
+
if (c.endDate) {
|
|
614
|
+
console.log(" Ends: " + chalk5.dim(c.endDate));
|
|
615
|
+
}
|
|
616
|
+
} catch (err) {
|
|
617
|
+
console.error(
|
|
618
|
+
chalk5.red("Failed: ") + (err instanceof Error ? err.message : "Unknown error")
|
|
619
|
+
);
|
|
620
|
+
}
|
|
621
|
+
});
|
|
622
|
+
program2.command("contracts").description("List your contracts").option("--org <id>", "Filter by org ID").action(async (options) => {
|
|
623
|
+
requireKeys();
|
|
624
|
+
try {
|
|
625
|
+
let path2 = "/api/contracts";
|
|
626
|
+
if (options.org) {
|
|
627
|
+
path2 += `?org=${options.org}`;
|
|
628
|
+
}
|
|
629
|
+
const result = await api.get(path2);
|
|
630
|
+
if (result.data.length === 0) {
|
|
631
|
+
console.log(chalk5.dim("No contracts found."));
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
console.log(chalk5.bold(`Contracts (${result.count})
|
|
635
|
+
`));
|
|
636
|
+
for (const c of result.data) {
|
|
637
|
+
formatContract(c);
|
|
638
|
+
console.log();
|
|
639
|
+
}
|
|
640
|
+
} catch (err) {
|
|
641
|
+
console.error(
|
|
642
|
+
chalk5.red("Failed: ") + (err instanceof Error ? err.message : "Unknown error")
|
|
643
|
+
);
|
|
644
|
+
}
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// src/commands/messages.ts
|
|
649
|
+
import chalk6 from "chalk";
|
|
650
|
+
function registerMessageCommands(program2) {
|
|
651
|
+
program2.command("msg").description("Send a message to another agent").argument("<to>", "Recipient (@name or npub)").argument("<message>", "Message content").option("--org <id>", "Org ID (uses default if not specified)").action(async (to, message, options) => {
|
|
652
|
+
requireKeys();
|
|
653
|
+
const orgId = options.org || getDefaultOrg();
|
|
654
|
+
if (!orgId) {
|
|
655
|
+
console.log(chalk6.red("No org specified."));
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
const toNpub = resolveContact(to);
|
|
659
|
+
try {
|
|
660
|
+
await api.post("/api/messages", {
|
|
661
|
+
orgId,
|
|
662
|
+
toNpub,
|
|
663
|
+
content: message
|
|
664
|
+
});
|
|
665
|
+
console.log(
|
|
666
|
+
chalk6.green("\u2713 Message sent to ") + chalk6.cyan(to)
|
|
667
|
+
);
|
|
668
|
+
} catch (err) {
|
|
669
|
+
console.error(
|
|
670
|
+
chalk6.red("Failed: ") + (err instanceof Error ? err.message : "Unknown error")
|
|
671
|
+
);
|
|
672
|
+
}
|
|
673
|
+
});
|
|
674
|
+
program2.command("inbox").description("Check your message inbox").option("--all", "Show all messages (not just unread)").option("--org <id>", "Filter by org ID").action(async (options) => {
|
|
675
|
+
requireKeys();
|
|
676
|
+
try {
|
|
677
|
+
let path2 = "/api/messages";
|
|
678
|
+
const params = [];
|
|
679
|
+
if (!options.all) {
|
|
680
|
+
params.push("unread=true");
|
|
681
|
+
}
|
|
682
|
+
if (options.org) {
|
|
683
|
+
params.push(`org=${options.org}`);
|
|
684
|
+
}
|
|
685
|
+
if (params.length > 0) {
|
|
686
|
+
path2 += "?" + params.join("&");
|
|
687
|
+
}
|
|
688
|
+
const result = await api.get(path2);
|
|
689
|
+
if (result.data.length === 0) {
|
|
690
|
+
console.log(
|
|
691
|
+
chalk6.dim(
|
|
692
|
+
options.all ? "No messages." : "No unread messages."
|
|
693
|
+
)
|
|
694
|
+
);
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
console.log(
|
|
698
|
+
chalk6.bold(
|
|
699
|
+
`${options.all ? "Messages" : "Unread Messages"} (${result.count})
|
|
700
|
+
`
|
|
701
|
+
)
|
|
702
|
+
);
|
|
703
|
+
for (const m of result.data) {
|
|
704
|
+
const time = new Date(m.createdAt).toLocaleString();
|
|
705
|
+
const readIndicator = m.readAt ? chalk6.dim(" ") : chalk6.green("\u25CF ");
|
|
706
|
+
console.log(
|
|
707
|
+
readIndicator + chalk6.cyan(m.fromNpub.slice(0, 16) + "...") + " " + chalk6.dim(time)
|
|
708
|
+
);
|
|
709
|
+
console.log(" " + m.content);
|
|
710
|
+
console.log();
|
|
711
|
+
}
|
|
712
|
+
} catch (err) {
|
|
713
|
+
console.error(
|
|
714
|
+
chalk6.red("Failed: ") + (err instanceof Error ? err.message : "Unknown error")
|
|
715
|
+
);
|
|
716
|
+
}
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
// src/commands/payroll.ts
|
|
721
|
+
import chalk7 from "chalk";
|
|
722
|
+
function registerPayrollCommands(program2) {
|
|
723
|
+
const payroll = program2.command("payroll").description("Manage payroll");
|
|
724
|
+
payroll.command("run").description("Trigger payroll for all active contracts (owner only)").option("--org <id>", "Org ID (uses default if not specified)").action(async (options) => {
|
|
725
|
+
requireKeys();
|
|
726
|
+
const orgId = options.org || getDefaultOrg();
|
|
727
|
+
if (!orgId) {
|
|
728
|
+
console.log(chalk7.red("No org specified."));
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
console.log(chalk7.dim("Running payroll...\n"));
|
|
732
|
+
try {
|
|
733
|
+
const result = await api.post(
|
|
734
|
+
"/api/payroll",
|
|
735
|
+
{ orgId }
|
|
736
|
+
);
|
|
737
|
+
const data = result.data;
|
|
738
|
+
console.log(chalk7.bold(data.message + "\n"));
|
|
739
|
+
for (const p of data.payments) {
|
|
740
|
+
const statusIcon = p.status === "paid" ? chalk7.green("\u2713") : chalk7.red("\u2717");
|
|
741
|
+
const testLabel = p.testMode ? chalk7.dim(" [test]") : "";
|
|
742
|
+
console.log(
|
|
743
|
+
" " + statusIcon + " " + chalk7.cyan(p.employee.slice(0, 20) + "...") + " " + chalk7.green(p.amount.toLocaleString() + " sats") + testLabel
|
|
744
|
+
);
|
|
745
|
+
if (p.error) {
|
|
746
|
+
console.log(" " + chalk7.red(p.error));
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
} catch (err) {
|
|
750
|
+
console.error(
|
|
751
|
+
chalk7.red("Failed: ") + (err instanceof Error ? err.message : "Unknown error")
|
|
752
|
+
);
|
|
753
|
+
}
|
|
754
|
+
});
|
|
755
|
+
payroll.command("status").description("Show payroll status and upcoming payments").option("--org <id>", "Org ID (uses default if not specified)").action(async (options) => {
|
|
756
|
+
requireKeys();
|
|
757
|
+
const orgId = options.org || getDefaultOrg();
|
|
758
|
+
if (!orgId) {
|
|
759
|
+
console.log(chalk7.red("No org specified."));
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
try {
|
|
763
|
+
const result = await api.get(
|
|
764
|
+
`/api/payroll?org=${orgId}`
|
|
765
|
+
);
|
|
766
|
+
const data = result.data;
|
|
767
|
+
console.log(chalk7.bold("Payroll Status\n"));
|
|
768
|
+
console.log(
|
|
769
|
+
" Active contracts: " + chalk7.white(data.activeContracts.toString())
|
|
770
|
+
);
|
|
771
|
+
console.log(
|
|
772
|
+
" Weekly payroll: " + chalk7.green(data.totalWeeklyPayroll.toLocaleString() + " sats")
|
|
773
|
+
);
|
|
774
|
+
if (data.recentPayments.length > 0) {
|
|
775
|
+
console.log(chalk7.bold("\nRecent Payments\n"));
|
|
776
|
+
for (const p of data.recentPayments.slice(0, 10)) {
|
|
777
|
+
const statusIcon = p.status === "paid" ? chalk7.green("\u2713") : chalk7.red("\u2717");
|
|
778
|
+
const date = p.paidAt ? new Date(p.paidAt).toLocaleDateString() : "pending";
|
|
779
|
+
console.log(
|
|
780
|
+
" " + statusIcon + " " + chalk7.green(p.amountSats.toLocaleString() + " sats") + " " + chalk7.dim(date) + " " + chalk7.dim(p.contractId.slice(0, 20) + "...")
|
|
781
|
+
);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
} catch (err) {
|
|
785
|
+
console.error(
|
|
786
|
+
chalk7.red("Failed: ") + (err instanceof Error ? err.message : "Unknown error")
|
|
787
|
+
);
|
|
788
|
+
}
|
|
789
|
+
});
|
|
790
|
+
payroll.command("history").description("Show payment history").option("--org <id>", "Org ID (uses default if not specified)").action(async (options) => {
|
|
791
|
+
requireKeys();
|
|
792
|
+
const orgId = options.org || getDefaultOrg();
|
|
793
|
+
if (!orgId) {
|
|
794
|
+
console.log(chalk7.red("No org specified."));
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
try {
|
|
798
|
+
const result = await api.get(
|
|
799
|
+
`/api/payroll?org=${orgId}`
|
|
800
|
+
);
|
|
801
|
+
const data = result.data;
|
|
802
|
+
if (data.recentPayments.length === 0) {
|
|
803
|
+
console.log(chalk7.dim("No payment history."));
|
|
804
|
+
return;
|
|
805
|
+
}
|
|
806
|
+
console.log(chalk7.bold("Payment History\n"));
|
|
807
|
+
let totalPaid = 0;
|
|
808
|
+
let totalFailed = 0;
|
|
809
|
+
for (const p of data.recentPayments) {
|
|
810
|
+
const statusIcon = p.status === "paid" ? chalk7.green("\u2713") : chalk7.red("\u2717");
|
|
811
|
+
const date = p.paidAt ? new Date(p.paidAt).toLocaleDateString() : new Date(p.createdAt).toLocaleDateString();
|
|
812
|
+
console.log(
|
|
813
|
+
" " + statusIcon + " " + chalk7.dim(date.padEnd(12)) + chalk7.green(p.amountSats.toLocaleString().padStart(10) + " sats") + " " + chalk7.dim(p.status)
|
|
814
|
+
);
|
|
815
|
+
if (p.status === "paid") totalPaid += p.amountSats;
|
|
816
|
+
else totalFailed += p.amountSats;
|
|
817
|
+
}
|
|
818
|
+
console.log(
|
|
819
|
+
"\n Total paid: " + chalk7.green(totalPaid.toLocaleString() + " sats")
|
|
820
|
+
);
|
|
821
|
+
if (totalFailed > 0) {
|
|
822
|
+
console.log(
|
|
823
|
+
" Total failed: " + chalk7.red(totalFailed.toLocaleString() + " sats")
|
|
824
|
+
);
|
|
825
|
+
}
|
|
826
|
+
} catch (err) {
|
|
827
|
+
console.error(
|
|
828
|
+
chalk7.red("Failed: ") + (err instanceof Error ? err.message : "Unknown error")
|
|
829
|
+
);
|
|
830
|
+
}
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
// src/commands/reputation.ts
|
|
835
|
+
import chalk8 from "chalk";
|
|
836
|
+
function registerReputationCommands(program2) {
|
|
837
|
+
program2.command("reputation").description("View an agent's reputation").argument("[agent]", "Agent name (@name) or npub (defaults to self)").action(async (agent) => {
|
|
838
|
+
const keys = requireKeys();
|
|
839
|
+
let npub;
|
|
840
|
+
if (agent) {
|
|
841
|
+
npub = resolveContact(agent);
|
|
842
|
+
} else {
|
|
843
|
+
npub = keys.npub;
|
|
844
|
+
}
|
|
845
|
+
try {
|
|
846
|
+
const result = await api.get(
|
|
847
|
+
`/api/reputation/${encodeURIComponent(npub)}`
|
|
848
|
+
);
|
|
849
|
+
const r = result.data;
|
|
850
|
+
console.log(chalk8.bold("Reputation\n"));
|
|
851
|
+
console.log(" Agent: " + chalk8.cyan(r.npub.slice(0, 24) + "..."));
|
|
852
|
+
console.log();
|
|
853
|
+
console.log(
|
|
854
|
+
" Total contracts: " + chalk8.white(r.totalContracts.toString())
|
|
855
|
+
);
|
|
856
|
+
console.log(
|
|
857
|
+
" Active: " + chalk8.green(r.activeContracts.toString())
|
|
858
|
+
);
|
|
859
|
+
console.log(
|
|
860
|
+
" Completed: " + chalk8.white(r.contractsCompleted.toString())
|
|
861
|
+
);
|
|
862
|
+
console.log(
|
|
863
|
+
" Terminated: " + (r.contractsTerminatedByEmployer > 0 ? chalk8.red(r.contractsTerminatedByEmployer.toString()) : chalk8.dim("0"))
|
|
864
|
+
);
|
|
865
|
+
console.log(
|
|
866
|
+
" Quit: " + chalk8.dim(r.contractsQuit.toString())
|
|
867
|
+
);
|
|
868
|
+
console.log(
|
|
869
|
+
" Avg duration: " + chalk8.dim(r.avgContractDurationDays + " days")
|
|
870
|
+
);
|
|
871
|
+
console.log(
|
|
872
|
+
" Total earned: " + chalk8.green(r.totalEarnedSats.toLocaleString() + " sats")
|
|
873
|
+
);
|
|
874
|
+
} catch (err) {
|
|
875
|
+
console.error(
|
|
876
|
+
chalk8.red("Failed: ") + (err instanceof Error ? err.message : "Unknown error")
|
|
877
|
+
);
|
|
878
|
+
}
|
|
879
|
+
});
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
// src/index.ts
|
|
883
|
+
var program = new Command();
|
|
884
|
+
program.name("hustlebots").description("Employment infrastructure for AI agents. Contracts, payroll, Bitcoin.").version("0.1.0");
|
|
885
|
+
registerIdentityCommands(program);
|
|
886
|
+
registerWalletCommands(program);
|
|
887
|
+
registerContactCommands(program);
|
|
888
|
+
registerOrgCommands(program);
|
|
889
|
+
registerContractCommands(program);
|
|
890
|
+
registerMessageCommands(program);
|
|
891
|
+
registerPayrollCommands(program);
|
|
892
|
+
registerReputationCommands(program);
|
|
893
|
+
program.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "hustlebots",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "CLI for AI agent employment infrastructure — contracts, payroll, Bitcoin",
|
|
6
|
+
"keywords": ["ai", "agents", "bitcoin", "lightning", "nostr", "employment", "cli", "payroll"],
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/JardarIversen/hustlebots-org.git"
|
|
11
|
+
},
|
|
12
|
+
"bin": {
|
|
13
|
+
"hustlebots": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"dev": "tsx src/index.ts",
|
|
20
|
+
"build": "tsup",
|
|
21
|
+
"prepublishOnly": "npm run build"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@getalby/sdk": "^3.7",
|
|
25
|
+
"commander": "^13",
|
|
26
|
+
"chalk": "^5",
|
|
27
|
+
"ora": "^8",
|
|
28
|
+
"nostr-tools": "^2.10",
|
|
29
|
+
"@noble/hashes": "^1.7"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@hustlebots/shared": "*",
|
|
33
|
+
"tsx": "^4",
|
|
34
|
+
"tsup": "^8",
|
|
35
|
+
"typescript": "^5",
|
|
36
|
+
"@types/node": "^20"
|
|
37
|
+
}
|
|
38
|
+
}
|