Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ data/
uploads/
.DS_Store
coverage/
npm/
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ pnpm exec tsbb init
pnpm start
```

Just want to use a board from the terminal? `curl -fsSL https://tsbb.dev/install.sh | sh`
installs the client (`tsbb`, `tsbb-tui`, `tsbb-mcp`) from npm.

Open <http://localhost:3000>, put in your email address, and click the link.
**The first account to sign in becomes the administrator.**

Expand Down Expand Up @@ -216,9 +219,12 @@ Turn it off in the admin panel and the `frame-src` permission goes with it.
## The terminal client

```
pnpm exec tsbb-tui https://forum.example.com
curl -fsSL https://tsbb.dev/install.sh | sh # tsbb, tsbb-tui and tsbb-mcp
tsbb-tui https://forum.example.com
```

From a checkout, `pnpm exec tsbb-tui https://forum.example.com` is the same client.

Press `L` to sign in: the client shows a short code, you approve it in a browser,
and the board hands the terminal a token — once. It is a *client* of a
centralised install, not a second board: it holds no database and can only see
Expand Down
19 changes: 19 additions & 0 deletions apps/cli/bin/tsbb.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,25 @@ if (existsSync(envPath)) {

const baseUrl = process.env.TSBB_BASE_URL ?? 'http://localhost:3000';

/*
* Running a board needs the whole repository (server, migrations, plugins). The
* npm package (curl -fsSL https://tsbb.dev/install.sh | sh) is only the client
* half, so say how to get a board instead of failing on a missing module.
*/
const BOARD_COMMANDS = new Set([
'init', 'serve', 'start', 'worker', 'migrate', 'status', 'update', 'upgrade',
'admin', 'unadmin', 'invite', 'plugin', 'plugins',
]);
if (BOARD_COMMANDS.has(command) && !existsSync(join(REPO_ROOT, 'apps/server/src/index.ts'))) {
console.error(`tsbb ${command} runs a board, which needs a checkout of the repository:

git clone https://github.com/profullstack/tsbb && cd tsbb
pnpm install && node apps/cli/bin/tsbb.mjs init

This install is the client: tsbb login, forums, latest, read, post, mcp (see tsbb --help).`);
process.exit(1);
}

/**
* Split the flags out of the arguments.
*
Expand Down
5 changes: 5 additions & 0 deletions apps/mcp/bin/tsbb-mcp.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,10 @@ try {
/* the version is a label in the handshake, not something to fail over */
}

if (process.argv[2] === '--version' || process.argv[2] === '-v') {
console.log(version);
process.exit(0);
}

const { main } = await import('../src/main.ts');
process.exitCode = await main(process.argv.slice(2), version);
4 changes: 2 additions & 2 deletions apps/mcp/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ Claude Desktop MCP config:
{
"mcpServers": {
"myboard": {
"command": "node",
"args": ["/path/to/tsbb/apps/mcp/bin/tsbb-mcp.mjs", "--server", "https://board.example"],
"command": "tsbb-mcp",
"args": ["--server", "https://board.example"],
"env": { "TSBB_TOKEN": "…" }
}
}
Expand Down
59 changes: 59 additions & 0 deletions apps/server/public/install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#!/bin/sh
# Install the tsbb bulletin board client: tsbb (CLI), tsbb-tui (terminal client)
# and tsbb-mcp (MCP server). npm: @profullstack/tsbb, -tui, -mcp.
#
# curl -fsSL https://tsbb.dev/install.sh | sh
#
# Works against any tsbb board. Needs Node >= 22.6 (the commands run on Node).
# Installs with bun if present, otherwise npm. When the global npm prefix is not writable it installs into
# ~/.local instead of asking for sudo. Re-run it to upgrade.
set -eu

PKGS="@profullstack/tsbb@latest @profullstack/tsbb-tui@latest @profullstack/tsbb-mcp@latest"
BIN="tsbb"

say() { printf '%s\n' "$*"; }
die() { printf 'tsbb install: %s\n' "$*" >&2; exit 1; }
have() { command -v "$1" >/dev/null 2>&1; }

node_ok() {
have node || return 1
node -e 'const [a,b]=process.versions.node.split(".").map(Number);process.exit(a>22||(a===22&&b>=6)?0:1)' 2>/dev/null
}

say "Installing tsbb, tsbb-tui and tsbb-mcp..."

node_ok || die "Node >= 22.6 required (found $(node -v 2>/dev/null || echo none)). Install it from https://nodejs.org"

if have bun; then
bun add -g $PKGS
BIN_DIR="${BUN_INSTALL:-$HOME/.bun}/bin"
elif have npm; then
prefix=$(npm config get prefix)
modules="$prefix/lib/node_modules"
[ -d "$modules" ] || modules="$prefix/lib"
if [ -w "$modules" ] && [ -w "$prefix/bin" ]; then
npm install -g $PKGS
BIN_DIR="$prefix/bin"
else
BIN_DIR="$HOME/.local/bin"
say "npm prefix $prefix is not writable; installing into $HOME/.local"
npm install -g --prefix "$HOME/.local" $PKGS
fi
else
die "needs npm (comes with Node: https://nodejs.org) or bun (https://bun.sh)"
fi

say ""
if have "$BIN"; then
say "tsbb $("$BIN" --version 2>/dev/null || echo '') installed (with tsbb-tui and tsbb-mcp)."
else
say "$BIN installed to $BIN_DIR, which is not on your PATH. Add it:"
say " export PATH=\"$BIN_DIR:\$PATH\""
fi
say ""
say "Next:"
say " tsbb login tsbb.dev # approve a code in your browser"
say " tsbb latest # recently active topics"
say " tsbb-tui tsbb.dev # read and post in the terminal"
say " tsbb --help # everything else, including tsbb mcp"
12 changes: 12 additions & 0 deletions apps/server/src/routes/discovery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,17 @@ export function discoveryRoutes(services: Services) {
);
});

// --- install.sh -----------------------------------------------------------
// The client installer: curl -fsSL https://<board>/install.sh | sh. It
// installs tsbb, tsbb-tui and tsbb-mcp from npm, which work against any board.

app.get('/install.sh', (c) =>
c.body(readFileSync(join(PUBLIC_DIR, 'install.sh'), 'utf8'), 200, {
'content-type': 'text/x-shellscript; charset=utf-8',
'cache-control': 'public, max-age=600',
}),
);

// --- llms.txt -------------------------------------------------------------

app.get('/llms.txt', async (c) => {
Expand Down Expand Up @@ -299,6 +310,7 @@ export function discoveryRoutes(services: Services) {
link('OpenAPI description', '/api/v1/openapi.json', 'The REST API, machine-readable.'),
link('MCP endpoint', '/api/mcp', 'Streamable HTTP MCP server; a bearer token makes it act as a member.'),
link('Agent skill', '/skill.md', 'What an agent can do here and how to authenticate.'),
`- Terminal client, CLI and stdio MCP server: \`curl -fsSL ${absolute('/install.sh')} | sh\` (npm: @profullstack/tsbb, @profullstack/tsbb-tui, @profullstack/tsbb-mcp)`,
'',
'## Optional',
'',
Expand Down
9 changes: 8 additions & 1 deletion apps/tui/bin/tsbb-tui.mjs
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
#!/usr/bin/env node
import { currentBoard, loadConfig, normaliseServer, rememberBoard } from '../src/config.ts';
import { currentBoard, loadConfig, normaliseServer, rememberBoard } from '@tsbb/client';
import { run } from '../src/app.ts';

const argv = process.argv.slice(2);

if (argv.includes('--version') || argv.includes('-v')) {
const { readFileSync } = await import('node:fs');
const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
console.log(pkg.version);
process.exit(0);
}

if (argv.includes('--help') || argv.includes('-h')) {
console.log(`tsbb-tui — terminal client for a tsbb bulletin board

Expand Down
17 changes: 11 additions & 6 deletions docs/CLI.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,21 @@ anybody else's.
The packages are not on npm yet, so both jobs are done from a checkout:

```
git clone https://github.com/profullstack/tsbb
cd tsbb && pnpm install
pnpm exec tsbb --help
curl -fsSL https://tsbb.dev/install.sh | sh
```

Put it on your path if you are going to use it from elsewhere, which is what the
rest of this page assumes:
That installs `tsbb`, `tsbb-tui` and `tsbb-mcp` from npm (`@profullstack/tsbb`,
`-tui`, `-mcp`; Node 22.6+), with bun if you have it, and into `~/.local` rather
than asking for sudo. Re-run it to upgrade.

The installed `tsbb` is the client half: everything under "Using a board". The
commands that *run* a board (`init`, `serve`, `migrate`, `admin`, …) need a
checkout:

```
ln -s "$PWD/apps/cli/bin/tsbb.mjs" ~/.local/bin/tsbb
git clone https://github.com/profullstack/tsbb
cd tsbb && pnpm install
pnpm exec tsbb --help
```

## Signing in
Expand Down
23 changes: 12 additions & 11 deletions docs/MCP.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,20 @@ missing from `list_forums` is a forum that member cannot read.

## Over stdio

`tsbb-mcp` is not on npm yet, so point your client at the binary in a checkout:
Install it (with `tsbb` and `tsbb-tui`; Node 22.6+):

```
curl -fsSL https://tsbb.dev/install.sh | sh
```

Then point your client at it:

```json
{
"mcpServers": {
"myboard": {
"command": "node",
"args": [
"/path/to/tsbb/apps/mcp/bin/tsbb-mcp.mjs",
"--server",
"https://forum.example.com"
],
"command": "tsbb-mcp",
"args": ["--server", "https://forum.example.com"],
"env": { "TSBB_TOKEN": "tsbb_…" }
}
}
Expand All @@ -39,12 +41,11 @@ missing from `list_forums` is a forum that member cannot read.
Or, in Claude Code:

```
claude mcp add myboard -- node /path/to/tsbb/apps/mcp/bin/tsbb-mcp.mjs --server https://forum.example.com
claude mcp add myboard -- tsbb-mcp --server https://forum.example.com
```

Nothing needs building — Node runs the TypeScript directly — but the checkout
does need `pnpm install` once, because the server shares the board's client
package.
Without installing anything: `"command": "npx", "args": ["-y", "@profullstack/tsbb-mcp", "--server", "…"]`.
From a checkout, `node /path/to/tsbb/apps/mcp/bin/tsbb-mcp.mjs` is the same program.

Where the token comes from, in order: `--token`, `TSBB_TOKEN`, then whichever
board you last ran `tsbb login` against. If you have already signed in with the
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@
"tui": "node apps/tui/src/index.ts",
"test": "node --test \"test/*.test.ts\"",
"typecheck": "tsc -p tsconfig.json --noEmit",
"build:npm": "node scripts/build-npm.mjs",
"format": "prettier --write ."
},
"devDependencies": {
"@profullstack/bridges": "^0.2.0",
"@profullstack/hqtui": "^0.5.0",
"@types/node": "^24.3.0",
"esbuild": "0.25.10",
"hono": "^4.6.14",
"prettier": "^3.3.3",
"typescript": "^5.9.2"
Expand Down
Loading
Loading