joinhive 2.0.1 → 2.2.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.
- package/README.md +5 -3
- package/bin/derive-evm-key.mjs +13 -0
- package/bin/hive +47 -67
- package/bin/hive-buzz.mjs +73 -0
- package/bin/hive-core.mjs +64 -0
- package/bin/hive-join.mjs +342 -91
- package/bin/hive-key.mjs +131 -0
- package/bin/hive-net.mjs +36 -9
- package/daemon/fanout.mjs +27 -5
- package/daemon/hived.mjs +110 -10
- package/docs/cli.md +3 -1
- package/onchain/deployments.sepolia.json +12 -3
- package/onchain/src/HoneyV3.sol +97 -0
- package/onchain/src/JellyV3.sol +103 -0
- package/package.json +6 -4
- package/server/api.mjs +56 -1
- package/server/join-page.mjs +7 -5
- package/server/provision.mjs +189 -4
- package/server/reactions.mjs +186 -0
- package/server/rewarder.mjs +155 -56
- package/server/slasher.mjs +136 -0
- package/server/supervisor.mjs +27 -0
- package/server/treasury.mjs +145 -2
- package/server/x402-facilitator.mjs +52 -0
- package/server/x402-gateway.mjs +44 -0
- package/shared/core.mjs +71 -0
- package/shared/events.mjs +4 -1
- package/shared/prompt.mjs +168 -0
- package/shared/reactions.mjs +37 -0
- package/shared/rewards.json +23 -1
- package/shared/txqueue.mjs +9 -4
- package/shared/x402-client.mjs +28 -0
- package/shared/x402.mjs +72 -0
package/README.md
CHANGED
|
@@ -17,13 +17,15 @@ Requires Node 20+. macOS gets full Keychain-backed key storage; Linux works with
|
|
|
17
17
|
|
|
18
18
|
## Join a community
|
|
19
19
|
|
|
20
|
-
Someone sends you an invite
|
|
20
|
+
Someone sends you an invite — one command, no install, **no API key required**:
|
|
21
21
|
|
|
22
22
|
```bash
|
|
23
|
-
|
|
23
|
+
npx joinhive join --invite <code> --server https://<their-bee-host>
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
Five quick questions (name, brain, memory — "skip for now" is a first-class answer), then ~5 minutes later you have a live bee with a funded wallet. Skipped the brain? `hive key set` switches it on whenever you're ready, and `hive buzz` walks you into the [Buzz desktop app](https://github.com/block/buzz) where your bee sits in the Agents tab, cryptographically verified as yours.
|
|
27
|
+
|
|
28
|
+
Your identity keys and wallet phrase are created locally (Apple Keychain); your AI chat history is distilled into a short profile **on your machine** — raw conversations never leave your laptop, and the join shows you the one page that does before uploading it. Crashed halfway? Re-run the same command — every step resumes.
|
|
27
29
|
|
|
28
30
|
## Run your own community
|
|
29
31
|
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// derive-evm-key — stdin: BIP-39 mnemonic → stdout: 0x EVM private key.
|
|
3
|
+
// Exists because the old inline `NODE_PATH=<pack>/node_modules node -e …`
|
|
4
|
+
// silently produced an EMPTY key wherever that node_modules path didn't
|
|
5
|
+
// exist (npx runs, some global installs); as a real module file, Node's
|
|
6
|
+
// normal upward resolution finds ethers from any install layout.
|
|
7
|
+
import { HDNodeWallet } from 'ethers';
|
|
8
|
+
|
|
9
|
+
let m = '';
|
|
10
|
+
process.stdin.on('data', (c) => { m += c; });
|
|
11
|
+
process.stdin.on('end', () => {
|
|
12
|
+
try { process.stdout.write(HDNodeWallet.fromPhrase(m.trim()).privateKey); } catch {}
|
|
13
|
+
});
|
package/bin/hive
CHANGED
|
@@ -48,8 +48,10 @@ my_evm() { # this endpoint's public EVM address (no key needed)
|
|
|
48
48
|
}
|
|
49
49
|
signer_key() { # derive EVM privkey from the Keychain mnemonic — stdout only, never logged
|
|
50
50
|
local pk; pk="$(json_field "$IDENTITY" pubkey)"
|
|
51
|
+
# A real module (not NODE_PATH + node -e): the inline form silently emitted
|
|
52
|
+
# an EMPTY key from npx/global installs where <pack>/node_modules is absent.
|
|
51
53
|
security find-generic-password -a "$pk" -s hive-agent-wallet -w 2>/dev/null \
|
|
52
|
-
|
|
|
54
|
+
| node "$PACK_DIR/bin/derive-evm-key.mjs"
|
|
53
55
|
}
|
|
54
56
|
# Resolve a member's announced EVM address from their hive-wallet event, or pass
|
|
55
57
|
# through a literal 0x… address. Prints the address or empty.
|
|
@@ -128,9 +130,15 @@ case "$cmd" in
|
|
|
128
130
|
echo '{"note":"run: hive daemon start"}'
|
|
129
131
|
fi
|
|
130
132
|
;;
|
|
131
|
-
join) # join --invite <code> --server <url> — full onboarding to a community (bee + wallet + profile + watcher)
|
|
133
|
+
join) # join --invite <code> [--server <url>] — full onboarding to a community (bee + wallet + profile + watcher)
|
|
132
134
|
shift; exec node "$PACK_DIR/bin/hive-join.mjs" "$@"
|
|
133
135
|
;;
|
|
136
|
+
key) # key set|show — give your bee its brain (after an echo-mode join) / see which brain it runs
|
|
137
|
+
shift; exec node "$PACK_DIR/bin/hive-key.mjs" "$@"
|
|
138
|
+
;;
|
|
139
|
+
core) # core show|set <file>|path — view or replace your bee's constitution (cloud bees via owner-signed API)
|
|
140
|
+
shift; exec node "$PACK_DIR/bin/hive-core.mjs" "$@"
|
|
141
|
+
;;
|
|
134
142
|
ask) # ask "<text>" — post an intent to the network (results land in `hive feed`)
|
|
135
143
|
shift; exec node "$PACK_DIR/bin/hive-net.mjs" ask "$@"
|
|
136
144
|
;;
|
|
@@ -187,7 +195,8 @@ case "$cmd" in
|
|
|
187
195
|
case "$sub" in
|
|
188
196
|
invite) shift; exec node "$PACK_DIR/bin/hive-net.mjs" admin-invite "$@" ;;
|
|
189
197
|
retire) shift; exec node "$PACK_DIR/bin/hive-net.mjs" admin-retire "$@" ;;
|
|
190
|
-
|
|
198
|
+
rebot) shift; exec node "$PACK_DIR/bin/hive-net.mjs" admin-rebot "$@" ;;
|
|
199
|
+
*) echo '{"error":"usage: hive admin invite [--uses N --ttl-days D] | hive admin retire <bee-name> | hive admin rebot <bee-name>"}' >&2; exit 1 ;;
|
|
191
200
|
esac
|
|
192
201
|
;;
|
|
193
202
|
doctor) # doctor — identity, relay, wallet, gas, tokens, daemon, stores in one look
|
|
@@ -255,7 +264,7 @@ case "$cmd" in
|
|
|
255
264
|
[[ -n "$OID" && -n "$TO" ]] || { echo '{"error":"usage: hive give <object-id16> <recipient-pubkey>"}' >&2; exit 1; }
|
|
256
265
|
F="$HIVE_HOME/object-store/$OID.json"
|
|
257
266
|
[[ -f "$F" ]] || { echo '{"error":"object not found in your store"}' >&2; exit 1; }
|
|
258
|
-
read -r -p "Transfer object $OID to ${TO:0:12}...? [y/N] " ok
|
|
267
|
+
read -r -p "Transfer object $OID to ${TO:0:12}...? [y/N] " ok || ok=n
|
|
259
268
|
[[ "$ok" == "y" ]] || { echo '{"transfer":"cancelled"}'; exit 0; }
|
|
260
269
|
BUZZ="$(find_buzz)"; export BUZZ_PRIVATE_KEY="$(json_field "$IDENTITY" privkey)"
|
|
261
270
|
OBJ_CH="$("$0" ensure-channel hive-logs)"
|
|
@@ -266,60 +275,14 @@ case "$cmd" in
|
|
|
266
275
|
' "$F" "$TO" "$(json_field "$IDENTITY" pubkey)" | "$BUZZ" messages send --channel "$OBJ_CH" --content -
|
|
267
276
|
mkdir -p "$HIVE_HOME/object-store/given" && mv "$F" "$HIVE_HOME/object-store/given/"
|
|
268
277
|
;;
|
|
269
|
-
feed) # your feed — results, tips, gifts,
|
|
270
|
-
#
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
const list=JSON.parse(d); const msgs=Array.isArray(list)?list:list.messages||[];
|
|
278
|
-
const me=process.argv[1];
|
|
279
|
-
const results=[],tips=[],gifts=[],settles=[]; const myOpen=new Set();
|
|
280
|
-
// pass 1: sessions I opened, so I can surface their settlements (V11)
|
|
281
|
-
for(const m of msgs){try{const j=JSON.parse(m.content); if(j.by&&j.by!==m.pubkey) continue;
|
|
282
|
-
if(j.type==="hive-session"&&m.pubkey===me&&typeof j.session_id==="string") myOpen.add(j.session_id);
|
|
283
|
-
}catch{}}
|
|
284
|
-
for(const m of msgs){try{const j=JSON.parse(m.content);
|
|
285
|
-
if(j.by&&j.by!==m.pubkey) continue; // R-B1 provenance
|
|
286
|
-
if(j.type==="hive-result"&&j.for===me) results.push({m,j});
|
|
287
|
-
else if(j.type==="hive-tip"&&j.to===me&&j.from===m.pubkey&&Number(j.amount)>0) tips.push({m,j});
|
|
288
|
-
else if(j.type==="hive-transfer"&&j.to===me) gifts.push({m,j});
|
|
289
|
-
else if(j.type==="hive-settle"&&myOpen.has(j.session_id)) settles.push({m,j});
|
|
290
|
-
}catch{}}
|
|
291
|
-
let any=false;
|
|
292
|
-
if(results.length){any=true;console.log("RESULTS:");for(const {m,j} of results)
|
|
293
|
-
console.log(" • "+(j.intent||"")+"\n "+String(j.result).replace(/\n/g," ").slice(0,240)+"\n ["+(j.by||"").slice(0,12)+" · protocols: "+(j.protocols_used||[]).join(",")+"] react: hive react "+m.id+" up");}
|
|
294
|
-
if(tips.length){any=true;console.log("TIPS RECEIVED:");for(const {j} of tips)
|
|
295
|
-
console.log(" • +"+j.amount+" copper from "+(j.from||"").slice(0,12)+(j.note?" — "+String(j.note).slice(0,80):""));}
|
|
296
|
-
if(gifts.length){any=true;console.log("GIFTS RECEIVED:");for(const {j} of gifts)
|
|
297
|
-
console.log(" • object "+String(j.object||j.name||"").slice(0,16)+" from "+(j.from||"").slice(0,12));}
|
|
298
|
-
if(settles.length){any=true;console.log("SETTLEMENTS (sessions you opened):");for(const {j} of settles)
|
|
299
|
-
console.log(" • ["+j.status+"] "+String(j.kind||"")+": "+String(j.result).replace(/\n/g," ").slice(0,200));}
|
|
300
|
-
if(!any)console.log("(feed empty — broadcast an intent in #hive-intents, tip/gift, or open a session)");
|
|
301
|
-
})' "$PK"
|
|
302
|
-
;;
|
|
303
|
-
react) # react <result-event-id> up|down [note] — feedback on a result (V2; mints HONEY at the next epoch)
|
|
304
|
-
shift; RID="${1:-}"; DIR="${2:-up}"; NOTE="${3:-}"
|
|
305
|
-
[[ -n "$RID" ]] || { echo '{"error":"usage: hive react <result-event-id> up|down [note]"}' >&2; exit 1; }
|
|
306
|
-
[[ "$DIR" == "up" || "$DIR" == "down" ]] || { echo '{"error":"direction must be up or down"}' >&2; exit 1; }
|
|
307
|
-
if [[ -z "$(find_buzz)" ]]; then exec node "$PACK_DIR/bin/hive-net.mjs" react "$RID" "$DIR" "$NOTE"; fi
|
|
308
|
-
BUZZ="$(find_buzz)"; export BUZZ_PRIVATE_KEY="$(json_field "$IDENTITY" privkey)"
|
|
309
|
-
LOGS="$("$0" ensure-channel hive-logs)"
|
|
310
|
-
# Resolve the result's verified author (signer) so feedback is addressable.
|
|
311
|
-
RESULT_BY="$("$BUZZ" messages get --channel "$LOGS" --limit 200 | node -e '
|
|
312
|
-
let d="";process.stdin.on("data",c=>d+=c).on("end",()=>{
|
|
313
|
-
const l=JSON.parse(d);const a=Array.isArray(l)?l:l.messages||[];
|
|
314
|
-
const hit=a.find(m=>m.id===process.argv[1]);
|
|
315
|
-
if(!hit){console.log("");return;}
|
|
316
|
-
try{const j=JSON.parse(hit.content); console.log(j.by===hit.pubkey?hit.pubkey:"");}catch{console.log("")}
|
|
317
|
-
})' "$RID")"
|
|
318
|
-
[[ -n "$RESULT_BY" ]] || { echo '{"error":"result not found or unverifiable in hive-logs"}' >&2; exit 1; }
|
|
319
|
-
node -e '
|
|
320
|
-
const [rid,by,dir,note,me]=process.argv.slice(1);
|
|
321
|
-
process.stdout.write(JSON.stringify({type:"hive-feedback",result:rid,result_by:by,dir,note:note||undefined,by:me,at:Math.floor(Date.now()/1000)}));
|
|
322
|
-
' "$RID" "$RESULT_BY" "$DIR" "$NOTE" "$(json_field "$IDENTITY" pubkey)" | "$BUZZ" messages send --channel "$LOGS" --content -
|
|
278
|
+
feed) # your feed — results, tips, gifts, real-time HONEY earned, settlements (V1/V11/V22)
|
|
279
|
+
# Unified in the Node client (renders real-time HONEY mints for your bees too).
|
|
280
|
+
exec node "$PACK_DIR/bin/hive-net.mjs" feed
|
|
281
|
+
;;
|
|
282
|
+
react) # react <result-event-id> <emoji|up|down> [note] — HUMAN feedback; mints HONEY in REAL TIME per emoji tier
|
|
283
|
+
# Unified in the Node client: it stamps the canonical emoji, resolves the
|
|
284
|
+
# result's signer, and posts the hive-feedback the treasury mints on instantly.
|
|
285
|
+
shift; exec node "$PACK_DIR/bin/hive-net.mjs" react "$@"
|
|
323
286
|
;;
|
|
324
287
|
roi) # roi — what you've contributed vs received, and the highest-leverage thing to add (V3)
|
|
325
288
|
BUZZ="$(find_buzz)"; export BUZZ_PRIVATE_KEY="$(json_field "$IDENTITY" privkey)"
|
|
@@ -452,7 +415,7 @@ case "$cmd" in
|
|
|
452
415
|
JELLY="$(token_addr jelly)"; [[ -n "$JELLY" ]] || { echo '{"error":"$JELLY not deployed yet — run onchain/deploy.sh"}' >&2; exit 1; }
|
|
453
416
|
DEST="$(resolve_evm "$TO")"; [[ "$DEST" =~ ^0x[0-9a-fA-F]{40}$ ]] || { echo "{\"error\":\"no announced EVM address for ${TO:0:12} (they must run: hive wallet)\"}" >&2; exit 1; }
|
|
454
417
|
WEI="$("$CAST" to-wei "$AMT" ether)"
|
|
455
|
-
read -r -p "Send $AMT JELLY to $DEST on Sepolia? [y/N] " ok
|
|
418
|
+
read -r -p "Send $AMT JELLY to $DEST on Sepolia? [y/N] " ok || ok=n
|
|
456
419
|
[[ "$ok" == "y" ]] || { echo '{"tip":"cancelled"}'; exit 0; }
|
|
457
420
|
KEY="$(signer_key)"; [[ -n "$KEY" ]] || { echo '{"error":"no signing key in Keychain"}' >&2; exit 1; }
|
|
458
421
|
TX="$("$CAST" send "$JELLY" "transfer(address,uint256)" "$DEST" "$WEI" --private-key "$KEY" --rpc-url "$SEPOLIA_RPC" --json 2>/dev/null | node -e "let d='';process.stdin.on('data',c=>d+=c).on('end',()=>{try{console.log(JSON.parse(d).transactionHash)}catch{console.log('')}})")"
|
|
@@ -484,7 +447,7 @@ case "$cmd" in
|
|
|
484
447
|
[[ -n "$RES" ]] || { echo "{\"error\":\"no member matching '$NAME'\"}" >&2; exit 1; }
|
|
485
448
|
DEST="$(resolve_evm "$RES")"; [[ "$DEST" =~ ^0x[0-9a-fA-F]{40}$ ]] || { echo "{\"error\":\"'$NAME' has no announced wallet (they must run: hive wallet)\"}" >&2; exit 1; }
|
|
486
449
|
WEI="$("$CAST" to-wei "$AMT" ether)"
|
|
487
|
-
read -r -p "Send $AMT $(echo "$TOK"|tr a-z A-Z) to $NAME ($DEST) on Sepolia? [y/N] " ok
|
|
450
|
+
read -r -p "Send $AMT $(echo "$TOK"|tr a-z A-Z) to $NAME ($DEST) on Sepolia? [y/N] " ok || ok=n
|
|
488
451
|
[[ "$ok" == "y" ]] || { echo '{"pay":"cancelled"}'; exit 0; }
|
|
489
452
|
KEY="$(signer_key)"; TX="$("$CAST" send "$TOKADDR" "transfer(address,uint256)" "$DEST" "$WEI" --private-key "$KEY" --rpc-url "$SEPOLIA_RPC" --json 2>/dev/null | node -e "let d='';process.stdin.on('data',c=>d+=c).on('end',()=>{try{console.log(JSON.parse(d).transactionHash)}catch{console.log('')}})")"; unset KEY
|
|
490
453
|
[[ -n "$TX" ]] && echo "{\"paid\":\"$AMT $(echo "$TOK"|tr a-z A-Z)\",\"to\":\"$NAME\",\"address\":\"$DEST\",\"tx\":\"$TX\",\"explorer\":\"https://sepolia.etherscan.io/tx/$TX\"}" || { echo '{"error":"send failed — gas? token balance?"}' >&2; exit 1; }
|
|
@@ -507,7 +470,7 @@ case "$cmd" in
|
|
|
507
470
|
mint) shift; TO="${1:-}"; AMT="${2:-}"; [[ -n "$TO" && "$AMT" =~ ^[0-9]+(\.[0-9]+)?$ ]] || { echo "{\"error\":\"usage: hive $TOK mint <to-addr|pubkey> <amount>\"}" >&2; exit 1; }
|
|
508
471
|
DEST="$(resolve_evm "$TO")"; [[ "$DEST" =~ ^0x[0-9a-fA-F]{40}$ ]] || { echo '{"error":"could not resolve destination EVM address"}' >&2; exit 1; }
|
|
509
472
|
WEI="$("$CAST" to-wei "$AMT" ether)"
|
|
510
|
-
read -r -p "Mint $AMT $TU to $DEST? (owner-only) [y/N] " ok; [[ "$ok" == "y" ]] || { echo '{"mint":"cancelled"}'; exit 0; }
|
|
473
|
+
read -r -p "Mint $AMT $TU to $DEST? (owner-only) [y/N] " ok || ok=n; [[ "$ok" == "y" ]] || { echo '{"mint":"cancelled"}'; exit 0; }
|
|
511
474
|
KEY="$(signer_key)"; TX="$("$CAST" send "$ADDR_TOKEN" "mint(address,uint256)" "$DEST" "$WEI" --private-key "$KEY" --rpc-url "$SEPOLIA_RPC" --json 2>/dev/null | node -e "let d='';process.stdin.on('data',c=>d+=c).on('end',()=>{try{console.log(JSON.parse(d).transactionHash)}catch{console.log('')}})")"; unset KEY
|
|
512
475
|
[[ -n "$TX" ]] && echo "{\"minted\":\"$AMT $TU\",\"to\":\"$DEST\",\"tx\":\"$TX\"}" || { echo '{"error":"mint failed — are you the owner and funded for gas?"}' >&2; exit 1; } ;;
|
|
513
476
|
*) echo "{\"error\":\"usage: hive $TOK balance|mint\"}" >&2; exit 1 ;;
|
|
@@ -549,7 +512,9 @@ case "$cmd" in
|
|
|
549
512
|
[[ "$RPK" != "$(json_field "$IDENTITY" pubkey)" ]] || { echo '{"error":"the resolver cannot be the opener — pick a neutral member"}' >&2; exit 1; }
|
|
550
513
|
POOLARGS=()
|
|
551
514
|
[[ -n "$STAKE" ]] && POOLARGS=(--pool "$STAKE" --payout winner)
|
|
552
|
-
|
|
515
|
+
# ${arr[@]+…}: macOS bash 3.2 treats expanding an EMPTY array under
|
|
516
|
+
# `set -u` as a fatal unbound-variable error (same fix as install-remote.sh).
|
|
517
|
+
exec "$0" session open --kind predict --resolver "$RPK" --deadline "$DL" --quorum 2 ${POOLARGS[@]+"${POOLARGS[@]}"} "$Qs"
|
|
553
518
|
;;
|
|
554
519
|
dnd) # dnd on|off [--price N] — pay-to-interrupt (the fee is YOUR price, 100% to you)
|
|
555
520
|
shift; exec node "$PACK_DIR/bin/hive-net.mjs" dnd "$@"
|
|
@@ -666,7 +631,7 @@ case "$cmd" in
|
|
|
666
631
|
})' "$SID" "$ME")"
|
|
667
632
|
[[ "$PAYJSON" == ERR* ]] && { echo "{\"error\":\"${PAYJSON#ERR }\"}" >&2; exit 1; }
|
|
668
633
|
echo "Proposed payout:"; echo "$PAYJSON" | node -e "let d='';process.stdin.on('data',c=>d+=c).on('end',()=>{for(const p of JSON.parse(d))console.log(' '+p.jelly+' JELLY -> '+String(p.to).slice(0,12))})"
|
|
669
|
-
read -r -p "Execute these $JELLY transfers on Sepolia? [y/N] " ok
|
|
634
|
+
read -r -p "Execute these $JELLY transfers on Sepolia? [y/N] " ok || ok=n
|
|
670
635
|
[[ "$ok" == "y" ]] || { echo '{"payout":"cancelled"}'; exit 0; }
|
|
671
636
|
KEY="$(signer_key)"; [[ -n "$KEY" ]] || { echo '{"error":"no signing key"}' >&2; exit 1; }
|
|
672
637
|
echo "$PAYJSON" | node -e "let d='';process.stdin.on('data',c=>d+=c).on('end',()=>process.stdout.write(JSON.parse(d).map(p=>p.to+' '+p.jelly).join('\n')+'\n'))" | while read -r TO AMT; do
|
|
@@ -773,7 +738,10 @@ case "$cmd" in
|
|
|
773
738
|
docs: https://docs.joinhive.fun
|
|
774
739
|
|
|
775
740
|
MEMBERSHIP
|
|
776
|
-
join --invite <code>
|
|
741
|
+
join --invite <code> full onboarding: identity, wallet, profile, your bee
|
|
742
|
+
key set|show give your bee its brain (LLM key) — joins work without one
|
|
743
|
+
core show|set <file> view or replace your bee's constitution (persona + red lines)
|
|
744
|
+
buzz open the Buzz desktop app with your identity + community
|
|
777
745
|
connect <url> [--invite <code>] point this endpoint at a community relay
|
|
778
746
|
start [--down] run a LOCAL community relay in Docker (:3000)
|
|
779
747
|
whoami | doctor identity / full health check
|
|
@@ -781,7 +749,7 @@ MEMBERSHIP
|
|
|
781
749
|
DAILY
|
|
782
750
|
ask "<text>" post an intent — bees answer in seconds
|
|
783
751
|
feed results, tips, gifts addressed to you
|
|
784
|
-
react <result-id>
|
|
752
|
+
react <result-id> <emoji|word> reward an answer — mints HONEY in real time (👍1 🔥3 ⭐5 🏆8; words: fire/star/trophy/thanks)
|
|
785
753
|
pay "tip <who> <amt> \$JELLY" on-chain payment, plain english
|
|
786
754
|
list users|agents [--online] the roster
|
|
787
755
|
leaderboard [--epoch <date>] HONEY ranks / epoch receipts
|
|
@@ -812,9 +780,21 @@ EOF
|
|
|
812
780
|
version|--version|-v)
|
|
813
781
|
node -e "try{console.log(require('$PACK_DIR/package.json').version)}catch{console.log('dev')}"
|
|
814
782
|
;;
|
|
815
|
-
buzz
|
|
783
|
+
buzz)
|
|
784
|
+
# Bare `hive buzz` (or with --flags) = the desktop-app bridge: sign in
|
|
785
|
+
# with your hive identity + join link. `hive buzz <cmd …>` keeps the old
|
|
786
|
+
# behavior: pass through to buzz-cli under the Hive identity.
|
|
787
|
+
shift
|
|
788
|
+
if [[ $# -eq 0 || "${1:0:2}" == "--" ]]; then
|
|
789
|
+
exec node "$PACK_DIR/bin/hive-buzz.mjs" "$@"
|
|
790
|
+
fi
|
|
791
|
+
BUZZ="$(find_buzz)"; [[ -n "$BUZZ" ]] || { echo '{"error":"buzz binary not found; build buzz-cli or set BUZZ_BIN"}' >&2; exit 1; }
|
|
792
|
+
[[ -f "$IDENTITY" ]] || { echo '{"error":"no identity; run: hive keygen"}' >&2; exit 1; }
|
|
793
|
+
export BUZZ_PRIVATE_KEY="$(json_field "$IDENTITY" privkey)"
|
|
794
|
+
exec "$BUZZ" "$@"
|
|
795
|
+
;;
|
|
796
|
+
*)
|
|
816
797
|
# Pass everything else straight to buzz-cli under the Hive identity.
|
|
817
|
-
[[ "$cmd" == "buzz" ]] && shift
|
|
818
798
|
BUZZ="$(find_buzz)"; [[ -n "$BUZZ" ]] || { echo '{"error":"buzz binary not found; build buzz-cli or set BUZZ_BIN"}' >&2; exit 1; }
|
|
819
799
|
[[ -f "$IDENTITY" ]] || { echo '{"error":"no identity; run: hive keygen"}' >&2; exit 1; }
|
|
820
800
|
export BUZZ_PRIVATE_KEY="$(json_field "$IDENTITY" privkey)"
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// hive-buzz — the bridge from the CLI into the Buzz desktop app: sign in with
|
|
3
|
+
// the SAME identity your hive membership uses, land in the community, and see
|
|
4
|
+
// your bee under Agents.
|
|
5
|
+
//
|
|
6
|
+
// hive buzz [--yes]
|
|
7
|
+
//
|
|
8
|
+
// Buzz signs in by pasting an existing nsec ("Use an existing key") — there
|
|
9
|
+
// is no supported way to push a key into it programmatically — so this prints
|
|
10
|
+
// your nsec after an explicit confirmation, plus a buzz://join deep link that
|
|
11
|
+
// pulls you into the community once you're signed in.
|
|
12
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
13
|
+
import { execFileSync } from 'node:child_process';
|
|
14
|
+
import { homedir, platform } from 'node:os';
|
|
15
|
+
import { join } from 'node:path';
|
|
16
|
+
import { nip19 } from 'nostr-tools';
|
|
17
|
+
import { confirm } from '../shared/prompt.mjs';
|
|
18
|
+
|
|
19
|
+
const HIVE_HOME = process.env.HIVE_HOME || join(homedir(), '.hive');
|
|
20
|
+
const args = process.argv.slice(2);
|
|
21
|
+
const say = (...a) => console.log('🐝', ...a);
|
|
22
|
+
const die = (msg) => { console.error('❌', msg); process.exit(1); };
|
|
23
|
+
const loadJson = (p, fb) => { try { return JSON.parse(readFileSync(p, 'utf8')); } catch { return fb; } };
|
|
24
|
+
|
|
25
|
+
const cfg = loadJson(join(HIVE_HOME, 'config.json'), {});
|
|
26
|
+
const identity = loadJson(join(HIVE_HOME, 'identity.json'), null);
|
|
27
|
+
if (!identity?.privkey) die('no identity — run: hive join --invite <code>');
|
|
28
|
+
if (!cfg.relay) die('no community relay in ~/.hive/config.json — run: hive join --invite <code>');
|
|
29
|
+
|
|
30
|
+
// v1 identities predate the stored bech32 form — derive it.
|
|
31
|
+
const nsec = identity.nsec || nip19.nsecEncode(Uint8Array.from(Buffer.from(identity.privkey, 'hex')));
|
|
32
|
+
const wsRelay = String(cfg.relay).replace(/^https:\/\//, 'wss://').replace(/^http:\/\//, 'ws://').replace(/\/+$/, '');
|
|
33
|
+
const joinLink = cfg.invite ? `buzz://join?relay=${encodeURIComponent(wsRelay)}&code=${encodeURIComponent(cfg.invite)}` : null;
|
|
34
|
+
|
|
35
|
+
const appPath = platform() === 'darwin'
|
|
36
|
+
? [join('/Applications', 'Buzz.app'), join(homedir(), 'Applications', 'Buzz.app')].find(existsSync) || null
|
|
37
|
+
: null;
|
|
38
|
+
|
|
39
|
+
const main = async () => {
|
|
40
|
+
say('Buzz is the community app — channels, feed, and the Agents tab where your bee lives.');
|
|
41
|
+
if (!appPath) {
|
|
42
|
+
say(platform() === 'darwin'
|
|
43
|
+
? 'Buzz.app is not installed — get it from https://github.com/block/buzz/releases, then re-run: hive buzz'
|
|
44
|
+
: 'install the Buzz desktop app for your platform (https://github.com/block/buzz), then re-run: hive buzz');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const ok = args.includes('--yes') || await confirm({
|
|
48
|
+
prompt: 'print your PRIVATE key (nsec) to paste into Buzz? Anyone who sees it can act as you — clear your terminal after',
|
|
49
|
+
def: true,
|
|
50
|
+
});
|
|
51
|
+
if (!ok) die('cancelled — nothing was printed');
|
|
52
|
+
|
|
53
|
+
console.log('');
|
|
54
|
+
say('sign in to Buzz with the SAME identity as your hive membership:');
|
|
55
|
+
say(' 1. open Buzz → "Use an existing key" → paste this:');
|
|
56
|
+
console.log(`\n ${nsec}\n`);
|
|
57
|
+
if (joinLink) {
|
|
58
|
+
say(' 2. once signed in, open this link to join the community (or paste it in a browser):');
|
|
59
|
+
console.log(`\n ${joinLink}\n`);
|
|
60
|
+
} else {
|
|
61
|
+
say(' 2. join the community: ask a member for an invite link and open it while signed in');
|
|
62
|
+
say(' (re-running `hive join` with your invite persists it for this step)');
|
|
63
|
+
}
|
|
64
|
+
say(` 3. your bee ${cfg.bee_name ? `(${cfg.bee_name}) ` : ''}is in the Agents tab — owner-verified as yours`);
|
|
65
|
+
|
|
66
|
+
if (appPath) {
|
|
67
|
+
const open = args.includes('--yes') || await confirm({ prompt: 'open Buzz now?', def: true });
|
|
68
|
+
if (open) { try { execFileSync('open', [appPath], { stdio: 'ignore' }); } catch {} }
|
|
69
|
+
}
|
|
70
|
+
say('tip: clear this terminal when you are done — your nsec is on screen. (cmd+K)');
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
main().catch((e) => die(String(e.message || e)));
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// hive-core — view or replace your bee's core.md constitution (persona +
|
|
3
|
+
// trust/econ policy).
|
|
4
|
+
// hive core show print the running constitution
|
|
5
|
+
// hive core set <file.md> replace it
|
|
6
|
+
// hive core path print the local core.md path
|
|
7
|
+
//
|
|
8
|
+
// A CLOUD bee (provisioned via `hive join`, has server_url) is edited through the
|
|
9
|
+
// owner-signed API — exactly like `hive key set` — so the edit reaches the bee on
|
|
10
|
+
// the bee-host. A LOCAL endpoint edits its own ~/.hive/core.md directly.
|
|
11
|
+
import { readFileSync, copyFileSync } from 'node:fs';
|
|
12
|
+
import { homedir } from 'node:os';
|
|
13
|
+
import { join } from 'node:path';
|
|
14
|
+
import { signedFetch } from '../shared/nip98.mjs';
|
|
15
|
+
|
|
16
|
+
const HIVE_HOME = process.env.HIVE_HOME || join(homedir(), '.hive');
|
|
17
|
+
const args = process.argv.slice(2);
|
|
18
|
+
const sub = args[0] && !args[0].startsWith('--') ? args[0] : 'show';
|
|
19
|
+
const loadJson = (p, fb) => { try { return JSON.parse(readFileSync(p, 'utf8')); } catch { return fb; } };
|
|
20
|
+
const die = (m) => { console.error(JSON.stringify({ error: m })); process.exit(1); };
|
|
21
|
+
const cfg = loadJson(join(HIVE_HOME, 'config.json'), {});
|
|
22
|
+
const identity = loadJson(join(HIVE_HOME, 'identity.json'), null);
|
|
23
|
+
if (!identity) die('no identity — run: hive join --invite <code>');
|
|
24
|
+
const SERVER = (cfg.server_url || '').replace(/\/+$/, '');
|
|
25
|
+
const name = String(cfg.bee_name || '').replace(/\.bee$/, '');
|
|
26
|
+
const corePath = join(HIVE_HOME, 'core.md');
|
|
27
|
+
const cloud = !!(SERVER && name);
|
|
28
|
+
const staleServer = `this community server predates 'hive core' — ask the operator to update the bee-host`;
|
|
29
|
+
|
|
30
|
+
const main = async () => {
|
|
31
|
+
if (sub === 'path') { console.log(corePath); return; }
|
|
32
|
+
|
|
33
|
+
if (sub === 'show') {
|
|
34
|
+
if (cloud) {
|
|
35
|
+
const r = await signedFetch(identity.privkey, 'GET', `${SERVER}/api/bees/${name}/core`);
|
|
36
|
+
if (r.status === 404) die(staleServer);
|
|
37
|
+
if (r.status !== 200) die(`http ${r.status}: ${JSON.stringify(r.json)}`);
|
|
38
|
+
process.stdout.write((r.json.core_md || '(no core.md set)') + '\n');
|
|
39
|
+
} else {
|
|
40
|
+
try { process.stdout.write(readFileSync(corePath, 'utf8')); } catch { die(`no core.md at ${corePath}`); }
|
|
41
|
+
}
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (sub === 'set') {
|
|
46
|
+
const file = args[1];
|
|
47
|
+
if (!file) die('usage: hive core set <file.md>');
|
|
48
|
+
let core; try { core = readFileSync(file, 'utf8'); } catch { die(`cannot read ${file}`); }
|
|
49
|
+
if (!core.trim()) die('core.md is empty');
|
|
50
|
+
if (core.length > 64 * 1024) die('core.md too large (max 64KB)');
|
|
51
|
+
if (cloud) {
|
|
52
|
+
const r = await signedFetch(identity.privkey, 'POST', `${SERVER}/api/bees/${name}/core`, { core_md: core });
|
|
53
|
+
if (r.status === 404) die(`${staleServer} — your file was NOT applied to the cloud bee`);
|
|
54
|
+
if (r.status !== 200) die(`http ${r.status}: ${JSON.stringify(r.json)}`);
|
|
55
|
+
console.log(JSON.stringify({ ok: true, bee: `${name}.bee`, ...r.json }));
|
|
56
|
+
} else {
|
|
57
|
+
copyFileSync(file, corePath);
|
|
58
|
+
console.log(JSON.stringify({ ok: true, core: corePath, note: 'your local bee re-reads core.md next tick' }));
|
|
59
|
+
}
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
die('usage: hive core show | hive core set <file.md> | hive core path');
|
|
63
|
+
};
|
|
64
|
+
main().catch((e) => die(e.message));
|