session-intelligence-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/dist/api.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare function sendEvent(payload: Record<string, unknown>): Promise<void>;
package/dist/api.js ADDED
@@ -0,0 +1,19 @@
1
+ import { readConfig } from "./config.js";
2
+ export async function sendEvent(payload) {
3
+ const config = readConfig();
4
+ if (!config)
5
+ return;
6
+ try {
7
+ await fetch(`${config.serverUrl}/api/events`, {
8
+ method: "POST",
9
+ headers: {
10
+ "Content-Type": "application/json",
11
+ "Authorization": `Bearer ${config.apiKey}`,
12
+ },
13
+ body: JSON.stringify(payload),
14
+ });
15
+ }
16
+ catch {
17
+ // Silent — don't block Claude Code
18
+ }
19
+ }
@@ -0,0 +1,6 @@
1
+ export interface Config {
2
+ apiKey: string;
3
+ serverUrl: string;
4
+ }
5
+ export declare function readConfig(): Config | null;
6
+ export declare function writeConfig(config: Config): void;
package/dist/config.js ADDED
@@ -0,0 +1,16 @@
1
+ import { readFileSync, writeFileSync, mkdirSync, existsSync, chmodSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { homedir } from "node:os";
4
+ const CONFIG_DIR = join(homedir(), ".session-intelligence");
5
+ const CONFIG_FILE = join(CONFIG_DIR, "config.json");
6
+ export function readConfig() {
7
+ if (!existsSync(CONFIG_FILE))
8
+ return null;
9
+ const raw = readFileSync(CONFIG_FILE, "utf-8");
10
+ return JSON.parse(raw);
11
+ }
12
+ export function writeConfig(config) {
13
+ mkdirSync(CONFIG_DIR, { recursive: true });
14
+ writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
15
+ chmodSync(CONFIG_FILE, 0o600);
16
+ }
package/dist/git.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ export interface GitState {
2
+ branch: string;
3
+ lastCommitSha: string;
4
+ lastCommitMessage: string;
5
+ hasUncommittedChanges: boolean;
6
+ }
7
+ export interface GitDiffStats {
8
+ filesChanged: number;
9
+ insertions: number;
10
+ deletions: number;
11
+ }
12
+ export declare function readGitState(cwd: string): GitState | null;
13
+ export declare function readGitDiffStats(cwd: string): GitDiffStats;
package/dist/git.js ADDED
@@ -0,0 +1,33 @@
1
+ import { execSync } from "node:child_process";
2
+ function run(cmd, cwd) {
3
+ try {
4
+ return execSync(cmd, { cwd, encoding: "utf-8", timeout: 5000 }).trim();
5
+ }
6
+ catch {
7
+ return "";
8
+ }
9
+ }
10
+ export function readGitState(cwd) {
11
+ const branch = run("git rev-parse --abbrev-ref HEAD", cwd);
12
+ if (!branch)
13
+ return null;
14
+ const logLine = run("git log -1 --format=%H%n%s", cwd);
15
+ const [sha = "", ...msgParts] = logLine.split("\n");
16
+ return {
17
+ branch,
18
+ lastCommitSha: sha,
19
+ lastCommitMessage: msgParts.join("\n"),
20
+ hasUncommittedChanges: run("git status --porcelain", cwd).length > 0,
21
+ };
22
+ }
23
+ export function readGitDiffStats(cwd) {
24
+ const stat = run("git diff --cached --shortstat", cwd) || run("git diff HEAD~1 --shortstat", cwd);
25
+ const files = stat.match(/(\d+) file/);
26
+ const ins = stat.match(/(\d+) insertion/);
27
+ const del = stat.match(/(\d+) deletion/);
28
+ return {
29
+ filesChanged: files ? parseInt(files[1]) : 0,
30
+ insertions: ins ? parseInt(ins[1]) : 0,
31
+ deletions: del ? parseInt(del[1]) : 0,
32
+ };
33
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env node
2
+ import { report } from "./report.js";
3
+ import { setup } from "./setup.js";
4
+ import { status } from "./status.js";
5
+ const [, , command, ...args] = process.argv;
6
+ switch (command) {
7
+ case "report":
8
+ report(args[0] ?? "unknown").catch(() => process.exit(0));
9
+ break;
10
+ case "setup":
11
+ setup().catch((e) => {
12
+ console.error(e);
13
+ process.exit(1);
14
+ });
15
+ break;
16
+ case "status":
17
+ status().catch((e) => {
18
+ console.error(e);
19
+ process.exit(1);
20
+ });
21
+ break;
22
+ default:
23
+ console.log("Usage: session-intelligence <setup|report|status> [args]");
24
+ process.exit(1);
25
+ }
@@ -0,0 +1 @@
1
+ export declare function report(eventType: string): Promise<void>;
package/dist/report.js ADDED
@@ -0,0 +1,86 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { readGitState, readGitDiffStats } from "./git.js";
3
+ import { sendEvent } from "./api.js";
4
+ const EVENT_MAP = {
5
+ "session-start": "SessionStart",
6
+ "stop": "Stop",
7
+ "session-end": "SessionEnd",
8
+ "tool-use": "PostToolUse",
9
+ "notification": "Notification",
10
+ "subagent-stop": "SubagentStop",
11
+ };
12
+ function parseTranscriptTokens(transcriptPath) {
13
+ const zero = { input_tokens: 0, output_tokens: 0, cache_read_tokens: 0, cache_creation_tokens: 0 };
14
+ try {
15
+ const content = readFileSync(transcriptPath, "utf-8");
16
+ for (const line of content.split("\n")) {
17
+ const trimmed = line.trim();
18
+ if (!trimmed)
19
+ continue;
20
+ try {
21
+ const entry = JSON.parse(trimmed);
22
+ if (entry.type !== "assistant" || !entry.message?.usage)
23
+ continue;
24
+ const u = entry.message.usage;
25
+ zero.input_tokens += u.input_tokens ?? 0;
26
+ zero.output_tokens += u.output_tokens ?? 0;
27
+ zero.cache_read_tokens += u.cache_read_input_tokens ?? 0;
28
+ zero.cache_creation_tokens += u.cache_creation_input_tokens ?? 0;
29
+ }
30
+ catch {
31
+ // skip malformed lines
32
+ }
33
+ }
34
+ }
35
+ catch {
36
+ // file not found or unreadable
37
+ }
38
+ return zero;
39
+ }
40
+ export async function report(eventType) {
41
+ let stdinData = {};
42
+ try {
43
+ const chunks = [];
44
+ for await (const chunk of process.stdin) {
45
+ chunks.push(chunk);
46
+ }
47
+ const raw = Buffer.concat(chunks).toString("utf-8").trim();
48
+ if (raw)
49
+ stdinData = JSON.parse(raw);
50
+ }
51
+ catch {
52
+ // No stdin or invalid JSON
53
+ }
54
+ const sessionId = stdinData.session_id ?? "";
55
+ const cwd = stdinData.cwd ?? process.cwd();
56
+ const payload = {
57
+ session_id: sessionId,
58
+ cwd,
59
+ hook_event_name: EVENT_MAP[eventType] ?? eventType,
60
+ model: stdinData.model,
61
+ tool_name: stdinData.tool_name,
62
+ };
63
+ if (eventType === "session-end") {
64
+ const transcriptPath = stdinData.transcript_path;
65
+ if (transcriptPath) {
66
+ payload.usage = parseTranscriptTokens(transcriptPath);
67
+ }
68
+ }
69
+ const git = readGitState(cwd);
70
+ if (git) {
71
+ const gitPayload = {
72
+ branch: git.branch,
73
+ lastCommitSha: git.lastCommitSha,
74
+ lastCommitMessage: git.lastCommitMessage,
75
+ hasUncommittedChanges: git.hasUncommittedChanges,
76
+ };
77
+ if (eventType === "stop" || eventType === "session-end") {
78
+ const stats = readGitDiffStats(cwd);
79
+ gitPayload.filesChanged = stats.filesChanged;
80
+ gitPayload.insertions = stats.insertions;
81
+ gitPayload.deletions = stats.deletions;
82
+ }
83
+ payload.git = gitPayload;
84
+ }
85
+ await sendEvent(payload);
86
+ }
@@ -0,0 +1 @@
1
+ export declare function setup(): Promise<void>;
package/dist/setup.js ADDED
@@ -0,0 +1,61 @@
1
+ import { createInterface } from "node:readline";
2
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
3
+ import { join, dirname } from "node:path";
4
+ import { homedir } from "node:os";
5
+ import { writeConfig } from "./config.js";
6
+ function prompt(question) {
7
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
8
+ return new Promise((resolve) => {
9
+ rl.question(question, (answer) => {
10
+ rl.close();
11
+ resolve(answer.trim());
12
+ });
13
+ });
14
+ }
15
+ const HOOKS_CONFIG = {
16
+ SessionStart: [{ type: "command", command: "npx session-intelligence-cli report session-start" }],
17
+ Stop: [{ type: "command", command: "npx session-intelligence-cli report stop" }],
18
+ PostToolUse: [{ type: "command", command: "npx session-intelligence-cli report tool-use", timeout: 3000 }],
19
+ Notification: [{ type: "command", command: "npx session-intelligence-cli report notification" }],
20
+ SubagentStop: [{ type: "command", command: "npx session-intelligence-cli report subagent-stop", timeout: 3000 }],
21
+ };
22
+ export async function setup() {
23
+ console.log("\n Session Intelligence — Setup\n");
24
+ const serverUrl = await prompt(" Server URL (e.g. https://your-app.vercel.app): ");
25
+ const apiKey = await prompt(" API Key: ");
26
+ writeConfig({ apiKey, serverUrl });
27
+ console.log("\n Config saved to ~/.session-intelligence/config.json");
28
+ try {
29
+ const res = await fetch(`${serverUrl}/api/health`, {
30
+ method: "POST",
31
+ headers: { Authorization: `Bearer ${apiKey}` },
32
+ });
33
+ if (!res.ok)
34
+ throw new Error(`HTTP ${res.status}`);
35
+ console.log(" Server connection verified");
36
+ }
37
+ catch (e) {
38
+ console.warn(` Warning: Could not reach server (${e}). Check URL and try again.`);
39
+ }
40
+ const claudeSettingsPath = join(homedir(), ".claude", "settings.json");
41
+ let settings = {};
42
+ if (existsSync(claudeSettingsPath)) {
43
+ settings = JSON.parse(readFileSync(claudeSettingsPath, "utf-8"));
44
+ }
45
+ const existingHooks = (settings.hooks ?? {});
46
+ for (const [event, handlers] of Object.entries(HOOKS_CONFIG)) {
47
+ const existing = existingHooks[event] ?? [];
48
+ const alreadyInstalled = existing.some((h) => typeof h === "object" &&
49
+ h !== null &&
50
+ "command" in h &&
51
+ h.command.includes("session-intelligence"));
52
+ if (!alreadyInstalled) {
53
+ existingHooks[event] = [...existing, ...handlers];
54
+ }
55
+ }
56
+ settings.hooks = existingHooks;
57
+ mkdirSync(dirname(claudeSettingsPath), { recursive: true });
58
+ writeFileSync(claudeSettingsPath, JSON.stringify(settings, null, 2));
59
+ console.log(" Claude Code hooks installed to ~/.claude/settings.json");
60
+ console.log("\n Done! Your next Claude Code session will start reporting.\n");
61
+ }
@@ -0,0 +1 @@
1
+ export declare function status(): Promise<void>;
package/dist/status.js ADDED
@@ -0,0 +1,63 @@
1
+ import { readConfig } from "./config.js";
2
+ const STATUS_DISPLAY = {
3
+ thinking: { icon: "\u25CF", label: "thinking", color: "\x1b[32m" },
4
+ needs_input: { icon: "\u25D0", label: "needs_input", color: "\x1b[33m" },
5
+ idle: { icon: "\u25CB", label: "idle", color: "\x1b[90m" },
6
+ archived: { icon: "\u25CB", label: "archived", color: "\x1b[90m" },
7
+ };
8
+ const RESET = "\x1b[0m";
9
+ const BOLD = "\x1b[1m";
10
+ const DIM = "\x1b[2m";
11
+ function timeAgo(timestamp) {
12
+ const seconds = Math.floor((Date.now() - timestamp) / 1000);
13
+ if (seconds < 60)
14
+ return `${seconds}s ago`;
15
+ const minutes = Math.floor(seconds / 60);
16
+ if (minutes < 60)
17
+ return `${minutes}m ago`;
18
+ const hours = Math.floor(minutes / 60);
19
+ if (hours < 24)
20
+ return `${hours}h ago`;
21
+ const days = Math.floor(hours / 24);
22
+ return `${days}d ago`;
23
+ }
24
+ export async function status() {
25
+ const config = readConfig();
26
+ if (!config) {
27
+ console.error("Not configured. Run: session-intelligence setup");
28
+ process.exit(1);
29
+ }
30
+ let data;
31
+ try {
32
+ const res = await fetch(`${config.serverUrl}/api/sessions`, {
33
+ headers: { Authorization: `Bearer ${config.apiKey}` },
34
+ });
35
+ if (!res.ok) {
36
+ console.error(`Server returned ${res.status}`);
37
+ process.exit(1);
38
+ }
39
+ data = (await res.json());
40
+ }
41
+ catch {
42
+ console.error("Could not reach server");
43
+ process.exit(1);
44
+ }
45
+ const { sessions, limited } = data;
46
+ console.log(`${BOLD}Sessions (${sessions.length})${RESET}`);
47
+ console.log();
48
+ if (sessions.length === 0) {
49
+ console.log(` ${DIM}No active sessions${RESET}`);
50
+ console.log();
51
+ return;
52
+ }
53
+ for (const s of sessions) {
54
+ const display = STATUS_DISPLAY[s.status] ?? STATUS_DISPLAY.idle;
55
+ const branch = s.gitBranch ? `${DIM}${s.gitBranch}${RESET}` : "";
56
+ const ago = timeAgo(s.lastEventAt);
57
+ console.log(` ${display.color}${display.icon} ${display.label}${RESET} ${s.project} ${branch} ${DIM}${ago}${RESET}`);
58
+ }
59
+ if (limited) {
60
+ console.log();
61
+ console.log(`${DIM}More sessions available — upgrade to Pro${RESET}`);
62
+ }
63
+ }
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "session-intelligence-cli",
3
+ "version": "0.1.0",
4
+ "description": "CLI plugin for Session Intelligence — hooks into Claude Code to track sessions",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/0xleal/personal-dashboard.git",
10
+ "directory": "cli"
11
+ },
12
+ "bin": {
13
+ "session-intelligence": "./dist/index.js"
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "scripts": {
19
+ "build": "tsc",
20
+ "dev": "tsc --watch",
21
+ "prepublishOnly": "npm run build"
22
+ },
23
+ "devDependencies": {
24
+ "typescript": "^5"
25
+ },
26
+ "engines": {
27
+ "node": ">=18"
28
+ }
29
+ }