@elefunc/send 0.1.1 → 0.1.2
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 -0
- package/package.json +1 -1
- package/src/index.ts +43 -13
- package/src/tui/app.ts +35 -0
package/README.md
CHANGED
|
@@ -15,16 +15,21 @@ bun add -g @elefunc/send
|
|
|
15
15
|
## Usage
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
|
+
send
|
|
18
19
|
send peers
|
|
19
20
|
send offer ./file.txt
|
|
20
21
|
send accept
|
|
21
22
|
send tui --events
|
|
22
23
|
```
|
|
23
24
|
|
|
25
|
+
When no subcommand is provided, `send` launches the TUI by default.
|
|
26
|
+
|
|
24
27
|
## Rooms
|
|
25
28
|
|
|
26
29
|
`--room` is optional on all commands. If you omit it, `send` creates a random room and prints or shows it.
|
|
27
30
|
|
|
31
|
+
In the TUI, the room row includes a `📋` invite link that opens the equivalent web app URL for the current committed room and toggle state. Set `SEND_WEB_URL` to change its base URL; it defaults to `https://send.rt.ht/`.
|
|
32
|
+
|
|
28
33
|
## Self Identity
|
|
29
34
|
|
|
30
35
|
`--self` accepts three forms:
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -197,8 +197,23 @@ const tuiCommand = async (options: Record<string, unknown>) => {
|
|
|
197
197
|
await startTui(initialConfig, !!options.events)
|
|
198
198
|
}
|
|
199
199
|
|
|
200
|
-
|
|
200
|
+
type CliHandlers = {
|
|
201
|
+
peers: typeof peersCommand
|
|
202
|
+
offer: typeof offerCommand
|
|
203
|
+
accept: typeof acceptCommand
|
|
204
|
+
tui: typeof tuiCommand
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const defaultCliHandlers: CliHandlers = {
|
|
208
|
+
peers: peersCommand,
|
|
209
|
+
offer: offerCommand,
|
|
210
|
+
accept: acceptCommand,
|
|
211
|
+
tui: tuiCommand,
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export const createCli = (handlers: CliHandlers = defaultCliHandlers) => {
|
|
201
215
|
const cli = cac("send")
|
|
216
|
+
cli.usage("[command] [options]")
|
|
202
217
|
|
|
203
218
|
cli
|
|
204
219
|
.command("peers", "list discovered peers")
|
|
@@ -210,7 +225,7 @@ export const createCli = () => {
|
|
|
210
225
|
.option("--turn-url <url>", "custom TURN url, repeat or comma-separate")
|
|
211
226
|
.option("--turn-username <value>", "custom TURN username")
|
|
212
227
|
.option("--turn-credential <value>", "custom TURN credential")
|
|
213
|
-
.action(
|
|
228
|
+
.action(handlers.peers)
|
|
214
229
|
|
|
215
230
|
cli
|
|
216
231
|
.command("offer [...files]", "offer files to browser-compatible peers")
|
|
@@ -223,7 +238,7 @@ export const createCli = () => {
|
|
|
223
238
|
.option("--turn-url <url>", "custom TURN url, repeat or comma-separate")
|
|
224
239
|
.option("--turn-username <value>", "custom TURN username")
|
|
225
240
|
.option("--turn-credential <value>", "custom TURN credential")
|
|
226
|
-
.action(
|
|
241
|
+
.action(handlers.offer)
|
|
227
242
|
|
|
228
243
|
cli
|
|
229
244
|
.command("accept", "receive and save files")
|
|
@@ -235,7 +250,7 @@ export const createCli = () => {
|
|
|
235
250
|
.option("--turn-url <url>", "custom TURN url, repeat or comma-separate")
|
|
236
251
|
.option("--turn-username <value>", "custom TURN username")
|
|
237
252
|
.option("--turn-credential <value>", "custom TURN credential")
|
|
238
|
-
.action(
|
|
253
|
+
.action(handlers.accept)
|
|
239
254
|
|
|
240
255
|
cli
|
|
241
256
|
.command("tui", "launch the interactive terminal UI")
|
|
@@ -246,24 +261,39 @@ export const createCli = () => {
|
|
|
246
261
|
.option("--turn-url <url>", "custom TURN url, repeat or comma-separate")
|
|
247
262
|
.option("--turn-username <value>", "custom TURN username")
|
|
248
263
|
.option("--turn-credential <value>", "custom TURN credential")
|
|
249
|
-
.action(
|
|
264
|
+
.action(handlers.tui)
|
|
250
265
|
|
|
251
|
-
cli.help(
|
|
266
|
+
cli.help(sections => {
|
|
267
|
+
const usage = sections.find(section => section.title === "Usage:")
|
|
268
|
+
if (usage) usage.body = " $ send [command] [options]"
|
|
269
|
+
const moreInfoIndex = sections.findIndex(section => section.title?.startsWith("For more info"))
|
|
270
|
+
const defaultSection = {
|
|
271
|
+
title: "Default",
|
|
272
|
+
body: " send with no command launches the terminal UI (same as `send tui`).",
|
|
273
|
+
}
|
|
274
|
+
if (moreInfoIndex < 0) sections.push(defaultSection)
|
|
275
|
+
else sections.splice(moreInfoIndex, 0, defaultSection)
|
|
276
|
+
})
|
|
252
277
|
|
|
253
278
|
return cli
|
|
254
279
|
}
|
|
255
280
|
|
|
256
|
-
const
|
|
281
|
+
const explicitCommand = (cli: CAC, argv: string[]) => {
|
|
257
282
|
const command = argv[2]
|
|
258
|
-
if (!command || command.startsWith("-")) return
|
|
259
|
-
if (cli.commands.some(entry => entry.isMatched(command))) return
|
|
283
|
+
if (!command || command.startsWith("-")) return undefined
|
|
284
|
+
if (cli.commands.some(entry => entry.isMatched(command))) return command
|
|
260
285
|
throw new ExitError(`Unknown command \`${command}\``, 1)
|
|
261
286
|
}
|
|
262
287
|
|
|
263
|
-
export const runCli = async (argv = process.argv) => {
|
|
264
|
-
const cli = createCli()
|
|
265
|
-
|
|
266
|
-
cli.parse(argv, { run: false })
|
|
288
|
+
export const runCli = async (argv = process.argv, handlers: CliHandlers = defaultCliHandlers) => {
|
|
289
|
+
const cli = createCli(handlers)
|
|
290
|
+
const command = explicitCommand(cli, argv)
|
|
291
|
+
const parsed = cli.parse(argv, { run: false }) as { options: Record<string, unknown> }
|
|
292
|
+
const helpRequested = !!parsed.options.help || !!parsed.options.h
|
|
293
|
+
if (!command && !helpRequested) {
|
|
294
|
+
await handlers.tui(parsed.options)
|
|
295
|
+
return
|
|
296
|
+
}
|
|
267
297
|
await cli.runMatchedCommand()
|
|
268
298
|
}
|
|
269
299
|
|
package/src/tui/app.ts
CHANGED
|
@@ -97,6 +97,7 @@ const NAME_INPUT_ID = "name-input"
|
|
|
97
97
|
const DRAFT_INPUT_ID = "draft-input"
|
|
98
98
|
const TRANSPARENT_BORDER_STYLE = { fg: rgb(7, 10, 12) } as const
|
|
99
99
|
const METRIC_BORDER_STYLE = { fg: rgb(20, 25, 32) } as const
|
|
100
|
+
const DEFAULT_WEB_URL = "https://send.rt.ht/"
|
|
100
101
|
|
|
101
102
|
const countFormat = new Intl.NumberFormat(undefined, { maximumFractionDigits: 0 })
|
|
102
103
|
const percentFormat = new Intl.NumberFormat(undefined, { maximumFractionDigits: 0 })
|
|
@@ -107,6 +108,32 @@ export const visiblePanes = (showEvents: boolean): VisiblePane[] => showEvents ?
|
|
|
107
108
|
|
|
108
109
|
const noop = () => {}
|
|
109
110
|
|
|
111
|
+
const hashBool = (value: boolean) => value ? "1" : "0"
|
|
112
|
+
|
|
113
|
+
export const resolveWebUrlBase = (value = process.env.SEND_WEB_URL) => {
|
|
114
|
+
const candidate = `${value ?? ""}`.trim() || DEFAULT_WEB_URL
|
|
115
|
+
try {
|
|
116
|
+
return new URL(candidate).toString()
|
|
117
|
+
} catch {
|
|
118
|
+
return DEFAULT_WEB_URL
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export const webInviteUrl = (
|
|
123
|
+
state: Pick<TuiState, "snapshot" | "hideTerminalPeers" | "autoAcceptIncoming" | "autoOfferOutgoing" | "autoSaveIncoming">,
|
|
124
|
+
baseUrl = resolveWebUrlBase(),
|
|
125
|
+
) => {
|
|
126
|
+
const url = new URL(baseUrl)
|
|
127
|
+
url.hash = new URLSearchParams({
|
|
128
|
+
room: cleanRoom(state.snapshot.room),
|
|
129
|
+
clean: hashBool(state.hideTerminalPeers),
|
|
130
|
+
accept: hashBool(state.autoAcceptIncoming),
|
|
131
|
+
offer: hashBool(state.autoOfferOutgoing),
|
|
132
|
+
save: hashBool(state.autoSaveIncoming),
|
|
133
|
+
}).toString()
|
|
134
|
+
return url.toString()
|
|
135
|
+
}
|
|
136
|
+
|
|
110
137
|
export const createNoopTuiActions = (): TuiActions => ({
|
|
111
138
|
toggleEvents: noop,
|
|
112
139
|
jumpToRandomRoom: noop,
|
|
@@ -505,6 +532,14 @@ const renderRoomCard = (state: TuiState, actions: TuiActions) => denseSection({
|
|
|
505
532
|
onBlur: actions.commitRoom,
|
|
506
533
|
}),
|
|
507
534
|
]),
|
|
535
|
+
ui.row({ id: "room-invite-slot", width: 6, justify: "center", items: "center" }, [
|
|
536
|
+
ui.link({
|
|
537
|
+
id: "room-invite-link",
|
|
538
|
+
label: "📋",
|
|
539
|
+
accessibleLabel: "Open invite link",
|
|
540
|
+
url: webInviteUrl(state),
|
|
541
|
+
}),
|
|
542
|
+
]),
|
|
508
543
|
]),
|
|
509
544
|
])
|
|
510
545
|
|