@moltbankhq/openclaw 0.1.2 → 0.1.4

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 CHANGED
@@ -1,20 +1,30 @@
1
1
  # @moltbankhq/openclaw
2
2
 
3
- OpenClaw plugin for [MoltBank](https://moltbank.bot) — stablecoin treasury controls, approvals, and audit trails for agent fleets.
3
+ Standalone CLI and optional OpenClaw plugin for [MoltBank](https://moltbank.bot) — stablecoin treasury controls, approvals, and audit trails for agent fleets.
4
4
 
5
5
  ## Install
6
6
 
7
7
  ```bash
8
- openclaw plugins install @moltbankhq/openclaw
8
+ npm install -g @moltbankhq/openclaw
9
9
  ```
10
10
 
11
- Direct `npm install` can be useful to acquire the package, but by itself it does not complete OpenClaw plugin registration or setup.
11
+ You can also run it without a global install:
12
+
13
+ ```bash
14
+ npx @moltbankhq/openclaw setup
15
+ ```
16
+
17
+ OpenClaw plugin mode still exists in this pass for compatibility:
18
+
19
+ ```bash
20
+ openclaw plugins install @moltbankhq/openclaw
21
+ ```
12
22
 
13
23
  ## What it does
14
24
 
15
- This plugin connects your OpenClaw agent to MoltBank's stablecoin treasury infrastructure. On install, it:
25
+ This package connects your OpenClaw agent to MoltBank's stablecoin treasury infrastructure. During setup, it:
16
26
 
17
- - Downloads and configures the MoltBank skill (SKILL.md + scripts)
27
+ - Downloads and configures the MoltBank skill (`skill.md` + scripts)
18
28
  - Installs and registers [mcporter](https://www.npmjs.com/package/mcporter) for MCP server connectivity
19
29
  - Handles OAuth device-code authentication to link your agent to your MoltBank account
20
30
  - Configures sandbox (Docker) or host mode automatically based on your OpenClaw setup
@@ -27,24 +37,33 @@ Once set up, your agent can manage treasury operations, set per-agent spending l
27
37
  After installing, run:
28
38
 
29
39
  ```bash
30
- openclaw moltbank setup
40
+ moltbank setup
31
41
  ```
32
42
 
33
- The plugin will guide you through linking your MoltBank account via browser-based OAuth. Once approved, setup finalizes automatically.
43
+ The CLI will guide you through linking your MoltBank account via browser-based OAuth. Once approved, setup finalizes automatically.
34
44
 
35
45
  ## CLI Commands
36
46
 
37
47
  | Command | Description |
38
48
  |---------|-------------|
39
- | `openclaw moltbank setup` | Run full setup (nonblocking auth) |
40
- | `openclaw moltbank setup-blocking` | Setup and wait for OAuth approval |
41
- | `openclaw moltbank auth-status` | Check current auth state |
42
- | `openclaw moltbank sandbox-setup` | Reconfigure sandbox Docker settings |
43
- | `openclaw moltbank inject-key` | Re-inject env vars from credentials |
44
- | `openclaw moltbank register` | Re-register mcporter MCP server |
49
+ | `moltbank setup` | Run full setup (nonblocking auth by default) |
50
+ | `moltbank setup --blocking` | Run setup and wait for OAuth approval |
51
+ | `moltbank setup-blocking` | Run setup and wait for OAuth approval |
52
+ | `moltbank auth-status` | Check current auth state |
53
+ | `moltbank sandbox-setup` | Reconfigure sandbox Docker settings |
54
+ | `moltbank inject-key` | Re-inject env vars from credentials |
55
+ | `moltbank register` | Re-register mcporter MCP server |
45
56
 
46
57
  ## Configuration
47
58
 
59
+ CLI flags:
60
+
61
+ ```bash
62
+ moltbank setup --app-base-url https://app.moltbank.bot --skill-name MoltBank
63
+ ```
64
+
65
+ Plugin mode can still read optional config from your `openclaw.json`:
66
+
48
67
  Optional config in your `openclaw.json`:
49
68
 
50
69
  ```json
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { spawnSync } from 'child_process';
4
+ import { dirname, resolve } from 'path';
5
+ import { fileURLToPath } from 'url';
6
+
7
+ const binDir = dirname(fileURLToPath(import.meta.url));
8
+ const cliPath = resolve(binDir, '../cli.ts');
9
+ const result = spawnSync(process.execPath, ['--import', 'tsx/esm', cliPath, ...process.argv.slice(2)], {
10
+ stdio: 'inherit',
11
+ env: process.env
12
+ });
13
+
14
+ if (result.error) {
15
+ console.error(`[moltbank] failed to start CLI: ${result.error.message}`);
16
+ process.exit(1);
17
+ }
18
+
19
+ process.exit(result.status ?? 1);
package/cli.ts ADDED
@@ -0,0 +1,203 @@
1
+ import { readFileSync } from 'fs';
2
+ import process from 'process';
3
+ import {
4
+ configureSandbox,
5
+ ensureMcporterConfig,
6
+ getAppBaseUrl,
7
+ getSetupAuthWaitMode,
8
+ getSkillDir,
9
+ injectSandboxEnv,
10
+ printAuthStatus,
11
+ recreateSandboxAndRestart,
12
+ runSetup
13
+ } from './index.ts';
14
+
15
+ type CliConfig = {
16
+ appBaseUrl?: string;
17
+ skillName?: string;
18
+ };
19
+
20
+ type ParsedArgs = {
21
+ command?: string;
22
+ config: CliConfig;
23
+ help: boolean;
24
+ version: boolean;
25
+ blocking: boolean;
26
+ };
27
+
28
+ const HELP_TEXT = `MoltBank CLI
29
+
30
+ Usage:
31
+ moltbank <command> [options]
32
+
33
+ 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
37
+ sandbox-setup Reconfigure sandbox Docker settings in openclaw.json
38
+ inject-key Re-inject sandbox env vars from credentials.json
39
+ register Re-register mcporter MCP config
40
+
41
+ Options:
42
+ --app-base-url <url> Override MoltBank deployment URL
43
+ --skill-name <name> Override skill folder name
44
+ --blocking Alias for blocking auth mode with setup
45
+ -h, --help Show help
46
+ -v, --version Show package version
47
+
48
+ Examples:
49
+ moltbank setup
50
+ moltbank setup --blocking
51
+ moltbank auth-status
52
+ moltbank register --app-base-url https://app.moltbank.bot
53
+ `;
54
+
55
+ function readVersion(): string {
56
+ const packageUrl = new URL('./package.json', import.meta.url);
57
+ const packageJson = JSON.parse(readFileSync(packageUrl, 'utf8')) as { version?: string };
58
+ return packageJson.version ?? '0.0.0';
59
+ }
60
+
61
+ function parseArgs(argv: string[]): ParsedArgs {
62
+ const config: CliConfig = {};
63
+ const positional: string[] = [];
64
+ let help = false;
65
+ let version = false;
66
+ let blocking = false;
67
+
68
+ for (let index = 0; index < argv.length; index += 1) {
69
+ const arg = argv[index];
70
+
71
+ if (arg === '-h' || arg === '--help') {
72
+ help = true;
73
+ continue;
74
+ }
75
+
76
+ if (arg === '-v' || arg === '--version') {
77
+ version = true;
78
+ continue;
79
+ }
80
+
81
+ if (arg === '--blocking') {
82
+ blocking = true;
83
+ continue;
84
+ }
85
+
86
+ if (arg === '--app-base-url') {
87
+ const value = argv[index + 1];
88
+ if (!value) {
89
+ throw new Error('missing value for --app-base-url');
90
+ }
91
+ config.appBaseUrl = value;
92
+ index += 1;
93
+ continue;
94
+ }
95
+
96
+ if (arg.startsWith('--app-base-url=')) {
97
+ config.appBaseUrl = arg.slice('--app-base-url='.length);
98
+ continue;
99
+ }
100
+
101
+ if (arg === '--skill-name') {
102
+ const value = argv[index + 1];
103
+ if (!value) {
104
+ throw new Error('missing value for --skill-name');
105
+ }
106
+ config.skillName = value;
107
+ index += 1;
108
+ continue;
109
+ }
110
+
111
+ if (arg.startsWith('--skill-name=')) {
112
+ config.skillName = arg.slice('--skill-name='.length);
113
+ continue;
114
+ }
115
+
116
+ if (arg.startsWith('-')) {
117
+ throw new Error(`unknown option: ${arg}`);
118
+ }
119
+
120
+ positional.push(arg);
121
+ }
122
+
123
+ return {
124
+ command: positional[0],
125
+ config,
126
+ help,
127
+ version,
128
+ blocking
129
+ };
130
+ }
131
+
132
+ async function runCommand(parsed: ParsedArgs): Promise<void> {
133
+ const cfg = parsed.config;
134
+ const logger = { logger: console };
135
+
136
+ switch (parsed.command) {
137
+ case 'setup': {
138
+ const authWaitMode = parsed.blocking ? 'blocking' : getSetupAuthWaitMode('nonblocking');
139
+ await runSetup(cfg, logger, { authWaitMode });
140
+ return;
141
+ }
142
+
143
+ case 'setup-blocking': {
144
+ await runSetup(cfg, logger, { authWaitMode: 'blocking' });
145
+ return;
146
+ }
147
+
148
+ case 'auth-status': {
149
+ printAuthStatus(getSkillDir(cfg), getAppBaseUrl(cfg), logger);
150
+ return;
151
+ }
152
+
153
+ case 'sandbox-setup': {
154
+ const changed = configureSandbox(logger);
155
+ if (changed) {
156
+ recreateSandboxAndRestart(logger);
157
+ } else {
158
+ console.log('[moltbank] No sandbox docker changes — not scheduling teardown');
159
+ }
160
+ return;
161
+ }
162
+
163
+ case 'inject-key': {
164
+ const changed = injectSandboxEnv(getSkillDir(cfg), logger);
165
+ if (changed) {
166
+ recreateSandboxAndRestart(logger);
167
+ } else {
168
+ console.log('[moltbank] No env changes — not scheduling teardown');
169
+ }
170
+ return;
171
+ }
172
+
173
+ case 'register': {
174
+ ensureMcporterConfig(getSkillDir(cfg), getAppBaseUrl(cfg), logger);
175
+ return;
176
+ }
177
+
178
+ default:
179
+ throw new Error(`unknown command: ${parsed.command}`);
180
+ }
181
+ }
182
+
183
+ async function main(): Promise<void> {
184
+ const parsed = parseArgs(process.argv.slice(2));
185
+
186
+ if (parsed.version) {
187
+ console.log(readVersion());
188
+ return;
189
+ }
190
+
191
+ if (parsed.help || !parsed.command) {
192
+ console.log(HELP_TEXT);
193
+ return;
194
+ }
195
+
196
+ await runCommand(parsed);
197
+ }
198
+
199
+ main().catch((error: unknown) => {
200
+ const message = error instanceof Error ? error.message : String(error);
201
+ console.error(`[moltbank] ${message}`);
202
+ process.exit(1);
203
+ });
package/index.ts CHANGED
@@ -7,7 +7,7 @@ const IS_WIN = process.platform === 'win32';
7
7
  type OpenclawConfig = Record<string, unknown>;
8
8
  type ParsedJsonObject = Record<string, unknown>;
9
9
 
10
- interface MoltbankPluginConfig {
10
+ export interface MoltbankPluginConfig {
11
11
  skillName?: string;
12
12
  appBaseUrl?: string;
13
13
  }
@@ -62,7 +62,7 @@ interface PluginApi extends LoggerApi {
62
62
  registerCli(handler: (args: { program: CliCommandLike }) => void, options: { commands: string[] }): void;
63
63
  }
64
64
 
65
- type AuthWaitMode = 'blocking' | 'nonblocking';
65
+ export type AuthWaitMode = 'blocking' | 'nonblocking';
66
66
  const oauthPollers = new Map<string, ReturnType<typeof spawn>>();
67
67
  const backgroundFinalizers = new Map<string, ReturnType<typeof spawn>>();
68
68
 
@@ -86,7 +86,7 @@ function asStringRecord(value: unknown): Record<string, string> {
86
86
  return out;
87
87
  }
88
88
 
89
- function getSetupAuthWaitMode(defaultMode: AuthWaitMode): AuthWaitMode {
89
+ export function getSetupAuthWaitMode(defaultMode: AuthWaitMode): AuthWaitMode {
90
90
  const raw = asString(process.env.MOLTBANK_SETUP_AUTH_WAIT_MODE).trim().toLowerCase();
91
91
  if (raw === 'blocking' || raw === 'wait') return 'blocking';
92
92
  if (raw === 'nonblocking' || raw === 'nowait') return 'nonblocking';
@@ -133,14 +133,19 @@ function getWorkspace(): string {
133
133
  return process.env.OPENCLAW_WORKSPACE || join(homedir(), '.openclaw', 'workspace');
134
134
  }
135
135
 
136
- function getSkillName(cfg: MoltbankPluginConfig): string {
136
+ export function getSkillName(cfg: MoltbankPluginConfig): string {
137
137
  return cfg?.skillName || process.env.MOLTBANK_SKILL_NAME || 'MoltBank';
138
138
  }
139
139
 
140
- function getAppBaseUrl(cfg: MoltbankPluginConfig): string {
140
+ export function getAppBaseUrl(cfg: MoltbankPluginConfig): string {
141
141
  return (cfg?.appBaseUrl || process.env.APP_BASE_URL || 'https://app.moltbank.bot').trim();
142
142
  }
143
143
 
144
+ export function getSkillBundleBaseUrl(cfg: MoltbankPluginConfig): string {
145
+ const base = getAppBaseUrl(cfg).replace(/\/$/, '');
146
+ return base.endsWith('/skill') ? base : `${base}/skill`;
147
+ }
148
+
144
149
  function isSandboxEnabled(): boolean {
145
150
  try {
146
151
  const configPath = join(homedir(), '.openclaw', 'openclaw.json');
@@ -152,7 +157,7 @@ function isSandboxEnabled(): boolean {
152
157
  }
153
158
  }
154
159
 
155
- function getSkillDir(cfg: MoltbankPluginConfig): string {
160
+ export function getSkillDir(cfg: MoltbankPluginConfig): string {
156
161
  const skillName = getSkillName(cfg);
157
162
  return join(getWorkspace(), 'skills', skillName);
158
163
  }
@@ -850,7 +855,7 @@ function ensureMoltbankAuth(skillDir: string, appBaseUrl: string, api: LoggerApi
850
855
  return true;
851
856
  }
852
857
 
853
- function printAuthStatus(skillDir: string, appBaseUrl: string, api: LoggerApi): void {
858
+ export function printAuthStatus(skillDir: string, appBaseUrl: string, api: LoggerApi): void {
854
859
  const now = Math.floor(Date.now() / 1000);
855
860
  const existing = parseActiveTokenFromCredentials();
856
861
  const pending = readPendingOauthCode(skillDir);
@@ -878,11 +883,11 @@ function printAuthStatus(skillDir: string, appBaseUrl: string, api: LoggerApi):
878
883
  api.logger.info(`[moltbank] background poll: ${pollerRunning ? 'running' : 'idle'}`);
879
884
  api.logger.info(`[moltbank] background finalize: ${finalizerRunning ? 'running' : 'idle'}`);
880
885
  if (!existing.ok && !pending) {
881
- api.logger.info('[moltbank] hint: run `openclaw moltbank setup` to start onboarding');
886
+ api.logger.info('[moltbank] hint: run `moltbank setup` to start onboarding');
882
887
  }
883
888
  }
884
889
 
885
- function ensureMcporterConfig(skillDir: string, appBaseUrl: string, api: LoggerApi) {
890
+ export function ensureMcporterConfig(skillDir: string, appBaseUrl: string, api: LoggerApi) {
886
891
  const cfgPath = join(skillDir, 'assets', 'mcporter.json');
887
892
  mkdirSync(join(skillDir, 'assets'), { recursive: true });
888
893
 
@@ -937,7 +942,7 @@ function ensureMcporterConfig(skillDir: string, appBaseUrl: string, api: LoggerA
937
942
 
938
943
  // ─── sandbox env vars ────────────────────────────────────────────────────────
939
944
 
940
- function injectSandboxEnv(skillDir: string, api: LoggerApi): boolean {
945
+ export function injectSandboxEnv(skillDir: string, api: LoggerApi): boolean {
941
946
  const credsPath = getCredentialsPath();
942
947
 
943
948
  if (!existsSync(credsPath)) {
@@ -1035,7 +1040,7 @@ function injectSandboxEnv(skillDir: string, api: LoggerApi): boolean {
1035
1040
 
1036
1041
  // ─── sandbox docker config ────────────────────────────────────────────────────
1037
1042
 
1038
- function configureSandbox(api: LoggerApi): boolean {
1043
+ export function configureSandbox(api: LoggerApi): boolean {
1039
1044
  const SETUP_CMD =
1040
1045
  'echo \'APT::Sandbox::User "root";\' > /etc/apt/apt.conf.d/99sandbox && ' +
1041
1046
  'apt-get update -qq && ' +
@@ -1107,7 +1112,7 @@ function configureSandbox(api: LoggerApi): boolean {
1107
1112
 
1108
1113
  // ─── sandbox recreate + gateway restart ──────────────────────────────────────
1109
1114
 
1110
- function recreateSandboxAndRestart(api: LoggerApi) {
1115
+ export function recreateSandboxAndRestart(api: LoggerApi) {
1111
1116
  api.logger.info('[moltbank] recreating sandbox containers...');
1112
1117
  api.logger.info('[moltbank] ⏳ waiting 8s before recreate (hot container protection)...');
1113
1118
  setTimeout(() => {
@@ -1142,16 +1147,21 @@ function recreateSandboxAndRestart(api: LoggerApi) {
1142
1147
 
1143
1148
  // ─── main setup ───────────────────────────────────────────────────────────────
1144
1149
 
1145
- async function runSetup(cfg: MoltbankPluginConfig, api: LoggerApi, options: { authWaitMode?: AuthWaitMode } = {}) {
1150
+ export async function runSetup(
1151
+ cfg: MoltbankPluginConfig,
1152
+ api: LoggerApi,
1153
+ options: { authWaitMode?: AuthWaitMode } = {}
1154
+ ) {
1146
1155
  let hostReady = false;
1147
1156
  const appBaseUrl = getAppBaseUrl(cfg);
1157
+ const skillBundleBaseUrl = getSkillBundleBaseUrl(cfg);
1148
1158
  const skillName = getSkillName(cfg);
1149
1159
  const sandbox = isSandboxEnabled();
1150
1160
  const skillDir = getSkillDir(cfg);
1151
1161
  const waitForAuth = (options.authWaitMode ?? 'blocking') === 'blocking';
1152
1162
 
1153
1163
  api.logger.info(`[moltbank] ══════════════════════════════════════`);
1154
- api.logger.info(`[moltbank] MoltBank plugin setup starting`);
1164
+ api.logger.info(`[moltbank] MoltBank setup starting`);
1155
1165
  api.logger.info(`[moltbank] mode: ${sandbox ? 'sandbox (Docker)' : 'host (direct)'}`);
1156
1166
  api.logger.info(`[moltbank] skill dir: ${skillDir}`);
1157
1167
  api.logger.info(`[moltbank] base url: ${appBaseUrl}`);
@@ -1166,7 +1176,7 @@ async function runSetup(cfg: MoltbankPluginConfig, api: LoggerApi, options: { au
1166
1176
  ensureMcporter(api);
1167
1177
 
1168
1178
  api.logger.info('[moltbank] [sandbox 1/10] installing skill files...');
1169
- const skillInstalled = ensureSkillInstalled(skillDir, appBaseUrl, skillName, api, 'sandbox');
1179
+ const skillInstalled = ensureSkillInstalled(skillDir, skillBundleBaseUrl, skillName, api, 'sandbox');
1170
1180
  if (!skillInstalled) {
1171
1181
  api.logger.warn('[moltbank] skill install failed — aborting sandbox setup');
1172
1182
  return;
@@ -1223,7 +1233,7 @@ async function runSetup(cfg: MoltbankPluginConfig, api: LoggerApi, options: { au
1223
1233
  ensureMcporter(api);
1224
1234
 
1225
1235
  api.logger.info('[moltbank] [host 2/8] installing skill files...');
1226
- const installed = ensureSkillInstalled(skillDir, appBaseUrl, skillName, api, 'host');
1236
+ const installed = ensureSkillInstalled(skillDir, skillBundleBaseUrl, skillName, api, 'host');
1227
1237
  if (!installed) {
1228
1238
  api.logger.warn('[moltbank] host setup aborted: skill install failed. Verify install.sh/base URL and retry.');
1229
1239
  return;
package/package.json CHANGED
@@ -1,8 +1,11 @@
1
1
  {
2
2
  "name": "@moltbankhq/openclaw",
3
- "version": "0.1.2",
4
- "description": "MoltBank stablecoin treasury OpenClaw plugin",
3
+ "version": "0.1.4",
4
+ "description": "MoltBank stablecoin treasury CLI and OpenClaw plugin",
5
5
  "main": "index.ts",
6
+ "bin": {
7
+ "moltbank": "./bin/moltbank.mjs"
8
+ },
6
9
  "openclaw": {
7
10
  "extensions": [
8
11
  "./index.ts"
@@ -12,6 +15,9 @@
12
15
  "author": "",
13
16
  "license": "ISC",
14
17
  "type": "module",
18
+ "dependencies": {
19
+ "tsx": "^4.20.6"
20
+ },
15
21
  "devDependencies": {
16
22
  "@types/node": "^25.5.0"
17
23
  }