@1sat/cli 0.0.90 → 0.0.92

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@1sat/cli",
3
- "version": "0.0.90",
3
+ "version": "0.0.92",
4
4
  "description": "CLI for 1Sat Ordinals SDK",
5
5
  "type": "module",
6
6
  "main": "./src/cli.ts",
@@ -26,13 +26,13 @@
26
26
  ],
27
27
  "license": "MIT",
28
28
  "dependencies": {
29
- "@1sat/actions": "0.0.189",
30
- "@1sat/client": "0.0.45",
31
- "@1sat/types": "0.0.36",
32
- "@1sat/wallet-node": "0.0.62",
33
- "@1sat/wallet-server": "0.0.36",
34
- "@bsv/sdk": "^2.1.6",
35
- "@bsv/wallet-toolbox": "2.1.24",
29
+ "@1sat/actions": "0.0.192",
30
+ "@1sat/client": "0.0.46",
31
+ "@1sat/types": "0.0.37",
32
+ "@1sat/wallet-node": "0.0.64",
33
+ "@1sat/wallet-server": "0.0.38",
34
+ "@bsv/sdk": "^2.1.9",
35
+ "@bsv/wallet-toolbox": "2.4.3",
36
36
  "chalk": "^5.0.0",
37
37
  "@clack/prompts": "^0.8.0",
38
38
  "bitcoin-backup": "^0.0.11",
package/src/beef.ts ADDED
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Parse --beef from file path, hex, or base64.
3
+ */
4
+ import { readFileSync } from 'node:fs'
5
+ import { Utils } from '@bsv/sdk'
6
+ import { fatal } from './output'
7
+
8
+ export function parseBeefFlag(value: string | undefined): number[] | undefined {
9
+ if (!value) return undefined
10
+ try {
11
+ if (!value.includes('\n') && !value.includes(' ') && value.length < 4096) {
12
+ try {
13
+ const buf = readFileSync(value)
14
+ return Array.from(buf)
15
+ } catch {
16
+ // not a readable path — treat as hex/base64
17
+ }
18
+ }
19
+ const hex = value.replace(/^0x/i, '')
20
+ if (/^[0-9a-fA-F]+$/.test(hex) && hex.length % 2 === 0) {
21
+ return Utils.toArray(hex, 'hex')
22
+ }
23
+ return Utils.toArray(value, 'base64')
24
+ } catch (e) {
25
+ fatal(`Invalid --beef: ${e instanceof Error ? e.message : String(e)}`)
26
+ }
27
+ }
28
+
29
+ /** Bare id from tags (strips id: prefix). */
30
+ export function idFromTags(tags: string[] | undefined): string | undefined {
31
+ const t = tags?.find((x) => x.startsWith('id:'))
32
+ return t ? t.slice(3) : undefined
33
+ }
34
+
35
+ /**
36
+ * Parse --to as P2PKH address or identity pubkey hex.
37
+ * Identity keys: 66 hex chars starting with 02/03.
38
+ */
39
+ export function parseToFlag(to: string): {
40
+ address?: string
41
+ counterparty?: string
42
+ } {
43
+ const trimmed = to.trim()
44
+ if (/^0[23][0-9a-fA-F]{64}$/.test(trimmed)) {
45
+ return { counterparty: trimmed }
46
+ }
47
+ return { address: trimmed }
48
+ }
@@ -0,0 +1,187 @@
1
+ /**
2
+ * Authenticated HTTP client using CLI wallet keys (BRC-104 AuthFetch).
3
+ *
4
+ * 1sat authfetch <method> <url> [--body <json|@file>] [--header 'K: V']...
5
+ *
6
+ * Always authenticates. On 402 Payment Required, prompts unless --yes, then
7
+ * AuthFetch pays and retries.
8
+ */
9
+
10
+ import { readFileSync } from 'node:fs'
11
+ import { AuthFetch } from '@bsv/sdk'
12
+ import { confirm, isCancel } from '@clack/prompts'
13
+ import type { GlobalFlags } from '../args'
14
+ import { loadContext } from '../context'
15
+ import { printCommandHelp } from '../help'
16
+ import { loadKey } from '../keys'
17
+ import { fatal, output } from '../output'
18
+
19
+ const METHODS = new Set([
20
+ 'GET',
21
+ 'POST',
22
+ 'PUT',
23
+ 'PATCH',
24
+ 'DELETE',
25
+ 'HEAD',
26
+ 'OPTIONS',
27
+ ])
28
+
29
+ export async function handleAuthfetchCommand(
30
+ args: string[],
31
+ opts: GlobalFlags,
32
+ ): Promise<void> {
33
+ if (
34
+ !args[0] ||
35
+ args[0] === 'help' ||
36
+ args.includes('--help') ||
37
+ args.includes('-h')
38
+ ) {
39
+ printCommandHelp('authfetch', opts.json)
40
+ return
41
+ }
42
+
43
+ let method: string | undefined
44
+ let url: string | undefined
45
+ let bodyRaw: string | undefined
46
+ const headerArgs: string[] = []
47
+
48
+ for (let i = 0; i < args.length; i++) {
49
+ const a = args[i]
50
+ if (a === '--body') {
51
+ bodyRaw = args[++i]
52
+ if (bodyRaw === undefined) fatal('--body requires a value')
53
+ continue
54
+ }
55
+ if (a === '--header') {
56
+ const h = args[++i]
57
+ if (h === undefined) fatal('--header requires a value')
58
+ headerArgs.push(h)
59
+ continue
60
+ }
61
+ if (a.startsWith('--')) {
62
+ fatal(`Unknown flag: ${a}`)
63
+ }
64
+ if (!method) {
65
+ method = a.toUpperCase()
66
+ continue
67
+ }
68
+ if (!url) {
69
+ url = a
70
+ continue
71
+ }
72
+ fatal(`Unexpected argument: ${a}`)
73
+ }
74
+
75
+ if (!method || !METHODS.has(method)) {
76
+ fatal(
77
+ `Usage: 1sat authfetch <method> <url> [--body …] [--header 'K: V']…\nUnknown or missing method: ${method ?? ''}`,
78
+ )
79
+ }
80
+ if (!url) fatal('Missing URL')
81
+ try {
82
+ new URL(url)
83
+ } catch {
84
+ fatal(`Invalid URL: ${url}`)
85
+ }
86
+
87
+ const headers: Record<string, string> = {}
88
+ for (const h of headerArgs) {
89
+ const i = h.indexOf(':')
90
+ if (i <= 0) fatal(`Invalid --header (want 'Name: value'): ${h}`)
91
+ headers[h.slice(0, i).trim()] = h.slice(i + 1).trim()
92
+ }
93
+
94
+ let body: string | undefined
95
+ if (bodyRaw !== undefined) {
96
+ body = bodyRaw.startsWith('@')
97
+ ? readFileSync(bodyRaw.slice(1), 'utf8')
98
+ : bodyRaw
99
+ if (!headers['Content-Type'] && !headers['content-type']) {
100
+ headers['Content-Type'] = 'application/json'
101
+ }
102
+ }
103
+
104
+ const privateKey = await loadKey()
105
+ const { walletResult, destroy } = await loadContext(privateKey, {
106
+ chain: opts.chain,
107
+ })
108
+
109
+ try {
110
+ const auth = new AuthFetch(walletResult.wallet)
111
+ const init = {
112
+ method,
113
+ headers,
114
+ ...(body !== undefined ? { body } : {}),
115
+ }
116
+
117
+ let res: Response
118
+ try {
119
+ // Authenticate but do not auto-pay on first try (so we can confirm).
120
+ res = await auth.fetch(url, {
121
+ ...init,
122
+ paymentRetryAttempts: 0,
123
+ })
124
+ } catch (err) {
125
+ // Public routes may return 200 without BSV auth headers; AuthFetch rejects those.
126
+ const msg = err instanceof Error ? err.message : String(err)
127
+ if (/without valid BSV authentication/i.test(msg)) {
128
+ res = await fetch(url, init)
129
+ } else {
130
+ throw err
131
+ }
132
+ }
133
+
134
+ if (res.status === 402) {
135
+ const satsHeader = res.headers.get('x-bsv-payment-satoshis-required')
136
+ const sats = satsHeader ? Number(satsHeader) : undefined
137
+ const payMsg =
138
+ sats != null && Number.isFinite(sats)
139
+ ? `Pay ${sats} sats for ${method} ${url}?`
140
+ : `Server requires payment (402) for ${method} ${url}. Continue?`
141
+
142
+ if (!opts.yes) {
143
+ const ok = await confirm({ message: payMsg })
144
+ if (isCancel(ok) || !ok) fatal('Payment cancelled.')
145
+ }
146
+
147
+ res = await auth.fetch(url, init)
148
+ }
149
+
150
+ const text = await res.text()
151
+ let parsed: unknown = text
152
+ const ct = res.headers.get('content-type') ?? ''
153
+ if (ct.includes('application/json') && text.length > 0) {
154
+ try {
155
+ parsed = JSON.parse(text)
156
+ } catch {
157
+ /* keep raw */
158
+ }
159
+ }
160
+
161
+ if (opts.json) {
162
+ output(
163
+ {
164
+ status: res.status,
165
+ ok: res.ok,
166
+ headers: Object.fromEntries(res.headers.entries()),
167
+ body: parsed,
168
+ },
169
+ opts,
170
+ )
171
+ if (!res.ok) process.exit(1)
172
+ return
173
+ }
174
+
175
+ if (!opts.quiet) {
176
+ console.log(`${res.status} ${res.statusText || ''}`.trim())
177
+ }
178
+ if (text.length > 0) {
179
+ console.log(
180
+ typeof parsed === 'string' ? parsed : JSON.stringify(parsed, null, 2),
181
+ )
182
+ }
183
+ if (!res.ok) process.exit(1)
184
+ } finally {
185
+ await destroy()
186
+ }
187
+ }
@@ -1,15 +1,16 @@
1
1
  /**
2
- * Lock commands - info, lock, unlock.
2
+ * Lock commands list, bsv (lock), unlock.
3
3
  */
4
4
 
5
- import { getLockData, lockBsv, unlockBsv } from '@1sat/actions'
5
+ import { listLocks, lockBsv, unlockBsv } from '@1sat/actions'
6
6
  import { confirm, isCancel } from '@clack/prompts'
7
7
  import type { GlobalFlags } from '../args'
8
8
  import { extractFlag } from '../args'
9
+ import { idFromTags } from '../beef'
9
10
  import { loadContext } from '../context'
10
11
  import { printCommandHelp } from '../help'
11
12
  import { loadKey } from '../keys'
12
- import { fatal, output, printKeyValue } from '../output'
13
+ import { fatal, formatLabel, formatValue, output } from '../output'
13
14
 
14
15
  export async function handleLocksCommand(
15
16
  args: string[],
@@ -18,10 +19,12 @@ export async function handleLocksCommand(
18
19
  const [subcommand, ...rest] = args
19
20
 
20
21
  switch (subcommand) {
21
- case 'info':
22
- return locksInfo(rest, opts)
23
- case 'lock':
24
- return locksLock(rest, opts)
22
+ case 'list':
23
+ case 'info': // deprecated alias — still summary-friendly
24
+ return locksList(rest, opts)
25
+ case 'bsv':
26
+ case 'lock': // deprecated alias
27
+ return locksBsv(rest, opts)
25
28
  case 'unlock':
26
29
  return locksUnlock(rest, opts)
27
30
  default:
@@ -32,89 +35,99 @@ export async function handleLocksCommand(
32
35
  }
33
36
  }
34
37
 
35
- async function locksInfo(_args: string[], opts: GlobalFlags): Promise<void> {
38
+ async function locksList(_args: string[], opts: GlobalFlags): Promise<void> {
36
39
  const privateKey = await loadKey()
37
- const { ctx, destroy } = await loadContext(privateKey, {
38
- chain: opts.chain,
39
- })
40
+ const { ctx, destroy } = await loadContext(privateKey, { chain: opts.chain })
40
41
 
41
42
  try {
42
- const data = await getLockData.execute(ctx, {})
43
+ const result = await listLocks.execute(ctx, {})
43
44
 
44
45
  if (opts.json) {
45
- output(data, opts)
46
+ output(result.outputs, opts)
46
47
  return
47
48
  }
48
49
 
49
- printKeyValue({
50
- 'Total Locked (sats)': data.totalLocked,
51
- 'Unlockable (sats)': data.unlockable,
52
- 'Next Unlock Block': data.nextUnlock || 'none',
53
- })
50
+ if (result.outputs.length === 0) {
51
+ output('No locks found.', opts)
52
+ return
53
+ }
54
+
55
+ let total = 0
56
+ for (const o of result.outputs) {
57
+ const id = idFromTags(o.tags) ?? ''
58
+ const until = o.tags?.find((t) => t.startsWith('until:'))?.slice(6) ?? '?'
59
+ total += o.satoshis
60
+ console.log(
61
+ ` ${formatValue(id)} ${formatValue(String(o.satoshis))} sats until ${formatLabel(until)} ${formatValue(o.outpoint)}`,
62
+ )
63
+ }
64
+ console.log(`\n ${result.outputs.length} lock(s), ${total} sats total.`)
54
65
  } finally {
55
66
  await destroy()
56
67
  }
57
68
  }
58
69
 
59
- async function locksLock(args: string[], opts: GlobalFlags): Promise<void> {
70
+ async function locksBsv(args: string[], opts: GlobalFlags): Promise<void> {
60
71
  const satsStr = extractFlag(args, '--sats')
61
- const blocksStr = extractFlag(args, '--blocks')
72
+ const untilStr = extractFlag(args, '--until') ?? extractFlag(args, '--blocks')
62
73
 
63
74
  if (!satsStr) fatal('Missing --sats <amount>')
64
- if (!blocksStr) fatal('Missing --blocks <n>')
75
+ if (!untilStr) fatal('Missing --until <block-height>')
65
76
 
66
77
  const satoshis = Number(satsStr)
67
78
  if (!Number.isFinite(satoshis) || satoshis <= 0) {
68
79
  fatal('--sats must be a positive number')
69
80
  }
70
81
 
71
- const until = Number(blocksStr)
82
+ const until = Number(untilStr)
72
83
  if (!Number.isFinite(until) || until <= 0) {
73
- fatal('--blocks must be a positive block height')
84
+ fatal('--until must be a positive block height')
74
85
  }
75
86
 
76
87
  if (!opts.yes) {
77
88
  const ok = await confirm({
78
89
  message: `Lock ${satoshis} satoshis until block ${until}?`,
79
90
  })
80
- if (isCancel(ok) || !ok) {
81
- fatal('Lock cancelled.')
82
- }
91
+ if (isCancel(ok) || !ok) fatal('Lock cancelled.')
83
92
  }
84
93
 
85
94
  const privateKey = await loadKey()
86
- const { ctx, destroy } = await loadContext(privateKey, {
87
- chain: opts.chain,
88
- })
95
+ const { ctx, destroy } = await loadContext(privateKey, { chain: opts.chain })
89
96
 
90
97
  try {
91
98
  const result = await lockBsv.execute(ctx, {
92
99
  requests: [{ satoshis, until }],
93
100
  })
94
-
95
- if (result.error) {
96
- fatal(result.error)
97
- }
98
-
101
+ if (result.error) fatal(result.error)
99
102
  output(opts.json ? result : { txid: result.txid }, opts)
100
103
  } finally {
101
104
  await destroy()
102
105
  }
103
106
  }
104
107
 
105
- async function locksUnlock(_args: string[], opts: GlobalFlags): Promise<void> {
106
- const privateKey = await loadKey()
107
- const { ctx, destroy } = await loadContext(privateKey, {
108
- chain: opts.chain,
109
- })
108
+ async function locksUnlock(args: string[], opts: GlobalFlags): Promise<void> {
109
+ const ids = extractFlag(args, '--ids')
110
+ ?.split(',')
111
+ .map((s) => s.trim())
112
+ .filter(Boolean)
110
113
 
111
- try {
112
- const result = await unlockBsv.execute(ctx, {})
114
+ if (!opts.yes) {
115
+ const ok = await confirm({
116
+ message: ids?.length
117
+ ? `Unlock ${ids.length} lock(s)?`
118
+ : 'Unlock all matured locks?',
119
+ })
120
+ if (isCancel(ok) || !ok) fatal('Unlock cancelled.')
121
+ }
113
122
 
114
- if (result.error) {
115
- fatal(result.error)
116
- }
123
+ const privateKey = await loadKey()
124
+ const { ctx, destroy } = await loadContext(privateKey, { chain: opts.chain })
117
125
 
126
+ try {
127
+ const result = await unlockBsv.execute(ctx, {
128
+ ...(ids?.length ? { ids } : {}),
129
+ })
130
+ if (result.error) fatal(result.error)
118
131
  output(opts.json ? result : { txid: result.txid }, opts)
119
132
  } finally {
120
133
  await destroy()
@@ -0,0 +1,59 @@
1
+ /**
2
+ * MessageBox commands — pull paymail / P2P inbox into the wallet.
3
+ *
4
+ * 1sat messagebox sync [--url <host>] [--box <name>]
5
+ */
6
+
7
+ import { syncMessages } from '@1sat/actions'
8
+ import type { GlobalFlags } from '../args'
9
+ import { extractFlag } from '../args'
10
+ import { loadContext } from '../context'
11
+ import { printCommandHelp } from '../help'
12
+ import { loadKey } from '../keys'
13
+ import { output } from '../output'
14
+
15
+ export async function handleMessageboxCommand(
16
+ args: string[],
17
+ opts: GlobalFlags,
18
+ ): Promise<void> {
19
+ const [subcommand, ...rest] = args
20
+
21
+ switch (subcommand) {
22
+ case 'sync':
23
+ return messageboxSync(rest, opts)
24
+ default:
25
+ printCommandHelp('messagebox', opts.json)
26
+ if (subcommand && subcommand !== 'help') {
27
+ process.exit(1)
28
+ }
29
+ }
30
+ }
31
+
32
+ async function messageboxSync(
33
+ args: string[],
34
+ opts: GlobalFlags,
35
+ ): Promise<void> {
36
+ const url = extractFlag(args, '--url')
37
+ const box = extractFlag(args, '--box')
38
+
39
+ const privateKey = await loadKey()
40
+ const { ctx, destroy } = await loadContext(privateKey, {
41
+ chain: opts.chain,
42
+ })
43
+
44
+ try {
45
+ const result = await syncMessages.execute(ctx, {
46
+ ...(url ? { messageboxUrl: url } : {}),
47
+ ...(box ? { messageBox: box } : {}),
48
+ })
49
+
50
+ if (opts.json) {
51
+ output(result, opts)
52
+ return
53
+ }
54
+
55
+ console.log(`\nprocessed: ${result.processed} failed: ${result.failed}\n`)
56
+ } finally {
57
+ await destroy()
58
+ }
59
+ }