solguard-cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -0
- package/bin/solguard-cli.js +19 -0
- package/lib/solguard/agents/consultant.js +57 -0
- package/lib/solguard/agents/localRun.js +537 -0
- package/lib/solguard/aiSummary.js +127 -0
- package/lib/solguard/exploits.js +13 -0
- package/lib/solguard/llm/client.js +83 -0
- package/lib/solguard/llm/prompts/consultant.js +45 -0
- package/lib/solguard/llm/providers.js +43 -0
- package/lib/solguard/openclawAudit.js +187 -0
- package/lib/solguard/reportBuilder.js +105 -0
- package/lib/solguard/scanEngine.js +283 -0
- package/lib/solguard/verdictValidation.js +44 -0
- package/package.json +37 -0
- package/src/api.js +88 -0
- package/src/config.js +93 -0
- package/src/inputs.js +34 -0
- package/src/output.js +82 -0
- package/src/premium.js +85 -0
- package/src/wizard.js +483 -0
package/src/premium.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { createLlmClient } from "../lib/solguard/llm/client.js";
|
|
2
|
+
|
|
3
|
+
import { listByokProviders } from "../lib/solguard/llm/providers.js";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
|
|
7
|
+
runAgentLocal,
|
|
8
|
+
|
|
9
|
+
canRunLocalPremium,
|
|
10
|
+
|
|
11
|
+
agentUsesByokLlm,
|
|
12
|
+
|
|
13
|
+
localPremiumRequiresHelius,
|
|
14
|
+
|
|
15
|
+
} from "../lib/solguard/agents/localRun.js";
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
export { listByokProviders, canRunLocalPremium, agentUsesByokLlm, localPremiumRequiresHelius };
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
const PROVIDER_DATA_SOURCE = {
|
|
24
|
+
|
|
25
|
+
openai: ["OpenAI (local BYOK)"],
|
|
26
|
+
|
|
27
|
+
anthropic: ["Anthropic Claude (local BYOK)"],
|
|
28
|
+
|
|
29
|
+
gemini: ["Google Gemini (local BYOK)"],
|
|
30
|
+
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
/** @deprecated use runAgentLocalPremium */
|
|
36
|
+
|
|
37
|
+
export async function runConsultantLocal(query, { provider, apiKey }) {
|
|
38
|
+
|
|
39
|
+
return runAgentLocalPremium("ai-consultant", { query }, { provider, apiKey });
|
|
40
|
+
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
|
|
47
|
+
* Run an agent locally — no SolGuard backend calls.
|
|
48
|
+
|
|
49
|
+
* BYOK LLM key required for ai-consultant and solana-token-verification (AI verdict).
|
|
50
|
+
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
export async function runAgentLocalPremium(agentId, inputs, { provider, apiKey } = {}) {
|
|
54
|
+
|
|
55
|
+
if (!canRunLocalPremium(agentId)) {
|
|
56
|
+
|
|
57
|
+
throw new Error(`Agent "${agentId}" does not support premium local mode`);
|
|
58
|
+
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
let llmClient = null;
|
|
64
|
+
|
|
65
|
+
let dataSource = ["Local execution (CLI)"];
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
if (agentUsesByokLlm(agentId)) {
|
|
70
|
+
|
|
71
|
+
if (!apiKey) throw new Error("API key is required for this agent in premium local mode");
|
|
72
|
+
|
|
73
|
+
llmClient = createLlmClient({ provider, apiKey });
|
|
74
|
+
|
|
75
|
+
dataSource = PROVIDER_DATA_SOURCE[provider] || ["Local BYOK"];
|
|
76
|
+
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
return runAgentLocal(agentId, inputs, { llmClient, dataSource });
|
|
82
|
+
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
|
package/src/wizard.js
ADDED
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
import * as p from "@clack/prompts";
|
|
2
|
+
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
|
|
5
|
+
import { ensureConfig, updateConfig, saveConfig, resolveBaseUrl } from "./config.js";
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
|
|
9
|
+
ensureAuth,
|
|
10
|
+
|
|
11
|
+
fetchConfig,
|
|
12
|
+
|
|
13
|
+
fetchServices,
|
|
14
|
+
|
|
15
|
+
fetchServiceDetail,
|
|
16
|
+
|
|
17
|
+
fetchMe,
|
|
18
|
+
|
|
19
|
+
runAgentFree,
|
|
20
|
+
|
|
21
|
+
} from "./api.js";
|
|
22
|
+
|
|
23
|
+
import { formatReport, printReport, reportFilename } from "./output.js";
|
|
24
|
+
|
|
25
|
+
import {
|
|
26
|
+
|
|
27
|
+
listByokProviders,
|
|
28
|
+
|
|
29
|
+
runAgentLocalPremium,
|
|
30
|
+
|
|
31
|
+
canRunLocalPremium,
|
|
32
|
+
|
|
33
|
+
agentUsesByokLlm,
|
|
34
|
+
|
|
35
|
+
localPremiumRequiresHelius,
|
|
36
|
+
|
|
37
|
+
} from "./premium.js";
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
function isCancel(v) {
|
|
42
|
+
|
|
43
|
+
if (p.isCancel(v)) {
|
|
44
|
+
|
|
45
|
+
p.cancel("Cancelled.");
|
|
46
|
+
|
|
47
|
+
process.exit(0);
|
|
48
|
+
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return v;
|
|
52
|
+
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
async function promptInputs(fields) {
|
|
58
|
+
|
|
59
|
+
const inputs = {};
|
|
60
|
+
|
|
61
|
+
for (const field of fields) {
|
|
62
|
+
|
|
63
|
+
const opts = {
|
|
64
|
+
|
|
65
|
+
message: field.label || field.key,
|
|
66
|
+
|
|
67
|
+
placeholder: field.placeholder || field.example || "",
|
|
68
|
+
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
if (field.type === "json") {
|
|
72
|
+
|
|
73
|
+
opts.message += " (paste JSON)";
|
|
74
|
+
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (field.multiline || field.type === "text" || field.type === "json") {
|
|
78
|
+
|
|
79
|
+
let val = isCancel(await p.text({ ...opts, defaultValue: "" }));
|
|
80
|
+
|
|
81
|
+
while (!val?.trim()) {
|
|
82
|
+
|
|
83
|
+
p.log.warn("Please enter a value.");
|
|
84
|
+
|
|
85
|
+
val = isCancel(await p.text({ ...opts, defaultValue: "" }));
|
|
86
|
+
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
inputs[field.key] = val.trim();
|
|
90
|
+
|
|
91
|
+
} else {
|
|
92
|
+
|
|
93
|
+
let val = isCancel(await p.text({ ...opts, defaultValue: "" }));
|
|
94
|
+
|
|
95
|
+
while (!val?.trim()) {
|
|
96
|
+
|
|
97
|
+
p.log.warn("Please enter a value.");
|
|
98
|
+
|
|
99
|
+
val = isCancel(await p.text({ ...opts, defaultValue: "" }));
|
|
100
|
+
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
inputs[field.key] = val.trim();
|
|
104
|
+
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return inputs;
|
|
110
|
+
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
function showReport(report, opts) {
|
|
116
|
+
|
|
117
|
+
p.log.step("Report");
|
|
118
|
+
|
|
119
|
+
printReport(report, opts);
|
|
120
|
+
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
async function promptSaveReport(report) {
|
|
126
|
+
|
|
127
|
+
const save = isCancel(
|
|
128
|
+
|
|
129
|
+
await p.confirm({ message: "Save result to file?", initialValue: false })
|
|
130
|
+
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
if (!save) return;
|
|
134
|
+
|
|
135
|
+
const name = reportFilename();
|
|
136
|
+
|
|
137
|
+
fs.writeFileSync(name, JSON.stringify(report, null, 2), "utf8");
|
|
138
|
+
|
|
139
|
+
p.log.success(`Saved to ${name}`);
|
|
140
|
+
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
async function promptByokCredentials(config) {
|
|
146
|
+
|
|
147
|
+
const providers = listByokProviders();
|
|
148
|
+
|
|
149
|
+
const providerId = isCancel(
|
|
150
|
+
|
|
151
|
+
await p.select({
|
|
152
|
+
|
|
153
|
+
message: "LLM provider",
|
|
154
|
+
|
|
155
|
+
options: providers.map((pr) => ({ value: pr.id, label: pr.label })),
|
|
156
|
+
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
let apiKey = config.savedKeys?.[providerId];
|
|
164
|
+
|
|
165
|
+
if (apiKey) {
|
|
166
|
+
|
|
167
|
+
const reuse = isCancel(
|
|
168
|
+
|
|
169
|
+
await p.confirm({
|
|
170
|
+
|
|
171
|
+
message: `Use saved ${providerId} key from ~/.solguard/config.json?`,
|
|
172
|
+
|
|
173
|
+
initialValue: true,
|
|
174
|
+
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
if (!reuse) apiKey = null;
|
|
180
|
+
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
if (!apiKey) {
|
|
186
|
+
|
|
187
|
+
apiKey = isCancel(await p.password({ message: "API key (input hidden)" }));
|
|
188
|
+
|
|
189
|
+
const saveKey = isCancel(
|
|
190
|
+
|
|
191
|
+
await p.confirm({ message: "Save this key for future runs?", initialValue: false })
|
|
192
|
+
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
if (saveKey) {
|
|
196
|
+
|
|
197
|
+
config.savedKeys = { ...config.savedKeys, [providerId]: apiKey };
|
|
198
|
+
|
|
199
|
+
saveConfig(config);
|
|
200
|
+
|
|
201
|
+
p.log.info("Key saved to ~/.solguard/config.json (mode 600)");
|
|
202
|
+
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
return { providerId, apiKey };
|
|
210
|
+
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
function premiumModeLabel(agentId) {
|
|
216
|
+
|
|
217
|
+
if (agentUsesByokLlm(agentId)) {
|
|
218
|
+
|
|
219
|
+
return "Premium (Beta) — your own API key (runs locally, key never sent to SolGuard)";
|
|
220
|
+
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return "Premium (Beta) — run locally (no SolGuard backend)";
|
|
224
|
+
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
export async function runWizard() {
|
|
230
|
+
|
|
231
|
+
p.intro("SolGuard CLI · Beta");
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
let config = ensureConfig();
|
|
236
|
+
|
|
237
|
+
config.baseUrl = await resolveBaseUrl(config);
|
|
238
|
+
|
|
239
|
+
updateConfig({ baseUrl: config.baseUrl });
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
config = { ...config, ...(await ensureAuth(config)) };
|
|
244
|
+
|
|
245
|
+
updateConfig({ token: config.token });
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
if (config.baseUrl.includes("localhost")) {
|
|
250
|
+
|
|
251
|
+
p.log.info(`Using local API: ${config.baseUrl}`);
|
|
252
|
+
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
const remoteConfig = await fetchConfig(config.baseUrl);
|
|
258
|
+
|
|
259
|
+
const services = await fetchServices(config.baseUrl);
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
if (!services.length) {
|
|
264
|
+
|
|
265
|
+
p.log.error("No services returned from API.");
|
|
266
|
+
|
|
267
|
+
process.exit(1);
|
|
268
|
+
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
const serviceId = isCancel(
|
|
274
|
+
|
|
275
|
+
await p.select({
|
|
276
|
+
|
|
277
|
+
message: "Select a service",
|
|
278
|
+
|
|
279
|
+
options: services.map((s) => ({
|
|
280
|
+
|
|
281
|
+
value: s.id,
|
|
282
|
+
|
|
283
|
+
label: s.name,
|
|
284
|
+
|
|
285
|
+
hint: s.category,
|
|
286
|
+
|
|
287
|
+
})),
|
|
288
|
+
|
|
289
|
+
})
|
|
290
|
+
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
const service = services.find((s) => s.id === serviceId);
|
|
296
|
+
|
|
297
|
+
const detail = await fetchServiceDetail(config.baseUrl, serviceId);
|
|
298
|
+
|
|
299
|
+
const agentId = service.primaryAgentId;
|
|
300
|
+
|
|
301
|
+
const inputFields = detail?.agent?.inputs || [];
|
|
302
|
+
|
|
303
|
+
const supportsLocalPremium = canRunLocalPremium(agentId);
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
let mode = "free";
|
|
308
|
+
|
|
309
|
+
if (supportsLocalPremium) {
|
|
310
|
+
|
|
311
|
+
mode = isCancel(
|
|
312
|
+
|
|
313
|
+
await p.select({
|
|
314
|
+
|
|
315
|
+
message: "Run mode",
|
|
316
|
+
|
|
317
|
+
options: [
|
|
318
|
+
|
|
319
|
+
{
|
|
320
|
+
|
|
321
|
+
value: "free",
|
|
322
|
+
|
|
323
|
+
label: "Free — SolGuard credits (server-side execution)",
|
|
324
|
+
|
|
325
|
+
hint: `${config.credits ?? "?"} credits remaining`,
|
|
326
|
+
|
|
327
|
+
},
|
|
328
|
+
|
|
329
|
+
{
|
|
330
|
+
|
|
331
|
+
value: "premium",
|
|
332
|
+
|
|
333
|
+
label: premiumModeLabel(agentId),
|
|
334
|
+
|
|
335
|
+
},
|
|
336
|
+
|
|
337
|
+
],
|
|
338
|
+
|
|
339
|
+
})
|
|
340
|
+
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
} else {
|
|
344
|
+
|
|
345
|
+
p.log.info("Premium local mode is not available for this service (requires SolGuard backend). Using free credits.");
|
|
346
|
+
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
const inputs = await promptInputs(inputFields);
|
|
352
|
+
|
|
353
|
+
const s = p.spinner();
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
let report;
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
if (mode === "premium" && supportsLocalPremium) {
|
|
362
|
+
|
|
363
|
+
if (localPremiumRequiresHelius(agentId) && !process.env.HELIUS_API_KEY) {
|
|
364
|
+
|
|
365
|
+
p.log.warn("HELIUS_API_KEY is not set — on-chain scans will fail. Export it before running.");
|
|
366
|
+
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
let providerId = null;
|
|
372
|
+
|
|
373
|
+
let apiKey = null;
|
|
374
|
+
|
|
375
|
+
if (agentUsesByokLlm(agentId)) {
|
|
376
|
+
|
|
377
|
+
({ providerId, apiKey } = await promptByokCredentials(config));
|
|
378
|
+
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
s.start(agentUsesByokLlm(agentId)
|
|
384
|
+
|
|
385
|
+
? "Running locally via your provider (no SolGuard backend call)…"
|
|
386
|
+
|
|
387
|
+
: "Running locally (no SolGuard backend call)…");
|
|
388
|
+
|
|
389
|
+
try {
|
|
390
|
+
|
|
391
|
+
const result = await runAgentLocalPremium(agentId, inputs, { provider: providerId, apiKey });
|
|
392
|
+
|
|
393
|
+
if (result?.error) {
|
|
394
|
+
|
|
395
|
+
s.stop("Failed");
|
|
396
|
+
|
|
397
|
+
p.log.error(result.error);
|
|
398
|
+
|
|
399
|
+
process.exit(1);
|
|
400
|
+
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
report = { agentId, agentName: service.name, result };
|
|
404
|
+
|
|
405
|
+
s.stop("Done");
|
|
406
|
+
|
|
407
|
+
const modeLabel = providerId ? `Premium (${providerId}, local)` : "Premium (local)";
|
|
408
|
+
|
|
409
|
+
showReport(report, { mode: modeLabel });
|
|
410
|
+
|
|
411
|
+
} catch (e) {
|
|
412
|
+
|
|
413
|
+
s.stop("Failed");
|
|
414
|
+
|
|
415
|
+
throw e;
|
|
416
|
+
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
} else {
|
|
420
|
+
|
|
421
|
+
const paymentMethod = remoteConfig.testingModeFreeRuns ? "testing" : "credit";
|
|
422
|
+
|
|
423
|
+
if (paymentMethod === "credit" && (config.credits ?? 0) <= 0) {
|
|
424
|
+
|
|
425
|
+
p.log.error("No free credits left. Try premium local mode or connect a wallet on solguard.space.");
|
|
426
|
+
|
|
427
|
+
process.exit(1);
|
|
428
|
+
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
s.start("Running via SolGuard API…");
|
|
434
|
+
|
|
435
|
+
try {
|
|
436
|
+
|
|
437
|
+
report = await runAgentFree(config.baseUrl, config.token, agentId, inputs, paymentMethod);
|
|
438
|
+
|
|
439
|
+
s.stop("Done");
|
|
440
|
+
|
|
441
|
+
const me = await fetchMe(config.baseUrl, config.token);
|
|
442
|
+
|
|
443
|
+
const creditsRemaining = me.credits;
|
|
444
|
+
|
|
445
|
+
showReport(report, {
|
|
446
|
+
|
|
447
|
+
mode: paymentMethod === "testing" ? "Free (testing mode)" : "Free (SolGuard credits)",
|
|
448
|
+
|
|
449
|
+
creditsRemaining,
|
|
450
|
+
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
config.credits = creditsRemaining;
|
|
454
|
+
|
|
455
|
+
updateConfig({ token: config.token, credits: creditsRemaining });
|
|
456
|
+
|
|
457
|
+
} catch (e) {
|
|
458
|
+
|
|
459
|
+
s.stop("Failed");
|
|
460
|
+
|
|
461
|
+
if (e.status === 402) {
|
|
462
|
+
|
|
463
|
+
p.log.error("Payment required — no credits left.");
|
|
464
|
+
|
|
465
|
+
process.exit(1);
|
|
466
|
+
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
throw e;
|
|
470
|
+
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
await promptSaveReport(report);
|
|
478
|
+
|
|
479
|
+
p.outro("Done");
|
|
480
|
+
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
|