OverviewFilesCommitsIssuesDocsReleasesPeers

wirecore commits / 70abd60

Wire up pear-runtime for P2P OTA updates
- Replace pear-appdrive with local filesystem serving in serveStatic() - Replace Pear.config.storage with XDG-compatible getStorageDir() - Add pear-runtime for background OTA updates in bare mode - Add upgrade field to package.json - Fix CI to copy pre-built deps (bare-sidecar prebuild issue) - Clean up bridge CLI Pear references - Bump version to 1.1.0 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
70abd60b931f2438aff602a73492db1aed3bfda9
by plankatron <fleeky@gmail.com> · 2026-02-20T19:42:19.000Z

Changed files (4) +77 -81

.pear-git/ci.yaml +3 -2
……@@ -1,3 +1,4 @@
11steps:
2 - name: install
3 run: npm install --production
2 - name: copy-deps
3 run: cp -r /storage/projects/code/hypercore-kb/hyper-fleeky/keet-cli/node_modules . && cp -r node_modules pear-app/node_modules
4 timeout: 120
pear-app/bridge/cli.js +13 -24
……@@ -1,29 +1,21 @@
11#!/usr/bin/env node
2/** @typedef {import('pear-interface')} */ /* global Pear */
3
4/**
5 * Wirecore Bridge CLI
6 *
7 * Commands:
8 * start - Start bridge daemon (watch + periodic pull)
9 * push - Push all local posts to relay
10 * pull - Pull posts from relay
11 * watch - Watch for new posts and auto-push
12 * sync - One-time sync (push + pull)
13 * status - Show bridge status
14 */
2// Wirecore Bridge CLI
153
164import { WirecoreBridge } from './wirecore-bridge.js'
175import process from 'bare-process'
6import os from 'bare-os'
7import path from 'bare-path'
188import goodbye from 'graceful-goodbye'
199
20// Check if running in Pear
21const isPear = typeof Pear !== 'undefined'
10function getStorageDir () {
11 const p = os.platform()
12 const name = 'wirecore'
13 if (p === 'darwin') return path.join(os.homedir(), 'Library', 'Application Support', name)
14 if (p === 'win32') return path.join(os.homedir(), 'AppData', 'Roaming', name)
15 return path.join(os.homedir(), '.config', name)
16}
2217
23// Get configuration from environment or Pear
24const storagePath = isPear && Pear.config.storage
25 ? Pear.config.storage
26 : process.env.PEAR_STORAGE || './wirecore-storage'
18const storagePath = process.env.PEAR_STORAGE || getStorageDir()
2719
2820const relayUrl = process.env.RELAY_URL || 'https://7rnx.net/relay'
2921
……@@ -65,11 +57,8 @@ async function main() {
6557 process.exit(0)
6658 }
6759
68 // Debug: log configuration values
69 console.log('[DEBUG] isPear:', isPear)
70 console.log('[DEBUG] Pear.config.storage:', isPear ? Pear.config.storage : 'N/A')
71 console.log('[DEBUG] storagePath:', storagePath)
72 console.log('[DEBUG] relayUrl:', relayUrl)
60 console.log('[bridge] Storage:', storagePath)
61 console.log('[bridge] Relay:', relayUrl)
7362
7463 // Initialize bridge (CLI runs standalone, so no existing store)
7564 const bridge = new WirecoreBridge(relayUrl, null, storagePath)
pear-app/index.js +54 -50
……@@ -1,15 +1,16 @@
11#!/usr/bin/env node
2/** @typedef {import('pear-interface')} */ /* global Pear */
3
4/**
5 * Wirecore Pear App - P2P Social Network Terminal Client
6 *
7 * Architecture:
8 * - Each user has their own writable Hypercore feed
9 * - Following = joining their Hyperswarm topic and replicating read-only
10 * - Timeline = merge all followed feeds by timestamp
11 * - Sparse replication = only followed users, not entire network
12 */
2// Wirecore - P2P Social Network Terminal Client
3//
4// Architecture:
5// - Each user has their own writable Hypercore feed
6// - Following = joining their Hyperswarm topic and replicating read-only
7// - Timeline = merge all followed feeds by timestamp
8// - Sparse replication = only followed users, not entire network
9//
10// pear-runtime provides P2P OTA updates when running from a deployed install.
11//
12// Usage: bare index.js <command> [options]
13// pear run pear://KEY <command> [options]
1314
1415import Corestore from 'corestore'
1516import Hyperswarm from 'hyperswarm'
……@@ -25,6 +26,7 @@ import bareWs from 'bare-ws'
2526import fs from 'bare-fs'
2627import path from 'bare-path'
2728import process from 'bare-process'
29import os from 'bare-os'
2830import { ANProto } from './lib/anproto.js'
2931import { YAMLHelper } from './lib/yaml-helper.js'
3032
……@@ -33,21 +35,20 @@ const { Server: WebSocketServer } = bareWs
3335// Check if running in Pear or Node
3436const isPear = typeof Pear !== 'undefined'
3537
36// Access the app bundle using pear-appdrive (read-only)
37let appDrive = null
38if (isPear) {
39 try {
40 const { default: AppDrive } = await import('pear-appdrive')
41 appDrive = new AppDrive()
42 await appDrive.ready()
43 console.log('[DEBUG] pear-appdrive ready - can read app bundle files')
44 } catch (err) {
45 console.log('[DEBUG] pear-appdrive failed:', err.message)
46 }
38function getAppDir () {
39 return path.dirname(new URL(import.meta.url).pathname)
40}
41
42function getStorageDir () {
43 const p = os.platform()
44 const name = 'wirecore'
45 if (p === 'darwin') return path.join(os.homedir(), 'Library', 'Application Support', name)
46 if (p === 'win32') return path.join(os.homedir(), 'AppData', 'Roaming', name)
47 return path.join(os.homedir(), '.config', name)
4748}
4849
4950// Configuration
50const STORAGE_PATH = isPear && Pear.config.storage ? Pear.config.storage : './wirecore-storage'
51const STORAGE_PATH = isPear && Pear.config.storage ? Pear.config.storage : getStorageDir()
5152
5253// Global state
5354let store = null
……@@ -1490,30 +1491,14 @@ function readBody(req) {
14901491 })
14911492}
14921493
1493// Serve static files
1494// Serve static files from local filesystem
14941495async function serveStatic(req, res, url) {
14951496 // Remove leading slash and default to index.html
14961497 let fileName = url.pathname === '/' ? 'index.html' : url.pathname.substring(1)
14971498
1498 console.log(`[STATIC] Request: ${url.pathname} → ${fileName}`)
1499
15001499 try {
1501 let data
1502
1503 if (appDrive) {
1504 // Running from pear:// link - read from app bundle using pear-appdrive
1505 const filePath = '/public/' + fileName
1506 console.log(`[STATIC] Reading from app bundle: ${filePath}`)
1507 data = await appDrive.get(filePath)
1508 if (!data) throw new Error('File not found in app bundle')
1509 } else {
1510 // Local development - read from filesystem
1511 const localPath = path.join('public', fileName)
1512 console.log(`[STATIC] Reading from filesystem: ${localPath}`)
1513 data = fs.readFileSync(localPath)
1514 }
1515
1516 console.log(`[STATIC] ✅ Success: ${fileName} (${data.length} bytes)`)
1500 const localPath = path.join(getAppDir(), 'public', fileName)
1501 const data = fs.readFileSync(localPath)
15171502
15181503 const ext = path.extname(fileName)
15191504 const contentType = {
……@@ -1527,8 +1512,6 @@ async function serveStatic(req, res, url) {
15271512 res.writeHead(200, { 'Content-Type': contentType })
15281513 res.end(data)
15291514 } catch (err) {
1530 console.error(`[STATIC] ❌ Failed: ${fileName}`)
1531 console.error(`[STATIC] Error: ${err.message}`)
15321515 res.writeHead(404, { 'Content-Type': 'text/plain' })
15331516 res.end('Not found')
15341517 }
……@@ -1873,7 +1856,7 @@ function showUsage() {
18731856 * Main CLI handler
18741857 */
18751858async function main() {
1876 const args = isPear ? (Pear.config.args || []) : process.argv.slice(2)
1859 const args = isPear ? (Pear.config.args || []) : (typeof Bare !== 'undefined' ? Bare.argv.slice(2) : process.argv.slice(2))
18771860
18781861 if (args.length === 0) {
18791862 showUsage()
……@@ -2009,10 +1992,6 @@ async function main() {
20091992
20101993 case 'storage-path':
20111994 console.log('Storage path:', STORAGE_PATH)
2012 console.log('Is Pear:', isPear)
2013 if (isPear) {
2014 console.log('Pear.config.storage:', Pear.config.storage)
2015 }
20161995 break
20171996
20181997 case 'migrate-storage':
……@@ -2073,8 +2052,33 @@ async function main() {
20732052 }
20742053}
20752054
2055// Initialize pear-runtime for P2P OTA updates (non-blocking)
2056async function initPearRuntime () {
2057 try {
2058 const pkg = JSON.parse(fs.readFileSync(path.join(getAppDir(), 'package.json'), 'utf-8'))
2059 if (!pkg.upgrade) return null
2060
2061 const { default: PearRuntime } = await import('pear-runtime')
2062 const dir = getStorageDir()
2063
2064 const runtime = new PearRuntime({ dir, app: getAppDir(), version: pkg.version, upgrade: pkg.upgrade })
2065 runtime.on('updated', () => {
2066 runtime.applyUpdate().catch(() => {})
2067 })
2068 return runtime
2069 } catch {
2070 return null
2071 }
2072}
2073
20762074// Run main
2075let runtimeRef = null
2076if (!isPear) {
2077 runtimeRef = initPearRuntime()
2078}
2079
20772080main().catch(err => {
20782081 console.error('Fatal error:', err)
2079 process.exit(1)
2082 if (typeof Bare !== 'undefined') Bare.exit(1)
2083 else process.exit(1)
20802084})
pear-app/package.json +7 -5
……@@ -1,9 +1,11 @@
11{
22 "name": "wirecore-pear",
3 "version": "1.0.0",
4 "description": "Wirecore P2P social network - Pear terminal app",
3 "version": "1.1.0",
4 "description": "Wirecore P2P social network",
55 "type": "module",
66 "main": "index.js",
7 "private": true,
8 "upgrade": "pear://zupx3qzpwj61hdt1bxanabctcyxqqu3arogctwim4qt3wfbzn4bo",
79 "pear": {
810 "name": "wirecore-pear",
911 "type": "terminal",
……@@ -12,15 +14,14 @@
1214 }
1315 },
1416 "scripts": {
15 "dev": "pear run -d .",
17 "dev": "bare index.js",
1618 "prune": "node prune-prebuilds.js",
17 "stage": "pear stage wirecore .",
18 "seed": "pear seed wirecore .",
1919 "test": "brittle test/*.js"
2020 },
2121 "dependencies": {
2222 "b4a": "^1.6.6",
2323 "bare-http1": "^4.0.2",
24 "bare-os": "^3.0.0",
2425 "bare-process": "^2.2.0",
2526 "bare-ws": "^2.0.0",
2627 "compact-encoding": "^2.15.0",
……@@ -33,6 +34,7 @@
3334 "hyperswarm": "^4.8.2",
3435 "keet-identity-key": "^3.1.0",
3536 "pear-appdrive": "^1.1.2",
37 "pear-runtime": "^0.4.2",
3638 "sodium-universal": "^4.0.0"
3739 },
3840 "devDependencies": {