runtime-inspector 0.1.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/LICENSE +21 -0
- package/README.md +278 -0
- package/bin/cli.js +129 -0
- package/package.json +49 -0
- package/src/agent/actions.js +157 -0
- package/src/agent/attentionInfer.js +149 -0
- package/src/agent/dashboard.js +1178 -0
- package/src/agent/detector.js +235 -0
- package/src/agent/explainer.js +137 -0
- package/src/agent/grouper.js +161 -0
- package/src/agent/index.js +233 -0
- package/src/agent/progressInfer.js +46 -0
- package/src/agent/purposer.js +253 -0
- package/src/agent/repoActivity.js +142 -0
- package/src/agent/scanner.js +117 -0
- package/src/agent/shellEvents.js +115 -0
- package/src/agent/shellMerge.js +103 -0
- package/src/agent/stateInfer.js +72 -0
- package/src/agent/tmux.js +210 -0
- package/src/agent/tmuxMerge.js +96 -0
- package/src/shell/hooks.js +181 -0
- package/src/shell/setup.js +85 -0
- package/src/wrapper/buffer.js +34 -0
- package/src/wrapper/runner.js +149 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Asaf Erdman
|
|
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,278 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://img.shields.io/badge/node-%3E%3D18-brightgreen" alt="Node.js 18+" />
|
|
3
|
+
<img src="https://img.shields.io/badge/platform-macOS%20%7C%20Linux-blue" alt="macOS | Linux" />
|
|
4
|
+
<img src="https://img.shields.io/badge/license-MIT-green" alt="MIT License" />
|
|
5
|
+
<img src="https://img.shields.io/badge/status-coming%20soon-orange" alt="Coming Soon" />
|
|
6
|
+
</p>
|
|
7
|
+
|
|
8
|
+
<h1 align="center">RuntimeInspector</h1>
|
|
9
|
+
|
|
10
|
+
<p align="center">
|
|
11
|
+
<strong>See what your AI agents are really doing.</strong><br/>
|
|
12
|
+
A local CLI tool that scans your processes, groups them into sessions,<br/>
|
|
13
|
+
and serves a real-time dashboard. Install from npm. Runs on localhost. No cloud.
|
|
14
|
+
</p>
|
|
15
|
+
|
|
16
|
+
<br/>
|
|
17
|
+
|
|
18
|
+
<p align="center">
|
|
19
|
+
<img width="720" src="https://github.com/user-attachments/assets/placeholder-dashboard.png" alt="RuntimeInspector Dashboard" />
|
|
20
|
+
</p>
|
|
21
|
+
|
|
22
|
+
> **Note:** `runtime-inspector` is not yet published to npm. Star this repo to get notified when it drops.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Why
|
|
27
|
+
|
|
28
|
+
Modern dev workflows spawn dozens of invisible processes — AI agents, dev servers, Docker containers, test watchers. Without visibility, you're flying blind.
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
$ ps aux | grep node
|
|
32
|
+
# 47 lines of mystery
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
RuntimeInspector replaces that chaos with a single, organized dashboard:
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
┌─────────────────────────────────────────────────────────┐
|
|
39
|
+
│ RuntimeInspector · localhost:7331 6 sessions │
|
|
40
|
+
├─────────────────────────────────────────────────────────┤
|
|
41
|
+
│ │
|
|
42
|
+
│ ● Claude Code — Refactor auth acme/web-app │
|
|
43
|
+
│ PID 48201 + 3 children · 18m · CPU 12% · 340 MB │
|
|
44
|
+
│ Spawned 3 child shells; editing 12 files in │
|
|
45
|
+
│ src/auth/. Dev server on :3000. │
|
|
46
|
+
│ │
|
|
47
|
+
│ ● Codex — Generate API tests acme/api-service │
|
|
48
|
+
│ PID 48305 + 8 children · 42m · CPU 68% · 1.2 GB │
|
|
49
|
+
│ ⚠ HIGH CPU — Jest watch mode, 23 suites in parallel │
|
|
50
|
+
│ │
|
|
51
|
+
│ ● Vite Dev Server acme/dashboard-ui │
|
|
52
|
+
│ PID 47102 · 2h 14m · CPU 3% · 180 MB │
|
|
53
|
+
│ ⚠ LONG-RUNNING — No file changes in 47 min │
|
|
54
|
+
│ │
|
|
55
|
+
│ ● Express API — payments acme/payments │
|
|
56
|
+
│ PID 44089 · 3h 02m · CPU 1% · 95 MB │
|
|
57
|
+
│ ⚠ ORPHAN — Parent gone. Still bound to :4000. │
|
|
58
|
+
│ │
|
|
59
|
+
│ ● Docker — Postgres + Redis acme/infra │
|
|
60
|
+
│ PID 43998 + 2 containers · 4h 50m · 520 MB │
|
|
61
|
+
│ postgres:16 on :5432, redis:7 on :6379. Healthy. │
|
|
62
|
+
│ │
|
|
63
|
+
│ ● Cursor Agent — Fix CI pipeline acme/deploy-config │
|
|
64
|
+
│ PID 49120 · 7m · CPU 22% · 410 MB │
|
|
65
|
+
│ Reading .github/workflows/. Modifying deploy.yml. │
|
|
66
|
+
│ │
|
|
67
|
+
└─────────────────────────────────────────────────────────┘
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Quick Start
|
|
73
|
+
|
|
74
|
+
### 1. Install globally
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
npm install -g runtime-inspector
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### 2. Add shell integration (optional)
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
runtime-inspector setup --yes
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
This installs lightweight `preexec`/`precmd` hooks into your shell (zsh, bash, or fish) for command-level tracking. Restart your terminal after setup.
|
|
87
|
+
|
|
88
|
+
### 3. Start the dashboard
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
runtime-inspector start --open
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Your browser opens `http://localhost:7331` with a live view of everything running on your machine — grouped, explained, and scored.
|
|
95
|
+
|
|
96
|
+
### Bonus: wrap a command
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
runtime-inspector run -- npm run dev
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Wraps any command for deeper output tracking and richer session metadata.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## How It Works
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
|
110
|
+
│ 1. SCAN │────▶│ 2. GROUP │────▶│ 3. EXPLAIN │
|
|
111
|
+
│ │ │ │ │ │
|
|
112
|
+
│ Read process │ │ Cluster by │ │ Plain-English│
|
|
113
|
+
│ tree: PIDs, │ │ agent, repo, │ │ summaries + │
|
|
114
|
+
│ commands, │ │ runtime into │ │ risk signals │
|
|
115
|
+
│ ports, CPU │ │ sessions │ │ per session │
|
|
116
|
+
└──────────────┘ └──────────────┘ └──────────────┘
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**Scan** — Reads your process tree to identify CLIs, agents, servers, and their children.
|
|
120
|
+
|
|
121
|
+
**Group** — Clusters related processes by agent, repository, and runtime into coherent sessions.
|
|
122
|
+
|
|
123
|
+
**Explain** — Each session gets a summary and risk signals: long-running, orphaned, duplicated, or resource-heavy.
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## CLI Reference
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
runtime-inspector start [--open] Start agent + dashboard
|
|
131
|
+
runtime-inspector run -- <command> Run a command with output tracking
|
|
132
|
+
runtime-inspector init [--shell zsh] Print shell hook script to stdout
|
|
133
|
+
runtime-inspector setup [--yes] Install shell integration into rc file
|
|
134
|
+
runtime-inspector --help Show help
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Setup options
|
|
138
|
+
|
|
139
|
+
| Flag | Description |
|
|
140
|
+
|---|---|
|
|
141
|
+
| `--shell zsh\|bash` | Override shell detection |
|
|
142
|
+
| `--rc <path>` | Override rc file path |
|
|
143
|
+
| `--yes` | Skip confirmation prompt |
|
|
144
|
+
| `--open`, `-o` | Open browser after starting |
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## What Gets Detected
|
|
149
|
+
|
|
150
|
+
### AI Agents & Coding Tools
|
|
151
|
+
| Tool | Status |
|
|
152
|
+
|---|---|
|
|
153
|
+
| Claude Code | Supported |
|
|
154
|
+
| OpenAI Codex CLI | Supported |
|
|
155
|
+
| Cursor Agent | Supported |
|
|
156
|
+
| Aider | Supported |
|
|
157
|
+
| GitHub Copilot CLI | Supported |
|
|
158
|
+
| Continue | Supported |
|
|
159
|
+
|
|
160
|
+
### Frameworks & Runtimes
|
|
161
|
+
| Runtime | Status |
|
|
162
|
+
|---|---|
|
|
163
|
+
| Node.js | Supported |
|
|
164
|
+
| Vite / Next.js | Supported |
|
|
165
|
+
| Docker | Supported |
|
|
166
|
+
| Python | Supported |
|
|
167
|
+
| Go | Supported |
|
|
168
|
+
| Rust (cargo) | Supported |
|
|
169
|
+
|
|
170
|
+
Detection is plugin-based — adding new tools takes minutes.
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## Risk Signals
|
|
175
|
+
|
|
176
|
+
RuntimeInspector automatically flags sessions that need attention:
|
|
177
|
+
|
|
178
|
+
| Signal | Trigger | Default |
|
|
179
|
+
|---|---|---|
|
|
180
|
+
| **Long-running** | Session exceeds duration threshold | 30 min |
|
|
181
|
+
| **High CPU** | CPU usage exceeds threshold | 80% |
|
|
182
|
+
| **Orphan** | Parent process no longer exists | Immediate |
|
|
183
|
+
| **Duplicate** | Multiple instances of the same agent/server | Immediate |
|
|
184
|
+
|
|
185
|
+
All thresholds are configurable via YAML config.
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Privacy & Security
|
|
190
|
+
|
|
191
|
+
RuntimeInspector is **local-first by design**:
|
|
192
|
+
|
|
193
|
+
- Runs entirely on `localhost` — no cloud dependency
|
|
194
|
+
- Reads **process metadata only** (PIDs, command names, ports, resource counters)
|
|
195
|
+
- **No source code** or terminal output leaves your machine
|
|
196
|
+
- No telemetry, no tracking, no accounts
|
|
197
|
+
- Configurable allowlist for any outbound data
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## Project Structure
|
|
202
|
+
|
|
203
|
+
```
|
|
204
|
+
runtime-inspector/
|
|
205
|
+
├── bin/
|
|
206
|
+
│ └── cli.js # CLI entry point
|
|
207
|
+
├── src/
|
|
208
|
+
│ ├── agent/
|
|
209
|
+
│ │ ├── index.js # Express server + dashboard
|
|
210
|
+
│ │ ├── scanner.js # Process tree scanner
|
|
211
|
+
│ │ ├── detector.js # Agent/framework detection
|
|
212
|
+
│ │ ├── grouper.js # Session grouping logic
|
|
213
|
+
│ │ ├── explainer.js # Plain-English summaries
|
|
214
|
+
│ │ ├── purposer.js # Intent inference
|
|
215
|
+
│ │ ├── stateInfer.js # State detection
|
|
216
|
+
│ │ ├── progressInfer.js # Progress tracking
|
|
217
|
+
│ │ ├── attentionInfer.js# Attention signals
|
|
218
|
+
│ │ ├── actions.js # Suggested actions
|
|
219
|
+
│ │ ├── dashboard.js # Dashboard HTML renderer
|
|
220
|
+
│ │ ├── shellEvents.js # Shell event processing
|
|
221
|
+
│ │ ├── shellMerge.js # Shell session merging
|
|
222
|
+
│ │ ├── repoActivity.js # Repo activity tracking
|
|
223
|
+
│ │ ├── tmux.js # Tmux detection
|
|
224
|
+
│ │ └── tmuxMerge.js # Tmux session merging
|
|
225
|
+
│ ├── shell/
|
|
226
|
+
│ │ ├── hooks.js # Shell hook script generator
|
|
227
|
+
│ │ └── setup.js # RC file installer
|
|
228
|
+
│ └── wrapper/
|
|
229
|
+
│ ├── runner.js # Command wrapper
|
|
230
|
+
│ └── buffer.js # Output buffer
|
|
231
|
+
├── runtimeinspector/ # Next.js landing page
|
|
232
|
+
│ └── ...
|
|
233
|
+
├── package.json
|
|
234
|
+
└── README.md
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## Requirements
|
|
240
|
+
|
|
241
|
+
- **Node.js 18** or later
|
|
242
|
+
- **macOS** or **Linux** (Windows/WSL on the roadmap)
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## Contributing
|
|
247
|
+
|
|
248
|
+
Contributions are welcome! Feel free to open issues or submit PRs.
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
# Clone the repo
|
|
252
|
+
git clone https://github.com/asaferdman23/runtime_inspector_landing.git
|
|
253
|
+
cd runtime_inspector_landing
|
|
254
|
+
|
|
255
|
+
# Install CLI dependencies
|
|
256
|
+
npm install
|
|
257
|
+
|
|
258
|
+
# Install landing page dependencies
|
|
259
|
+
cd runtimeinspector && npm install
|
|
260
|
+
|
|
261
|
+
# Run the CLI
|
|
262
|
+
node bin/cli.js start --open
|
|
263
|
+
|
|
264
|
+
# Run the landing page
|
|
265
|
+
cd runtimeinspector && npm run dev
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## License
|
|
271
|
+
|
|
272
|
+
[MIT](LICENSE) — Asaf Erdman
|
|
273
|
+
|
|
274
|
+
---
|
|
275
|
+
|
|
276
|
+
<p align="center">
|
|
277
|
+
<strong>Star this repo</strong> to get notified when <code>runtime-inspector</code> lands on npm.
|
|
278
|
+
</p>
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// RuntimeInspector CLI
|
|
4
|
+
// Usage: runtime-inspector start [--open]
|
|
5
|
+
// runtime-inspector run -- <command> [args...]
|
|
6
|
+
// runtime-inspector init [--shell zsh|bash]
|
|
7
|
+
// runtime-inspector setup [--shell zsh|bash] [--rc <path>] [--yes]
|
|
8
|
+
|
|
9
|
+
const args = process.argv.slice(2);
|
|
10
|
+
const command = args[0];
|
|
11
|
+
|
|
12
|
+
function getFlag(name) {
|
|
13
|
+
const idx = args.indexOf(name);
|
|
14
|
+
if (idx === -1) return null;
|
|
15
|
+
return args[idx + 1] || null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function hasFlag(name) {
|
|
19
|
+
return args.includes(name);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (command === 'init') {
|
|
23
|
+
// Print shell hook script to stdout
|
|
24
|
+
const shell = getFlag('--shell') || 'zsh';
|
|
25
|
+
import('../src/shell/hooks.js').then(({ generateHookScript }) => {
|
|
26
|
+
process.stdout.write(generateHookScript(shell));
|
|
27
|
+
}).catch((err) => {
|
|
28
|
+
console.error('Failed to generate hook script:', err.message);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
} else if (command === 'setup') {
|
|
33
|
+
// Install integration into shell rc file
|
|
34
|
+
const shell = getFlag('--shell') || null;
|
|
35
|
+
const rc = getFlag('--rc') || null;
|
|
36
|
+
const yes = hasFlag('--yes');
|
|
37
|
+
|
|
38
|
+
import('../src/shell/setup.js').then(({ detectShell, installRcBlock }) => {
|
|
39
|
+
const { shell: detectedShell, rcPath } = detectShell(shell, rc);
|
|
40
|
+
|
|
41
|
+
if (!yes) {
|
|
42
|
+
console.log(` RuntimeInspector Shell Integration`);
|
|
43
|
+
console.log(` Shell: ${detectedShell}`);
|
|
44
|
+
console.log(` RC file: ${rcPath}`);
|
|
45
|
+
console.log('');
|
|
46
|
+
console.log(' This will append the RuntimeInspector integration block to your rc file.');
|
|
47
|
+
console.log(' A backup will be created before any changes.');
|
|
48
|
+
console.log('');
|
|
49
|
+
console.log(' Run with --yes to proceed, or:');
|
|
50
|
+
console.log(` runtimeinspector setup --shell ${detectedShell} --yes`);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const result = installRcBlock({ shell: detectedShell, rcPath, yes });
|
|
55
|
+
|
|
56
|
+
if (result.status === 'already') {
|
|
57
|
+
console.log(` ${result.message}`);
|
|
58
|
+
console.log(' No changes made.');
|
|
59
|
+
} else if (result.status === 'installed') {
|
|
60
|
+
console.log(` ${result.message}`);
|
|
61
|
+
console.log('');
|
|
62
|
+
console.log(' Next steps:');
|
|
63
|
+
console.log(' 1. Restart your terminal (or run: source ' + rcPath + ')');
|
|
64
|
+
console.log(' 2. Start the agent: runtimeinspector start');
|
|
65
|
+
console.log(' 3. Open dashboard: http://localhost:7331');
|
|
66
|
+
} else {
|
|
67
|
+
console.error(` Error: ${result.message}`);
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
}).catch((err) => {
|
|
71
|
+
console.error('Failed to run setup:', err.message);
|
|
72
|
+
process.exit(1);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
} else if (command === 'run') {
|
|
76
|
+
// Find the -- separator
|
|
77
|
+
const dashIndex = args.indexOf('--');
|
|
78
|
+
if (dashIndex === -1 || dashIndex === args.length - 1) {
|
|
79
|
+
console.error('Usage: runtime-inspector run -- <command> [args...]');
|
|
80
|
+
console.error('Example: runtime-inspector run -- npm run dev');
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
const cmdArgs = args.slice(dashIndex + 1);
|
|
84
|
+
|
|
85
|
+
import('../src/wrapper/runner.js').then(({ runWrapped }) => {
|
|
86
|
+
runWrapped(cmdArgs);
|
|
87
|
+
}).catch((err) => {
|
|
88
|
+
console.error('Failed to start wrapper:', err.message);
|
|
89
|
+
process.exit(1);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
} else if (!command || command === 'start') {
|
|
93
|
+
const shouldOpen = args.includes('--open') || args.includes('-o');
|
|
94
|
+
|
|
95
|
+
import('../src/agent/index.js').then(({ startServer }) => {
|
|
96
|
+
startServer({ open: shouldOpen });
|
|
97
|
+
}).catch((err) => {
|
|
98
|
+
console.error('Failed to start RuntimeInspector:', err.message);
|
|
99
|
+
process.exit(1);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
} else if (command === '--help' || command === '-h') {
|
|
103
|
+
console.log(`
|
|
104
|
+
RuntimeInspector — local runtime inspection tool
|
|
105
|
+
|
|
106
|
+
Usage:
|
|
107
|
+
runtime-inspector start [--open] Start the agent + dashboard
|
|
108
|
+
runtime-inspector run -- <command> Run a command through RuntimeInspector
|
|
109
|
+
runtime-inspector init [--shell zsh] Print shell hook script to stdout
|
|
110
|
+
runtime-inspector setup [--yes] Install shell integration into rc file
|
|
111
|
+
runtime-inspector --help Show this help
|
|
112
|
+
|
|
113
|
+
Setup options:
|
|
114
|
+
--shell zsh|bash Override shell detection
|
|
115
|
+
--rc <path> Override rc file path
|
|
116
|
+
--yes Skip confirmation prompt
|
|
117
|
+
|
|
118
|
+
Quick start:
|
|
119
|
+
runtimeinspector setup --yes
|
|
120
|
+
# restart terminal
|
|
121
|
+
runtimeinspector start --open
|
|
122
|
+
|
|
123
|
+
Dashboard: http://localhost:7331
|
|
124
|
+
`);
|
|
125
|
+
} else {
|
|
126
|
+
console.error(`Unknown command: ${command}`);
|
|
127
|
+
console.error('Run "runtime-inspector --help" for usage.');
|
|
128
|
+
process.exit(1);
|
|
129
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "runtime-inspector",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Local-first runtime inspection tool for developers. Scans processes, groups them into sessions, and serves a real-time dashboard on localhost.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"runtime-inspector": "./bin/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"start": "node bin/cli.js start"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"bin/",
|
|
14
|
+
"src/agent/",
|
|
15
|
+
"src/shell/",
|
|
16
|
+
"src/wrapper/",
|
|
17
|
+
"README.md",
|
|
18
|
+
"LICENSE"
|
|
19
|
+
],
|
|
20
|
+
"keywords": [
|
|
21
|
+
"cli",
|
|
22
|
+
"process",
|
|
23
|
+
"inspector",
|
|
24
|
+
"developer-tools",
|
|
25
|
+
"terminal",
|
|
26
|
+
"runtime",
|
|
27
|
+
"observability",
|
|
28
|
+
"ai-agents",
|
|
29
|
+
"dashboard",
|
|
30
|
+
"localhost"
|
|
31
|
+
],
|
|
32
|
+
"author": "Asaf Erdman <asaf.erdman.dev@gmail.com>",
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "git+https://github.com/asaferdman23/runtime_inspector_landing.git"
|
|
37
|
+
},
|
|
38
|
+
"homepage": "https://github.com/asaferdman23/runtime_inspector_landing#readme",
|
|
39
|
+
"bugs": {
|
|
40
|
+
"url": "https://github.com/asaferdman23/runtime_inspector_landing/issues"
|
|
41
|
+
},
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=18"
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"express": "^4.21.0",
|
|
47
|
+
"open": "^10.1.0"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
// Context actions — safe OS-level actions triggered from the dashboard.
|
|
2
|
+
// Only whitelisted actions. No arbitrary command execution.
|
|
3
|
+
|
|
4
|
+
import { execSync, spawnSync } from 'child_process';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Open a terminal window at the given directory path.
|
|
8
|
+
* macOS: uses osascript to open Terminal.app.
|
|
9
|
+
*/
|
|
10
|
+
function openTerminal(cwd) {
|
|
11
|
+
if (!cwd || typeof cwd !== 'string') throw new Error('No working directory available');
|
|
12
|
+
if (!cwd.startsWith('/')) throw new Error('Invalid path');
|
|
13
|
+
|
|
14
|
+
// AppleScript to open Terminal.app at a specific directory
|
|
15
|
+
const script = `tell application "Terminal"
|
|
16
|
+
do script "cd ${escapeAppleScript(cwd)}"
|
|
17
|
+
activate
|
|
18
|
+
end tell`;
|
|
19
|
+
|
|
20
|
+
execSync(`osascript -e '${script.replace(/'/g, "'\\''")}'`, { timeout: 5000 });
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Open a folder in Finder (macOS file explorer).
|
|
25
|
+
*/
|
|
26
|
+
function openFolder(cwd) {
|
|
27
|
+
if (!cwd || typeof cwd !== 'string') throw new Error('No working directory available');
|
|
28
|
+
if (!cwd.startsWith('/')) throw new Error('Invalid path');
|
|
29
|
+
|
|
30
|
+
execSync(`open "${cwd}"`, { timeout: 5000 });
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Escape a string for AppleScript.
|
|
35
|
+
*/
|
|
36
|
+
function escapeAppleScript(str) {
|
|
37
|
+
return str.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Register action routes on the Express app.
|
|
42
|
+
* Requires a function to look up sessions by id.
|
|
43
|
+
*/
|
|
44
|
+
export function registerActions(app, getSession) {
|
|
45
|
+
app.use('/api/actions', (req, res, next) => {
|
|
46
|
+
// All action routes use JSON
|
|
47
|
+
if (req.method === 'POST') {
|
|
48
|
+
let body = '';
|
|
49
|
+
req.on('data', chunk => body += chunk);
|
|
50
|
+
req.on('end', () => {
|
|
51
|
+
try { req.body = JSON.parse(body); } catch { req.body = {}; }
|
|
52
|
+
next();
|
|
53
|
+
});
|
|
54
|
+
} else {
|
|
55
|
+
next();
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// POST /api/actions/open-terminal { sessionId }
|
|
60
|
+
app.post('/api/actions/open-terminal', (req, res) => {
|
|
61
|
+
try {
|
|
62
|
+
const session = getSession(req.body?.sessionId);
|
|
63
|
+
if (!session) return res.status(404).json({ error: 'Session not found' });
|
|
64
|
+
if (!session.cwd) return res.status(400).json({ error: 'No working directory for this session' });
|
|
65
|
+
|
|
66
|
+
openTerminal(session.cwd);
|
|
67
|
+
res.json({ ok: true, action: 'open-terminal', cwd: session.cwd });
|
|
68
|
+
} catch (err) {
|
|
69
|
+
res.status(500).json({ error: err.message });
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// POST /api/actions/open-folder { sessionId }
|
|
74
|
+
app.post('/api/actions/open-folder', (req, res) => {
|
|
75
|
+
try {
|
|
76
|
+
const session = getSession(req.body?.sessionId);
|
|
77
|
+
if (!session) return res.status(404).json({ error: 'Session not found' });
|
|
78
|
+
if (!session.cwd) return res.status(400).json({ error: 'No working directory for this session' });
|
|
79
|
+
|
|
80
|
+
openFolder(session.cwd);
|
|
81
|
+
res.json({ ok: true, action: 'open-folder', cwd: session.cwd });
|
|
82
|
+
} catch (err) {
|
|
83
|
+
res.status(500).json({ error: err.message });
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// POST /api/actions/focus-terminal { sessionId }
|
|
88
|
+
// Best-effort: activate Terminal.app and switch to the TTY tab
|
|
89
|
+
app.post('/api/actions/focus-terminal', (req, res) => {
|
|
90
|
+
try {
|
|
91
|
+
const session = getSession(req.body?.sessionId);
|
|
92
|
+
if (!session) return res.status(404).json({ error: 'Session not found' });
|
|
93
|
+
|
|
94
|
+
const cwd = session.cwd;
|
|
95
|
+
if (!cwd) return res.status(400).json({ error: 'No working directory for this session' });
|
|
96
|
+
|
|
97
|
+
// Best-effort: open Terminal.app at cwd. If the session has a tty, try to find that tab.
|
|
98
|
+
const script = `tell application "Terminal"
|
|
99
|
+
activate
|
|
100
|
+
do script "cd ${escapeAppleScript(cwd)}"
|
|
101
|
+
end tell`;
|
|
102
|
+
execSync(`osascript -e '${script.replace(/'/g, "'\\''")}'`, { timeout: 5000 });
|
|
103
|
+
res.json({ ok: true, action: 'focus-terminal', cwd });
|
|
104
|
+
} catch (err) {
|
|
105
|
+
res.status(500).json({ error: err.message });
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// POST /api/actions/tmux-focus { sessionId }
|
|
110
|
+
// Switch tmux client to the pane associated with this session
|
|
111
|
+
app.post('/api/actions/tmux-focus', (req, res) => {
|
|
112
|
+
try {
|
|
113
|
+
const session = getSession(req.body?.sessionId);
|
|
114
|
+
if (!session) return res.status(404).json({ error: 'Session not found' });
|
|
115
|
+
if (!session.tmux) return res.status(400).json({ error: 'Session has no tmux pane' });
|
|
116
|
+
|
|
117
|
+
const { paneId, sessionName } = session.tmux;
|
|
118
|
+
|
|
119
|
+
// Try: switch-client to session and select-pane
|
|
120
|
+
const r = spawnSync('tmux', ['switch-client', '-t', sessionName, ';', 'select-pane', '-t', paneId], {
|
|
121
|
+
timeout: 2000,
|
|
122
|
+
encoding: 'utf-8',
|
|
123
|
+
stdio: 'pipe',
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
if (r.status !== 0) {
|
|
127
|
+
// Fallback: just select-pane (works if already in that session)
|
|
128
|
+
const r2 = spawnSync('tmux', ['select-pane', '-t', paneId], {
|
|
129
|
+
timeout: 2000,
|
|
130
|
+
encoding: 'utf-8',
|
|
131
|
+
stdio: 'pipe',
|
|
132
|
+
});
|
|
133
|
+
if (r2.status !== 0) {
|
|
134
|
+
return res.json({ ok: false, error: 'Could not focus tmux pane — are you in tmux?' });
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
res.json({ ok: true, action: 'tmux-focus', paneId });
|
|
139
|
+
} catch (err) {
|
|
140
|
+
res.status(500).json({ error: err.message });
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// POST /api/actions/copy-command { sessionId }
|
|
145
|
+
// Returns the root process command so the frontend can copy it.
|
|
146
|
+
app.post('/api/actions/copy-command', (req, res) => {
|
|
147
|
+
try {
|
|
148
|
+
const session = getSession(req.body?.sessionId);
|
|
149
|
+
if (!session) return res.status(404).json({ error: 'Session not found' });
|
|
150
|
+
|
|
151
|
+
const rootCmd = session.processes[0]?.cmd || '';
|
|
152
|
+
res.json({ ok: true, action: 'copy-command', command: rootCmd });
|
|
153
|
+
} catch (err) {
|
|
154
|
+
res.status(500).json({ error: err.message });
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
}
|