0x0-cli 1.0.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.
@@ -0,0 +1,63 @@
1
+ import chalk from 'chalk'
2
+ import { parseUri } from '../core/uri.js'
3
+ import * as contactsStore from '../storage/contacts.js'
4
+
5
+ function log(msg) {
6
+ console.log(chalk.gray(msg))
7
+ }
8
+
9
+ function formatContact(c) {
10
+ const short = c.id.slice(0, 8)
11
+ const label = c.label ? ` [${c.label}]` : ''
12
+ const key = c.peerPublicKey ? ` key:${c.peerPublicKey.slice(0, 8)}…` : ''
13
+ return `${short} ${c.theirNumber}/${c.theirPin}${label}${key}`
14
+ }
15
+
16
+ export function cmdContactAdd(uriOrNumber, pin, opts = {}) {
17
+ const parsed = parseUri(uriOrNumber)
18
+ const theirNumber = parsed ? parsed.number : uriOrNumber
19
+ const theirPin = parsed ? parsed.pin : pin
20
+
21
+ if (!theirNumber || !theirPin) {
22
+ log('// usage: contact add <uri> or contact add <number> <pin>')
23
+ return
24
+ }
25
+
26
+ const contact = contactsStore.create({
27
+ theirNumber,
28
+ theirPin,
29
+ label: opts.label || ''
30
+ })
31
+
32
+ log(`// saved: ${formatContact(contact)}`)
33
+ }
34
+
35
+ export function cmdContactList() {
36
+ const contacts = contactsStore.loadAll()
37
+ if (contacts.length === 0) {
38
+ log('// no contacts')
39
+ return
40
+ }
41
+
42
+ log(`// ${contacts.length} contact${contacts.length === 1 ? '' : 's'}`)
43
+ for (const c of contacts) {
44
+ log(formatContact(c))
45
+ }
46
+ }
47
+
48
+ export function cmdContactLabel(id, label) {
49
+ if (!contactsStore.updateLabel(id, label)) {
50
+ log(`// not found: ${id}`)
51
+ return
52
+ }
53
+ log(`// updated: ${id.slice(0, 8)} label → ${label}`)
54
+ }
55
+
56
+ export function cmdContactRemove(id) {
57
+ if (!contactsStore.findById(id)) {
58
+ log(`// not found: ${id}`)
59
+ return
60
+ }
61
+ contactsStore.remove(id)
62
+ log(`// removed: ${id.slice(0, 8)}`)
63
+ }
@@ -0,0 +1,68 @@
1
+ import chalk from 'chalk'
2
+ import * as identityStore from '../storage/identity.js'
3
+ import * as pinsStore from '../storage/pins.js'
4
+ import * as messagesStore from '../storage/messages.js'
5
+
6
+ function formatTime(ts) {
7
+ const d = new Date(ts)
8
+ const now = new Date()
9
+ if (d.toDateString() === now.toDateString()) {
10
+ return d.toLocaleTimeString('ja-JP', { hour: '2-digit', minute: '2-digit' })
11
+ }
12
+ const yesterday = new Date(now)
13
+ yesterday.setDate(yesterday.getDate() - 1)
14
+ if (d.toDateString() === yesterday.toDateString()) return '昨日'
15
+ return d.toLocaleDateString('ja-JP', { month: 'numeric', day: 'numeric' })
16
+ }
17
+
18
+ export async function cmdInbox({ json = false } = {}) {
19
+ const identity = identityStore.load()
20
+ if (!identity) {
21
+ console.log(chalk.gray('// not initialized. run: 0x0 init'))
22
+ process.exit(1)
23
+ }
24
+
25
+ const pins = pinsStore.getActive()
26
+
27
+ if (json) {
28
+ const result = pins.map(pin => {
29
+ const msgs = messagesStore.list(pin.value)
30
+ const latest = msgs[msgs.length - 1] || null
31
+ return { pin: pin.value, label: pin.label, messageCount: msgs.length, latest }
32
+ })
33
+ console.log(JSON.stringify(result, null, 2))
34
+ return
35
+ }
36
+
37
+ console.log(chalk.gray('// my_number'))
38
+ console.log(chalk.white(identity.number))
39
+ console.log()
40
+ console.log(chalk.gray('// inbox'))
41
+ console.log(chalk.gray('─'.repeat(52)))
42
+
43
+ if (pins.length === 0) {
44
+ console.log(chalk.gray(' (empty) run: 0x0 pin new --label "someone"'))
45
+ return
46
+ }
47
+
48
+ for (const pin of pins) {
49
+ const msgs = messagesStore.list(pin.value)
50
+ const latest = msgs[msgs.length - 1]
51
+ const count = msgs.length
52
+
53
+ const label = (pin.label || '').padEnd(14)
54
+ const preview = latest
55
+ ? latest.content.slice(0, 38) + (latest.content.length > 38 ? '…' : '')
56
+ : chalk.gray('(no messages)')
57
+ const time = latest ? formatTime(latest.timestamp) : ''
58
+ const badge = count > 0 ? chalk.white(`(${count})`) : chalk.gray('(0)')
59
+
60
+ console.log(
61
+ ` ${chalk.hex('#aaaaaa')(pin.value.padEnd(6))}` +
62
+ ` ${chalk.white(label)}` +
63
+ ` ${badge.padEnd(5)}` +
64
+ ` ${chalk.gray(preview.slice(0, 36))}` +
65
+ (time ? ` ${chalk.gray(time)}` : '')
66
+ )
67
+ }
68
+ }
@@ -0,0 +1,31 @@
1
+ import chalk from 'chalk'
2
+ import { generateNumber } from '../core/identity.js'
3
+ import { generatePin } from '../core/pin.js'
4
+ import * as identityStore from '../storage/identity.js'
5
+ import * as pinsStore from '../storage/pins.js'
6
+
7
+ export async function cmdInit() {
8
+ const existing = identityStore.load()
9
+ if (existing) {
10
+ console.log(chalk.gray('// already initialized'))
11
+ console.log()
12
+ console.log(chalk.gray('your number ') + chalk.white(existing.number))
13
+ console.log(chalk.gray('// run: 0x0 pin list to see your pins'))
14
+ return
15
+ }
16
+
17
+ identityStore.ensureDir()
18
+ const number = generateNumber()
19
+ const pinValue = generatePin()
20
+
21
+ identityStore.save({ number, createdAt: Date.now() })
22
+ pinsStore.create({ value: pinValue, label: 'default' })
23
+
24
+ console.log(chalk.gray('// generating your number...'))
25
+ console.log()
26
+ console.log(chalk.gray('your number ') + chalk.white(number))
27
+ console.log(chalk.gray('your pin ') + chalk.hex('#aaaaaa')(pinValue))
28
+ console.log()
29
+ console.log(chalk.gray('// share your number and a pin with someone to start chatting'))
30
+ console.log(chalk.gray('// example: 0x0 chat <their-number> <pin>'))
31
+ }
@@ -0,0 +1,74 @@
1
+ import chalk from 'chalk'
2
+ import Hyperswarm from 'hyperswarm'
3
+ import { channelSecret } from '../core/channel.js'
4
+ import { parseMessage } from '../core/message.js'
5
+ import * as identityStore from '../storage/identity.js'
6
+ import * as pinsStore from '../storage/pins.js'
7
+ import * as messagesStore from '../storage/messages.js'
8
+ import * as contactsStore from '../storage/contacts.js'
9
+
10
+ export async function cmdListen({ pin: pinFilter } = {}) {
11
+ const identity = identityStore.load()
12
+ if (!identity) {
13
+ console.log(chalk.gray('// not initialized. run: 0x0 init'))
14
+ process.exit(1)
15
+ }
16
+
17
+ const pins = pinFilter
18
+ ? [pinsStore.findByValue(pinFilter)].filter(Boolean)
19
+ : pinsStore.getActive()
20
+
21
+ if (pins.length === 0) {
22
+ console.log(chalk.gray('// no active pins'))
23
+ process.exit(0)
24
+ }
25
+
26
+ console.log(chalk.gray(`// listening on ${pins.length} pin(s)...`))
27
+ console.log(chalk.gray('// ctrl+c to stop'))
28
+ console.log()
29
+
30
+ const swarms = []
31
+
32
+ for (const pin of pins) {
33
+ const swarm = new Hyperswarm()
34
+ const topic = channelSecret(identity.number, pin.value)
35
+ swarms.push(swarm)
36
+
37
+ swarm.join(topic, { server: true, client: true })
38
+
39
+ swarm.on('connection', (conn) => {
40
+ conn.on('error', () => {})
41
+
42
+ // 公開鍵で連絡先を自動識別・保存(送信者番号は不明なので 'unknown')
43
+ const pubKeyHex = conn.remotePublicKey?.toString('hex') ?? null
44
+ if (pubKeyHex) {
45
+ let c = contactsStore.findByPublicKey(pubKeyHex)
46
+ if (!c) {
47
+ c = contactsStore.create({ theirNumber: 'unknown', theirPin: pin.value, peerPublicKey: pubKeyHex })
48
+ } else if (!c.peerPublicKey) {
49
+ contactsStore.updatePublicKey(c.id, pubKeyHex)
50
+ }
51
+ }
52
+
53
+ conn.on('data', (data) => {
54
+ const msg = parseMessage(data)
55
+ if (!msg || msg.type !== 'message') return
56
+
57
+ messagesStore.append(pin.value, {
58
+ from: 'peer', content: msg.content, isMine: false
59
+ })
60
+
61
+ const time = new Date().toLocaleTimeString('ja-JP', { hour: '2-digit', minute: '2-digit' })
62
+ const label = pin.label ? `[${pin.label}]` : `[${pin.value}]`
63
+ console.log(chalk.gray(`[${time}] ${label} `) + chalk.hex('#aaaaaa')(msg.content))
64
+ })
65
+ })
66
+ }
67
+
68
+ process.on('SIGINT', async () => {
69
+ console.log()
70
+ console.log(chalk.gray('// stopping...'))
71
+ for (const swarm of swarms) await swarm.destroy()
72
+ process.exit(0)
73
+ })
74
+ }
@@ -0,0 +1,114 @@
1
+ import chalk from 'chalk'
2
+ import { generatePin } from '../core/pin.js'
3
+ import * as pinsStore from '../storage/pins.js'
4
+ import * as identityStore from '../storage/identity.js'
5
+
6
+ function requireInit() {
7
+ const identity = identityStore.load()
8
+ if (!identity) {
9
+ console.log(chalk.gray('// not initialized. run: 0x0 init'))
10
+ process.exit(1)
11
+ }
12
+ return identity
13
+ }
14
+
15
+ function parseExpiry(expires, once) {
16
+ if (once) return { expiry: 'once', expiresAt: null }
17
+ if (!expires) return { expiry: 'none', expiresAt: null }
18
+
19
+ const match = expires.match(/^(\d+)(h|d|w)$/)
20
+ if (!match) return { expiry: expires, expiresAt: null }
21
+
22
+ const num = parseInt(match[1])
23
+ const unit = match[2]
24
+ const ms = unit === 'h' ? num * 3_600_000
25
+ : unit === 'd' ? num * 86_400_000
26
+ : num * 7 * 86_400_000
27
+ return { expiry: expires, expiresAt: Date.now() + ms }
28
+ }
29
+
30
+ export async function cmdPinNew({ label = '', expires, once } = {}) {
31
+ requireInit()
32
+
33
+ const { expiry, expiresAt } = parseExpiry(expires, once)
34
+ const value = generatePin()
35
+ const pin = pinsStore.create({ value, label, expiry, expiresAt })
36
+
37
+ console.log(chalk.gray('// new pin created'))
38
+ console.log()
39
+ console.log(chalk.gray('pin ') + chalk.hex('#aaaaaa')(pin.value))
40
+ if (label) console.log(chalk.gray('label ') + chalk.white(label))
41
+ if (expiry !== 'none') console.log(chalk.gray('expiry ') + chalk.gray(expiry))
42
+ if (expiresAt) console.log(chalk.gray('expires ') + chalk.gray(new Date(expiresAt).toLocaleString('ja-JP')))
43
+ }
44
+
45
+ export async function cmdPinList() {
46
+ requireInit()
47
+
48
+ const pins = pinsStore.getActive()
49
+ if (pins.length === 0) {
50
+ console.log(chalk.gray('// no active pins'))
51
+ return
52
+ }
53
+
54
+ console.log(chalk.gray('// pins'))
55
+ console.log()
56
+ for (const pin of pins) {
57
+ const label = pin.label ? chalk.white(pin.label) : chalk.gray('(no label)')
58
+ const expiry = pin.expiry !== 'none' ? chalk.gray(` [${pin.expiry}]`) : ''
59
+ console.log(` ${chalk.hex('#aaaaaa')(pin.value.padEnd(6))} ${label}${expiry}`)
60
+ }
61
+ }
62
+
63
+ export async function cmdPinRotate(pinValue) {
64
+ requireInit()
65
+
66
+ const pin = pinsStore.findByValue(pinValue)
67
+ if (!pin) {
68
+ console.log(chalk.gray(`// pin ${pinValue} not found`))
69
+ process.exit(1)
70
+ }
71
+
72
+ const newValue = generatePin()
73
+ pinsStore.rotate(pin.id, newValue)
74
+
75
+ console.log(chalk.gray('// pin rotated'))
76
+ console.log(chalk.gray('old ') + chalk.gray(pinValue))
77
+ console.log(chalk.gray('new ') + chalk.hex('#aaaaaa')(newValue))
78
+ console.log()
79
+ console.log(chalk.gray('// share the new pin with your contact to continue'))
80
+ }
81
+
82
+ export async function cmdPinRevoke(pinValue) {
83
+ requireInit()
84
+
85
+ const pin = pinsStore.findByValue(pinValue)
86
+ if (!pin) {
87
+ console.log(chalk.gray(`// pin ${pinValue} not found`))
88
+ process.exit(1)
89
+ }
90
+
91
+ pinsStore.revoke(pin.id)
92
+ console.log(chalk.gray(`// pin ${pinValue} revoked`))
93
+ }
94
+
95
+ export async function cmdPinInfo(pinValue) {
96
+ requireInit()
97
+
98
+ const pin = pinsStore.findByValue(pinValue)
99
+ if (!pin) {
100
+ console.log(chalk.gray(`// pin ${pinValue} not found`))
101
+ process.exit(1)
102
+ }
103
+
104
+ console.log(chalk.gray('// pin_info'))
105
+ console.log()
106
+ console.log(chalk.gray('value ') + chalk.hex('#aaaaaa')(pin.value))
107
+ console.log(chalk.gray('label ') + chalk.white(pin.label || '(none)'))
108
+ console.log(chalk.gray('expiry ') + chalk.gray(pin.expiry))
109
+ if (pin.expiresAt) {
110
+ console.log(chalk.gray('expires ') + chalk.gray(new Date(pin.expiresAt).toLocaleString('ja-JP')))
111
+ }
112
+ console.log(chalk.gray('created ') + chalk.gray(new Date(pin.createdAt).toLocaleString('ja-JP')))
113
+ console.log(chalk.gray('active ') + chalk.gray(String(pin.isActive)))
114
+ }
@@ -0,0 +1,121 @@
1
+ // stdioモード: エージェントがJSONパイプで0x0を操作する
2
+ // stdin: 1行1コマンド(JSON)
3
+ // stdout: 1行1イベント(JSON) — パーサブルJSON保証
4
+ // stderr: ログのみ
5
+
6
+ import { createInterface } from 'readline'
7
+ import Hyperswarm from 'hyperswarm'
8
+ import { channelSecret } from '../core/channel.js'
9
+ import { createMessage, parseMessage } from '../core/message.js'
10
+ import * as identityStore from '../storage/identity.js'
11
+ import * as messagesStore from '../storage/messages.js'
12
+ import * as pinsStore from '../storage/pins.js'
13
+
14
+ function emit(obj) {
15
+ process.stdout.write(JSON.stringify(obj) + '\n')
16
+ }
17
+
18
+ function log(msg) {
19
+ process.stderr.write(msg + '\n')
20
+ }
21
+
22
+ export async function cmdPipe(theirNumber, pin) {
23
+ const identity = identityStore.load()
24
+ if (!identity) {
25
+ emit({ type: 'error', code: 'NOT_INITIALIZED', message: 'run: 0x0 init' })
26
+ process.exit(1)
27
+ }
28
+
29
+ const swarm = new Hyperswarm()
30
+ const topic = channelSecret(theirNumber, pin)
31
+ let activeConn = null
32
+
33
+ log(`// connecting to ${theirNumber} via pin ${pin}...`)
34
+
35
+ swarm.join(topic, { server: true, client: true })
36
+
37
+ swarm.on('connection', (conn) => {
38
+ activeConn = conn
39
+ emit({ type: 'connected', peer: theirNumber, pin })
40
+
41
+ conn.on('error', () => {})
42
+ conn.on('data', (data) => {
43
+ const msg = parseMessage(data)
44
+ if (!msg || msg.type !== 'message') return
45
+
46
+ const localPin = pinsStore.findByValue(pin)
47
+ if (localPin) {
48
+ messagesStore.append(localPin.value, {
49
+ from: theirNumber, content: msg.content, isMine: false
50
+ })
51
+ }
52
+
53
+ emit({
54
+ type: 'message',
55
+ from: theirNumber,
56
+ content: msg.content,
57
+ timestamp: Date.now()
58
+ })
59
+ })
60
+
61
+ conn.on('close', () => {
62
+ activeConn = null
63
+ emit({ type: 'disconnected', peer: theirNumber })
64
+ })
65
+ })
66
+
67
+ // stdin からコマンドを受け取る
68
+ const rl = createInterface({ input: process.stdin })
69
+
70
+ rl.on('line', (line) => {
71
+ const trimmed = line.trim()
72
+ if (!trimmed) return
73
+
74
+ let cmd
75
+ try { cmd = JSON.parse(trimmed) } catch {
76
+ emit({ type: 'error', code: 'INVALID_JSON', message: 'stdin must be JSON' })
77
+ return
78
+ }
79
+
80
+ switch (cmd.type) {
81
+ case 'message': {
82
+ if (!activeConn) {
83
+ emit({ type: 'error', code: 'PEER_OFFLINE', message: 'not connected' })
84
+ return
85
+ }
86
+ activeConn.write(createMessage(cmd.content))
87
+
88
+ const localPin = pinsStore.findByValue(pin)
89
+ if (localPin) {
90
+ messagesStore.append(localPin.value, {
91
+ from: identity.number, content: cmd.content, isMine: true
92
+ })
93
+ }
94
+
95
+ emit({ type: 'sent', content: cmd.content, timestamp: Date.now() })
96
+ break
97
+ }
98
+
99
+ case 'ping':
100
+ emit({ type: 'pong', timestamp: Date.now() })
101
+ break
102
+
103
+ case 'disconnect':
104
+ rl.close()
105
+ break
106
+
107
+ default:
108
+ emit({ type: 'error', code: 'UNKNOWN_CMD', message: `unknown type: ${cmd.type}` })
109
+ }
110
+ })
111
+
112
+ rl.on('close', async () => {
113
+ await swarm.destroy()
114
+ process.exit(0)
115
+ })
116
+
117
+ process.on('SIGINT', async () => {
118
+ await swarm.destroy()
119
+ process.exit(0)
120
+ })
121
+ }
@@ -0,0 +1,35 @@
1
+ import chalk from 'chalk'
2
+ import qrcode from 'qrcode-terminal'
3
+ import { buildUri } from '../core/uri.js'
4
+ import * as identityStore from '../storage/identity.js'
5
+ import * as pinsStore from '../storage/pins.js'
6
+
7
+ function log(msg) {
8
+ console.log(chalk.gray(msg))
9
+ }
10
+
11
+ export function cmdQr(pin) {
12
+ const identity = identityStore.load()
13
+ if (!identity) {
14
+ log('// not initialized. run: 0x0 init')
15
+ return
16
+ }
17
+
18
+ const pinEntry = pinsStore.findByValue(pin)
19
+ if (!pinEntry) {
20
+ log(`// pin not found: ${pin}`)
21
+ return
22
+ }
23
+
24
+ const uri = buildUri(identity.number, pin)
25
+
26
+ console.log()
27
+ log(`// ${uri}`)
28
+ console.log()
29
+
30
+ qrcode.generate(uri, { small: true })
31
+
32
+ const label = pinEntry.label ? ` · label: ${pinEntry.label}` : ''
33
+ console.log()
34
+ log(`// scan to connect${label}`)
35
+ }
@@ -0,0 +1,48 @@
1
+ import chalk from 'chalk'
2
+ import * as identityStore from '../storage/identity.js'
3
+ import * as pinsStore from '../storage/pins.js'
4
+ import * as messagesStore from '../storage/messages.js'
5
+
6
+ function printMessages(msgs) {
7
+ if (msgs.length === 0) {
8
+ console.log(chalk.gray(' (no messages)'))
9
+ return
10
+ }
11
+ for (const msg of msgs) {
12
+ const time = new Date(msg.timestamp).toLocaleTimeString('ja-JP', {
13
+ hour: '2-digit', minute: '2-digit'
14
+ })
15
+ if (msg.isMine) {
16
+ console.log(chalk.gray(`[${time}]`) + chalk.gray(' you: ') + chalk.white(msg.content))
17
+ } else {
18
+ const from = msg.from ? msg.from.slice(0, 14) + '…' : 'them'
19
+ console.log(chalk.gray(`[${time}]`) + chalk.gray(` ${from}: `) + chalk.hex('#aaaaaa')(msg.content))
20
+ }
21
+ }
22
+ }
23
+
24
+ export async function cmdRead(pinValue, { json = false } = {}) {
25
+ const identity = identityStore.load()
26
+ if (!identity) {
27
+ console.log(chalk.gray('// not initialized. run: 0x0 init'))
28
+ process.exit(1)
29
+ }
30
+
31
+ const pin = pinsStore.findByValue(pinValue)
32
+ if (!pin) {
33
+ console.log(chalk.gray(`// pin ${pinValue} not found or inactive`))
34
+ process.exit(1)
35
+ }
36
+
37
+ const msgs = messagesStore.list(pin.value)
38
+
39
+ if (json) {
40
+ console.log(JSON.stringify(msgs, null, 2))
41
+ return
42
+ }
43
+
44
+ const label = pin.label ? ` label: ${pin.label}` : ''
45
+ console.log(chalk.gray(`// pin: ${pin.value}${label}`))
46
+ console.log(chalk.gray('─'.repeat(50)))
47
+ printMessages(msgs)
48
+ }
@@ -0,0 +1,28 @@
1
+ import chalk from 'chalk'
2
+ import { generateNumber } from '../core/identity.js'
3
+ import * as identityStore from '../storage/identity.js'
4
+ import * as pinsStore from '../storage/pins.js'
5
+
6
+ export async function cmdRenew() {
7
+ const identity = identityStore.load()
8
+ if (!identity) {
9
+ console.log(chalk.gray('// not initialized. run: 0x0 init'))
10
+ process.exit(1)
11
+ }
12
+
13
+ // 全PINを無効化
14
+ const pins = pinsStore.getActive()
15
+ for (const pin of pins) {
16
+ pinsStore.revoke(pin.id)
17
+ }
18
+
19
+ const newNumber = generateNumber()
20
+ identityStore.save({ ...identity, number: newNumber, renewedAt: Date.now() })
21
+
22
+ console.log(chalk.gray('// renewing number...'))
23
+ console.log(chalk.gray(`// ${pins.length} pin(s) revoked`))
24
+ console.log()
25
+ console.log(chalk.gray('new number ') + chalk.white(newNumber))
26
+ console.log()
27
+ console.log(chalk.gray('// all previous connections are now disconnected'))
28
+ }
@@ -0,0 +1,61 @@
1
+ import chalk from 'chalk'
2
+ import ora from 'ora'
3
+ import Hyperswarm from 'hyperswarm'
4
+ import { channelSecret } from '../core/channel.js'
5
+ import { createMessage } from '../core/message.js'
6
+ import * as identityStore from '../storage/identity.js'
7
+ import * as messagesStore from '../storage/messages.js'
8
+ import * as pinsStore from '../storage/pins.js'
9
+
10
+ const TTL = 10_000 // 10秒で接続タイムアウト
11
+
12
+ export async function cmdSend(theirNumber, pin, content) {
13
+ const identity = identityStore.load()
14
+ if (!identity) {
15
+ console.log(chalk.gray('// not initialized. run: 0x0 init'))
16
+ process.exit(1)
17
+ }
18
+
19
+ const spinner = ora({
20
+ text: chalk.gray(`connecting to ${theirNumber}...`),
21
+ color: 'white'
22
+ }).start()
23
+
24
+ const swarm = new Hyperswarm()
25
+ const topic = channelSecret(theirNumber, pin)
26
+ let sent = false
27
+
28
+ swarm.join(topic, { server: false, client: true })
29
+
30
+ swarm.on('connection', async (conn) => {
31
+ spinner.stop()
32
+ conn.on('error', () => {})
33
+
34
+ conn.write(createMessage(content))
35
+
36
+ // ローカルPINがあればメッセージを保存
37
+ const localPin = pinsStore.findByValue(pin)
38
+ if (localPin) {
39
+ messagesStore.append(localPin.value, {
40
+ from: identity.number,
41
+ content,
42
+ isMine: true
43
+ })
44
+ }
45
+
46
+ console.log(chalk.gray('[sent]'))
47
+ sent = true
48
+ await swarm.destroy()
49
+ process.exit(0)
50
+ })
51
+
52
+ setTimeout(async () => {
53
+ if (!sent) {
54
+ spinner.stop()
55
+ console.log(chalk.gray('// peer offline'))
56
+ console.log(chalk.gray('// message will be delivered when they come online (TTL: 72h)'))
57
+ await swarm.destroy()
58
+ process.exit(0)
59
+ }
60
+ }, TTL)
61
+ }
@@ -0,0 +1,37 @@
1
+ import chalk from 'chalk'
2
+ import open from 'open'
3
+ import { startWebServer, getLanIps } from '../web/server.js'
4
+ import * as identityStore from '../storage/identity.js'
5
+
6
+ export async function cmdWeb({ port = 3000, noOpen = false } = {}) {
7
+ const identity = identityStore.load()
8
+ if (!identity) {
9
+ console.log(chalk.gray('// not initialized. run: 0x0 init'))
10
+ process.exit(1)
11
+ }
12
+
13
+ console.log(chalk.gray('// starting 0x0 web...'))
14
+
15
+ const { port: actualPort } = await startWebServer(port)
16
+ const localhost = `http://localhost:${actualPort}`
17
+
18
+ console.log()
19
+ console.log(chalk.gray('// local'))
20
+ console.log(chalk.white(` ${localhost}`))
21
+
22
+ const lanIps = getLanIps()
23
+ if (lanIps.length > 0) {
24
+ console.log()
25
+ console.log(chalk.gray('// mobile (same wifi)'))
26
+ for (const ip of lanIps) {
27
+ console.log(chalk.white(` http://${ip}:${actualPort}`))
28
+ }
29
+ }
30
+
31
+ console.log()
32
+ console.log(chalk.gray('// ctrl+c to stop'))
33
+
34
+ if (!noOpen) {
35
+ await open(localhost)
36
+ }
37
+ }