clawmoney 0.17.39 → 0.17.40

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.
@@ -7,6 +7,7 @@ import { Poller } from "./poller.js";
7
7
  import { Executor } from "./executor.js";
8
8
  import { startDedup, stopDedup } from "./dedup.js";
9
9
  import { logger } from "./logger.js";
10
+ import { syncSkillRegistry } from "./sync-skills.js";
10
11
  const CONFIG_DIR = join(homedir(), ".clawmoney");
11
12
  const CONFIG_FILE = join(CONFIG_DIR, "config.yaml");
12
13
  const PID_FILE = join(CONFIG_DIR, "provider.pid");
@@ -92,6 +93,7 @@ function loadProviderConfig(cliCommand, autoAccept) {
92
93
  DEFAULT_PROVIDER.reconnect.multiplier,
93
94
  },
94
95
  skills: userProvider.skills,
96
+ disabled_skills: userProvider.disabled_skills,
95
97
  };
96
98
  return {
97
99
  api_key: raw.api_key,
@@ -117,6 +119,13 @@ export function runProvider(cliCommand, autoAccept) {
117
119
  });
118
120
  // Create executor
119
121
  const executor = new Executor(config, (event) => wsClient.send(event));
122
+ // Fire-and-forget skill auto-sync. Catches everything so a registry
123
+ // hiccup never crashes the daemon.
124
+ setImmediate(() => {
125
+ syncSkillRegistry(config).catch((err) => {
126
+ logger.warn(`Skill sync errored: ${err instanceof Error ? err.message : String(err)}`);
127
+ });
128
+ });
120
129
  // Event router
121
130
  function handleEvent(event) {
122
131
  switch (event.event) {
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Default marketplace listing metadata for each built-in skill.
3
+ *
4
+ * `syncSkillRegistry` reads this table to figure out which skills to
5
+ * auto-publish to the marketplace on `clawmoney market start`. Anything
6
+ * not in this map is intentionally not auto-registered — typically
7
+ * because it requires manual configuration (e.g. chatgpt.ask) or is a
8
+ * back-compat alias.
9
+ *
10
+ * `requiresPlatform` is a forward-looking field for the eventual
11
+ * platform-login check. v1 of syncSkillRegistry ignores it and
12
+ * registers everything.
13
+ */
14
+ export interface SkillDefault {
15
+ category: string;
16
+ description: string;
17
+ price: number;
18
+ skill_type?: "instant" | "escrow";
19
+ /** Cookie/login dependency. Empty/undefined = standalone. */
20
+ requiresPlatform?: string;
21
+ }
22
+ export declare const SKILL_DEFAULTS: Record<string, SkillDefault>;
23
+ export declare function listDefaultSkillNames(): string[];
@@ -0,0 +1,392 @@
1
+ /**
2
+ * Default marketplace listing metadata for each built-in skill.
3
+ *
4
+ * `syncSkillRegistry` reads this table to figure out which skills to
5
+ * auto-publish to the marketplace on `clawmoney market start`. Anything
6
+ * not in this map is intentionally not auto-registered — typically
7
+ * because it requires manual configuration (e.g. chatgpt.ask) or is a
8
+ * back-compat alias.
9
+ *
10
+ * `requiresPlatform` is a forward-looking field for the eventual
11
+ * platform-login check. v1 of syncSkillRegistry ignores it and
12
+ * registers everything.
13
+ */
14
+ function batch(prefix, category, price, requiresPlatform, items) {
15
+ const out = {};
16
+ for (const [suffix, description] of Object.entries(items)) {
17
+ out[`${prefix}.${suffix}`] = {
18
+ category,
19
+ description,
20
+ price,
21
+ skill_type: "instant",
22
+ ...(requiresPlatform ? { requiresPlatform } : {}),
23
+ };
24
+ }
25
+ return out;
26
+ }
27
+ export const SKILL_DEFAULTS = {
28
+ // ── Tier 5: utility ──
29
+ echo: {
30
+ category: "chat",
31
+ description: "Echo input back (test skill)",
32
+ price: 0,
33
+ skill_type: "instant",
34
+ },
35
+ // ── Tier 1: public content ──
36
+ // Wikipedia
37
+ ...batch("wiki", "reference/wikipedia", 0.004, undefined, {
38
+ search: "Wikipedia article search",
39
+ summary: "Wikipedia article summary",
40
+ random: "Random Wikipedia article",
41
+ trending: "Trending Wikipedia articles",
42
+ page: "Wikipedia full page",
43
+ }),
44
+ // Yahoo Finance
45
+ "yf.quote": {
46
+ category: "finance/yahoo",
47
+ description: "Yahoo Finance stock quote",
48
+ price: 0.001,
49
+ skill_type: "instant",
50
+ },
51
+ // Hacker News
52
+ ...batch("hn", "community/hackernews", 0.001, undefined, {
53
+ top: "Hacker News top stories",
54
+ new: "Hacker News newest stories",
55
+ best: "Hacker News best stories",
56
+ ask: "Hacker News Ask HN",
57
+ show: "Hacker News Show HN",
58
+ jobs: "Hacker News jobs",
59
+ search: "Hacker News keyword search",
60
+ user: "Hacker News user profile",
61
+ read: "Hacker News story detail + comments",
62
+ }),
63
+ // Reddit
64
+ ...batch("rd", "community/reddit", 0.002, undefined, {
65
+ popular_posts: "Reddit popular posts",
66
+ top_popular_posts: "Reddit top popular posts",
67
+ rising_popular_posts: "Reddit rising posts",
68
+ best_popular_posts: "Reddit best posts",
69
+ popular_posts_by_country: "Reddit popular posts by country",
70
+ posts_by_subreddit: "Posts in a subreddit",
71
+ top_posts_by_subreddit: "Top posts in a subreddit",
72
+ controversial_posts_by_subreddit: "Controversial posts in a subreddit",
73
+ comments_by_subreddit: "Comments in a subreddit",
74
+ subreddit_info: "Subreddit metadata",
75
+ subreddit_rules: "Subreddit rules",
76
+ similar_subreddits: "Similar subreddits",
77
+ new_subreddits: "Newly created subreddits",
78
+ popular_subreddits: "Popular subreddits",
79
+ posts_by_username: "User's posts",
80
+ top_posts_by_username: "User's top posts",
81
+ comments_by_username: "User's comments",
82
+ top_comments_by_username: "User's top comments",
83
+ user_overview: "Reddit user overview",
84
+ user_post_rank_in_subreddit: "User rank in a subreddit",
85
+ profile: "Reddit user profile",
86
+ user_stats: "Reddit user statistics",
87
+ search_users: "Search Reddit users",
88
+ search_posts: "Search Reddit posts",
89
+ search_subreddits: "Search subreddits",
90
+ post_details: "Reddit post detail",
91
+ post_comments: "Reddit post comments",
92
+ post_comments_with_sort: "Reddit post comments (sorted)",
93
+ post_duplicates: "Reddit post duplicates",
94
+ }),
95
+ // Bilibili (B 站)
96
+ ...batch("bili", "media/bilibili", 0.002, undefined, {
97
+ search: "Bilibili search",
98
+ hot: "Bilibili trending",
99
+ ranking: "Bilibili rankings",
100
+ video: "Bilibili video detail",
101
+ subtitle: "Bilibili video subtitle",
102
+ comments: "Bilibili video comments",
103
+ following: "Bilibili user following",
104
+ user_videos: "Bilibili user videos",
105
+ feed: "Bilibili dynamic feed",
106
+ feed_detail: "Bilibili dynamic detail",
107
+ download: "Bilibili video download",
108
+ }),
109
+ // Douban (豆瓣)
110
+ ...batch("db", "media/douban", 0.002, undefined, {
111
+ search: "Douban search",
112
+ movie_hot: "Douban hot movies",
113
+ book_hot: "Douban hot books",
114
+ top250: "Douban top 250",
115
+ photos: "Douban photos",
116
+ }),
117
+ // Sina Finance (新浪财经)
118
+ ...batch("sf", "finance/sina", 0.002, undefined, {
119
+ news: "Sina Finance news",
120
+ rolling_news: "Sina Finance rolling news",
121
+ stock: "Sina Finance stock quote",
122
+ }),
123
+ // Xueqiu (雪球)
124
+ ...batch("xq", "finance/xueqiu", 0.002, undefined, {
125
+ search: "Xueqiu search",
126
+ hot: "Xueqiu hot topics",
127
+ hot_stock: "Xueqiu hot stocks",
128
+ stock: "Xueqiu stock detail",
129
+ comments: "Xueqiu stock comments",
130
+ kline: "Xueqiu K-line",
131
+ earnings_date: "Xueqiu earnings calendar",
132
+ }),
133
+ // Xiaoyuzhou (小宇宙)
134
+ ...batch("xyz", "media/xiaoyuzhou", 0.002, undefined, {
135
+ podcast: "Xiaoyuzhou podcast info",
136
+ podcast_episodes: "Xiaoyuzhou podcast episodes",
137
+ episode: "Xiaoyuzhou episode detail",
138
+ }),
139
+ // WeRead (微信读书)
140
+ ...batch("wr", "read/weread", 0.002, undefined, {
141
+ search: "WeRead book search",
142
+ ranking: "WeRead rankings",
143
+ book: "WeRead book detail",
144
+ }),
145
+ // Ctrip (携程)
146
+ ...batch("ct", "travel/ctrip", 0.002, undefined, {
147
+ search: "Ctrip search",
148
+ hotel_suggest: "Ctrip hotel suggestions",
149
+ hotel_search: "Ctrip hotel search",
150
+ flight: "Ctrip flight search",
151
+ }),
152
+ // Jike (即刻)
153
+ ...batch("jk", "community/jike", 0.002, undefined, {
154
+ feed: "Jike feed",
155
+ search: "Jike search",
156
+ }),
157
+ // Bloomberg
158
+ ...batch("bbg", "news/bloomberg", 0.002, undefined, {
159
+ main: "Bloomberg homepage",
160
+ markets: "Bloomberg markets",
161
+ economics: "Bloomberg economics",
162
+ industries: "Bloomberg industries",
163
+ tech: "Bloomberg technology",
164
+ politics: "Bloomberg politics",
165
+ businessweek: "Bloomberg Businessweek",
166
+ opinions: "Bloomberg opinions",
167
+ feeds: "Bloomberg feeds",
168
+ article: "Bloomberg article detail",
169
+ }),
170
+ // BBC
171
+ ...batch("bbc", "news/bbc", 0.002, undefined, {
172
+ news: "BBC news",
173
+ topic: "BBC topic",
174
+ }),
175
+ // Medium
176
+ ...batch("med", "news/medium", 0.002, undefined, {
177
+ search: "Medium search",
178
+ tag: "Medium articles by tag",
179
+ feed: "Medium feed",
180
+ user: "Medium user articles",
181
+ }),
182
+ // Substack
183
+ ...batch("sub", "news/substack", 0.002, undefined, {
184
+ search: "Substack search",
185
+ publication: "Substack publication",
186
+ feed: "Substack feed",
187
+ }),
188
+ // 36Kr
189
+ ...batch("36kr", "news/36kr", 0.002, undefined, {
190
+ news: "36Kr news",
191
+ hot: "36Kr trending",
192
+ search: "36Kr search",
193
+ article: "36Kr article detail",
194
+ }),
195
+ // WXMP (微信公众号)
196
+ ...batch("wxmp", "news/wxmp", 0.002, undefined, {
197
+ article_search: "WeChat article search",
198
+ article: "WeChat article detail",
199
+ }),
200
+ // ── Tier 2: Google search ──
201
+ ...batch("gg", "search/google", 0.005, undefined, {
202
+ search: "Google web search",
203
+ suggest: "Google search suggestions",
204
+ news: "Google news",
205
+ trends: "Google Trends",
206
+ }),
207
+ // ── Tier 3: social platforms (login required) ──
208
+ // X / Twitter — 0.0004
209
+ ...batch("x", "social/x", 0.0004, "x", {
210
+ search: "Search tweets on X",
211
+ user_by_screen_name: "X user by screen name",
212
+ user_tweets: "X user tweets",
213
+ tweet: "X tweet detail",
214
+ trends: "X trending topics",
215
+ user_followers: "X user followers",
216
+ user_following: "X user following",
217
+ tweet_favoriters: "X tweet likers",
218
+ tweet_retweeters: "X tweet retweeters",
219
+ tweet_article: "X long-form article",
220
+ }),
221
+ // NB: x.search.legacy is a back-compat alias — not auto-registered.
222
+ // YouTube — 0.002
223
+ ...batch("yt", "social/youtube", 0.002, "youtube", {
224
+ video_details: "YouTube video details",
225
+ channel_details: "YouTube channel details",
226
+ channel_videos: "YouTube channel videos",
227
+ trending: "YouTube trending",
228
+ channel_search: "YouTube channel search",
229
+ video_streaming: "YouTube video streaming info",
230
+ video_related: "YouTube related videos",
231
+ video_comments: "YouTube comments",
232
+ video_transcript: "YouTube video transcript",
233
+ }),
234
+ // TikTok — 0.002
235
+ ...batch("tk", "social/tiktok", 0.002, "tiktok", {
236
+ search_video: "TikTok video search",
237
+ search_account: "TikTok account search",
238
+ user_info: "TikTok user info",
239
+ user_posts: "TikTok user posts",
240
+ user_followers: "TikTok user followers",
241
+ post_detail: "TikTok post detail",
242
+ post_comments: "TikTok post comments",
243
+ trending: "TikTok trending",
244
+ video_download: "TikTok video download",
245
+ challenge_info: "TikTok challenge info",
246
+ challenge_posts: "TikTok challenge posts",
247
+ music_info: "TikTok music info",
248
+ music_posts: "TikTok music posts",
249
+ music_unlimited_sounds: "TikTok unlimited sounds",
250
+ user_info_region: "TikTok user info by region",
251
+ user_info_by_id: "TikTok user info by ID",
252
+ user_followings: "TikTok user followings",
253
+ user_liked_posts: "TikTok user liked posts",
254
+ user_playlist: "TikTok user playlist",
255
+ user_repost: "TikTok user reposts",
256
+ user_story: "TikTok user story",
257
+ search_general: "TikTok general search",
258
+ search_live: "TikTok live search",
259
+ search_suggestions: "TikTok search suggestions",
260
+ post_related: "TikTok related posts",
261
+ post_explore: "TikTok explore feed",
262
+ post_discover: "TikTok discover feed",
263
+ ads_detail: "TikTok ad detail",
264
+ ads_top: "TikTok top ads",
265
+ trending_creator: "TikTok trending creators",
266
+ trending_video: "TikTok trending videos",
267
+ trending_hashtag: "TikTok trending hashtags",
268
+ trending_song: "TikTok trending songs",
269
+ trending_keyword: "TikTok trending keywords",
270
+ trending_keyword_posts: "TikTok keyword posts",
271
+ trending_keyword_sentence: "TikTok keyword sentences",
272
+ commercial_music_library: "TikTok commercial music library",
273
+ commercial_music_playlists: "TikTok commercial music playlists",
274
+ commercial_music_playlist_detail: "TikTok commercial playlist detail",
275
+ top_products: "TikTok top products",
276
+ top_product_detail: "TikTok product detail",
277
+ top_product_metrics: "TikTok product metrics",
278
+ place_info: "TikTok place info",
279
+ place_posts: "TikTok place posts",
280
+ effect_info: "TikTok effect info",
281
+ effect_posts: "TikTok effect posts",
282
+ collection_info: "TikTok collection info",
283
+ collection_posts: "TikTok collection posts",
284
+ post_comment_replies: "TikTok comment replies",
285
+ music_download: "TikTok music download",
286
+ user_video_download_batch: "TikTok user video batch download",
287
+ }),
288
+ // Douyin (抖音) — 0.002
289
+ ...batch("dy", "social/douyin", 0.002, "douyin", {
290
+ user_info: "Douyin user info",
291
+ user_posts: "Douyin user posts",
292
+ user_liked_posts: "Douyin user liked posts",
293
+ user_followers: "Douyin user followers",
294
+ user_following: "Douyin user following",
295
+ post_comments: "Douyin post comments",
296
+ search_general: "Douyin general search",
297
+ search_video: "Douyin video search",
298
+ search_account: "Douyin account search",
299
+ search_live: "Douyin live search",
300
+ challenge_posts: "Douyin challenge posts",
301
+ music_posts: "Douyin music posts",
302
+ }),
303
+ // Weibo (微博) — 0.002
304
+ ...batch("wb", "social/weibo", 0.002, "weibo", {
305
+ hot: "Weibo trending",
306
+ search: "Weibo search",
307
+ feed: "Weibo feed",
308
+ user: "Weibo user",
309
+ post: "Weibo post detail",
310
+ comments: "Weibo comments",
311
+ }),
312
+ // Xiaohongshu (小红书) — 0.02
313
+ ...batch("xhs", "social/xiaohongshu", 0.02, "xiaohongshu", {
314
+ creator_hot_inspiration_feed: "XHS creator hot inspiration feed",
315
+ product_recommendations: "XHS product recommendations",
316
+ note_comments: "XHS note comments",
317
+ search_groups: "XHS search groups",
318
+ mixed_note_detail: "XHS mixed note detail",
319
+ search_notes: "XHS search notes",
320
+ product_detail: "XHS product detail",
321
+ creator_inspiration_feed: "XHS creator inspiration feed",
322
+ image_note_detail: "XHS image note detail",
323
+ }),
324
+ // Zhihu (知乎) — 0.01
325
+ ...batch("zh", "social/zhihu", 0.01, "zhihu", {
326
+ search: "Zhihu search",
327
+ hot: "Zhihu trending",
328
+ recommend: "Zhihu recommendations",
329
+ question: "Zhihu question detail",
330
+ answer_detail: "Zhihu answer detail",
331
+ answer_comments: "Zhihu answer comments",
332
+ }),
333
+ // LinkedIn — 0.012
334
+ "li.job_search": {
335
+ category: "social/linkedin",
336
+ description: "LinkedIn job search",
337
+ price: 0.012,
338
+ skill_type: "instant",
339
+ requiresPlatform: "linkedin",
340
+ },
341
+ // Facebook — 0.01
342
+ ...batch("fb", "social/facebook", 0.01, "facebook", {
343
+ search: "Facebook search",
344
+ profile: "Facebook profile",
345
+ events: "Facebook events",
346
+ }),
347
+ // ── Tier 4: AI generation (chatgpt.ask intentionally skipped) ──
348
+ "codex.image_generate": {
349
+ category: "generation/image",
350
+ description: "Codex Desktop image generation",
351
+ price: 0.02,
352
+ skill_type: "instant",
353
+ requiresPlatform: "codex",
354
+ },
355
+ "chatgpt.image_generate": {
356
+ category: "generation/image",
357
+ description: "ChatGPT Desktop image generation",
358
+ price: 0.02,
359
+ skill_type: "instant",
360
+ requiresPlatform: "chatgpt",
361
+ },
362
+ "chatgpt_web.image_generate": {
363
+ category: "generation/image",
364
+ description: "ChatGPT Web image generation",
365
+ price: 0.02,
366
+ skill_type: "instant",
367
+ requiresPlatform: "chatgpt",
368
+ },
369
+ "gemini.image_generate": {
370
+ category: "generation/image",
371
+ description: "Gemini image generation",
372
+ price: 0.03,
373
+ skill_type: "instant",
374
+ },
375
+ "flow.image_generate": {
376
+ category: "generation/image",
377
+ description: "Google Labs Flow image generation",
378
+ price: 0.01,
379
+ skill_type: "instant",
380
+ requiresPlatform: "google",
381
+ },
382
+ "flow.video_generate": {
383
+ category: "generation/video",
384
+ description: "Google Labs Flow video generation",
385
+ price: 0.6,
386
+ skill_type: "instant",
387
+ requiresPlatform: "google",
388
+ },
389
+ };
390
+ export function listDefaultSkillNames() {
391
+ return Object.keys(SKILL_DEFAULTS);
392
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Auto-sync the built-in skill registry to the marketplace.
3
+ *
4
+ * Runs once on `clawmoney market start` (fire-and-forget, see provider.ts).
5
+ * For each skill in SKILL_DEFAULTS that the agent has not yet listed and
6
+ * has not opted out of, POST it to /api/v1/market/skills.
7
+ *
8
+ * Failures are warn-logged and never crash the daemon. The user can put
9
+ * skill names in `provider.disabled_skills` (config.yaml) to opt out;
10
+ * we never delete or modify listings the user already has.
11
+ */
12
+ import type { ProviderConfig } from "./types.js";
13
+ export declare function syncSkillRegistry(config: ProviderConfig): Promise<void>;
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Auto-sync the built-in skill registry to the marketplace.
3
+ *
4
+ * Runs once on `clawmoney market start` (fire-and-forget, see provider.ts).
5
+ * For each skill in SKILL_DEFAULTS that the agent has not yet listed and
6
+ * has not opted out of, POST it to /api/v1/market/skills.
7
+ *
8
+ * Failures are warn-logged and never crash the daemon. The user can put
9
+ * skill names in `provider.disabled_skills` (config.yaml) to opt out;
10
+ * we never delete or modify listings the user already has.
11
+ */
12
+ import { apiGet, apiPost } from "../utils/api.js";
13
+ import { logger } from "./logger.js";
14
+ import { SKILL_DEFAULTS } from "./skill-defaults.js";
15
+ const REGISTER_DELAY_MS = 200;
16
+ async function fetchMineSkillNames(apiKey) {
17
+ const resp = await apiGet("/api/v1/market/skills/mine", apiKey);
18
+ if (!resp.ok) {
19
+ logger.warn(`Skill sync: /skills/mine returned ${resp.status}`);
20
+ return null;
21
+ }
22
+ const rows = Array.isArray(resp.data)
23
+ ? resp.data
24
+ : (resp.data?.data ?? []);
25
+ const names = new Set();
26
+ for (const row of rows) {
27
+ const name = row?.skill_name ?? row?.name;
28
+ if (typeof name === "string" && name) {
29
+ names.add(name);
30
+ }
31
+ }
32
+ return names;
33
+ }
34
+ async function registerOne(apiKey, name, meta) {
35
+ const payload = {
36
+ skill_name: name,
37
+ category: meta.category,
38
+ description: meta.description,
39
+ price: meta.price,
40
+ skill_type: meta.skill_type ?? "instant",
41
+ };
42
+ const resp = await apiPost("/api/v1/market/skills", payload, apiKey);
43
+ if (!resp.ok) {
44
+ const detail = typeof resp.data === "object" && resp.data !== null && "detail" in resp.data
45
+ ? resp.data.detail
46
+ : resp.data;
47
+ logger.warn(`Skill sync: failed to register "${name}" (${resp.status}): ${typeof detail === "string" ? detail : JSON.stringify(detail).slice(0, 120)}`);
48
+ return false;
49
+ }
50
+ return true;
51
+ }
52
+ export async function syncSkillRegistry(config) {
53
+ if (!config.api_key) {
54
+ return;
55
+ }
56
+ const mine = await fetchMineSkillNames(config.api_key);
57
+ if (mine === null) {
58
+ return;
59
+ }
60
+ const disabled = new Set(config.provider.disabled_skills ?? []);
61
+ const toRegister = [];
62
+ for (const [name, meta] of Object.entries(SKILL_DEFAULTS)) {
63
+ if (mine.has(name))
64
+ continue;
65
+ if (disabled.has(name))
66
+ continue;
67
+ toRegister.push([name, meta]);
68
+ }
69
+ if (toRegister.length === 0) {
70
+ logger.info(`Skill sync: nothing to register (${mine.size} already listed)`);
71
+ return;
72
+ }
73
+ logger.info(`Skill sync: registering ${toRegister.length} new skill(s) (${mine.size} already listed)…`);
74
+ let ok = 0;
75
+ let fail = 0;
76
+ for (const [name, meta] of toRegister) {
77
+ const success = await registerOne(config.api_key, name, meta);
78
+ if (success)
79
+ ok++;
80
+ else
81
+ fail++;
82
+ if (REGISTER_DELAY_MS > 0) {
83
+ await new Promise((r) => setTimeout(r, REGISTER_DELAY_MS));
84
+ }
85
+ }
86
+ logger.info(`Skill sync: done (${ok} added, ${fail} failed)`);
87
+ }
@@ -91,6 +91,8 @@ export interface ProviderSettings {
91
91
  skills?: Record<string, {
92
92
  prompt_template?: string;
93
93
  }>;
94
+ /** Skill names the user has opted out of auto-registering / kept off the marketplace. */
95
+ disabled_skills?: string[];
94
96
  }
95
97
  export interface ProviderConfig {
96
98
  api_key: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawmoney",
3
- "version": "0.17.39",
3
+ "version": "0.17.40",
4
4
  "description": "ClawMoney CLI -- Earn rewards with your AI agent",
5
5
  "type": "module",
6
6
  "bin": {