bloby-bot 0.28.1 → 0.29.2

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/LICENSE ADDED
@@ -0,0 +1,75 @@
1
+ License text copyright (c) 2020 MariaDB Corporation Ab, All Rights Reserved.
2
+ "Business Source License" is a trademark of MariaDB Corporation Ab.
3
+
4
+ Parameters
5
+
6
+ Licensor: BERTA ONE LLC
7
+ Licensed Work: bloby-bot. The Licensed Work is (c) 2026 BERTA ONE LLC.
8
+ Additional Use Grant: You may make production use of the Licensed Work,
9
+ including for commercial purposes, provided that Your
10
+ use does not consist of offering to third parties a
11
+ hosted, managed, or multi-tenant service that (a)
12
+ provides Bloby agent functionality as a service, and
13
+ (b) operates a relay, marketplace, or equivalent
14
+ backend that substitutes for the services offered at
15
+ bloby.bot.
16
+
17
+ For clarity: deploying Bloby on behalf of a client,
18
+ reselling installation or configuration services,
19
+ running Bloby for Your own internal or single-tenant
20
+ use, and replacing the bloby.bot relay with Your own
21
+ private infrastructure (e.g., Tailscale, custom
22
+ tunnel) for non-competing use are all permitted.
23
+
24
+ For interpretive guidance, please contact
25
+ legal@bloby.bot.
26
+ Change Date: 2028-04-30
27
+ Change License: Apache License, Version 2.0
28
+
29
+ For information about alternative licensing arrangements for the Licensed
30
+ Work, please contact legal@bloby.bot.
31
+
32
+ Notice
33
+
34
+ Business Source License 1.1
35
+
36
+ Terms
37
+
38
+ The Licensor hereby grants you the right to copy, modify, create derivative
39
+ works, redistribute, and make non-production use of the Licensed Work. The
40
+ Licensor may make an Additional Use Grant, above, permitting limited production use.
41
+
42
+ Effective on the Change Date, or the fourth anniversary of the first publicly
43
+ available distribution of a specific version of the Licensed Work under this
44
+ License, whichever comes first, the Licensor hereby grants you rights under
45
+ the terms of the Change License, and the rights granted in the paragraph
46
+ above terminate.
47
+
48
+ If your use of the Licensed Work does not comply with the requirements
49
+ currently in effect as described in this License, you must purchase a
50
+ commercial license from the Licensor, its affiliated entities, or authorized
51
+ resellers, or you must refrain from using the Licensed Work.
52
+
53
+ All copies of the original and modified Licensed Work, and derivative works
54
+ of the Licensed Work, are subject to this License. This License applies
55
+ separately for each version of the Licensed Work and the Change Date may vary
56
+ for each version of the Licensed Work released by Licensor.
57
+
58
+ You must conspicuously display this License on each original or modified copy
59
+ of the Licensed Work. If you receive the Licensed Work in original or
60
+ modified form from a third party, the terms and conditions set forth in this
61
+ License apply to your use of that work.
62
+
63
+ Any use of the Licensed Work in violation of this License will automatically
64
+ terminate your rights under this License for the current and all other
65
+ versions of the Licensed Work.
66
+
67
+ This License does not grant you any right in any trademark or logo of
68
+ Licensor or its affiliates (provided that you may use a trademark or logo of
69
+ Licensor as expressly required by this License).
70
+
71
+ TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON
72
+ AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,
73
+ EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF
74
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND
75
+ TITLE.
package/README.md CHANGED
@@ -591,3 +591,15 @@ Quick Tunnel is the default for simplicity -- zero configuration, no Cloudflare
591
591
 
592
592
  **Why memory files instead of a database for agent memory?**
593
593
  Files are the natural interface for the Claude Agent SDK -- it can read and write them with its built-in tools. No custom tool needed, no API integration. The agent manages its own memory with the same tools it uses to edit code.
594
+
595
+ ---
596
+
597
+ ## License
598
+
599
+ Bloby is licensed under the **Business Source License 1.1** (BSL 1.1).
600
+
601
+ - **Permitted:** self-hosted personal/internal use, modifications, redistribution, deploying Bloby for clients (consulting/integration), and replacing the `bloby.bot` relay with your own private infrastructure.
602
+ - **Restricted:** offering Bloby as a hosted/managed/multi-tenant service that operates a relay or marketplace competing with `bloby.bot`.
603
+ - **Change Date:** 2028-04-30 — on this date the license auto-converts to **Apache License 2.0**.
604
+
605
+ See [LICENSE](./LICENSE) for the full terms. For commercial licensing inquiries, contact `legal@bloby.bot`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bloby-bot",
3
- "version": "0.28.1",
3
+ "version": "0.29.2",
4
4
  "releaseNotes": [
5
5
  "1. # voice note (PTT bubble)",
6
6
  "2. # audio file + caption",
@@ -9,11 +9,12 @@
9
9
  ],
10
10
  "description": "Self-hosted, self-evolving AI agent with its own dashboard.",
11
11
  "type": "module",
12
- "license": "MIT",
12
+ "license": "BUSL-1.1",
13
13
  "bin": {
14
14
  "bloby": "./bin/cli.js"
15
15
  },
16
16
  "files": [
17
+ "LICENSE",
17
18
  "bin/",
18
19
  "cli/",
19
20
  "dist-bloby/",
@@ -0,0 +1,71 @@
1
+ // Read on-chain USDC balances for one or more Tempo addresses.
2
+ //
3
+ // Fill in the CONFIG block below, then run from repo root:
4
+ // cp scripts/tempo-balance.ts /tmp/tempo-transfer/ && \
5
+ // (cd /tmp/tempo-transfer && ./node_modules/.bin/tsx tempo-balance.ts)
6
+ //
7
+ // (The script must be executed from inside the isolated /tmp/tempo-transfer/
8
+ // install — the repo's own node_modules has a @noble/hashes version conflict
9
+ // that breaks viem's ESM import resolution. To rebuild the isolated dir if
10
+ // missing: mkdir -p /tmp/tempo-transfer && cd /tmp/tempo-transfer \
11
+ // && npm init -y && npm install viem@2.47.6 tsx)
12
+ //
13
+ // Each entry prints: USDC balance, nonce (0 = wallet has never sent a tx),
14
+ // and whether the address is an EOA or a contract.
15
+
16
+ // ─── CONFIG ──────────────────────────────────────────────────────────────────
17
+
18
+ const ACCOUNTS = [
19
+ { label: 'Bot wallet', wallet: '0xc65d8a0dB80f637337B87e42b8BEb5D86D46f942' },
20
+ { label: 'Treasury', wallet: '0x084A80e395FaE8Aaf58F591f47F63DA1EeF06bbC' },
21
+ ];
22
+
23
+ // ─────────────────────────────────────────────────────────────────────────────
24
+
25
+ import { createPublicClient, http, formatUnits } from 'viem';
26
+ import { tempo } from 'viem/chains';
27
+
28
+ const USDC = '0x20c000000000000000000000b9537d11c60e8b50' as const;
29
+ const DECIMALS = 6;
30
+ const RPC = 'https://rpc.tempo.xyz';
31
+
32
+ const erc20Abi = [{
33
+ name: 'balanceOf',
34
+ type: 'function',
35
+ stateMutability: 'view',
36
+ inputs: [{ name: 'account', type: 'address' }],
37
+ outputs: [{ name: '', type: 'uint256' }],
38
+ }] as const;
39
+
40
+ async function main() {
41
+ const client = createPublicClient({ chain: tempo, transport: http(RPC) });
42
+ const block = await client.getBlockNumber();
43
+
44
+ console.log(`Block: ${block}`);
45
+ console.log('');
46
+
47
+ for (const { label, wallet } of ACCOUNTS) {
48
+ const address = wallet as `0x${string}`;
49
+
50
+ const [balance, nonce, code] = await Promise.all([
51
+ client.readContract({
52
+ address: USDC, abi: erc20Abi, functionName: 'balanceOf', args: [address],
53
+ }),
54
+ client.getTransactionCount({ address }),
55
+ client.getCode({ address }),
56
+ ]);
57
+
58
+ const isContract = code && code !== '0x';
59
+
60
+ console.log(`${label} ${address}`);
61
+ console.log(` USDC: ${formatUnits(balance, DECIMALS)}`);
62
+ console.log(` nonce: ${nonce}${nonce === 0 ? ' (never sent a tx)' : ''}`);
63
+ console.log(` type: ${isContract ? 'contract' : 'EOA'}`);
64
+ console.log('');
65
+ }
66
+ }
67
+
68
+ main().catch((err) => {
69
+ console.error(err);
70
+ process.exit(1);
71
+ });
@@ -0,0 +1,153 @@
1
+ // End-to-end test for the marketplace single-product MPP buy flow.
2
+ //
3
+ // Fill in the CONFIG block, then run:
4
+ // cp scripts/test-mpp-buy.ts /tmp/tempo-transfer/ && \
5
+ // (cd /tmp/tempo-transfer && ./node_modules/.bin/tsx test-mpp-buy.ts)
6
+ //
7
+ // (The script must run inside the isolated /tmp/tempo-transfer/ install — the
8
+ // repo's @noble/hashes version conflicts with viem's ESM imports here. To
9
+ // rebuild that dir: mkdir -p /tmp/tempo-transfer && cd /tmp/tempo-transfer \
10
+ // && npm init -y && npm install viem@2.47.6 mppx tsx)
11
+ //
12
+ // What it does:
13
+ // 1. Reads the bot wallet's USDC balance + treasury + (optionally) seller.
14
+ // 2. POSTs to /api/marketplace/buy/:productId via the mppx client.
15
+ // 3. If the product has a seller with a wallet, the on-chain charge splits
16
+ // 80/20 — verify both wallets move in the same block.
17
+ // 4. Follows the first download URL to confirm the JWT-gated download works.
18
+
19
+ // ─── CONFIG ──────────────────────────────────────────────────────────────────
20
+
21
+ const RELAY = 'https://api.bloby.bot';
22
+ const PRODUCT_ID = '<FILL_ME — e.g. sebastians-skill>';
23
+
24
+ const BOT = {
25
+ relayToken: '<FILL_ME — 64-hex-char relay token>',
26
+ privateKey: '<FILL_ME — bot wallet private key, with or without 0x prefix>',
27
+ wallet: '<FILL_ME — bot wallet address>',
28
+ };
29
+
30
+ // Optional: seller wallet to monitor for the 80% split landing.
31
+ // Leave null if the product has no seller (then 100% goes to treasury).
32
+ const SELLER_WALLET: string | null = null;
33
+
34
+ const TREASURY = '0x084A80e395FaE8Aaf58F591f47F63DA1EeF06bbC';
35
+
36
+ // ─────────────────────────────────────────────────────────────────────────────
37
+
38
+ import { Mppx, tempo } from 'mppx/client';
39
+ import { privateKeyToAccount } from 'viem/accounts';
40
+ import { createPublicClient, http, formatUnits } from 'viem';
41
+ import { tempo as tempoChain } from 'viem/chains';
42
+
43
+ const USDC = '0x20c000000000000000000000b9537d11c60e8b50' as const;
44
+ const RPC = 'https://rpc.tempo.xyz';
45
+
46
+ const erc20Abi = [{
47
+ name: 'balanceOf',
48
+ type: 'function',
49
+ stateMutability: 'view',
50
+ inputs: [{ name: 'account', type: 'address' }],
51
+ outputs: [{ name: '', type: 'uint256' }],
52
+ }] as const;
53
+
54
+ function normalizePk(pk: string): `0x${string}` {
55
+ const t = pk.trim();
56
+ return (t.startsWith('0x') ? t : `0x${t}`) as `0x${string}`;
57
+ }
58
+
59
+ async function balanceOf(client: any, address: `0x${string}`) {
60
+ return client.readContract({
61
+ address: USDC, abi: erc20Abi, functionName: 'balanceOf', args: [address],
62
+ });
63
+ }
64
+
65
+ async function main() {
66
+ for (const [k, v] of [['PRODUCT_ID', PRODUCT_ID], ['BOT.relayToken', BOT.relayToken], ['BOT.privateKey', BOT.privateKey], ['BOT.wallet', BOT.wallet]]) {
67
+ if (typeof v !== 'string' || v.startsWith('<')) {
68
+ console.error(`Fill in ${k} before running.`);
69
+ process.exit(1);
70
+ }
71
+ }
72
+
73
+ const account = privateKeyToAccount(normalizePk(BOT.privateKey));
74
+ if (account.address.toLowerCase() !== BOT.wallet.toLowerCase()) {
75
+ console.error(`privateKey derives ${account.address}, but BOT.wallet is ${BOT.wallet}`);
76
+ process.exit(1);
77
+ }
78
+
79
+ const publicClient = createPublicClient({ chain: tempoChain, transport: http(RPC) });
80
+
81
+ const [botBefore, treasuryBefore, sellerBefore] = await Promise.all([
82
+ balanceOf(publicClient, account.address),
83
+ balanceOf(publicClient, TREASURY as `0x${string}`),
84
+ SELLER_WALLET ? balanceOf(publicClient, SELLER_WALLET as `0x${string}`) : Promise.resolve(0n),
85
+ ]);
86
+
87
+ console.log(`Wallet: ${account.address}`);
88
+ console.log(`Balance before: ${formatUnits(botBefore, 6)} USDC`);
89
+ console.log(`Treasury before: ${formatUnits(treasuryBefore, 6)} USDC`);
90
+ if (SELLER_WALLET) console.log(`Seller before: ${formatUnits(sellerBefore, 6)} USDC`);
91
+ console.log('');
92
+
93
+ const mppx = Mppx.create({
94
+ polyfill: false,
95
+ methods: [tempo({ account })],
96
+ });
97
+
98
+ const url = `${RELAY}/api/marketplace/buy/${PRODUCT_ID}`;
99
+ console.log(`POST ${url}`);
100
+
101
+ const res = await mppx.fetch(url, {
102
+ method: 'POST',
103
+ headers: {
104
+ 'X-Bloby-Token': BOT.relayToken,
105
+ 'Content-Type': 'application/json',
106
+ },
107
+ body: JSON.stringify({}),
108
+ });
109
+
110
+ const body = await res.text();
111
+ const receipt = res.headers.get('Payment-Receipt');
112
+ console.log(`Status: ${res.status}`);
113
+ console.log(`Body: ${body}`);
114
+ console.log(`Payment-Receipt: ${receipt ?? '(none)'}`);
115
+ console.log('');
116
+
117
+ if (!res.ok) {
118
+ process.exit(1);
119
+ }
120
+
121
+ const parsed = JSON.parse(body);
122
+ if (parsed.skills?.length) {
123
+ console.log(`Bought ${parsed.skills.length} skill(s) via "${parsed.paidVia}":`);
124
+ for (const skill of parsed.skills) {
125
+ console.log(` - ${skill.name}@${skill.version} → ${skill.url}`);
126
+ }
127
+ console.log('');
128
+
129
+ // Verify the first download URL actually returns a tarball
130
+ const dlUrl = parsed.skills[0].url;
131
+ console.log(`Probing download: HEAD ${dlUrl}`);
132
+ const dl = await fetch(dlUrl, { method: 'GET' });
133
+ const contentLength = dl.headers.get('Content-Length');
134
+ console.log(` status: ${dl.status} content-type: ${dl.headers.get('Content-Type')} size: ${contentLength ?? '?'} bytes`);
135
+ // drain body to release connection
136
+ if (dl.body) await dl.arrayBuffer();
137
+ console.log('');
138
+ }
139
+
140
+ const [botAfter, treasuryAfter, sellerAfter] = await Promise.all([
141
+ balanceOf(publicClient, account.address),
142
+ balanceOf(publicClient, TREASURY as `0x${string}`),
143
+ SELLER_WALLET ? balanceOf(publicClient, SELLER_WALLET as `0x${string}`) : Promise.resolve(0n),
144
+ ]);
145
+ console.log(`Balance after: ${formatUnits(botAfter, 6)} USDC (Δ ${formatUnits(botAfter - botBefore, 6)})`);
146
+ console.log(`Treasury after: ${formatUnits(treasuryAfter, 6)} USDC (Δ +${formatUnits(treasuryAfter - treasuryBefore, 6)})`);
147
+ if (SELLER_WALLET) console.log(`Seller after: ${formatUnits(sellerAfter, 6)} USDC (Δ +${formatUnits(sellerAfter - sellerBefore, 6)})`);
148
+ }
149
+
150
+ main().catch((err) => {
151
+ console.error(err);
152
+ process.exit(1);
153
+ });
@@ -335,6 +335,16 @@ MPPX_PRIVATE_KEY=$(jq -r .wallet.privateKey ~/.bloby/config.json) \
335
335
 
336
336
  If you see `ACCOUNT_NOT_FOUND - No account found.`, you forgot to set `MPPX_PRIVATE_KEY` — the CLI looked in its empty keychain. Re-run with the env var.
337
337
 
338
+ **Buying a skill, blueprint, or bundle autonomously.** Same shape as the service call — point mppx at `/api/marketplace/buy/<productId>` instead. The relay tries your owner's credits first, then falls back to MPP from your wallet. Returns `{ skills: [{ url, sha256, ... }] }` — follow each `url` to download the .tar.gz and extract to `skills/`.
339
+
340
+ ```bash
341
+ MPPX_PRIVATE_KEY=$(jq -r .wallet.privateKey ~/.bloby/config.json) \
342
+ npx -y mppx https://api.bloby.bot/api/marketplace/buy/<productId> \
343
+ -X POST -H "X-Bloby-Token: $RELAY_TOKEN"
344
+ ```
345
+
346
+ **Don't use `/marketplace/checkout/bot` for autonomous buys.** That endpoint is the cart-style flow — balance-only, no MPP fallback. If you hit it and credits are short you'll get a 402 with no way to retry. Always prefer `/marketplace/buy/:productId`.
347
+
338
348
  **Wallet on disk:** `~/.bloby/config.json` field `wallet` — `address` is public, `privateKey` is secret. Never print the private key in chat. The address is fine to share.
339
349
 
340
350
  **Running low?** Tell your human you need funds. Don't mention USDC, Tempo, or crypto unless they ask — to them, it's just adding dollars to your wallet via the "Add Funds" button in the chat header.
@@ -372,6 +382,21 @@ Always prefer editing existing files over creating new ones. This prevents file
372
382
  ## Careful Execution
373
383
  Consider the reversibility and blast radius of actions. Prefer `trash` over `rm` — recoverable beats gone forever. If something fails, pivot — don't retry the same thing blindly. Read error messages carefully and address root causes.
374
384
 
385
+ ## Stop looping (HARD RULE)
386
+
387
+ If your second attempt at fixing a problem produces the same error as the first, **STOP**. Do not try a third variation of the same family of fix.
388
+
389
+ - If the error is identical, your diagnosis is wrong. Re-read the error from scratch, ignoring your previous theory.
390
+ - Run a different *kind* of check (read the file, check the filesystem, inspect logs) — not another *kind* of fix.
391
+ - If you genuinely can't identify the root cause after re-diagnosing, escalate: tell your human plainly what's broken, what you tried, and ask them to restart bloby (`bloby restart` from their terminal). Asking for help is faster than burning their time on a doomed loop.
392
+
393
+ Examples of patterns that count as "the same family of fix":
394
+ - Clearing Vite cache → installing dummy dep → touching files → adding new dep — these are all "force Vite to re-bundle" and count as ONE attempt, not four.
395
+ - Restarting the same process twice in a row.
396
+ - Running `npm install` repeatedly when the underlying error is unchanged.
397
+
398
+ A user staring at a black dashboard while you try the seventh variation of the same fix is worse off than a user who got told "I think node_modules is in a bad state, can you restart bloby?" after the second failure.
399
+
375
400
  ## Parallel Operations
376
401
  Run independent tool calls in parallel. Don't serialize what can run concurrently. When you need to read multiple files, read them all at once.
377
402
 
@@ -506,6 +531,26 @@ Before installing, check if a suitable package is already in `node_modules/`. Pr
506
531
 
507
532
  **Never** run `npm install` from the parent directory or modify the parent's `package.json`. Your dependencies are sandboxed to workspace — this boundary is enforced at the runtime level.
508
533
 
534
+ ### Hard rule: imports MUST have matching installed packages
535
+
536
+ Every `import` statement you add to a file must correspond to a real package in `node_modules/`. Workflow:
537
+
538
+ 1. **Install first, import second.** Run `npm install <pkg>` BEFORE you save a file that imports it. Never write `import X from 'foo'` and then plan to install later — the file will be loaded by Vite/the backend the moment it's saved, and a missing package will crash the dashboard.
539
+ 2. **Confirm it landed.** After `npm install`, verify with `ls workspace/node_modules/<pkg>/package.json` (or `cat` the workspace `package.json` to confirm it was added to `dependencies`). If the directory isn't there, the install didn't actually take.
540
+ 3. **Same rule for delegated work.** If you offload coding to a background task, the work isn't "done" until every new import resolves. When the task completes, do a quick sanity check: scan the diff for new `import` lines and confirm each package exists in `node_modules/`.
541
+ 4. **Never use `--no-save`.** Always let `npm install` write to `package.json`. Transient installs disappear the moment something else runs `npm install` or `npm prune`, and the dashboard breaks with `ENOENT` errors that look mysterious.
542
+
543
+ ### Diagnosing Vite / dev-server errors
544
+
545
+ When the dashboard shows a black screen or Vite logs an error, READ the error before acting. Don't reflexively clear caches.
546
+
547
+ - **`Error: ENOENT: no such file or directory, open '.../node_modules/<pkg>/...'`** → The package is **physically missing on disk**. The fix is `npm install <pkg>` — full stop. Clearing `.vite/deps/`, touching files, adding dummy deps, or restarting will NOT recreate the missing file.
548
+ - **`Failed to resolve import "<pkg>"`** with no path in the error → Same diagnosis: package isn't installed. Run `npm install <pkg>`.
549
+ - **Pre-bundling / optimizer errors that don't reference a missing path, OR errors that persist after dependency changes** → Vite's dep cache is stale. Clear it: `rm -rf workspace/node_modules/.vite` and reload. (This is the ONLY case where clearing the cache helps.)
550
+ - **Backend crash loop** → Read `.backend.log`. Don't guess.
551
+
552
+ If you've tried a fix and the same error recurs, do NOT try a variation of the same fix. Re-diagnose from the error message, or stop and ask your human to restart bloby. See "Stop looping" below.
553
+
509
554
  ## Backend Lifecycle (Critical)
510
555
 
511
556
  The supervisor manages the backend process. You don't need to manage it yourself.