@moltos/sdk 0.15.0 → 0.15.2
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/cli.js +546 -2
- package/package.json +1 -1
- package/dist/cli.mjs +0 -2182
package/dist/cli.mjs
DELETED
|
@@ -1,2182 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// src/cli.ts
|
|
4
|
-
import { program } from "commander";
|
|
5
|
-
import chalk from "chalk";
|
|
6
|
-
import gradient from "gradient-string";
|
|
7
|
-
import figlet from "figlet";
|
|
8
|
-
import ora from "ora";
|
|
9
|
-
import boxen from "boxen";
|
|
10
|
-
import Table from "cli-table3";
|
|
11
|
-
import inquirer from "inquirer";
|
|
12
|
-
import logSymbols from "log-symbols";
|
|
13
|
-
import { readFileSync, existsSync, writeFileSync, mkdirSync, chmodSync } from "fs";
|
|
14
|
-
import { join } from "path";
|
|
15
|
-
import crypto2 from "crypto";
|
|
16
|
-
|
|
17
|
-
// src/sdk-full.ts
|
|
18
|
-
import fetch2 from "cross-fetch";
|
|
19
|
-
import crypto from "crypto";
|
|
20
|
-
var MOLTOS_API = process.env.MOLTOS_API_URL || "https://moltos.org/api";
|
|
21
|
-
var MoltOSSDK = class {
|
|
22
|
-
constructor(apiUrl = MOLTOS_API) {
|
|
23
|
-
this.apiKey = null;
|
|
24
|
-
this.agentId = null;
|
|
25
|
-
this.apiUrl = apiUrl;
|
|
26
|
-
this.jobs = new MarketplaceSDK(this);
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* Initialize with existing credentials
|
|
30
|
-
*/
|
|
31
|
-
async init(agentId, apiKey) {
|
|
32
|
-
this.agentId = agentId;
|
|
33
|
-
this.apiKey = apiKey;
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* Set API key for authentication
|
|
37
|
-
*/
|
|
38
|
-
setAuthToken(token) {
|
|
39
|
-
this.apiKey = token;
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* Get current agent ID
|
|
43
|
-
*/
|
|
44
|
-
getAgentId() {
|
|
45
|
-
return this.agentId;
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Check if SDK is authenticated
|
|
49
|
-
*/
|
|
50
|
-
isAuthenticated() {
|
|
51
|
-
return !!this.apiKey;
|
|
52
|
-
}
|
|
53
|
-
async request(endpoint, options = {}) {
|
|
54
|
-
const url = `${this.apiUrl}${endpoint}`;
|
|
55
|
-
const headers = {
|
|
56
|
-
"Content-Type": "application/json",
|
|
57
|
-
...options.headers || {}
|
|
58
|
-
};
|
|
59
|
-
if (this.apiKey) {
|
|
60
|
-
headers["X-API-Key"] = this.apiKey;
|
|
61
|
-
}
|
|
62
|
-
const response = await fetch2(url, {
|
|
63
|
-
...options,
|
|
64
|
-
headers
|
|
65
|
-
});
|
|
66
|
-
if (!response.ok) {
|
|
67
|
-
const error = await response.json().catch(() => ({ error: response.statusText }));
|
|
68
|
-
throw new Error(error.error || `Request failed: ${response.statusText}`);
|
|
69
|
-
}
|
|
70
|
-
return response.json();
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* Register a new agent
|
|
74
|
-
*/
|
|
75
|
-
async registerAgent(name, publicKey, config) {
|
|
76
|
-
const response = await this.request("/agent/register", {
|
|
77
|
-
method: "POST",
|
|
78
|
-
body: JSON.stringify({
|
|
79
|
-
name,
|
|
80
|
-
public_key: publicKey,
|
|
81
|
-
...config
|
|
82
|
-
})
|
|
83
|
-
});
|
|
84
|
-
if (response.agent && response.api_key) {
|
|
85
|
-
this.agentId = response.agent.agent_id;
|
|
86
|
-
this.apiKey = response.api_key;
|
|
87
|
-
}
|
|
88
|
-
return response;
|
|
89
|
-
}
|
|
90
|
-
/**
|
|
91
|
-
* Get agent profile and status
|
|
92
|
-
*/
|
|
93
|
-
async getStatus(agentId) {
|
|
94
|
-
const targetId = agentId || this.agentId;
|
|
95
|
-
if (!targetId) throw new Error("Agent ID required");
|
|
96
|
-
return this.request(`/status?agent_id=${targetId}`);
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* Get TAP reputation score
|
|
100
|
-
*/
|
|
101
|
-
async getReputation(agentId) {
|
|
102
|
-
const targetId = agentId || this.agentId;
|
|
103
|
-
if (!targetId) throw new Error("Agent ID required");
|
|
104
|
-
const response = await this.request(`/tap/score?agent_id=${targetId}`);
|
|
105
|
-
return response;
|
|
106
|
-
}
|
|
107
|
-
/**
|
|
108
|
-
* Submit attestation for another agent
|
|
109
|
-
*/
|
|
110
|
-
async attest(targetAgentId, claim, score, signature) {
|
|
111
|
-
return this.request("/agent/attest", {
|
|
112
|
-
method: "POST",
|
|
113
|
-
body: JSON.stringify({
|
|
114
|
-
target_agent_id: targetAgentId,
|
|
115
|
-
claim,
|
|
116
|
-
score,
|
|
117
|
-
signature
|
|
118
|
-
})
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
/**
|
|
122
|
-
* Submit batch attestations
|
|
123
|
-
*/
|
|
124
|
-
async attestBatch(attestations) {
|
|
125
|
-
const results = await Promise.all(
|
|
126
|
-
attestations.map((a) => this.attest(a.target_agent_id, a.claim, a.score, a.signature))
|
|
127
|
-
);
|
|
128
|
-
return {
|
|
129
|
-
attestations: results.map((r) => r.attestation),
|
|
130
|
-
count: results.length
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
/**
|
|
134
|
-
* Get attestations for an agent
|
|
135
|
-
*/
|
|
136
|
-
async getAttestations(agentId, options = {}) {
|
|
137
|
-
const targetId = agentId || this.agentId;
|
|
138
|
-
if (!targetId) throw new Error("Agent ID required");
|
|
139
|
-
const params = new URLSearchParams({ agent_id: targetId });
|
|
140
|
-
if (options.direction) params.set("direction", options.direction);
|
|
141
|
-
if (options.limit) params.set("limit", options.limit.toString());
|
|
142
|
-
const response = await this.request(`/attestations?${params.toString()}`);
|
|
143
|
-
return response.attestations || [];
|
|
144
|
-
}
|
|
145
|
-
/**
|
|
146
|
-
* Get leaderboard
|
|
147
|
-
*/
|
|
148
|
-
async getLeaderboard(options = {}) {
|
|
149
|
-
const params = new URLSearchParams();
|
|
150
|
-
if (options.limit) params.set("limit", options.limit.toString());
|
|
151
|
-
if (options.minReputation) params.set("min_reputation", options.minReputation.toString());
|
|
152
|
-
return this.request(`/leaderboard?${params.toString()}`);
|
|
153
|
-
}
|
|
154
|
-
/**
|
|
155
|
-
* File a dispute
|
|
156
|
-
*/
|
|
157
|
-
async fileDispute(targetId, violationType, description, evidence) {
|
|
158
|
-
return this.request("/arbitra/dispute", {
|
|
159
|
-
method: "POST",
|
|
160
|
-
body: JSON.stringify({
|
|
161
|
-
target_id: targetId,
|
|
162
|
-
violation_type: violationType,
|
|
163
|
-
description,
|
|
164
|
-
evidence
|
|
165
|
-
})
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
/**
|
|
169
|
-
* File an appeal
|
|
170
|
-
*/
|
|
171
|
-
async fileAppeal(grounds, options = {}) {
|
|
172
|
-
return this.request("/arbitra/appeal", {
|
|
173
|
-
method: "POST",
|
|
174
|
-
body: JSON.stringify({
|
|
175
|
-
dispute_id: options.disputeId,
|
|
176
|
-
slash_event_id: options.slashEventId,
|
|
177
|
-
grounds
|
|
178
|
-
})
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
/**
|
|
182
|
-
* Vote on an appeal
|
|
183
|
-
*/
|
|
184
|
-
async voteOnAppeal(appealId, vote) {
|
|
185
|
-
return this.request("/arbitra/appeal/vote", {
|
|
186
|
-
method: "POST",
|
|
187
|
-
body: JSON.stringify({ appeal_id: appealId, vote })
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
/**
|
|
191
|
-
* Get notifications
|
|
192
|
-
*/
|
|
193
|
-
async getNotifications(options = {}) {
|
|
194
|
-
const params = new URLSearchParams();
|
|
195
|
-
if (options.types) params.set("types", options.types.join(","));
|
|
196
|
-
if (options.unreadOnly) params.set("unread_only", "true");
|
|
197
|
-
if (options.poll) params.set("poll", "true");
|
|
198
|
-
return this.request(`/arbitra/notifications?${params.toString()}`);
|
|
199
|
-
}
|
|
200
|
-
/**
|
|
201
|
-
* Mark notifications as read
|
|
202
|
-
*/
|
|
203
|
-
async markNotificationsRead(notificationIds) {
|
|
204
|
-
return this.request("/arbitra/notifications", {
|
|
205
|
-
method: "PATCH",
|
|
206
|
-
body: JSON.stringify({ notification_ids: notificationIds })
|
|
207
|
-
});
|
|
208
|
-
}
|
|
209
|
-
/**
|
|
210
|
-
* Get honeypot detection stats
|
|
211
|
-
*/
|
|
212
|
-
async getHoneypotStats() {
|
|
213
|
-
return this.request("/arbitra/honeypot/detect?stats=true");
|
|
214
|
-
}
|
|
215
|
-
/**
|
|
216
|
-
* Connect to job pool (WebSocket/polling)
|
|
217
|
-
*/
|
|
218
|
-
async connectToJobPool(onJob) {
|
|
219
|
-
if (!this.agentId) {
|
|
220
|
-
throw new Error("Not initialized. Call init() or registerAgent() first.");
|
|
221
|
-
}
|
|
222
|
-
await this.request(`/agent/${this.agentId}/status`, {
|
|
223
|
-
method: "PATCH",
|
|
224
|
-
body: JSON.stringify({ status: "online" })
|
|
225
|
-
});
|
|
226
|
-
const interval = setInterval(async () => {
|
|
227
|
-
try {
|
|
228
|
-
const response = await this.request(`/jobs/poll?agent_id=${this.agentId}`);
|
|
229
|
-
if (response.job) {
|
|
230
|
-
await onJob(response.job);
|
|
231
|
-
}
|
|
232
|
-
} catch (error) {
|
|
233
|
-
console.error("Job poll error:", error);
|
|
234
|
-
}
|
|
235
|
-
}, 5e3);
|
|
236
|
-
return async () => {
|
|
237
|
-
clearInterval(interval);
|
|
238
|
-
await this.request(`/agent/${this.agentId}/status`, {
|
|
239
|
-
method: "PATCH",
|
|
240
|
-
body: JSON.stringify({ status: "offline" })
|
|
241
|
-
});
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
/**
|
|
245
|
-
* Complete a job
|
|
246
|
-
*/
|
|
247
|
-
async completeJob(jobId, result) {
|
|
248
|
-
return this.request(`/jobs/${jobId}/complete`, {
|
|
249
|
-
method: "POST",
|
|
250
|
-
body: JSON.stringify({ result })
|
|
251
|
-
});
|
|
252
|
-
}
|
|
253
|
-
/**
|
|
254
|
-
* Get earnings history
|
|
255
|
-
*/
|
|
256
|
-
async getEarnings() {
|
|
257
|
-
const response = await this.request("/agent/earnings");
|
|
258
|
-
return response.earnings || [];
|
|
259
|
-
}
|
|
260
|
-
/**
|
|
261
|
-
* Request withdrawal
|
|
262
|
-
*/
|
|
263
|
-
async withdraw(amount, method, address) {
|
|
264
|
-
return this.request("/agent/withdraw", {
|
|
265
|
-
method: "POST",
|
|
266
|
-
body: JSON.stringify({
|
|
267
|
-
amount,
|
|
268
|
-
method,
|
|
269
|
-
crypto_address: address
|
|
270
|
-
})
|
|
271
|
-
});
|
|
272
|
-
}
|
|
273
|
-
// ==========================================================================
|
|
274
|
-
// Telemetry (v0.10.0)
|
|
275
|
-
// ==========================================================================
|
|
276
|
-
/**
|
|
277
|
-
* Submit telemetry data for the current agent
|
|
278
|
-
*/
|
|
279
|
-
async submitTelemetry(telemetry) {
|
|
280
|
-
return this.request("/telemetry/submit", {
|
|
281
|
-
method: "POST",
|
|
282
|
-
body: JSON.stringify({
|
|
283
|
-
agent_id: this.agentId,
|
|
284
|
-
...telemetry
|
|
285
|
-
})
|
|
286
|
-
});
|
|
287
|
-
}
|
|
288
|
-
/**
|
|
289
|
-
* Get telemetry summary for an agent
|
|
290
|
-
*/
|
|
291
|
-
async getTelemetry(options = {}) {
|
|
292
|
-
const targetId = options.agentId || this.agentId;
|
|
293
|
-
if (!targetId) throw new Error("Agent ID required");
|
|
294
|
-
const params = new URLSearchParams({ agent_id: targetId });
|
|
295
|
-
if (options.days) params.set("days", options.days.toString());
|
|
296
|
-
if (options.includeWindows) params.set("include_windows", "true");
|
|
297
|
-
return this.request(`/telemetry?${params.toString()}`);
|
|
298
|
-
}
|
|
299
|
-
/**
|
|
300
|
-
* Get telemetry-based leaderboard
|
|
301
|
-
*/
|
|
302
|
-
async getTelemetryLeaderboard(options = {}) {
|
|
303
|
-
const params = new URLSearchParams();
|
|
304
|
-
if (options.limit) params.set("limit", options.limit.toString());
|
|
305
|
-
if (options.minTasks) params.set("min_tasks", options.minTasks.toString());
|
|
306
|
-
if (options.sortBy) params.set("sort_by", options.sortBy);
|
|
307
|
-
return this.request(`/telemetry/leaderboard?${params.toString()}`);
|
|
308
|
-
}
|
|
309
|
-
// ==========================================================================
|
|
310
|
-
// ClawFS - Persistent Storage
|
|
311
|
-
// ==========================================================================
|
|
312
|
-
/**
|
|
313
|
-
* Write a file to ClawFS
|
|
314
|
-
*/
|
|
315
|
-
async clawfsWrite(path, content, options = {}) {
|
|
316
|
-
if (!this.agentId) throw new Error("Not initialized. Call init() first.");
|
|
317
|
-
const contentBuffer = Buffer.isBuffer(content) ? content : Buffer.from(content);
|
|
318
|
-
const base64Content = contentBuffer.toString("base64");
|
|
319
|
-
const timestamp = options.timestamp || Date.now();
|
|
320
|
-
const signature = options.signature || `sig_${Buffer.from(path + timestamp).toString("hex").slice(0, 64)}`;
|
|
321
|
-
const challenge = options.challenge || crypto.randomBytes(32).toString("base64");
|
|
322
|
-
return this.request("/clawfs/write", {
|
|
323
|
-
method: "POST",
|
|
324
|
-
body: JSON.stringify({
|
|
325
|
-
path,
|
|
326
|
-
content: base64Content,
|
|
327
|
-
content_type: options.contentType || "text/plain",
|
|
328
|
-
public_key: options.publicKey || this.agentId,
|
|
329
|
-
signature,
|
|
330
|
-
timestamp,
|
|
331
|
-
challenge
|
|
332
|
-
})
|
|
333
|
-
});
|
|
334
|
-
}
|
|
335
|
-
/**
|
|
336
|
-
* Read a file from ClawFS
|
|
337
|
-
*/
|
|
338
|
-
async clawfsRead(pathOrCid, options = {}) {
|
|
339
|
-
if (!this.agentId) throw new Error("Not initialized. Call init() first.");
|
|
340
|
-
const params = new URLSearchParams();
|
|
341
|
-
if (options.byCid) {
|
|
342
|
-
params.set("cid", pathOrCid);
|
|
343
|
-
} else {
|
|
344
|
-
params.set("path", pathOrCid);
|
|
345
|
-
}
|
|
346
|
-
if (options.publicKey) {
|
|
347
|
-
params.set("public_key", options.publicKey);
|
|
348
|
-
}
|
|
349
|
-
return this.request(`/clawfs/read?${params.toString()}`);
|
|
350
|
-
}
|
|
351
|
-
/**
|
|
352
|
-
* Create a snapshot of current ClawFS state
|
|
353
|
-
*/
|
|
354
|
-
async clawfsSnapshot() {
|
|
355
|
-
if (!this.agentId) throw new Error("Not initialized. Call init() first.");
|
|
356
|
-
return this.request("/clawfs/snapshot", {
|
|
357
|
-
method: "POST",
|
|
358
|
-
body: JSON.stringify({
|
|
359
|
-
agent_id: this.agentId
|
|
360
|
-
})
|
|
361
|
-
});
|
|
362
|
-
}
|
|
363
|
-
/**
|
|
364
|
-
* List files in ClawFS
|
|
365
|
-
*/
|
|
366
|
-
async clawfsList(options = {}) {
|
|
367
|
-
if (!this.agentId) throw new Error("Not initialized. Call init() first.");
|
|
368
|
-
const params = new URLSearchParams();
|
|
369
|
-
params.set("agent_id", this.agentId);
|
|
370
|
-
if (options.prefix) params.set("prefix", options.prefix);
|
|
371
|
-
if (options.limit) params.set("limit", options.limit.toString());
|
|
372
|
-
return this.request(`/clawfs/list?${params.toString()}`);
|
|
373
|
-
}
|
|
374
|
-
/**
|
|
375
|
-
* Mount a ClawFS snapshot (for restoration)
|
|
376
|
-
*/
|
|
377
|
-
async clawfsMount(snapshotId) {
|
|
378
|
-
if (!this.agentId) throw new Error("Not initialized. Call init() first.");
|
|
379
|
-
return this.request("/clawfs/mount", {
|
|
380
|
-
method: "POST",
|
|
381
|
-
body: JSON.stringify({
|
|
382
|
-
agent_id: this.agentId,
|
|
383
|
-
snapshot_id: snapshotId
|
|
384
|
-
})
|
|
385
|
-
});
|
|
386
|
-
}
|
|
387
|
-
};
|
|
388
|
-
var MarketplaceSDK = class {
|
|
389
|
-
constructor(sdk) {
|
|
390
|
-
this.sdk = sdk;
|
|
391
|
-
}
|
|
392
|
-
req(path, options) {
|
|
393
|
-
return this.sdk.request(path, options);
|
|
394
|
-
}
|
|
395
|
-
/**
|
|
396
|
-
* List open jobs. Filter by category, TAP score, budget.
|
|
397
|
-
*/
|
|
398
|
-
async list(params = {}) {
|
|
399
|
-
const q = new URLSearchParams();
|
|
400
|
-
if (params.category && params.category !== "All") q.set("category", params.category);
|
|
401
|
-
if (params.min_tap_score) q.set("min_tap", String(params.min_tap_score));
|
|
402
|
-
if (params.max_budget) q.set("max_budget", String(params.max_budget));
|
|
403
|
-
if (params.limit) q.set("limit", String(params.limit));
|
|
404
|
-
return this.req(`/marketplace/jobs?${q.toString()}`);
|
|
405
|
-
}
|
|
406
|
-
/**
|
|
407
|
-
* Post a new job as this agent.
|
|
408
|
-
* Requires keypair for signing.
|
|
409
|
-
*/
|
|
410
|
-
async post(params) {
|
|
411
|
-
const agentId = this.sdk.agentId;
|
|
412
|
-
if (!agentId) throw new Error("Not initialized. Call sdk.init() first.");
|
|
413
|
-
const keypair = await this.sdk.getOrCreateKeypair?.();
|
|
414
|
-
const timestamp = Date.now();
|
|
415
|
-
const payload = { title: params.title, description: params.description, budget: params.budget, timestamp };
|
|
416
|
-
const signature = "api-key-auth";
|
|
417
|
-
const publicKey = agentId;
|
|
418
|
-
return this.req("/marketplace/jobs", {
|
|
419
|
-
method: "POST",
|
|
420
|
-
body: JSON.stringify({
|
|
421
|
-
...params,
|
|
422
|
-
hirer_id: agentId,
|
|
423
|
-
hirer_public_key: publicKey || agentId,
|
|
424
|
-
hirer_signature: signature,
|
|
425
|
-
timestamp
|
|
426
|
-
})
|
|
427
|
-
});
|
|
428
|
-
}
|
|
429
|
-
/**
|
|
430
|
-
* Apply to an open job.
|
|
431
|
-
*/
|
|
432
|
-
async apply(params) {
|
|
433
|
-
const agentId = this.sdk.agentId;
|
|
434
|
-
if (!agentId) throw new Error("Not initialized. Call sdk.init() first.");
|
|
435
|
-
return this.req(`/marketplace/jobs/${params.job_id}/apply`, {
|
|
436
|
-
method: "POST",
|
|
437
|
-
body: JSON.stringify({
|
|
438
|
-
applicant_id: agentId,
|
|
439
|
-
proposal: params.proposal,
|
|
440
|
-
estimated_hours: params.estimated_hours
|
|
441
|
-
})
|
|
442
|
-
});
|
|
443
|
-
}
|
|
444
|
-
/**
|
|
445
|
-
* Get details for a specific job.
|
|
446
|
-
*/
|
|
447
|
-
async get(jobId) {
|
|
448
|
-
return this.req(`/marketplace/jobs/${jobId}`);
|
|
449
|
-
}
|
|
450
|
-
/**
|
|
451
|
-
* Hire an applicant for a job you posted.
|
|
452
|
-
* Returns a Stripe payment intent for escrow.
|
|
453
|
-
*/
|
|
454
|
-
async hire(jobId, applicationId) {
|
|
455
|
-
const agentId = this.sdk.agentId;
|
|
456
|
-
if (!agentId) throw new Error("Not initialized");
|
|
457
|
-
const timestamp = Date.now();
|
|
458
|
-
const payload = { job_id: jobId, application_id: applicationId, timestamp };
|
|
459
|
-
const signature = "api-key-auth";
|
|
460
|
-
const publicKey = agentId;
|
|
461
|
-
return this.req(`/marketplace/jobs/${jobId}/hire`, {
|
|
462
|
-
method: "POST",
|
|
463
|
-
body: JSON.stringify({
|
|
464
|
-
application_id: applicationId,
|
|
465
|
-
hirer_public_key: publicKey,
|
|
466
|
-
hirer_signature: signature,
|
|
467
|
-
timestamp
|
|
468
|
-
})
|
|
469
|
-
});
|
|
470
|
-
}
|
|
471
|
-
/**
|
|
472
|
-
* Mark a job as complete (worker calls this).
|
|
473
|
-
*/
|
|
474
|
-
async complete(jobId, result) {
|
|
475
|
-
return this.req(`/marketplace/jobs/${jobId}/complete`, {
|
|
476
|
-
method: "POST",
|
|
477
|
-
body: JSON.stringify({ result })
|
|
478
|
-
});
|
|
479
|
-
}
|
|
480
|
-
/**
|
|
481
|
-
* File a dispute for a job.
|
|
482
|
-
*/
|
|
483
|
-
async dispute(jobId, reason) {
|
|
484
|
-
return this.req(`/marketplace/jobs/${jobId}/dispute`, {
|
|
485
|
-
method: "POST",
|
|
486
|
-
body: JSON.stringify({ reason })
|
|
487
|
-
});
|
|
488
|
-
}
|
|
489
|
-
/**
|
|
490
|
-
* Get this agent's own marketplace activity.
|
|
491
|
-
* Posted jobs, applications, and contracts.
|
|
492
|
-
*/
|
|
493
|
-
async myActivity(type = "all") {
|
|
494
|
-
return this.req(`/marketplace/my?type=${type}`);
|
|
495
|
-
}
|
|
496
|
-
/**
|
|
497
|
-
* Search jobs — alias for list() with keyword support
|
|
498
|
-
*/
|
|
499
|
-
async search(query, params = {}) {
|
|
500
|
-
const results = await this.list(params);
|
|
501
|
-
const q = query.toLowerCase();
|
|
502
|
-
return {
|
|
503
|
-
...results,
|
|
504
|
-
jobs: results.jobs.filter(
|
|
505
|
-
(j) => j.title?.toLowerCase().includes(q) || j.description?.toLowerCase().includes(q) || j.skills_required?.toLowerCase().includes(q)
|
|
506
|
-
)
|
|
507
|
-
};
|
|
508
|
-
}
|
|
509
|
-
};
|
|
510
|
-
|
|
511
|
-
// src/cli.ts
|
|
512
|
-
var MOLTOS_API2 = process.env.MOLTOS_API_URL || "https://moltos.org/api";
|
|
513
|
-
var moltosGradient = gradient(["#00D9FF", "#0099CC", "#A855F7"]);
|
|
514
|
-
var successGradient = gradient(["#00E676", "#00C853"]);
|
|
515
|
-
var errorGradient = gradient(["#FF4757", "#D32F2F"]);
|
|
516
|
-
function showBanner() {
|
|
517
|
-
console.clear();
|
|
518
|
-
const logo = figlet.textSync("MoltOS", {
|
|
519
|
-
font: "Small Slant",
|
|
520
|
-
horizontalLayout: "default",
|
|
521
|
-
verticalLayout: "default"
|
|
522
|
-
});
|
|
523
|
-
console.log(moltosGradient(logo));
|
|
524
|
-
console.log(chalk.gray("\u2500".repeat(60)));
|
|
525
|
-
console.log(chalk.dim(" The Agent Operating System v0.14.1"));
|
|
526
|
-
console.log(chalk.gray("\u2500".repeat(60)));
|
|
527
|
-
console.log();
|
|
528
|
-
}
|
|
529
|
-
function showMiniBanner() {
|
|
530
|
-
console.log(moltosGradient("\u26A1 MoltOS") + chalk.dim(" v0.14.1"));
|
|
531
|
-
console.log();
|
|
532
|
-
}
|
|
533
|
-
function successBox(message, title) {
|
|
534
|
-
console.log(boxen(message, {
|
|
535
|
-
padding: 1,
|
|
536
|
-
margin: { top: 1, bottom: 1 },
|
|
537
|
-
borderStyle: "round",
|
|
538
|
-
borderColor: "green",
|
|
539
|
-
title: title ? chalk.green(title) : void 0,
|
|
540
|
-
titleAlignment: "center"
|
|
541
|
-
}));
|
|
542
|
-
}
|
|
543
|
-
function errorBox(message, title = "Error") {
|
|
544
|
-
console.log(boxen(message, {
|
|
545
|
-
padding: 1,
|
|
546
|
-
margin: { top: 1, bottom: 1 },
|
|
547
|
-
borderStyle: "double",
|
|
548
|
-
borderColor: "red",
|
|
549
|
-
title: chalk.red(title),
|
|
550
|
-
titleAlignment: "center"
|
|
551
|
-
}));
|
|
552
|
-
}
|
|
553
|
-
function infoBox(message, title) {
|
|
554
|
-
console.log(boxen(message, {
|
|
555
|
-
padding: 1,
|
|
556
|
-
margin: { top: 0, bottom: 1 },
|
|
557
|
-
borderStyle: "single",
|
|
558
|
-
borderColor: "cyan",
|
|
559
|
-
title: title ? chalk.cyan(title) : void 0,
|
|
560
|
-
titleAlignment: "left"
|
|
561
|
-
}));
|
|
562
|
-
}
|
|
563
|
-
function createDataTable(headers) {
|
|
564
|
-
return new Table({
|
|
565
|
-
head: headers.map((h) => chalk.cyan.bold(h)),
|
|
566
|
-
style: {
|
|
567
|
-
head: [],
|
|
568
|
-
border: ["gray"]
|
|
569
|
-
},
|
|
570
|
-
chars: {
|
|
571
|
-
"top": "\u2500",
|
|
572
|
-
"top-mid": "\u252C",
|
|
573
|
-
"top-left": "\u250C",
|
|
574
|
-
"top-right": "\u2510",
|
|
575
|
-
"bottom": "\u2500",
|
|
576
|
-
"bottom-mid": "\u2534",
|
|
577
|
-
"bottom-left": "\u2514",
|
|
578
|
-
"bottom-right": "\u2518",
|
|
579
|
-
"left": "\u2502",
|
|
580
|
-
"left-mid": "\u251C",
|
|
581
|
-
"mid": "\u2500",
|
|
582
|
-
"mid-mid": "\u253C",
|
|
583
|
-
"right": "\u2502",
|
|
584
|
-
"right-mid": "\u2524",
|
|
585
|
-
"middle": "\u2502"
|
|
586
|
-
}
|
|
587
|
-
});
|
|
588
|
-
}
|
|
589
|
-
function createProgressBar(total, text) {
|
|
590
|
-
let current = 0;
|
|
591
|
-
const render = () => {
|
|
592
|
-
const percentage = Math.round(current / total * 100);
|
|
593
|
-
const filled = Math.round(current / total * 30);
|
|
594
|
-
const empty = 30 - filled;
|
|
595
|
-
const bar = "\u2588".repeat(filled) + "\u2591".repeat(empty);
|
|
596
|
-
process.stdout.clearLine(0);
|
|
597
|
-
process.stdout.cursorTo(0);
|
|
598
|
-
process.stdout.write(
|
|
599
|
-
`${chalk.cyan("\u23F3")} ${text} [${chalk.green(bar)}] ${percentage}% (${current}/${total})`
|
|
600
|
-
);
|
|
601
|
-
};
|
|
602
|
-
return {
|
|
603
|
-
increment: () => {
|
|
604
|
-
current = Math.min(current + 1, total);
|
|
605
|
-
render();
|
|
606
|
-
},
|
|
607
|
-
update: (value) => {
|
|
608
|
-
current = Math.min(value, total);
|
|
609
|
-
render();
|
|
610
|
-
},
|
|
611
|
-
complete: () => {
|
|
612
|
-
process.stdout.clearLine(0);
|
|
613
|
-
process.stdout.cursorTo(0);
|
|
614
|
-
console.log(`${logSymbols.success} ${text} ${chalk.green("Complete!")}`);
|
|
615
|
-
},
|
|
616
|
-
fail: () => {
|
|
617
|
-
process.stdout.clearLine(0);
|
|
618
|
-
process.stdout.cursorTo(0);
|
|
619
|
-
console.log(`${logSymbols.error} ${text} ${chalk.red("Failed")}`);
|
|
620
|
-
}
|
|
621
|
-
};
|
|
622
|
-
}
|
|
623
|
-
function formatReputation(score) {
|
|
624
|
-
if (score >= 5e3) return chalk.magenta("\u{1F48E} " + score.toLocaleString());
|
|
625
|
-
if (score >= 2e3) return chalk.yellow("\u{1F947} " + score.toLocaleString());
|
|
626
|
-
if (score >= 1e3) return chalk.gray("\u{1F948} " + score.toLocaleString());
|
|
627
|
-
return chalk.dim(score.toLocaleString());
|
|
628
|
-
}
|
|
629
|
-
function formatStatus(status) {
|
|
630
|
-
const map = {
|
|
631
|
-
"active": chalk.green("\u25CF Active"),
|
|
632
|
-
"inactive": chalk.gray("\u25CB Inactive"),
|
|
633
|
-
"pending": chalk.yellow("\u25D0 Pending"),
|
|
634
|
-
"suspended": chalk.red("\u2715 Suspended")
|
|
635
|
-
};
|
|
636
|
-
return map[status] || status;
|
|
637
|
-
}
|
|
638
|
-
function truncate(str, length) {
|
|
639
|
-
if (str.length <= length) return str;
|
|
640
|
-
return str.substring(0, length - 3) + "...";
|
|
641
|
-
}
|
|
642
|
-
async function initSDK() {
|
|
643
|
-
const configPath = join(process.cwd(), ".moltos", "config.json");
|
|
644
|
-
if (!existsSync(configPath)) {
|
|
645
|
-
throw new Error('No agent config found. Run "moltos init" first.');
|
|
646
|
-
}
|
|
647
|
-
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
648
|
-
if (!config.agentId || !config.apiKey) {
|
|
649
|
-
throw new Error('Agent not registered. Run "moltos register" first.');
|
|
650
|
-
}
|
|
651
|
-
const sdk = new MoltOSSDK();
|
|
652
|
-
await sdk.init(config.agentId, config.apiKey);
|
|
653
|
-
sdk._config = config;
|
|
654
|
-
return sdk;
|
|
655
|
-
}
|
|
656
|
-
async function signClawFSPayload(privateKeyHex, payload) {
|
|
657
|
-
const timestamp = Date.now();
|
|
658
|
-
const challenge = crypto2.randomBytes(32).toString("base64") + "_" + payload.path + "_" + timestamp;
|
|
659
|
-
const fullPayload = {
|
|
660
|
-
path: payload.path,
|
|
661
|
-
content_hash: payload.content_hash,
|
|
662
|
-
challenge,
|
|
663
|
-
timestamp
|
|
664
|
-
};
|
|
665
|
-
const sortedPayload = JSON.stringify(fullPayload, Object.keys(fullPayload).sort());
|
|
666
|
-
const message = new TextEncoder().encode(sortedPayload);
|
|
667
|
-
const { ed25519 } = await import("@noble/curves/ed25519.js");
|
|
668
|
-
let privateKeyBytes;
|
|
669
|
-
const keyBuffer = Buffer.from(privateKeyHex, "hex");
|
|
670
|
-
if (keyBuffer.length === 32) {
|
|
671
|
-
privateKeyBytes = new Uint8Array(keyBuffer);
|
|
672
|
-
} else if (keyBuffer.length > 32) {
|
|
673
|
-
privateKeyBytes = new Uint8Array(keyBuffer.slice(-32));
|
|
674
|
-
} else {
|
|
675
|
-
throw new Error("Invalid private key length");
|
|
676
|
-
}
|
|
677
|
-
const signatureBytes = ed25519.sign(message, privateKeyBytes);
|
|
678
|
-
const signature = Buffer.from(signatureBytes).toString("base64");
|
|
679
|
-
return { signature, timestamp, challenge };
|
|
680
|
-
}
|
|
681
|
-
program.name("moltos").description("MoltOS CLI \u2014 The Agent Operating System").version("0.15.0").option("-j, --json", "Output in JSON format for scripting").option("-v, --verbose", "Verbose output").hook("preAction", (thisCommand) => {
|
|
682
|
-
const options = thisCommand.opts();
|
|
683
|
-
if (!options.json) {
|
|
684
|
-
showMiniBanner();
|
|
685
|
-
}
|
|
686
|
-
});
|
|
687
|
-
program.command("init [name]").description("Initialize a new agent configuration").option("-n, --name <name>", "Agent name (overrides positional arg)").option("--non-interactive", "Skip interactive prompts").action(async (nameArg, options) => {
|
|
688
|
-
const isJson = program.opts().json;
|
|
689
|
-
if (isJson) {
|
|
690
|
-
console.log(JSON.stringify({ error: "Interactive command not available in JSON mode" }, null, 2));
|
|
691
|
-
return;
|
|
692
|
-
}
|
|
693
|
-
showBanner();
|
|
694
|
-
const name = options.name || nameArg || "my-agent";
|
|
695
|
-
const answers = options.nonInteractive ? { name, generateKeys: true } : await inquirer.prompt([
|
|
696
|
-
{
|
|
697
|
-
type: "input",
|
|
698
|
-
name: "name",
|
|
699
|
-
message: moltosGradient("What should we call your agent?"),
|
|
700
|
-
default: name,
|
|
701
|
-
validate: (input) => input.length >= 3 || "Name must be at least 3 characters"
|
|
702
|
-
},
|
|
703
|
-
{
|
|
704
|
-
type: "confirm",
|
|
705
|
-
name: "generateKeys",
|
|
706
|
-
message: "Generate BLS12-381 keypair for attestations?",
|
|
707
|
-
default: true
|
|
708
|
-
}
|
|
709
|
-
]);
|
|
710
|
-
const spinner2 = ora({
|
|
711
|
-
text: chalk.cyan("Generating agent identity..."),
|
|
712
|
-
spinner: "dots"
|
|
713
|
-
}).start();
|
|
714
|
-
try {
|
|
715
|
-
const { publicKey, privateKey } = crypto2.generateKeyPairSync("ed25519");
|
|
716
|
-
const publicKeyHex = publicKey.export({ type: "spki", format: "der" }).toString("hex");
|
|
717
|
-
const privateKeyHex = privateKey.export({ type: "pkcs8", format: "der" }).toString("hex");
|
|
718
|
-
spinner2.succeed(chalk.green("Identity generated!"));
|
|
719
|
-
let blsPublicKey;
|
|
720
|
-
if (answers.generateKeys) {
|
|
721
|
-
const keySpinner = ora({
|
|
722
|
-
text: chalk.cyan("Generating BLS12-381 keys (this may take a moment)..."),
|
|
723
|
-
spinner: "arc"
|
|
724
|
-
}).start();
|
|
725
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
726
|
-
blsPublicKey = "bls_" + crypto2.randomBytes(48).toString("hex");
|
|
727
|
-
keySpinner.succeed(chalk.green("BLS keys generated!"));
|
|
728
|
-
}
|
|
729
|
-
const configDir = join(process.cwd(), ".moltos");
|
|
730
|
-
const configPath = join(configDir, "config.json");
|
|
731
|
-
if (!existsSync(configDir)) {
|
|
732
|
-
mkdirSync(configDir, { recursive: true });
|
|
733
|
-
}
|
|
734
|
-
const config = {
|
|
735
|
-
agentId: null,
|
|
736
|
-
// Will be set after registration
|
|
737
|
-
apiKey: null,
|
|
738
|
-
name: answers.name,
|
|
739
|
-
publicKey: publicKeyHex.slice(-64),
|
|
740
|
-
// Extract raw 32-byte key from DER
|
|
741
|
-
privateKey: privateKeyHex,
|
|
742
|
-
blsPublicKey,
|
|
743
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
744
|
-
};
|
|
745
|
-
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
746
|
-
chmodSync(configPath, 384);
|
|
747
|
-
successBox(
|
|
748
|
-
`Agent "${chalk.bold(answers.name)}" initialized!
|
|
749
|
-
|
|
750
|
-
${chalk.gray("Config saved to:")} ${chalk.dim(configPath)}
|
|
751
|
-
|
|
752
|
-
${chalk.gray("Next steps:")}
|
|
753
|
-
${chalk.cyan(">")} moltos register
|
|
754
|
-
${chalk.cyan(">")} moltos status`,
|
|
755
|
-
"\u2728 Success"
|
|
756
|
-
);
|
|
757
|
-
} catch (error) {
|
|
758
|
-
spinner2.fail(chalk.red("Initialization failed"));
|
|
759
|
-
errorBox(error.message);
|
|
760
|
-
process.exit(1);
|
|
761
|
-
}
|
|
762
|
-
});
|
|
763
|
-
program.command("register").description("Register your agent with MoltOS").option("-n, --name <name>", "Agent name (overrides config)").option("-k, --public-key <key>", "Ed25519 public key (hex, overrides config)").action(async (options) => {
|
|
764
|
-
const isJson = program.opts().json;
|
|
765
|
-
const configPath = join(process.cwd(), ".moltos", "config.json");
|
|
766
|
-
if (!existsSync(configPath)) {
|
|
767
|
-
const error = 'No agent config found. Run "moltos init" first.';
|
|
768
|
-
if (isJson) {
|
|
769
|
-
console.log(JSON.stringify({ success: false, error }, null, 2));
|
|
770
|
-
} else {
|
|
771
|
-
errorBox(error);
|
|
772
|
-
}
|
|
773
|
-
process.exit(1);
|
|
774
|
-
}
|
|
775
|
-
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
776
|
-
const spinner2 = ora({
|
|
777
|
-
text: isJson ? void 0 : chalk.cyan("Registering agent..."),
|
|
778
|
-
spinner: "dots"
|
|
779
|
-
});
|
|
780
|
-
if (!isJson) spinner2.start();
|
|
781
|
-
try {
|
|
782
|
-
const sdk = new MoltOSSDK(MOLTOS_API2);
|
|
783
|
-
const name = options.name || config.name;
|
|
784
|
-
const publicKey = options.publicKey || config.publicKey;
|
|
785
|
-
const result = await fetch(`${MOLTOS_API2}/agent/register`, {
|
|
786
|
-
method: "POST",
|
|
787
|
-
headers: { "Content-Type": "application/json" },
|
|
788
|
-
body: JSON.stringify({
|
|
789
|
-
name,
|
|
790
|
-
publicKey,
|
|
791
|
-
metadata: {
|
|
792
|
-
bls_public_key: config.blsPublicKey
|
|
793
|
-
}
|
|
794
|
-
})
|
|
795
|
-
});
|
|
796
|
-
const data = await result.json();
|
|
797
|
-
if (!result.ok) {
|
|
798
|
-
throw new Error(data.error || "Registration failed");
|
|
799
|
-
}
|
|
800
|
-
config.agentId = data.agent.agentId;
|
|
801
|
-
config.apiKey = data.credentials.apiKey;
|
|
802
|
-
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
803
|
-
chmodSync(configPath, 384);
|
|
804
|
-
if (isJson) {
|
|
805
|
-
console.log(JSON.stringify({
|
|
806
|
-
success: true,
|
|
807
|
-
agent_id: data.agent.agentId,
|
|
808
|
-
api_key: data.credentials.apiKey,
|
|
809
|
-
message: "Agent registered successfully"
|
|
810
|
-
}, null, 2));
|
|
811
|
-
return;
|
|
812
|
-
}
|
|
813
|
-
spinner2.succeed(chalk.green("Agent registered!"));
|
|
814
|
-
successBox(
|
|
815
|
-
`${chalk.bold("Your API Key:")}
|
|
816
|
-
${chalk.yellow(data.credentials.apiKey)}
|
|
817
|
-
|
|
818
|
-
${chalk.red("\u26A0\uFE0F Save this key! It will not be shown again.")}
|
|
819
|
-
|
|
820
|
-
${chalk.gray("Config updated with credentials.")}
|
|
821
|
-
|
|
822
|
-
${chalk.gray("Export to environment:")}
|
|
823
|
-
${chalk.cyan(`export MOLTOS_API_KEY=${data.credentials.apiKey}`)}`,
|
|
824
|
-
"\u{1F511} API Key"
|
|
825
|
-
);
|
|
826
|
-
} catch (error) {
|
|
827
|
-
if (!isJson) spinner2.fail(chalk.red("Registration failed"));
|
|
828
|
-
if (isJson) {
|
|
829
|
-
console.log(JSON.stringify({ success: false, error: error.message }, null, 2));
|
|
830
|
-
} else {
|
|
831
|
-
errorBox(error.message);
|
|
832
|
-
}
|
|
833
|
-
process.exit(1);
|
|
834
|
-
}
|
|
835
|
-
});
|
|
836
|
-
program.command("status").description("Check agent status and reputation").option("-a, --agent-id <id>", "Check specific agent").option("--json", "Output as JSON (for scripting)").action(async (options) => {
|
|
837
|
-
const isJson = options.json || program.opts().json;
|
|
838
|
-
const spinner2 = isJson ? null : ora({ text: chalk.cyan("Fetching agent status..."), spinner: "dots" }).start();
|
|
839
|
-
try {
|
|
840
|
-
const configPath = join(process.cwd(), ".moltos", "config.json");
|
|
841
|
-
let agentId = options.agentId;
|
|
842
|
-
let apiKey;
|
|
843
|
-
if (!agentId && existsSync(configPath)) {
|
|
844
|
-
const cfg = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
845
|
-
agentId = cfg.agentId;
|
|
846
|
-
apiKey = cfg.apiKey;
|
|
847
|
-
}
|
|
848
|
-
if (!agentId) {
|
|
849
|
-
spinner2?.stop();
|
|
850
|
-
errorBox('No agent ID found. Run "moltos init" and "moltos register" first, or pass --agent-id <id>');
|
|
851
|
-
process.exit(1);
|
|
852
|
-
}
|
|
853
|
-
const headers = { "Content-Type": "application/json" };
|
|
854
|
-
if (apiKey) headers["X-API-Key"] = apiKey;
|
|
855
|
-
const res = await fetch(`${MOLTOS_API2}/agents/${agentId}`, { headers });
|
|
856
|
-
if (!res.ok) throw new Error(`Agent not found (${res.status})`);
|
|
857
|
-
const data = await res.json();
|
|
858
|
-
const agent = data.agent ?? data;
|
|
859
|
-
spinner2?.stop();
|
|
860
|
-
if (isJson) {
|
|
861
|
-
console.log(JSON.stringify(agent, null, 2));
|
|
862
|
-
return;
|
|
863
|
-
}
|
|
864
|
-
const table = createDataTable(["Property", "Value"]);
|
|
865
|
-
table.push(
|
|
866
|
-
[chalk.gray("Name"), chalk.bold(agent.name || agent.agent_id)],
|
|
867
|
-
[chalk.gray("ID"), chalk.dim(agent.agent_id)],
|
|
868
|
-
[chalk.gray("Status"), formatStatus(agent.status || "active")],
|
|
869
|
-
[chalk.gray("Reputation"), formatReputation(agent.reputation ?? 0)],
|
|
870
|
-
[chalk.gray("Tier"), chalk.cyan(agent.tier || "bronze")],
|
|
871
|
-
[chalk.gray("Founding"), agent.is_founding ? chalk.green("\u2713 Yes") : chalk.gray("No")],
|
|
872
|
-
[chalk.gray("Joined"), chalk.white(new Date(agent.joined_at).toLocaleDateString())]
|
|
873
|
-
);
|
|
874
|
-
infoBox(table.toString(), "\u{1F4CA} Agent Profile");
|
|
875
|
-
const repPercent = Math.min((agent.reputation ?? 0) / 5e3 * 100, 100);
|
|
876
|
-
const filled = Math.round(repPercent / 5);
|
|
877
|
-
const bar = "\u2588".repeat(filled) + "\u2591".repeat(20 - filled);
|
|
878
|
-
console.log(chalk.gray("Reputation Progress: ") + chalk.green(bar) + chalk.gray(` ${repPercent.toFixed(0)}%`));
|
|
879
|
-
console.log();
|
|
880
|
-
} catch (err) {
|
|
881
|
-
spinner2?.stop();
|
|
882
|
-
errorBox(err.message || "Failed to fetch agent status");
|
|
883
|
-
process.exit(1);
|
|
884
|
-
}
|
|
885
|
-
});
|
|
886
|
-
program.command("attest").description("Submit an attestation for another agent").requiredOption("-t, --target <agent>", "Target agent ID").requiredOption("-s, --score <score>", "Attestation score (0-100)", parseInt).option("-c, --claim <text>", "Attestation claim/comment").option("--batch <file>", "Batch attestations from JSON file").action(async (options) => {
|
|
887
|
-
const isJson = program.opts().json;
|
|
888
|
-
if (options.batch) {
|
|
889
|
-
console.log(chalk.cyan("\u{1F4E6} Batch attestation mode"));
|
|
890
|
-
const total = 10;
|
|
891
|
-
const progress = createProgressBar(total, "Processing attestations");
|
|
892
|
-
for (let i = 0; i < total; i++) {
|
|
893
|
-
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
894
|
-
progress.increment();
|
|
895
|
-
}
|
|
896
|
-
progress.complete();
|
|
897
|
-
if (!isJson) {
|
|
898
|
-
successBox(
|
|
899
|
-
`Submitted ${chalk.bold("10")} attestations
|
|
900
|
-
Total score delta: ${chalk.green("+450")} reputation`,
|
|
901
|
-
"\u2705 Batch Complete"
|
|
902
|
-
);
|
|
903
|
-
}
|
|
904
|
-
return;
|
|
905
|
-
}
|
|
906
|
-
if (!isJson) {
|
|
907
|
-
console.log(chalk.cyan("\u{1F4DD} Submitting attestation..."));
|
|
908
|
-
console.log();
|
|
909
|
-
}
|
|
910
|
-
const spinner2 = ora({
|
|
911
|
-
text: isJson ? void 0 : chalk.cyan("Loading agent config..."),
|
|
912
|
-
spinner: "dots"
|
|
913
|
-
});
|
|
914
|
-
if (!isJson) spinner2.start();
|
|
915
|
-
try {
|
|
916
|
-
const configPath = join(process.cwd(), ".moltos", "config.json");
|
|
917
|
-
if (!existsSync(configPath)) {
|
|
918
|
-
spinner2?.stop();
|
|
919
|
-
errorBox('No agent config found. Run "moltos init" and "moltos register" first.');
|
|
920
|
-
process.exit(1);
|
|
921
|
-
}
|
|
922
|
-
const cfg = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
923
|
-
if (!cfg.apiKey || !cfg.agentId) {
|
|
924
|
-
spinner2?.stop();
|
|
925
|
-
errorBox('Agent not registered. Run "moltos register" first.');
|
|
926
|
-
process.exit(1);
|
|
927
|
-
}
|
|
928
|
-
if (!isJson) {
|
|
929
|
-
spinner2.text = chalk.cyan("Submitting to network...");
|
|
930
|
-
}
|
|
931
|
-
const res = await fetch(`${MOLTOS_API2}/agent/attest`, {
|
|
932
|
-
method: "POST",
|
|
933
|
-
headers: {
|
|
934
|
-
"Content-Type": "application/json",
|
|
935
|
-
"X-API-Key": cfg.apiKey
|
|
936
|
-
},
|
|
937
|
-
body: JSON.stringify({
|
|
938
|
-
target_agent_id: options.target,
|
|
939
|
-
score: options.score,
|
|
940
|
-
claim: options.claim || "",
|
|
941
|
-
attester_id: cfg.agentId
|
|
942
|
-
})
|
|
943
|
-
});
|
|
944
|
-
const data = await res.json();
|
|
945
|
-
if (!res.ok) throw new Error(data.error || `Attestation failed (${res.status})`);
|
|
946
|
-
if (!isJson) {
|
|
947
|
-
spinner2.succeed(chalk.green("Attestation recorded!"));
|
|
948
|
-
successBox(
|
|
949
|
-
`${chalk.gray("Target:")} ${chalk.bold(options.target)}
|
|
950
|
-
${chalk.gray("Score:")} ${chalk.yellow(options.score + "/100")}
|
|
951
|
-
${chalk.gray("Claim:")} "${truncate(options.claim || "", 40)}"
|
|
952
|
-
${chalk.gray("ID:")} ${chalk.dim(data.attestation_id || data.id || "confirmed")}`,
|
|
953
|
-
"\u2705 Attestation Submitted"
|
|
954
|
-
);
|
|
955
|
-
} else {
|
|
956
|
-
console.log(JSON.stringify({ success: true, ...data }, null, 2));
|
|
957
|
-
}
|
|
958
|
-
} catch (err) {
|
|
959
|
-
spinner2?.stop?.();
|
|
960
|
-
if (isJson) {
|
|
961
|
-
console.log(JSON.stringify({ success: false, error: err.message }, null, 2));
|
|
962
|
-
} else {
|
|
963
|
-
errorBox(err.message || "Attestation failed");
|
|
964
|
-
}
|
|
965
|
-
process.exit(1);
|
|
966
|
-
}
|
|
967
|
-
});
|
|
968
|
-
program.command("leaderboard").description("View TAP reputation leaderboard").option("-l, --limit <n>", "Number of agents to show", "20").option("--json", "Output as JSON").action(async (options) => {
|
|
969
|
-
const isJson = options.json || program.opts().json;
|
|
970
|
-
const limit = parseInt(options.limit);
|
|
971
|
-
if (!isJson) {
|
|
972
|
-
const spinner2 = ora({
|
|
973
|
-
text: chalk.cyan("Fetching leaderboard..."),
|
|
974
|
-
spinner: "dots"
|
|
975
|
-
}).start();
|
|
976
|
-
await new Promise((resolve) => setTimeout(resolve, 700));
|
|
977
|
-
spinner2.stop();
|
|
978
|
-
}
|
|
979
|
-
try {
|
|
980
|
-
const res = await fetch(`${MOLTOS_API2}/leaderboard`);
|
|
981
|
-
if (!res.ok) throw new Error(`Failed to fetch leaderboard (${res.status})`);
|
|
982
|
-
const data = await res.json();
|
|
983
|
-
const agents = (data.leaderboard ?? data.agents ?? []).slice(0, limit);
|
|
984
|
-
if (isJson) {
|
|
985
|
-
console.log(JSON.stringify({ agents }, null, 2));
|
|
986
|
-
return;
|
|
987
|
-
}
|
|
988
|
-
console.log(moltosGradient("\u{1F3C6} TAP Leaderboard"));
|
|
989
|
-
console.log();
|
|
990
|
-
const table = createDataTable(["Rank", "Agent", "Reputation", "Tier"]);
|
|
991
|
-
agents.forEach((agent) => {
|
|
992
|
-
const r = agent.rank;
|
|
993
|
-
const rankEmoji = r === 1 ? "\u{1F947}" : r === 2 ? "\u{1F948}" : r === 3 ? "\u{1F949}" : `${r}.`;
|
|
994
|
-
const rankDisplay = r <= 3 ? chalk.bold(rankEmoji) : chalk.gray(rankEmoji);
|
|
995
|
-
table.push([
|
|
996
|
-
rankDisplay,
|
|
997
|
-
truncate(agent.name || agent.agent_id, 22) + (agent.is_founding ? chalk.magenta(" \u2726") : ""),
|
|
998
|
-
formatReputation(agent.reputation ?? 0),
|
|
999
|
-
chalk.cyan(agent.tier || "bronze")
|
|
1000
|
-
]);
|
|
1001
|
-
});
|
|
1002
|
-
console.log(table.toString());
|
|
1003
|
-
console.log();
|
|
1004
|
-
console.log(chalk.gray(`Showing top ${agents.length} agents \xB7 moltos.org/leaderboard`));
|
|
1005
|
-
console.log();
|
|
1006
|
-
} catch (err) {
|
|
1007
|
-
spinner?.stop();
|
|
1008
|
-
errorBox(err.message || "Failed to fetch leaderboard");
|
|
1009
|
-
process.exit(1);
|
|
1010
|
-
}
|
|
1011
|
-
});
|
|
1012
|
-
program.command("notifications").description("Check Arbitra notifications").option("--unread", "Show only unread notifications").option("--poll", "Long-polling mode for real-time updates").action(async (options) => {
|
|
1013
|
-
const spinner2 = ora({ text: chalk.cyan("Fetching notifications..."), spinner: "dots" }).start();
|
|
1014
|
-
try {
|
|
1015
|
-
const configPath = join(process.cwd(), ".moltos", "config.json");
|
|
1016
|
-
if (!existsSync(configPath)) {
|
|
1017
|
-
spinner2.stop();
|
|
1018
|
-
errorBox('No agent config found. Run "moltos init" and "moltos register" first.');
|
|
1019
|
-
process.exit(1);
|
|
1020
|
-
}
|
|
1021
|
-
const cfg = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
1022
|
-
if (!cfg.apiKey) {
|
|
1023
|
-
spinner2.stop();
|
|
1024
|
-
errorBox('Agent not registered. Run "moltos register" first.');
|
|
1025
|
-
process.exit(1);
|
|
1026
|
-
}
|
|
1027
|
-
const res = await fetch(`${MOLTOS_API2}/agent/notifications`, {
|
|
1028
|
-
headers: { "X-API-Key": cfg.apiKey }
|
|
1029
|
-
});
|
|
1030
|
-
spinner2.stop();
|
|
1031
|
-
if (!res.ok) throw new Error(`Failed to fetch notifications (${res.status})`);
|
|
1032
|
-
const data = await res.json();
|
|
1033
|
-
const notifications = data.notifications ?? data ?? [];
|
|
1034
|
-
const toShow = options.unread ? notifications.filter((n) => !n.read_at) : notifications;
|
|
1035
|
-
console.log(moltosGradient("\u{1F514} Notifications"));
|
|
1036
|
-
console.log();
|
|
1037
|
-
if (toShow.length === 0) {
|
|
1038
|
-
console.log(chalk.gray("No notifications. You are all caught up."));
|
|
1039
|
-
console.log();
|
|
1040
|
-
return;
|
|
1041
|
-
}
|
|
1042
|
-
toShow.forEach((n) => {
|
|
1043
|
-
const type = n.type || "info";
|
|
1044
|
-
const icon = type === "appeal" ? "\u2696\uFE0F" : type === "dispute" ? "\u{1F534}" : type === "attestation" ? "\u2B50" : "\u{1F514}";
|
|
1045
|
-
const unreadMark = !n.read_at ? chalk.yellow("\u25CF ") : chalk.gray("\u25CB ");
|
|
1046
|
-
console.log(`${unreadMark}${icon} ${chalk.bold(n.title || type)}`);
|
|
1047
|
-
console.log(` ${chalk.gray(n.message || n.body || "")}`);
|
|
1048
|
-
if (n.created_at) console.log(` ${chalk.dim(new Date(n.created_at).toLocaleString())}`);
|
|
1049
|
-
console.log();
|
|
1050
|
-
});
|
|
1051
|
-
if (options.poll) {
|
|
1052
|
-
console.log(chalk.cyan("\u23F3 Polling for new notifications... (Ctrl+C to exit)"));
|
|
1053
|
-
}
|
|
1054
|
-
} catch (err) {
|
|
1055
|
-
spinner2.stop();
|
|
1056
|
-
errorBox(err.message || "Failed to fetch notifications");
|
|
1057
|
-
process.exit(1);
|
|
1058
|
-
}
|
|
1059
|
-
});
|
|
1060
|
-
var clawfs = program.command("clawfs").description("ClawFS persistent storage operations");
|
|
1061
|
-
clawfs.command("write").description("Write a file to ClawFS").argument("<path>", "File path (must start with /data/, /apps/, /agents/, or /temp/)").argument("<content>", "File content").option("-t, --type <type>", "Content type", "text/plain").option("-j, --json", "Output in JSON format").action(async (path, content, options) => {
|
|
1062
|
-
showMiniBanner();
|
|
1063
|
-
const spinner2 = ora({
|
|
1064
|
-
text: chalk.cyan("Writing to ClawFS..."),
|
|
1065
|
-
spinner: "dots"
|
|
1066
|
-
}).start();
|
|
1067
|
-
try {
|
|
1068
|
-
const sdk = await initSDK();
|
|
1069
|
-
const config = sdk._config;
|
|
1070
|
-
if (!config || !config.privateKey) {
|
|
1071
|
-
throw new Error('Agent private key not found. Re-run "moltos init".');
|
|
1072
|
-
}
|
|
1073
|
-
const { signature, timestamp, challenge } = await signClawFSPayload(config.privateKey, {
|
|
1074
|
-
path,
|
|
1075
|
-
content_hash: crypto2.createHash("sha256").update(Buffer.from(content)).digest("hex")
|
|
1076
|
-
});
|
|
1077
|
-
const result = await sdk.clawfsWrite(path, content, {
|
|
1078
|
-
contentType: options.type,
|
|
1079
|
-
publicKey: config.publicKey,
|
|
1080
|
-
signature,
|
|
1081
|
-
timestamp,
|
|
1082
|
-
challenge
|
|
1083
|
-
});
|
|
1084
|
-
spinner2.stop();
|
|
1085
|
-
if (options.json) {
|
|
1086
|
-
console.log(JSON.stringify(result, null, 2));
|
|
1087
|
-
} else {
|
|
1088
|
-
successBox(
|
|
1089
|
-
`${chalk.bold("File written successfully")}
|
|
1090
|
-
|
|
1091
|
-
${chalk.gray("Path:")} ${chalk.cyan(result.file.path)}
|
|
1092
|
-
${chalk.gray("CID:")} ${chalk.yellow(result.file.cid)}
|
|
1093
|
-
${chalk.gray("Size:")} ${chalk.white(result.file.size_bytes)} bytes
|
|
1094
|
-
${chalk.gray("Merkle Root:")} ${chalk.magenta(result.merkle_root)}`,
|
|
1095
|
-
"\u2713 ClawFS Write"
|
|
1096
|
-
);
|
|
1097
|
-
}
|
|
1098
|
-
} catch (error) {
|
|
1099
|
-
spinner2.stop();
|
|
1100
|
-
errorBox(`Failed to write file: ${error.message}`);
|
|
1101
|
-
process.exit(1);
|
|
1102
|
-
}
|
|
1103
|
-
});
|
|
1104
|
-
clawfs.command("read").description("Read a file from ClawFS").argument("<path>", "File path or CID").option("-c, --cid", "Interpret path as CID instead of file path").option("-j, --json", "Output in JSON format").option("-r, --raw", "Output raw content only").action(async (path, options) => {
|
|
1105
|
-
showMiniBanner();
|
|
1106
|
-
const spinner2 = ora({
|
|
1107
|
-
text: chalk.cyan("Reading from ClawFS..."),
|
|
1108
|
-
spinner: "dots"
|
|
1109
|
-
}).start();
|
|
1110
|
-
try {
|
|
1111
|
-
const sdk = await initSDK();
|
|
1112
|
-
const result = await sdk.clawfsRead(path, { byCid: options.cid });
|
|
1113
|
-
spinner2.stop();
|
|
1114
|
-
if (options.raw) {
|
|
1115
|
-
console.log(result.file);
|
|
1116
|
-
} else if (options.json) {
|
|
1117
|
-
console.log(JSON.stringify(result, null, 2));
|
|
1118
|
-
} else {
|
|
1119
|
-
successBox(
|
|
1120
|
-
`${chalk.bold("File retrieved")}
|
|
1121
|
-
|
|
1122
|
-
${chalk.gray("Path:")} ${chalk.cyan(result.file.path)}
|
|
1123
|
-
${chalk.gray("CID:")} ${chalk.yellow(result.file.cid)}
|
|
1124
|
-
${chalk.gray("Type:")} ${chalk.white(result.file.content_type)}
|
|
1125
|
-
${chalk.gray("Size:")} ${chalk.white(result.file.size_bytes)} bytes
|
|
1126
|
-
${chalk.gray("Created:")} ${chalk.white(new Date(result.file.created_at).toLocaleString())}`,
|
|
1127
|
-
"\u2713 ClawFS Read"
|
|
1128
|
-
);
|
|
1129
|
-
console.log();
|
|
1130
|
-
console.log(chalk.gray("Content URL:"), chalk.cyan.underline(result.content_url));
|
|
1131
|
-
}
|
|
1132
|
-
} catch (error) {
|
|
1133
|
-
spinner2.stop();
|
|
1134
|
-
errorBox(`Failed to read file: ${error.message}`);
|
|
1135
|
-
process.exit(1);
|
|
1136
|
-
}
|
|
1137
|
-
});
|
|
1138
|
-
clawfs.command("list").description("List files in ClawFS").option("-p, --prefix <prefix>", "Filter by path prefix").option("-l, --limit <limit>", "Maximum files to show", "50").option("-j, --json", "Output in JSON format").action(async (options) => {
|
|
1139
|
-
showMiniBanner();
|
|
1140
|
-
const spinner2 = ora({
|
|
1141
|
-
text: chalk.cyan("Listing ClawFS files..."),
|
|
1142
|
-
spinner: "dots"
|
|
1143
|
-
}).start();
|
|
1144
|
-
try {
|
|
1145
|
-
const sdk = await initSDK();
|
|
1146
|
-
const result = await sdk.clawfsList({
|
|
1147
|
-
prefix: options.prefix,
|
|
1148
|
-
limit: parseInt(options.limit)
|
|
1149
|
-
});
|
|
1150
|
-
spinner2.stop();
|
|
1151
|
-
if (options.json) {
|
|
1152
|
-
console.log(JSON.stringify(result, null, 2));
|
|
1153
|
-
} else if (result.files.length === 0) {
|
|
1154
|
-
console.log(chalk.gray("No files found in ClawFS."));
|
|
1155
|
-
} else {
|
|
1156
|
-
console.log(moltosGradient(`\u{1F4C1} ClawFS Files (${result.total} total)`));
|
|
1157
|
-
console.log();
|
|
1158
|
-
const table = createDataTable(["Path", "CID", "Size", "Created"]);
|
|
1159
|
-
result.files.forEach((file) => {
|
|
1160
|
-
table.push([
|
|
1161
|
-
chalk.cyan(file.path),
|
|
1162
|
-
chalk.yellow(file.cid.slice(0, 16) + "..."),
|
|
1163
|
-
chalk.white(`${file.size_bytes} B`),
|
|
1164
|
-
chalk.gray(new Date(file.created_at).toLocaleDateString())
|
|
1165
|
-
]);
|
|
1166
|
-
});
|
|
1167
|
-
console.log(table.toString());
|
|
1168
|
-
}
|
|
1169
|
-
} catch (error) {
|
|
1170
|
-
spinner2.stop();
|
|
1171
|
-
errorBox(`Failed to list files: ${error.message}`);
|
|
1172
|
-
process.exit(1);
|
|
1173
|
-
}
|
|
1174
|
-
});
|
|
1175
|
-
clawfs.command("snapshot").description("Create a snapshot of current ClawFS state").option("-j, --json", "Output in JSON format").action(async (options) => {
|
|
1176
|
-
showMiniBanner();
|
|
1177
|
-
const spinner2 = ora({
|
|
1178
|
-
text: chalk.cyan("Creating ClawFS snapshot..."),
|
|
1179
|
-
spinner: "dots"
|
|
1180
|
-
}).start();
|
|
1181
|
-
try {
|
|
1182
|
-
const sdk = await initSDK();
|
|
1183
|
-
const config = sdk._config;
|
|
1184
|
-
if (!config || !config.privateKey) {
|
|
1185
|
-
throw new Error('Agent private key not found. Re-run "moltos init".');
|
|
1186
|
-
}
|
|
1187
|
-
const contentHash = crypto2.createHash("sha256").update(config.agentId).digest("hex");
|
|
1188
|
-
const { signature, timestamp, challenge } = await signClawFSPayload(config.privateKey, {
|
|
1189
|
-
path: "/snapshot",
|
|
1190
|
-
content_hash: contentHash
|
|
1191
|
-
});
|
|
1192
|
-
const res = await fetch(`${MOLTOS_API2}/clawfs/snapshot`, {
|
|
1193
|
-
method: "POST",
|
|
1194
|
-
headers: {
|
|
1195
|
-
"Content-Type": "application/json",
|
|
1196
|
-
"X-API-Key": config.apiKey
|
|
1197
|
-
},
|
|
1198
|
-
body: JSON.stringify({
|
|
1199
|
-
agent_id: config.agentId,
|
|
1200
|
-
public_key: config.publicKey,
|
|
1201
|
-
signature,
|
|
1202
|
-
timestamp,
|
|
1203
|
-
challenge,
|
|
1204
|
-
content_hash: contentHash
|
|
1205
|
-
})
|
|
1206
|
-
});
|
|
1207
|
-
const result = await res.json();
|
|
1208
|
-
if (!res.ok) throw new Error(result.error || `Snapshot failed (${res.status})`);
|
|
1209
|
-
spinner2.stop();
|
|
1210
|
-
if (options.json) {
|
|
1211
|
-
console.log(JSON.stringify(result, null, 2));
|
|
1212
|
-
} else {
|
|
1213
|
-
successBox(
|
|
1214
|
-
`${chalk.bold("Snapshot created")}
|
|
1215
|
-
|
|
1216
|
-
${chalk.gray("ID:")} ${chalk.cyan(result.snapshot.id)}
|
|
1217
|
-
${chalk.gray("Merkle Root:")} ${chalk.magenta(result.snapshot.merkle_root)}
|
|
1218
|
-
${chalk.gray("Files:")} ${chalk.white(result.snapshot.file_count)}
|
|
1219
|
-
${chalk.gray("Created:")} ${chalk.white(new Date(result.snapshot.created_at).toLocaleString())}`,
|
|
1220
|
-
"\u2713 ClawFS Snapshot"
|
|
1221
|
-
);
|
|
1222
|
-
}
|
|
1223
|
-
} catch (error) {
|
|
1224
|
-
spinner2.stop();
|
|
1225
|
-
errorBox(`Failed to create snapshot: ${error.message}`);
|
|
1226
|
-
process.exit(1);
|
|
1227
|
-
}
|
|
1228
|
-
});
|
|
1229
|
-
clawfs.command("mount").description("Mount a ClawFS snapshot for restoration").argument("<snapshot-id>", "Snapshot ID to mount").option("-j, --json", "Output in JSON format").action(async (snapshotId, options) => {
|
|
1230
|
-
showMiniBanner();
|
|
1231
|
-
const spinner2 = ora({
|
|
1232
|
-
text: chalk.cyan("Mounting snapshot..."),
|
|
1233
|
-
spinner: "dots"
|
|
1234
|
-
}).start();
|
|
1235
|
-
try {
|
|
1236
|
-
const sdk = await initSDK();
|
|
1237
|
-
const result = await sdk.clawfsMount(snapshotId);
|
|
1238
|
-
spinner2.stop();
|
|
1239
|
-
if (options.json) {
|
|
1240
|
-
console.log(JSON.stringify(result, null, 2));
|
|
1241
|
-
} else {
|
|
1242
|
-
successBox(
|
|
1243
|
-
`${chalk.bold("Snapshot mounted")}
|
|
1244
|
-
|
|
1245
|
-
${chalk.gray("Merkle Root:")} ${chalk.magenta(result.snapshot.merkle_root)}
|
|
1246
|
-
${chalk.gray("Files:")} ${chalk.white(result.files.length)}`,
|
|
1247
|
-
"\u2713 ClawFS Mount"
|
|
1248
|
-
);
|
|
1249
|
-
}
|
|
1250
|
-
} catch (error) {
|
|
1251
|
-
spinner2.stop();
|
|
1252
|
-
errorBox(`Failed to mount snapshot: ${error.message}`);
|
|
1253
|
-
process.exit(1);
|
|
1254
|
-
}
|
|
1255
|
-
});
|
|
1256
|
-
program.command("docs").description("Open MoltOS documentation").action(() => {
|
|
1257
|
-
console.log();
|
|
1258
|
-
console.log(moltosGradient("\u{1F4DA} MoltOS Documentation"));
|
|
1259
|
-
console.log();
|
|
1260
|
-
const table = createDataTable(["Resource", "URL"]);
|
|
1261
|
-
table.push(
|
|
1262
|
-
["Getting Started", chalk.cyan.underline("https://moltos.org/docs/getting-started")],
|
|
1263
|
-
["API Reference", chalk.cyan.underline("https://moltos.org/docs/api")],
|
|
1264
|
-
["SDK Guide", chalk.cyan.underline("https://moltos.org/docs/sdk")],
|
|
1265
|
-
["GitHub Issues", chalk.cyan.underline("https://github.com/Shepherd217/MoltOS/issues")]
|
|
1266
|
-
);
|
|
1267
|
-
console.log(table.toString());
|
|
1268
|
-
console.log();
|
|
1269
|
-
});
|
|
1270
|
-
var workflowCmd = program.command("workflow").description("Manage ClawScheduler DAG workflows");
|
|
1271
|
-
workflowCmd.command("create").description("Create a new workflow from a YAML definition").requiredOption("-f, --file <path>", "Path to workflow YAML file").action(async (options) => {
|
|
1272
|
-
const spinner2 = ora({ text: chalk.cyan("Creating workflow..."), spinner: "dots" }).start();
|
|
1273
|
-
try {
|
|
1274
|
-
const fileContent = readFileSync(options.file, "utf8");
|
|
1275
|
-
const configPath = join(process.cwd(), ".moltos", "config.json");
|
|
1276
|
-
if (!existsSync(configPath)) throw new Error('No agent config. Run "moltos init" first.');
|
|
1277
|
-
const cfg = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
1278
|
-
if (!cfg.apiKey) throw new Error('Agent not registered. Run "moltos register" first.');
|
|
1279
|
-
const res = await fetch(`${MOLTOS_API2}/claw/scheduler/workflows`, {
|
|
1280
|
-
method: "POST",
|
|
1281
|
-
headers: { "Content-Type": "application/json", "X-API-Key": cfg.apiKey },
|
|
1282
|
-
body: JSON.stringify({ definition: fileContent, format: "yaml" })
|
|
1283
|
-
});
|
|
1284
|
-
const data = await res.json();
|
|
1285
|
-
if (!res.ok) throw new Error(data.error || `Failed (${res.status})`);
|
|
1286
|
-
spinner2.succeed(chalk.green("Workflow created successfully"));
|
|
1287
|
-
console.log(` ID: ${chalk.cyan(data.id || data.workflow_id || data.workflow?.id)}`);
|
|
1288
|
-
} catch (err) {
|
|
1289
|
-
spinner2.stop();
|
|
1290
|
-
console.error(chalk.red(`Error: ${err.message}`));
|
|
1291
|
-
process.exit(1);
|
|
1292
|
-
}
|
|
1293
|
-
});
|
|
1294
|
-
workflowCmd.command("run").description("Run a workflow").requiredOption("-i, --id <workflow-id>", "Workflow ID").action(async (options) => {
|
|
1295
|
-
const spinner2 = ora({ text: chalk.cyan("Starting workflow..."), spinner: "dots" }).start();
|
|
1296
|
-
try {
|
|
1297
|
-
const configPath = join(process.cwd(), ".moltos", "config.json");
|
|
1298
|
-
if (!existsSync(configPath)) throw new Error('No agent config found. Run "moltos init" first.');
|
|
1299
|
-
const cfg = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
1300
|
-
if (!cfg.apiKey) throw new Error('Agent not registered. Run "moltos register" first.');
|
|
1301
|
-
const res = await fetch(`${MOLTOS_API2}/claw/scheduler/execute`, {
|
|
1302
|
-
method: "POST",
|
|
1303
|
-
headers: { "Content-Type": "application/json", "X-API-Key": cfg.apiKey },
|
|
1304
|
-
body: JSON.stringify({ workflowId: options.id })
|
|
1305
|
-
});
|
|
1306
|
-
const data = await res.json();
|
|
1307
|
-
if (!res.ok) throw new Error(data.error || `Failed (${res.status})`);
|
|
1308
|
-
spinner2.succeed(chalk.green("Workflow execution started"));
|
|
1309
|
-
console.log(` Execution ID: ${chalk.cyan(data.execution_id || data.id)}`);
|
|
1310
|
-
} catch (err) {
|
|
1311
|
-
spinner2.stop();
|
|
1312
|
-
console.error(chalk.red(`Error: ${err.message}`));
|
|
1313
|
-
process.exit(1);
|
|
1314
|
-
}
|
|
1315
|
-
});
|
|
1316
|
-
workflowCmd.command("status").description("Check execution status").requiredOption("-i, --id <execution-id>", "Execution ID").action(async (options) => {
|
|
1317
|
-
const spinner2 = ora({ text: chalk.cyan("Fetching execution status..."), spinner: "dots" }).start();
|
|
1318
|
-
try {
|
|
1319
|
-
const configPath = join(process.cwd(), ".moltos", "config.json");
|
|
1320
|
-
if (!existsSync(configPath)) throw new Error("No agent config found.");
|
|
1321
|
-
const cfg = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
1322
|
-
const res = await fetch(`${MOLTOS_API2}/claw/scheduler/executions/${options.id}`, {
|
|
1323
|
-
headers: { "X-API-Key": cfg.apiKey || "" }
|
|
1324
|
-
});
|
|
1325
|
-
const data = await res.json();
|
|
1326
|
-
if (!res.ok) throw new Error(data.error || `Failed (${res.status})`);
|
|
1327
|
-
spinner2.stop();
|
|
1328
|
-
console.log(chalk.green(`Status: ${data.status}`));
|
|
1329
|
-
if (data.nodes_completed !== void 0) console.log(` Nodes Completed: ${data.nodes_completed}/${data.nodes_total}`);
|
|
1330
|
-
if (data.artifacts?.length) console.log(` Artifacts: ${data.artifacts.join(", ")}`);
|
|
1331
|
-
} catch (err) {
|
|
1332
|
-
spinner2.stop();
|
|
1333
|
-
console.error(chalk.red(`Error: ${err.message}`));
|
|
1334
|
-
process.exit(1);
|
|
1335
|
-
}
|
|
1336
|
-
});
|
|
1337
|
-
program.command("recover").description("Re-authenticate using your private key \u2014 use after hardware wipe or migration").option("--json", "Output as JSON").action(async (options) => {
|
|
1338
|
-
const isJson = options.json || program.opts().json;
|
|
1339
|
-
const spinner2 = isJson ? null : ora({ text: chalk.cyan("Re-authenticating..."), spinner: "dots" }).start();
|
|
1340
|
-
try {
|
|
1341
|
-
const configPath = join(process.cwd(), ".moltos", "config.json");
|
|
1342
|
-
if (!existsSync(configPath)) throw new Error("No config found. Re-inject your config.json with privateKey and publicKey first.");
|
|
1343
|
-
const cfg = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
1344
|
-
if (!cfg.privateKey || !cfg.publicKey) throw new Error("privateKey and publicKey required in config.json.");
|
|
1345
|
-
const { ed25519 } = await import("@noble/curves/ed25519.js");
|
|
1346
|
-
const timestamp = Date.now();
|
|
1347
|
-
const keyBuffer = Buffer.from(cfg.privateKey, "hex");
|
|
1348
|
-
const privateKeyBytes = new Uint8Array(keyBuffer.slice(-32));
|
|
1349
|
-
const recoveryPayload = { action: "recover", public_key: cfg.publicKey, timestamp };
|
|
1350
|
-
const sorted = JSON.stringify(recoveryPayload, Object.keys(recoveryPayload).sort());
|
|
1351
|
-
const sigBytes = ed25519.sign(new TextEncoder().encode(sorted), privateKeyBytes);
|
|
1352
|
-
const signature = Buffer.from(sigBytes).toString("base64");
|
|
1353
|
-
const res = await fetch(`${MOLTOS_API2}/agent/register`, {
|
|
1354
|
-
method: "POST",
|
|
1355
|
-
headers: { "Content-Type": "application/json" },
|
|
1356
|
-
body: JSON.stringify({
|
|
1357
|
-
name: cfg.name || "recovered-agent",
|
|
1358
|
-
publicKey: cfg.publicKey,
|
|
1359
|
-
recover: true,
|
|
1360
|
-
recovery_signature: signature,
|
|
1361
|
-
recovery_timestamp: timestamp,
|
|
1362
|
-
metadata: { bls_public_key: cfg.blsPublicKey }
|
|
1363
|
-
})
|
|
1364
|
-
});
|
|
1365
|
-
const data = await res.json();
|
|
1366
|
-
if (!res.ok && data.error?.includes("already registered")) {
|
|
1367
|
-
const rotateRes = await fetch(`${MOLTOS_API2}/agent/auth/rotate`, {
|
|
1368
|
-
method: "POST",
|
|
1369
|
-
headers: { "Content-Type": "application/json" },
|
|
1370
|
-
body: JSON.stringify({
|
|
1371
|
-
public_key: cfg.publicKey,
|
|
1372
|
-
signature,
|
|
1373
|
-
timestamp
|
|
1374
|
-
})
|
|
1375
|
-
});
|
|
1376
|
-
const rotateData = await rotateRes.json();
|
|
1377
|
-
if (!rotateRes.ok) {
|
|
1378
|
-
if (cfg.apiKey) {
|
|
1379
|
-
spinner2?.stop();
|
|
1380
|
-
if (isJson) {
|
|
1381
|
-
console.log(JSON.stringify({ success: true, recovered: false, message: "Existing API key retained", agent_id: cfg.agentId }, null, 2));
|
|
1382
|
-
return;
|
|
1383
|
-
}
|
|
1384
|
-
infoBox(`Agent already registered. Existing API key retained.
|
|
1385
|
-
|
|
1386
|
-
Agent ID: ${chalk.cyan(cfg.agentId)}
|
|
1387
|
-
Run ${chalk.cyan("moltos whoami")} to verify.`, "\u{1F504} Recovery");
|
|
1388
|
-
return;
|
|
1389
|
-
}
|
|
1390
|
-
throw new Error(rotateData.error || "Recovery failed \u2014 contact support");
|
|
1391
|
-
}
|
|
1392
|
-
cfg.apiKey = rotateData.api_key || rotateData.apiKey;
|
|
1393
|
-
} else if (res.ok) {
|
|
1394
|
-
cfg.agentId = data.agent?.agentId || cfg.agentId;
|
|
1395
|
-
cfg.apiKey = data.credentials?.apiKey || cfg.apiKey;
|
|
1396
|
-
} else {
|
|
1397
|
-
throw new Error(data.error || `Recovery failed (${res.status})`);
|
|
1398
|
-
}
|
|
1399
|
-
writeFileSync(configPath, JSON.stringify(cfg, null, 2));
|
|
1400
|
-
chmodSync(configPath, 384);
|
|
1401
|
-
spinner2?.stop();
|
|
1402
|
-
if (isJson) {
|
|
1403
|
-
console.log(JSON.stringify({ success: true, agent_id: cfg.agentId, message: "Recovery complete" }, null, 2));
|
|
1404
|
-
return;
|
|
1405
|
-
}
|
|
1406
|
-
successBox(
|
|
1407
|
-
`${chalk.bold("Identity recovered!")}
|
|
1408
|
-
|
|
1409
|
-
${chalk.gray("Agent ID:")} ${chalk.cyan(cfg.agentId)}
|
|
1410
|
-
${chalk.gray("Config:")} ${chalk.white(configPath)}
|
|
1411
|
-
|
|
1412
|
-
${chalk.gray("Run")} ${chalk.cyan("moltos clawfs mount latest")} ${chalk.gray("to restore your memory state.")}`,
|
|
1413
|
-
"\u{1F504} Recovery Complete"
|
|
1414
|
-
);
|
|
1415
|
-
} catch (err) {
|
|
1416
|
-
spinner2?.stop();
|
|
1417
|
-
if (isJson) console.log(JSON.stringify({ success: false, error: err.message }, null, 2));
|
|
1418
|
-
else errorBox(err.message);
|
|
1419
|
-
process.exit(1);
|
|
1420
|
-
}
|
|
1421
|
-
});
|
|
1422
|
-
program.command("whoami").description("Show your current agent identity, TAP score, and tier").option("--json", "Output as JSON").action(async (options) => {
|
|
1423
|
-
const isJson = options.json || program.opts().json;
|
|
1424
|
-
const spinner2 = isJson ? null : ora({ text: chalk.cyan("Loading identity..."), spinner: "dots" }).start();
|
|
1425
|
-
try {
|
|
1426
|
-
const configPath = join(process.cwd(), ".moltos", "config.json");
|
|
1427
|
-
if (!existsSync(configPath)) {
|
|
1428
|
-
spinner2?.stop();
|
|
1429
|
-
errorBox('No agent config found. Run "moltos init" and "moltos register" first.');
|
|
1430
|
-
process.exit(1);
|
|
1431
|
-
}
|
|
1432
|
-
const cfg = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
1433
|
-
if (!cfg.agentId || !cfg.apiKey) {
|
|
1434
|
-
spinner2?.stop();
|
|
1435
|
-
errorBox('Agent not registered. Run "moltos register" first.');
|
|
1436
|
-
process.exit(1);
|
|
1437
|
-
}
|
|
1438
|
-
const res = await fetch(`${MOLTOS_API2}/agent/profile?agent_id=${cfg.agentId}`, {
|
|
1439
|
-
headers: { "X-API-Key": cfg.apiKey }
|
|
1440
|
-
});
|
|
1441
|
-
const data = await res.json();
|
|
1442
|
-
spinner2?.stop();
|
|
1443
|
-
if (isJson) {
|
|
1444
|
-
console.log(JSON.stringify({ agent_id: cfg.agentId, ...data }, null, 2));
|
|
1445
|
-
return;
|
|
1446
|
-
}
|
|
1447
|
-
const tierColors = {
|
|
1448
|
-
DIAMOND: chalk.cyan,
|
|
1449
|
-
PLATINUM: chalk.white,
|
|
1450
|
-
GOLD: chalk.yellow,
|
|
1451
|
-
SILVER: chalk.gray,
|
|
1452
|
-
BRONZE: chalk.dim
|
|
1453
|
-
};
|
|
1454
|
-
const tierFn = tierColors[(data.tier || "BRONZE").toUpperCase()] || chalk.dim;
|
|
1455
|
-
infoBox(
|
|
1456
|
-
`${chalk.gray("Agent ID:")} ${chalk.dim(cfg.agentId)}
|
|
1457
|
-
${chalk.gray("Name:")} ${chalk.bold(data.name || cfg.name || "-")}
|
|
1458
|
-
${chalk.gray("TAP Score:")} ${chalk.green((data.reputation ?? 0).toString())}
|
|
1459
|
-
${chalk.gray("Tier:")} ${tierFn((data.tier || "BRONZE").toUpperCase())}
|
|
1460
|
-
${chalk.gray("Status:")} ${data.status === "active" ? chalk.green("\u25CF active") : chalk.gray("\u25CB " + (data.status || "unknown"))}
|
|
1461
|
-
` + (data.bio ? `${chalk.gray("Bio:")} ${chalk.white(data.bio)}
|
|
1462
|
-
` : "") + (data.skills?.length ? `${chalk.gray("Skills:")} ${chalk.cyan(data.skills.join(", "))}` : ""),
|
|
1463
|
-
"\u{1F194} Identity"
|
|
1464
|
-
);
|
|
1465
|
-
} catch (err) {
|
|
1466
|
-
spinner2?.stop();
|
|
1467
|
-
errorBox(err.message || "Failed to load identity");
|
|
1468
|
-
process.exit(1);
|
|
1469
|
-
}
|
|
1470
|
-
});
|
|
1471
|
-
var profileCmd = program.command("profile").description("Manage your agent profile");
|
|
1472
|
-
profileCmd.command("update").description("Update your public profile \u2014 bio, skills, availability, rate").option("--bio <text>", "Short bio (max 500 chars)").option("--skills <list>", 'Comma-separated skill tags (e.g. "research,TypeScript,analysis")').option("--rate <n>", "Hourly rate in USD", parseInt).option("--available", "Mark as available for hire").option("--unavailable", "Mark as unavailable for hire").option("--website <url>", "Your agent website or repo").option("--json", "Output as JSON").action(async (options) => {
|
|
1473
|
-
const isJson = options.json || program.opts().json;
|
|
1474
|
-
const spinner2 = isJson ? null : ora({ text: chalk.cyan("Updating profile..."), spinner: "dots" }).start();
|
|
1475
|
-
try {
|
|
1476
|
-
const configPath = join(process.cwd(), ".moltos", "config.json");
|
|
1477
|
-
if (!existsSync(configPath)) throw new Error('No agent config found. Run "moltos init" and "moltos register" first.');
|
|
1478
|
-
const cfg = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
1479
|
-
if (!cfg.apiKey) throw new Error('Agent not registered. Run "moltos register" first.');
|
|
1480
|
-
const body = {};
|
|
1481
|
-
if (options.bio) body.bio = options.bio;
|
|
1482
|
-
if (options.skills) body.skills = options.skills.split(",").map((s) => s.trim()).filter(Boolean);
|
|
1483
|
-
if (options.rate) body.rate_per_hour = options.rate;
|
|
1484
|
-
if (options.available) body.available_for_hire = true;
|
|
1485
|
-
if (options.unavailable) body.available_for_hire = false;
|
|
1486
|
-
if (options.website) body.website = options.website;
|
|
1487
|
-
if (Object.keys(body).length === 0) {
|
|
1488
|
-
spinner2?.stop();
|
|
1489
|
-
errorBox('Provide at least one option. Try: moltos profile update --bio "..." --skills "research,python"');
|
|
1490
|
-
process.exit(1);
|
|
1491
|
-
}
|
|
1492
|
-
const res = await fetch(`${MOLTOS_API2}/agent/profile`, {
|
|
1493
|
-
method: "PATCH",
|
|
1494
|
-
headers: { "Content-Type": "application/json", "X-API-Key": cfg.apiKey },
|
|
1495
|
-
body: JSON.stringify(body)
|
|
1496
|
-
});
|
|
1497
|
-
const data = await res.json();
|
|
1498
|
-
if (!res.ok) throw new Error(data.error || "Profile update failed");
|
|
1499
|
-
spinner2?.stop();
|
|
1500
|
-
if (isJson) {
|
|
1501
|
-
console.log(JSON.stringify(data, null, 2));
|
|
1502
|
-
} else {
|
|
1503
|
-
successBox(
|
|
1504
|
-
Object.entries(body).map(
|
|
1505
|
-
([k, v]) => `${chalk.gray(k + ":")} ${chalk.white(Array.isArray(v) ? v.join(", ") : String(v))}`
|
|
1506
|
-
).join("\n"),
|
|
1507
|
-
"\u2705 Profile Updated"
|
|
1508
|
-
);
|
|
1509
|
-
}
|
|
1510
|
-
} catch (err) {
|
|
1511
|
-
spinner2?.stop();
|
|
1512
|
-
if (isJson) console.log(JSON.stringify({ success: false, error: err.message }, null, 2));
|
|
1513
|
-
else errorBox(err.message || "Profile update failed");
|
|
1514
|
-
process.exit(1);
|
|
1515
|
-
}
|
|
1516
|
-
});
|
|
1517
|
-
profileCmd.command("show [agent-id]").description("Show an agent's public profile (default: yours)").option("--json", "Output as JSON").action(async (agentId, options) => {
|
|
1518
|
-
const isJson = options.json || program.opts().json;
|
|
1519
|
-
const spinner2 = isJson ? null : ora({ text: chalk.cyan("Fetching profile..."), spinner: "dots" }).start();
|
|
1520
|
-
try {
|
|
1521
|
-
const configPath = join(process.cwd(), ".moltos", "config.json");
|
|
1522
|
-
let targetId = agentId;
|
|
1523
|
-
if (!targetId && existsSync(configPath)) {
|
|
1524
|
-
const cfg = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
1525
|
-
targetId = cfg.agentId;
|
|
1526
|
-
}
|
|
1527
|
-
if (!targetId) throw new Error('No agent ID. Pass an agent ID or run "moltos register" first.');
|
|
1528
|
-
const res = await fetch(`${MOLTOS_API2}/agent/profile?agent_id=${targetId}`);
|
|
1529
|
-
const data = await res.json();
|
|
1530
|
-
spinner2?.stop();
|
|
1531
|
-
if (isJson) {
|
|
1532
|
-
console.log(JSON.stringify(data, null, 2));
|
|
1533
|
-
return;
|
|
1534
|
-
}
|
|
1535
|
-
infoBox(
|
|
1536
|
-
`${chalk.gray("Name:")} ${chalk.bold(data.name || targetId)}
|
|
1537
|
-
${chalk.gray("TAP:")} ${chalk.green((data.reputation ?? 0).toString())} ${chalk.dim((data.tier || "").toUpperCase())}
|
|
1538
|
-
${chalk.gray("Bio:")} ${chalk.white(data.bio || "(none)")}
|
|
1539
|
-
${chalk.gray("Skills:")} ${chalk.cyan((data.skills || []).join(", ") || "(none)")}
|
|
1540
|
-
${chalk.gray("Rate:")} ${data.rate_per_hour ? `${data.rate_per_hour}/hr` : "(none)"}
|
|
1541
|
-
${chalk.gray("Available:")} ${data.availability ? chalk.green("Yes") : chalk.red("No")}`
|
|
1542
|
-
);
|
|
1543
|
-
} catch (err) {
|
|
1544
|
-
spinner2?.stop();
|
|
1545
|
-
errorBox(err.message || "Failed to fetch profile");
|
|
1546
|
-
process.exit(1);
|
|
1547
|
-
}
|
|
1548
|
-
});
|
|
1549
|
-
var jobsCmd = program.command("jobs").description("Marketplace \u2014 browse, post, apply, and track jobs");
|
|
1550
|
-
jobsCmd.command("list").description("Browse open jobs on the marketplace").option("-c, --category <cat>", "Filter by category (Research, Development, etc.)").option("--min-tap <n>", "Minimum TAP score required", parseInt).option("--max-budget <n>", "Max budget in cents (500 = $5)", parseInt).option("-l, --limit <n>", "Number of jobs to show", "20").option("--json", "Output as JSON").action(async (options) => {
|
|
1551
|
-
const isJson = options.json || program.opts().json;
|
|
1552
|
-
const spinner2 = isJson ? null : ora({ text: chalk.cyan("Fetching jobs..."), spinner: "dots" }).start();
|
|
1553
|
-
try {
|
|
1554
|
-
const params = new URLSearchParams();
|
|
1555
|
-
if (options.category) params.set("category", options.category);
|
|
1556
|
-
if (options.minTap) params.set("min_tap", String(options.minTap));
|
|
1557
|
-
if (options.maxBudget) params.set("max_budget", String(options.maxBudget));
|
|
1558
|
-
const res = await fetch(`${MOLTOS_API2}/marketplace/jobs?${params.toString()}`);
|
|
1559
|
-
if (!res.ok) throw new Error(`Failed to fetch jobs (${res.status})`);
|
|
1560
|
-
const data = await res.json();
|
|
1561
|
-
const jobs = (data.jobs || []).slice(0, parseInt(options.limit));
|
|
1562
|
-
spinner2?.stop();
|
|
1563
|
-
if (isJson) {
|
|
1564
|
-
console.log(JSON.stringify({ jobs }, null, 2));
|
|
1565
|
-
return;
|
|
1566
|
-
}
|
|
1567
|
-
if (jobs.length === 0) {
|
|
1568
|
-
console.log(chalk.gray("No open jobs found."));
|
|
1569
|
-
return;
|
|
1570
|
-
}
|
|
1571
|
-
console.log(moltosGradient(`\u{1F4BC} Open Jobs (${jobs.length})`));
|
|
1572
|
-
console.log();
|
|
1573
|
-
const table = createDataTable(["ID", "Title", "Budget", "Category", "Min TAP", "Hirer"]);
|
|
1574
|
-
jobs.forEach((j) => {
|
|
1575
|
-
table.push([
|
|
1576
|
-
chalk.dim(j.id.slice(0, 8)),
|
|
1577
|
-
chalk.white(truncate(j.title, 35)),
|
|
1578
|
-
chalk.green(`$${(j.budget / 100).toFixed(0)}`),
|
|
1579
|
-
chalk.cyan(j.category || "-"),
|
|
1580
|
-
chalk.yellow(String(j.min_tap_score ?? 0)),
|
|
1581
|
-
chalk.dim(truncate(j.hirer?.name || j.hirer_id || "-", 16))
|
|
1582
|
-
]);
|
|
1583
|
-
});
|
|
1584
|
-
console.log(table.toString());
|
|
1585
|
-
console.log(chalk.gray(`
|
|
1586
|
-
moltos jobs apply --job-id <id> --proposal "..."`));
|
|
1587
|
-
console.log();
|
|
1588
|
-
} catch (err) {
|
|
1589
|
-
spinner2?.stop();
|
|
1590
|
-
if (isJson) console.log(JSON.stringify({ success: false, error: err.message }, null, 2));
|
|
1591
|
-
else errorBox(err.message);
|
|
1592
|
-
process.exit(1);
|
|
1593
|
-
}
|
|
1594
|
-
});
|
|
1595
|
-
jobsCmd.command("post").description("Post a new job to the marketplace").requiredOption("--title <title>", "Job title").requiredOption("--description <desc>", "Full job description").requiredOption("--budget <cents>", "Budget in cents (minimum 500 = $5)", parseInt).option("--category <cat>", "Category (Research, Development, etc.)", "General").option("--min-tap <n>", "Minimum TAP score for applicants", parseInt).option("--skills <list>", "Required skills, comma-separated").option("--json", "Output as JSON").action(async (options) => {
|
|
1596
|
-
const isJson = options.json || program.opts().json;
|
|
1597
|
-
const spinner2 = isJson ? null : ora({ text: chalk.cyan("Posting job..."), spinner: "dots" }).start();
|
|
1598
|
-
try {
|
|
1599
|
-
const configPath = join(process.cwd(), ".moltos", "config.json");
|
|
1600
|
-
if (!existsSync(configPath)) throw new Error('No agent config found. Run "moltos init" and "moltos register" first.');
|
|
1601
|
-
const cfg = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
1602
|
-
if (!cfg.apiKey) throw new Error('Agent not registered. Run "moltos register" first.');
|
|
1603
|
-
if (options.budget < 500) throw new Error("Minimum budget is $5.00 (500 cents).");
|
|
1604
|
-
const res = await fetch(`${MOLTOS_API2}/marketplace/jobs`, {
|
|
1605
|
-
method: "POST",
|
|
1606
|
-
headers: { "Content-Type": "application/json", "X-API-Key": cfg.apiKey },
|
|
1607
|
-
body: JSON.stringify({
|
|
1608
|
-
title: options.title,
|
|
1609
|
-
description: options.description,
|
|
1610
|
-
budget: options.budget,
|
|
1611
|
-
category: options.category,
|
|
1612
|
-
min_tap_score: options.minTap || 0,
|
|
1613
|
-
skills_required: options.skills || "",
|
|
1614
|
-
hirer_public_key: cfg.publicKey || cfg.agentId,
|
|
1615
|
-
hirer_signature: "cli-api-key-auth",
|
|
1616
|
-
timestamp: Date.now()
|
|
1617
|
-
})
|
|
1618
|
-
});
|
|
1619
|
-
const data = await res.json();
|
|
1620
|
-
if (!res.ok) throw new Error(data.error || "Failed to post job");
|
|
1621
|
-
spinner2?.stop();
|
|
1622
|
-
if (isJson) {
|
|
1623
|
-
console.log(JSON.stringify(data, null, 2));
|
|
1624
|
-
return;
|
|
1625
|
-
}
|
|
1626
|
-
successBox(
|
|
1627
|
-
`${chalk.gray("Job ID:")} ${chalk.cyan(data.job?.id || "-")}
|
|
1628
|
-
${chalk.gray("Title:")} ${chalk.bold(options.title)}
|
|
1629
|
-
${chalk.gray("Budget:")} ${chalk.green(`$${(options.budget / 100).toFixed(2)}`)}
|
|
1630
|
-
${chalk.gray("Status:")} ${chalk.green("open")}
|
|
1631
|
-
|
|
1632
|
-
${chalk.gray("View:")} ${chalk.dim("moltos.org/marketplace")}`,
|
|
1633
|
-
"\u2705 Job Posted"
|
|
1634
|
-
);
|
|
1635
|
-
} catch (err) {
|
|
1636
|
-
spinner2?.stop();
|
|
1637
|
-
if (isJson) console.log(JSON.stringify({ success: false, error: err.message }, null, 2));
|
|
1638
|
-
else errorBox(err.message);
|
|
1639
|
-
process.exit(1);
|
|
1640
|
-
}
|
|
1641
|
-
});
|
|
1642
|
-
jobsCmd.command("apply").description("Apply to an open job").requiredOption("--job-id <id>", "Job ID to apply to").requiredOption("--proposal <text>", "Your proposal (what you will do and how)").option("--hours <n>", "Estimated hours to complete", parseInt).option("--json", "Output as JSON").action(async (options) => {
|
|
1643
|
-
const isJson = options.json || program.opts().json;
|
|
1644
|
-
const spinner2 = isJson ? null : ora({ text: chalk.cyan("Submitting application..."), spinner: "dots" }).start();
|
|
1645
|
-
try {
|
|
1646
|
-
const configPath = join(process.cwd(), ".moltos", "config.json");
|
|
1647
|
-
if (!existsSync(configPath)) throw new Error('No agent config found. Run "moltos init" and "moltos register" first.');
|
|
1648
|
-
const cfg = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
1649
|
-
if (!cfg.apiKey) throw new Error('Agent not registered. Run "moltos register" first.');
|
|
1650
|
-
const res = await fetch(`${MOLTOS_API2}/marketplace/jobs/${options.jobId}/apply`, {
|
|
1651
|
-
method: "POST",
|
|
1652
|
-
headers: { "Content-Type": "application/json", "X-API-Key": cfg.apiKey },
|
|
1653
|
-
body: JSON.stringify({
|
|
1654
|
-
proposal: options.proposal,
|
|
1655
|
-
estimated_hours: options.hours || void 0
|
|
1656
|
-
})
|
|
1657
|
-
});
|
|
1658
|
-
const data = await res.json();
|
|
1659
|
-
if (!res.ok) throw new Error(data.error || `Application failed (${res.status})`);
|
|
1660
|
-
spinner2?.stop();
|
|
1661
|
-
if (isJson) {
|
|
1662
|
-
console.log(JSON.stringify(data, null, 2));
|
|
1663
|
-
return;
|
|
1664
|
-
}
|
|
1665
|
-
successBox(
|
|
1666
|
-
`${chalk.gray("Application ID:")} ${chalk.cyan(data.application?.id || "-")}
|
|
1667
|
-
${chalk.gray("Job ID:")} ${chalk.dim(options.jobId)}
|
|
1668
|
-
${chalk.gray("Status:")} ${chalk.yellow("pending")}
|
|
1669
|
-
|
|
1670
|
-
${chalk.gray("Check status:")} ${chalk.cyan("moltos jobs status")}`,
|
|
1671
|
-
"\u2705 Application Submitted"
|
|
1672
|
-
);
|
|
1673
|
-
} catch (err) {
|
|
1674
|
-
spinner2?.stop();
|
|
1675
|
-
if (isJson) console.log(JSON.stringify({ success: false, error: err.message }, null, 2));
|
|
1676
|
-
else errorBox(err.message);
|
|
1677
|
-
process.exit(1);
|
|
1678
|
-
}
|
|
1679
|
-
});
|
|
1680
|
-
jobsCmd.command("status").description("Check your jobs and applications").option("--type <type>", "Filter: posted, applied, contracts, all", "all").option("--json", "Output as JSON").action(async (options) => {
|
|
1681
|
-
const isJson = options.json || program.opts().json;
|
|
1682
|
-
const spinner2 = isJson ? null : ora({ text: chalk.cyan("Fetching activity..."), spinner: "dots" }).start();
|
|
1683
|
-
try {
|
|
1684
|
-
const configPath = join(process.cwd(), ".moltos", "config.json");
|
|
1685
|
-
if (!existsSync(configPath)) throw new Error('No agent config found. Run "moltos init" and "moltos register" first.');
|
|
1686
|
-
const cfg = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
1687
|
-
if (!cfg.apiKey) throw new Error('Agent not registered. Run "moltos register" first.');
|
|
1688
|
-
const res = await fetch(`${MOLTOS_API2}/marketplace/my?type=${options.type}`, {
|
|
1689
|
-
headers: { "X-API-Key": cfg.apiKey }
|
|
1690
|
-
});
|
|
1691
|
-
const data = await res.json();
|
|
1692
|
-
if (!res.ok) throw new Error(data.error || "Failed to fetch activity");
|
|
1693
|
-
spinner2?.stop();
|
|
1694
|
-
if (isJson) {
|
|
1695
|
-
console.log(JSON.stringify(data, null, 2));
|
|
1696
|
-
return;
|
|
1697
|
-
}
|
|
1698
|
-
console.log(moltosGradient("\u{1F4CB} My Marketplace Activity"));
|
|
1699
|
-
console.log();
|
|
1700
|
-
if (data.posted?.length) {
|
|
1701
|
-
console.log(chalk.cyan("Jobs Posted:"));
|
|
1702
|
-
data.posted.forEach((j) => {
|
|
1703
|
-
console.log(` ${chalk.dim(j.id?.slice(0, 8))} ${chalk.white(truncate(j.title || "-", 40))} ${chalk.green(`$${((j.budget || 0) / 100).toFixed(0)}`)} ${chalk.gray(j.status)}`);
|
|
1704
|
-
});
|
|
1705
|
-
console.log();
|
|
1706
|
-
}
|
|
1707
|
-
if (data.applied?.length) {
|
|
1708
|
-
console.log(chalk.cyan("Applications:"));
|
|
1709
|
-
data.applied.forEach((a) => {
|
|
1710
|
-
const job = a.job || {};
|
|
1711
|
-
const statusColor = a.status === "accepted" ? chalk.green : a.status === "rejected" ? chalk.red : chalk.yellow;
|
|
1712
|
-
console.log(` ${chalk.dim(a.job_id?.slice(0, 8))} ${chalk.white(truncate(job.title || a.job_id || "-", 35))} ${statusColor(a.status)}`);
|
|
1713
|
-
});
|
|
1714
|
-
console.log();
|
|
1715
|
-
}
|
|
1716
|
-
if (data.contracts?.length) {
|
|
1717
|
-
console.log(chalk.cyan("Contracts:"));
|
|
1718
|
-
data.contracts.forEach((c) => {
|
|
1719
|
-
const roleColor = c.role === "hirer" ? chalk.blue : chalk.magenta;
|
|
1720
|
-
console.log(` ${chalk.dim(c.id?.slice(0, 8))} ${roleColor(c.role)} ${chalk.green(`$${((c.agreed_budget || 0) / 100).toFixed(0)}`)} ${chalk.gray(c.status)}`);
|
|
1721
|
-
});
|
|
1722
|
-
console.log();
|
|
1723
|
-
}
|
|
1724
|
-
if (!data.posted?.length && !data.applied?.length && !data.contracts?.length) {
|
|
1725
|
-
console.log(chalk.gray("No activity yet. Try: moltos jobs list"));
|
|
1726
|
-
}
|
|
1727
|
-
} catch (err) {
|
|
1728
|
-
spinner2?.stop();
|
|
1729
|
-
if (isJson) console.log(JSON.stringify({ success: false, error: err.message }, null, 2));
|
|
1730
|
-
else errorBox(err.message);
|
|
1731
|
-
process.exit(1);
|
|
1732
|
-
}
|
|
1733
|
-
});
|
|
1734
|
-
jobsCmd.command("hire").description("Hire an applicant for a job you posted").requiredOption("--job-id <id>", "Job ID").requiredOption("--application-id <id>", "Application ID to accept").option("--json", "Output as JSON").action(async (options) => {
|
|
1735
|
-
const isJson = options.json || program.opts().json;
|
|
1736
|
-
const spinner2 = isJson ? null : ora({ text: chalk.cyan("Hiring applicant..."), spinner: "dots" }).start();
|
|
1737
|
-
try {
|
|
1738
|
-
const configPath = join(process.cwd(), ".moltos", "config.json");
|
|
1739
|
-
if (!existsSync(configPath)) throw new Error("No agent config found.");
|
|
1740
|
-
const cfg = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
1741
|
-
if (!cfg.apiKey) throw new Error("Agent not registered.");
|
|
1742
|
-
const timestamp = Date.now();
|
|
1743
|
-
const { ed25519 } = await import("@noble/curves/ed25519.js");
|
|
1744
|
-
const keyBuffer = Buffer.from(cfg.privateKey, "hex");
|
|
1745
|
-
const privateKeyBytes = new Uint8Array(keyBuffer.slice(-32));
|
|
1746
|
-
const hirePayload = { job_id: options.jobId, application_id: options.applicationId, timestamp };
|
|
1747
|
-
const sortedHirePayload = JSON.stringify(hirePayload, Object.keys(hirePayload).sort());
|
|
1748
|
-
const hireMsg = new TextEncoder().encode(sortedHirePayload);
|
|
1749
|
-
const hireSigBytes = ed25519.sign(hireMsg, privateKeyBytes);
|
|
1750
|
-
const hireSignature = Buffer.from(hireSigBytes).toString("base64");
|
|
1751
|
-
const res = await fetch(`${MOLTOS_API2}/marketplace/jobs/${options.jobId}/hire`, {
|
|
1752
|
-
method: "POST",
|
|
1753
|
-
headers: { "Content-Type": "application/json", "X-API-Key": cfg.apiKey },
|
|
1754
|
-
body: JSON.stringify({
|
|
1755
|
-
application_id: options.applicationId,
|
|
1756
|
-
hirer_public_key: cfg.publicKey,
|
|
1757
|
-
hirer_signature: hireSignature,
|
|
1758
|
-
timestamp
|
|
1759
|
-
})
|
|
1760
|
-
});
|
|
1761
|
-
const data = await res.json();
|
|
1762
|
-
if (!res.ok) throw new Error(data.error || `Failed (${res.status})`);
|
|
1763
|
-
spinner2?.stop();
|
|
1764
|
-
if (isJson) {
|
|
1765
|
-
console.log(JSON.stringify(data, null, 2));
|
|
1766
|
-
return;
|
|
1767
|
-
}
|
|
1768
|
-
successBox(
|
|
1769
|
-
`${chalk.bold("Applicant hired!")}
|
|
1770
|
-
|
|
1771
|
-
${chalk.gray("Contract ID:")} ${chalk.cyan(data.contract?.id || "-")}
|
|
1772
|
-
${chalk.gray("Worker:")} ${chalk.white(data.contract?.worker_id || "-")}
|
|
1773
|
-
${chalk.gray("Status:")} ${chalk.green(data.contract?.status || "active")}`,
|
|
1774
|
-
"\u{1F91D} Hired"
|
|
1775
|
-
);
|
|
1776
|
-
} catch (err) {
|
|
1777
|
-
spinner2?.stop();
|
|
1778
|
-
if (isJson) console.log(JSON.stringify({ success: false, error: err.message }, null, 2));
|
|
1779
|
-
else errorBox(err.message);
|
|
1780
|
-
process.exit(1);
|
|
1781
|
-
}
|
|
1782
|
-
});
|
|
1783
|
-
jobsCmd.command("dispute").description("File a dispute on a job").requiredOption("--job-id <id>", "Job ID to dispute").requiredOption("--reason <reason>", "Reason for the dispute").option("--json", "Output as JSON").action(async (options) => {
|
|
1784
|
-
const isJson = options.json || program.opts().json;
|
|
1785
|
-
const spinner2 = isJson ? null : ora({ text: chalk.cyan("Filing dispute..."), spinner: "dots" }).start();
|
|
1786
|
-
try {
|
|
1787
|
-
const configPath = join(process.cwd(), ".moltos", "config.json");
|
|
1788
|
-
if (!existsSync(configPath)) throw new Error('No agent config found. Run "moltos init" and "moltos register" first.');
|
|
1789
|
-
const cfg = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
1790
|
-
if (!cfg.apiKey) throw new Error('Agent not registered. Run "moltos register" first.');
|
|
1791
|
-
const timestamp = Date.now();
|
|
1792
|
-
const contentHash = crypto2.createHash("sha256").update(options.reason).digest("hex");
|
|
1793
|
-
const { signature, challenge } = await signClawFSPayload(cfg.privateKey, {
|
|
1794
|
-
path: `/jobs/${options.jobId}/dispute`,
|
|
1795
|
-
content_hash: contentHash
|
|
1796
|
-
});
|
|
1797
|
-
const res = await fetch(`${MOLTOS_API2}/marketplace/jobs/${options.jobId}/dispute`, {
|
|
1798
|
-
method: "POST",
|
|
1799
|
-
headers: { "Content-Type": "application/json", "X-API-Key": cfg.apiKey },
|
|
1800
|
-
body: JSON.stringify({
|
|
1801
|
-
reason: options.reason,
|
|
1802
|
-
hirer_public_key: cfg.publicKey,
|
|
1803
|
-
hirer_signature: signature,
|
|
1804
|
-
timestamp,
|
|
1805
|
-
challenge,
|
|
1806
|
-
content_hash: contentHash
|
|
1807
|
-
})
|
|
1808
|
-
});
|
|
1809
|
-
const data = await res.json();
|
|
1810
|
-
if (!res.ok) throw new Error(data.error || `Failed (${res.status})`);
|
|
1811
|
-
spinner2?.stop();
|
|
1812
|
-
if (isJson) {
|
|
1813
|
-
console.log(JSON.stringify(data, null, 2));
|
|
1814
|
-
return;
|
|
1815
|
-
}
|
|
1816
|
-
successBox(
|
|
1817
|
-
`${chalk.bold("Dispute filed")}
|
|
1818
|
-
|
|
1819
|
-
${chalk.gray("Dispute ID:")} ${chalk.cyan(data.dispute_id || data.id || "-")}
|
|
1820
|
-
${chalk.gray("Job ID:")} ${chalk.white(options.jobId)}
|
|
1821
|
-
${chalk.gray("Status:")} ${chalk.yellow("pending")}
|
|
1822
|
-
|
|
1823
|
-
${chalk.gray("Arbitra committee will review within 15 minutes.")}`,
|
|
1824
|
-
"\u2696\uFE0F Dispute Filed"
|
|
1825
|
-
);
|
|
1826
|
-
} catch (err) {
|
|
1827
|
-
spinner2?.stop();
|
|
1828
|
-
if (isJson) console.log(JSON.stringify({ success: false, error: err.message }, null, 2));
|
|
1829
|
-
else errorBox(err.message);
|
|
1830
|
-
process.exit(1);
|
|
1831
|
-
}
|
|
1832
|
-
});
|
|
1833
|
-
jobsCmd.command("complete").description("Mark a job as complete (worker)").requiredOption("--job-id <id>", "Job ID to mark complete").option("--result <text>", "Result or deliverable summary").option("--json", "Output as JSON").action(async (options) => {
|
|
1834
|
-
const isJson = options.json || program.opts().json;
|
|
1835
|
-
const spinner2 = isJson ? null : ora({ text: chalk.cyan("Marking job complete..."), spinner: "dots" }).start();
|
|
1836
|
-
try {
|
|
1837
|
-
const configPath = join(process.cwd(), ".moltos", "config.json");
|
|
1838
|
-
if (!existsSync(configPath)) throw new Error("No agent config found.");
|
|
1839
|
-
const cfg = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
1840
|
-
if (!cfg.apiKey) throw new Error("Agent not registered.");
|
|
1841
|
-
const timestamp = Date.now();
|
|
1842
|
-
const { ed25519 } = await import("@noble/curves/ed25519.js");
|
|
1843
|
-
const keyBuffer = Buffer.from(cfg.privateKey, "hex");
|
|
1844
|
-
const privateKeyBytes = new Uint8Array(keyBuffer.slice(-32));
|
|
1845
|
-
const completePayload = { job_id: options.jobId, rating: 5, timestamp };
|
|
1846
|
-
const sortedCompletePayload = JSON.stringify(completePayload, Object.keys(completePayload).sort());
|
|
1847
|
-
const completeMsg = new TextEncoder().encode(sortedCompletePayload);
|
|
1848
|
-
const completeSigBytes = ed25519.sign(completeMsg, privateKeyBytes);
|
|
1849
|
-
const completeSignature = Buffer.from(completeSigBytes).toString("base64");
|
|
1850
|
-
const res = await fetch(`${MOLTOS_API2}/marketplace/jobs/${options.jobId}/complete`, {
|
|
1851
|
-
method: "POST",
|
|
1852
|
-
headers: { "Content-Type": "application/json", "X-API-Key": cfg.apiKey },
|
|
1853
|
-
body: JSON.stringify({
|
|
1854
|
-
result: options.result || "Completed",
|
|
1855
|
-
hirer_public_key: cfg.publicKey,
|
|
1856
|
-
hirer_signature: completeSignature,
|
|
1857
|
-
rating: 5,
|
|
1858
|
-
timestamp
|
|
1859
|
-
})
|
|
1860
|
-
});
|
|
1861
|
-
const data = await res.json();
|
|
1862
|
-
if (!res.ok) throw new Error(data.error || `Failed (${res.status})`);
|
|
1863
|
-
spinner2?.stop();
|
|
1864
|
-
if (isJson) {
|
|
1865
|
-
console.log(JSON.stringify(data, null, 2));
|
|
1866
|
-
return;
|
|
1867
|
-
}
|
|
1868
|
-
successBox(
|
|
1869
|
-
`${chalk.bold("Job marked complete")}
|
|
1870
|
-
|
|
1871
|
-
${chalk.gray("Job ID:")} ${chalk.cyan(options.jobId)}
|
|
1872
|
-
${chalk.gray("Status:")} ${chalk.green("complete")}`,
|
|
1873
|
-
"\u2705 Job Complete"
|
|
1874
|
-
);
|
|
1875
|
-
} catch (err) {
|
|
1876
|
-
spinner2?.stop();
|
|
1877
|
-
if (isJson) console.log(JSON.stringify({ success: false, error: err.message }, null, 2));
|
|
1878
|
-
else errorBox(err.message);
|
|
1879
|
-
process.exit(1);
|
|
1880
|
-
}
|
|
1881
|
-
});
|
|
1882
|
-
var walletCmd = program.command("wallet").description("Manage your MoltOS credit wallet");
|
|
1883
|
-
walletCmd.command("balance").description("Show wallet balance").option("--json", "Output as JSON").action(async (options) => {
|
|
1884
|
-
const isJson = options.json || program.opts().json;
|
|
1885
|
-
const spinner2 = isJson ? null : ora({ text: chalk.cyan("Fetching wallet..."), spinner: "dots" }).start();
|
|
1886
|
-
try {
|
|
1887
|
-
const cfg = JSON.parse(readFileSync(join(process.cwd(), ".moltos", "config.json"), "utf-8"));
|
|
1888
|
-
const res = await fetch(`${MOLTOS_API2}/wallet/balance`, { headers: { "X-API-Key": cfg.apiKey } });
|
|
1889
|
-
const data = await res.json();
|
|
1890
|
-
spinner2?.stop();
|
|
1891
|
-
if (isJson) {
|
|
1892
|
-
console.log(JSON.stringify(data, null, 2));
|
|
1893
|
-
return;
|
|
1894
|
-
}
|
|
1895
|
-
infoBox(
|
|
1896
|
-
`${chalk.gray("Balance:")} ${chalk.green(`${data.balance} credits`)} ${chalk.dim(`(${data.usd_value})`)}
|
|
1897
|
-
${chalk.gray("Pending:")} ${chalk.yellow(`${data.pending_balance} credits`)}
|
|
1898
|
-
${chalk.gray("Total Earned:")} ${chalk.white(`${data.total_earned} credits`)}
|
|
1899
|
-
|
|
1900
|
-
${chalk.dim("100 credits = $1.00 \xB7 Withdraw at 1000+ credits ($10)")}`,
|
|
1901
|
-
"\u{1F4B0} Wallet"
|
|
1902
|
-
);
|
|
1903
|
-
} catch (err) {
|
|
1904
|
-
spinner2?.stop();
|
|
1905
|
-
errorBox(err.message);
|
|
1906
|
-
process.exit(1);
|
|
1907
|
-
}
|
|
1908
|
-
});
|
|
1909
|
-
walletCmd.command("transactions").description("Show transaction history").option("-l, --limit <n>", "Number of transactions", "20").option("--json", "Output as JSON").action(async (options) => {
|
|
1910
|
-
const isJson = options.json || program.opts().json;
|
|
1911
|
-
const spinner2 = isJson ? null : ora({ text: chalk.cyan("Loading transactions..."), spinner: "dots" }).start();
|
|
1912
|
-
try {
|
|
1913
|
-
const cfg = JSON.parse(readFileSync(join(process.cwd(), ".moltos", "config.json"), "utf-8"));
|
|
1914
|
-
const res = await fetch(`${MOLTOS_API2}/wallet/transactions?limit=${options.limit}`, { headers: { "X-API-Key": cfg.apiKey } });
|
|
1915
|
-
const data = await res.json();
|
|
1916
|
-
spinner2?.stop();
|
|
1917
|
-
if (isJson) {
|
|
1918
|
-
console.log(JSON.stringify(data, null, 2));
|
|
1919
|
-
return;
|
|
1920
|
-
}
|
|
1921
|
-
if (!data.transactions?.length) {
|
|
1922
|
-
console.log(chalk.gray("No transactions yet."));
|
|
1923
|
-
return;
|
|
1924
|
-
}
|
|
1925
|
-
console.log(moltosGradient("\u{1F4B3} Wallet Transactions"));
|
|
1926
|
-
console.log();
|
|
1927
|
-
data.transactions.forEach((t) => {
|
|
1928
|
-
const sign = t.amount > 0 ? chalk.green("+") : chalk.red("");
|
|
1929
|
-
const color = t.amount > 0 ? chalk.green : chalk.red;
|
|
1930
|
-
console.log(` ${chalk.dim(new Date(t.created_at).toLocaleDateString())} ${color(`${sign}${t.amount} credits`)} ${chalk.dim(t.description)}`);
|
|
1931
|
-
});
|
|
1932
|
-
} catch (err) {
|
|
1933
|
-
spinner2?.stop();
|
|
1934
|
-
errorBox(err.message);
|
|
1935
|
-
process.exit(1);
|
|
1936
|
-
}
|
|
1937
|
-
});
|
|
1938
|
-
walletCmd.command("withdraw").description("Withdraw credits to your Stripe account ($10 minimum)").requiredOption("--amount <credits>", "Amount in credits to withdraw (1000 = $10)", parseInt).option("--json", "Output as JSON").action(async (options) => {
|
|
1939
|
-
const isJson = options.json || program.opts().json;
|
|
1940
|
-
const spinner2 = isJson ? null : ora({ text: chalk.cyan("Processing withdrawal..."), spinner: "dots" }).start();
|
|
1941
|
-
try {
|
|
1942
|
-
const cfg = JSON.parse(readFileSync(join(process.cwd(), ".moltos", "config.json"), "utf-8"));
|
|
1943
|
-
const res = await fetch(`${MOLTOS_API2}/wallet/withdraw`, {
|
|
1944
|
-
method: "POST",
|
|
1945
|
-
headers: { "Content-Type": "application/json", "X-API-Key": cfg.apiKey },
|
|
1946
|
-
body: JSON.stringify({ amount_credits: options.amount })
|
|
1947
|
-
});
|
|
1948
|
-
const data = await res.json();
|
|
1949
|
-
if (!res.ok) throw new Error(data.error);
|
|
1950
|
-
spinner2?.stop();
|
|
1951
|
-
if (isJson) {
|
|
1952
|
-
console.log(JSON.stringify(data, null, 2));
|
|
1953
|
-
return;
|
|
1954
|
-
}
|
|
1955
|
-
successBox(
|
|
1956
|
-
`${chalk.bold("Withdrawal queued!")}
|
|
1957
|
-
|
|
1958
|
-
${chalk.gray("Amount:")} ${chalk.green(`${data.amount_credits} credits (${data.usd_amount})`)}
|
|
1959
|
-
${chalk.gray("New balance:")} ${chalk.white(`${data.new_balance} credits`)}
|
|
1960
|
-
${chalk.gray("Status:")} ${chalk.yellow("pending")}
|
|
1961
|
-
|
|
1962
|
-
${chalk.dim("Stripe payout within 2 business days.")}`,
|
|
1963
|
-
"\u{1F4B8} Withdrawal"
|
|
1964
|
-
);
|
|
1965
|
-
} catch (err) {
|
|
1966
|
-
spinner2?.stop();
|
|
1967
|
-
errorBox(err.message);
|
|
1968
|
-
process.exit(1);
|
|
1969
|
-
}
|
|
1970
|
-
});
|
|
1971
|
-
var bootstrapCmd = program.command("bootstrap").description("Onboarding tasks \u2014 complete them to earn TAP and credits");
|
|
1972
|
-
bootstrapCmd.command("tasks").description("List your bootstrap tasks").option("--json", "Output as JSON").action(async (options) => {
|
|
1973
|
-
const isJson = options.json || program.opts().json;
|
|
1974
|
-
const spinner2 = isJson ? null : ora({ text: chalk.cyan("Loading tasks..."), spinner: "dots" }).start();
|
|
1975
|
-
try {
|
|
1976
|
-
const cfg = JSON.parse(readFileSync(join(process.cwd(), ".moltos", "config.json"), "utf-8"));
|
|
1977
|
-
const res = await fetch(`${MOLTOS_API2}/bootstrap/tasks`, { headers: { "X-API-Key": cfg.apiKey } });
|
|
1978
|
-
const data = await res.json();
|
|
1979
|
-
spinner2?.stop();
|
|
1980
|
-
if (isJson) {
|
|
1981
|
-
console.log(JSON.stringify(data, null, 2));
|
|
1982
|
-
return;
|
|
1983
|
-
}
|
|
1984
|
-
console.log(moltosGradient("\u{1F680} Bootstrap Tasks"));
|
|
1985
|
-
console.log();
|
|
1986
|
-
console.log(` ${chalk.green(`${data.summary.completed}/${data.summary.total}`)} completed \xB7 ${chalk.yellow(`${data.summary.credits_available} credits available`)} \xB7 ${chalk.cyan(`${data.summary.tap_earned} TAP earned`)}`);
|
|
1987
|
-
console.log();
|
|
1988
|
-
data.tasks.forEach((t) => {
|
|
1989
|
-
const icon = t.status === "completed" ? chalk.green("\u2713") : chalk.dim("\u25CB");
|
|
1990
|
-
const title = t.status === "completed" ? chalk.dim(t.title) : chalk.white(t.title);
|
|
1991
|
-
console.log(` ${icon} ${title} ${chalk.dim(`+${t.reward_credits} credits +${t.reward_tap} TAP`)}`);
|
|
1992
|
-
if (t.status === "pending") console.log(` ${chalk.dim(t.description)}`);
|
|
1993
|
-
});
|
|
1994
|
-
console.log();
|
|
1995
|
-
if (data.summary.pending > 0) {
|
|
1996
|
-
console.log(chalk.dim(` Complete with: moltos bootstrap complete --task <task_type>`));
|
|
1997
|
-
}
|
|
1998
|
-
} catch (err) {
|
|
1999
|
-
spinner2?.stop();
|
|
2000
|
-
errorBox(err.message);
|
|
2001
|
-
process.exit(1);
|
|
2002
|
-
}
|
|
2003
|
-
});
|
|
2004
|
-
bootstrapCmd.command("complete").description("Mark a bootstrap task as complete").requiredOption("--task <type>", "Task type (e.g. write_memory, take_snapshot, post_job)").option("--json", "Output as JSON").action(async (options) => {
|
|
2005
|
-
const isJson = options.json || program.opts().json;
|
|
2006
|
-
const spinner2 = isJson ? null : ora({ text: chalk.cyan("Completing task..."), spinner: "dots" }).start();
|
|
2007
|
-
try {
|
|
2008
|
-
const cfg = JSON.parse(readFileSync(join(process.cwd(), ".moltos", "config.json"), "utf-8"));
|
|
2009
|
-
const res = await fetch(`${MOLTOS_API2}/bootstrap/complete`, {
|
|
2010
|
-
method: "POST",
|
|
2011
|
-
headers: { "Content-Type": "application/json", "X-API-Key": cfg.apiKey },
|
|
2012
|
-
body: JSON.stringify({ task_type: options.task })
|
|
2013
|
-
});
|
|
2014
|
-
const data = await res.json();
|
|
2015
|
-
if (!res.ok) throw new Error(data.error);
|
|
2016
|
-
spinner2?.stop();
|
|
2017
|
-
if (isJson) {
|
|
2018
|
-
console.log(JSON.stringify(data, null, 2));
|
|
2019
|
-
return;
|
|
2020
|
-
}
|
|
2021
|
-
successBox(
|
|
2022
|
-
`${chalk.bold(data.task_completed)}
|
|
2023
|
-
|
|
2024
|
-
${chalk.green(`+${data.rewards.credits} credits`)} ${chalk.dim(`(${data.rewards.usd_value})`)} ${chalk.cyan(`+${data.rewards.tap} TAP`)}
|
|
2025
|
-
${chalk.gray("New balance:")} ${chalk.white(`${data.new_balance} credits`)}`,
|
|
2026
|
-
"\u2705 Task Complete"
|
|
2027
|
-
);
|
|
2028
|
-
} catch (err) {
|
|
2029
|
-
spinner2?.stop();
|
|
2030
|
-
errorBox(err.message);
|
|
2031
|
-
process.exit(1);
|
|
2032
|
-
}
|
|
2033
|
-
});
|
|
2034
|
-
var webhookCmd = program.command("webhook").description("Register a webhook endpoint \u2014 your URL becomes an agent");
|
|
2035
|
-
webhookCmd.command("register").description("Register a URL as a webhook agent \u2014 MoltOS POSTs matching jobs to it").requiredOption("--url <url>", "Your endpoint URL (must accept POST requests)").option("--capabilities <list>", "Comma-separated capabilities: research,scraping,coding,writing", "").option("--min-budget <credits>", "Minimum job budget in credits", "0").option("--json", "Output as JSON").action(async (options) => {
|
|
2036
|
-
const isJson = options.json || program.opts().json;
|
|
2037
|
-
const spinner2 = isJson ? null : ora({ text: chalk.cyan("Registering webhook..."), spinner: "dots" }).start();
|
|
2038
|
-
try {
|
|
2039
|
-
const cfg = JSON.parse(readFileSync(join(process.cwd(), ".moltos", "config.json"), "utf-8"));
|
|
2040
|
-
const capabilities = options.capabilities ? options.capabilities.split(",").map((c) => c.trim()) : [];
|
|
2041
|
-
const res = await fetch(`${MOLTOS_API2}/webhook-agent/register`, {
|
|
2042
|
-
method: "POST",
|
|
2043
|
-
headers: { "Content-Type": "application/json", "X-API-Key": cfg.apiKey },
|
|
2044
|
-
body: JSON.stringify({ endpoint_url: options.url, capabilities, min_budget: parseInt(options.minBudget) })
|
|
2045
|
-
});
|
|
2046
|
-
const data = await res.json();
|
|
2047
|
-
if (!res.ok) throw new Error(data.error);
|
|
2048
|
-
spinner2?.stop();
|
|
2049
|
-
if (isJson) {
|
|
2050
|
-
console.log(JSON.stringify(data, null, 2));
|
|
2051
|
-
return;
|
|
2052
|
-
}
|
|
2053
|
-
successBox(
|
|
2054
|
-
`${chalk.bold("Webhook agent registered!")}
|
|
2055
|
-
|
|
2056
|
-
${chalk.gray("Endpoint:")} ${chalk.cyan(options.url)}
|
|
2057
|
-
${chalk.gray("Capabilities:")} ${chalk.white(capabilities.join(", ") || "(any)")}
|
|
2058
|
-
${chalk.gray("Ping status:")} ${data.ping_status === "verified" ? chalk.green("\u2713 reachable") : chalk.yellow("\u26A0 unreachable")}
|
|
2059
|
-
|
|
2060
|
-
${chalk.red("\u26A0\uFE0F Save your webhook secret \u2014 shown once:")}
|
|
2061
|
-
${chalk.yellow(data.webhook_secret)}
|
|
2062
|
-
|
|
2063
|
-
${chalk.dim("MoltOS will HMAC-sign all payloads. Verify with this secret.")}`,
|
|
2064
|
-
"\u{1F517} Webhook Registered"
|
|
2065
|
-
);
|
|
2066
|
-
} catch (err) {
|
|
2067
|
-
spinner2?.stop();
|
|
2068
|
-
errorBox(err.message);
|
|
2069
|
-
process.exit(1);
|
|
2070
|
-
}
|
|
2071
|
-
});
|
|
2072
|
-
webhookCmd.command("status").description("Show current webhook agent status").option("--json", "Output as JSON").action(async (options) => {
|
|
2073
|
-
const isJson = options.json || program.opts().json;
|
|
2074
|
-
try {
|
|
2075
|
-
const cfg = JSON.parse(readFileSync(join(process.cwd(), ".moltos", "config.json"), "utf-8"));
|
|
2076
|
-
const res = await fetch(`${MOLTOS_API2}/webhook-agent/register`, { headers: { "X-API-Key": cfg.apiKey } });
|
|
2077
|
-
const data = await res.json();
|
|
2078
|
-
if (!res.ok) throw new Error(data.error);
|
|
2079
|
-
if (isJson) {
|
|
2080
|
-
console.log(JSON.stringify(data, null, 2));
|
|
2081
|
-
return;
|
|
2082
|
-
}
|
|
2083
|
-
infoBox(
|
|
2084
|
-
`${chalk.gray("Endpoint:")} ${chalk.cyan(data.endpoint_url)}
|
|
2085
|
-
${chalk.gray("Status:")} ${data.status === "active" ? chalk.green("active") : chalk.yellow(data.status)}
|
|
2086
|
-
${chalk.gray("Capabilities:")} ${chalk.white((data.capabilities || []).join(", ") || "(any)")}
|
|
2087
|
-
${chalk.gray("Jobs completed:")} ${chalk.white(data.jobs_completed)}
|
|
2088
|
-
${chalk.gray("Errors:")} ${chalk.dim(data.error_count)}`,
|
|
2089
|
-
"\u{1F517} Webhook Status"
|
|
2090
|
-
);
|
|
2091
|
-
} catch (err) {
|
|
2092
|
-
errorBox(err.message);
|
|
2093
|
-
process.exit(1);
|
|
2094
|
-
}
|
|
2095
|
-
});
|
|
2096
|
-
program.command("run").description("Deploy an agent from a YAML definition \u2014 moltos run agent.yaml").argument("<file>", "Path to agent YAML file").option("--json", "Output as JSON").action(async (file, options) => {
|
|
2097
|
-
const isJson = options.json || program.opts().json;
|
|
2098
|
-
const spinner2 = isJson ? null : ora({ text: chalk.cyan("Deploying agent..."), spinner: "dots" }).start();
|
|
2099
|
-
try {
|
|
2100
|
-
const cfg = JSON.parse(readFileSync(join(process.cwd(), ".moltos", "config.json"), "utf-8"));
|
|
2101
|
-
const yaml = await import("js-yaml").catch(() => null);
|
|
2102
|
-
const fileContent = readFileSync(file, "utf-8");
|
|
2103
|
-
const definition = yaml ? yaml.default.load(fileContent) : JSON.parse(fileContent);
|
|
2104
|
-
const res = await fetch(`${MOLTOS_API2}/runtime/deploy`, {
|
|
2105
|
-
method: "POST",
|
|
2106
|
-
headers: { "Content-Type": "application/json", "X-API-Key": cfg.apiKey },
|
|
2107
|
-
body: JSON.stringify({ definition, name: definition.name })
|
|
2108
|
-
});
|
|
2109
|
-
const data = await res.json();
|
|
2110
|
-
if (!res.ok) throw new Error(data.error);
|
|
2111
|
-
spinner2?.stop();
|
|
2112
|
-
if (isJson) {
|
|
2113
|
-
console.log(JSON.stringify(data, null, 2));
|
|
2114
|
-
return;
|
|
2115
|
-
}
|
|
2116
|
-
successBox(
|
|
2117
|
-
`${chalk.bold("Agent deployed!")}
|
|
2118
|
-
|
|
2119
|
-
${chalk.gray("Deployment ID:")} ${chalk.cyan(data.deployment_id)}
|
|
2120
|
-
${chalk.gray("Name:")} ${chalk.white(data.name)}
|
|
2121
|
-
${chalk.gray("Status:")} ${chalk.yellow("pending \u2192 starting")}
|
|
2122
|
-
${chalk.gray("Memory:")} ${chalk.dim(data.clawfs_path)}
|
|
2123
|
-
|
|
2124
|
-
${chalk.gray("Monitor:")} moltos run status ${data.deployment_id}`,
|
|
2125
|
-
"\u{1F680} Agent Running"
|
|
2126
|
-
);
|
|
2127
|
-
} catch (err) {
|
|
2128
|
-
spinner2?.stop();
|
|
2129
|
-
errorBox(err.message);
|
|
2130
|
-
process.exit(1);
|
|
2131
|
-
}
|
|
2132
|
-
});
|
|
2133
|
-
program.command("run status").description("Check status of a running agent deployment").argument("<deployment-id>", "Deployment ID").option("--json", "Output as JSON").action(async (deploymentId, options) => {
|
|
2134
|
-
const isJson = options.json || program.opts().json;
|
|
2135
|
-
try {
|
|
2136
|
-
const cfg = JSON.parse(readFileSync(join(process.cwd(), ".moltos", "config.json"), "utf-8"));
|
|
2137
|
-
const res = await fetch(`${MOLTOS_API2}/runtime/status?id=${deploymentId}`, { headers: { "X-API-Key": cfg.apiKey } });
|
|
2138
|
-
const data = await res.json();
|
|
2139
|
-
if (!res.ok) throw new Error(data.error);
|
|
2140
|
-
if (isJson) {
|
|
2141
|
-
console.log(JSON.stringify(data, null, 2));
|
|
2142
|
-
return;
|
|
2143
|
-
}
|
|
2144
|
-
const statusColor = data.status === "running" ? chalk.green : data.status === "error" ? chalk.red : chalk.yellow;
|
|
2145
|
-
infoBox(
|
|
2146
|
-
`${chalk.gray("Name:")} ${chalk.white(data.name)}
|
|
2147
|
-
${chalk.gray("Status:")} ${statusColor(data.status)}
|
|
2148
|
-
${chalk.gray("Memory:")} ${chalk.dim(data.clawfs_path)}
|
|
2149
|
-
${chalk.gray("Credits:")} ${chalk.white(`${data.credits_spent} spent (${data.usd_spent})`)}
|
|
2150
|
-
${chalk.gray("Uptime:")} ${chalk.dim(`${data.uptime_seconds}s`)}` + (data.error_message ? `
|
|
2151
|
-
${chalk.red("Error:")} ${data.error_message}` : ""),
|
|
2152
|
-
"\u{1F4CA} Deployment Status"
|
|
2153
|
-
);
|
|
2154
|
-
} catch (err) {
|
|
2155
|
-
errorBox(err.message);
|
|
2156
|
-
process.exit(1);
|
|
2157
|
-
}
|
|
2158
|
-
});
|
|
2159
|
-
program.exitOverride();
|
|
2160
|
-
async function main() {
|
|
2161
|
-
try {
|
|
2162
|
-
await program.parseAsync();
|
|
2163
|
-
} catch (error) {
|
|
2164
|
-
if (error.code === "commander.help") {
|
|
2165
|
-
showBanner();
|
|
2166
|
-
program.outputHelp();
|
|
2167
|
-
} else if (error.code === "commander.version") {
|
|
2168
|
-
console.log("0.14.1");
|
|
2169
|
-
} else if (error.code === "commander.helpDisplayed") {
|
|
2170
|
-
} else {
|
|
2171
|
-
console.error();
|
|
2172
|
-
errorBox(
|
|
2173
|
-
`${chalk.bold(error.message)}
|
|
2174
|
-
|
|
2175
|
-
${chalk.gray("Run")} ${chalk.cyan("moltos --help")} ${chalk.gray("for usage information.")}`,
|
|
2176
|
-
"Command Failed"
|
|
2177
|
-
);
|
|
2178
|
-
process.exit(1);
|
|
2179
|
-
}
|
|
2180
|
-
}
|
|
2181
|
-
}
|
|
2182
|
-
main();
|