@fml-inc/panopticon 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.
Files changed (124) hide show
  1. package/.claude-plugin/plugin.json +10 -0
  2. package/LICENSE +5 -0
  3. package/README.md +363 -0
  4. package/bin/hook-handler +3 -0
  5. package/bin/mcp-server +3 -0
  6. package/bin/panopticon +3 -0
  7. package/bin/proxy +3 -0
  8. package/bin/server +3 -0
  9. package/dist/api/client.d.ts +67 -0
  10. package/dist/api/client.js +48 -0
  11. package/dist/api/client.js.map +1 -0
  12. package/dist/chunk-3BUJ7URA.js +387 -0
  13. package/dist/chunk-3BUJ7URA.js.map +1 -0
  14. package/dist/chunk-3TZAKV3M.js +158 -0
  15. package/dist/chunk-3TZAKV3M.js.map +1 -0
  16. package/dist/chunk-4SM2H22C.js +169 -0
  17. package/dist/chunk-4SM2H22C.js.map +1 -0
  18. package/dist/chunk-7Q3BJMLG.js +62 -0
  19. package/dist/chunk-7Q3BJMLG.js.map +1 -0
  20. package/dist/chunk-BVOE7A2Z.js +412 -0
  21. package/dist/chunk-BVOE7A2Z.js.map +1 -0
  22. package/dist/chunk-CF4GPWLI.js +170 -0
  23. package/dist/chunk-CF4GPWLI.js.map +1 -0
  24. package/dist/chunk-DZ5HJFB4.js +467 -0
  25. package/dist/chunk-DZ5HJFB4.js.map +1 -0
  26. package/dist/chunk-HQCY722C.js +428 -0
  27. package/dist/chunk-HQCY722C.js.map +1 -0
  28. package/dist/chunk-HRCEIYKU.js +134 -0
  29. package/dist/chunk-HRCEIYKU.js.map +1 -0
  30. package/dist/chunk-K7YUPLES.js +76 -0
  31. package/dist/chunk-K7YUPLES.js.map +1 -0
  32. package/dist/chunk-L7G27XWF.js +130 -0
  33. package/dist/chunk-L7G27XWF.js.map +1 -0
  34. package/dist/chunk-LWXF7YRG.js +626 -0
  35. package/dist/chunk-LWXF7YRG.js.map +1 -0
  36. package/dist/chunk-NXH7AONS.js +1120 -0
  37. package/dist/chunk-NXH7AONS.js.map +1 -0
  38. package/dist/chunk-QK5442ZP.js +55 -0
  39. package/dist/chunk-QK5442ZP.js.map +1 -0
  40. package/dist/chunk-QVK6VGCV.js +1703 -0
  41. package/dist/chunk-QVK6VGCV.js.map +1 -0
  42. package/dist/chunk-RX2RXHBH.js +1699 -0
  43. package/dist/chunk-RX2RXHBH.js.map +1 -0
  44. package/dist/chunk-SEXU2WYG.js +788 -0
  45. package/dist/chunk-SEXU2WYG.js.map +1 -0
  46. package/dist/chunk-SUGSQ4YI.js +264 -0
  47. package/dist/chunk-SUGSQ4YI.js.map +1 -0
  48. package/dist/chunk-TGXFVAID.js +138 -0
  49. package/dist/chunk-TGXFVAID.js.map +1 -0
  50. package/dist/chunk-WLBNFVIG.js +447 -0
  51. package/dist/chunk-WLBNFVIG.js.map +1 -0
  52. package/dist/chunk-XLTCUH5A.js +1072 -0
  53. package/dist/chunk-XLTCUH5A.js.map +1 -0
  54. package/dist/chunk-YVRWVDIA.js +146 -0
  55. package/dist/chunk-YVRWVDIA.js.map +1 -0
  56. package/dist/chunk-ZEC4LRKS.js +176 -0
  57. package/dist/chunk-ZEC4LRKS.js.map +1 -0
  58. package/dist/cli.d.ts +1 -0
  59. package/dist/cli.js +1084 -0
  60. package/dist/cli.js.map +1 -0
  61. package/dist/config-NwoZC-GM.d.ts +20 -0
  62. package/dist/db.d.ts +46 -0
  63. package/dist/db.js +15 -0
  64. package/dist/db.js.map +1 -0
  65. package/dist/doctor.d.ts +37 -0
  66. package/dist/doctor.js +14 -0
  67. package/dist/doctor.js.map +1 -0
  68. package/dist/hooks/handler.d.ts +23 -0
  69. package/dist/hooks/handler.js +295 -0
  70. package/dist/hooks/handler.js.map +1 -0
  71. package/dist/index.d.ts +57 -0
  72. package/dist/index.js +101 -0
  73. package/dist/index.js.map +1 -0
  74. package/dist/mcp/server.d.ts +1 -0
  75. package/dist/mcp/server.js +243 -0
  76. package/dist/mcp/server.js.map +1 -0
  77. package/dist/otlp/server.d.ts +7 -0
  78. package/dist/otlp/server.js +17 -0
  79. package/dist/otlp/server.js.map +1 -0
  80. package/dist/permissions.d.ts +33 -0
  81. package/dist/permissions.js +14 -0
  82. package/dist/permissions.js.map +1 -0
  83. package/dist/pricing.d.ts +29 -0
  84. package/dist/pricing.js +13 -0
  85. package/dist/pricing.js.map +1 -0
  86. package/dist/proxy/server.d.ts +10 -0
  87. package/dist/proxy/server.js +20 -0
  88. package/dist/proxy/server.js.map +1 -0
  89. package/dist/prune.d.ts +18 -0
  90. package/dist/prune.js +13 -0
  91. package/dist/prune.js.map +1 -0
  92. package/dist/query.d.ts +56 -0
  93. package/dist/query.js +27 -0
  94. package/dist/query.js.map +1 -0
  95. package/dist/reparse-636YZCE3.js +14 -0
  96. package/dist/reparse-636YZCE3.js.map +1 -0
  97. package/dist/repo.d.ts +17 -0
  98. package/dist/repo.js +9 -0
  99. package/dist/repo.js.map +1 -0
  100. package/dist/scanner.d.ts +73 -0
  101. package/dist/scanner.js +15 -0
  102. package/dist/scanner.js.map +1 -0
  103. package/dist/sdk.d.ts +82 -0
  104. package/dist/sdk.js +208 -0
  105. package/dist/sdk.js.map +1 -0
  106. package/dist/server.d.ts +5 -0
  107. package/dist/server.js +25 -0
  108. package/dist/server.js.map +1 -0
  109. package/dist/setup.d.ts +35 -0
  110. package/dist/setup.js +19 -0
  111. package/dist/setup.js.map +1 -0
  112. package/dist/sync/index.d.ts +29 -0
  113. package/dist/sync/index.js +32 -0
  114. package/dist/sync/index.js.map +1 -0
  115. package/dist/targets.d.ts +279 -0
  116. package/dist/targets.js +20 -0
  117. package/dist/targets.js.map +1 -0
  118. package/dist/types-D-MYCBol.d.ts +128 -0
  119. package/dist/types.d.ts +164 -0
  120. package/dist/types.js +1 -0
  121. package/dist/types.js.map +1 -0
  122. package/hooks/hooks.json +274 -0
  123. package/package.json +124 -0
  124. package/skills/panopticon-optimize/SKILL.md +222 -0
@@ -0,0 +1,18 @@
1
+ interface PruneResult {
2
+ otel_logs: number;
3
+ otel_metrics: number;
4
+ hook_events: number;
5
+ session_repositories: number;
6
+ session_cwds: number;
7
+ model_pricing: number;
8
+ sessions: number;
9
+ messages: number;
10
+ tool_calls: number;
11
+ scanner_turns: number;
12
+ scanner_events: number;
13
+ }
14
+ declare function pruneEstimate(cutoffMs: number): PruneResult;
15
+ declare function pruneExecute(cutoffMs: number): PruneResult;
16
+ declare function autoPrune(maxAgeDays: number, maxSizeMb: number): void;
17
+
18
+ export { type PruneResult, autoPrune, pruneEstimate, pruneExecute };
package/dist/prune.js ADDED
@@ -0,0 +1,13 @@
1
+ import {
2
+ autoPrune,
3
+ pruneEstimate,
4
+ pruneExecute
5
+ } from "./chunk-HRCEIYKU.js";
6
+ import "./chunk-DZ5HJFB4.js";
7
+ import "./chunk-K7YUPLES.js";
8
+ export {
9
+ autoPrune,
10
+ pruneEstimate,
11
+ pruneExecute
12
+ };
13
+ //# sourceMappingURL=prune.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,56 @@
1
+ import { ActivitySummaryResult, SpendingResult, SessionListResult, SearchResult, SessionTimelineResult } from './types.js';
2
+
3
+ declare function listSessions(opts?: {
4
+ limit?: number;
5
+ since?: string;
6
+ }): SessionListResult;
7
+ declare function sessionTimeline(opts: {
8
+ sessionId: string;
9
+ limit?: number;
10
+ offset?: number;
11
+ fullPayloads?: boolean;
12
+ }): SessionTimelineResult;
13
+ declare function costBreakdown(opts?: {
14
+ since?: string;
15
+ groupBy?: "session" | "model" | "day";
16
+ }): SpendingResult;
17
+ declare function search(opts: {
18
+ query: string;
19
+ eventTypes?: string[];
20
+ since?: string;
21
+ limit?: number;
22
+ offset?: number;
23
+ fullPayloads?: boolean;
24
+ }): SearchResult;
25
+ declare function activitySummary(opts?: {
26
+ since?: string;
27
+ }): ActivitySummaryResult;
28
+ declare function listPlans(opts?: {
29
+ session_id?: string;
30
+ since?: string;
31
+ limit?: number;
32
+ }): {
33
+ id: number;
34
+ session_id: string;
35
+ timestamp: string;
36
+ plan: string | null;
37
+ allowed_prompts: any;
38
+ }[];
39
+ declare function print(opts: {
40
+ source: "hook" | "otel" | "message";
41
+ id: number;
42
+ }): {} | null;
43
+ declare function rawQuery(sql: string): unknown[];
44
+ declare function dbStats(): {
45
+ sessions: number;
46
+ messages: number;
47
+ tool_calls: number;
48
+ scanner_turns: number;
49
+ scanner_events: number;
50
+ hook_events: number;
51
+ otel_logs: number;
52
+ otel_metrics: number;
53
+ otel_spans: number;
54
+ };
55
+
56
+ export { activitySummary, costBreakdown, dbStats, listPlans, listSessions, print, rawQuery, search, sessionTimeline };
package/dist/query.js ADDED
@@ -0,0 +1,27 @@
1
+ import {
2
+ activitySummary,
3
+ costBreakdown,
4
+ dbStats,
5
+ listPlans,
6
+ listSessions,
7
+ print,
8
+ rawQuery,
9
+ search,
10
+ sessionTimeline
11
+ } from "./chunk-LWXF7YRG.js";
12
+ import "./chunk-ZEC4LRKS.js";
13
+ import "./chunk-QVK6VGCV.js";
14
+ import "./chunk-DZ5HJFB4.js";
15
+ import "./chunk-K7YUPLES.js";
16
+ export {
17
+ activitySummary,
18
+ costBreakdown,
19
+ dbStats,
20
+ listPlans,
21
+ listSessions,
22
+ print,
23
+ rawQuery,
24
+ search,
25
+ sessionTimeline
26
+ };
27
+ //# sourceMappingURL=query.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,14 @@
1
+ import {
2
+ reparseAll
3
+ } from "./chunk-NXH7AONS.js";
4
+ import "./chunk-BVOE7A2Z.js";
5
+ import "./chunk-YVRWVDIA.js";
6
+ import "./chunk-7Q3BJMLG.js";
7
+ import "./chunk-3TZAKV3M.js";
8
+ import "./chunk-QVK6VGCV.js";
9
+ import "./chunk-DZ5HJFB4.js";
10
+ import "./chunk-K7YUPLES.js";
11
+ export {
12
+ reparseAll
13
+ };
14
+ //# sourceMappingURL=reparse-636YZCE3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
package/dist/repo.d.ts ADDED
@@ -0,0 +1,17 @@
1
+ interface RepoInfo {
2
+ repo: string;
3
+ branch?: string | null;
4
+ }
5
+ /**
6
+ * Resolve the GitHub "org/repo" and branch for a working directory.
7
+ * Results are cached for the lifetime of the process.
8
+ *
9
+ * 1. Try git directly on the CWD
10
+ * 2. On failure, ask registered workspace providers (e.g. Superset)
11
+ * for an alternative repo directory to resolve against
12
+ */
13
+ declare function resolveRepoFromCwd(cwd: string): RepoInfo | null;
14
+ /** Reset caches (for testing). */
15
+ declare function _resetRepoCache(): void;
16
+
17
+ export { type RepoInfo, _resetRepoCache, resolveRepoFromCwd };
package/dist/repo.js ADDED
@@ -0,0 +1,9 @@
1
+ import {
2
+ _resetRepoCache,
3
+ resolveRepoFromCwd
4
+ } from "./chunk-YVRWVDIA.js";
5
+ export {
6
+ _resetRepoCache,
7
+ resolveRepoFromCwd
8
+ };
9
+ //# sourceMappingURL=repo.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,73 @@
1
+ interface ConfigLayer {
2
+ settings: Record<string, unknown> | null;
3
+ hooks: Array<{
4
+ event: string;
5
+ matcher: string | null;
6
+ type: string;
7
+ }>;
8
+ mcpServers: Array<{
9
+ name: string;
10
+ command: string;
11
+ }>;
12
+ commands: Array<{
13
+ name: string;
14
+ content: string;
15
+ }>;
16
+ agents: Array<{
17
+ name: string;
18
+ content: string;
19
+ }>;
20
+ rules: Array<{
21
+ name: string;
22
+ content: string;
23
+ }>;
24
+ skills: Array<{
25
+ name: string;
26
+ content: string;
27
+ }>;
28
+ permissions: {
29
+ allow: string[];
30
+ ask: string[];
31
+ deny: string[];
32
+ };
33
+ }
34
+ interface ClaudeCodeConfig {
35
+ managed: ConfigLayer | null;
36
+ user: ConfigLayer;
37
+ project: ConfigLayer | null;
38
+ projectLocal: ConfigLayer | null;
39
+ instructions: Array<{
40
+ path: string;
41
+ content: string;
42
+ lineCount: number;
43
+ }>;
44
+ enabledPlugins: Array<{
45
+ pluginName: string;
46
+ marketplace: string;
47
+ }>;
48
+ }
49
+ /**
50
+ * Resolve the git repository root for a directory.
51
+ * Cached per cwd for the lifetime of the process.
52
+ */
53
+ declare function resolveGitRoot(cwd: string): string | null;
54
+ /**
55
+ * Check if a file path is gitignored.
56
+ */
57
+ declare function isGitignored(filePath: string, cwd: string): boolean;
58
+ /**
59
+ * Read all Claude Code config from the filesystem.
60
+ * Returns structured layers (managed, user, project, projectLocal) plus
61
+ * instruction files.
62
+ */
63
+ declare function readConfig(cwd?: string): ClaudeCodeConfig;
64
+ /**
65
+ * Merge-write a settings patch into the given layer's settings.json.
66
+ */
67
+ declare function writeSettings(level: "project" | "projectLocal" | "user", patch: Record<string, unknown>, cwd?: string): void;
68
+ /**
69
+ * Write a config file (command, agent, rule, or skill) to the given layer.
70
+ */
71
+ declare function writeFile(level: "project" | "user", type: "command" | "agent" | "rule" | "skill", name: string, content: string, cwd?: string): void;
72
+
73
+ export { type ClaudeCodeConfig, type ConfigLayer, isGitignored, readConfig, resolveGitRoot, writeFile, writeSettings };
@@ -0,0 +1,15 @@
1
+ import {
2
+ isGitignored,
3
+ readConfig,
4
+ resolveGitRoot,
5
+ writeFile,
6
+ writeSettings
7
+ } from "./chunk-3BUJ7URA.js";
8
+ export {
9
+ isGitignored,
10
+ readConfig,
11
+ resolveGitRoot,
12
+ writeFile,
13
+ writeSettings
14
+ };
15
+ //# sourceMappingURL=scanner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
package/dist/sdk.d.ts ADDED
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Panopticon SDK shim for Claude Agent SDK.
3
+ *
4
+ * Wraps the `query()` async iterator to capture observability data and emit
5
+ * it to the panopticon server. Zero-dependency on the SDK — uses duck typing.
6
+ *
7
+ * Usage:
8
+ * import { query } from "@anthropic-ai/claude-agent-sdk";
9
+ * import { observe } from "panopticon/sdk";
10
+ *
11
+ * for await (const msg of observe(query({ prompt: "..." }))) {
12
+ * // use msg normally
13
+ * }
14
+ */
15
+ interface AnyMessage {
16
+ type: string;
17
+ subtype?: string;
18
+ session_id?: string;
19
+ uuid?: string;
20
+ message?: {
21
+ id?: string;
22
+ model?: string;
23
+ content?: Array<{
24
+ type: string;
25
+ name?: string;
26
+ input?: unknown;
27
+ text?: string;
28
+ }>;
29
+ usage?: {
30
+ input_tokens?: number;
31
+ output_tokens?: number;
32
+ cache_creation_input_tokens?: number;
33
+ cache_read_input_tokens?: number;
34
+ };
35
+ stop_reason?: string;
36
+ };
37
+ result?: string;
38
+ duration_ms?: number;
39
+ duration_api_ms?: number;
40
+ num_turns?: number;
41
+ total_cost_usd?: number;
42
+ stop_reason?: string | null;
43
+ usage?: {
44
+ input_tokens: number;
45
+ output_tokens: number;
46
+ cache_creation_input_tokens?: number;
47
+ cache_read_input_tokens?: number;
48
+ };
49
+ modelUsage?: Record<string, {
50
+ inputTokens: number;
51
+ outputTokens: number;
52
+ cacheReadInputTokens?: number;
53
+ cacheCreationInputTokens?: number;
54
+ costUSD?: number;
55
+ }>;
56
+ model?: string;
57
+ cwd?: string;
58
+ tools?: string[];
59
+ mcp_servers?: Array<{
60
+ name: string;
61
+ status: string;
62
+ }>;
63
+ rate_limit_info?: {
64
+ status: string;
65
+ resetsAt?: number;
66
+ utilization?: number;
67
+ };
68
+ [key: string]: unknown;
69
+ }
70
+ interface ObserveOptions {
71
+ /** Override the panopticon server port (default: 4318 or PANOPTICON_PORT) */
72
+ port?: number;
73
+ /** Custom session ID. If not set, uses the SDK's session_id from init. */
74
+ sessionId?: string;
75
+ }
76
+ /**
77
+ * Wrap a Claude Agent SDK `query()` iterator to capture observability data.
78
+ * Yields all messages unchanged — fully transparent to the consumer.
79
+ */
80
+ declare function observe<T extends AnyMessage>(source: AsyncIterable<T>, options?: ObserveOptions): AsyncGenerator<T, void, undefined>;
81
+
82
+ export { type ObserveOptions, observe };
package/dist/sdk.js ADDED
@@ -0,0 +1,208 @@
1
+ // src/sdk.ts
2
+ import http from "http";
3
+ var DEFAULT_PORT = 4318;
4
+ var PORT = parseInt(
5
+ process.env.PANOPTICON_PORT ?? process.env.PANOPTICON_OTLP_PORT ?? String(DEFAULT_PORT),
6
+ 10
7
+ );
8
+ function postJSON(path, body) {
9
+ const data = JSON.stringify(body);
10
+ const req = http.request(
11
+ {
12
+ hostname: "127.0.0.1",
13
+ port: PORT,
14
+ path,
15
+ method: "POST",
16
+ headers: {
17
+ "Content-Type": "application/json",
18
+ "Content-Length": Buffer.byteLength(data)
19
+ },
20
+ timeout: 3e3
21
+ },
22
+ (res) => {
23
+ res.resume();
24
+ }
25
+ );
26
+ req.on("error", () => {
27
+ });
28
+ req.on("timeout", () => req.destroy());
29
+ req.write(data);
30
+ req.end();
31
+ }
32
+ function emitHook(event) {
33
+ postJSON("/hooks", event);
34
+ }
35
+ function emitMetrics(metrics) {
36
+ if (metrics.length === 0) return;
37
+ const now = String(Date.now() * 1e6);
38
+ const sessionId = metrics[0].sessionId;
39
+ const resourceAttrs = [];
40
+ if (sessionId) {
41
+ resourceAttrs.push({
42
+ key: "session.id",
43
+ value: { stringValue: sessionId }
44
+ });
45
+ }
46
+ const byName = /* @__PURE__ */ new Map();
47
+ for (const m of metrics) {
48
+ const dps = byName.get(m.name) ?? [];
49
+ dps.push({
50
+ timeUnixNano: now,
51
+ asDouble: m.value,
52
+ attributes: Object.entries(m.attributes ?? {}).map(([key, value]) => ({
53
+ key,
54
+ value: typeof value === "number" ? { doubleValue: value } : { stringValue: String(value) }
55
+ }))
56
+ });
57
+ byName.set(m.name, dps);
58
+ }
59
+ postJSON("/v1/metrics", {
60
+ resourceMetrics: [
61
+ {
62
+ resource: { attributes: resourceAttrs },
63
+ scopeMetrics: [
64
+ {
65
+ metrics: [...byName.entries()].map(([name, dataPoints]) => ({
66
+ name,
67
+ gauge: { dataPoints }
68
+ }))
69
+ }
70
+ ]
71
+ }
72
+ ]
73
+ });
74
+ }
75
+ async function* observe(source, options) {
76
+ let sessionId = options?.sessionId ?? "sdk-unknown";
77
+ const seenMessageIds = /* @__PURE__ */ new Set();
78
+ for await (const msg of source) {
79
+ try {
80
+ processMessage(msg, sessionId, seenMessageIds);
81
+ if (msg.type === "system" && msg.subtype === "init" && msg.session_id && !options?.sessionId) {
82
+ sessionId = msg.session_id;
83
+ emitHook({
84
+ session_id: sessionId,
85
+ hook_event_name: "SessionStart",
86
+ source: "sdk",
87
+ cwd: msg.cwd,
88
+ model: msg.model,
89
+ tools: msg.tools
90
+ });
91
+ }
92
+ } catch {
93
+ }
94
+ yield msg;
95
+ }
96
+ emitHook({
97
+ session_id: sessionId,
98
+ hook_event_name: "SessionEnd",
99
+ source: "sdk"
100
+ });
101
+ }
102
+ function processMessage(msg, sessionId, seenMessageIds) {
103
+ if (msg.type === "assistant" && msg.message) {
104
+ const msgId = msg.message.id;
105
+ if (msgId && seenMessageIds.has(msgId)) return;
106
+ if (msgId) seenMessageIds.add(msgId);
107
+ const content = msg.message.content ?? [];
108
+ const usage = msg.message.usage;
109
+ const model = msg.message.model ?? "unknown";
110
+ for (const block of content) {
111
+ if (block.type === "tool_use" && block.name) {
112
+ emitHook({
113
+ session_id: sessionId,
114
+ hook_event_name: "PreToolUse",
115
+ source: "sdk",
116
+ tool_name: block.name,
117
+ tool_input: block.input
118
+ });
119
+ }
120
+ }
121
+ if (usage) {
122
+ const metrics = [];
123
+ if (usage.input_tokens) {
124
+ metrics.push({
125
+ name: "token.usage",
126
+ value: usage.input_tokens,
127
+ attributes: { model, token_type: "input", source: "sdk" },
128
+ sessionId
129
+ });
130
+ }
131
+ if (usage.output_tokens) {
132
+ metrics.push({
133
+ name: "token.usage",
134
+ value: usage.output_tokens,
135
+ attributes: { model, token_type: "output", source: "sdk" },
136
+ sessionId
137
+ });
138
+ }
139
+ if (usage.cache_read_input_tokens) {
140
+ metrics.push({
141
+ name: "token.usage",
142
+ value: usage.cache_read_input_tokens,
143
+ attributes: { model, token_type: "cacheRead", source: "sdk" },
144
+ sessionId
145
+ });
146
+ }
147
+ if (usage.cache_creation_input_tokens) {
148
+ metrics.push({
149
+ name: "token.usage",
150
+ value: usage.cache_creation_input_tokens,
151
+ attributes: { model, token_type: "cacheWrite", source: "sdk" },
152
+ sessionId
153
+ });
154
+ }
155
+ emitMetrics(metrics);
156
+ }
157
+ }
158
+ if (msg.type === "result") {
159
+ emitHook({
160
+ session_id: sessionId,
161
+ hook_event_name: "Stop",
162
+ source: "sdk",
163
+ stop_reason: msg.stop_reason,
164
+ num_turns: msg.num_turns,
165
+ duration_ms: msg.duration_ms,
166
+ duration_api_ms: msg.duration_api_ms,
167
+ total_cost_usd: msg.total_cost_usd
168
+ });
169
+ if (msg.total_cost_usd != null) {
170
+ emitMetrics([
171
+ {
172
+ name: "cost.usage",
173
+ value: msg.total_cost_usd,
174
+ attributes: { source: "sdk" },
175
+ sessionId
176
+ }
177
+ ]);
178
+ }
179
+ if (msg.modelUsage) {
180
+ for (const [model, usage] of Object.entries(msg.modelUsage)) {
181
+ const metrics = [];
182
+ if (usage.costUSD != null) {
183
+ metrics.push({
184
+ name: "cost.usage",
185
+ value: usage.costUSD,
186
+ attributes: { model, source: "sdk" },
187
+ sessionId
188
+ });
189
+ }
190
+ emitMetrics(metrics);
191
+ }
192
+ }
193
+ }
194
+ if (msg.type === "rate_limit_event" && msg.rate_limit_info) {
195
+ emitHook({
196
+ session_id: sessionId,
197
+ hook_event_name: "RateLimit",
198
+ source: "sdk",
199
+ rate_limit_status: msg.rate_limit_info.status,
200
+ rate_limit_utilization: msg.rate_limit_info.utilization,
201
+ rate_limit_resets_at: msg.rate_limit_info.resetsAt
202
+ });
203
+ }
204
+ }
205
+ export {
206
+ observe
207
+ };
208
+ //# sourceMappingURL=sdk.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/sdk.ts"],"sourcesContent":["/**\n * Panopticon SDK shim for Claude Agent SDK.\n *\n * Wraps the `query()` async iterator to capture observability data and emit\n * it to the panopticon server. Zero-dependency on the SDK — uses duck typing.\n *\n * Usage:\n * import { query } from \"@anthropic-ai/claude-agent-sdk\";\n * import { observe } from \"panopticon/sdk\";\n *\n * for await (const msg of observe(query({ prompt: \"...\" }))) {\n * // use msg normally\n * }\n */\n\nimport http from \"node:http\";\n\nconst DEFAULT_PORT = 4318;\nconst PORT = parseInt(\n process.env.PANOPTICON_PORT ??\n process.env.PANOPTICON_OTLP_PORT ??\n String(DEFAULT_PORT),\n 10,\n);\n\n// ---------------------------------------------------------------------------\n// HTTP helpers\n// ---------------------------------------------------------------------------\n\nfunction postJSON(path: string, body: unknown): void {\n const data = JSON.stringify(body);\n const req = http.request(\n {\n hostname: \"127.0.0.1\",\n port: PORT,\n path,\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Content-Length\": Buffer.byteLength(data),\n },\n timeout: 3000,\n },\n (res) => {\n res.resume();\n },\n );\n req.on(\"error\", () => {}); // fire and forget\n req.on(\"timeout\", () => req.destroy());\n req.write(data);\n req.end();\n}\n\nfunction emitHook(event: Record<string, unknown>): void {\n postJSON(\"/hooks\", event);\n}\n\nfunction emitMetrics(\n metrics: Array<{\n name: string;\n value: number;\n attributes?: Record<string, unknown>;\n sessionId?: string;\n }>,\n): void {\n if (metrics.length === 0) return;\n\n const now = String(Date.now() * 1_000_000);\n const sessionId = metrics[0].sessionId;\n\n const resourceAttrs: Array<{ key: string; value: { stringValue: string } }> =\n [];\n if (sessionId) {\n resourceAttrs.push({\n key: \"session.id\",\n value: { stringValue: sessionId },\n });\n }\n\n const byName = new Map<\n string,\n Array<{\n timeUnixNano: string;\n asDouble: number;\n attributes: Array<{\n key: string;\n value: { stringValue?: string; doubleValue?: number };\n }>;\n }>\n >();\n\n for (const m of metrics) {\n const dps = byName.get(m.name) ?? [];\n dps.push({\n timeUnixNano: now,\n asDouble: m.value,\n attributes: Object.entries(m.attributes ?? {}).map(([key, value]) => ({\n key,\n value:\n typeof value === \"number\"\n ? { doubleValue: value }\n : { stringValue: String(value) },\n })),\n });\n byName.set(m.name, dps);\n }\n\n postJSON(\"/v1/metrics\", {\n resourceMetrics: [\n {\n resource: { attributes: resourceAttrs },\n scopeMetrics: [\n {\n metrics: [...byName.entries()].map(([name, dataPoints]) => ({\n name,\n gauge: { dataPoints },\n })),\n },\n ],\n },\n ],\n });\n}\n\n// ---------------------------------------------------------------------------\n// Message type detection (duck typing — no SDK dependency)\n// ---------------------------------------------------------------------------\n\ninterface AnyMessage {\n type: string;\n subtype?: string;\n session_id?: string;\n uuid?: string;\n message?: {\n id?: string;\n model?: string;\n content?: Array<{\n type: string;\n name?: string;\n input?: unknown;\n text?: string;\n }>;\n usage?: {\n input_tokens?: number;\n output_tokens?: number;\n cache_creation_input_tokens?: number;\n cache_read_input_tokens?: number;\n };\n stop_reason?: string;\n };\n // ResultMessage fields\n result?: string;\n duration_ms?: number;\n duration_api_ms?: number;\n num_turns?: number;\n total_cost_usd?: number;\n stop_reason?: string | null;\n usage?: {\n input_tokens: number;\n output_tokens: number;\n cache_creation_input_tokens?: number;\n cache_read_input_tokens?: number;\n };\n modelUsage?: Record<\n string,\n {\n inputTokens: number;\n outputTokens: number;\n cacheReadInputTokens?: number;\n cacheCreationInputTokens?: number;\n costUSD?: number;\n }\n >;\n // SystemMessage (init) fields\n model?: string;\n cwd?: string;\n tools?: string[];\n mcp_servers?: Array<{ name: string; status: string }>;\n // Rate limit\n rate_limit_info?: {\n status: string;\n resetsAt?: number;\n utilization?: number;\n };\n [key: string]: unknown;\n}\n\n// ---------------------------------------------------------------------------\n// Observer\n// ---------------------------------------------------------------------------\n\nexport interface ObserveOptions {\n /** Override the panopticon server port (default: 4318 or PANOPTICON_PORT) */\n port?: number;\n /** Custom session ID. If not set, uses the SDK's session_id from init. */\n sessionId?: string;\n}\n\n/**\n * Wrap a Claude Agent SDK `query()` iterator to capture observability data.\n * Yields all messages unchanged — fully transparent to the consumer.\n */\nexport async function* observe<T extends AnyMessage>(\n source: AsyncIterable<T>,\n options?: ObserveOptions,\n): AsyncGenerator<T, void, undefined> {\n let sessionId = options?.sessionId ?? \"sdk-unknown\";\n const seenMessageIds = new Set<string>();\n\n for await (const msg of source) {\n try {\n processMessage(msg, sessionId, seenMessageIds);\n\n // Capture session_id from init message\n if (\n msg.type === \"system\" &&\n msg.subtype === \"init\" &&\n msg.session_id &&\n !options?.sessionId\n ) {\n sessionId = msg.session_id;\n emitHook({\n session_id: sessionId,\n hook_event_name: \"SessionStart\",\n source: \"sdk\",\n cwd: msg.cwd,\n model: msg.model,\n tools: msg.tools,\n });\n }\n } catch {\n // Never block the consumer\n }\n\n yield msg;\n }\n\n // Stream ended — emit SessionEnd\n emitHook({\n session_id: sessionId,\n hook_event_name: \"SessionEnd\",\n source: \"sdk\",\n });\n}\n\nfunction processMessage(\n msg: AnyMessage,\n sessionId: string,\n seenMessageIds: Set<string>,\n): void {\n if (msg.type === \"assistant\" && msg.message) {\n // Deduplicate by message.id (parallel tool calls share the same id)\n const msgId = msg.message.id;\n if (msgId && seenMessageIds.has(msgId)) return;\n if (msgId) seenMessageIds.add(msgId);\n\n const content = msg.message.content ?? [];\n const usage = msg.message.usage;\n const model = msg.message.model ?? \"unknown\";\n\n // Extract tool calls\n for (const block of content) {\n if (block.type === \"tool_use\" && block.name) {\n emitHook({\n session_id: sessionId,\n hook_event_name: \"PreToolUse\",\n source: \"sdk\",\n tool_name: block.name,\n tool_input: block.input as Record<string, unknown>,\n });\n }\n }\n\n // Emit per-turn token metrics\n if (usage) {\n const metrics: Array<{\n name: string;\n value: number;\n attributes?: Record<string, unknown>;\n sessionId?: string;\n }> = [];\n\n if (usage.input_tokens) {\n metrics.push({\n name: \"token.usage\",\n value: usage.input_tokens,\n attributes: { model, token_type: \"input\", source: \"sdk\" },\n sessionId,\n });\n }\n if (usage.output_tokens) {\n metrics.push({\n name: \"token.usage\",\n value: usage.output_tokens,\n attributes: { model, token_type: \"output\", source: \"sdk\" },\n sessionId,\n });\n }\n if (usage.cache_read_input_tokens) {\n metrics.push({\n name: \"token.usage\",\n value: usage.cache_read_input_tokens,\n attributes: { model, token_type: \"cacheRead\", source: \"sdk\" },\n sessionId,\n });\n }\n if (usage.cache_creation_input_tokens) {\n metrics.push({\n name: \"token.usage\",\n value: usage.cache_creation_input_tokens,\n attributes: { model, token_type: \"cacheWrite\", source: \"sdk\" },\n sessionId,\n });\n }\n\n emitMetrics(metrics);\n }\n }\n\n if (msg.type === \"result\") {\n // ResultMessage — authoritative totals\n emitHook({\n session_id: sessionId,\n hook_event_name: \"Stop\",\n source: \"sdk\",\n stop_reason: msg.stop_reason,\n num_turns: msg.num_turns,\n duration_ms: msg.duration_ms,\n duration_api_ms: msg.duration_api_ms,\n total_cost_usd: msg.total_cost_usd,\n });\n\n // Emit cost metric\n if (msg.total_cost_usd != null) {\n emitMetrics([\n {\n name: \"cost.usage\",\n value: msg.total_cost_usd,\n attributes: { source: \"sdk\" },\n sessionId,\n },\n ]);\n }\n\n // Emit per-model usage breakdown\n if (msg.modelUsage) {\n for (const [model, usage] of Object.entries(msg.modelUsage)) {\n const metrics: Array<{\n name: string;\n value: number;\n attributes?: Record<string, unknown>;\n sessionId?: string;\n }> = [];\n\n if (usage.costUSD != null) {\n metrics.push({\n name: \"cost.usage\",\n value: usage.costUSD,\n attributes: { model, source: \"sdk\" },\n sessionId,\n });\n }\n\n emitMetrics(metrics);\n }\n }\n }\n\n if (msg.type === \"rate_limit_event\" && msg.rate_limit_info) {\n emitHook({\n session_id: sessionId,\n hook_event_name: \"RateLimit\",\n source: \"sdk\",\n rate_limit_status: msg.rate_limit_info.status,\n rate_limit_utilization: msg.rate_limit_info.utilization,\n rate_limit_resets_at: msg.rate_limit_info.resetsAt,\n });\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,5 @@
1
+ import http from 'node:http';
2
+
3
+ declare function createUnifiedServer(): http.Server;
4
+
5
+ export { createUnifiedServer };
package/dist/server.js ADDED
@@ -0,0 +1,25 @@
1
+ import {
2
+ createUnifiedServer
3
+ } from "./chunk-WLBNFVIG.js";
4
+ import "./chunk-XLTCUH5A.js";
5
+ import "./chunk-RX2RXHBH.js";
6
+ import "./chunk-HQCY722C.js";
7
+ import "./chunk-NXH7AONS.js";
8
+ import "./chunk-BVOE7A2Z.js";
9
+ import "./chunk-YVRWVDIA.js";
10
+ import "./chunk-HRCEIYKU.js";
11
+ import "./chunk-3BUJ7URA.js";
12
+ import "./chunk-CF4GPWLI.js";
13
+ import "./chunk-7Q3BJMLG.js";
14
+ import "./chunk-3TZAKV3M.js";
15
+ import "./chunk-SEXU2WYG.js";
16
+ import "./chunk-QK5442ZP.js";
17
+ import "./chunk-LWXF7YRG.js";
18
+ import "./chunk-ZEC4LRKS.js";
19
+ import "./chunk-QVK6VGCV.js";
20
+ import "./chunk-DZ5HJFB4.js";
21
+ import "./chunk-K7YUPLES.js";
22
+ export {
23
+ createUnifiedServer
24
+ };
25
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,35 @@
1
+ export { c as config } from './config-NwoZC-GM.js';
2
+
3
+ /**
4
+ * Setup/install utilities for external consumers.
5
+ *
6
+ * Extracted from cli.ts so fml-plugin (and other integrators) can run
7
+ * panopticon setup steps without shelling out to the panopticon CLI.
8
+ */
9
+ /**
10
+ * Initialize the panopticon database — creates the data directory,
11
+ * schema, indexes, and runs migrations.
12
+ */
13
+ declare function initDb(): void;
14
+ /**
15
+ * Fetch model pricing from LiteLLM and cache locally.
16
+ * Returns the number of models cached, or null if the fetch failed.
17
+ */
18
+ declare function fetchPricing(): Promise<number | null>;
19
+ interface ShellEnvOptions {
20
+ /** Overwrite user-customized env vars (default false) */
21
+ force?: boolean;
22
+ /** Target CLI target id or "all" (default "claude") */
23
+ target?: string;
24
+ /** Also configure API proxy (default false) */
25
+ proxy?: boolean;
26
+ }
27
+ /**
28
+ * Configure shell environment variables (.zshrc / .bashrc) so that
29
+ * coding tools send telemetry to panopticon.
30
+ *
31
+ * Returns the path to the shell rc file that was updated.
32
+ */
33
+ declare function configureShellEnv(opts?: ShellEnvOptions): string;
34
+
35
+ export { type ShellEnvOptions, configureShellEnv, fetchPricing, initDb };