@moltbankhq/openclaw 0.1.6 → 0.1.7

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 (3) hide show
  1. package/cli.ts +35 -12
  2. package/index.ts +237 -13
  3. package/package.json +1 -1
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
@@ -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
  }
@@ -1357,19 +1570,15 @@ export default function register(api: PluginApi) {
1357
1570
  .addCommand(
1358
1571
  program
1359
1572
  .createCommand('setup')
1360
- .description('Re-run MoltBank setup (nonblocking auth by default)')
1573
+ .description('Re-run MoltBank setup')
1361
1574
  .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)');
1575
+ const verbose = process.argv.includes('--verbose');
1576
+ const logger = createSetupCommandLogger({ verbose, statusCommand: 'openclaw moltbank status' });
1577
+ try {
1578
+ await runSetup(cfg, logger, { authWaitMode: 'blocking' });
1579
+ } finally {
1580
+ logger.finish();
1371
1581
  }
1372
- await runSetup(cfg, { logger: console }, { authWaitMode });
1373
1582
  })
1374
1583
  )
1375
1584
  .addCommand(
@@ -1377,8 +1586,13 @@ export default function register(api: PluginApi) {
1377
1586
  .createCommand('setup-blocking')
1378
1587
  .description('Re-run full MoltBank setup and wait for OAuth approval')
1379
1588
  .action(async () => {
1380
- console.log('Running MoltBank setup (blocking auth mode)...');
1381
- await runSetup(cfg, { logger: console }, { authWaitMode: 'blocking' });
1589
+ const verbose = process.argv.includes('--verbose');
1590
+ const logger = createSetupCommandLogger({ verbose, statusCommand: 'openclaw moltbank status' });
1591
+ try {
1592
+ await runSetup(cfg, logger, { authWaitMode: 'blocking' });
1593
+ } finally {
1594
+ logger.finish();
1595
+ }
1382
1596
  })
1383
1597
  )
1384
1598
  .addCommand(
@@ -1408,6 +1622,16 @@ export default function register(api: PluginApi) {
1408
1622
  }
1409
1623
  })
1410
1624
  )
1625
+ .addCommand(
1626
+ program
1627
+ .createCommand('status')
1628
+ .description('Show current MoltBank auth state')
1629
+ .action(() => {
1630
+ const appBaseUrl = getAppBaseUrl(cfg);
1631
+ const skillDir = getSkillDir(cfg);
1632
+ printAuthStatus(skillDir, appBaseUrl, { logger: console });
1633
+ })
1634
+ )
1411
1635
  .addCommand(
1412
1636
  program
1413
1637
  .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.7",
4
4
  "description": "MoltBank stablecoin treasury CLI and OpenClaw plugin",
5
5
  "main": "index.ts",
6
6
  "bin": {