3 Commits

Author SHA1 Message Date
clawbot
8854b17ebc refactor: move CLI code from cmd/ to internal/cli
All checks were successful
check / check (push) Successful in 4s
Move all non-bootstrapping CLI code to internal/cli package.
cmd/neoirc-cli/main.go now contains only minimal bootstrapping
that calls cli.Run(). The App struct, UI, command handlers, poll
loop, and api client are now in internal/cli/ and internal/cli/api/.
2026-03-10 03:28:52 -07:00
clawbot
6fa46f4b75 fix: move hashcash PoW from build artifact to JSX source
The hashcash proof-of-work implementation was incorrectly added to the
build artifact web/dist/app.js instead of the source file web/src/app.jsx.
Running web/build.sh would overwrite all hashcash changes.

Changes:
- Add checkLeadingZeros() and mintHashcash() functions to app.jsx
- Integrate hashcash into LoginScreen: fetch hashcash_bits from /server,
  compute stamp via Web Crypto API before session creation, show
  'Computing proof-of-work...' feedback
- Remove web/dist/ from git tracking (build artifacts)
- Add web/dist/ to .gitignore
2026-03-10 03:27:16 -07:00
clawbot
a89393186f feat: implement hashcash proof-of-work for session creation
Add SHA-256-based hashcash proof-of-work requirement to POST /session
to prevent abuse via rapid session creation. The server advertises the
required difficulty via GET /server (hashcash_bits field), and clients
must include a valid stamp in the X-Hashcash request header.

Server-side:
- New internal/hashcash package with stamp validation (format, bits,
  date, resource, replay prevention via in-memory spent set)
- Config: NEOIRC_HASHCASH_BITS env var (default 20, set 0 to disable)
- GET /server includes hashcash_bits when > 0
- POST /session validates X-Hashcash header when enabled
- Returns HTTP 402 for missing/invalid stamps

Client-side:
- SPA: fetches hashcash_bits from /server, computes stamp using Web
  Crypto API with batched SHA-256, shows 'Computing proof-of-work...'
  feedback during computation
- CLI: api package gains MintHashcash() function, CreateSession()
  auto-fetches server info and computes stamp when required

Stamp format: 1:bits:YYMMDD:resource::counter (standard hashcash)

closes #11
2026-03-10 03:27:16 -07:00
3 changed files with 20 additions and 21 deletions

View File

@@ -2311,15 +2311,18 @@ neoirc/
├── cmd/ ├── cmd/
│ ├── neoircd/ # Server binary entry point │ ├── neoircd/ # Server binary entry point
│ │ └── main.go │ │ └── main.go
│ └── neoirc-cli/ # TUI client │ └── neoirc-cli/ # TUI client entry point
── main.go # Command handling, poll loop ── main.go # Minimal bootstrapping (calls internal/cli)
│ ├── ui.go # tview-based terminal UI
│ └── api/
│ ├── client.go # HTTP API client library
│ └── types.go # Request/response types
├── internal/ ├── internal/
│ ├── broker/ # In-memory pub/sub for long-poll notifications │ ├── broker/ # In-memory pub/sub for long-poll notifications
│ │ └── broker.go │ │ └── broker.go
│ ├── cli/ # TUI client implementation
│ │ ├── app.go # App struct, command handling, poll loop
│ │ ├── ui.go # tview-based terminal UI
│ │ └── api/
│ │ ├── client.go # HTTP API client library
│ │ ├── types.go # Request/response types
│ │ └── hashcash.go # Hashcash proof-of-work minting
│ ├── config/ # Viper-based configuration │ ├── config/ # Viper-based configuration
│ │ └── config.go │ │ └── config.go
│ ├── db/ # Database access and migrations │ ├── db/ # Database access and migrations

View File

@@ -1,17 +1,8 @@
// Package main is the entry point for the neoirc-cli client. // Package main is the entry point for the neoirc-cli client.
package main package main
import ( import "git.eeqj.de/sneak/neoirc/internal/cli"
"fmt"
"os"
"git.eeqj.de/sneak/neoirc/internal/cli"
)
func main() { func main() {
err := cli.Run() cli.Run()
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
} }

View File

@@ -1,8 +1,9 @@
// Package cli implements the neoirc-cli client logic. // Package cli implements the neoirc-cli terminal client.
package cli package cli
import ( import (
"fmt" "fmt"
"os"
"strings" "strings"
"sync" "sync"
"time" "time"
@@ -31,8 +32,8 @@ type App struct {
stopPoll chan struct{} stopPoll chan struct{}
} }
// Run starts the neoirc-cli application. // Run creates and runs the CLI application.
func Run() error { func Run() {
app := &App{ //nolint:exhaustruct app := &App{ //nolint:exhaustruct
ui: NewUI(), ui: NewUI(),
nick: "guest", nick: "guest",
@@ -50,7 +51,11 @@ func Run() error {
"or [yellow]/help[white] for commands", "or [yellow]/help[white] for commands",
) )
return app.ui.Run() err := app.ui.Run()
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
} }
func (a *App) handleInput(text string) { func (a *App) handleInput(text string) {