claw-insights 0.1.0 → 0.1.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/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Claw Insights Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<img src="packages/web/public/logo/icon-dark.svg" width="80" alt="Claw Insights" />
|
|
3
|
+
<h1>Claw Insights</h1>
|
|
4
|
+
<p><strong>Replay, metrics, logs & shareable snapshots for <a href="https://github.com/openclaw/openclaw">OpenClaw</a> agents</strong></p>
|
|
5
|
+
<p>
|
|
6
|
+
<img src="https://img.shields.io/badge/%F0%9F%94%8C_Zero_Intrusion-read--only_sidecar-10b981" alt="Zero Intrusion" />
|
|
7
|
+
<img src="https://img.shields.io/badge/%F0%9F%94%8D_Full_Replay-session_transcripts-6366f1" alt="Full Replay" />
|
|
8
|
+
<img src="https://img.shields.io/badge/%F0%9F%93%B8_Shareable_Snapshots-PNG_%7C_SVG-f59e0b" alt="Shareable Snapshots" />
|
|
9
|
+
</p>
|
|
10
|
+
<p>
|
|
11
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT" /></a>
|
|
12
|
+
<a href="https://nodejs.org"><img src="https://img.shields.io/badge/Node.js-%E2%89%A522.5-green" alt="Node.js" /></a>
|
|
13
|
+
<img src="https://img.shields.io/badge/macOS-supported-blue" alt="macOS" />
|
|
14
|
+
<img src="https://img.shields.io/badge/Linux-supported-blue" alt="Linux" />
|
|
15
|
+
</p>
|
|
16
|
+
<br />
|
|
17
|
+
<img src="docs/assets/hero-montage.png" width="100%" alt="Dashboard, Session Transcript, and Snapshot API" />
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
<p align="center">
|
|
23
|
+
<strong>English</strong> ·
|
|
24
|
+
<a href="README.zh-CN.md">中文</a>
|
|
25
|
+
</p>
|
|
26
|
+
|
|
27
|
+
## Features
|
|
28
|
+
|
|
29
|
+
- **Zero Intrusion** — Pure read-only sidecar; no code changes, no cloud calls, data never leaves your machine
|
|
30
|
+
- **Session Replay** — Full transcript timeline with role separation, tool calls, and per-turn token tracking
|
|
31
|
+
- **Shareable Snapshots** — Generate PNG/SVG status cards via REST API with themes, languages, and detail levels
|
|
32
|
+
- **Metrics Dashboard** — Per-model token breakdown, error rates, and uptime over 30m / 1h / 6h / 12h / 24h
|
|
33
|
+
- **Event Logs** — Structured viewer with density heatmap, filtering, and search
|
|
34
|
+
- **One Command Setup** — Auto-discovers your running gateway, lightweight SQLite storage
|
|
35
|
+
- **Dark / Light · EN / 中文** — Full theming and i18n with runtime toggle
|
|
36
|
+
|
|
37
|
+
## Quick Start
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# Install
|
|
41
|
+
npm install -g claw-insights
|
|
42
|
+
|
|
43
|
+
# Start (auto-connects to your running OpenClaw gateway)
|
|
44
|
+
claw-insights start
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
On launch you'll see an access URL:
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
✅ Claw Insights v0.1.0 ready in 1.2s
|
|
51
|
+
|
|
52
|
+
➜ Open: http://127.0.0.1:41041/?token=abc123...
|
|
53
|
+
Auth: token (auto-generated)
|
|
54
|
+
|
|
55
|
+
PID 12345 · daemon · Port 41041
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Open the URL — token is exchanged for a session cookie, and you're in.
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
claw-insights status # Show current access URL
|
|
62
|
+
claw-insights stop # Stop daemon
|
|
63
|
+
claw-insights start --no-auth # Disable authentication
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
→ Full install options, snapshot API, and troubleshooting: [docs/configuration.md](docs/configuration.md)
|
|
67
|
+
|
|
68
|
+
## 🤖 AI Agent Friendly
|
|
69
|
+
|
|
70
|
+
Ships with structured resources for AI agents — see **[AGENTS.md](AGENTS.md)** for the full index:
|
|
71
|
+
|
|
72
|
+
| Skill | Use case |
|
|
73
|
+
| ----------------------------------------- | ------------------------------------------------- |
|
|
74
|
+
| [install](docs/skills/install/SKILL.md) | Install, configure, and launch |
|
|
75
|
+
| [snapshot](docs/skills/snapshot/SKILL.md) | Capture dashboard as PNG/SVG/JSON via REST or CLI |
|
|
76
|
+
|
|
77
|
+
## Architecture
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
claw-insights/
|
|
81
|
+
├── packages/
|
|
82
|
+
│ ├── server/ Express + GraphQL Yoga + SQLite + Satori renderer
|
|
83
|
+
│ ├── web/ React 19 + Vite + Tailwind + ECharts + urql
|
|
84
|
+
│ └── shared/ Codegen TypeScript types (shared between server & web)
|
|
85
|
+
├── bin/ CLI entry (start/stop/restart/status/logs/snapshot/run)
|
|
86
|
+
└── codegen.ts GraphQL codegen config (3 targets)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**Data flow:** OpenClaw gateway → log tailing + CLI → SQLite → GraphQL (SSE subscriptions) → React
|
|
90
|
+
|
|
91
|
+
→ Full architecture, dev setup, and codegen: [docs/architecture.md](docs/architecture.md)
|
|
92
|
+
|
|
93
|
+
## Documentation
|
|
94
|
+
|
|
95
|
+
| Document | Description |
|
|
96
|
+
| -------------------------------------- | ------------------------------------- |
|
|
97
|
+
| [Configuration](docs/configuration.md) | All env vars, config file, auth model |
|
|
98
|
+
| [Architecture](docs/architecture.md) | System design, dev setup, testing |
|
|
99
|
+
| [API Reference](docs/api-reference.md) | GraphQL + REST endpoint signatures |
|
|
100
|
+
| [AGENTS.md](AGENTS.md) | AI agent skill index |
|
|
101
|
+
|
|
102
|
+
## Contributing
|
|
103
|
+
|
|
104
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, PR guidelines, and code conventions.
|
|
105
|
+
|
|
106
|
+
## Security
|
|
107
|
+
|
|
108
|
+
See [SECURITY.md](SECURITY.md) for vulnerability reporting and security model.
|
|
109
|
+
|
|
110
|
+
## License
|
|
111
|
+
|
|
112
|
+
[MIT](LICENSE)
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claw-insights",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Real-time monitoring dashboard for OpenClaw gateway",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"claw-insights": "./bin/claw-insights"
|
|
8
8
|
},
|
|
9
|
-
"files": ["bin/", "server/", "web/", "assets/"],
|
|
9
|
+
"files": ["bin/", "server/", "web/", "assets/", "README.md", "LICENSE"],
|
|
10
10
|
"engines": { "node": ">=22" },
|
|
11
11
|
"license": "MIT",
|
|
12
12
|
"author": "Luca Liao",
|
|
@@ -12,6 +12,11 @@ import { execFile } from "child_process";
|
|
|
12
12
|
import { promisify } from "util";
|
|
13
13
|
var execFileAsync = promisify(execFile);
|
|
14
14
|
var log = createChildLogger("cli-adapter");
|
|
15
|
+
var DEFAULT_TIMEOUT_MS = 8e3;
|
|
16
|
+
var STATUS_JSON_TIMEOUT_MS = 15e3;
|
|
17
|
+
function resolveTimeoutMs(argv) {
|
|
18
|
+
return argv[0] === "status" && argv.includes("--json") ? STATUS_JSON_TIMEOUT_MS : DEFAULT_TIMEOUT_MS;
|
|
19
|
+
}
|
|
15
20
|
var PosixCliAdapter = class {
|
|
16
21
|
constructor(cliPath, env) {
|
|
17
22
|
this.cliPath = cliPath;
|
|
@@ -21,7 +26,7 @@ var PosixCliAdapter = class {
|
|
|
21
26
|
try {
|
|
22
27
|
log.debug({ argv }, "CLI exec");
|
|
23
28
|
const { stdout, stderr: _stderr } = await execFileAsync(this.cliPath, argv, {
|
|
24
|
-
timeout:
|
|
29
|
+
timeout: resolveTimeoutMs(argv),
|
|
25
30
|
encoding: "utf-8",
|
|
26
31
|
env: this.env
|
|
27
32
|
});
|
package/server/index.js
CHANGED
|
@@ -1594,9 +1594,9 @@ async function loadPlatform() {
|
|
|
1594
1594
|
log7.info({ os }, "loading platform adapter");
|
|
1595
1595
|
switch (os) {
|
|
1596
1596
|
case "darwin":
|
|
1597
|
-
return (await import("./darwin-
|
|
1597
|
+
return (await import("./darwin-IIBM6CQD.js")).platform;
|
|
1598
1598
|
case "linux":
|
|
1599
|
-
return (await import("./linux-
|
|
1599
|
+
return (await import("./linux-ZEULX5H5.js")).platform;
|
|
1600
1600
|
default:
|
|
1601
1601
|
throw new Error(`Unsupported platform: ${os}`);
|
|
1602
1602
|
}
|
|
@@ -3513,6 +3513,7 @@ var CACHE_TTL = 1e4;
|
|
|
3513
3513
|
var FAIL_CACHE_TTL = 3e3;
|
|
3514
3514
|
var VERSION_CACHE_TTL = 6e4;
|
|
3515
3515
|
var VERSION_FAIL_CACHE_TTL = 5e3;
|
|
3516
|
+
var STATUS_STALE_FALLBACK_MS = 3e4;
|
|
3516
3517
|
function createGatewayClient(platform, options) {
|
|
3517
3518
|
const logPath = options?.gatewayLogPath ?? config.openclawDir + "/logs/gateway.log";
|
|
3518
3519
|
async function getStartedAtFromLog(pid) {
|
|
@@ -3551,6 +3552,7 @@ function createGatewayClient(platform, options) {
|
|
|
3551
3552
|
let versionCache = null;
|
|
3552
3553
|
let statusInFlight = null;
|
|
3553
3554
|
let lastStatusJson = "";
|
|
3555
|
+
let lastSuccessfulStatus = null;
|
|
3554
3556
|
async function getVersion() {
|
|
3555
3557
|
if (isCacheValid(versionCache)) {
|
|
3556
3558
|
return versionCache.data;
|
|
@@ -3561,58 +3563,83 @@ function createGatewayClient(platform, options) {
|
|
|
3561
3563
|
return version;
|
|
3562
3564
|
}
|
|
3563
3565
|
async function parseStatusJson(json, version) {
|
|
3566
|
+
if (json.trim() === "") {
|
|
3567
|
+
return { ok: false, reason: "empty" };
|
|
3568
|
+
}
|
|
3569
|
+
let d;
|
|
3570
|
+
try {
|
|
3571
|
+
d = JSON.parse(json);
|
|
3572
|
+
} catch {
|
|
3573
|
+
return { ok: false, reason: "invalid-json" };
|
|
3574
|
+
}
|
|
3564
3575
|
try {
|
|
3565
|
-
const
|
|
3566
|
-
const
|
|
3567
|
-
const
|
|
3568
|
-
const
|
|
3569
|
-
const
|
|
3570
|
-
const
|
|
3576
|
+
const gw = d.gateway ?? {};
|
|
3577
|
+
const svc = d.gatewayService ?? {};
|
|
3578
|
+
const channelSummary = d.channelSummary ?? [];
|
|
3579
|
+
const update = d.update ?? {};
|
|
3580
|
+
const updateRegistry = update.registry ?? {};
|
|
3581
|
+
const securityAudit = d.securityAudit ?? {};
|
|
3582
|
+
const secAudit = securityAudit.summary ?? { critical: 0, warn: 0, info: 0 };
|
|
3583
|
+
const sessions = d.sessions ?? {};
|
|
3584
|
+
const runtimeShort = typeof svc.runtimeShort === "string" ? svc.runtimeShort : "";
|
|
3585
|
+
const pidMatch = runtimeShort.match(/pid\s+(\d+)/);
|
|
3571
3586
|
let pid = pidMatch ? Number(pidMatch[1]) : null;
|
|
3572
|
-
const running =
|
|
3587
|
+
const running = gw.reachable === true || runtimeShort.includes("running");
|
|
3573
3588
|
if (!pid && running) {
|
|
3574
|
-
|
|
3589
|
+
const port = typeof gw.port === "number" ? gw.port : 18789;
|
|
3590
|
+
pid = await platform.process.findPidByPort(port);
|
|
3575
3591
|
}
|
|
3576
|
-
const
|
|
3592
|
+
const latestFromRegistry = typeof updateRegistry.latestVersion === "string" ? updateRegistry.latestVersion : null;
|
|
3593
|
+
const latestFromUpdate = typeof update.latestVersion === "string" ? update.latestVersion : null;
|
|
3594
|
+
const latest = latestFromRegistry ?? latestFromUpdate;
|
|
3577
3595
|
const updateAvailable = latest && latest !== version ? latest : null;
|
|
3578
|
-
const connectLatencyMs = gw
|
|
3579
|
-
const latestVersion =
|
|
3580
|
-
const
|
|
3581
|
-
const sessionDefaults =
|
|
3596
|
+
const connectLatencyMs = typeof gw.connectLatencyMs === "number" ? gw.connectLatencyMs : null;
|
|
3597
|
+
const latestVersion = latestFromRegistry ?? latestFromUpdate;
|
|
3598
|
+
const rawSessionDefaults = sessions.defaults;
|
|
3599
|
+
const sessionDefaults = rawSessionDefaults && typeof rawSessionDefaults.model === "string" && typeof rawSessionDefaults.contextTokens === "number" ? { model: rawSessionDefaults.model, contextTokens: rawSessionDefaults.contextTokens } : null;
|
|
3600
|
+
const critical = typeof secAudit.critical === "number" ? secAudit.critical : 0;
|
|
3601
|
+
const warn = typeof secAudit.warn === "number" ? secAudit.warn : 0;
|
|
3602
|
+
const info = typeof secAudit.info === "number" ? secAudit.info : 0;
|
|
3582
3603
|
const startedAt = pid ? await getStartedAtFromLog(pid) : null;
|
|
3583
3604
|
return {
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3605
|
+
ok: true,
|
|
3606
|
+
data: {
|
|
3607
|
+
running,
|
|
3608
|
+
pid,
|
|
3609
|
+
version: version ?? "unknown",
|
|
3610
|
+
updateAvailable,
|
|
3611
|
+
uptime: pid ? await platform.process.getUptime(pid) : "unknown",
|
|
3612
|
+
startedAt,
|
|
3613
|
+
channels: parseChannels(channelSummary),
|
|
3614
|
+
connectLatencyMs,
|
|
3615
|
+
latestVersion,
|
|
3616
|
+
securitySummary: {
|
|
3617
|
+
critical,
|
|
3618
|
+
warn,
|
|
3619
|
+
info
|
|
3620
|
+
},
|
|
3621
|
+
sessionDefaults
|
|
3622
|
+
}
|
|
3599
3623
|
};
|
|
3600
3624
|
} catch {
|
|
3601
|
-
return {
|
|
3602
|
-
running: false,
|
|
3603
|
-
pid: null,
|
|
3604
|
-
version: "unknown",
|
|
3605
|
-
updateAvailable: null,
|
|
3606
|
-
uptime: "unknown",
|
|
3607
|
-
startedAt: null,
|
|
3608
|
-
channels: [],
|
|
3609
|
-
connectLatencyMs: null,
|
|
3610
|
-
latestVersion: null,
|
|
3611
|
-
securitySummary: { critical: 0, warn: 0, info: 0 },
|
|
3612
|
-
sessionDefaults: null
|
|
3613
|
-
};
|
|
3625
|
+
return { ok: false, reason: "invalid-json" };
|
|
3614
3626
|
}
|
|
3615
3627
|
}
|
|
3628
|
+
function unavailableStatus() {
|
|
3629
|
+
return {
|
|
3630
|
+
running: false,
|
|
3631
|
+
pid: null,
|
|
3632
|
+
version: "unknown",
|
|
3633
|
+
updateAvailable: null,
|
|
3634
|
+
uptime: "unknown",
|
|
3635
|
+
startedAt: null,
|
|
3636
|
+
channels: [],
|
|
3637
|
+
connectLatencyMs: null,
|
|
3638
|
+
latestVersion: null,
|
|
3639
|
+
securitySummary: { critical: 0, warn: 0, info: 0 },
|
|
3640
|
+
sessionDefaults: null
|
|
3641
|
+
};
|
|
3642
|
+
}
|
|
3616
3643
|
async function getGatewayStatus() {
|
|
3617
3644
|
if (isCacheValid(statusCache)) {
|
|
3618
3645
|
return statusCache.data;
|
|
@@ -3623,9 +3650,24 @@ function createGatewayClient(platform, options) {
|
|
|
3623
3650
|
statusInFlight = (async () => {
|
|
3624
3651
|
try {
|
|
3625
3652
|
const [raw, version] = await Promise.all([platform.cli.exec(["status", "--json"]), getVersion()]);
|
|
3626
|
-
const
|
|
3653
|
+
const parsed = await parseStatusJson(raw, version);
|
|
3654
|
+
let status;
|
|
3655
|
+
let ttl;
|
|
3656
|
+
if (parsed.ok) {
|
|
3657
|
+
status = parsed.data;
|
|
3658
|
+
lastSuccessfulStatus = { data: status, ts: Date.now() };
|
|
3659
|
+
ttl = status.running ? CACHE_TTL : FAIL_CACHE_TTL;
|
|
3660
|
+
} else {
|
|
3661
|
+
const staleAge = lastSuccessfulStatus ? Date.now() - lastSuccessfulStatus.ts : Number.POSITIVE_INFINITY;
|
|
3662
|
+
if (lastSuccessfulStatus && staleAge <= STATUS_STALE_FALLBACK_MS) {
|
|
3663
|
+
status = lastSuccessfulStatus.data;
|
|
3664
|
+
ttl = FAIL_CACHE_TTL;
|
|
3665
|
+
} else {
|
|
3666
|
+
status = unavailableStatus();
|
|
3667
|
+
ttl = FAIL_CACHE_TTL;
|
|
3668
|
+
}
|
|
3669
|
+
}
|
|
3627
3670
|
const curJson = JSON.stringify(status);
|
|
3628
|
-
const ttl = status.running ? CACHE_TTL : FAIL_CACHE_TTL;
|
|
3629
3671
|
statusCache = { data: status, ts: Date.now(), ttl };
|
|
3630
3672
|
if (curJson !== lastStatusJson) {
|
|
3631
3673
|
lastStatusJson = curJson;
|