@moltbankhq/openclaw 0.1.6 → 0.1.8
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 +8 -5
- package/cli.ts +35 -12
- package/index.ts +334 -43
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -40,6 +40,8 @@ This package connects your OpenClaw agent to MoltBank's stablecoin treasury infr
|
|
|
40
40
|
|
|
41
41
|
Once set up, your agent can manage treasury operations, set per-agent spending limits, handle x402 payments, and interact with MoltBank's MCP server — all within OpenClaw.
|
|
42
42
|
|
|
43
|
+
The standalone `moltbank` CLI is for install, setup, auth status, and repair flows. Actual treasury operations should run through the installed skill wrapper scripts inside the skill directory.
|
|
44
|
+
|
|
43
45
|
## Setup
|
|
44
46
|
|
|
45
47
|
After installing, run:
|
|
@@ -48,16 +50,17 @@ After installing, run:
|
|
|
48
50
|
moltbank setup
|
|
49
51
|
```
|
|
50
52
|
|
|
51
|
-
The CLI will guide you through linking your MoltBank account via browser-based OAuth. Once approved, setup
|
|
53
|
+
The CLI will guide you through linking your MoltBank account via browser-based OAuth. Once approved, setup verifies a real authenticated balance read before reporting success.
|
|
52
54
|
|
|
53
55
|
## CLI Commands
|
|
54
56
|
|
|
55
57
|
| Command | Description |
|
|
56
58
|
|---------|-------------|
|
|
57
|
-
| `moltbank setup` | Run full setup
|
|
58
|
-
| `moltbank setup --
|
|
59
|
-
| `moltbank setup-blocking` |
|
|
60
|
-
| `moltbank
|
|
59
|
+
| `moltbank setup` | Run full setup and verify readiness |
|
|
60
|
+
| `moltbank setup --verbose` | Run setup with detailed logs |
|
|
61
|
+
| `moltbank setup-blocking` | Alias for setup |
|
|
62
|
+
| `moltbank status` | Check current auth state |
|
|
63
|
+
| `moltbank auth-status` | Alias for `moltbank status` |
|
|
61
64
|
| `moltbank sandbox-setup` | Reconfigure sandbox Docker settings |
|
|
62
65
|
| `moltbank inject-key` | Re-inject env vars from credentials |
|
|
63
66
|
| `moltbank register` | Re-register mcporter MCP server |
|
package/cli.ts
CHANGED
|
@@ -2,9 +2,9 @@ import { readFileSync } from 'fs';
|
|
|
2
2
|
import process from 'process';
|
|
3
3
|
import {
|
|
4
4
|
configureSandbox,
|
|
5
|
+
createSetupCommandLogger,
|
|
5
6
|
ensureMcporterConfig,
|
|
6
7
|
getAppBaseUrl,
|
|
7
|
-
getSetupAuthWaitMode,
|
|
8
8
|
getSkillDir,
|
|
9
9
|
injectSandboxEnv,
|
|
10
10
|
printAuthStatus,
|
|
@@ -23,6 +23,7 @@ type ParsedArgs = {
|
|
|
23
23
|
help: boolean;
|
|
24
24
|
version: boolean;
|
|
25
25
|
blocking: boolean;
|
|
26
|
+
verbose: boolean;
|
|
26
27
|
};
|
|
27
28
|
|
|
28
29
|
const HELP_TEXT = `MoltBank CLI
|
|
@@ -31,9 +32,10 @@ Usage:
|
|
|
31
32
|
moltbank <command> [options]
|
|
32
33
|
|
|
33
34
|
Commands:
|
|
34
|
-
setup Run MoltBank setup
|
|
35
|
-
setup-blocking
|
|
36
|
-
|
|
35
|
+
setup Run MoltBank setup
|
|
36
|
+
setup-blocking Alias for setup
|
|
37
|
+
status Show current MoltBank auth state
|
|
38
|
+
auth-status Alias for status
|
|
37
39
|
sandbox-setup Reconfigure sandbox Docker settings in openclaw.json
|
|
38
40
|
inject-key Re-inject sandbox env vars from credentials.json
|
|
39
41
|
register Re-register mcporter MCP config
|
|
@@ -41,14 +43,15 @@ Commands:
|
|
|
41
43
|
Options:
|
|
42
44
|
--app-base-url <url> Override MoltBank deployment URL
|
|
43
45
|
--skill-name <name> Override skill folder name
|
|
44
|
-
--blocking
|
|
46
|
+
--blocking Compatibility flag; setup already waits for approval
|
|
47
|
+
--verbose Show detailed setup logs
|
|
45
48
|
-h, --help Show help
|
|
46
49
|
-v, --version Show package version
|
|
47
50
|
|
|
48
51
|
Examples:
|
|
49
52
|
moltbank setup
|
|
50
|
-
moltbank setup --
|
|
51
|
-
moltbank
|
|
53
|
+
moltbank setup --verbose
|
|
54
|
+
moltbank status
|
|
52
55
|
moltbank register --app-base-url https://app.moltbank.bot
|
|
53
56
|
`;
|
|
54
57
|
|
|
@@ -64,6 +67,7 @@ function parseArgs(argv: string[]): ParsedArgs {
|
|
|
64
67
|
let help = false;
|
|
65
68
|
let version = false;
|
|
66
69
|
let blocking = false;
|
|
70
|
+
let verbose = false;
|
|
67
71
|
|
|
68
72
|
for (let index = 0; index < argv.length; index += 1) {
|
|
69
73
|
const arg = argv[index];
|
|
@@ -83,6 +87,11 @@ function parseArgs(argv: string[]): ParsedArgs {
|
|
|
83
87
|
continue;
|
|
84
88
|
}
|
|
85
89
|
|
|
90
|
+
if (arg === '--verbose') {
|
|
91
|
+
verbose = true;
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
|
|
86
95
|
if (arg === '--app-base-url') {
|
|
87
96
|
const value = argv[index + 1];
|
|
88
97
|
if (!value) {
|
|
@@ -125,32 +134,44 @@ function parseArgs(argv: string[]): ParsedArgs {
|
|
|
125
134
|
config,
|
|
126
135
|
help,
|
|
127
136
|
version,
|
|
128
|
-
blocking
|
|
137
|
+
blocking,
|
|
138
|
+
verbose
|
|
129
139
|
};
|
|
130
140
|
}
|
|
131
141
|
|
|
132
142
|
async function runCommand(parsed: ParsedArgs): Promise<void> {
|
|
133
143
|
const cfg = parsed.config;
|
|
134
|
-
const logger = { logger: console };
|
|
135
144
|
|
|
136
145
|
switch (parsed.command) {
|
|
137
146
|
case 'setup': {
|
|
138
|
-
const
|
|
139
|
-
|
|
147
|
+
const logger = createSetupCommandLogger({ verbose: parsed.verbose, statusCommand: 'moltbank status' });
|
|
148
|
+
try {
|
|
149
|
+
await runSetup(cfg, logger, { authWaitMode: 'blocking' });
|
|
150
|
+
} finally {
|
|
151
|
+
logger.finish();
|
|
152
|
+
}
|
|
140
153
|
return;
|
|
141
154
|
}
|
|
142
155
|
|
|
143
156
|
case 'setup-blocking': {
|
|
144
|
-
|
|
157
|
+
const logger = createSetupCommandLogger({ verbose: parsed.verbose, statusCommand: 'moltbank status' });
|
|
158
|
+
try {
|
|
159
|
+
await runSetup(cfg, logger, { authWaitMode: 'blocking' });
|
|
160
|
+
} finally {
|
|
161
|
+
logger.finish();
|
|
162
|
+
}
|
|
145
163
|
return;
|
|
146
164
|
}
|
|
147
165
|
|
|
166
|
+
case 'status':
|
|
148
167
|
case 'auth-status': {
|
|
168
|
+
const logger = { logger: console };
|
|
149
169
|
printAuthStatus(getSkillDir(cfg), getAppBaseUrl(cfg), logger);
|
|
150
170
|
return;
|
|
151
171
|
}
|
|
152
172
|
|
|
153
173
|
case 'sandbox-setup': {
|
|
174
|
+
const logger = { logger: console };
|
|
154
175
|
const changed = configureSandbox(logger);
|
|
155
176
|
if (changed) {
|
|
156
177
|
recreateSandboxAndRestart(logger);
|
|
@@ -161,6 +182,7 @@ async function runCommand(parsed: ParsedArgs): Promise<void> {
|
|
|
161
182
|
}
|
|
162
183
|
|
|
163
184
|
case 'inject-key': {
|
|
185
|
+
const logger = { logger: console };
|
|
164
186
|
const changed = injectSandboxEnv(getSkillDir(cfg), logger);
|
|
165
187
|
if (changed) {
|
|
166
188
|
recreateSandboxAndRestart(logger);
|
|
@@ -171,6 +193,7 @@ async function runCommand(parsed: ParsedArgs): Promise<void> {
|
|
|
171
193
|
}
|
|
172
194
|
|
|
173
195
|
case 'register': {
|
|
196
|
+
const logger = { logger: console };
|
|
174
197
|
ensureMcporterConfig(getSkillDir(cfg), getAppBaseUrl(cfg), logger);
|
|
175
198
|
return;
|
|
176
199
|
}
|
package/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { execSync, spawn } from 'child_process';
|
|
1
|
+
import { execSync, spawn, spawnSync } from 'child_process';
|
|
2
2
|
import { chmodSync, existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'fs';
|
|
3
3
|
import { join, dirname } from 'path';
|
|
4
4
|
import { homedir } from 'os';
|
|
@@ -32,6 +32,11 @@ interface LoggerApi {
|
|
|
32
32
|
logger: LoggerLike;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
export interface SetupCommandLoggerOptions {
|
|
36
|
+
verbose?: boolean;
|
|
37
|
+
statusCommand?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
35
40
|
interface ServiceDefinition {
|
|
36
41
|
id: string;
|
|
37
42
|
start: () => void | Promise<void>;
|
|
@@ -66,6 +71,214 @@ export type AuthWaitMode = 'blocking' | 'nonblocking';
|
|
|
66
71
|
const oauthPollers = new Map<string, ReturnType<typeof spawn>>();
|
|
67
72
|
const backgroundFinalizers = new Map<string, ReturnType<typeof spawn>>();
|
|
68
73
|
|
|
74
|
+
class SetupCommandLogger implements LoggerLike {
|
|
75
|
+
private readonly verbose: boolean;
|
|
76
|
+
private readonly statusCommand: string;
|
|
77
|
+
private setupLineOpen = false;
|
|
78
|
+
private authLink = '';
|
|
79
|
+
private authCode = '';
|
|
80
|
+
private authExpiryMinutes: number | null = null;
|
|
81
|
+
private authPromptPrinted = false;
|
|
82
|
+
private waitingLineOpen = false;
|
|
83
|
+
private setupCompletePrinted = false;
|
|
84
|
+
|
|
85
|
+
constructor(options: SetupCommandLoggerOptions = {}) {
|
|
86
|
+
this.verbose = Boolean(options.verbose);
|
|
87
|
+
this.statusCommand = options.statusCommand || 'moltbank status';
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
info(message: string): void {
|
|
91
|
+
this.handle(message, 'info');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
warn(message: string): void {
|
|
95
|
+
this.handle(message, 'warn');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
finish(): void {
|
|
99
|
+
if (this.setupLineOpen) {
|
|
100
|
+
this.writeRaw('done\n');
|
|
101
|
+
this.setupLineOpen = false;
|
|
102
|
+
}
|
|
103
|
+
if (this.waitingLineOpen && process.stdout.isTTY) {
|
|
104
|
+
this.writeRaw('\n');
|
|
105
|
+
this.waitingLineOpen = false;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private handle(message: string, level: 'info' | 'warn'): void {
|
|
110
|
+
if (this.verbose) {
|
|
111
|
+
this.finish();
|
|
112
|
+
this.writeLine(message, level);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (message === '[moltbank] MoltBank setup starting') {
|
|
117
|
+
this.startSetupLine();
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (this.captureAuthPrompt(message)) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (message === '[moltbank] waiting for approval and polling token...') {
|
|
126
|
+
this.finishSetupLine();
|
|
127
|
+
this.flushAuthPrompt();
|
|
128
|
+
this.showWaitingLine();
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const linkedOrg = this.extractLinkedOrg(message);
|
|
133
|
+
if (linkedOrg) {
|
|
134
|
+
this.finishSetupLine();
|
|
135
|
+
this.flushAuthPrompt();
|
|
136
|
+
this.resolveWaitingLine(`[moltbank] ✓ Linked to org "${linkedOrg}"`, 'info');
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (
|
|
141
|
+
message === '[moltbank] ✗ onboarding code expired or already consumed (invalid_grant)' ||
|
|
142
|
+
message === '[moltbank] ✗ onboarding poll failed or timed out'
|
|
143
|
+
) {
|
|
144
|
+
this.finishSetupLine();
|
|
145
|
+
this.flushAuthPrompt();
|
|
146
|
+
this.resolveWaitingLine('[moltbank] ✗ Code expired. Run `moltbank setup` to get a new code.', 'warn');
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (
|
|
151
|
+
message === '[moltbank] host auth not ready — complete onboarding and run setup again' ||
|
|
152
|
+
message === '[moltbank] sandbox auth not ready — complete onboarding and run setup again' ||
|
|
153
|
+
message === '[moltbank] host auth pending — startup continues without blocking channel startup' ||
|
|
154
|
+
message === '[moltbank] sandbox auth pending — startup continues without blocking channel startup' ||
|
|
155
|
+
message.startsWith('[moltbank] poll detail: ')
|
|
156
|
+
) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (message === '[moltbank] ✓ setup complete') {
|
|
161
|
+
this.finishSetupLine();
|
|
162
|
+
this.clearWaitingLine();
|
|
163
|
+
if (!this.setupCompletePrinted) {
|
|
164
|
+
this.writeLine(`[moltbank] ✓ Setup complete. Run \`${this.statusCommand}\` to verify.`, 'info');
|
|
165
|
+
this.setupCompletePrinted = true;
|
|
166
|
+
}
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (level === 'warn') {
|
|
171
|
+
this.failSetupLine();
|
|
172
|
+
this.clearWaitingLine();
|
|
173
|
+
this.writeLine(message, 'warn');
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
private startSetupLine(): void {
|
|
178
|
+
if (this.setupLineOpen) return;
|
|
179
|
+
this.writeRaw('[moltbank] Setting up MoltBank skill... ');
|
|
180
|
+
this.setupLineOpen = true;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private finishSetupLine(): void {
|
|
184
|
+
if (!this.setupLineOpen) return;
|
|
185
|
+
this.writeRaw('done\n');
|
|
186
|
+
this.setupLineOpen = false;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
private failSetupLine(): void {
|
|
190
|
+
if (!this.setupLineOpen) return;
|
|
191
|
+
this.writeRaw('failed\n');
|
|
192
|
+
this.setupLineOpen = false;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
private captureAuthPrompt(message: string): boolean {
|
|
196
|
+
const openMatch = message.match(/^\[moltbank\] 1\) Open: (.+)$/);
|
|
197
|
+
if (openMatch) {
|
|
198
|
+
this.authLink = openMatch[1];
|
|
199
|
+
return true;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const codeMatch = message.match(/^\[moltbank\] 2\) Enter code: (.+)$/);
|
|
203
|
+
if (codeMatch) {
|
|
204
|
+
this.authCode = codeMatch[1];
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const expiryMatch = message.match(/^\[moltbank\] 3\) Code expires in ~?(\d+) min$/);
|
|
209
|
+
if (expiryMatch) {
|
|
210
|
+
this.authExpiryMinutes = Number(expiryMatch[1]);
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return (
|
|
215
|
+
message === '[moltbank] ACTION REQUIRED: link this agent to your MoltBank account' ||
|
|
216
|
+
message === '[moltbank] 4) Optional: reply in chat "MoltBank done" for a live status check'
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
private flushAuthPrompt(): void {
|
|
221
|
+
if (this.authPromptPrinted || !this.authLink || !this.authCode) return;
|
|
222
|
+
this.writeLine('[moltbank]', 'info');
|
|
223
|
+
this.writeLine(`[moltbank] Link your agent → ${this.authLink}`, 'info');
|
|
224
|
+
const expirySuffix = this.authExpiryMinutes ? ` (expires in ${this.authExpiryMinutes} min)` : '';
|
|
225
|
+
this.writeLine(`[moltbank] Code: ${this.authCode}${expirySuffix}`, 'info');
|
|
226
|
+
this.writeLine('[moltbank]', 'info');
|
|
227
|
+
this.authPromptPrinted = true;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
private showWaitingLine(): void {
|
|
231
|
+
if (this.waitingLineOpen) return;
|
|
232
|
+
if (process.stdout.isTTY) {
|
|
233
|
+
this.writeRaw('[moltbank] Waiting for approval...');
|
|
234
|
+
this.waitingLineOpen = true;
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
this.writeLine('[moltbank] Waiting for approval...', 'info');
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
private resolveWaitingLine(message: string, level: 'info' | 'warn'): void {
|
|
241
|
+
if (this.waitingLineOpen && process.stdout.isTTY) {
|
|
242
|
+
this.writeRaw(`\r\u001b[2K${message}\n`, level);
|
|
243
|
+
this.waitingLineOpen = false;
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
this.writeLine(message, level);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
private clearWaitingLine(): void {
|
|
250
|
+
if (!this.waitingLineOpen) return;
|
|
251
|
+
if (process.stdout.isTTY) {
|
|
252
|
+
this.writeRaw('\n');
|
|
253
|
+
}
|
|
254
|
+
this.waitingLineOpen = false;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
private extractLinkedOrg(message: string): string | null {
|
|
258
|
+
const match = message.match(
|
|
259
|
+
/^\[moltbank\] ✓ (?:background )?(?:onboarding completed|credentials\.json already available) \(active org: (.+)\)$/
|
|
260
|
+
);
|
|
261
|
+
return match?.[1] ?? null;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
private writeLine(message: string, level: 'info' | 'warn'): void {
|
|
265
|
+
this.writeRaw(`${message}\n`, level);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
private writeRaw(message: string, level: 'info' | 'warn' = 'info'): void {
|
|
269
|
+
const stream = level === 'warn' ? process.stderr : process.stdout;
|
|
270
|
+
stream.write(message);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export function createSetupCommandLogger(options: SetupCommandLoggerOptions = {}): LoggerApi & { finish(): void } {
|
|
275
|
+
const logger = new SetupCommandLogger(options);
|
|
276
|
+
return {
|
|
277
|
+
logger,
|
|
278
|
+
finish: () => logger.finish()
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
69
282
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
70
283
|
return typeof value === 'object' && value !== null;
|
|
71
284
|
}
|
|
@@ -550,6 +763,84 @@ function readPendingOauthCode(skillDir: string): {
|
|
|
550
763
|
}
|
|
551
764
|
}
|
|
552
765
|
|
|
766
|
+
function clearPendingOauthCode(skillDir: string): void {
|
|
767
|
+
const pendingPath = join(skillDir, '.oauth_device_code.json');
|
|
768
|
+
try {
|
|
769
|
+
if (existsSync(pendingPath)) unlinkSync(pendingPath);
|
|
770
|
+
} catch {
|
|
771
|
+
// ignore
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
function runProcess(
|
|
776
|
+
command: string,
|
|
777
|
+
args: string[],
|
|
778
|
+
opts: { cwd?: string; silent?: boolean; env?: Record<string, string | undefined> } = {}
|
|
779
|
+
) {
|
|
780
|
+
try {
|
|
781
|
+
const mergedEnv: NodeJS.ProcessEnv = { ...process.env, ...(opts.env ?? {}) };
|
|
782
|
+
const result = spawnSync(command, args, {
|
|
783
|
+
cwd: opts.cwd,
|
|
784
|
+
env: mergedEnv,
|
|
785
|
+
encoding: 'utf8',
|
|
786
|
+
stdio: opts.silent ? 'pipe' : 'inherit'
|
|
787
|
+
});
|
|
788
|
+
return {
|
|
789
|
+
ok: result.status === 0,
|
|
790
|
+
stdout: (result.stdout ?? '').toString().trim(),
|
|
791
|
+
stderr: (result.stderr ?? '').toString().trim()
|
|
792
|
+
};
|
|
793
|
+
} catch (e: unknown) {
|
|
794
|
+
return {
|
|
795
|
+
ok: false,
|
|
796
|
+
stdout: '',
|
|
797
|
+
stderr: getExecErrorMessage(e)
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
function runMoltbankWrapper(skillDir: string, args: string[]) {
|
|
803
|
+
if (IS_WIN) {
|
|
804
|
+
const ps1Path = join(skillDir, 'scripts', 'moltbank.ps1');
|
|
805
|
+
return runProcess('powershell', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', ps1Path, ...args], {
|
|
806
|
+
cwd: skillDir,
|
|
807
|
+
silent: true
|
|
808
|
+
});
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
return runProcess(join(skillDir, 'scripts', 'moltbank.sh'), args, {
|
|
812
|
+
cwd: skillDir,
|
|
813
|
+
silent: true
|
|
814
|
+
});
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
function verifyMoltbankOperationalReadiness(skillDir: string, api: LoggerApi): boolean {
|
|
818
|
+
const existing = parseActiveTokenFromCredentials();
|
|
819
|
+
if (!existing.ok || !existing.activeOrg) {
|
|
820
|
+
api.logger.warn('[moltbank] ✗ setup incomplete: authenticated org could not be resolved for balance verification');
|
|
821
|
+
return false;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
const verification = runMoltbankWrapper(skillDir, [
|
|
825
|
+
'call',
|
|
826
|
+
'MoltBank.get_balance',
|
|
827
|
+
`organizationName=${existing.activeOrg}`,
|
|
828
|
+
'date=today'
|
|
829
|
+
]);
|
|
830
|
+
|
|
831
|
+
if (verification.ok) {
|
|
832
|
+
api.logger.info(`[moltbank] ✓ balance verification passed (org: ${existing.activeOrg})`);
|
|
833
|
+
return true;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
api.logger.warn('[moltbank] ✗ setup incomplete: auth is linked, but balance verification failed (`MoltBank.get_balance`)');
|
|
837
|
+
const detail = verification.stderr || verification.stdout;
|
|
838
|
+
if (detail) {
|
|
839
|
+
api.logger.warn('[moltbank] verification detail: ' + detail);
|
|
840
|
+
}
|
|
841
|
+
return false;
|
|
842
|
+
}
|
|
843
|
+
|
|
553
844
|
function startBackgroundOauthPoll(
|
|
554
845
|
skillDir: string,
|
|
555
846
|
appBaseUrl: string,
|
|
@@ -601,12 +892,7 @@ function startBackgroundOauthPoll(
|
|
|
601
892
|
oauthPollers.delete(skillDir);
|
|
602
893
|
const after = parseActiveTokenFromCredentials();
|
|
603
894
|
if (after.ok) {
|
|
604
|
-
|
|
605
|
-
try {
|
|
606
|
-
if (existsSync(pendingPath)) unlinkSync(pendingPath);
|
|
607
|
-
} catch {
|
|
608
|
-
// ignore
|
|
609
|
-
}
|
|
895
|
+
clearPendingOauthCode(skillDir);
|
|
610
896
|
api.logger.info(`[moltbank] ✓ background onboarding completed (active org: ${after.activeOrg})`);
|
|
611
897
|
try {
|
|
612
898
|
startBackgroundFinalizeAfterAuth(skillDir, appBaseUrl, api);
|
|
@@ -686,6 +972,8 @@ function ensureMoltbankAuth(skillDir: string, appBaseUrl: string, api: LoggerApi
|
|
|
686
972
|
const existing = parseActiveTokenFromCredentials();
|
|
687
973
|
if (existing.ok) {
|
|
688
974
|
stopBackgroundOauthPoll(skillDir);
|
|
975
|
+
stopBackgroundFinalize(skillDir);
|
|
976
|
+
clearPendingOauthCode(skillDir);
|
|
689
977
|
api.logger.info(`[moltbank] ✓ credentials.json already available (active org: ${existing.activeOrg})`);
|
|
690
978
|
return true;
|
|
691
979
|
}
|
|
@@ -824,11 +1112,7 @@ function ensureMoltbankAuth(skillDir: string, appBaseUrl: string, api: LoggerApi
|
|
|
824
1112
|
|
|
825
1113
|
if (oauthError === 'invalid_grant') {
|
|
826
1114
|
api.logger.warn('[moltbank] ✗ onboarding code expired or already consumed (invalid_grant)');
|
|
827
|
-
|
|
828
|
-
if (existsSync(pendingPath)) unlinkSync(pendingPath);
|
|
829
|
-
} catch {
|
|
830
|
-
// ignore
|
|
831
|
-
}
|
|
1115
|
+
clearPendingOauthCode(skillDir);
|
|
832
1116
|
} else {
|
|
833
1117
|
api.logger.warn('[moltbank] ✗ onboarding poll failed or timed out');
|
|
834
1118
|
}
|
|
@@ -845,11 +1129,7 @@ function ensureMoltbankAuth(skillDir: string, appBaseUrl: string, api: LoggerApi
|
|
|
845
1129
|
return false;
|
|
846
1130
|
}
|
|
847
1131
|
|
|
848
|
-
|
|
849
|
-
if (existsSync(pendingPath)) unlinkSync(pendingPath);
|
|
850
|
-
} catch {
|
|
851
|
-
// ignore
|
|
852
|
-
}
|
|
1132
|
+
clearPendingOauthCode(skillDir);
|
|
853
1133
|
|
|
854
1134
|
api.logger.info(`[moltbank] ✓ onboarding completed (active org: ${after.activeOrg})`);
|
|
855
1135
|
return true;
|
|
@@ -858,7 +1138,10 @@ function ensureMoltbankAuth(skillDir: string, appBaseUrl: string, api: LoggerApi
|
|
|
858
1138
|
export function printAuthStatus(skillDir: string, appBaseUrl: string, api: LoggerApi): void {
|
|
859
1139
|
const now = Math.floor(Date.now() / 1000);
|
|
860
1140
|
const existing = parseActiveTokenFromCredentials();
|
|
861
|
-
|
|
1141
|
+
if (existing.ok) {
|
|
1142
|
+
clearPendingOauthCode(skillDir);
|
|
1143
|
+
}
|
|
1144
|
+
const pending = existing.ok ? null : readPendingOauthCode(skillDir);
|
|
862
1145
|
const poller = oauthPollers.get(skillDir);
|
|
863
1146
|
const finalizer = backgroundFinalizers.get(skillDir);
|
|
864
1147
|
const pollerRunning = Boolean(poller && poller.exitCode === null && !poller.killed);
|
|
@@ -1228,6 +1511,11 @@ export async function runSetup(
|
|
|
1228
1511
|
} else {
|
|
1229
1512
|
api.logger.info('[moltbank] sandbox unchanged — no restart needed');
|
|
1230
1513
|
}
|
|
1514
|
+
|
|
1515
|
+
api.logger.info('[moltbank] [sandbox 11/11] verifying authenticated balance read...');
|
|
1516
|
+
if (!verifyMoltbankOperationalReadiness(skillDir, api)) {
|
|
1517
|
+
return;
|
|
1518
|
+
}
|
|
1231
1519
|
} else {
|
|
1232
1520
|
api.logger.info('[moltbank] [host 1/8] ensuring mcporter on host...');
|
|
1233
1521
|
ensureMcporter(api);
|
|
@@ -1270,19 +1558,11 @@ export async function runSetup(
|
|
|
1270
1558
|
api.logger.info('[moltbank] [host 7/8] writing/registering mcporter config...');
|
|
1271
1559
|
ensureMcporterConfig(skillDir, appBaseUrl, api);
|
|
1272
1560
|
|
|
1273
|
-
api.logger.info('[moltbank] [host 8/8]
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
? `powershell -NoProfile -ExecutionPolicy Bypass -File "${ps1Path}" list MoltBank`
|
|
1278
|
-
: `"${skillDir}/scripts/moltbank.sh" list MoltBank`;
|
|
1279
|
-
const smoke = run(smokeCmd, { cwd: skillDir, silent: true });
|
|
1280
|
-
if (!smoke.ok) {
|
|
1281
|
-
api.logger.warn('[moltbank] host setup incomplete: smoke test failed (`moltbank list MoltBank` via platform script)');
|
|
1282
|
-
} else {
|
|
1283
|
-
api.logger.info('[moltbank] host smoke test passed (`moltbank list MoltBank`)');
|
|
1561
|
+
api.logger.info('[moltbank] [host 8/8] verifying authenticated balance read...');
|
|
1562
|
+
hostReady = verifyMoltbankOperationalReadiness(skillDir, api);
|
|
1563
|
+
if (!hostReady) {
|
|
1564
|
+
return;
|
|
1284
1565
|
}
|
|
1285
|
-
hostReady = smoke.ok;
|
|
1286
1566
|
|
|
1287
1567
|
api.logger.info('[moltbank] host mode — setup completed with onboarding flow');
|
|
1288
1568
|
}
|
|
@@ -1357,19 +1637,15 @@ export default function register(api: PluginApi) {
|
|
|
1357
1637
|
.addCommand(
|
|
1358
1638
|
program
|
|
1359
1639
|
.createCommand('setup')
|
|
1360
|
-
.description('Re-run MoltBank setup
|
|
1640
|
+
.description('Re-run MoltBank setup')
|
|
1361
1641
|
.action(async () => {
|
|
1362
|
-
|
|
1363
|
-
const
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
console.log('[moltbank] optional immediate check: openclaw moltbank auth-status');
|
|
1369
|
-
} else {
|
|
1370
|
-
console.log('[moltbank] setup auth mode: blocking (waiting for OAuth approval)');
|
|
1642
|
+
const verbose = process.argv.includes('--verbose');
|
|
1643
|
+
const logger = createSetupCommandLogger({ verbose, statusCommand: 'openclaw moltbank status' });
|
|
1644
|
+
try {
|
|
1645
|
+
await runSetup(cfg, logger, { authWaitMode: 'blocking' });
|
|
1646
|
+
} finally {
|
|
1647
|
+
logger.finish();
|
|
1371
1648
|
}
|
|
1372
|
-
await runSetup(cfg, { logger: console }, { authWaitMode });
|
|
1373
1649
|
})
|
|
1374
1650
|
)
|
|
1375
1651
|
.addCommand(
|
|
@@ -1377,8 +1653,13 @@ export default function register(api: PluginApi) {
|
|
|
1377
1653
|
.createCommand('setup-blocking')
|
|
1378
1654
|
.description('Re-run full MoltBank setup and wait for OAuth approval')
|
|
1379
1655
|
.action(async () => {
|
|
1380
|
-
|
|
1381
|
-
|
|
1656
|
+
const verbose = process.argv.includes('--verbose');
|
|
1657
|
+
const logger = createSetupCommandLogger({ verbose, statusCommand: 'openclaw moltbank status' });
|
|
1658
|
+
try {
|
|
1659
|
+
await runSetup(cfg, logger, { authWaitMode: 'blocking' });
|
|
1660
|
+
} finally {
|
|
1661
|
+
logger.finish();
|
|
1662
|
+
}
|
|
1382
1663
|
})
|
|
1383
1664
|
)
|
|
1384
1665
|
.addCommand(
|
|
@@ -1408,6 +1689,16 @@ export default function register(api: PluginApi) {
|
|
|
1408
1689
|
}
|
|
1409
1690
|
})
|
|
1410
1691
|
)
|
|
1692
|
+
.addCommand(
|
|
1693
|
+
program
|
|
1694
|
+
.createCommand('status')
|
|
1695
|
+
.description('Show current MoltBank auth state')
|
|
1696
|
+
.action(() => {
|
|
1697
|
+
const appBaseUrl = getAppBaseUrl(cfg);
|
|
1698
|
+
const skillDir = getSkillDir(cfg);
|
|
1699
|
+
printAuthStatus(skillDir, appBaseUrl, { logger: console });
|
|
1700
|
+
})
|
|
1701
|
+
)
|
|
1411
1702
|
.addCommand(
|
|
1412
1703
|
program
|
|
1413
1704
|
.createCommand('auth-status')
|