solguard-cli 0.1.0 → 0.1.1
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 +75 -69
- package/bin/solguard-cli.js +1 -1
- package/package.json +2 -2
- package/src/config.js +9 -2
- package/src/localCatalog.js +103 -0
- package/src/wizard.js +50 -308
package/README.md
CHANGED
|
@@ -1,69 +1,75 @@
|
|
|
1
|
-
# solguard-cli
|
|
2
|
-
|
|
3
|
-
Run SolGuard security agents from your terminal.
|
|
4
|
-
|
|
5
|
-
## Install / run
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
npx solguard-cli
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
No global install required. Works with `npm install -g solguard-cli` as well.
|
|
12
|
-
|
|
13
|
-
Requires **Node.js 18+**.
|
|
14
|
-
|
|
15
|
-
## Modes
|
|
16
|
-
|
|
17
|
-
| Mode | Description |
|
|
18
|
-
|------|-------------|
|
|
19
|
-
| **
|
|
20
|
-
| **
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
1
|
+
# solguard-cli
|
|
2
|
+
|
|
3
|
+
Run SolGuard security agents from your terminal.
|
|
4
|
+
|
|
5
|
+
## Install / run
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx solguard-cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
No global install required. Works with `npm install -g solguard-cli` as well.
|
|
12
|
+
|
|
13
|
+
Requires **Node.js 18+**.
|
|
14
|
+
|
|
15
|
+
## Modes
|
|
16
|
+
|
|
17
|
+
| Mode | Description |
|
|
18
|
+
|------|-------------|
|
|
19
|
+
| **Local (recommended)** | Runs on your machine. No SolGuard free credits. Bring your own OpenAI / Anthropic / Gemini key for AI agents. |
|
|
20
|
+
| **Free** | Optional — uses SolGuard API + signup credits (same as website). |
|
|
21
|
+
|
|
22
|
+
Local mode does **not** call the SolGuard backend for execution. Keys stay on your machine (`~/.solguard/config.json`).
|
|
23
|
+
|
|
24
|
+
## Services (local)
|
|
25
|
+
|
|
26
|
+
- Cyber Security Consultant (BYOK LLM)
|
|
27
|
+
- Solana Token Verification (BYOK LLM + Helius for on-chain)
|
|
28
|
+
- Smart Contract Security Audit (Helius)
|
|
29
|
+
- Wallet Verification (Helius)
|
|
30
|
+
- Web3 dApp Frontend Verification
|
|
31
|
+
- OpenClaw AI Agent Verification
|
|
32
|
+
|
|
33
|
+
## Config
|
|
34
|
+
|
|
35
|
+
`~/.solguard/config.json` (mode `600` on Unix):
|
|
36
|
+
|
|
37
|
+
```json
|
|
38
|
+
{
|
|
39
|
+
"cliInstallId": "<uuid>",
|
|
40
|
+
"token": "<jwt>",
|
|
41
|
+
"baseUrl": "https://www.solguard.space/api",
|
|
42
|
+
"savedKeys": {}
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Local tips
|
|
47
|
+
|
|
48
|
+
- **BYOK LLM:** Cyber Security Consultant, Solana Token Verification
|
|
49
|
+
- **On-chain agents:** set `HELIUS_API_KEY` in your environment
|
|
50
|
+
- Free mode only: needs network access to `https://www.solguard.space/api`
|
|
51
|
+
|
|
52
|
+
## Local development
|
|
53
|
+
|
|
54
|
+
```powershell
|
|
55
|
+
$env:SOLGUARD_API = "http://localhost:3000/api"
|
|
56
|
+
node bin/solguard-cli.js
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Local **premium** mode works without the API. Free mode needs `npm run dev`.
|
|
60
|
+
|
|
61
|
+
## Publish (maintainers)
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
npm login
|
|
65
|
+
cd packages/solguard-cli
|
|
66
|
+
npm publish --access public
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Bump `version` in `package.json` before republishing.
|
|
70
|
+
|
|
71
|
+
## QA
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
node scripts/qa-cli-local-premium.mjs
|
|
75
|
+
```
|
package/bin/solguard-cli.js
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "solguard-cli",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Run SolGuard security agents from your terminal — free
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Run SolGuard security agents locally from your terminal (BYOK) — optional free SolGuard credits",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"solguard-cli": "./bin/solguard-cli.js"
|
package/src/config.js
CHANGED
|
@@ -6,6 +6,10 @@ import crypto from "crypto";
|
|
|
6
6
|
export const DEFAULT_BASE_URL = "https://www.solguard.space/api";
|
|
7
7
|
export const LOCAL_DEV_URL = "http://localhost:3000/api";
|
|
8
8
|
|
|
9
|
+
function isLocalUrl(url) {
|
|
10
|
+
return /localhost|127\.0\.0\.1/i.test(url || "");
|
|
11
|
+
}
|
|
12
|
+
|
|
9
13
|
/** Resolve API base: --api flag > SOLGUARD_API env > config > local dev probe > production. */
|
|
10
14
|
export async function resolveBaseUrl(config, cliArgs = process.argv.slice(2)) {
|
|
11
15
|
const flagIdx = cliArgs.findIndex((a) => a === "--api" || a === "-a");
|
|
@@ -15,13 +19,16 @@ export async function resolveBaseUrl(config, cliArgs = process.argv.slice(2)) {
|
|
|
15
19
|
if (process.env.SOLGUARD_API) {
|
|
16
20
|
return process.env.SOLGUARD_API.replace(/\/$/, "");
|
|
17
21
|
}
|
|
22
|
+
// Prefer a saved non-default URL, but never stick on dead localhost.
|
|
18
23
|
if (config?.baseUrl && config.baseUrl !== DEFAULT_BASE_URL) {
|
|
19
|
-
|
|
24
|
+
const saved = config.baseUrl.replace(/\/$/, "");
|
|
25
|
+
if (!isLocalUrl(saved)) return saved;
|
|
26
|
+
if (await probeApi(saved)) return saved;
|
|
20
27
|
}
|
|
21
28
|
if (await probeApi(LOCAL_DEV_URL)) {
|
|
22
29
|
return LOCAL_DEV_URL;
|
|
23
30
|
}
|
|
24
|
-
return
|
|
31
|
+
return DEFAULT_BASE_URL;
|
|
25
32
|
}
|
|
26
33
|
|
|
27
34
|
async function probeApi(baseUrl) {
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in catalog for premium local runs — no SolGuard API required.
|
|
3
|
+
* Kept in sync with lib/solguard/services.js + formSchemas.js for the 6 local agents.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const LOCAL_SERVICES = [
|
|
7
|
+
{
|
|
8
|
+
id: "cyber-consultant",
|
|
9
|
+
name: "Cyber Security Consultant",
|
|
10
|
+
category: "Advisory",
|
|
11
|
+
primaryAgentId: "ai-consultant",
|
|
12
|
+
inputs: [
|
|
13
|
+
{
|
|
14
|
+
key: "query",
|
|
15
|
+
type: "text",
|
|
16
|
+
label: "Your Question",
|
|
17
|
+
placeholder: "e.g. How do honeypot tokens lock liquidity?",
|
|
18
|
+
example: "What is the most common Solana token rug-pull pattern in 2025?",
|
|
19
|
+
multiline: true,
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
id: "solana-token-verification",
|
|
25
|
+
name: "Solana Token Verification",
|
|
26
|
+
category: "Token",
|
|
27
|
+
primaryAgentId: "solana-token-verification",
|
|
28
|
+
inputs: [
|
|
29
|
+
{
|
|
30
|
+
key: "tokenAddress",
|
|
31
|
+
label: "Token Mint Address",
|
|
32
|
+
placeholder: "e.g. DezXAZ8z7Pnr...",
|
|
33
|
+
example: "DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263",
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
id: "smart-contract-audit",
|
|
39
|
+
name: "Smart Contract Security Audit",
|
|
40
|
+
category: "Contract",
|
|
41
|
+
primaryAgentId: "contract-security",
|
|
42
|
+
inputs: [
|
|
43
|
+
{
|
|
44
|
+
key: "tokenAddress",
|
|
45
|
+
label: "Token Mint Address",
|
|
46
|
+
placeholder: "e.g. DezXAZ8z7Pnr...",
|
|
47
|
+
example: "DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263",
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
id: "wallet-verification",
|
|
53
|
+
name: "Wallet Verification",
|
|
54
|
+
category: "Wallet",
|
|
55
|
+
primaryAgentId: "wallet-verification",
|
|
56
|
+
inputs: [
|
|
57
|
+
{
|
|
58
|
+
key: "walletAddress",
|
|
59
|
+
label: "Wallet Address",
|
|
60
|
+
placeholder: "Solana wallet pubkey",
|
|
61
|
+
example: "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM",
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
id: "dapp-frontend-verification",
|
|
67
|
+
name: "Web3 dApp Frontend Verification",
|
|
68
|
+
category: "Web",
|
|
69
|
+
primaryAgentId: "website-security",
|
|
70
|
+
inputs: [
|
|
71
|
+
{
|
|
72
|
+
key: "url",
|
|
73
|
+
type: "url",
|
|
74
|
+
label: "Website URL",
|
|
75
|
+
placeholder: "https://example.com",
|
|
76
|
+
example: "https://solana.com",
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
id: "openclaw-ai-agent-verification",
|
|
82
|
+
name: "OpenClaw AI Agent Verification",
|
|
83
|
+
category: "AI Agent",
|
|
84
|
+
primaryAgentId: "openclaw-ai-agent-verification",
|
|
85
|
+
inputs: [
|
|
86
|
+
{
|
|
87
|
+
key: "config",
|
|
88
|
+
type: "json",
|
|
89
|
+
label: "Agent Config (JSON)",
|
|
90
|
+
placeholder: '{"name":"support-agent","gateway":{"auth":"none"}}',
|
|
91
|
+
example: '{"name":"support-agent","gateway":{"auth":"none"}}',
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
},
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
export function listLocalServices() {
|
|
98
|
+
return LOCAL_SERVICES;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function getLocalService(serviceId) {
|
|
102
|
+
return LOCAL_SERVICES.find((s) => s.id === serviceId) || null;
|
|
103
|
+
}
|
package/src/wizard.js
CHANGED
|
@@ -1,483 +1,225 @@
|
|
|
1
1
|
import * as p from "@clack/prompts";
|
|
2
|
-
|
|
3
2
|
import fs from "fs";
|
|
4
3
|
|
|
5
4
|
import { ensureConfig, updateConfig, saveConfig, resolveBaseUrl } from "./config.js";
|
|
6
|
-
|
|
7
5
|
import {
|
|
8
|
-
|
|
9
6
|
ensureAuth,
|
|
10
|
-
|
|
11
7
|
fetchConfig,
|
|
12
|
-
|
|
13
|
-
fetchServices,
|
|
14
|
-
|
|
15
|
-
fetchServiceDetail,
|
|
16
|
-
|
|
17
8
|
fetchMe,
|
|
18
|
-
|
|
19
9
|
runAgentFree,
|
|
20
|
-
|
|
21
10
|
} from "./api.js";
|
|
22
|
-
|
|
23
|
-
import { formatReport, printReport, reportFilename } from "./output.js";
|
|
24
|
-
|
|
11
|
+
import { printReport, reportFilename } from "./output.js";
|
|
25
12
|
import {
|
|
26
|
-
|
|
27
13
|
listByokProviders,
|
|
28
|
-
|
|
29
14
|
runAgentLocalPremium,
|
|
30
|
-
|
|
31
|
-
canRunLocalPremium,
|
|
32
|
-
|
|
33
15
|
agentUsesByokLlm,
|
|
34
|
-
|
|
35
16
|
localPremiumRequiresHelius,
|
|
36
|
-
|
|
37
17
|
} from "./premium.js";
|
|
38
|
-
|
|
39
|
-
|
|
18
|
+
import { listLocalServices } from "./localCatalog.js";
|
|
40
19
|
|
|
41
20
|
function isCancel(v) {
|
|
42
|
-
|
|
43
21
|
if (p.isCancel(v)) {
|
|
44
|
-
|
|
45
22
|
p.cancel("Cancelled.");
|
|
46
|
-
|
|
47
23
|
process.exit(0);
|
|
48
|
-
|
|
49
24
|
}
|
|
50
|
-
|
|
51
25
|
return v;
|
|
52
|
-
|
|
53
26
|
}
|
|
54
27
|
|
|
55
|
-
|
|
56
|
-
|
|
57
28
|
async function promptInputs(fields) {
|
|
58
|
-
|
|
59
29
|
const inputs = {};
|
|
60
|
-
|
|
61
30
|
for (const field of fields) {
|
|
62
|
-
|
|
63
31
|
const opts = {
|
|
64
|
-
|
|
65
32
|
message: field.label || field.key,
|
|
66
|
-
|
|
67
33
|
placeholder: field.placeholder || field.example || "",
|
|
68
|
-
|
|
69
34
|
};
|
|
70
|
-
|
|
71
35
|
if (field.type === "json") {
|
|
72
|
-
|
|
73
36
|
opts.message += " (paste JSON)";
|
|
74
|
-
|
|
75
37
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
38
|
+
let val = isCancel(await p.text({ ...opts, defaultValue: "" }));
|
|
39
|
+
while (!val?.trim()) {
|
|
40
|
+
p.log.warn("Please enter a value.");
|
|
41
|
+
val = isCancel(await p.text({ ...opts, defaultValue: "" }));
|
|
105
42
|
}
|
|
106
|
-
|
|
43
|
+
inputs[field.key] = val.trim();
|
|
107
44
|
}
|
|
108
|
-
|
|
109
45
|
return inputs;
|
|
110
|
-
|
|
111
46
|
}
|
|
112
47
|
|
|
113
|
-
|
|
114
|
-
|
|
115
48
|
function showReport(report, opts) {
|
|
116
|
-
|
|
117
49
|
p.log.step("Report");
|
|
118
|
-
|
|
119
50
|
printReport(report, opts);
|
|
120
|
-
|
|
121
51
|
}
|
|
122
52
|
|
|
123
|
-
|
|
124
|
-
|
|
125
53
|
async function promptSaveReport(report) {
|
|
126
|
-
|
|
127
54
|
const save = isCancel(
|
|
128
|
-
|
|
129
55
|
await p.confirm({ message: "Save result to file?", initialValue: false })
|
|
130
|
-
|
|
131
56
|
);
|
|
132
|
-
|
|
133
57
|
if (!save) return;
|
|
134
|
-
|
|
135
58
|
const name = reportFilename();
|
|
136
|
-
|
|
137
59
|
fs.writeFileSync(name, JSON.stringify(report, null, 2), "utf8");
|
|
138
|
-
|
|
139
60
|
p.log.success(`Saved to ${name}`);
|
|
140
|
-
|
|
141
61
|
}
|
|
142
62
|
|
|
143
|
-
|
|
144
|
-
|
|
145
63
|
async function promptByokCredentials(config) {
|
|
146
|
-
|
|
147
64
|
const providers = listByokProviders();
|
|
148
|
-
|
|
149
65
|
const providerId = isCancel(
|
|
150
|
-
|
|
151
66
|
await p.select({
|
|
152
|
-
|
|
153
67
|
message: "LLM provider",
|
|
154
|
-
|
|
155
68
|
options: providers.map((pr) => ({ value: pr.id, label: pr.label })),
|
|
156
|
-
|
|
157
69
|
})
|
|
158
|
-
|
|
159
70
|
);
|
|
160
71
|
|
|
161
|
-
|
|
162
|
-
|
|
163
72
|
let apiKey = config.savedKeys?.[providerId];
|
|
164
|
-
|
|
165
73
|
if (apiKey) {
|
|
166
|
-
|
|
167
74
|
const reuse = isCancel(
|
|
168
|
-
|
|
169
75
|
await p.confirm({
|
|
170
|
-
|
|
171
76
|
message: `Use saved ${providerId} key from ~/.solguard/config.json?`,
|
|
172
|
-
|
|
173
77
|
initialValue: true,
|
|
174
|
-
|
|
175
78
|
})
|
|
176
|
-
|
|
177
79
|
);
|
|
178
|
-
|
|
179
80
|
if (!reuse) apiKey = null;
|
|
180
|
-
|
|
181
81
|
}
|
|
182
82
|
|
|
183
|
-
|
|
184
|
-
|
|
185
83
|
if (!apiKey) {
|
|
186
|
-
|
|
187
84
|
apiKey = isCancel(await p.password({ message: "API key (input hidden)" }));
|
|
188
|
-
|
|
189
85
|
const saveKey = isCancel(
|
|
190
|
-
|
|
191
86
|
await p.confirm({ message: "Save this key for future runs?", initialValue: false })
|
|
192
|
-
|
|
193
87
|
);
|
|
194
|
-
|
|
195
88
|
if (saveKey) {
|
|
196
|
-
|
|
197
89
|
config.savedKeys = { ...config.savedKeys, [providerId]: apiKey };
|
|
198
|
-
|
|
199
90
|
saveConfig(config);
|
|
200
|
-
|
|
201
91
|
p.log.info("Key saved to ~/.solguard/config.json (mode 600)");
|
|
202
|
-
|
|
203
92
|
}
|
|
204
|
-
|
|
205
93
|
}
|
|
206
94
|
|
|
207
|
-
|
|
208
|
-
|
|
209
95
|
return { providerId, apiKey };
|
|
210
|
-
|
|
211
96
|
}
|
|
212
97
|
|
|
213
|
-
|
|
214
|
-
|
|
215
98
|
function premiumModeLabel(agentId) {
|
|
216
|
-
|
|
217
99
|
if (agentUsesByokLlm(agentId)) {
|
|
218
|
-
|
|
219
|
-
return "Premium (Beta) — your own API key (runs locally, key never sent to SolGuard)";
|
|
220
|
-
|
|
100
|
+
return "Local (recommended) — your own API key, no SolGuard credits";
|
|
221
101
|
}
|
|
222
|
-
|
|
223
|
-
return "Premium (Beta) — run locally (no SolGuard backend)";
|
|
224
|
-
|
|
102
|
+
return "Local (recommended) — runs on your machine, no SolGuard credits";
|
|
225
103
|
}
|
|
226
104
|
|
|
227
|
-
|
|
228
|
-
|
|
229
105
|
export async function runWizard() {
|
|
230
|
-
|
|
231
106
|
p.intro("SolGuard CLI · Beta");
|
|
232
|
-
|
|
233
|
-
|
|
107
|
+
p.log.info("Default: run locally on your machine. SolGuard free credits are optional.");
|
|
234
108
|
|
|
235
109
|
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
|
-
|
|
110
|
+
const services = listLocalServices();
|
|
272
111
|
|
|
273
112
|
const serviceId = isCancel(
|
|
274
|
-
|
|
275
113
|
await p.select({
|
|
276
|
-
|
|
277
114
|
message: "Select a service",
|
|
278
|
-
|
|
279
115
|
options: services.map((s) => ({
|
|
280
|
-
|
|
281
116
|
value: s.id,
|
|
282
|
-
|
|
283
117
|
label: s.name,
|
|
284
|
-
|
|
285
118
|
hint: s.category,
|
|
286
|
-
|
|
287
119
|
})),
|
|
288
|
-
|
|
289
120
|
})
|
|
290
|
-
|
|
291
121
|
);
|
|
292
122
|
|
|
293
|
-
|
|
294
|
-
|
|
295
123
|
const service = services.find((s) => s.id === serviceId);
|
|
296
|
-
|
|
297
|
-
const detail = await fetchServiceDetail(config.baseUrl, serviceId);
|
|
298
|
-
|
|
299
124
|
const agentId = service.primaryAgentId;
|
|
125
|
+
const inputFields = service.inputs || [];
|
|
300
126
|
|
|
301
|
-
const
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
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
|
-
|
|
127
|
+
const mode = isCancel(
|
|
128
|
+
await p.select({
|
|
129
|
+
message: "Run mode",
|
|
130
|
+
initialValue: "premium",
|
|
131
|
+
options: [
|
|
132
|
+
{
|
|
133
|
+
value: "premium",
|
|
134
|
+
label: premiumModeLabel(agentId),
|
|
135
|
+
hint: agentUsesByokLlm(agentId) ? "BYOK" : "offline rules / RPC",
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
value: "free",
|
|
139
|
+
label: "Free — SolGuard credits (server-side)",
|
|
140
|
+
hint: "uses your signup credits",
|
|
141
|
+
},
|
|
142
|
+
],
|
|
143
|
+
})
|
|
144
|
+
);
|
|
350
145
|
|
|
351
146
|
const inputs = await promptInputs(inputFields);
|
|
352
|
-
|
|
353
147
|
const s = p.spinner();
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
148
|
let report;
|
|
358
149
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
if (mode === "premium" && supportsLocalPremium) {
|
|
362
|
-
|
|
150
|
+
if (mode === "premium") {
|
|
363
151
|
if (localPremiumRequiresHelius(agentId) && !process.env.HELIUS_API_KEY) {
|
|
364
|
-
|
|
365
152
|
p.log.warn("HELIUS_API_KEY is not set — on-chain scans will fail. Export it before running.");
|
|
366
|
-
|
|
367
153
|
}
|
|
368
154
|
|
|
369
|
-
|
|
370
|
-
|
|
371
155
|
let providerId = null;
|
|
372
|
-
|
|
373
156
|
let apiKey = null;
|
|
374
|
-
|
|
375
157
|
if (agentUsesByokLlm(agentId)) {
|
|
376
|
-
|
|
377
158
|
({ providerId, apiKey } = await promptByokCredentials(config));
|
|
378
|
-
|
|
379
159
|
}
|
|
380
160
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
: "Running locally (no SolGuard backend call)…");
|
|
388
|
-
|
|
161
|
+
s.start(
|
|
162
|
+
agentUsesByokLlm(agentId)
|
|
163
|
+
? "Running locally via your provider (no SolGuard credits)…"
|
|
164
|
+
: "Running locally (no SolGuard credits)…"
|
|
165
|
+
);
|
|
389
166
|
try {
|
|
390
|
-
|
|
391
167
|
const result = await runAgentLocalPremium(agentId, inputs, { provider: providerId, apiKey });
|
|
392
|
-
|
|
393
168
|
if (result?.error) {
|
|
394
|
-
|
|
395
169
|
s.stop("Failed");
|
|
396
|
-
|
|
397
170
|
p.log.error(result.error);
|
|
398
|
-
|
|
399
171
|
process.exit(1);
|
|
400
|
-
|
|
401
172
|
}
|
|
402
|
-
|
|
403
173
|
report = { agentId, agentName: service.name, result };
|
|
404
|
-
|
|
405
174
|
s.stop("Done");
|
|
406
|
-
|
|
407
|
-
const modeLabel = providerId ? `Premium (${providerId}, local)` : "Premium (local)";
|
|
408
|
-
|
|
175
|
+
const modeLabel = providerId ? `Local (${providerId})` : "Local";
|
|
409
176
|
showReport(report, { mode: modeLabel });
|
|
410
|
-
|
|
411
177
|
} catch (e) {
|
|
412
|
-
|
|
413
178
|
s.stop("Failed");
|
|
414
|
-
|
|
415
179
|
throw e;
|
|
416
|
-
|
|
417
180
|
}
|
|
418
|
-
|
|
419
181
|
} else {
|
|
182
|
+
config.baseUrl = await resolveBaseUrl(config);
|
|
183
|
+
updateConfig({ baseUrl: config.baseUrl });
|
|
184
|
+
if (config.baseUrl.includes("localhost")) {
|
|
185
|
+
p.log.info(`Using local API: ${config.baseUrl}`);
|
|
186
|
+
}
|
|
420
187
|
|
|
188
|
+
config = { ...config, ...(await ensureAuth(config)) };
|
|
189
|
+
updateConfig({ token: config.token });
|
|
190
|
+
|
|
191
|
+
const remoteConfig = await fetchConfig(config.baseUrl);
|
|
421
192
|
const paymentMethod = remoteConfig.testingModeFreeRuns ? "testing" : "credit";
|
|
422
193
|
|
|
423
194
|
if (paymentMethod === "credit" && (config.credits ?? 0) <= 0) {
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
195
|
+
p.log.error(
|
|
196
|
+
"No free credits left. Choose Local mode (your own API key) or top up on solguard.space."
|
|
197
|
+
);
|
|
427
198
|
process.exit(1);
|
|
428
|
-
|
|
429
199
|
}
|
|
430
200
|
|
|
431
|
-
|
|
432
|
-
|
|
433
201
|
s.start("Running via SolGuard API…");
|
|
434
|
-
|
|
435
202
|
try {
|
|
436
|
-
|
|
437
203
|
report = await runAgentFree(config.baseUrl, config.token, agentId, inputs, paymentMethod);
|
|
438
|
-
|
|
439
204
|
s.stop("Done");
|
|
440
|
-
|
|
441
205
|
const me = await fetchMe(config.baseUrl, config.token);
|
|
442
|
-
|
|
443
206
|
const creditsRemaining = me.credits;
|
|
444
|
-
|
|
445
207
|
showReport(report, {
|
|
446
|
-
|
|
447
208
|
mode: paymentMethod === "testing" ? "Free (testing mode)" : "Free (SolGuard credits)",
|
|
448
|
-
|
|
449
209
|
creditsRemaining,
|
|
450
|
-
|
|
451
210
|
});
|
|
452
|
-
|
|
453
211
|
config.credits = creditsRemaining;
|
|
454
|
-
|
|
455
212
|
updateConfig({ token: config.token, credits: creditsRemaining });
|
|
456
|
-
|
|
457
213
|
} catch (e) {
|
|
458
|
-
|
|
459
214
|
s.stop("Failed");
|
|
460
|
-
|
|
461
215
|
if (e.status === 402) {
|
|
462
|
-
|
|
463
|
-
p.log.error("Payment required — no credits left.");
|
|
464
|
-
|
|
216
|
+
p.log.error("Payment required — no credits left. Use Local mode instead.");
|
|
465
217
|
process.exit(1);
|
|
466
|
-
|
|
467
218
|
}
|
|
468
|
-
|
|
469
219
|
throw e;
|
|
470
|
-
|
|
471
220
|
}
|
|
472
|
-
|
|
473
221
|
}
|
|
474
222
|
|
|
475
|
-
|
|
476
|
-
|
|
477
223
|
await promptSaveReport(report);
|
|
478
|
-
|
|
479
224
|
p.outro("Done");
|
|
480
|
-
|
|
481
225
|
}
|
|
482
|
-
|
|
483
|
-
|