@1sat/cli 0.0.1

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/bin/1sat ADDED
Binary file
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@1sat/cli",
3
+ "version": "0.0.1",
4
+ "description": "CLI for 1Sat Ordinals SDK",
5
+ "type": "module",
6
+ "main": "./src/cli.ts",
7
+ "bin": {
8
+ "1sat": "./src/cli.ts"
9
+ },
10
+ "files": ["src", "bin"],
11
+ "scripts": {
12
+ "build": "bun build ./src/cli.ts --compile --outfile=bin/1sat --external pg --external pg-native --external pg-query-stream --external tedious --external oracledb --external mysql",
13
+ "dev": "bun run src/cli.ts",
14
+ "lint": "biome check src"
15
+ },
16
+ "keywords": ["1sat", "bsv", "ordinals", "cli"],
17
+ "license": "MIT",
18
+ "dependencies": {
19
+ "@1sat/actions": "0.0.52",
20
+ "@1sat/client": "0.0.15",
21
+ "@1sat/types": "0.0.13",
22
+ "@1sat/wallet-node": "0.0.11",
23
+ "@bsv/sdk": "^2.0.4",
24
+ "chalk": "^5.0.0",
25
+ "@clack/prompts": "^0.8.0",
26
+ "bitcoin-backup": "^0.0.8"
27
+ },
28
+ "devDependencies": {
29
+ "@types/bun": "^1.3.9",
30
+ "typescript": "^5.9.3"
31
+ }
32
+ }
package/src/args.ts ADDED
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Argument parsing utilities for the 1sat CLI.
3
+ *
4
+ * Pure manual parsing - no frameworks. Follows clawnet pattern.
5
+ */
6
+
7
+ export interface GlobalFlags {
8
+ json: boolean
9
+ quiet: boolean
10
+ yes: boolean
11
+ chain: 'main' | 'test'
12
+ help: boolean
13
+ version: boolean
14
+ rest: string[]
15
+ }
16
+
17
+ /**
18
+ * Parse global flags from raw argv, returning structured flags and remaining args.
19
+ */
20
+ export function parseGlobalFlags(args: string[]): GlobalFlags {
21
+ let json = false
22
+ let quiet = false
23
+ let yes = false
24
+ let chain: 'main' | 'test' = 'main'
25
+ let help = false
26
+ let version = false
27
+ const rest: string[] = []
28
+ const skip = new Set<number>()
29
+
30
+ for (let i = 0; i < args.length; i++) {
31
+ if (skip.has(i)) continue
32
+
33
+ const arg = args[i]
34
+
35
+ switch (arg) {
36
+ case '--json':
37
+ json = true
38
+ break
39
+ case '--quiet':
40
+ case '-q':
41
+ quiet = true
42
+ break
43
+ case '--yes':
44
+ case '-y':
45
+ yes = true
46
+ break
47
+ case '--chain': {
48
+ const value = args[i + 1]
49
+ if (value === 'main' || value === 'test') {
50
+ chain = value
51
+ skip.add(i + 1)
52
+ } else {
53
+ throw new Error(
54
+ `Invalid chain value: ${value}. Must be 'main' or 'test'.`,
55
+ )
56
+ }
57
+ break
58
+ }
59
+ case '--help':
60
+ case '-h':
61
+ help = true
62
+ break
63
+ case '--version':
64
+ case '-v':
65
+ version = true
66
+ break
67
+ default:
68
+ rest.push(arg)
69
+ }
70
+ }
71
+
72
+ return { json, quiet, yes, chain, help, version, rest }
73
+ }
74
+
75
+ /**
76
+ * Extract a named flag value from args.
77
+ * Returns the value following the flag, or undefined if not found.
78
+ */
79
+ export function extractFlag(args: string[], flag: string): string | undefined {
80
+ const idx = args.indexOf(flag)
81
+ if (idx === -1 || idx + 1 >= args.length) return undefined
82
+ return args[idx + 1]
83
+ }
84
+
85
+ /**
86
+ * Check if a boolean flag is present.
87
+ */
88
+ export function hasFlag(args: string[], flag: string): boolean {
89
+ return args.includes(flag)
90
+ }
91
+
92
+ /**
93
+ * Extract positional arguments, skipping flag indices.
94
+ */
95
+ export function extractPositional(
96
+ args: string[],
97
+ skipIndices: Set<number>,
98
+ ): string[] {
99
+ return args.filter((arg, i) => !arg.startsWith('--') && !skipIndices.has(i))
100
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env bun
2
+
3
+ /**
4
+ * 1sat CLI - Command-line interface for 1Sat Ordinals SDK.
5
+ *
6
+ * Pure Bun CLI with manual arg parsing. No frameworks.
7
+ */
8
+
9
+ import { parseGlobalFlags } from './args'
10
+ import { handleActionCommand } from './commands/action'
11
+ import { handleConfigCommand } from './commands/config'
12
+ import { handleIdentityCommand } from './commands/identity'
13
+ import { handleInitCommand } from './commands/init'
14
+ import { handleLocksCommand } from './commands/locks'
15
+ import { handleOpnsCommand } from './commands/opns'
16
+ import { handleOrdinalsCommand } from './commands/ordinals'
17
+ import { handleSocialCommand } from './commands/social'
18
+ import { handleSweepCommand } from './commands/sweep'
19
+ import { handleTokensCommand } from './commands/tokens'
20
+ import { handleTxCommand } from './commands/tx'
21
+ import { handleWalletCommand } from './commands/wallet'
22
+ import { printHelp, printVersion } from './help'
23
+ import { formatError } from './output'
24
+
25
+ const rawArgs = process.argv.slice(2)
26
+
27
+ async function main(): Promise<void> {
28
+ const flags = parseGlobalFlags(rawArgs)
29
+
30
+ if (flags.version) {
31
+ printVersion()
32
+ process.exit(0)
33
+ }
34
+
35
+ const [command, ...rest] = flags.rest
36
+
37
+ if (!command || flags.help) {
38
+ printHelp()
39
+ process.exit(0)
40
+ }
41
+
42
+ switch (command) {
43
+ case 'init':
44
+ await handleInitCommand(rest, flags)
45
+ break
46
+
47
+ case 'config':
48
+ await handleConfigCommand(rest, flags)
49
+ break
50
+
51
+ case 'wallet':
52
+ await handleWalletCommand(rest, flags)
53
+ break
54
+
55
+ case 'ordinals':
56
+ await handleOrdinalsCommand(rest, flags)
57
+ break
58
+
59
+ case 'tokens':
60
+ await handleTokensCommand(rest, flags)
61
+ break
62
+
63
+ case 'locks':
64
+ await handleLocksCommand(rest, flags)
65
+ break
66
+
67
+ case 'identity':
68
+ await handleIdentityCommand(rest, flags)
69
+ break
70
+
71
+ case 'social':
72
+ await handleSocialCommand(rest, flags)
73
+ break
74
+
75
+ case 'opns':
76
+ await handleOpnsCommand(rest, flags)
77
+ break
78
+
79
+ case 'sweep':
80
+ await handleSweepCommand(rest, flags)
81
+ break
82
+
83
+ case 'action':
84
+ await handleActionCommand(rest, flags)
85
+ break
86
+
87
+ case 'tx':
88
+ await handleTxCommand(rest, flags)
89
+ break
90
+
91
+ case 'help':
92
+ printHelp()
93
+ break
94
+
95
+ default:
96
+ console.error(formatError(`Unknown command: ${command}`))
97
+ printHelp()
98
+ process.exit(1)
99
+ }
100
+ }
101
+
102
+ main().catch((err) => {
103
+ console.error(formatError(`Error: ${err.message}`))
104
+ process.exit(1)
105
+ })
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Generic action executor.
3
+ *
4
+ * Run any registered action by name with JSON input.
5
+ * Useful for scripting and testing.
6
+ */
7
+
8
+ import { actionRegistry } from '@1sat/actions'
9
+ import type { GlobalFlags } from '../args'
10
+ import { loadContext } from '../context'
11
+ import { loadKey, resolvePassword } from '../keys'
12
+ import { fatal, output } from '../output'
13
+
14
+ export async function handleActionCommand(
15
+ args: string[],
16
+ opts: GlobalFlags,
17
+ ): Promise<void> {
18
+ const [actionName, jsonInput] = args
19
+
20
+ if (!actionName) {
21
+ // List available actions
22
+ const actions = actionRegistry.list()
23
+ if (opts.json) {
24
+ output(
25
+ actions.map((a) => ({
26
+ name: a.meta.name,
27
+ description: a.meta.description,
28
+ category: a.meta.category,
29
+ })),
30
+ opts,
31
+ )
32
+ return
33
+ }
34
+
35
+ console.log('\nRegistered actions:\n')
36
+ const grouped = new Map<string, typeof actions>()
37
+ for (const action of actions) {
38
+ const cat = action.meta.category
39
+ if (!grouped.has(cat)) grouped.set(cat, [])
40
+ grouped.get(cat)!.push(action)
41
+ }
42
+ for (const [category, categoryActions] of grouped) {
43
+ console.log(` ${category}:`)
44
+ for (const a of categoryActions) {
45
+ console.log(` ${a.meta.name.padEnd(30)} ${a.meta.description}`)
46
+ }
47
+ console.log()
48
+ }
49
+ return
50
+ }
51
+
52
+ const action = actionRegistry.get(actionName)
53
+ if (!action) {
54
+ fatal(
55
+ `Unknown action: ${actionName}\n\nRun '1sat action' to list available actions.`,
56
+ )
57
+ }
58
+
59
+ let input: unknown = {}
60
+ if (jsonInput) {
61
+ try {
62
+ input = JSON.parse(jsonInput)
63
+ } catch {
64
+ fatal(`Invalid JSON input: ${jsonInput}`)
65
+ }
66
+ }
67
+
68
+ const privateKey = await loadKey(resolvePassword())
69
+ const { ctx, destroy } = await loadContext(privateKey, {
70
+ chain: opts.chain,
71
+ })
72
+
73
+ try {
74
+ const result = await action.execute(ctx, input)
75
+ output(result, opts)
76
+ } finally {
77
+ await destroy()
78
+ }
79
+ }
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Config command - show, set, and locate configuration.
3
+ *
4
+ * Subcommands:
5
+ * show - Display current configuration
6
+ * set - Set a configuration value
7
+ * path - Print config directory path
8
+ */
9
+
10
+ import type { GlobalFlags } from '../args'
11
+ import {
12
+ type OneSatCliConfig,
13
+ getConfigDir,
14
+ getConfigFile,
15
+ loadConfig,
16
+ updateConfig,
17
+ } from '../config'
18
+ import { printCommandHelp } from '../help'
19
+ import {
20
+ fatal,
21
+ formatLabel,
22
+ formatSuccess,
23
+ formatValue,
24
+ output,
25
+ printKeyValue,
26
+ } from '../output'
27
+
28
+ const SETTABLE_KEYS: Array<keyof OneSatCliConfig> = [
29
+ 'chain',
30
+ 'dataDir',
31
+ 'remoteStorageUrl',
32
+ 'storageIdentityKey',
33
+ ]
34
+
35
+ export async function handleConfigCommand(
36
+ args: string[],
37
+ opts: GlobalFlags,
38
+ ): Promise<void> {
39
+ const [subcommand, ...rest] = args
40
+
41
+ switch (subcommand) {
42
+ case 'show':
43
+ return configShow(opts)
44
+ case 'set':
45
+ return configSet(rest, opts)
46
+ case 'path':
47
+ return configPath(opts)
48
+ default:
49
+ printCommandHelp('config', {
50
+ show: 'Display current configuration',
51
+ set: 'Set a config value (e.g. 1sat config set chain test)',
52
+ path: 'Print config directory path',
53
+ })
54
+ if (subcommand && subcommand !== 'help') {
55
+ process.exit(1)
56
+ }
57
+ }
58
+ }
59
+
60
+ function configShow(opts: GlobalFlags): void {
61
+ const config = loadConfig()
62
+
63
+ if (opts.json) {
64
+ output(config, opts)
65
+ return
66
+ }
67
+
68
+ console.log()
69
+ printKeyValue({
70
+ chain: config.chain,
71
+ dataDir: config.dataDir,
72
+ remoteStorageUrl: config.remoteStorageUrl ?? '(not set)',
73
+ storageIdentityKey: config.storageIdentityKey ?? '(not set)',
74
+ })
75
+ console.log()
76
+ console.log(
77
+ ` ${formatLabel('config file:')} ${formatValue(getConfigFile())}`,
78
+ )
79
+ console.log()
80
+ }
81
+
82
+ function configSet(args: string[], opts: GlobalFlags): void {
83
+ const [key, value] = args
84
+
85
+ if (!key || !value) {
86
+ fatal(
87
+ `Usage: 1sat config set <key> <value>\n\nSettable keys: ${SETTABLE_KEYS.join(', ')}`,
88
+ )
89
+ }
90
+
91
+ if (!SETTABLE_KEYS.includes(key as keyof OneSatCliConfig)) {
92
+ fatal(
93
+ `Unknown config key: ${key}\n\nSettable keys: ${SETTABLE_KEYS.join(', ')}`,
94
+ )
95
+ }
96
+
97
+ // Validate specific keys
98
+ if (key === 'chain' && value !== 'main' && value !== 'test') {
99
+ fatal("chain must be 'main' or 'test'")
100
+ }
101
+
102
+ const updated = updateConfig({ [key]: value })
103
+ output(opts.json ? updated : formatSuccess(`Set ${key} = ${value}`), opts)
104
+ }
105
+
106
+ function configPath(opts: GlobalFlags): void {
107
+ const dir = getConfigDir()
108
+ output(opts.json ? { path: dir } : dir, opts)
109
+ }
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Identity commands - create, info, sign, verify.
3
+ *
4
+ * BAP (Bitcoin Attestation Protocol) identity management.
5
+ */
6
+
7
+ import { publishIdentity, signBsm } 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, resolvePassword } from '../keys'
13
+ import { fatal, output, printKeyValue } from '../output'
14
+
15
+ export async function handleIdentityCommand(
16
+ args: string[],
17
+ opts: GlobalFlags,
18
+ ): Promise<void> {
19
+ const [subcommand, ...rest] = args
20
+
21
+ switch (subcommand) {
22
+ case 'create':
23
+ return identityCreate(rest, opts)
24
+ case 'info':
25
+ return identityInfo(rest, opts)
26
+ case 'sign':
27
+ return identitySign(rest, opts)
28
+ case 'verify':
29
+ return identityVerify(rest, opts)
30
+ default:
31
+ printCommandHelp('identity', {
32
+ create: 'Create/publish a BAP identity',
33
+ info: 'Show BAP identity information',
34
+ sign: 'Sign a message with identity key (--message <text>)',
35
+ verify:
36
+ 'Verify a signed message (--message <text> --sig <sig> --address <addr>)',
37
+ })
38
+ if (subcommand && subcommand !== 'help') {
39
+ process.exit(1)
40
+ }
41
+ }
42
+ }
43
+
44
+ async function identityCreate(
45
+ _args: string[],
46
+ opts: GlobalFlags,
47
+ ): Promise<void> {
48
+ const privateKey = await loadKey(resolvePassword())
49
+ const { ctx, destroy } = await loadContext(privateKey, {
50
+ chain: opts.chain,
51
+ })
52
+
53
+ try {
54
+ const result = await publishIdentity.execute(ctx, {})
55
+
56
+ if (result.error) {
57
+ fatal(result.error)
58
+ }
59
+
60
+ output(result, opts)
61
+ } finally {
62
+ await destroy()
63
+ }
64
+ }
65
+
66
+ async function identityInfo(
67
+ _args: string[],
68
+ opts: GlobalFlags,
69
+ ): Promise<void> {
70
+ const privateKey = await loadKey(resolvePassword())
71
+ const { ctx, destroy } = await loadContext(privateKey, {
72
+ chain: opts.chain,
73
+ })
74
+
75
+ try {
76
+ const { publicKey } = await ctx.wallet.getPublicKey({
77
+ protocolID: [1, 'bap'],
78
+ keyID: '1',
79
+ counterparty: 'self',
80
+ forSelf: true,
81
+ })
82
+
83
+ if (opts.json) {
84
+ output({ identityKey: publicKey }, opts)
85
+ } else {
86
+ printKeyValue('Identity Key', publicKey)
87
+ }
88
+ } finally {
89
+ await destroy()
90
+ }
91
+ }
92
+
93
+ async function identitySign(args: string[], opts: GlobalFlags): Promise<void> {
94
+ const message = extractFlag(args, '--message')
95
+
96
+ if (!message) fatal('Missing --message <text>')
97
+
98
+ const privateKey = await loadKey(resolvePassword())
99
+ const { ctx, destroy } = await loadContext(privateKey, {
100
+ chain: opts.chain,
101
+ })
102
+
103
+ try {
104
+ const result = await signBsm.execute(ctx, { message })
105
+
106
+ if (result.error) {
107
+ fatal(result.error)
108
+ }
109
+
110
+ output(result, opts)
111
+ } finally {
112
+ await destroy()
113
+ }
114
+ }
115
+
116
+ async function identityVerify(
117
+ args: string[],
118
+ _opts: GlobalFlags,
119
+ ): Promise<void> {
120
+ const message = extractFlag(args, '--message')
121
+ const sig = extractFlag(args, '--sig')
122
+ const address = extractFlag(args, '--address')
123
+
124
+ if (!message) fatal('Missing --message <text>')
125
+ if (!sig) fatal('Missing --sig <signature>')
126
+ if (!address) fatal('Missing --address <addr>')
127
+
128
+ // BSM verification doesn't need a wallet context — it's pure crypto
129
+ // Use @bsv/sdk BSM.verify directly
130
+ fatal(
131
+ 'identity verify is not yet implemented (needs direct BSM.verify integration)',
132
+ )
133
+ }