@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.
Files changed (4) hide show
  1. package/README.md +8 -5
  2. package/cli.ts +35 -12
  3. package/index.ts +334 -43
  4. 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 finalizes automatically.
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 (nonblocking auth by default) |
58
- | `moltbank setup --blocking` | Run setup and wait for OAuth approval |
59
- | `moltbank setup-blocking` | Run setup and wait for OAuth approval |
60
- | `moltbank auth-status` | Check current auth state |
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 (nonblocking auth by default)
35
- setup-blocking Run MoltBank setup and wait for OAuth approval
36
- auth-status Show current MoltBank auth state
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 Alias for blocking auth mode with setup
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 --blocking
51
- moltbank auth-status
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 authWaitMode = parsed.blocking ? 'blocking' : getSetupAuthWaitMode('nonblocking');
139
- await runSetup(cfg, logger, { authWaitMode });
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
- await runSetup(cfg, logger, { authWaitMode: 'blocking' });
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
- const pendingPath = join(skillDir, '.oauth_device_code.json');
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
- try {
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
- try {
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
- const pending = readPendingOauthCode(skillDir);
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] running wrapper smoke test...');
1274
- // FIX: usar path absoluto para el .ps1 en Windows
1275
- const ps1Path = join(skillDir, 'scripts', 'moltbank.ps1');
1276
- const smokeCmd = IS_WIN
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 (nonblocking auth by default)')
1640
+ .description('Re-run MoltBank setup')
1361
1641
  .action(async () => {
1362
- console.log('Running MoltBank setup...');
1363
- const authWaitMode = getSetupAuthWaitMode('nonblocking');
1364
- if (authWaitMode === 'nonblocking') {
1365
- console.log('[moltbank] setup auth mode: nonblocking (default for channel reliability)');
1366
- console.log('[moltbank] set MOLTBANK_SETUP_AUTH_WAIT_MODE=blocking to wait for OAuth approval');
1367
- console.log('[moltbank] after browser approval, setup finalization continues automatically');
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
- console.log('Running MoltBank setup (blocking auth mode)...');
1381
- await runSetup(cfg, { logger: console }, { authWaitMode: 'blocking' });
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')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moltbankhq/openclaw",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "MoltBank stablecoin treasury CLI and OpenClaw plugin",
5
5
  "main": "index.ts",
6
6
  "bin": {