@lelouchhe/webagent 0.3.0 → 0.4.0
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 +58 -23
- package/bin/webagent.mjs +119 -8
- package/config.toml +96 -3
- package/dist/index.html +64 -41
- package/dist/js/app.GSAIYHML.js +4 -0
- package/dist/js/chunk.AJZBJBMO.js +1 -0
- package/dist/js/chunk.CGWFHJI2.js +76 -0
- package/dist/js/chunk.D4ZYHJAM.js +1 -0
- package/dist/js/chunk.VZXGXFNN.js +5 -0
- package/dist/js/login.PYIK52HN.js +1 -0
- package/dist/js/viewer.6DT53STL.js +1 -0
- package/dist/login.html +49 -0
- package/dist/share-viewer.00gubshk.css +114 -0
- package/dist/share-viewer.html +53 -0
- package/dist/styles.012p32dz.css +1443 -0
- package/dist/sw.js +79 -27
- package/dist/theme-init.js +6 -0
- package/lib/agent-detect.js +110 -0
- package/lib/atomic-write.js +50 -0
- package/lib/attachment-dispatch.js +86 -0
- package/lib/attachment-interceptor.js +130 -0
- package/lib/attachment-labels.js +139 -0
- package/lib/attachments.js +154 -0
- package/lib/auth-middleware.js +102 -0
- package/lib/auth-store.js +269 -0
- package/lib/auth.js +89 -0
- package/lib/bootstrap.js +70 -0
- package/lib/bridge.js +244 -93
- package/lib/client-registry.js +60 -0
- package/lib/config.js +123 -9
- package/lib/daemon.js +175 -41
- package/lib/event-handler.js +209 -91
- package/lib/log-fmt.js +67 -0
- package/lib/log.js +83 -0
- package/lib/message-cleanup.js +48 -0
- package/lib/mode-bucket.js +62 -0
- package/lib/preflight.js +195 -0
- package/lib/push-service.js +338 -45
- package/lib/routes.js +1202 -144
- package/lib/server.js +149 -33
- package/lib/session-manager.js +164 -18
- package/lib/session-state.js +160 -0
- package/lib/sessions-anchor.js +28 -0
- package/lib/share/cleanup.js +45 -0
- package/lib/share/routes.js +972 -0
- package/lib/share/sanitize.js +179 -0
- package/lib/sse-manager.js +94 -8
- package/lib/sse-ticket.js +45 -0
- package/lib/startup-checks.js +94 -0
- package/lib/store.js +624 -30
- package/lib/title-service.js +42 -9
- package/lib/tokens.js +50 -0
- package/lib/types.js +23 -0
- package/package.json +38 -4
- package/dist/js/app.2562YGRO.js +0 -10
- package/dist/styles.008ve1hx.css +0 -669
- package/lib/shared/constants.js +0 -17
package/README.md
CHANGED
|
@@ -5,6 +5,24 @@
|
|
|
5
5
|
|
|
6
6
|
A terminal-style web UI for [ACP](https://agentclientprotocol.com/)-compatible agents — Copilot CLI, Claude Code, Gemini CLI, and [more](docs/configuration.md#acp-compatible-agents).
|
|
7
7
|
|
|
8
|
+
WebAgent is a thin browser client + Node.js server that lets you drive any ACP agent from a desktop browser, phone, or PWA. Sessions, permissions, and notifications stay in sync across devices; nothing leaves your machine.
|
|
9
|
+
|
|
10
|
+
## Highlights
|
|
11
|
+
|
|
12
|
+
- **Zero-config first run** — `npx @lelouchhe/webagent` and you're online. Auto-detects the ACP agent on your `PATH`, mints an admin token on first start, persists everything in `./data/`.
|
|
13
|
+
- **Multi-device, real-time** — REST + SSE keeps sessions, permissions, and bash output synced. Approve a permission on your laptop, see it confirmed on your phone.
|
|
14
|
+
- **Web Push notifications** — Get pinged on `prompt_done`, `permission_request`, or `bash_done` when the tab isn't focused. Smart per-session suppression: if any device is actively viewing session X, no buzz from session X.
|
|
15
|
+
- **PWA + mobile-friendly** — Installable to iOS / Android home screen. Mobile-first input, attach via paste/upload, dark-mode native.
|
|
16
|
+
- **Attachments** — Drag, paste, or `^U` any file (images, code, PDFs, …). Server sniffs real MIME from content, so agents reliably read it.
|
|
17
|
+
- **Inline bash** — `!ls -la` runs directly in your session's cwd, output streams in real time, cancellable.
|
|
18
|
+
- **Sessions that survive everything** — SQLite-persisted history, auto-resume on page open, auto-restore via ACP `loadSession` after server restart, auto-generated titles via a fast model.
|
|
19
|
+
- **Rich slash menu** — `/new`, `/switch`, `/model`, `/mode`, `/think`, `/notify`, `/inbox`, `/share`, `/token`, `/log` — autocomplete with Tab, submenus for pickable values.
|
|
20
|
+
- **Public share links** — `/share` snapshots a session into a sanitized read-only viewer at `/s/<token>` for show-and-tell.
|
|
21
|
+
- **Daemon mode with crash recovery** — `webagent start` runs as a background service with PID file, log rotation, and exponential-backoff restart on crash.
|
|
22
|
+
- **Built-in security** — Bearer token auth, per-device tokens, signed image URLs, strict CSP, single-operator threat model.
|
|
23
|
+
|
|
24
|
+
See [Features](docs/features.md) for the full tour.
|
|
25
|
+
|
|
8
26
|
<table>
|
|
9
27
|
<tr>
|
|
10
28
|
<td width="60%">
|
|
@@ -36,17 +54,28 @@ A terminal-style web UI for [ACP](https://agentclientprotocol.com/)-compatible a
|
|
|
36
54
|
|
|
37
55
|
## Quick Start
|
|
38
56
|
|
|
39
|
-
**Prerequisites:** Node.js 22.6+, an ACP-compatible agent installed and authenticated.
|
|
57
|
+
**Prerequisites:** Node.js 22.6+, an ACP-compatible agent installed and authenticated (Copilot CLI, Claude Code adapter, Gemini CLI, etc.).
|
|
40
58
|
|
|
41
59
|
```bash
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
60
|
+
npx @lelouchhe/webagent # zero-install, runs on port 6800
|
|
61
|
+
# — or —
|
|
62
|
+
npm install -g @lelouchhe/webagent && webagent
|
|
45
63
|
```
|
|
46
64
|
|
|
47
|
-
|
|
65
|
+
On first run, the server prints a one-time admin token in the startup
|
|
66
|
+
diagnostic. Open `http://localhost:6800`, paste the token into the
|
|
67
|
+
login form, done. The token persists in `data/auth.json` — subsequent
|
|
68
|
+
runs skip the prompt.
|
|
69
|
+
|
|
70
|
+
Other ways to start:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
webagent start # background daemon (same first-run UX in your terminal)
|
|
74
|
+
webagent --config /path/to/config.toml # custom config (`webagent config init` to scaffold one)
|
|
75
|
+
webagent --create-token laptop # mint extra tokens for other devices / CI
|
|
76
|
+
```
|
|
48
77
|
|
|
49
|
-
Data (SQLite database, uploaded
|
|
78
|
+
Data (SQLite database, uploaded files) lives in `./data/` by default. See [Configuration & Operations](docs/configuration.md) for daemon mode, TOML settings, and agent setup.
|
|
50
79
|
|
|
51
80
|
## Architecture
|
|
52
81
|
|
|
@@ -57,14 +86,14 @@ Browser ←── REST + SSE ──→ Server ←── ACP ──→ Agent CLI
|
|
|
57
86
|
|
|
58
87
|
The frontend is a standard browser client that talks to the server over REST + SSE. The API is the boundary — anyone can build their own client.
|
|
59
88
|
|
|
60
|
-
| Module
|
|
61
|
-
|
|
62
|
-
| `routes.ts`
|
|
63
|
-
| `event-handler.ts`
|
|
64
|
-
| `session-manager.ts` | Session state, buffers, bash processes
|
|
65
|
-
| `bridge.ts`
|
|
66
|
-
| `store.ts`
|
|
67
|
-
| `daemon.ts`
|
|
89
|
+
| Module | Role |
|
|
90
|
+
| -------------------- | ----------------------------------------------------------- |
|
|
91
|
+
| `routes.ts` | REST API + static files ([full API reference](docs/api.md)) |
|
|
92
|
+
| `event-handler.ts` | ACP event routing → SSE broadcast |
|
|
93
|
+
| `session-manager.ts` | Session state, buffers, bash processes |
|
|
94
|
+
| `bridge.ts` | ACP bridge — agent subprocess lifecycle |
|
|
95
|
+
| `store.ts` | SQLite persistence (WAL mode) |
|
|
96
|
+
| `daemon.ts` | Background service with crash recovery |
|
|
68
97
|
|
|
69
98
|
Tech stack: Node.js + TypeScript (`--experimental-strip-types`), SQLite (`better-sqlite3`), Zod validation, esbuild bundling.
|
|
70
99
|
|
|
@@ -72,12 +101,18 @@ Frontend source lives in `public/js/*.ts`, bundled by esbuild into a single cont
|
|
|
72
101
|
|
|
73
102
|
## Documentation
|
|
74
103
|
|
|
75
|
-
| Document
|
|
76
|
-
|
|
77
|
-
| **[Features](docs/features.md)**
|
|
78
|
-
| **[Configuration & Operations](docs/configuration.md)** | TOML config, daemon commands, agent setup, upgrading
|
|
79
|
-
| **[
|
|
80
|
-
| **[
|
|
81
|
-
| **[
|
|
82
|
-
| **[
|
|
83
|
-
| **[
|
|
104
|
+
| Document | Contents |
|
|
105
|
+
| ------------------------------------------------------- | ------------------------------------------------------------------------------ |
|
|
106
|
+
| **[Features](docs/features.md)** | Chat, attachments, bash, sessions, slash commands, keyboard shortcuts, themes |
|
|
107
|
+
| **[Configuration & Operations](docs/configuration.md)** | TOML config, daemon commands, agent setup, upgrading |
|
|
108
|
+
| **[Security](docs/security.md)** | Bearer auth, token storage, SSE ticket, signed image URLs, CSP, data layout |
|
|
109
|
+
| **[API Reference](docs/api.md)** | REST endpoints, SSE events, implementation details |
|
|
110
|
+
| **[Attachments](docs/uploads.md)** | Upload pipeline, on-disk layout, lifecycle, permission auto-approve, observability |
|
|
111
|
+
| **[ACP Integration](docs/acp.md)** | Client extensions, protocol scope, current limits |
|
|
112
|
+
| **[Client Architecture](docs/client-architecture.md)** | Frontend modules, data flow, conventions |
|
|
113
|
+
| **[Slash Menu](docs/slash-menu.md)** | Walker pipeline, `CmdNode` tree, Tab/Enter/Click contract, how to add commands |
|
|
114
|
+
| **[Messages / Inbox](docs/messages.md)** | `/inbox` slash command, POST ingress, bound vs unbound messages |
|
|
115
|
+
| **[Share Links](docs/share.md)** | Public read-only session snapshots via `/share` + `/s/<token>` |
|
|
116
|
+
| **[Database Schema](docs/schema.md)** | SQLite tables, indexes, FK policy, cascade/lifecycle rules, migrations |
|
|
117
|
+
| **[Development](docs/development.md)** | Building from source, dev mode, testing, publishing |
|
|
118
|
+
| **[Auto-Start on Boot](docs/autostart.md)** | launchd, systemd, crontab, Windows Task Scheduler |
|
package/bin/webagent.mjs
CHANGED
|
@@ -3,9 +3,121 @@
|
|
|
3
3
|
import { spawn } from "node:child_process";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { dirname, join } from "node:path";
|
|
6
|
+
import { copyFileSync, existsSync } from "node:fs";
|
|
6
7
|
|
|
7
8
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
9
|
|
|
10
|
+
// ---- Subcommand: `webagent config <init|show>` ------------------------------
|
|
11
|
+
//
|
|
12
|
+
// `config init` copies the package's bundled config.toml (the same
|
|
13
|
+
// well-commented file that documents every key + its default) to
|
|
14
|
+
// ./config.toml. Refuses to overwrite without --force. The bundled
|
|
15
|
+
// file is the single source of truth, so what users get always matches
|
|
16
|
+
// the schema (test/config-coverage.test.ts guards that alignment).
|
|
17
|
+
//
|
|
18
|
+
// `config show` dumps the effective merged configuration (defaults
|
|
19
|
+
// overlaid with whatever --config provided) as TOML to stdout. Useful
|
|
20
|
+
// for "what is actually in effect right now".
|
|
21
|
+
if (process.argv[2] === "config") {
|
|
22
|
+
const sub = process.argv[3];
|
|
23
|
+
if (sub === "init") {
|
|
24
|
+
const force = process.argv.includes("--force");
|
|
25
|
+
const src = fileURLToPath(new URL("../config.toml", import.meta.url));
|
|
26
|
+
const dst = join(process.cwd(), "config.toml");
|
|
27
|
+
if (existsSync(dst) && !force) {
|
|
28
|
+
console.error(
|
|
29
|
+
`config.toml already exists at ${dst}. Use --force to overwrite.`,
|
|
30
|
+
);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
copyFileSync(src, dst);
|
|
35
|
+
} catch (err) {
|
|
36
|
+
console.error(`Failed to write ${dst}:`, err.message ?? err);
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
console.log(`wrote ${dst}`);
|
|
40
|
+
console.log(`edit it, then run: webagent --config config.toml`);
|
|
41
|
+
process.exit(0);
|
|
42
|
+
}
|
|
43
|
+
if (sub === "show") {
|
|
44
|
+
// Silence loadConfig's [config] log so stdout contains only TOML.
|
|
45
|
+
const origLog = console.log;
|
|
46
|
+
console.log = () => {};
|
|
47
|
+
const cfgUrl = new URL("../lib/config.js", import.meta.url).href;
|
|
48
|
+
const tomlUrl = "smol-toml";
|
|
49
|
+
const { loadConfig } = await import(cfgUrl);
|
|
50
|
+
const { stringify } = await import(tomlUrl);
|
|
51
|
+
const cfg = loadConfig();
|
|
52
|
+
console.log = origLog;
|
|
53
|
+
process.stdout.write(stringify(cfg));
|
|
54
|
+
process.exit(0);
|
|
55
|
+
}
|
|
56
|
+
console.error("Usage: webagent config <init|show> [--force]");
|
|
57
|
+
process.exit(64);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ---- One-shot: --create-token <name> ---------------------------------------
|
|
61
|
+
//
|
|
62
|
+
// Provision an admin-scope token without launching the server. Run this
|
|
63
|
+
// once on first install (or when adding a new client device). The raw
|
|
64
|
+
// token is printed to stdout exactly once; we do not store it anywhere
|
|
65
|
+
// retrievable.
|
|
66
|
+
{
|
|
67
|
+
const argv = process.argv.slice(2);
|
|
68
|
+
const idx = argv.indexOf("--create-token");
|
|
69
|
+
if (idx !== -1) {
|
|
70
|
+
const name = argv[idx + 1];
|
|
71
|
+
if (!name || name.startsWith("--")) {
|
|
72
|
+
console.error("Usage: webagent --create-token <name> [--config <path>]");
|
|
73
|
+
process.exit(64);
|
|
74
|
+
}
|
|
75
|
+
const cfgIdx = argv.indexOf("--config");
|
|
76
|
+
if (cfgIdx !== -1 && argv[cfgIdx + 1]) {
|
|
77
|
+
// loadConfig() reads --config from process.argv directly
|
|
78
|
+
process.argv = [
|
|
79
|
+
process.argv[0],
|
|
80
|
+
process.argv[1],
|
|
81
|
+
"--config",
|
|
82
|
+
argv[cfgIdx + 1],
|
|
83
|
+
];
|
|
84
|
+
} else {
|
|
85
|
+
process.argv = [process.argv[0], process.argv[1]];
|
|
86
|
+
}
|
|
87
|
+
// Silence loadConfig's [config] log so stdout contains only the raw
|
|
88
|
+
// token (machine-parseable). Errors still go to stderr.
|
|
89
|
+
const origLog = console.log;
|
|
90
|
+
console.log = () => {};
|
|
91
|
+
const cfgUrl = new URL("../lib/config.js", import.meta.url).href;
|
|
92
|
+
const storeUrl = new URL("../lib/auth-store.js", import.meta.url).href;
|
|
93
|
+
const { loadConfig } = await import(cfgUrl);
|
|
94
|
+
const { AuthStore } = await import(storeUrl);
|
|
95
|
+
const cfg = loadConfig();
|
|
96
|
+
console.log = origLog;
|
|
97
|
+
const store = new AuthStore(join(cfg.data_dir, "auth.json"));
|
|
98
|
+
await store.load();
|
|
99
|
+
try {
|
|
100
|
+
const created = await store.addToken(name, "admin");
|
|
101
|
+
// First line: raw token (machine-readable). Followed by a newline so
|
|
102
|
+
// `webagent --create-token foo | tr -d '\n' | pbcopy` works.
|
|
103
|
+
process.stdout.write(created.token + "\n");
|
|
104
|
+
console.error(
|
|
105
|
+
`\nCreated token '${name}' (admin scope). Save it now — it will not be shown again.`,
|
|
106
|
+
);
|
|
107
|
+
console.error(
|
|
108
|
+
`If the server is already running, send SIGHUP so it picks up the new token:`,
|
|
109
|
+
);
|
|
110
|
+
console.error(` kill -HUP $(pgrep -f 'lib/server.js')`);
|
|
111
|
+
await store.close();
|
|
112
|
+
process.exit(0);
|
|
113
|
+
} catch (err) {
|
|
114
|
+
console.error("Failed to create token:", err.message ?? err);
|
|
115
|
+
await store.close().catch(() => {});
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
9
121
|
// ---- Service management subcommands (start/stop/status/restart) -----------
|
|
10
122
|
|
|
11
123
|
const SUBCOMMANDS = new Set(["start", "stop", "status", "restart"]);
|
|
@@ -19,15 +131,14 @@ if (cmd && SUBCOMMANDS.has(cmd)) {
|
|
|
19
131
|
// ---- Direct server launch (foreground) ----------------------------------
|
|
20
132
|
const server = join(__dirname, "..", "lib", "server.js");
|
|
21
133
|
|
|
22
|
-
const child = spawn(
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
{ stdio: "inherit" },
|
|
26
|
-
);
|
|
134
|
+
const child = spawn(process.execPath, [server, ...process.argv.slice(2)], {
|
|
135
|
+
stdio: "inherit",
|
|
136
|
+
});
|
|
27
137
|
|
|
28
|
-
const signals =
|
|
29
|
-
|
|
30
|
-
|
|
138
|
+
const signals =
|
|
139
|
+
process.platform === "win32"
|
|
140
|
+
? ["SIGINT", "SIGTERM"]
|
|
141
|
+
: ["SIGINT", "SIGTERM", "SIGHUP"];
|
|
31
142
|
for (const sig of signals) {
|
|
32
143
|
process.on(sig, () => child.kill(sig));
|
|
33
144
|
}
|
package/config.toml
CHANGED
|
@@ -13,16 +13,25 @@ data_dir = "data"
|
|
|
13
13
|
# Static assets directory (default: "dist")
|
|
14
14
|
public_dir = "dist"
|
|
15
15
|
|
|
16
|
-
# ACP agent command (binary + args, space-separated)
|
|
17
|
-
|
|
16
|
+
# ACP agent command (binary + args, space-separated).
|
|
17
|
+
# Default "auto" scans PATH for known ACP-ready agents (in priority order):
|
|
18
|
+
# copilot, gemini, opencode, claude-agent-acp, codex-acp, qwen
|
|
19
|
+
# Set explicitly to override:
|
|
20
|
+
# agent_cmd = "claude-agent-acp"
|
|
21
|
+
# agent_cmd = "codex-acp"
|
|
22
|
+
# agent_cmd = "gemini --acp"
|
|
23
|
+
agent_cmd = "auto"
|
|
18
24
|
|
|
19
25
|
[limits]
|
|
20
26
|
# Max bash output stored in DB per command (bytes, default 1 MB)
|
|
21
27
|
bash_output = 1_048_576
|
|
22
28
|
|
|
23
|
-
# Max image upload size (bytes, default 10 MB)
|
|
29
|
+
# Max image upload size (bytes, default 10 MB). Applies to image/* attachments.
|
|
24
30
|
image_upload = 10_485_760
|
|
25
31
|
|
|
32
|
+
# Max non-image upload size (bytes, default 50 MB). Applies to all other mime types.
|
|
33
|
+
file_upload = 52_428_800
|
|
34
|
+
|
|
26
35
|
# Cancel timeout (ms, default 10s). After sending cancel, if the agent
|
|
27
36
|
# does not respond within this time the UI resets to idle. Set to 0 to disable.
|
|
28
37
|
cancel_timeout = 10_000
|
|
@@ -35,3 +44,87 @@ recent_paths_ttl = 30
|
|
|
35
44
|
|
|
36
45
|
[push]
|
|
37
46
|
vapid_subject = "mailto:noreply@example.com"
|
|
47
|
+
# Kill switch for cross-device global visibility suppression. When true,
|
|
48
|
+
# if any client is actively viewing session X, push for session X is
|
|
49
|
+
# suppressed for every device. Flip to false to rollback the feature
|
|
50
|
+
# without a code change.
|
|
51
|
+
global_visibility_suppression = true
|
|
52
|
+
|
|
53
|
+
[title]
|
|
54
|
+
# Models matched (by case-insensitive substring) against the agent's
|
|
55
|
+
# reported availableModels for the title-generation sub-session. The first
|
|
56
|
+
# pattern that matches any model id wins; that model is set via
|
|
57
|
+
# setConfigOption. No match → skip the call and inherit the agent's
|
|
58
|
+
# default model (currentModelId).
|
|
59
|
+
#
|
|
60
|
+
# Default list targets the cheap/fast tier across major providers:
|
|
61
|
+
# - "haiku" → Anthropic (claude-haiku-*)
|
|
62
|
+
# - "flash-lite" → Google Gemini (gemini-*-flash-lite) [must precede "flash"]
|
|
63
|
+
# - "nano" → OpenAI (gpt-*-nano), Gemini Nano
|
|
64
|
+
# - "mini" → OpenAI (gpt-*-mini, 4o-mini), Mistral
|
|
65
|
+
# - "flash" → Google Gemini (gemini-*-flash)
|
|
66
|
+
# - "lite" → Cohere, generic
|
|
67
|
+
#
|
|
68
|
+
# Set model = [] to disable matching entirely and always inherit the
|
|
69
|
+
# agent's default model. To pin one specific model, use a single-element
|
|
70
|
+
# array, e.g. model = ["claude-haiku-4.5"].
|
|
71
|
+
model = ["haiku", "flash-lite", "nano", "mini", "flash", "lite"]
|
|
72
|
+
|
|
73
|
+
[debug]
|
|
74
|
+
# Frontend log level. One of: off | debug | info | warn | error.
|
|
75
|
+
# Default "off". Users can override per page-load via ?debug=<level> in
|
|
76
|
+
# the URL, or at runtime via the /debug slash command. level != off
|
|
77
|
+
# emits to both DevTools console and the conversation-flow DOM.
|
|
78
|
+
level = "off"
|
|
79
|
+
|
|
80
|
+
[messages]
|
|
81
|
+
# Days before an unprocessed unbound inbox message is auto-cleaned
|
|
82
|
+
# (default 30). 0 = keep forever. Bound messages (to:"session:<id>")
|
|
83
|
+
# are stored on the owning session and are not subject to this TTL.
|
|
84
|
+
unprocessed_ttl_days = 30
|
|
85
|
+
|
|
86
|
+
[share]
|
|
87
|
+
# Public read-only session share links. Default off. Dogfood manually
|
|
88
|
+
# flips `enabled = true` after CF Access bypass + Rate Limiting are
|
|
89
|
+
# configured. See docs/share.md.
|
|
90
|
+
|
|
91
|
+
# Master kill switch. When false, all /api/v1/sessions/*/share* and
|
|
92
|
+
# /s/* routes return 410, and /share slash commands are hidden.
|
|
93
|
+
enabled = false
|
|
94
|
+
|
|
95
|
+
# Global default TTL for public share links (hours). 0 = never expire
|
|
96
|
+
# (default). Values >0 are clamped to 168 (7d) at startup with a WARN.
|
|
97
|
+
# Per-share overrides live in the shares.ttl_hours column.
|
|
98
|
+
ttl_hours = 0
|
|
99
|
+
|
|
100
|
+
# true emits Content-Security-Policy on /s/* + /api/v1/shared/*.
|
|
101
|
+
# Set false to fall back to Content-Security-Policy-Report-Only as an
|
|
102
|
+
# emergency kill switch without touching code.
|
|
103
|
+
csp_enforce = true
|
|
104
|
+
|
|
105
|
+
# Public viewer origin; null = same as webagent host. Set when viewer is
|
|
106
|
+
# fronted by a separate CF Worker route (e.g. "https://share.example.com").
|
|
107
|
+
viewer_origin = ""
|
|
108
|
+
|
|
109
|
+
# Sanitizer internal-domain allowlist; substrings matched (case-insensitive)
|
|
110
|
+
# get rewritten to `<internal-host>` before publishing. Example:
|
|
111
|
+
# internal_hosts = ["corp.example.com", "internal-db"]
|
|
112
|
+
internal_hosts = []
|
|
113
|
+
|
|
114
|
+
[auth]
|
|
115
|
+
# Bearer-token auth knobs.
|
|
116
|
+
#
|
|
117
|
+
# first_run_bootstrap controls the zero-config first-run UX. When true
|
|
118
|
+
# (default), if auth.json does NOT exist AND stdin is a TTY, the server
|
|
119
|
+
# auto-mints a one-time admin token and prints it as part of the
|
|
120
|
+
# startup-doctor stream. Operator copies the token from terminal
|
|
121
|
+
# scrollback and pastes it into the /login form.
|
|
122
|
+
#
|
|
123
|
+
# Set to false when deploying behind a supervisor that provisions auth.json
|
|
124
|
+
# out-of-band (CI, Ansible, k8s init container) — server falls back to
|
|
125
|
+
# "exit 78 + use --create-token" behavior.
|
|
126
|
+
#
|
|
127
|
+
# Note: this only kicks in for missing auth.json. If the file exists but
|
|
128
|
+
# the token list is empty (manual edit, parse failure), server still exits
|
|
129
|
+
# 78 — that's a config anomaly, not a fresh install.
|
|
130
|
+
first_run_bootstrap = true
|
package/dist/index.html
CHANGED
|
@@ -1,47 +1,70 @@
|
|
|
1
|
-
<!
|
|
1
|
+
<!doctype html>
|
|
2
2
|
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="utf-8"
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1"
|
|
6
|
-
<title>>_</title>
|
|
7
|
-
<link rel="icon" href="/icons/icon.svg" type="image/svg+xml"
|
|
8
|
-
<link rel="apple-touch-icon" href="/icons/icon-180.png"
|
|
9
|
-
<link rel="manifest" href="/manifest.json"
|
|
10
|
-
<meta
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
<
|
|
16
|
-
|
|
17
|
-
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>>_</title>
|
|
7
|
+
<link rel="icon" href="/icons/icon.svg" type="image/svg+xml" />
|
|
8
|
+
<link rel="apple-touch-icon" href="/icons/icon-180.png" />
|
|
9
|
+
<link rel="manifest" href="/manifest.json" />
|
|
10
|
+
<meta
|
|
11
|
+
name="theme-color"
|
|
12
|
+
content="#0d1117"
|
|
13
|
+
media="(prefers-color-scheme: dark)"
|
|
14
|
+
/>
|
|
15
|
+
<meta
|
|
16
|
+
name="theme-color"
|
|
17
|
+
content="#ffffff"
|
|
18
|
+
media="(prefers-color-scheme: light)"
|
|
19
|
+
/>
|
|
20
|
+
<meta name="mobile-web-app-capable" content="yes" />
|
|
21
|
+
<!-- Keep apple-prefixed version: iOS Safari still only recognizes this one
|
|
22
|
+
for standalone PWA mode. Chrome's deprecation warning is satisfied by
|
|
23
|
+
the standard version above. Remove once iOS supports the standard. -->
|
|
24
|
+
<meta name="apple-mobile-web-app-capable" content="yes" />
|
|
25
|
+
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
|
|
26
|
+
<script src="/theme-init.js"></script>
|
|
27
|
+
<link rel="stylesheet" href="/styles.012p32dz.css" />
|
|
28
|
+
<link rel="modulepreload" href="/js/chunk.D4ZYHJAM.js">
|
|
29
|
+
<link rel="modulepreload" href="/js/chunk.CGWFHJI2.js">
|
|
30
|
+
<link rel="modulepreload" href="/js/chunk.AJZBJBMO.js">
|
|
18
31
|
</head>
|
|
19
|
-
<body>
|
|
32
|
+
<body>
|
|
33
|
+
<div id="header">
|
|
34
|
+
<div class="header-side header-left">
|
|
35
|
+
<span class="logo">>_</span>
|
|
36
|
+
</div>
|
|
37
|
+
<span id="session-info" class="status"></span>
|
|
38
|
+
<div class="header-side header-right">
|
|
39
|
+
<span
|
|
40
|
+
id="status"
|
|
41
|
+
class="status-dot is-disconnected"
|
|
42
|
+
data-state="disconnected"
|
|
43
|
+
role="status"
|
|
44
|
+
aria-live="polite"
|
|
45
|
+
aria-label="disconnected"
|
|
46
|
+
title="disconnected"
|
|
47
|
+
></span>
|
|
48
|
+
<button id="theme-btn" title="Toggle theme">◑</button>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
20
51
|
|
|
21
|
-
<div id="
|
|
22
|
-
<div class="header-side header-left">
|
|
23
|
-
<span class="logo">>_</span>
|
|
24
|
-
</div>
|
|
25
|
-
<span id="session-info" class="status"></span>
|
|
26
|
-
<div class="header-side header-right">
|
|
27
|
-
<span id="status" class="status-dot is-disconnected" data-state="disconnected" role="status" aria-live="polite" aria-label="disconnected" title="disconnected"></span>
|
|
28
|
-
<button id="theme-btn" title="Toggle theme">◑</button>
|
|
29
|
-
</div>
|
|
30
|
-
</div>
|
|
52
|
+
<div id="messages"></div>
|
|
31
53
|
|
|
32
|
-
<div id="
|
|
54
|
+
<div id="attach-preview"></div>
|
|
55
|
+
<div id="input-area">
|
|
56
|
+
<div id="slash-menu"></div>
|
|
57
|
+
<span id="mode-pill"></span>
|
|
58
|
+
<span id="input-prompt" title="Cycle mode (Ctrl+M)"></span>
|
|
59
|
+
<textarea id="input" rows="1" placeholder="Message or ?"></textarea>
|
|
60
|
+
<button id="attach-btn" class="input-btn" title="Attach image (Ctrl+U)">
|
|
61
|
+
^U
|
|
62
|
+
</button>
|
|
63
|
+
<button id="send-btn" class="input-btn" title="Send (Enter)">↵</button>
|
|
64
|
+
<input type="file" id="file-input" multiple hidden />
|
|
65
|
+
</div>
|
|
66
|
+
<div id="status-bar"></div>
|
|
33
67
|
|
|
34
|
-
<
|
|
35
|
-
|
|
36
|
-
<div id="slash-menu"></div>
|
|
37
|
-
<span id="input-prompt" title="Cycle mode (Ctrl+M)">❯ </span>
|
|
38
|
-
<textarea id="input" rows="1" placeholder="Message or ?"></textarea>
|
|
39
|
-
<button id="attach-btn" class="input-btn" title="Attach image (Ctrl+U)">^U</button>
|
|
40
|
-
<button id="send-btn" class="input-btn" title="Send (Enter)">↵</button>
|
|
41
|
-
<input type="file" id="file-input" accept="image/*" multiple hidden>
|
|
42
|
-
</div>
|
|
43
|
-
<div id="status-bar"></div>
|
|
44
|
-
|
|
45
|
-
<script src="/js/app.2562YGRO.js"></script>
|
|
46
|
-
</body>
|
|
68
|
+
<script type="module" src="/js/app.GSAIYHML.js"></script>
|
|
69
|
+
</body>
|
|
47
70
|
</html>
|