codepiper 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/.env.example +28 -0
- package/CHANGELOG.md +10 -0
- package/LEGAL_NOTICE.md +39 -0
- package/LICENSE +21 -0
- package/README.md +524 -0
- package/package.json +90 -0
- package/packages/cli/package.json +13 -0
- package/packages/cli/src/commands/analytics.ts +157 -0
- package/packages/cli/src/commands/attach.ts +299 -0
- package/packages/cli/src/commands/audit.ts +50 -0
- package/packages/cli/src/commands/auth.ts +261 -0
- package/packages/cli/src/commands/daemon.ts +162 -0
- package/packages/cli/src/commands/doctor.ts +303 -0
- package/packages/cli/src/commands/env-set.ts +162 -0
- package/packages/cli/src/commands/hook-forward.ts +268 -0
- package/packages/cli/src/commands/keys.ts +77 -0
- package/packages/cli/src/commands/kill.ts +19 -0
- package/packages/cli/src/commands/logs.ts +419 -0
- package/packages/cli/src/commands/model.ts +172 -0
- package/packages/cli/src/commands/policy-set.ts +185 -0
- package/packages/cli/src/commands/policy.ts +227 -0
- package/packages/cli/src/commands/providers.ts +114 -0
- package/packages/cli/src/commands/resize.ts +34 -0
- package/packages/cli/src/commands/send.ts +184 -0
- package/packages/cli/src/commands/sessions.ts +202 -0
- package/packages/cli/src/commands/slash.ts +92 -0
- package/packages/cli/src/commands/start.ts +243 -0
- package/packages/cli/src/commands/stop.ts +19 -0
- package/packages/cli/src/commands/tail.ts +137 -0
- package/packages/cli/src/commands/workflow.ts +786 -0
- package/packages/cli/src/commands/workspace.ts +127 -0
- package/packages/cli/src/lib/api.ts +78 -0
- package/packages/cli/src/lib/args.ts +72 -0
- package/packages/cli/src/lib/format.ts +93 -0
- package/packages/cli/src/main.ts +563 -0
- package/packages/core/package.json +7 -0
- package/packages/core/src/config.ts +30 -0
- package/packages/core/src/errors.ts +38 -0
- package/packages/core/src/eventBus.ts +56 -0
- package/packages/core/src/eventBusAdapter.ts +143 -0
- package/packages/core/src/index.ts +10 -0
- package/packages/core/src/sqliteEventBus.ts +336 -0
- package/packages/core/src/types.ts +63 -0
- package/packages/daemon/package.json +11 -0
- package/packages/daemon/src/api/analyticsRoutes.ts +343 -0
- package/packages/daemon/src/api/authRoutes.ts +344 -0
- package/packages/daemon/src/api/bodyLimit.ts +133 -0
- package/packages/daemon/src/api/envSetRoutes.ts +170 -0
- package/packages/daemon/src/api/gitRoutes.ts +409 -0
- package/packages/daemon/src/api/hooks.ts +588 -0
- package/packages/daemon/src/api/inputPolicy.ts +249 -0
- package/packages/daemon/src/api/notificationRoutes.ts +532 -0
- package/packages/daemon/src/api/policyRoutes.ts +234 -0
- package/packages/daemon/src/api/policySetRoutes.ts +445 -0
- package/packages/daemon/src/api/routeUtils.ts +28 -0
- package/packages/daemon/src/api/routes.ts +1004 -0
- package/packages/daemon/src/api/server.ts +1388 -0
- package/packages/daemon/src/api/settingsRoutes.ts +367 -0
- package/packages/daemon/src/api/sqliteErrors.ts +47 -0
- package/packages/daemon/src/api/stt.ts +143 -0
- package/packages/daemon/src/api/terminalRoutes.ts +200 -0
- package/packages/daemon/src/api/validation.ts +287 -0
- package/packages/daemon/src/api/validationRoutes.ts +174 -0
- package/packages/daemon/src/api/workflowRoutes.ts +567 -0
- package/packages/daemon/src/api/workspaceRoutes.ts +151 -0
- package/packages/daemon/src/api/ws.ts +1588 -0
- package/packages/daemon/src/auth/apiRateLimiter.ts +73 -0
- package/packages/daemon/src/auth/authMiddleware.ts +305 -0
- package/packages/daemon/src/auth/authService.ts +496 -0
- package/packages/daemon/src/auth/rateLimiter.ts +137 -0
- package/packages/daemon/src/config/pricing.ts +79 -0
- package/packages/daemon/src/crypto/encryption.ts +196 -0
- package/packages/daemon/src/db/db.ts +2745 -0
- package/packages/daemon/src/db/index.ts +16 -0
- package/packages/daemon/src/db/migrations.ts +182 -0
- package/packages/daemon/src/db/policyDb.ts +349 -0
- package/packages/daemon/src/db/schema.sql +408 -0
- package/packages/daemon/src/db/workflowDb.ts +464 -0
- package/packages/daemon/src/git/gitUtils.ts +544 -0
- package/packages/daemon/src/index.ts +6 -0
- package/packages/daemon/src/main.ts +525 -0
- package/packages/daemon/src/notifications/pushNotifier.ts +369 -0
- package/packages/daemon/src/providers/codexAppServerScaffold.ts +49 -0
- package/packages/daemon/src/providers/registry.ts +111 -0
- package/packages/daemon/src/providers/types.ts +82 -0
- package/packages/daemon/src/sessions/auditLogger.ts +103 -0
- package/packages/daemon/src/sessions/policyEngine.ts +165 -0
- package/packages/daemon/src/sessions/policyMatcher.ts +114 -0
- package/packages/daemon/src/sessions/policyTypes.ts +94 -0
- package/packages/daemon/src/sessions/ptyProcess.ts +141 -0
- package/packages/daemon/src/sessions/sessionManager.ts +1770 -0
- package/packages/daemon/src/sessions/tmuxSession.ts +1073 -0
- package/packages/daemon/src/sessions/transcriptManager.ts +110 -0
- package/packages/daemon/src/sessions/transcriptParser.ts +149 -0
- package/packages/daemon/src/sessions/transcriptTailer.ts +214 -0
- package/packages/daemon/src/tracking/tokenTracker.ts +168 -0
- package/packages/daemon/src/workflows/contextManager.ts +83 -0
- package/packages/daemon/src/workflows/index.ts +31 -0
- package/packages/daemon/src/workflows/resultExtractor.ts +118 -0
- package/packages/daemon/src/workflows/waitConditionPoller.ts +131 -0
- package/packages/daemon/src/workflows/workflowParser.ts +217 -0
- package/packages/daemon/src/workflows/workflowRunner.ts +969 -0
- package/packages/daemon/src/workflows/workflowTypes.ts +188 -0
- package/packages/daemon/src/workflows/workflowValidator.ts +533 -0
- package/packages/providers/claude-code/package.json +11 -0
- package/packages/providers/claude-code/src/index.ts +7 -0
- package/packages/providers/claude-code/src/overlaySettings.ts +198 -0
- package/packages/providers/claude-code/src/provider.ts +311 -0
- package/packages/web/dist/android-chrome-192x192.png +0 -0
- package/packages/web/dist/android-chrome-512x512.png +0 -0
- package/packages/web/dist/apple-touch-icon.png +0 -0
- package/packages/web/dist/assets/AnalyticsPage-BIopKWRf.js +17 -0
- package/packages/web/dist/assets/PoliciesPage-CjdLN3dl.js +11 -0
- package/packages/web/dist/assets/SessionDetailPage-BtSA0V0M.js +179 -0
- package/packages/web/dist/assets/SettingsPage-Dbbz4Ca5.js +37 -0
- package/packages/web/dist/assets/WorkflowsPage-Dv6f3GgU.js +1 -0
- package/packages/web/dist/assets/chart-vendor-DlOHLaCG.js +49 -0
- package/packages/web/dist/assets/codicon-ngg6Pgfi.ttf +0 -0
- package/packages/web/dist/assets/css.worker-BvV5MPou.js +93 -0
- package/packages/web/dist/assets/editor.worker-CKy7Pnvo.js +26 -0
- package/packages/web/dist/assets/html.worker-BLJhxQJQ.js +470 -0
- package/packages/web/dist/assets/index-BbdhRfr2.css +1 -0
- package/packages/web/dist/assets/index-hgphORiw.js +204 -0
- package/packages/web/dist/assets/json.worker-usMZ-FED.js +58 -0
- package/packages/web/dist/assets/monaco-core-B_19GPAS.css +1 -0
- package/packages/web/dist/assets/monaco-core-DQ5Mk8AK.js +1234 -0
- package/packages/web/dist/assets/monaco-react-DfZNWvtW.js +11 -0
- package/packages/web/dist/assets/monacoSetup-DvBj52bT.js +1 -0
- package/packages/web/dist/assets/pencil-Dbczxz59.js +11 -0
- package/packages/web/dist/assets/react-vendor-B5MgMUHH.js +136 -0
- package/packages/web/dist/assets/refresh-cw-B0MGsYPL.js +6 -0
- package/packages/web/dist/assets/tabs-C8LsWiR5.js +1 -0
- package/packages/web/dist/assets/terminal-vendor-Cs8KPbV3.js +9 -0
- package/packages/web/dist/assets/terminal-vendor-LcAfv9l9.css +32 -0
- package/packages/web/dist/assets/trash-2-Btlg0d4l.js +6 -0
- package/packages/web/dist/assets/ts.worker-DGHjMaqB.js +67731 -0
- package/packages/web/dist/favicon.ico +0 -0
- package/packages/web/dist/icon.svg +1 -0
- package/packages/web/dist/index.html +29 -0
- package/packages/web/dist/manifest.json +29 -0
- package/packages/web/dist/og-image.png +0 -0
- package/packages/web/dist/originals/android-chrome-192x192.png +0 -0
- package/packages/web/dist/originals/android-chrome-512x512.png +0 -0
- package/packages/web/dist/originals/apple-touch-icon.png +0 -0
- package/packages/web/dist/originals/favicon.ico +0 -0
- package/packages/web/dist/piper.svg +1 -0
- package/packages/web/dist/sounds/codepiper-soft-chime.wav +0 -0
- package/packages/web/dist/sw.js +257 -0
- package/scripts/postinstall-link-workspaces.mjs +58 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Overlay settings generator for Claude Code sessions
|
|
3
|
+
*
|
|
4
|
+
* Generates a per-session settings file that configures hooks and statusline
|
|
5
|
+
* to forward events to the codepiper daemon.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as fs from "node:fs";
|
|
9
|
+
import { mkdir } from "node:fs/promises";
|
|
10
|
+
import { tmpdir } from "node:os";
|
|
11
|
+
import { dirname, join } from "node:path";
|
|
12
|
+
import { fileURLToPath } from "node:url";
|
|
13
|
+
|
|
14
|
+
export interface OverlaySettingsOptions {
|
|
15
|
+
sessionId: string;
|
|
16
|
+
socketPath: string;
|
|
17
|
+
secret: string;
|
|
18
|
+
outputDir?: string;
|
|
19
|
+
enableStatusline?: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ClaudeCodeSettings {
|
|
23
|
+
hooks?: {
|
|
24
|
+
SessionStart?: HookEntry[];
|
|
25
|
+
Notification?: HookEntry[];
|
|
26
|
+
PermissionRequest?: HookEntry[];
|
|
27
|
+
Stop?: HookEntry[];
|
|
28
|
+
};
|
|
29
|
+
statusline?: {
|
|
30
|
+
command: string;
|
|
31
|
+
stdin?: string;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface HookEntry {
|
|
36
|
+
matcher?: Record<string, unknown>;
|
|
37
|
+
hooks: Hook[];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface Hook {
|
|
41
|
+
type: "command";
|
|
42
|
+
command: string;
|
|
43
|
+
stdin?: string;
|
|
44
|
+
stdout?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function shellQuote(value: string): string {
|
|
48
|
+
return `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function writeExecutableScript(filePath: string, content: string): void {
|
|
52
|
+
const fd = fs.openSync(filePath, "w", 0o700);
|
|
53
|
+
try {
|
|
54
|
+
fs.writeSync(fd, content);
|
|
55
|
+
} finally {
|
|
56
|
+
fs.closeSync(fd);
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
fs.chmodSync(filePath, 0o700);
|
|
60
|
+
} catch {
|
|
61
|
+
// best-effort on non-POSIX filesystems
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Generate overlay settings file for a Claude Code session
|
|
67
|
+
*
|
|
68
|
+
* The overlay configures:
|
|
69
|
+
* - Hooks to forward events to codepiper daemon
|
|
70
|
+
* - Environment variables for socket path, session ID, and auth secret
|
|
71
|
+
* - Optional statusline for session state tracking
|
|
72
|
+
*
|
|
73
|
+
* @param options Configuration for overlay generation
|
|
74
|
+
* @returns Path to generated settings file
|
|
75
|
+
*/
|
|
76
|
+
export async function generateOverlaySettings(options: OverlaySettingsOptions): Promise<string> {
|
|
77
|
+
const {
|
|
78
|
+
sessionId,
|
|
79
|
+
socketPath,
|
|
80
|
+
secret,
|
|
81
|
+
outputDir = join(tmpdir(), "codepiper", "settings"),
|
|
82
|
+
enableStatusline = false,
|
|
83
|
+
} = options;
|
|
84
|
+
|
|
85
|
+
// Ensure output directory exists with restrictive permissions (contains session secret)
|
|
86
|
+
await mkdir(outputDir, { recursive: true, mode: 0o700 });
|
|
87
|
+
try {
|
|
88
|
+
fs.chmodSync(outputDir, 0o700);
|
|
89
|
+
} catch {
|
|
90
|
+
// best-effort on non-POSIX filesystems
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Generate settings file path
|
|
94
|
+
const settingsPath = join(outputDir, `${sessionId}.json`);
|
|
95
|
+
|
|
96
|
+
// Build hook command using full path to CLI (not relying on PATH)
|
|
97
|
+
// Get project root: packages/providers/claude-code/src/overlaySettings.ts -> ../../../..
|
|
98
|
+
const currentFile = fileURLToPath(import.meta.url);
|
|
99
|
+
const currentDir = dirname(currentFile);
|
|
100
|
+
const projectRoot = join(currentDir, "..", "..", "..", "..");
|
|
101
|
+
const cliPath = join(projectRoot, "packages", "cli", "src", "main.ts");
|
|
102
|
+
const hookScriptPath = join(outputDir, `${sessionId}.hook-forward.sh`);
|
|
103
|
+
const hookCommand = `sh ${shellQuote(hookScriptPath)}`;
|
|
104
|
+
const hookScript = [
|
|
105
|
+
"#!/usr/bin/env sh",
|
|
106
|
+
"set -eu",
|
|
107
|
+
`export CODEPIPER_UNIX_SOCK=${shellQuote(socketPath)}`,
|
|
108
|
+
`export CODEPIPER_SESSION=${shellQuote(sessionId)}`,
|
|
109
|
+
`export CODEPIPER_SECRET=${shellQuote(secret)}`,
|
|
110
|
+
`exec bun run ${shellQuote(cliPath)} hook-forward`,
|
|
111
|
+
"",
|
|
112
|
+
].join("\n");
|
|
113
|
+
writeExecutableScript(hookScriptPath, hookScript);
|
|
114
|
+
|
|
115
|
+
// Build settings object using new hooks format (array with matchers)
|
|
116
|
+
const settings: ClaudeCodeSettings = {
|
|
117
|
+
hooks: {
|
|
118
|
+
SessionStart: [
|
|
119
|
+
{
|
|
120
|
+
hooks: [
|
|
121
|
+
{
|
|
122
|
+
type: "command",
|
|
123
|
+
command: hookCommand,
|
|
124
|
+
stdin: "event",
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
},
|
|
128
|
+
],
|
|
129
|
+
Notification: [
|
|
130
|
+
{
|
|
131
|
+
hooks: [
|
|
132
|
+
{
|
|
133
|
+
type: "command",
|
|
134
|
+
command: hookCommand,
|
|
135
|
+
stdin: "event",
|
|
136
|
+
},
|
|
137
|
+
],
|
|
138
|
+
},
|
|
139
|
+
],
|
|
140
|
+
PermissionRequest: [
|
|
141
|
+
{
|
|
142
|
+
hooks: [
|
|
143
|
+
{
|
|
144
|
+
type: "command",
|
|
145
|
+
command: hookCommand,
|
|
146
|
+
stdin: "event",
|
|
147
|
+
stdout: "context", // Claude Code reads decision from stdout
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
},
|
|
151
|
+
],
|
|
152
|
+
Stop: [
|
|
153
|
+
{
|
|
154
|
+
hooks: [
|
|
155
|
+
{
|
|
156
|
+
type: "command",
|
|
157
|
+
command: hookCommand,
|
|
158
|
+
stdin: "event",
|
|
159
|
+
},
|
|
160
|
+
],
|
|
161
|
+
},
|
|
162
|
+
],
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
// Add statusline if enabled
|
|
167
|
+
if (enableStatusline) {
|
|
168
|
+
const statuslineScriptPath = join(outputDir, `${sessionId}.statusline-forward.sh`);
|
|
169
|
+
const statuslineCommand = `sh ${shellQuote(statuslineScriptPath)}`;
|
|
170
|
+
const statuslineScript = [
|
|
171
|
+
"#!/usr/bin/env sh",
|
|
172
|
+
"set -eu",
|
|
173
|
+
`export CODEPIPER_UNIX_SOCK=${shellQuote(socketPath)}`,
|
|
174
|
+
`export CODEPIPER_SESSION=${shellQuote(sessionId)}`,
|
|
175
|
+
`export CODEPIPER_SECRET=${shellQuote(secret)}`,
|
|
176
|
+
"exec codepiper statusline-forward",
|
|
177
|
+
"",
|
|
178
|
+
].join("\n");
|
|
179
|
+
writeExecutableScript(statuslineScriptPath, statuslineScript);
|
|
180
|
+
|
|
181
|
+
settings.statusline = {
|
|
182
|
+
command: statuslineCommand,
|
|
183
|
+
stdin: "event",
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Write settings file with restrictive permissions (contains session secret)
|
|
188
|
+
const fd = fs.openSync(settingsPath, "w", 0o600);
|
|
189
|
+
fs.writeSync(fd, JSON.stringify(settings, null, 2));
|
|
190
|
+
fs.closeSync(fd);
|
|
191
|
+
try {
|
|
192
|
+
fs.chmodSync(settingsPath, 0o600);
|
|
193
|
+
} catch {
|
|
194
|
+
// best-effort on non-POSIX filesystems
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return settingsPath;
|
|
198
|
+
}
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code provider implementation
|
|
3
|
+
*
|
|
4
|
+
* Spawns and manages Claude Code sessions via PTY, configuring hooks
|
|
5
|
+
* for event forwarding to the CodePiper daemon.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { randomBytes } from "node:crypto";
|
|
9
|
+
import type {
|
|
10
|
+
Provider,
|
|
11
|
+
ProviderEvent,
|
|
12
|
+
ProviderId,
|
|
13
|
+
SessionHandle,
|
|
14
|
+
StartSessionOptions,
|
|
15
|
+
} from "@codepiper/core";
|
|
16
|
+
import { PTYProcess } from "@codepiper/daemon";
|
|
17
|
+
import { generateOverlaySettings } from "./overlaySettings";
|
|
18
|
+
|
|
19
|
+
interface ActiveSession {
|
|
20
|
+
handle: SessionHandle;
|
|
21
|
+
pty: PTYProcess;
|
|
22
|
+
settingsPath: string;
|
|
23
|
+
currentModel?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Key name to escape sequence mapping
|
|
28
|
+
*/
|
|
29
|
+
const KEY_SEQUENCES: Record<string, string> = {
|
|
30
|
+
// Basic keys
|
|
31
|
+
enter: "\r",
|
|
32
|
+
return: "\r",
|
|
33
|
+
tab: "\t",
|
|
34
|
+
"shift+tab": "\x1b[Z",
|
|
35
|
+
escape: "\x1b",
|
|
36
|
+
esc: "\x1b",
|
|
37
|
+
space: " ",
|
|
38
|
+
backspace: "\x7f",
|
|
39
|
+
delete: "\x1b[3~",
|
|
40
|
+
|
|
41
|
+
// Control keys
|
|
42
|
+
"ctrl+a": "\x01",
|
|
43
|
+
"ctrl+b": "\x02",
|
|
44
|
+
"ctrl+c": "\x03",
|
|
45
|
+
"ctrl+d": "\x04",
|
|
46
|
+
"ctrl+e": "\x05",
|
|
47
|
+
"ctrl+f": "\x06",
|
|
48
|
+
"ctrl+g": "\x07",
|
|
49
|
+
"ctrl+h": "\x08",
|
|
50
|
+
"ctrl+i": "\x09",
|
|
51
|
+
"ctrl+j": "\x0a",
|
|
52
|
+
"ctrl+k": "\x0b",
|
|
53
|
+
"ctrl+l": "\x0c",
|
|
54
|
+
"ctrl+m": "\x0d",
|
|
55
|
+
"ctrl+n": "\x0e",
|
|
56
|
+
"ctrl+o": "\x0f",
|
|
57
|
+
"ctrl+p": "\x10",
|
|
58
|
+
"ctrl+q": "\x11",
|
|
59
|
+
"ctrl+r": "\x12",
|
|
60
|
+
"ctrl+s": "\x13",
|
|
61
|
+
"ctrl+t": "\x14",
|
|
62
|
+
"ctrl+u": "\x15",
|
|
63
|
+
"ctrl+v": "\x16",
|
|
64
|
+
"ctrl+w": "\x17",
|
|
65
|
+
"ctrl+x": "\x18",
|
|
66
|
+
"ctrl+y": "\x19",
|
|
67
|
+
"ctrl+z": "\x1a",
|
|
68
|
+
|
|
69
|
+
// Arrow keys
|
|
70
|
+
up: "\x1b[A",
|
|
71
|
+
down: "\x1b[B",
|
|
72
|
+
right: "\x1b[C",
|
|
73
|
+
left: "\x1b[D",
|
|
74
|
+
|
|
75
|
+
// Function keys
|
|
76
|
+
f1: "\x1bOP",
|
|
77
|
+
f2: "\x1bOQ",
|
|
78
|
+
f3: "\x1bOR",
|
|
79
|
+
f4: "\x1bOS",
|
|
80
|
+
f5: "\x1b[15~",
|
|
81
|
+
f6: "\x1b[17~",
|
|
82
|
+
f7: "\x1b[18~",
|
|
83
|
+
f8: "\x1b[19~",
|
|
84
|
+
f9: "\x1b[20~",
|
|
85
|
+
f10: "\x1b[21~",
|
|
86
|
+
f11: "\x1b[23~",
|
|
87
|
+
f12: "\x1b[24~",
|
|
88
|
+
|
|
89
|
+
// Special
|
|
90
|
+
home: "\x1b[H",
|
|
91
|
+
end: "\x1b[F",
|
|
92
|
+
pageup: "\x1b[5~",
|
|
93
|
+
pagedown: "\x1b[6~",
|
|
94
|
+
insert: "\x1b[2~",
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export class ClaudeCodeProvider implements Provider {
|
|
98
|
+
public readonly id: ProviderId = "claude-code";
|
|
99
|
+
|
|
100
|
+
private sessions: Map<string, ActiveSession> = new Map();
|
|
101
|
+
private eventCallbacks: Array<(evt: ProviderEvent) => void> = [];
|
|
102
|
+
private socketPath: string;
|
|
103
|
+
private enableStatusline: boolean;
|
|
104
|
+
|
|
105
|
+
constructor(options?: {
|
|
106
|
+
socketPath?: string;
|
|
107
|
+
enableStatusline?: boolean;
|
|
108
|
+
}) {
|
|
109
|
+
this.socketPath = options?.socketPath || "/tmp/codepiper.sock";
|
|
110
|
+
this.enableStatusline = options?.enableStatusline ?? false;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async startSession(opts: StartSessionOptions): Promise<SessionHandle> {
|
|
114
|
+
const { id: sessionId, cwd, env, args = [], model, billingMode } = opts;
|
|
115
|
+
|
|
116
|
+
// Conditionally scrub ANTHROPIC_API_KEY based on billing mode
|
|
117
|
+
const cleanEnv = { ...env };
|
|
118
|
+
if ((billingMode ?? "subscription") === "subscription") {
|
|
119
|
+
delete cleanEnv.ANTHROPIC_API_KEY;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Generate per-session secret for hook authentication
|
|
123
|
+
const secret = randomBytes(32).toString("hex");
|
|
124
|
+
|
|
125
|
+
// Generate overlay settings file
|
|
126
|
+
const settingsPath = await generateOverlaySettings({
|
|
127
|
+
sessionId,
|
|
128
|
+
socketPath: this.socketPath,
|
|
129
|
+
secret,
|
|
130
|
+
enableStatusline: this.enableStatusline,
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// Build command arguments
|
|
134
|
+
const command = ["claude", "--session-id", sessionId, "--settings", settingsPath];
|
|
135
|
+
|
|
136
|
+
// Add model flag if specified
|
|
137
|
+
if (model) {
|
|
138
|
+
command.push("--model", model);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
command.push(...args);
|
|
142
|
+
|
|
143
|
+
// Create session handle
|
|
144
|
+
const handle: SessionHandle = {
|
|
145
|
+
id: sessionId,
|
|
146
|
+
provider: this.id,
|
|
147
|
+
cwd,
|
|
148
|
+
status: "STARTING",
|
|
149
|
+
createdAt: new Date(),
|
|
150
|
+
updatedAt: new Date(),
|
|
151
|
+
metadata: {
|
|
152
|
+
settingsPath,
|
|
153
|
+
secret,
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
// Spawn PTY process
|
|
158
|
+
const pty = new PTYProcess({
|
|
159
|
+
command,
|
|
160
|
+
cwd,
|
|
161
|
+
env: cleanEnv,
|
|
162
|
+
cols: 120,
|
|
163
|
+
rows: 30,
|
|
164
|
+
onData: (data) => {
|
|
165
|
+
this.emitEvent({
|
|
166
|
+
sessionId,
|
|
167
|
+
type: "pty_output",
|
|
168
|
+
timestamp: new Date(),
|
|
169
|
+
payload: { data },
|
|
170
|
+
});
|
|
171
|
+
},
|
|
172
|
+
onExit: (exitCode, signal) => {
|
|
173
|
+
this.emitEvent({
|
|
174
|
+
sessionId,
|
|
175
|
+
type: "pty_exit",
|
|
176
|
+
timestamp: new Date(),
|
|
177
|
+
payload: { exitCode, signal },
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// Update handle and clean up
|
|
181
|
+
const session = this.sessions.get(sessionId);
|
|
182
|
+
if (session) {
|
|
183
|
+
session.handle.status = exitCode === 0 ? "STOPPED" : "CRASHED";
|
|
184
|
+
session.handle.updatedAt = new Date();
|
|
185
|
+
this.sessions.delete(sessionId);
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// Update handle with PID
|
|
191
|
+
handle.pid = pty.pid;
|
|
192
|
+
|
|
193
|
+
// Store session
|
|
194
|
+
const activeSession: ActiveSession = {
|
|
195
|
+
handle,
|
|
196
|
+
pty,
|
|
197
|
+
settingsPath,
|
|
198
|
+
};
|
|
199
|
+
if (model !== undefined) {
|
|
200
|
+
activeSession.currentModel = model;
|
|
201
|
+
}
|
|
202
|
+
this.sessions.set(sessionId, activeSession);
|
|
203
|
+
|
|
204
|
+
return handle;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
async sendText(sessionId: string, text: string): Promise<void> {
|
|
208
|
+
const session = this.sessions.get(sessionId);
|
|
209
|
+
if (!session) {
|
|
210
|
+
throw new Error(`Session not found: ${sessionId}`);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
session.pty.write(text);
|
|
214
|
+
session.handle.updatedAt = new Date();
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
async sendKeys(sessionId: string, keys: string[]): Promise<void> {
|
|
218
|
+
const session = this.sessions.get(sessionId);
|
|
219
|
+
if (!session) {
|
|
220
|
+
throw new Error(`Session not found: ${sessionId}`);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
for (const key of keys) {
|
|
224
|
+
const sequence = KEY_SEQUENCES[key.toLowerCase()];
|
|
225
|
+
if (sequence) {
|
|
226
|
+
session.pty.write(sequence);
|
|
227
|
+
} else {
|
|
228
|
+
// If not a special key, write it directly
|
|
229
|
+
session.pty.write(key);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
session.handle.updatedAt = new Date();
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async stopSession(sessionId: string): Promise<void> {
|
|
237
|
+
const session = this.sessions.get(sessionId);
|
|
238
|
+
if (!session) {
|
|
239
|
+
throw new Error(`Session not found: ${sessionId}`);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
await session.pty.kill();
|
|
243
|
+
session.handle.status = "STOPPED";
|
|
244
|
+
session.handle.updatedAt = new Date();
|
|
245
|
+
|
|
246
|
+
this.sessions.delete(sessionId);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Switch the model for an active session
|
|
251
|
+
*
|
|
252
|
+
* Uses the /model slash command to change the model in Claude Code
|
|
253
|
+
*
|
|
254
|
+
* @param sessionId - Session ID
|
|
255
|
+
* @param model - Model name (e.g., "sonnet", "opus", "haiku", "opusplan")
|
|
256
|
+
*/
|
|
257
|
+
async switchModel(sessionId: string, model: string): Promise<void> {
|
|
258
|
+
const session = this.sessions.get(sessionId);
|
|
259
|
+
if (!session) {
|
|
260
|
+
throw new Error(`Session not found: ${sessionId}`);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Send /model slash command
|
|
264
|
+
const command = `/model ${model}\r`;
|
|
265
|
+
session.pty.write(command);
|
|
266
|
+
|
|
267
|
+
// Update current model
|
|
268
|
+
session.currentModel = model;
|
|
269
|
+
session.handle.updatedAt = new Date();
|
|
270
|
+
|
|
271
|
+
// Emit model switch event
|
|
272
|
+
this.emitEvent({
|
|
273
|
+
sessionId,
|
|
274
|
+
type: "model_switch",
|
|
275
|
+
timestamp: new Date(),
|
|
276
|
+
payload: { model },
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Get the current model for a session
|
|
282
|
+
*/
|
|
283
|
+
getCurrentModel(sessionId: string): string | undefined {
|
|
284
|
+
const session = this.sessions.get(sessionId);
|
|
285
|
+
return session?.currentModel;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
onEvent(cb: (evt: ProviderEvent) => void): void {
|
|
289
|
+
this.eventCallbacks.push(cb);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Get list of active session IDs (useful for testing)
|
|
294
|
+
*/
|
|
295
|
+
getSessions(): string[] {
|
|
296
|
+
return Array.from(this.sessions.keys());
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Emit event to all registered callbacks
|
|
301
|
+
*/
|
|
302
|
+
private emitEvent(evt: ProviderEvent): void {
|
|
303
|
+
for (const cb of this.eventCallbacks) {
|
|
304
|
+
try {
|
|
305
|
+
cb(evt);
|
|
306
|
+
} catch (error) {
|
|
307
|
+
console.error("Error in event callback:", error);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import{r as i,j as e}from"./react-vendor-B5MgMUHH.js";import{c as A,a as f,f as h,u as U,S as ne,b as le,d as ce,e as de,g as N,B as xe,M as me,A as he,Z as pe}from"./index-hgphORiw.js";import{R as g,A as B,C as E,X as w,Y as S,T as v,a as u,B as K,b as F,P as ue,d as fe,e as je}from"./chart-vendor-DlOHLaCG.js";import"./monaco-core-DQ5Mk8AK.js";/**
|
|
2
|
+
* @license lucide-react v0.564.0 - ISC
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the ISC license.
|
|
5
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/const ye=[["line",{x1:"12",x2:"12",y1:"2",y2:"22",key:"7eqyqh"}],["path",{d:"M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6",key:"1b0p4s"}]],ge=A("dollar-sign",ye);/**
|
|
7
|
+
* @license lucide-react v0.564.0 - ISC
|
|
8
|
+
*
|
|
9
|
+
* This source code is licensed under the ISC license.
|
|
10
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
11
|
+
*/const ve=[["path",{d:"M12 15V3",key:"m9g1x1"}],["path",{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4",key:"ih7n3h"}],["path",{d:"m7 10 5 5 5-5",key:"brsn70"}]],be=A("download",ve);/**
|
|
12
|
+
* @license lucide-react v0.564.0 - ISC
|
|
13
|
+
*
|
|
14
|
+
* This source code is licensed under the ISC license.
|
|
15
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
16
|
+
*/const ke=[["rect",{width:"20",height:"8",x:"2",y:"2",rx:"2",ry:"2",key:"ngkwjq"}],["rect",{width:"20",height:"8",x:"2",y:"14",rx:"2",ry:"2",key:"iecqi9"}],["line",{x1:"6",x2:"6.01",y1:"6",y2:"6",key:"16zg32"}],["line",{x1:"6",x2:"6.01",y1:"18",y2:"18",key:"nzw8ys"}]],Ne=A("server",ke),l=i.forwardRef(({className:t,...a},s)=>e.jsx("div",{ref:s,className:f("rounded-lg border bg-card text-card-foreground shadow-sm",t),...a}));l.displayName="Card";const c=i.forwardRef(({className:t,...a},s)=>e.jsx("div",{ref:s,className:f("flex flex-col space-y-1.5 p-6",t),...a}));c.displayName="CardHeader";const d=i.forwardRef(({className:t,...a},s)=>e.jsx("h3",{ref:s,className:f("text-2xl font-semibold leading-none tracking-tight",t),...a}));d.displayName="CardTitle";const Ce=i.forwardRef(({className:t,...a},s)=>e.jsx("p",{ref:s,className:f("text-sm text-muted-foreground",t),...a}));Ce.displayName="CardDescription";const x=i.forwardRef(({className:t,...a},s)=>e.jsx("div",{ref:s,className:f("p-6 pt-0",t),...a}));x.displayName="CardContent";const we=i.forwardRef(({className:t,...a},s)=>e.jsx("div",{ref:s,className:f("flex items-center p-6 pt-0",t),...a}));we.displayName="CardFooter";function Se({data:t}){return t.length===0?e.jsxs(l,{children:[e.jsx(c,{children:e.jsx(d,{className:"text-base",children:"Activity Timeline"})}),e.jsx(x,{children:e.jsx("div",{className:"flex h-[280px] items-center justify-center text-sm text-muted-foreground/50",children:"No activity data yet"})})]}):e.jsxs(l,{children:[e.jsx(c,{children:e.jsx(d,{className:"text-base",children:"Activity Timeline"})}),e.jsx(x,{children:e.jsx(g,{width:"100%",height:280,children:e.jsxs(B,{data:t,children:[e.jsxs("defs",{children:[e.jsxs("linearGradient",{id:"userGrad",x1:"0",y1:"0",x2:"0",y2:"1",children:[e.jsx("stop",{offset:"5%",stopColor:"#06b6d4",stopOpacity:.3}),e.jsx("stop",{offset:"95%",stopColor:"#06b6d4",stopOpacity:0})]}),e.jsxs("linearGradient",{id:"assistantGrad",x1:"0",y1:"0",x2:"0",y2:"1",children:[e.jsx("stop",{offset:"5%",stopColor:"#8b5cf6",stopOpacity:.3}),e.jsx("stop",{offset:"95%",stopColor:"#8b5cf6",stopOpacity:0})]})]}),e.jsx(E,{strokeDasharray:"3 3",stroke:"hsl(var(--border))",strokeOpacity:.3}),e.jsx(w,{dataKey:"date",stroke:"hsl(var(--muted-foreground))",fontSize:11,tickLine:!1,axisLine:!1,tickFormatter:a=>{const s=new Date(a);return`${s.getMonth()+1}/${s.getDate()}`}}),e.jsx(S,{stroke:"hsl(var(--muted-foreground))",fontSize:11,tickLine:!1,axisLine:!1,allowDecimals:!1}),e.jsx(v,{contentStyle:{backgroundColor:"hsl(var(--card))",border:"1px solid hsl(var(--border))",borderRadius:"8px",fontSize:"12px",color:"hsl(var(--foreground))"},labelFormatter:a=>`Date: ${a}`}),e.jsx(u,{type:"monotone",dataKey:"user_messages",name:"User messages",stroke:"#06b6d4",strokeWidth:2,fill:"url(#userGrad)"}),e.jsx(u,{type:"monotone",dataKey:"assistant_messages",name:"Assistant messages",stroke:"#8b5cf6",strokeWidth:2,fill:"url(#assistantGrad)"})]})})})]})}function Re({data:t}){return t.length===0?e.jsxs(l,{children:[e.jsx(c,{children:e.jsx(d,{className:"text-base",children:"Sessions by Provider"})}),e.jsx(x,{children:e.jsx("div",{className:"flex h-[280px] items-center justify-center text-sm text-muted-foreground/50",children:"No session data yet"})})]}):e.jsxs(l,{children:[e.jsx(c,{children:e.jsx(d,{className:"text-base",children:"Sessions by Provider"})}),e.jsx(x,{children:e.jsx(g,{width:"100%",height:280,children:e.jsxs(K,{data:t,children:[e.jsx(w,{dataKey:"provider",stroke:"hsl(var(--muted-foreground))",fontSize:11,tickLine:!1,axisLine:!1}),e.jsx(S,{stroke:"hsl(var(--muted-foreground))",fontSize:11,tickLine:!1,axisLine:!1,allowDecimals:!1}),e.jsx(v,{contentStyle:{backgroundColor:"hsl(var(--card))",border:"1px solid hsl(var(--border))",borderRadius:"8px",fontSize:"12px",color:"hsl(var(--foreground))"},formatter:a=>[`${a??0} sessions`,"Count"]}),e.jsx(F,{dataKey:"count",fill:"#8b5cf6",radius:[4,4,0,0],maxBarSize:48})]})})})]})}function y({title:t,value:a,subtitle:s,icon:r}){return e.jsxs("div",{className:"rounded-xl border border-border bg-card backdrop-blur-sm p-5",children:[e.jsxs("div",{className:"flex items-center justify-between mb-3",children:[e.jsx("span",{className:"text-xs font-medium text-muted-foreground uppercase tracking-wider",children:t}),r]}),e.jsx("div",{className:"text-2xl font-bold tracking-tight",children:a}),s&&e.jsx("p",{className:"text-xs text-muted-foreground/60 mt-1",children:s})]})}const C=["#06b6d4","#8b5cf6","#10b981","#f59e0b","#ef4444","#ec4899"];function Te(t){return t.replace("claude-","").replace("-20250929","").replace("-20251001","").replace("-20260214","")}function Le({data:t}){if(t.length===0)return e.jsxs(l,{children:[e.jsx(c,{children:e.jsx(d,{className:"text-base",children:"Tokens by Model"})}),e.jsx(x,{children:e.jsx("div",{className:"flex h-[280px] items-center justify-center text-sm text-muted-foreground/50",children:"No model data yet"})})]});const a=t.map(r=>({...r,usedTokens:(r.prompt_tokens??0)+(r.completion_tokens??0)})),s=a.reduce((r,p)=>r+p.usedTokens,0);return e.jsxs(l,{children:[e.jsx(c,{children:e.jsx(d,{className:"text-base",children:"Tokens by Model"})}),e.jsx(x,{children:e.jsxs("div",{className:"flex items-center gap-6",children:[e.jsx(g,{width:180,height:180,children:e.jsxs(ue,{children:[e.jsx(fe,{data:a,dataKey:"usedTokens",nameKey:"model",cx:"50%",cy:"50%",innerRadius:50,outerRadius:80,strokeWidth:2,stroke:"hsl(var(--card))",children:a.map((r,p)=>e.jsx(je,{fill:C[p%C.length]},r.model))}),e.jsx(v,{contentStyle:{backgroundColor:"hsl(var(--card))",border:"1px solid hsl(var(--border))",borderRadius:"8px",fontSize:"12px",color:"hsl(var(--foreground))"},formatter:r=>h(r??0)})]})}),e.jsx("div",{className:"flex-1 space-y-2.5",children:a.map((r,p)=>{const b=r.usedTokens,R=s>0?(b/s*100).toFixed(1):"0";return e.jsxs("div",{className:"flex items-center gap-2.5",children:[e.jsx("div",{className:"h-2.5 w-2.5 rounded-full shrink-0",style:{backgroundColor:C[p%C.length]}}),e.jsxs("div",{className:"flex-1 min-w-0",children:[e.jsx("div",{className:"text-xs font-mono truncate",children:Te(r.model)}),e.jsxs("div",{className:"text-[10px] text-muted-foreground/60",children:[h(b)," tokens (",R,"%)",(r.cache_read??0)>0&&e.jsxs("span",{children:[" · ",h(r.cache_read)," cached"]}),e.jsxs("span",{children:[" · ",r.requests," req"]}),r.costEstimate!=null&&r.costEstimate>0&&e.jsxs("span",{children:[" · ~$",r.costEstimate.toFixed(2)]})]})]})]},r.model)})})]})})]})}const n={prompt:"#06b6d4",completion:"#8b5cf6",cacheCreation:"#f59e0b",cacheRead:"#10b981"};function De({data:t}){return t.length===0?e.jsxs(l,{children:[e.jsx(c,{children:e.jsx(d,{className:"text-base",children:"Token Usage Over Time"})}),e.jsx(x,{children:e.jsx("div",{className:"flex h-[280px] items-center justify-center text-sm text-muted-foreground/50",children:"No token data yet"})})]}):e.jsxs(l,{children:[e.jsx(c,{children:e.jsx(d,{className:"text-base",children:"Token Usage Over Time"})}),e.jsxs(x,{children:[e.jsx(g,{width:"100%",height:280,children:e.jsxs(B,{data:t,children:[e.jsxs("defs",{children:[e.jsxs("linearGradient",{id:"promptGrad",x1:"0",y1:"0",x2:"0",y2:"1",children:[e.jsx("stop",{offset:"5%",stopColor:n.prompt,stopOpacity:.3}),e.jsx("stop",{offset:"95%",stopColor:n.prompt,stopOpacity:0})]}),e.jsxs("linearGradient",{id:"completionGrad",x1:"0",y1:"0",x2:"0",y2:"1",children:[e.jsx("stop",{offset:"5%",stopColor:n.completion,stopOpacity:.3}),e.jsx("stop",{offset:"95%",stopColor:n.completion,stopOpacity:0})]}),e.jsxs("linearGradient",{id:"cacheCreateGrad",x1:"0",y1:"0",x2:"0",y2:"1",children:[e.jsx("stop",{offset:"5%",stopColor:n.cacheCreation,stopOpacity:.3}),e.jsx("stop",{offset:"95%",stopColor:n.cacheCreation,stopOpacity:0})]}),e.jsxs("linearGradient",{id:"cacheReadGrad",x1:"0",y1:"0",x2:"0",y2:"1",children:[e.jsx("stop",{offset:"5%",stopColor:n.cacheRead,stopOpacity:.3}),e.jsx("stop",{offset:"95%",stopColor:n.cacheRead,stopOpacity:0})]})]}),e.jsx(E,{strokeDasharray:"3 3",stroke:"hsl(var(--border))",strokeOpacity:.3}),e.jsx(w,{dataKey:"date",stroke:"hsl(var(--muted-foreground))",fontSize:11,tickLine:!1,axisLine:!1,tickFormatter:a=>{const s=new Date(a);return`${s.getMonth()+1}/${s.getDate()}`}}),e.jsx(S,{stroke:"hsl(var(--muted-foreground))",fontSize:11,tickLine:!1,axisLine:!1,allowDecimals:!1,tickFormatter:a=>h(a)}),e.jsx(v,{contentStyle:{backgroundColor:"hsl(var(--card))",border:"1px solid hsl(var(--border))",borderRadius:"8px",fontSize:"12px",color:"hsl(var(--foreground))"},formatter:a=>h(a??0),labelFormatter:a=>`Date: ${a}`}),e.jsx(u,{type:"monotone",dataKey:"prompt",stackId:"1",stroke:n.prompt,strokeWidth:2,fill:"url(#promptGrad)",name:"Prompt"}),e.jsx(u,{type:"monotone",dataKey:"completion",stackId:"1",stroke:n.completion,strokeWidth:2,fill:"url(#completionGrad)",name:"Completion"}),e.jsx(u,{type:"monotone",dataKey:"cacheCreation",stackId:"1",stroke:n.cacheCreation,strokeWidth:2,fill:"url(#cacheCreateGrad)",name:"Cache Creation"}),e.jsx(u,{type:"monotone",dataKey:"cacheRead",stackId:"1",stroke:n.cacheRead,strokeWidth:2,fill:"url(#cacheReadGrad)",name:"Cache Read"})]})}),e.jsx("div",{className:"mt-3 flex justify-center gap-5 text-xs",children:Object.entries(n).map(([a,s])=>e.jsxs("div",{className:"flex items-center gap-1.5",children:[e.jsx("div",{className:"h-2 w-2 rounded-full",style:{backgroundColor:s}}),e.jsx("span",{className:"text-muted-foreground capitalize",children:a.replace(/([A-Z])/g," $1").trim()})]},a))})]})]})}function Ae({data:t}){return t.length===0?e.jsxs(l,{children:[e.jsx(c,{children:e.jsx(d,{className:"text-base",children:"Tool Usage"})}),e.jsx(x,{children:e.jsx("div",{className:"flex h-[280px] items-center justify-center text-sm text-muted-foreground/50",children:"No tool usage data yet"})})]}):e.jsxs(l,{children:[e.jsx(c,{children:e.jsx(d,{className:"text-base",children:"Tool Usage"})}),e.jsx(x,{children:e.jsx(g,{width:"100%",height:280,children:e.jsxs(K,{data:t,layout:"vertical",margin:{left:60},children:[e.jsx(w,{type:"number",stroke:"hsl(var(--muted-foreground))",fontSize:11,tickLine:!1,axisLine:!1,allowDecimals:!1}),e.jsx(S,{type:"category",dataKey:"tool",stroke:"hsl(var(--muted-foreground))",fontSize:10,tickLine:!1,axisLine:!1,width:55}),e.jsx(v,{contentStyle:{backgroundColor:"hsl(var(--card))",border:"1px solid hsl(var(--border))",borderRadius:"8px",fontSize:"12px",color:"hsl(var(--foreground))"},formatter:a=>[`${a??0} calls`,"Usage"]}),e.jsx(F,{dataKey:"count",fill:"#06b6d4",radius:[0,4,4,0],maxBarSize:24})]})})})]})}function Me(){const[t,a]=i.useState("7d"),[s,r]=i.useState(null),[p,b]=i.useState([]),[R,_]=i.useState([]),[$,q]=i.useState([]),[H,I]=i.useState([]),[O,W]=i.useState([]),[V,Z]=i.useState([]),[X,P]=i.useState(!0),G=i.useCallback(async()=>{P(!0);try{const[o,T,L,D,k,j,m]=await Promise.all([fetch(`/api/analytics/overview?range=${t}`),fetch(`/api/analytics/activity-timeline?range=${t}`),fetch(`/api/analytics/tokens-by-model?range=${t}`),fetch(`/api/analytics/token-usage?range=${t}`),fetch(`/api/analytics/tool-usage?range=${t}`),fetch(`/api/analytics/sessions-by-provider?range=${t}`),fetch("/api/providers")]);if(!o.ok)throw new Error("Failed to load overview");const[se,te,ae,re,oe,ie,z]=await Promise.all([o.json(),T.json(),L.json(),D.json(),k.json(),j.json(),m.ok?m.json():Promise.resolve({providers:[]})]);r(se),b(te),_(ae),q(re),I(oe),W(ie),Z(Array.isArray(z.providers)?z.providers:[])}catch(o){console.error("Error loading analytics:",o),U.error("Failed to load analytics data")}finally{P(!1)}},[t]);i.useEffect(()=>{G()},[G]);const Y=()=>{const o=["Date","Prompt Tokens","Completion Tokens","Cache Read","Cache Creation"],T=$.map(m=>[m.date,m.prompt,m.completion,m.cacheRead,m.cacheCreation]),L=[o,...T].map(m=>m.join(",")).join(`
|
|
17
|
+
`),D=new Blob([L],{type:"text/csv"}),k=URL.createObjectURL(D),j=document.createElement("a");j.href=k,j.download=`analytics-${t}.csv`,j.click(),URL.revokeObjectURL(k),U.success("CSV exported successfully")};if(X||!s)return e.jsxs("div",{className:"p-4 md:p-8 max-w-7xl mx-auto",children:[e.jsx("div",{className:"mb-6 flex items-center justify-between",children:e.jsxs("div",{children:[e.jsx("h1",{className:"text-2xl md:text-3xl font-bold tracking-tight",children:"Analytics"}),e.jsx("p",{className:"text-sm text-muted-foreground mt-1",children:"Token usage, activity patterns, and session metrics"})]})}),e.jsx("div",{className:"flex h-64 items-center justify-center",children:e.jsxs("div",{className:"flex items-center gap-3 text-muted-foreground",children:[e.jsx("div",{className:"w-4 h-4 border-2 border-cyan-500/30 border-t-cyan-500 rounded-full animate-spin"}),e.jsx("span",{className:"text-sm",children:"Loading analytics..."})]})})]});const J=new Map(V.map(o=>[o.id,o])),M=O.filter(o=>Number(o.count)>0).map(o=>J.get(String(o.provider))).filter(o=>!!o&&o.capabilities.metricsChannel!=="transcript"),Q=M.length>0,ee=Array.from(new Set(M.map(o=>o.label)));return e.jsxs("div",{className:"p-4 md:p-8 max-w-7xl mx-auto",children:[e.jsxs("div",{className:"mb-6 flex items-center justify-between",children:[e.jsxs("div",{children:[e.jsx("h1",{className:"text-2xl md:text-3xl font-bold tracking-tight",children:"Analytics"}),e.jsx("p",{className:"text-sm text-muted-foreground mt-1",children:"Token usage, activity patterns, and session metrics"})]}),e.jsxs("div",{className:"flex gap-3",children:[e.jsxs(ne,{value:t,onValueChange:a,children:[e.jsx(le,{className:"w-36 border-border bg-muted/30",children:e.jsx(ce,{})}),e.jsxs(de,{children:[e.jsx(N,{value:"today",children:"Today"}),e.jsx(N,{value:"7d",children:"Last 7 Days"}),e.jsx(N,{value:"30d",children:"Last 30 Days"}),e.jsx(N,{value:"all",children:"All Time"})]})]}),e.jsxs(xe,{variant:"outline",onClick:Y,className:"border-border hover:bg-accent/60",children:[e.jsx(be,{className:"h-3.5 w-3.5 mr-1.5"}),"Export"]})]})]}),e.jsxs("div",{className:"mb-6 grid grid-cols-2 gap-3 md:gap-4 md:grid-cols-3 lg:grid-cols-5",children:[e.jsx(y,{title:"Total Sessions",value:s.sessionsCount,subtitle:`${s.activeSessions} active`,icon:e.jsx(Ne,{className:"h-4 w-4 text-cyan-400"})}),e.jsx(y,{title:"Total Messages",value:h(s.totalMessages),subtitle:"User + assistant turns",icon:e.jsx(me,{className:"h-4 w-4 text-emerald-400"})}),e.jsx(y,{title:"Tokens Used",value:h(s.inputTokens!=null?s.inputTokens+s.outputTokens:s.totalTokens),subtitle:(s.cacheReadTokens??0)>0?`${h(s.cacheReadTokens)} cached`:void 0,icon:e.jsx(he,{className:"h-4 w-4 text-violet-400"})}),e.jsx(y,{title:"Cache Hit Rate",value:`${s.cacheHitRate}%`,subtitle:s.cacheHitRate>=50?"Excellent":s.cacheHitRate>=20?"Good":"Low",icon:e.jsx(pe,{className:"h-4 w-4 text-amber-400"})}),e.jsx(y,{title:"Est. API Cost",value:`$${s.costEstimate.toFixed(2)}`,subtitle:"Equivalent if billed per-token",icon:e.jsx(ge,{className:"h-4 w-4 text-emerald-400"})})]}),s.costEstimate>0&&e.jsx("div",{className:"mb-6 rounded-lg border border-border/60 bg-muted/20 px-4 py-3",children:e.jsxs("p",{className:"text-xs text-muted-foreground/70",children:[e.jsx("span",{className:"font-medium text-muted-foreground",children:"Cost estimate disclaimer:"})," ","This is an approximate equivalent API cost based on published Anthropic pricing. Actual costs may differ due to long-context pricing (>200K tokens), internal requests not captured in transcripts (e.g. context summarization), and pricing changes. Max plan subscribers are not charged per-token."]})}),Q&&e.jsx("div",{className:"mb-6 rounded-lg border border-amber-500/40 bg-amber-500/5 px-4 py-3",children:e.jsxs("p",{className:"text-xs text-muted-foreground/80",children:[e.jsx("span",{className:"font-medium text-amber-300",children:"Provider metrics note:"})," Token and tool analytics are transcript-driven. Active sessions from"," ",ee.join(", ")," may not fully appear in token charts."]})}),e.jsxs("div",{className:"grid gap-4 lg:grid-cols-2",children:[e.jsx("div",{className:"lg:col-span-2",children:e.jsx(Se,{data:p})}),e.jsx(Le,{data:R}),e.jsx(Ae,{data:H}),e.jsx("div",{className:"lg:col-span-2",children:e.jsx(De,{data:$})}),e.jsx(Re,{data:O})]})]})}export{Me as AnalyticsPage};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import{r as c,j as e}from"./react-vendor-B5MgMUHH.js";import{c as z,S as V,b as O,d as U,e as q,g as y,h as v,i as B,j as f,u,B as p,P as D,I as j,D as S,k as $,l as F,m as I,n as E,o as R,p as T,C as X,q as J,X as K,r as L}from"./index-hgphORiw.js";import{T as _}from"./trash-2-Btlg0d4l.js";import{P as Q,L as k}from"./pencil-Dbczxz59.js";import{T as Y,a as Z,b as C,c as P}from"./tabs-C8LsWiR5.js";import"./monaco-core-DQ5Mk8AK.js";import"./chart-vendor-DlOHLaCG.js";/**
|
|
2
|
+
* @license lucide-react v0.564.0 - ISC
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the ISC license.
|
|
5
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/const ee=[["path",{d:"M12 3v18",key:"108xh3"}],["path",{d:"m19 8 3 8a5 5 0 0 1-6 0zV7",key:"zcdpyk"}],["path",{d:"M3 7h1a17 17 0 0 0 8-2 17 17 0 0 0 8 2h1",key:"1yorad"}],["path",{d:"m5 8 3 8a5 5 0 0 1-6 0zV7",key:"eua70x"}],["path",{d:"M7 21h10",key:"1b0cd5"}]],se=z("scale",ee);/**
|
|
7
|
+
* @license lucide-react v0.564.0 - ISC
|
|
8
|
+
*
|
|
9
|
+
* This source code is licensed under the ISC license.
|
|
10
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
11
|
+
*/const te=[["path",{d:"M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z",key:"r04s7s"}]],M=z("star",te);function re(){const[s,h]=c.useState([]),[n,i]=c.useState(!0),[m,a]=c.useState("all");c.useEffect(()=>{(async()=>{i(!0);try{const x=await f.getPolicyDecisions({decision:m==="all"?void 0:m,limit:100});h(x.decisions)}catch{u.error("Failed to load audit log")}finally{i(!1)}})()},[m]);const r=t=>{switch(t){case"allow":return"success";case"deny":return"destructive";case"ask":return"warning";default:return"secondary"}};return e.jsxs("div",{className:"space-y-4",children:[e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsx("p",{className:"text-sm text-muted-foreground",children:"Recent permission decisions across all sessions."}),e.jsxs(V,{value:m,onValueChange:t=>a(t),children:[e.jsx(O,{className:"w-32 h-8 text-xs border-border bg-muted/30",children:e.jsx(U,{})}),e.jsxs(q,{children:[e.jsx(y,{value:"all",children:"All"}),e.jsx(y,{value:"allow",children:"Allow"}),e.jsx(y,{value:"deny",children:"Deny"}),e.jsx(y,{value:"ask",children:"Ask"})]})]})]}),n?e.jsxs("div",{className:"flex items-center gap-2 text-muted-foreground py-8 justify-center",children:[e.jsx("div",{className:"w-3 h-3 border-2 border-cyan-500/30 border-t-cyan-500 rounded-full animate-spin"}),e.jsx("span",{className:"text-xs",children:"Loading audit log..."})]}):s.length===0?e.jsx("div",{className:"text-center py-12 text-muted-foreground/50 text-sm",children:"No policy decisions recorded yet."}):e.jsxs("div",{className:"rounded-xl border border-border overflow-hidden bg-card/80 backdrop-blur-sm",children:[e.jsxs("div",{className:"grid grid-cols-[80px_100px_1fr_80px_1fr_100px] gap-3 px-4 py-2.5 border-b border-border text-[11px] font-medium text-muted-foreground uppercase tracking-wider",children:[e.jsx("div",{children:"Decision"}),e.jsx("div",{children:"Session"}),e.jsx("div",{children:"Tool"}),e.jsx("div",{children:"Policy"}),e.jsx("div",{children:"Reason"}),e.jsx("div",{children:"Time"})]}),s.map(t=>e.jsxs("div",{className:"grid grid-cols-[80px_100px_1fr_80px_1fr_100px] gap-3 px-4 py-2.5 border-b border-border/60 last:border-0 text-sm items-center",children:[e.jsx("div",{children:e.jsx(v,{variant:r(t.decision),className:"text-[10px]",children:t.decision})}),e.jsx("div",{className:"font-mono text-xs text-muted-foreground truncate",children:t.sessionId.slice(0,8)}),e.jsx("div",{className:"font-mono text-xs truncate",children:t.toolName}),e.jsx("div",{className:"font-mono text-[10px] text-muted-foreground/50 truncate",children:t.policyId?t.policyId.slice(0,8):"-"}),e.jsx("div",{className:"text-xs text-muted-foreground truncate",children:t.reason||"-"}),e.jsx("div",{className:"text-[11px] text-muted-foreground",children:B(t.timestamp)})]},t.id))]})]})}function G({rules:s,onChange:h}){const n=()=>{h([...s,{id:crypto.randomUUID(),action:"allow",tool:"",reason:""}])},i=(a,r)=>{const t=[...s];t[a]={...t[a],...r},h(t)},m=a=>{h(s.filter((r,t)=>t!==a))};return e.jsxs("div",{className:"space-y-3",children:[e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsx("p",{className:"text-xs font-medium text-muted-foreground uppercase tracking-wider",children:"Rules"}),e.jsxs(p,{type:"button",variant:"ghost",size:"sm",onClick:n,className:"h-7 text-xs",children:[e.jsx(D,{className:"h-3 w-3 mr-1"}),"Add Rule"]})]}),s.length===0&&e.jsx("p",{className:"text-xs text-muted-foreground/50 text-center py-4 border border-dashed border-border rounded-lg",children:"No rules yet. Add a rule to define permission behavior."}),s.map((a,r)=>e.jsxs("div",{className:"grid grid-cols-[100px_1fr_1fr_32px] gap-2 items-start p-3 rounded-lg border border-border bg-muted/20",children:[e.jsxs(V,{value:a.action,onValueChange:t=>i(r,{action:t}),children:[e.jsx(O,{className:"h-8 text-xs border-border bg-muted/30",children:e.jsx(U,{})}),e.jsxs(q,{children:[e.jsx(y,{value:"allow",children:"Allow"}),e.jsx(y,{value:"deny",children:"Deny"}),e.jsx(y,{value:"ask",children:"Ask"})]})]}),e.jsx(j,{placeholder:"Tool pattern (e.g. Bash, Write)",value:typeof a.tool=="string"?a.tool:(a.tool||[]).join(", "),onChange:t=>i(r,{tool:t.target.value}),className:"h-8 text-xs border-border bg-muted/30 font-mono placeholder:text-muted-foreground/30"}),e.jsx(j,{placeholder:"Reason (optional)",value:a.reason||"",onChange:t=>i(r,{reason:t.target.value}),className:"h-8 text-xs border-border bg-muted/30 placeholder:text-muted-foreground/30"}),e.jsx(p,{type:"button",variant:"ghost",size:"sm",className:"h-8 w-8 p-0 text-muted-foreground hover:text-red-400",onClick:()=>m(r),children:e.jsx(_,{className:"h-3.5 w-3.5"})})]},a.id))]})}function ae({onCreated:s}){const[h,n]=c.useState(!1),[i,m]=c.useState(!1),[a,r]=c.useState({name:"",description:"",priority:0,enabled:!0,rules:[]}),t=async()=>{if(!a.name.trim()){u.error("Policy name is required");return}if(a.rules.length===0){u.error("At least one rule is required");return}try{m(!0),await f.createPolicy({id:crypto.randomUUID(),name:a.name,description:a.description||void 0,priority:a.priority,enabled:a.enabled,rules:a.rules}),u.success("Policy created"),n(!1),r({name:"",description:"",priority:0,enabled:!0,rules:[]}),s()}catch(x){u.error(x instanceof Error?x.message:"Failed to create policy")}finally{m(!1)}};return e.jsxs(S,{open:h,onOpenChange:n,children:[e.jsx($,{asChild:!0,children:e.jsxs(p,{className:"bg-cyan-600 hover:bg-cyan-700 text-white border-0",children:[e.jsx(D,{className:"h-4 w-4 mr-1.5"}),"New Policy"]})}),e.jsxs(F,{className:"border-border bg-popover max-w-2xl max-h-[85vh] overflow-y-auto mx-4 sm:mx-auto",children:[e.jsxs(I,{children:[e.jsx(E,{children:"Create Policy"}),e.jsx(R,{className:"text-muted-foreground/60",children:"Define permission rules for tool access control."})]}),e.jsxs("div",{className:"grid gap-4 py-4",children:[e.jsxs("div",{className:"grid grid-cols-1 sm:grid-cols-2 gap-4",children:[e.jsxs("div",{className:"grid gap-2",children:[e.jsx("label",{htmlFor:"create-policy-name",className:"text-xs font-medium text-muted-foreground uppercase tracking-wider",children:"Name *"}),e.jsx(j,{id:"create-policy-name",placeholder:"My Policy",value:a.name,onChange:x=>r(l=>({...l,name:x.target.value})),className:"border-border bg-muted/30 text-sm placeholder:text-muted-foreground/30"})]}),e.jsxs("div",{className:"grid gap-2",children:[e.jsx("label",{htmlFor:"create-policy-priority",className:"text-xs font-medium text-muted-foreground uppercase tracking-wider",children:"Priority"}),e.jsx(j,{id:"create-policy-priority",type:"number",value:a.priority,onChange:x=>r(l=>({...l,priority:parseInt(x.target.value,10)||0})),className:"border-border bg-muted/30 text-sm font-mono"})]})]}),e.jsxs("div",{className:"grid gap-2",children:[e.jsx("label",{htmlFor:"create-policy-desc",className:"text-xs font-medium text-muted-foreground uppercase tracking-wider",children:"Description"}),e.jsx(j,{id:"create-policy-desc",placeholder:"Optional description",value:a.description,onChange:x=>r(l=>({...l,description:x.target.value})),className:"border-border bg-muted/30 text-sm placeholder:text-muted-foreground/30"})]}),e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("input",{type:"checkbox",id:"enabled",checked:a.enabled,onChange:x=>r(l=>({...l,enabled:x.target.checked})),className:"rounded border-border"}),e.jsx("label",{htmlFor:"enabled",className:"text-sm text-muted-foreground",children:"Enabled"})]}),e.jsx(G,{rules:a.rules,onChange:x=>r(l=>({...l,rules:x}))})]}),e.jsxs(T,{children:[e.jsx(p,{variant:"outline",onClick:()=>n(!1),disabled:i,className:"border-border",children:"Cancel"}),e.jsx(p,{onClick:t,disabled:i,className:"bg-cyan-600 hover:bg-cyan-700 text-white border-0",children:i?"Creating...":"Create Policy"})]})]})]})}function de({policies:s,onCreated:h}){const[n,i]=c.useState(!1),[m,a]=c.useState(!1),[r,t]=c.useState({name:"",description:"",isDefault:!1,selectedPolicyIds:[]}),x=d=>{t(b=>({...b,selectedPolicyIds:b.selectedPolicyIds.includes(d)?b.selectedPolicyIds.filter(w=>w!==d):[...b.selectedPolicyIds,d]}))},l=async()=>{if(!r.name.trim()){u.error("Set name is required");return}try{a(!0),await f.createPolicySet({id:crypto.randomUUID(),name:r.name,description:r.description||void 0,isDefault:r.isDefault,policyIds:r.selectedPolicyIds.length>0?r.selectedPolicyIds:void 0}),u.success("Policy set created"),i(!1),t({name:"",description:"",isDefault:!1,selectedPolicyIds:[]}),h()}catch(d){u.error(d instanceof Error?d.message:"Failed to create policy set")}finally{a(!1)}};return e.jsxs(S,{open:n,onOpenChange:i,children:[e.jsx($,{asChild:!0,children:e.jsxs(p,{variant:"outline",className:"border-border",children:[e.jsx(D,{className:"h-4 w-4 mr-1.5"}),"New Set"]})}),e.jsxs(F,{className:"border-border bg-popover max-w-lg max-h-[85vh] overflow-y-auto",children:[e.jsxs(I,{children:[e.jsx(E,{children:"Create Policy Set"}),e.jsx(R,{className:"text-muted-foreground/60",children:"Group policies together for easy application to sessions."})]}),e.jsxs("div",{className:"grid gap-4 py-4",children:[e.jsxs("div",{className:"grid gap-2",children:[e.jsx("label",{htmlFor:"create-set-name",className:"text-xs font-medium text-muted-foreground uppercase tracking-wider",children:"Name *"}),e.jsx(j,{id:"create-set-name",placeholder:"Production Rules",value:r.name,onChange:d=>t(b=>({...b,name:d.target.value})),className:"border-border bg-muted/30 text-sm placeholder:text-muted-foreground/30"})]}),e.jsxs("div",{className:"grid gap-2",children:[e.jsx("label",{htmlFor:"create-set-desc",className:"text-xs font-medium text-muted-foreground uppercase tracking-wider",children:"Description"}),e.jsx(j,{id:"create-set-desc",placeholder:"Optional description",value:r.description,onChange:d=>t(b=>({...b,description:d.target.value})),className:"border-border bg-muted/30 text-sm placeholder:text-muted-foreground/30"})]}),e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("input",{type:"checkbox",id:"set-default",checked:r.isDefault,onChange:d=>t(b=>({...b,isDefault:d.target.checked})),className:"rounded border-border"}),e.jsx("label",{htmlFor:"set-default",className:"text-sm text-muted-foreground",children:"Set as default (auto-apply to new sessions)"})]}),s.length>0&&e.jsxs("div",{className:"grid gap-2",children:[e.jsx("p",{className:"text-xs font-medium text-muted-foreground uppercase tracking-wider",children:"Include Policies"}),e.jsx("div",{className:"space-y-1.5 max-h-48 overflow-y-auto rounded-lg border border-border p-2",children:s.map(d=>e.jsxs("label",{className:"flex items-center gap-2 px-2 py-1.5 rounded-md hover:bg-accent/30 cursor-pointer transition-colors",children:[e.jsx("input",{type:"checkbox",checked:r.selectedPolicyIds.includes(d.id),onChange:()=>x(d.id),className:"rounded border-border"}),e.jsx("span",{className:"text-sm",children:d.name}),e.jsxs("span",{className:"text-[10px] font-mono text-muted-foreground/50 ml-auto",children:["P:",d.priority]})]},d.id))})]})]}),e.jsxs(T,{children:[e.jsx(p,{variant:"outline",onClick:()=>i(!1),disabled:m,className:"border-border",children:"Cancel"}),e.jsx(p,{onClick:l,disabled:m,className:"bg-cyan-600 hover:bg-cyan-700 text-white border-0",children:m?"Creating...":"Create Set"})]})]})]})}function ie({policy:s,open:h,onOpenChange:n,onSaved:i}){const[m,a]=c.useState(!1),[r,t]=c.useState({name:s.name,description:s.description||"",priority:s.priority,enabled:s.enabled,rules:[...s.rules]}),x=async()=>{if(!r.name.trim()){u.error("Policy name is required");return}try{a(!0),await f.updatePolicy(s.id,{name:r.name,description:r.description||void 0,priority:r.priority,enabled:r.enabled,rules:r.rules}),u.success("Policy updated"),n(!1),i()}catch(l){u.error(l instanceof Error?l.message:"Failed to update policy")}finally{a(!1)}};return e.jsx(S,{open:h,onOpenChange:n,children:e.jsxs(F,{className:"border-border bg-popover max-w-2xl max-h-[85vh] overflow-y-auto",children:[e.jsxs(I,{children:[e.jsx(E,{children:"Edit Policy"}),e.jsx(R,{className:"text-muted-foreground/60",children:"Modify the permission rules for this policy."})]}),e.jsxs("div",{className:"grid gap-4 py-4",children:[e.jsxs("div",{className:"grid grid-cols-2 gap-4",children:[e.jsxs("div",{className:"grid gap-2",children:[e.jsx("label",{htmlFor:"edit-policy-name",className:"text-xs font-medium text-muted-foreground uppercase tracking-wider",children:"Name *"}),e.jsx(j,{id:"edit-policy-name",value:r.name,onChange:l=>t(d=>({...d,name:l.target.value})),className:"border-border bg-muted/30 text-sm"})]}),e.jsxs("div",{className:"grid gap-2",children:[e.jsx("label",{htmlFor:"edit-policy-priority",className:"text-xs font-medium text-muted-foreground uppercase tracking-wider",children:"Priority"}),e.jsx(j,{id:"edit-policy-priority",type:"number",value:r.priority,onChange:l=>t(d=>({...d,priority:parseInt(l.target.value,10)||0})),className:"border-border bg-muted/30 text-sm font-mono"})]})]}),e.jsxs("div",{className:"grid gap-2",children:[e.jsx("label",{htmlFor:"edit-policy-desc",className:"text-xs font-medium text-muted-foreground uppercase tracking-wider",children:"Description"}),e.jsx(j,{id:"edit-policy-desc",value:r.description,onChange:l=>t(d=>({...d,description:l.target.value})),className:"border-border bg-muted/30 text-sm"})]}),e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("input",{type:"checkbox",id:"edit-enabled",checked:r.enabled,onChange:l=>t(d=>({...d,enabled:l.target.checked})),className:"rounded border-border"}),e.jsx("label",{htmlFor:"edit-enabled",className:"text-sm text-muted-foreground",children:"Enabled"})]}),e.jsx(G,{rules:r.rules,onChange:l=>t(d=>({...d,rules:l}))})]}),e.jsxs(T,{children:[e.jsx(p,{variant:"outline",onClick:()=>n(!1),disabled:m,className:"border-border",children:"Cancel"}),e.jsx(p,{onClick:x,disabled:m,className:"bg-cyan-600 hover:bg-cyan-700 text-white border-0",children:m?"Saving...":"Save Changes"})]})]})})}function oe({policy:s,onUpdated:h}){const[n,i]=c.useState(!1),[m,a]=c.useState(!1),r=async()=>{if(confirm(`Delete policy "${s.name}"?`))try{a(!0),await f.deletePolicy(s.id),u.success("Policy deleted"),h()}catch(t){u.error(t instanceof Error?t.message:"Failed to delete policy")}finally{a(!1)}};return e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"rounded-xl border border-border bg-card/80 backdrop-blur-sm p-5 hover:bg-accent/30 transition-colors",children:[e.jsxs("div",{className:"flex items-center justify-between mb-3",children:[e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsx("h3",{className:"text-sm font-semibold",children:s.name}),e.jsx(v,{variant:s.enabled?"success":"secondary",children:s.enabled?"Active":"Disabled"}),e.jsxs("span",{className:"text-[10px] font-mono text-muted-foreground/50 bg-muted/50 px-1.5 py-0.5 rounded border border-border",children:["Priority: ",s.priority]}),s.sessionId&&e.jsxs("span",{className:"text-[10px] font-mono text-cyan-400/60 bg-cyan-500/5 px-1.5 py-0.5 rounded border border-cyan-500/10",children:["Session: ",s.sessionId.slice(0,8)]}),!s.sessionId&&e.jsx("span",{className:"text-[10px] font-mono text-amber-400/60 bg-amber-500/5 px-1.5 py-0.5 rounded border border-amber-500/10",children:"Global"})]}),e.jsxs("div",{className:"flex items-center gap-1",children:[e.jsx(p,{variant:"ghost",size:"sm",className:"h-7 w-7 p-0 text-muted-foreground hover:text-foreground",onClick:()=>i(!0),children:e.jsx(Q,{className:"h-3.5 w-3.5"})}),e.jsx(p,{variant:"ghost",size:"sm",className:"h-7 w-7 p-0 text-muted-foreground hover:text-red-400",onClick:r,disabled:m,children:e.jsx(_,{className:"h-3.5 w-3.5"})})]})]}),s.description&&e.jsx("p",{className:"text-sm text-muted-foreground mb-3",children:s.description}),e.jsxs("div",{className:"space-y-1.5 mb-3",children:[e.jsxs("p",{className:"text-[11px] font-medium text-muted-foreground uppercase tracking-wider",children:["Rules (",s.rules.length,")"]}),s.rules.map(t=>e.jsxs("div",{className:"flex items-center gap-2 text-sm",children:[e.jsx(v,{variant:t.action==="allow"?"success":t.action==="deny"?"destructive":"warning",children:t.action}),t.tool&&e.jsx("code",{className:"bg-muted/50 px-1.5 py-0.5 rounded text-[11px] font-mono border border-border",children:Array.isArray(t.tool)?t.tool.join(", "):t.tool}),t.reason&&e.jsxs("span",{className:"text-muted-foreground/60 text-xs",children:["- ",t.reason]})]},t.id))]}),e.jsxs("p",{className:"text-[11px] text-muted-foreground/50",children:["Updated ",B(s.updatedAt)]})]}),n&&e.jsx(ie,{policy:s,open:n,onOpenChange:i,onSaved:h})]})}function ne({policySet:s,allPolicies:h,onUpdated:n}){const[i,m]=c.useState(!1),[a,r]=c.useState([]),[t,x]=c.useState(!1),[l,d]=c.useState(!1);c.useEffect(()=>{if(!i)return;(async()=>{x(!0);try{const g=await f.getPolicySet(s.id);r(g.policySet.policies)}catch{u.error("Failed to load policy set members")}finally{x(!1)}})()},[i,s.id]);const b=async()=>{if(confirm(`Delete policy set "${s.name}"?`))try{d(!0),await f.deletePolicySet(s.id),u.success("Policy set deleted"),n()}catch(o){u.error(o instanceof Error?o.message:"Failed to delete")}finally{d(!1)}},w=async()=>{try{await f.updatePolicySet(s.id,{isDefault:!s.isDefault}),u.success(s.isDefault?"Removed as default":"Set as default"),n()}catch(o){u.error(o instanceof Error?o.message:"Failed to update")}},H=async o=>{try{await f.removePolicyFromSet(s.id,o),r(g=>g.filter(N=>N.id!==o)),u.success("Policy removed from set"),n()}catch(g){u.error(g instanceof Error?g.message:"Failed to remove")}},W=async o=>{try{await f.addPolicyToSet(s.id,o);const g=h.find(N=>N.id===o);g&&r(N=>[...N,g]),u.success("Policy added to set"),n()}catch(g){u.error(g instanceof Error?g.message:"Failed to add")}},A=h.filter(o=>!a.some(g=>g.id===o.id));return e.jsxs("div",{className:"rounded-xl border border-border bg-card/80 backdrop-blur-sm overflow-hidden",children:[e.jsxs("button",{type:"button",className:"flex items-center justify-between w-full p-5 cursor-pointer hover:bg-accent/30 transition-colors text-left",onClick:()=>m(!i),children:[e.jsxs("div",{className:"flex items-center gap-3",children:[i?e.jsx(X,{className:"h-4 w-4 text-muted-foreground"}):e.jsx(J,{className:"h-4 w-4 text-muted-foreground"}),e.jsx(k,{className:"h-4 w-4 text-cyan-400"}),e.jsx("h3",{className:"text-sm font-semibold",children:s.name}),s.isDefault&&e.jsxs(v,{variant:"warning",className:"text-[10px]",children:[e.jsx(M,{className:"h-2.5 w-2.5 mr-1"}),"Default"]}),e.jsxs("span",{className:"text-xs text-muted-foreground",children:[s.policyCount," ",s.policyCount===1?"policy":"policies"]}),e.jsxs("span",{className:"text-xs text-muted-foreground/50",children:[s.sessionCount," ",s.sessionCount===1?"session":"sessions"]})]}),e.jsxs("div",{className:"flex items-center gap-1",onClick:o=>o.stopPropagation(),children:[e.jsxs(p,{variant:"ghost",size:"sm",className:"h-7 px-2 text-xs text-muted-foreground hover:text-amber-400",onClick:w,children:[e.jsx(M,{className:`h-3 w-3 mr-1 ${s.isDefault?"fill-amber-400":""}`}),s.isDefault?"Unset Default":"Set Default"]}),e.jsx(p,{variant:"ghost",size:"sm",className:"h-7 w-7 p-0 text-muted-foreground hover:text-red-400",onClick:b,disabled:l,children:e.jsx(_,{className:"h-3.5 w-3.5"})})]})]}),s.description&&!i&&e.jsx("div",{className:"px-5 pb-3 -mt-2",children:e.jsx("p",{className:"text-xs text-muted-foreground/60 pl-11",children:s.description})}),i&&e.jsxs("div",{className:"border-t border-border p-5 pt-4 space-y-3",children:[s.description&&e.jsx("p",{className:"text-sm text-muted-foreground mb-3",children:s.description}),t?e.jsxs("div",{className:"flex items-center gap-2 text-muted-foreground py-4 justify-center",children:[e.jsx("div",{className:"w-3 h-3 border-2 border-cyan-500/30 border-t-cyan-500 rounded-full animate-spin"}),e.jsx("span",{className:"text-xs",children:"Loading..."})]}):e.jsxs(e.Fragment,{children:[e.jsx("p",{className:"text-[11px] font-medium text-muted-foreground uppercase tracking-wider",children:"Member Policies"}),a.length===0?e.jsx("p",{className:"text-xs text-muted-foreground/50 text-center py-3 border border-dashed border-border rounded-lg",children:"No policies in this set yet."}):e.jsx("div",{className:"space-y-1.5",children:a.map(o=>e.jsxs("div",{className:"flex items-center justify-between py-2 px-3 rounded-lg bg-muted/20 border border-border/50",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx(v,{variant:o.enabled?"success":"secondary",className:"text-[10px]",children:o.enabled?"Active":"Off"}),e.jsx("span",{className:"text-sm",children:o.name}),e.jsxs("span",{className:"text-[10px] font-mono text-muted-foreground/50",children:["P:",o.priority]})]}),e.jsx(p,{variant:"ghost",size:"sm",className:"h-6 w-6 p-0 text-muted-foreground hover:text-red-400",onClick:()=>H(o.id),children:e.jsx(K,{className:"h-3 w-3"})})]},o.id))}),A.length>0&&e.jsxs("div",{className:"pt-2",children:[e.jsx("p",{className:"text-[11px] font-medium text-muted-foreground uppercase tracking-wider mb-2",children:"Add Policy"}),e.jsx("div",{className:"flex flex-wrap gap-1.5",children:A.map(o=>e.jsxs("button",{type:"button",onClick:()=>W(o.id),className:"text-xs px-2.5 py-1 rounded-md border border-border bg-muted/30 text-muted-foreground hover:text-foreground hover:bg-accent/50 hover:border-cyan-500/30 transition-colors",children:["+ ",o.name]},o.id))})]})]})]})]})}function ge(){const[s,h]=c.useState([]),[n,i]=c.useState([]),[m,a]=c.useState(!0),r=c.useCallback(async()=>{try{const[t,x]=await Promise.all([f.getPolicies(),f.getPolicySets()]);h(t.policies),i(x.policySets)}catch{u.error("Failed to load policy data")}finally{a(!1)}},[]);return c.useEffect(()=>{r()},[r]),m?e.jsx("div",{className:"p-4 md:p-8 max-w-7xl mx-auto",children:e.jsx("div",{className:"flex justify-center items-center h-64",children:e.jsxs("div",{className:"flex items-center gap-3 text-muted-foreground",children:[e.jsx("div",{className:"w-4 h-4 border-2 border-cyan-500/30 border-t-cyan-500 rounded-full animate-spin"}),e.jsx("span",{className:"text-sm",children:"Loading policies..."})]})})}):e.jsxs("div",{className:"p-4 md:p-8 max-w-7xl mx-auto",children:[e.jsxs("div",{className:"mb-6",children:[e.jsx("h1",{className:"text-2xl md:text-3xl font-bold tracking-tight",children:"Policies"}),e.jsx("p",{className:"text-sm text-muted-foreground mt-1",children:"Permission rules, policy sets, and audit trail"})]}),e.jsxs(Y,{defaultValue:"policies",children:[e.jsxs(Z,{className:"bg-muted/50 border border-border mb-6",children:[e.jsxs(C,{value:"policies",className:"data-[state=active]:bg-background data-[state=active]:text-foreground text-sm gap-1.5",children:[e.jsx(L,{className:"h-3.5 w-3.5"}),"Policies"]}),e.jsxs(C,{value:"sets",className:"data-[state=active]:bg-background data-[state=active]:text-foreground text-sm gap-1.5",children:[e.jsx(k,{className:"h-3.5 w-3.5"}),"Policy Sets"]}),e.jsxs(C,{value:"audit",className:"data-[state=active]:bg-background data-[state=active]:text-foreground text-sm gap-1.5",children:[e.jsx(se,{className:"h-3.5 w-3.5"}),"Audit Log"]})]}),e.jsxs(P,{value:"policies",children:[e.jsxs("div",{className:"flex justify-between items-center mb-4",children:[e.jsxs("p",{className:"text-sm text-muted-foreground",children:[s.length," ",s.length===1?"policy":"policies"," configured"]}),e.jsx(ae,{onCreated:r})]}),s.length===0?e.jsxs("div",{className:"flex flex-col items-center justify-center h-64 rounded-xl border border-border bg-card/80",children:[e.jsx(L,{className:"h-10 w-10 text-muted-foreground/20 mb-3"}),e.jsx("p",{className:"text-sm text-muted-foreground mb-1",children:"No policies configured"}),e.jsx("p",{className:"text-xs text-muted-foreground/60",children:"Create your first policy to control tool permissions."})]}):e.jsx("div",{className:"space-y-3",children:s.map(t=>e.jsx(oe,{policy:t,onUpdated:r},t.id))})]}),e.jsxs(P,{value:"sets",children:[e.jsxs("div",{className:"flex justify-between items-center mb-4",children:[e.jsxs("p",{className:"text-sm text-muted-foreground",children:[n.length," ",n.length===1?"set":"sets"," configured"]}),e.jsx(de,{policies:s,onCreated:r})]}),n.length===0?e.jsxs("div",{className:"flex flex-col items-center justify-center h-64 rounded-xl border border-border bg-card/80",children:[e.jsx(k,{className:"h-10 w-10 text-muted-foreground/20 mb-3"}),e.jsx("p",{className:"text-sm text-muted-foreground mb-1",children:"No policy sets"}),e.jsx("p",{className:"text-xs text-muted-foreground/60",children:"Group policies into sets for easy application to sessions."})]}):e.jsx("div",{className:"space-y-3",children:n.map(t=>e.jsx(ne,{policySet:t,allPolicies:s,onUpdated:r},t.id))})]}),e.jsx(P,{value:"audit",children:e.jsx(re,{})})]})]})}export{ge as PoliciesPage};
|