@scrylog/cli 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 +836 -0
- package/package.json +75 -0
- package/src/api/docs/sessions.keys.ts +6 -0
- package/src/api/docs/sessions.ts +81 -0
- package/src/api/docs/system.keys.ts +5 -0
- package/src/api/docs/system.ts +66 -0
- package/src/api/docs/usage.keys.ts +3 -0
- package/src/api/docs/usage.ts +24 -0
- package/src/api/docs/webhooks.keys.ts +5 -0
- package/src/api/docs/webhooks.ts +78 -0
- package/src/api/index.ts +18 -0
- package/src/api/lib/auth.ts +43 -0
- package/src/api/lib/errors.ts +28 -0
- package/src/api/lib/route.ts +18 -0
- package/src/api/models/claude.ts +4 -0
- package/src/api/models/codex.ts +4 -0
- package/src/api/models/opencode.ts +11 -0
- package/src/api/routes/sessions.ts +47 -0
- package/src/api/routes/system.ts +103 -0
- package/src/api/routes/usage.ts +15 -0
- package/src/api/routes/webhooks/claude.ts +161 -0
- package/src/api/routes/webhooks/codex.ts +175 -0
- package/src/api/routes/webhooks/opencode.ts +107 -0
- package/src/api/routes.ts +93 -0
- package/src/api/schemas/common.ts +21 -0
- package/src/api/schemas/sessions.ts +72 -0
- package/src/api/schemas/usage.ts +50 -0
- package/src/api/schemas/webhooks/claude.ts +32 -0
- package/src/api/schemas/webhooks/codex.ts +34 -0
- package/src/api/schemas/webhooks/opencode.ts +29 -0
- package/src/api/services/claude-session.ts +9 -0
- package/src/api/services/codex-session.ts +8 -0
- package/src/api/services/opencode-session.ts +73 -0
- package/src/classifier.ts +206 -0
- package/src/cli/commands.ts +498 -0
- package/src/cli/prompt.ts +44 -0
- package/src/daemon/cli.ts +27 -0
- package/src/daemon/collectors/claude/README.md +108 -0
- package/src/daemon/collectors/claude/collector.ts +413 -0
- package/src/daemon/collectors/claude/discovery.ts +148 -0
- package/src/daemon/collectors/claude/hooks.ts +181 -0
- package/src/daemon/collectors/claude/index.ts +8 -0
- package/src/daemon/collectors/claude/jsonl.ts +85 -0
- package/src/daemon/collectors/claude/transcript.ts +246 -0
- package/src/daemon/collectors/codex/README.md +147 -0
- package/src/daemon/collectors/codex/collector.ts +661 -0
- package/src/daemon/collectors/codex/discovery.ts +104 -0
- package/src/daemon/collectors/codex/hooks.ts +179 -0
- package/src/daemon/collectors/codex/index.ts +7 -0
- package/src/daemon/collectors/codex/jsonl.ts +108 -0
- package/src/daemon/collectors/codex/transcript.ts +299 -0
- package/src/daemon/collectors/git.ts +295 -0
- package/src/daemon/collectors/opencode/README.md +106 -0
- package/src/daemon/collectors/opencode/collector.ts +543 -0
- package/src/daemon/collectors/opencode/discovery.ts +257 -0
- package/src/daemon/collectors/opencode/index.ts +5 -0
- package/src/daemon/collectors/opencode/session-builder.ts +59 -0
- package/src/daemon/collectors/opencode-db.ts +282 -0
- package/src/daemon/collectors/process.ts +201 -0
- package/src/daemon/collectors/tmux/README.md +128 -0
- package/src/daemon/collectors/tmux/collector.ts +472 -0
- package/src/daemon/collectors/tmux/enrichment.ts +241 -0
- package/src/daemon/collectors/tmux/index.ts +4 -0
- package/src/daemon/collectors/tmux/parse.ts +128 -0
- package/src/daemon/collectors/usage.ts +193 -0
- package/src/daemon/index.ts +207 -0
- package/src/daemon/scheduler.ts +86 -0
- package/src/daemon/store.ts +535 -0
- package/src/detect.ts +143 -0
- package/src/hooks/claude.ts +596 -0
- package/src/hooks/codex.ts +517 -0
- package/src/legacy/classifier.ts +215 -0
- package/src/legacy/detect.ts +143 -0
- package/src/mcp/client-server.ts +21 -0
- package/src/mcp/http-handler.ts +126 -0
- package/src/mcp/index.ts +61 -0
- package/src/mcp/resources/config-current.ts +19 -0
- package/src/mcp/resources/index.ts +11 -0
- package/src/mcp/resources/sessions-active.ts +19 -0
- package/src/mcp/resources/sessions-all.ts +19 -0
- package/src/mcp/schemas/sessions.ts +3 -0
- package/src/mcp/server.ts +17 -0
- package/src/mcp/services/api-adapter.ts +48 -0
- package/src/mcp/services/data-source.ts +22 -0
- package/src/mcp/services/store-adapter.ts +40 -0
- package/src/mcp/tools/get-active-sessions.ts +16 -0
- package/src/mcp/tools/get-session-children.ts +24 -0
- package/src/mcp/tools/get-session-messages.ts +24 -0
- package/src/mcp/tools/get-session.ts +23 -0
- package/src/mcp/tools/index.ts +15 -0
- package/src/mcp/tools/list-sessions.ts +16 -0
- package/src/neo-blessed.d.ts +4 -0
- package/src/oc/index.ts +302 -0
- package/src/service/manager.ts +242 -0
- package/src/shared/config.ts +478 -0
- package/src/shared/ipc.ts +80 -0
- package/src/shared/logger.ts +248 -0
- package/src/shared/paths.ts +73 -0
- package/src/shared/platform.ts +110 -0
- package/src/shared/session-view.ts +241 -0
- package/src/shared/types.ts +299 -0
- package/src/shared/utils.ts +339 -0
- package/src/tui/app.ts +920 -0
- package/src/tui/client.ts +149 -0
- package/src/tui/index.ts +181 -0
- package/src/types.ts +113 -0
- package/src/usage/README.md +323 -0
- package/src/usage/api.ts +99 -0
- package/src/usage/client.ts +3 -0
- package/src/usage/clients/anthropic-oauth-usage.ts +178 -0
- package/src/usage/clients/anthropic-web-usage.ts +245 -0
- package/src/usage/clients/claude-cli-probe.ts +187 -0
- package/src/usage/clients/codex-oauth-usage.ts +113 -0
- package/src/usage/clients/opencode-go-usage.ts +441 -0
- package/src/usage/index.ts +19 -0
- package/src/usage/mappers.ts +273 -0
- package/src/usage/planner.ts +54 -0
- package/src/usage/providers/claude.ts +113 -0
- package/src/usage/providers/codex.ts +34 -0
- package/src/usage/providers/opencode-go.ts +37 -0
- package/src/usage/resolver.ts +4 -0
- package/src/usage/resolvers/browser-cookies.ts +198 -0
- package/src/usage/resolvers/claude-auth.ts +99 -0
- package/src/usage/resolvers/claude-cli.ts +24 -0
- package/src/usage/resolvers/claude-web.ts +16 -0
- package/src/usage/resolvers/codex-auth.ts +117 -0
- package/src/usage/resolvers/opencode-go-web.ts +14 -0
- package/src/usage/types.ts +190 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { createDaemonFetch, getSocketPath } from "../shared/ipc";
|
|
2
|
+
import { IS_WINDOWS } from "../shared/platform";
|
|
3
|
+
import type { ScrylogConfig } from "../shared/config";
|
|
4
|
+
import type { SessionModel, StoreEvent } from "../shared/types";
|
|
5
|
+
import type { SessionListItem, SessionTreeItem, SessionViewCounts } from "../shared/session-view";
|
|
6
|
+
import { flattenSessionTree } from "../shared/session-view";
|
|
7
|
+
|
|
8
|
+
type FetchFn = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
|
|
9
|
+
|
|
10
|
+
export class DaemonClient {
|
|
11
|
+
private readonly fetch: FetchFn;
|
|
12
|
+
|
|
13
|
+
constructor(config: ScrylogConfig, socketPath?: string) {
|
|
14
|
+
this.fetch = createDaemonFetch({
|
|
15
|
+
host: config.api.host,
|
|
16
|
+
port: config.api.port,
|
|
17
|
+
socketPath: socketPath ?? (IS_WINDOWS ? undefined : getSocketPath(config.daemon.socketPath)),
|
|
18
|
+
bearerToken: config.api.bearerToken,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async getStatusSummary(): Promise<{ counts: SessionViewCounts; collectorStatus: Record<string, unknown>; generatedAt: number } | null> {
|
|
23
|
+
try {
|
|
24
|
+
const response = await this.fetch("/api/status");
|
|
25
|
+
if (!response.ok) return null;
|
|
26
|
+
return await response.json() as { counts: SessionViewCounts; collectorStatus: Record<string, unknown>; generatedAt: number };
|
|
27
|
+
} catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async isHealthy(): Promise<boolean> {
|
|
33
|
+
try {
|
|
34
|
+
const response = await this.fetch("/api/health");
|
|
35
|
+
return response.ok;
|
|
36
|
+
} catch {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async getSessions(): Promise<{ sessions: SessionListItem[]; counts: SessionViewCounts; generatedAt: number } | null> {
|
|
42
|
+
try {
|
|
43
|
+
const response = await this.fetch("/api/sessions");
|
|
44
|
+
if (!response.ok) return null;
|
|
45
|
+
const data = await response.json() as { sessions: SessionTreeItem[]; counts: SessionViewCounts; generatedAt: number };
|
|
46
|
+
return {
|
|
47
|
+
sessions: flattenSessionTree(data.sessions),
|
|
48
|
+
counts: data.counts,
|
|
49
|
+
generatedAt: data.generatedAt,
|
|
50
|
+
};
|
|
51
|
+
} catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async getSessionDetail(id: string): Promise<SessionModel | null> {
|
|
57
|
+
try {
|
|
58
|
+
const response = await this.fetch(`/api/sessions/${encodeURIComponent(id)}`);
|
|
59
|
+
if (!response.ok) return null;
|
|
60
|
+
const data = await response.json() as { session: SessionModel };
|
|
61
|
+
return data.session;
|
|
62
|
+
} catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Subscribe to daemon SSE events with automatic reconnect.
|
|
69
|
+
* Returns an unsubscribe function.
|
|
70
|
+
*/
|
|
71
|
+
subscribeEvents(onEvent: (event: StoreEvent | { type: "connected" } | { type: "disconnected" }) => void): () => void {
|
|
72
|
+
let aborted = false;
|
|
73
|
+
const controller = new AbortController();
|
|
74
|
+
let reconnectTimer: ReturnType<typeof setTimeout> | null = null;
|
|
75
|
+
let reconnectDelay = 1_000;
|
|
76
|
+
|
|
77
|
+
const connect = async () => {
|
|
78
|
+
if (aborted) return;
|
|
79
|
+
try {
|
|
80
|
+
const response = await this.fetch("/api/events", {
|
|
81
|
+
signal: controller.signal,
|
|
82
|
+
headers: { Accept: "text/event-stream" },
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
if (!response.ok || !response.body) {
|
|
86
|
+
if (!aborted) {
|
|
87
|
+
onEvent({ type: "disconnected" });
|
|
88
|
+
}
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
onEvent({ type: "connected" });
|
|
93
|
+
reconnectDelay = 1_000; // reset backoff on successful connect
|
|
94
|
+
|
|
95
|
+
const reader = response.body.getReader();
|
|
96
|
+
const decoder = new TextDecoder();
|
|
97
|
+
let buffer = "";
|
|
98
|
+
|
|
99
|
+
while (!aborted) {
|
|
100
|
+
const { value, done } = await reader.read();
|
|
101
|
+
if (done) break;
|
|
102
|
+
|
|
103
|
+
buffer += decoder.decode(value, { stream: true });
|
|
104
|
+
const lines = buffer.split("\n");
|
|
105
|
+
buffer = lines.pop() ?? "";
|
|
106
|
+
|
|
107
|
+
for (const line of lines) {
|
|
108
|
+
if (line.startsWith("data: ")) {
|
|
109
|
+
try {
|
|
110
|
+
const payload = JSON.parse(line.slice(6)) as StoreEvent;
|
|
111
|
+
onEvent(payload);
|
|
112
|
+
} catch {
|
|
113
|
+
// Ignore malformed events
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
} catch (error) {
|
|
119
|
+
if (!aborted) {
|
|
120
|
+
onEvent({ type: "disconnected" });
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const scheduleReconnect = () => {
|
|
126
|
+
if (aborted || reconnectTimer) return;
|
|
127
|
+
reconnectTimer = setTimeout(() => {
|
|
128
|
+
reconnectTimer = null;
|
|
129
|
+
void connect().then(() => {
|
|
130
|
+
if (!aborted) scheduleReconnect();
|
|
131
|
+
});
|
|
132
|
+
}, reconnectDelay);
|
|
133
|
+
reconnectDelay = Math.min(reconnectDelay * 1.5, 5_000);
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
void connect().then(() => {
|
|
137
|
+
if (!aborted) scheduleReconnect();
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
return () => {
|
|
141
|
+
aborted = true;
|
|
142
|
+
controller.abort();
|
|
143
|
+
if (reconnectTimer) {
|
|
144
|
+
clearTimeout(reconnectTimer);
|
|
145
|
+
reconnectTimer = null;
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
}
|
package/src/tui/index.ts
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import { loadConfig } from "../shared/config";
|
|
3
|
+
import type { ScrylogConfig } from "../shared/config";
|
|
4
|
+
import { DaemonClient } from "./client";
|
|
5
|
+
import { DashboardApp } from "./app";
|
|
6
|
+
import { getServiceManagerOrThrow } from "../service/manager";
|
|
7
|
+
import type { ServiceStatus } from "../service/manager";
|
|
8
|
+
import {
|
|
9
|
+
runClaudeHooksInstall,
|
|
10
|
+
runCodexHooksInstall,
|
|
11
|
+
runConfigCommand,
|
|
12
|
+
runDaemonCommand,
|
|
13
|
+
runDoctor,
|
|
14
|
+
runLogsCommand,
|
|
15
|
+
runSetupWizard,
|
|
16
|
+
runTokenCommand,
|
|
17
|
+
} from "../cli/commands";
|
|
18
|
+
|
|
19
|
+
function printHelp() {
|
|
20
|
+
process.stdout.write(`scrylog
|
|
21
|
+
|
|
22
|
+
Usage:
|
|
23
|
+
scrylog [--config <path>]
|
|
24
|
+
scrylog setup [--config <path>]
|
|
25
|
+
scrylog doctor [--config <path>]
|
|
26
|
+
scrylog daemon <install|uninstall|start|stop|restart|status> [--config <path>]
|
|
27
|
+
scrylog config <show|path> [--config <path>]
|
|
28
|
+
scrylog token <mcp|api|both> <generate|rotate|clear> [--config <path>]
|
|
29
|
+
scrylog claude hooks install [--config <path>]
|
|
30
|
+
scrylog codex hooks install [--config <path>]
|
|
31
|
+
scrylog logs
|
|
32
|
+
|
|
33
|
+
Options:
|
|
34
|
+
--config <path> Load config from an explicit path
|
|
35
|
+
-h, --help Show this help text
|
|
36
|
+
|
|
37
|
+
Notes:
|
|
38
|
+
Running scrylog with no subcommand launches the TUI dashboard.
|
|
39
|
+
'scrylog claude hooks install' configures Claude Code to emit lifecycle
|
|
40
|
+
hooks that scrylog uses for real-time session state tracking.
|
|
41
|
+
'scrylog codex hooks install' configures Codex CLI to emit lifecycle
|
|
42
|
+
hooks that scrylog uses for real-time session state tracking.
|
|
43
|
+
`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function extractGlobalOptions(argv: string[]) {
|
|
47
|
+
const args = [...argv];
|
|
48
|
+
let configPath: string | undefined;
|
|
49
|
+
const configFlagIndex = args.indexOf("--config");
|
|
50
|
+
if (configFlagIndex >= 0) {
|
|
51
|
+
configPath = args[configFlagIndex + 1];
|
|
52
|
+
args.splice(configFlagIndex, configPath ? 2 : 1);
|
|
53
|
+
}
|
|
54
|
+
return { args, options: { configPath } };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function launchTui(configPath?: string): Promise<void> {
|
|
58
|
+
const config = loadConfig(configPath);
|
|
59
|
+
|
|
60
|
+
const client = new DaemonClient(config);
|
|
61
|
+
if (!(await client.isHealthy())) {
|
|
62
|
+
printDaemonNotRunning(config, getServiceManagerOrThrow().platform);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const app = new DashboardApp(client, {
|
|
67
|
+
refreshMs: config.tui.refreshMs,
|
|
68
|
+
showStandalone: config.tui.showStandalone,
|
|
69
|
+
}, config);
|
|
70
|
+
|
|
71
|
+
app.start();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Print a platform-aware error explaining that the daemon is not running and
|
|
76
|
+
* listing the commands to verify, start, and inspect it. Exported for testing.
|
|
77
|
+
*/
|
|
78
|
+
export function printDaemonNotRunning(config: ScrylogConfig, platform: ServiceStatus["platform"]): void {
|
|
79
|
+
const logPath = config.daemon.logFilePath;
|
|
80
|
+
const hasService = platform === "macos" || platform === "linux";
|
|
81
|
+
|
|
82
|
+
process.stderr.write("scrylog: daemon is not running.\n\n");
|
|
83
|
+
process.stderr.write("Verify:\n");
|
|
84
|
+
process.stderr.write(" scrylog daemon status\n");
|
|
85
|
+
process.stderr.write(" scrylog doctor\n\n");
|
|
86
|
+
process.stderr.write("Start it (pick one):\n");
|
|
87
|
+
if (hasService) {
|
|
88
|
+
process.stderr.write(" scrylog daemon start # user service\n");
|
|
89
|
+
process.stderr.write(" scrylog daemon install # if the service is not installed yet\n");
|
|
90
|
+
}
|
|
91
|
+
process.stderr.write(" scrylogd # foreground\n\n");
|
|
92
|
+
process.stderr.write(`Logs:\n scrylog logs\n ${logPath}\n`);
|
|
93
|
+
|
|
94
|
+
if (!hasService) {
|
|
95
|
+
const note =
|
|
96
|
+
platform === "wsl"
|
|
97
|
+
? "Service management is unavailable (WSL without systemd). Run scrylogd directly or enable systemd in /etc/wsl.conf."
|
|
98
|
+
: platform === "linux-no-systemd"
|
|
99
|
+
? "Service management is unavailable (no systemd user session). Run scrylogd directly."
|
|
100
|
+
: "Service management is unavailable on this platform. Run scrylogd directly or use Task Scheduler.";
|
|
101
|
+
process.stderr.write(`\n${note}\n`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function main(): Promise<void> {
|
|
106
|
+
const { args, options } = extractGlobalOptions(process.argv.slice(2));
|
|
107
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
108
|
+
printHelp();
|
|
109
|
+
process.exit(0);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const [command, subcommand] = args;
|
|
113
|
+
if (!command) {
|
|
114
|
+
await launchTui(options.configPath ?? process.env.SCRYLOG_CONFIG);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (command === "setup") {
|
|
119
|
+
await runSetupWizard(options);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (command === "doctor") {
|
|
124
|
+
await runDoctor(options);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (command === "daemon") {
|
|
129
|
+
if (!subcommand) throw new Error("Missing daemon action");
|
|
130
|
+
await runDaemonCommand(subcommand, options);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (command === "config") {
|
|
135
|
+
if (!subcommand) throw new Error("Missing config action");
|
|
136
|
+
runConfigCommand(subcommand, options);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (command === "token") {
|
|
141
|
+
if (!subcommand) throw new Error("Missing token target (mcp, api, or both)");
|
|
142
|
+
const action = args[2];
|
|
143
|
+
if (!action) throw new Error("Missing token action (generate, rotate, or clear)");
|
|
144
|
+
const target = subcommand;
|
|
145
|
+
if (target !== "mcp" && target !== "api" && target !== "both") {
|
|
146
|
+
throw new Error(`Invalid token target: ${target}. Must be mcp, api, or both.`);
|
|
147
|
+
}
|
|
148
|
+
runTokenCommand(target as "mcp" | "api" | "both", action, options);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (command === "logs") {
|
|
153
|
+
await runLogsCommand();
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (command === "claude") {
|
|
158
|
+
if (subcommand === "hooks" && args[2] === "install") {
|
|
159
|
+
await runClaudeHooksInstall(options);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
throw new Error(`Unknown claude subcommand: ${subcommand ?? "(none)"}`);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (command === "codex") {
|
|
166
|
+
if (subcommand === "hooks" && args[2] === "install") {
|
|
167
|
+
await runCodexHooksInstall(options);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
throw new Error(`Unknown codex subcommand: ${subcommand ?? "(none)"}`);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
throw new Error(`Unknown command: ${command}`);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (import.meta.main) {
|
|
177
|
+
main().catch((err) => {
|
|
178
|
+
process.stderr.write(`scrylog: fatal: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
179
|
+
process.exit(1);
|
|
180
|
+
});
|
|
181
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
export type ToolKind = "opencode" | "claude" | "codex" | "copilotcli" | "shell" | "unknown";
|
|
2
|
+
|
|
3
|
+
export type PaneState = "working" | "idle" | "waiting-input" | "shell" | "unknown";
|
|
4
|
+
|
|
5
|
+
export interface SessionInfo {
|
|
6
|
+
name: string;
|
|
7
|
+
attached: boolean;
|
|
8
|
+
windows: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface TmuxPaneRecord {
|
|
12
|
+
sessionName: string;
|
|
13
|
+
windowIndex: number;
|
|
14
|
+
windowName: string;
|
|
15
|
+
paneIndex: number;
|
|
16
|
+
paneId: string;
|
|
17
|
+
panePid: number;
|
|
18
|
+
currentCommand: string;
|
|
19
|
+
currentPath: string;
|
|
20
|
+
active: boolean;
|
|
21
|
+
tty: string;
|
|
22
|
+
title: string;
|
|
23
|
+
width: number;
|
|
24
|
+
height: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ProjectInfo {
|
|
28
|
+
displayName: string;
|
|
29
|
+
compactLabel: string;
|
|
30
|
+
sourcePath: string;
|
|
31
|
+
isGitRepository: boolean;
|
|
32
|
+
gitRootPath?: string;
|
|
33
|
+
repositoryIdentityPath?: string;
|
|
34
|
+
branch?: string;
|
|
35
|
+
isWorktree?: boolean;
|
|
36
|
+
remoteName?: string;
|
|
37
|
+
remoteUrl?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface EnrichedPaneRecord extends TmuxPaneRecord {
|
|
41
|
+
project: ProjectInfo;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface ProcessInfo {
|
|
45
|
+
pid: number;
|
|
46
|
+
ppid: number;
|
|
47
|
+
state: string;
|
|
48
|
+
elapsed: string;
|
|
49
|
+
command: string;
|
|
50
|
+
comm: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface DetectionReason {
|
|
54
|
+
label: string;
|
|
55
|
+
detail: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface PaneSample {
|
|
59
|
+
paneId: string;
|
|
60
|
+
lines: string[];
|
|
61
|
+
preview: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface PaneModel extends EnrichedPaneRecord {
|
|
65
|
+
processTree: ProcessInfo[];
|
|
66
|
+
processChain: string[];
|
|
67
|
+
sample: PaneSample | null;
|
|
68
|
+
tool: ToolKind;
|
|
69
|
+
state: PaneState;
|
|
70
|
+
toolReasons: DetectionReason[];
|
|
71
|
+
stateReasons: DetectionReason[];
|
|
72
|
+
summary: string;
|
|
73
|
+
lastChangedAt: number;
|
|
74
|
+
stableForMs: number;
|
|
75
|
+
hasAttention: boolean;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface SnapshotError {
|
|
79
|
+
source: string;
|
|
80
|
+
message: string;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface DashboardSnapshot {
|
|
84
|
+
sessions: SessionInfo[];
|
|
85
|
+
panes: PaneModel[];
|
|
86
|
+
generatedAt: number;
|
|
87
|
+
errors: SnapshotError[];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface FilterOptions {
|
|
91
|
+
query: string;
|
|
92
|
+
actionableOnly: boolean;
|
|
93
|
+
tool: ToolKind | "all";
|
|
94
|
+
state: PaneState | "all";
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface SnapshotHistory {
|
|
98
|
+
tool: ToolKind;
|
|
99
|
+
state: PaneState;
|
|
100
|
+
lastFingerprint: string;
|
|
101
|
+
lastChangedAt: number;
|
|
102
|
+
stableCount: number;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface CollectorOptions {
|
|
106
|
+
previewLines: number;
|
|
107
|
+
detailLines: number;
|
|
108
|
+
refreshMs: number;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export interface AppOptions extends CollectorOptions {
|
|
112
|
+
selectedPaneId?: string;
|
|
113
|
+
}
|