clawmoney 0.3.0 → 0.5.0

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.
@@ -3,8 +3,7 @@ interface TweetOptions {
3
3
  draft?: boolean;
4
4
  }
5
5
  /**
6
- * Post a tweet via BNBot Chrome Extension through the Bridge server.
7
- * Requires `clawmoney serve` to be running.
6
+ * Post a tweet by delegating to bnbot-cli.
8
7
  */
9
8
  export declare function tweetCommand(text: string, options: TweetOptions): Promise<void>;
10
9
  export {};
@@ -1,50 +1,29 @@
1
1
  import chalk from 'chalk';
2
- import ora from 'ora';
3
- import { sendAction } from '../utils/bridge.js';
2
+ import { spawn } from 'node:child_process';
4
3
  /**
5
- * Post a tweet via BNBot Chrome Extension through the Bridge server.
6
- * Requires `clawmoney serve` to be running.
4
+ * Post a tweet by delegating to bnbot-cli.
7
5
  */
8
6
  export async function tweetCommand(text, options) {
9
- const isDraft = options.draft || false;
10
- console.log('');
11
- console.log(chalk.dim(` ${isDraft ? 'Drafting' : 'Posting'} tweet: "${text.slice(0, 80)}${text.length > 80 ? '...' : ''}"`));
12
- if (options.media) {
13
- console.log(chalk.dim(` Media: ${options.media}`));
14
- }
15
- console.log('');
16
- const spinner = ora(isDraft ? 'Filling tweet draft...' : 'Posting tweet...').start();
7
+ const args = ['bnbot', 'tweet', text];
8
+ if (options.draft)
9
+ args.push('--draft');
10
+ if (options.media)
11
+ args.push('--media', options.media);
17
12
  try {
18
- const params = {
19
- text,
20
- draftOnly: isDraft,
21
- };
22
- if (options.media) {
23
- params.media = [{ type: 'image', url: options.media }];
24
- }
25
- const result = await sendAction('post_tweet', params);
26
- if (!result.success) {
27
- spinner.fail(result.error || 'Failed');
28
- return;
29
- }
30
- if (isDraft) {
31
- spinner.succeed('Tweet draft ready — review and post manually');
32
- }
33
- else {
34
- spinner.succeed('Tweet posted');
35
- const data = result.data;
36
- if (data?.url || data?.tweet_url) {
37
- console.log(` ${chalk.dim('URL:')} ${chalk.cyan(data.url || data.tweet_url)}`);
38
- }
39
- }
13
+ const code = await new Promise((resolve, reject) => {
14
+ const child = spawn('npx', args, {
15
+ stdio: 'inherit',
16
+ shell: true,
17
+ });
18
+ child.on('close', (code) => resolve(code || 0));
19
+ child.on('error', reject);
20
+ });
21
+ if (code !== 0)
22
+ process.exit(code);
40
23
  }
41
24
  catch (err) {
42
- spinner.fail('Failed');
43
25
  console.error(chalk.red(err.message));
44
- console.log('');
45
- console.log(chalk.dim(' Make sure:'));
46
- console.log(chalk.dim(' 1. clawmoney serve is running'));
47
- console.log(chalk.dim(' 2. BNBot Chrome Extension is open on a Twitter tab'));
26
+ console.log(chalk.dim(' Make sure bnbot-cli is installed: npm install -g bnbot-cli'));
27
+ process.exit(1);
48
28
  }
49
- console.log('');
50
29
  }
package/dist/index.js CHANGED
@@ -5,7 +5,6 @@ import { browseCommand } from './commands/browse.js';
5
5
  import { hireSubmitCommand, hireVerifyCommand } from './commands/hire.js';
6
6
  import { walletStatusCommand, walletBalanceCommand, walletAddressCommand, walletSendCommand, } from './commands/wallet.js';
7
7
  import { tweetCommand } from './commands/tweet.js';
8
- import { serveCommand } from './commands/serve.js';
9
8
  const program = new Command();
10
9
  program
11
10
  .name('clawmoney')
@@ -119,20 +118,6 @@ wallet
119
118
  process.exit(1);
120
119
  }
121
120
  });
122
- // serve
123
- program
124
- .command('serve')
125
- .description('Start the bridge server for Chrome Extension communication')
126
- .option('-p, --port <port>', 'WebSocket port', '18900')
127
- .action(async (options) => {
128
- try {
129
- await serveCommand(options);
130
- }
131
- catch (err) {
132
- console.error(err.message);
133
- process.exit(1);
134
- }
135
- });
136
121
  // tweet
137
122
  program
138
123
  .command('tweet <text>')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawmoney",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "ClawMoney CLI -- Earn crypto with your AI agent",
5
5
  "type": "module",
6
6
  "bin": {
@@ -16,12 +16,11 @@
16
16
  "prepublishOnly": "npm run build"
17
17
  },
18
18
  "dependencies": {
19
- "@types/ws": "^8.18.1",
20
19
  "awal": "^2.2.0",
20
+ "bnbot-cli": "^2.0.0",
21
21
  "chalk": "^5.3.0",
22
22
  "commander": "^12.0.0",
23
23
  "ora": "^8.0.0",
24
- "ws": "^8.20.0",
25
24
  "yaml": "^2.4.0"
26
25
  },
27
26
  "devDependencies": {
@@ -1,5 +0,0 @@
1
- interface ServeOptions {
2
- port?: string;
3
- }
4
- export declare function serveCommand(options: ServeOptions): Promise<void>;
5
- export {};
@@ -1,38 +0,0 @@
1
- import chalk from 'chalk';
2
- import { BridgeServer } from '../utils/bridge.js';
3
- export async function serveCommand(options) {
4
- const port = parseInt(options.port || '18900', 10);
5
- const server = new BridgeServer(port);
6
- try {
7
- await server.start();
8
- }
9
- catch (err) {
10
- console.error(chalk.red(err.message));
11
- process.exit(1);
12
- }
13
- console.log('');
14
- console.log(chalk.green(' ClawMoney Bridge Server running'));
15
- console.log(chalk.dim(` WebSocket listening on ws://127.0.0.1:${port}`));
16
- console.log('');
17
- console.log(chalk.dim(' Waiting for BNBot Chrome Extension to connect...'));
18
- console.log(chalk.dim(' Press Ctrl+C to stop'));
19
- console.log('');
20
- // Check extension connection periodically
21
- const statusInterval = setInterval(() => {
22
- if (server.isExtensionConnected()) {
23
- const ver = server.getExtensionVersion();
24
- console.log(chalk.green(` Extension connected${ver ? ` (v${ver})` : ''}`));
25
- clearInterval(statusInterval);
26
- }
27
- }, 2000);
28
- // Graceful shutdown
29
- const shutdown = () => {
30
- console.log('');
31
- console.log(chalk.dim(' Shutting down...'));
32
- clearInterval(statusInterval);
33
- server.stop();
34
- process.exit(0);
35
- };
36
- process.on('SIGINT', shutdown);
37
- process.on('SIGTERM', shutdown);
38
- }
@@ -1,37 +0,0 @@
1
- /**
2
- * Bridge — WebSocket server that sits between CLI commands and the Chrome Extension.
3
- *
4
- * Architecture:
5
- * clawmoney serve → starts WS server (port 18900)
6
- * Chrome Extension → connects as client (localRelayManager)
7
- * clawmoney tweet → connects as client, sends action, gets result via server
8
- *
9
- * The server keeps a persistent connection to the extension.
10
- * CLI commands connect briefly, send an action, and disconnect after getting a result.
11
- */
12
- /**
13
- * The Bridge server — start with `clawmoney serve`
14
- */
15
- export declare class BridgeServer {
16
- private wss;
17
- private extensionClient;
18
- private pendingRequests;
19
- private extensionVersion;
20
- private port;
21
- constructor(port?: number);
22
- start(): Promise<void>;
23
- private handleMessage;
24
- stop(): void;
25
- isExtensionConnected(): boolean;
26
- getExtensionVersion(): string | null;
27
- getPort(): number;
28
- }
29
- /**
30
- * Send an action to the extension via the running Bridge server.
31
- * Connects as a CLI client, sends action, waits for result, disconnects.
32
- */
33
- export declare function sendAction(actionType: string, params: Record<string, unknown>, port?: number): Promise<{
34
- success: boolean;
35
- data?: unknown;
36
- error?: string;
37
- }>;
@@ -1,218 +0,0 @@
1
- /**
2
- * Bridge — WebSocket server that sits between CLI commands and the Chrome Extension.
3
- *
4
- * Architecture:
5
- * clawmoney serve → starts WS server (port 18900)
6
- * Chrome Extension → connects as client (localRelayManager)
7
- * clawmoney tweet → connects as client, sends action, gets result via server
8
- *
9
- * The server keeps a persistent connection to the extension.
10
- * CLI commands connect briefly, send an action, and disconnect after getting a result.
11
- */
12
- import { WebSocketServer, WebSocket } from 'ws';
13
- import { randomUUID } from 'node:crypto';
14
- const DEFAULT_PORT = 18900;
15
- const ACTION_TIMEOUT = 60000;
16
- /**
17
- * The Bridge server — start with `clawmoney serve`
18
- */
19
- export class BridgeServer {
20
- wss = null;
21
- extensionClient = null;
22
- pendingRequests = new Map();
23
- extensionVersion = null;
24
- port;
25
- constructor(port) {
26
- this.port = port || DEFAULT_PORT;
27
- }
28
- start() {
29
- return new Promise((resolve, reject) => {
30
- this.wss = new WebSocketServer({ port: this.port, host: '127.0.0.1' });
31
- this.wss.on('listening', () => {
32
- resolve();
33
- });
34
- this.wss.on('error', (error) => {
35
- if (error.code === 'EADDRINUSE') {
36
- reject(new Error(`Port ${this.port} already in use. Another serve instance may be running.`));
37
- }
38
- else {
39
- reject(error);
40
- }
41
- });
42
- this.wss.on('connection', (ws) => {
43
- // Determine if this is the extension or a CLI client
44
- // Extension sends a 'status' message on connect
45
- // CLI clients send an 'action' message
46
- ws.on('message', (data) => {
47
- try {
48
- const message = JSON.parse(data.toString());
49
- this.handleMessage(ws, message);
50
- }
51
- catch {
52
- // ignore
53
- }
54
- });
55
- ws.on('close', () => {
56
- if (this.extensionClient === ws) {
57
- this.extensionClient = null;
58
- this.extensionVersion = null;
59
- // Reject all pending requests
60
- for (const [id, pending] of this.pendingRequests) {
61
- clearTimeout(pending.timer);
62
- pending.reject(new Error('Extension disconnected'));
63
- this.pendingRequests.delete(id);
64
- }
65
- }
66
- });
67
- });
68
- });
69
- }
70
- handleMessage(ws, message) {
71
- switch (message.type) {
72
- case 'status':
73
- // Extension connected
74
- if (this.extensionClient && this.extensionClient !== ws && this.extensionClient.readyState === WebSocket.OPEN) {
75
- this.extensionClient.close(1000, 'Replaced by new connection');
76
- }
77
- this.extensionClient = ws;
78
- this.extensionVersion = message.version;
79
- break;
80
- case 'action':
81
- // CLI client sending an action — forward to extension
82
- if (!this.extensionClient || this.extensionClient.readyState !== WebSocket.OPEN) {
83
- ws.send(JSON.stringify({
84
- type: 'action_result',
85
- requestId: message.requestId,
86
- success: false,
87
- error: 'Extension not connected. Make sure BNBot Chrome Extension is running on a Twitter tab.',
88
- }));
89
- return;
90
- }
91
- // Store pending request keyed to the CLI client WebSocket
92
- const requestId = message.requestId;
93
- const timer = setTimeout(() => {
94
- this.pendingRequests.delete(requestId);
95
- if (ws.readyState === WebSocket.OPEN) {
96
- ws.send(JSON.stringify({
97
- type: 'action_result',
98
- requestId,
99
- success: false,
100
- error: `Action '${message.actionType}' timed out`,
101
- }));
102
- }
103
- }, ACTION_TIMEOUT);
104
- this.pendingRequests.set(requestId, {
105
- resolve: (result) => {
106
- if (ws.readyState === WebSocket.OPEN) {
107
- ws.send(JSON.stringify(result));
108
- }
109
- },
110
- reject: (error) => {
111
- if (ws.readyState === WebSocket.OPEN) {
112
- ws.send(JSON.stringify({
113
- type: 'action_result',
114
- requestId,
115
- success: false,
116
- error: error.message,
117
- }));
118
- }
119
- },
120
- timer,
121
- });
122
- // Forward to extension
123
- this.extensionClient.send(JSON.stringify(message));
124
- break;
125
- case 'action_result':
126
- // Extension sending back a result — forward to CLI client
127
- const pending = this.pendingRequests.get(message.requestId);
128
- if (pending) {
129
- clearTimeout(pending.timer);
130
- this.pendingRequests.delete(message.requestId);
131
- pending.resolve(message);
132
- }
133
- break;
134
- case 'heartbeat':
135
- // Respond to heartbeat from extension
136
- break;
137
- }
138
- }
139
- stop() {
140
- if (this.extensionClient) {
141
- this.extensionClient.close(1000, 'Server shutting down');
142
- this.extensionClient = null;
143
- }
144
- if (this.wss) {
145
- this.wss.close();
146
- this.wss = null;
147
- }
148
- for (const [id, pending] of this.pendingRequests) {
149
- clearTimeout(pending.timer);
150
- pending.reject(new Error('Server shutting down'));
151
- }
152
- this.pendingRequests.clear();
153
- }
154
- isExtensionConnected() {
155
- return this.extensionClient !== null && this.extensionClient.readyState === WebSocket.OPEN;
156
- }
157
- getExtensionVersion() {
158
- return this.extensionVersion;
159
- }
160
- getPort() {
161
- return this.port;
162
- }
163
- }
164
- /**
165
- * Send an action to the extension via the running Bridge server.
166
- * Connects as a CLI client, sends action, waits for result, disconnects.
167
- */
168
- export async function sendAction(actionType, params, port) {
169
- const wsPort = port || DEFAULT_PORT;
170
- return new Promise((resolve, reject) => {
171
- const ws = new WebSocket(`ws://127.0.0.1:${wsPort}`);
172
- const requestId = randomUUID();
173
- let settled = false;
174
- const timer = setTimeout(() => {
175
- if (!settled) {
176
- settled = true;
177
- ws.close();
178
- reject(new Error(`Action '${actionType}' timed out after ${ACTION_TIMEOUT / 1000}s`));
179
- }
180
- }, ACTION_TIMEOUT);
181
- ws.on('open', () => {
182
- ws.send(JSON.stringify({
183
- type: 'action',
184
- requestId,
185
- actionType,
186
- actionPayload: params,
187
- }));
188
- });
189
- ws.on('message', (data) => {
190
- try {
191
- const msg = JSON.parse(data.toString());
192
- if (msg.type === 'action_result' && msg.requestId === requestId) {
193
- settled = true;
194
- clearTimeout(timer);
195
- ws.close();
196
- resolve({ success: msg.success, data: msg.data, error: msg.error });
197
- }
198
- }
199
- catch {
200
- // ignore
201
- }
202
- });
203
- ws.on('error', (err) => {
204
- if (!settled) {
205
- settled = true;
206
- clearTimeout(timer);
207
- reject(new Error(`Cannot connect to bridge server on port ${wsPort}. Run 'clawmoney serve' first.`));
208
- }
209
- });
210
- ws.on('close', () => {
211
- if (!settled) {
212
- settled = true;
213
- clearTimeout(timer);
214
- reject(new Error('Connection closed before response'));
215
- }
216
- });
217
- });
218
- }