@rynfar/meridian 1.24.1 → 1.25.1
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/README.md +128 -184
- package/dist/cli-a05ws7rb.js +18 -0
- package/dist/cli-m9pfb7h9.js +203 -0
- package/dist/cli-rtab0qa6.js +67 -0
- package/dist/{cli-bjpad5x9.js → cli-s6f9jefk.js} +177 -124
- package/dist/cli.js +56 -3
- package/dist/proxy/adapter.d.ts +1 -1
- package/dist/proxy/errors.d.ts +16 -0
- package/dist/proxy/errors.d.ts.map +1 -1
- package/dist/proxy/fileChanges.d.ts.map +1 -1
- package/dist/proxy/models.d.ts +17 -1
- package/dist/proxy/models.d.ts.map +1 -1
- package/dist/proxy/server.d.ts.map +1 -1
- package/dist/proxy/setup.d.ts +42 -0
- package/dist/proxy/setup.d.ts.map +1 -0
- package/dist/proxy/tokenRefresh.d.ts +51 -0
- package/dist/proxy/tokenRefresh.d.ts.map +1 -0
- package/dist/server.js +4 -1
- package/dist/setup-5x116vbs.js +13 -0
- package/dist/telemetry/dashboard.d.ts +1 -1
- package/dist/telemetry/dashboard.d.ts.map +1 -1
- package/dist/telemetry/routes.d.ts.map +1 -1
- package/dist/tokenRefresh-ywwpe8k2.js +11 -0
- package/package.json +76 -75
- package/plugin/meridian.ts +54 -0
package/dist/cli.js
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
__require,
|
|
4
3
|
startProxyServer
|
|
5
|
-
} from "./cli-
|
|
4
|
+
} from "./cli-s6f9jefk.js";
|
|
5
|
+
import"./cli-rtab0qa6.js";
|
|
6
|
+
import"./cli-m9pfb7h9.js";
|
|
7
|
+
import {
|
|
8
|
+
__require
|
|
9
|
+
} from "./cli-a05ws7rb.js";
|
|
6
10
|
|
|
7
11
|
// bin/cli.ts
|
|
8
12
|
import { createRequire } from "module";
|
|
@@ -20,7 +24,12 @@ if (args.includes("--help") || args.includes("-h")) {
|
|
|
20
24
|
|
|
21
25
|
Local Anthropic API powered by your Claude Max subscription.
|
|
22
26
|
|
|
23
|
-
Usage: meridian [options]
|
|
27
|
+
Usage: meridian [command] [options]
|
|
28
|
+
|
|
29
|
+
Commands:
|
|
30
|
+
(default) Start the proxy server
|
|
31
|
+
setup Configure the OpenCode plugin (run once after install)
|
|
32
|
+
refresh-token Refresh the Claude Code OAuth token
|
|
24
33
|
|
|
25
34
|
Options:
|
|
26
35
|
-v, --version Show version
|
|
@@ -35,6 +44,38 @@ Environment variables:
|
|
|
35
44
|
See https://github.com/rynfar/meridian for full documentation.`);
|
|
36
45
|
process.exit(0);
|
|
37
46
|
}
|
|
47
|
+
if (args[0] === "setup") {
|
|
48
|
+
const { findPluginPath, runSetup } = await import("./setup-5x116vbs.js");
|
|
49
|
+
const pluginPath = findPluginPath(import.meta.url);
|
|
50
|
+
const result = runSetup(pluginPath);
|
|
51
|
+
if (result.alreadyConfigured) {
|
|
52
|
+
console.log(`\x1B[32m✓ Meridian plugin already configured\x1B[0m`);
|
|
53
|
+
console.log(` ${result.configPath}`);
|
|
54
|
+
} else {
|
|
55
|
+
if (result.removedStale.length > 0) {
|
|
56
|
+
console.log(` Removed ${result.removedStale.length} stale plugin entr${result.removedStale.length === 1 ? "y" : "ies"}`);
|
|
57
|
+
}
|
|
58
|
+
console.log(`\x1B[32m✓ Meridian plugin configured\x1B[0m`);
|
|
59
|
+
console.log(` Config: ${result.configPath}`);
|
|
60
|
+
console.log(` Plugin: ${result.pluginPath}`);
|
|
61
|
+
if (!result.created) {
|
|
62
|
+
console.log(`
|
|
63
|
+
Restart OpenCode for the plugin to take effect.`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
process.exit(0);
|
|
67
|
+
}
|
|
68
|
+
if (args[0] === "refresh-token") {
|
|
69
|
+
const { refreshOAuthToken } = await import("./tokenRefresh-ywwpe8k2.js");
|
|
70
|
+
const success = await refreshOAuthToken();
|
|
71
|
+
if (success) {
|
|
72
|
+
console.log("Token refreshed successfully");
|
|
73
|
+
process.exit(0);
|
|
74
|
+
} else {
|
|
75
|
+
console.error("Token refresh failed. If the problem persists, run: claude login");
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
38
79
|
var exec = promisify(execCallback);
|
|
39
80
|
process.on("uncaughtException", (err) => {
|
|
40
81
|
console.error(`[PROXY] Uncaught exception (recovered): ${err.message}`);
|
|
@@ -46,6 +87,18 @@ var port = parseInt(process.env.MERIDIAN_PORT ?? process.env.CLAUDE_PROXY_PORT ?
|
|
|
46
87
|
var host = process.env.MERIDIAN_HOST ?? process.env.CLAUDE_PROXY_HOST ?? "127.0.0.1";
|
|
47
88
|
var idleTimeoutSeconds = parseInt(process.env.MERIDIAN_IDLE_TIMEOUT_SECONDS ?? process.env.CLAUDE_PROXY_IDLE_TIMEOUT_SECONDS ?? "120", 10);
|
|
48
89
|
async function runCli(start = startProxyServer, runExec = exec) {
|
|
90
|
+
try {
|
|
91
|
+
const { findOpencodeConfigPath, checkPluginConfigured, findPluginPath } = await import("./setup-5x116vbs.js");
|
|
92
|
+
const configPath = findOpencodeConfigPath();
|
|
93
|
+
const { existsSync } = await import("fs");
|
|
94
|
+
if (existsSync(configPath) && !checkPluginConfigured(configPath)) {
|
|
95
|
+
const pluginPath = findPluginPath(import.meta.url);
|
|
96
|
+
console.error("\x1B[33m⚠ Meridian plugin not found in OpenCode config.\x1B[0m");
|
|
97
|
+
console.error(" Session tracking and subagent model selection won't work.");
|
|
98
|
+
console.error(` Fix: meridian setup`);
|
|
99
|
+
console.error("");
|
|
100
|
+
}
|
|
101
|
+
} catch {}
|
|
49
102
|
try {
|
|
50
103
|
const { stdout } = await runExec("claude auth status", { timeout: 5000 });
|
|
51
104
|
const auth = JSON.parse(stdout);
|
package/dist/proxy/adapter.d.ts
CHANGED
|
@@ -69,7 +69,7 @@ export interface AgentAdapter {
|
|
|
69
69
|
*
|
|
70
70
|
* When this method is defined and returns false, the proxy forces
|
|
71
71
|
* stream=false regardless of the client's body.stream setting.
|
|
72
|
-
* When undefined or returns true, body.stream is used (defaulting to
|
|
72
|
+
* When undefined or returns true, body.stream is used (defaulting to false).
|
|
73
73
|
*/
|
|
74
74
|
prefersStreaming?(body: any): boolean;
|
|
75
75
|
/**
|
package/dist/proxy/errors.d.ts
CHANGED
|
@@ -11,6 +11,16 @@ export interface ClassifiedError {
|
|
|
11
11
|
* Detect specific SDK errors and return helpful messages to the client.
|
|
12
12
|
*/
|
|
13
13
|
export declare function classifyError(errMsg: string): ClassifiedError;
|
|
14
|
+
/**
|
|
15
|
+
* Detect errors caused by an expired or missing OAuth access token.
|
|
16
|
+
* Triggers an inline token refresh + retry in server.ts.
|
|
17
|
+
*
|
|
18
|
+
* Two distinct messages from the Claude Code CLI:
|
|
19
|
+
* - "OAuth token has expired" — CLI sent the token, Anthropic API rejected it
|
|
20
|
+
* - "Not logged in" — CLI checked expiresAt locally and refused to try
|
|
21
|
+
* Both are resolved by refreshing the token.
|
|
22
|
+
*/
|
|
23
|
+
export declare function isExpiredTokenError(errMsg: string): boolean;
|
|
14
24
|
/**
|
|
15
25
|
* Detect errors caused by stale session/message UUIDs.
|
|
16
26
|
* These happen when the upstream Claude session no longer contains
|
|
@@ -22,4 +32,10 @@ export declare function isStaleSessionError(error: unknown): boolean;
|
|
|
22
32
|
* Used by server.ts to decide whether to retry with a smaller context window.
|
|
23
33
|
*/
|
|
24
34
|
export declare function isRateLimitError(errMsg: string): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Detect errors caused by the 1M context window requiring Extra Usage.
|
|
37
|
+
* Max subscribers without Extra Usage enabled get this error when using
|
|
38
|
+
* sonnet[1m] or opus[1m]. The fix is to fall back to the base model.
|
|
39
|
+
*/
|
|
40
|
+
export declare function isExtraUsageRequiredError(errMsg: string): boolean;
|
|
25
41
|
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/proxy/errors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;CAChB;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,eAAe,
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/proxy/errors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;CAChB;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,eAAe,CA+G7D;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAG3D;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAG3D;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAGxD;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAGjE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fileChanges.d.ts","sourceRoot":"","sources":["../../src/proxy/fileChanges.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,4DAA4D;AAC5D,MAAM,WAAW,UAAU;IACzB,8CAA8C;IAC9C,SAAS,EAAE,OAAO,GAAG,QAAQ,CAAA;IAC7B,wCAAwC;IACxC,IAAI,EAAE,MAAM,CAAA;CACb;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,OAAO,EAClB,SAAS,EAAE,MAAM,GAChB,UAAU,GAAG,SAAS,CAexB;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,UAAU,EAAE,EACrB,SAAS,EAAE,MAAM;;oBAIO;QACpB,SAAS,EAAE,MAAM,CAAA;QACjB,UAAU,EAAE,OAAO,CAAA;QACnB,aAAa,EAAE,OAAO,CAAA;QACtB,WAAW,EAAE,MAAM,CAAA;KACpB;EAkBJ;
|
|
1
|
+
{"version":3,"file":"fileChanges.d.ts","sourceRoot":"","sources":["../../src/proxy/fileChanges.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,4DAA4D;AAC5D,MAAM,WAAW,UAAU;IACzB,8CAA8C;IAC9C,SAAS,EAAE,OAAO,GAAG,QAAQ,CAAA;IAC7B,wCAAwC;IACxC,IAAI,EAAE,MAAM,CAAA;CACb;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,OAAO,EAClB,SAAS,EAAE,MAAM,GAChB,UAAU,GAAG,SAAS,CAexB;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,UAAU,EAAE,EACrB,SAAS,EAAE,MAAM;;oBAIO;QACpB,SAAS,EAAE,MAAM,CAAA;QACjB,UAAU,EAAE,OAAO,CAAA;QACnB,aAAa,EAAE,OAAO,CAAA;QACtB,WAAW,EAAE,MAAM,CAAA;KACpB;EAkBJ;AAcD;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,EAAE,CAwCxE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,8BAA8B,CAC5C,QAAQ,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC,EACnD,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,KAAK,UAAU,EAAE,GAChE,UAAU,EAAE,CA0Bd;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,MAAM,GAAG,SAAS,CAgBjF"}
|
package/dist/proxy/models.d.ts
CHANGED
|
@@ -7,7 +7,23 @@ export interface ClaudeAuthStatus {
|
|
|
7
7
|
subscriptionType?: string;
|
|
8
8
|
email?: string;
|
|
9
9
|
}
|
|
10
|
-
export declare function mapModelToClaudeModel(model: string, subscriptionType?: string | null): ClaudeModel;
|
|
10
|
+
export declare function mapModelToClaudeModel(model: string, subscriptionType?: string | null, agentMode?: string | null): ClaudeModel;
|
|
11
|
+
/**
|
|
12
|
+
* Record that Extra Usage is not enabled on this subscription.
|
|
13
|
+
* For the next hour, mapModelToClaudeModel will return the base model
|
|
14
|
+
* directly — no failed [1m] attempt per request. After the cooldown
|
|
15
|
+
* the next request probes [1m] once; if Extra Usage was enabled in the
|
|
16
|
+
* meantime it succeeds and the flag is never set again.
|
|
17
|
+
*/
|
|
18
|
+
export declare function recordExtendedContextUnavailable(): void;
|
|
19
|
+
/**
|
|
20
|
+
* Returns true while within the cooldown window after a confirmed
|
|
21
|
+
* Extra Usage failure. After the window expires this returns false,
|
|
22
|
+
* allowing one probe to check whether Extra Usage has been enabled.
|
|
23
|
+
*/
|
|
24
|
+
export declare function isExtendedContextKnownUnavailable(): boolean;
|
|
25
|
+
/** Reset the Extended Context unavailability timer — for testing only. */
|
|
26
|
+
export declare function resetExtendedContextUnavailable(): void;
|
|
11
27
|
/**
|
|
12
28
|
* Strip the [1m] suffix from a model, returning the base variant.
|
|
13
29
|
* Used for fallback when the 1M context window is rate-limited.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../../src/proxy/models.ts"],"names":[],"mappings":"AAAA;;GAEG;AAUH,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,YAAY,GAAG,MAAM,GAAG,UAAU,GAAG,OAAO,CAAA;AACjF,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AA0BD,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,EAAE,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,WAAW,
|
|
1
|
+
{"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../../src/proxy/models.ts"],"names":[],"mappings":"AAAA;;GAEG;AAUH,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,YAAY,GAAG,MAAM,GAAG,UAAU,GAAG,OAAO,CAAA;AACjF,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AA0BD,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,EAAE,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,WAAW,CAuB7H;AAWD;;;;;;GAMG;AACH,wBAAgB,gCAAgC,IAAI,IAAI,CAEvD;AAED;;;;GAIG;AACH,wBAAgB,iCAAiC,IAAI,OAAO,CAG3D;AAED,0EAA0E;AAC1E,wBAAgB,+BAA+B,IAAI,IAAI,CAEtD;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,WAAW,GAAG,WAAW,CAIpE;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAE9D;AAED,wBAAsB,wBAAwB,IAAI,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAkCjF;AAOD;;;;;;;;;;GAUG;AACH,wBAAsB,4BAA4B,IAAI,OAAO,CAAC,MAAM,CAAC,CA4DpE;AAED,2CAA2C;AAC3C,wBAAgB,qBAAqB,IAAI,IAAI,CAG5C;AAED,kDAAkD;AAClD,wBAAgB,2BAA2B,IAAI,IAAI,CAMlD;AAED;;6DAE6D;AAC7D,wBAAgB,qBAAqB,IAAI,IAAI,CAG5C;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAG/D"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/proxy/server.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AACtE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,CAAA;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/proxy/server.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AACtE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,CAAA;AAmBvD,OAAO,EACL,kBAAkB,EAClB,WAAW,EACX,oBAAoB,EACpB,KAAK,aAAa,EACnB,MAAM,mBAAmB,CAAA;AAG1B,OAAO,EAA+B,iBAAiB,EAAE,mBAAmB,EAAgB,MAAM,iBAAiB,CAAA;AAEnH,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,oBAAoB,EAAE,CAAA;AAChE,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,CAAA;AACjD,YAAY,EAAE,aAAa,EAAE,CAAA;AAyF7B,wBAAgB,iBAAiB,CAAC,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM,GAAG,WAAW,CAyxChF;AAED,wBAAsB,gBAAgB,CAAC,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CA0ChG"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Meridian setup — OpenCode plugin configuration.
|
|
3
|
+
*
|
|
4
|
+
* Manages the meridian plugin entry in ~/.config/opencode/opencode.json
|
|
5
|
+
* (or the platform-equivalent path). Called by:
|
|
6
|
+
* - `meridian setup` — writes the plugin entry
|
|
7
|
+
* - `meridian` startup — warns if plugin is missing
|
|
8
|
+
* - `GET /health` — reports plugin status
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Resolve the OpenCode global config file path.
|
|
12
|
+
* Respects OPENCODE_CONFIG_DIR and XDG_CONFIG_HOME env vars.
|
|
13
|
+
*/
|
|
14
|
+
export declare function findOpencodeConfigPath(): string;
|
|
15
|
+
/**
|
|
16
|
+
* Resolve the absolute path to plugin/meridian.ts from any entry point.
|
|
17
|
+
* Works whether called from bin/cli.ts (dev) or dist/cli.js (installed).
|
|
18
|
+
*/
|
|
19
|
+
export declare function findPluginPath(fromUrl: string): string;
|
|
20
|
+
/**
|
|
21
|
+
* Returns true if the meridian plugin is already configured in the
|
|
22
|
+
* OpenCode global config. Returns false if config doesn't exist or
|
|
23
|
+
* plugin is missing.
|
|
24
|
+
*/
|
|
25
|
+
export declare function checkPluginConfigured(configPath?: string): boolean;
|
|
26
|
+
export interface SetupResult {
|
|
27
|
+
configPath: string;
|
|
28
|
+
pluginPath: string;
|
|
29
|
+
alreadyConfigured: boolean;
|
|
30
|
+
removedStale: string[];
|
|
31
|
+
created: boolean;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Configure the meridian plugin in ~/.config/opencode/opencode.json.
|
|
35
|
+
*
|
|
36
|
+
* - Creates the config file if it doesn't exist
|
|
37
|
+
* - Removes stale meridian plugin entries from previous installs
|
|
38
|
+
* - Adds the current plugin path
|
|
39
|
+
* - Leaves all other plugins untouched
|
|
40
|
+
*/
|
|
41
|
+
export declare function runSetup(pluginPath: string, configPath?: string): SetupResult;
|
|
42
|
+
//# sourceMappingURL=setup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../src/proxy/setup.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAWH;;;GAGG;AACH,wBAAgB,sBAAsB,IAAI,MAAM,CAW/C;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAGtD;AAkBD;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAWlE;AAMD,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;IAClB,iBAAiB,EAAE,OAAO,CAAA;IAC1B,YAAY,EAAE,MAAM,EAAE,CAAA;IACtB,OAAO,EAAE,OAAO,CAAA;CACjB;AAED;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,WAAW,CAgC7E"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-platform OAuth token refresh for Claude Code credentials.
|
|
3
|
+
*
|
|
4
|
+
* Storage backends:
|
|
5
|
+
* macOS — system Keychain via /usr/bin/security (no prompt — pre-authorised)
|
|
6
|
+
* Linux — ~/.claude/.credentials.json
|
|
7
|
+
*
|
|
8
|
+
* The credential store is dependency-injectable for testing. Production code
|
|
9
|
+
* uses createPlatformCredentialStore() which picks the right backend
|
|
10
|
+
* automatically.
|
|
11
|
+
*
|
|
12
|
+
* Concurrent calls to refreshOAuthToken() are deduplicated: if a refresh is
|
|
13
|
+
* already in flight, subsequent callers wait for the same promise rather than
|
|
14
|
+
* issuing a second network request and racing on the write.
|
|
15
|
+
*/
|
|
16
|
+
interface OAuthCredentials {
|
|
17
|
+
accessToken: string;
|
|
18
|
+
refreshToken: string;
|
|
19
|
+
expiresAt: number;
|
|
20
|
+
scopes?: string[];
|
|
21
|
+
subscriptionType?: string;
|
|
22
|
+
rateLimitTier?: string;
|
|
23
|
+
}
|
|
24
|
+
interface CredentialsFile {
|
|
25
|
+
claudeAiOauth: OAuthCredentials;
|
|
26
|
+
[key: string]: unknown;
|
|
27
|
+
}
|
|
28
|
+
export interface CredentialStore {
|
|
29
|
+
read(): Promise<CredentialsFile | null>;
|
|
30
|
+
write(credentials: CredentialsFile): Promise<boolean>;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Returns the appropriate credential store for the current platform.
|
|
34
|
+
*/
|
|
35
|
+
export declare function createPlatformCredentialStore(): CredentialStore;
|
|
36
|
+
/**
|
|
37
|
+
* Refresh the Claude Code OAuth access token.
|
|
38
|
+
*
|
|
39
|
+
* Reads the stored refresh token, exchanges it for a new access token via
|
|
40
|
+
* Anthropic's OAuth endpoint, and writes the updated credentials back.
|
|
41
|
+
*
|
|
42
|
+
* Returns true on success, false on any failure. Concurrent calls share one
|
|
43
|
+
* in-flight request so only one network round-trip is made.
|
|
44
|
+
*
|
|
45
|
+
* @param store Override the credential store (for testing).
|
|
46
|
+
*/
|
|
47
|
+
export declare function refreshOAuthToken(store?: CredentialStore): Promise<boolean>;
|
|
48
|
+
/** Reset in-flight state — for testing only. */
|
|
49
|
+
export declare function resetInflightRefresh(): void;
|
|
50
|
+
export {};
|
|
51
|
+
//# sourceMappingURL=tokenRefresh.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokenRefresh.d.ts","sourceRoot":"","sources":["../../src/proxy/tokenRefresh.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAeH,UAAU,gBAAgB;IACxB,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,CAAA;IACpB,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;IACjB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,UAAU,eAAe;IACvB,aAAa,EAAE,gBAAgB,CAAA;IAC/B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB;AAMD,MAAM,WAAW,eAAe;IAC9B,IAAI,IAAI,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAAA;IACvC,KAAK,CAAC,WAAW,EAAE,eAAe,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;CACtD;AA2FD;;GAEG;AACH,wBAAgB,6BAA6B,IAAI,eAAe,CAE/D;AASD;;;;;;;;;;GAUG;AACH,wBAAsB,iBAAiB,CAAC,KAAK,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,OAAO,CAAC,CAQjF;AAiED,gDAAgD;AAChD,wBAAgB,oBAAoB,IAAI,IAAI,CAE3C"}
|
package/dist/server.js
CHANGED
|
@@ -6,7 +6,10 @@ import {
|
|
|
6
6
|
getMaxSessionsLimit,
|
|
7
7
|
hashMessage,
|
|
8
8
|
startProxyServer
|
|
9
|
-
} from "./cli-
|
|
9
|
+
} from "./cli-s6f9jefk.js";
|
|
10
|
+
import"./cli-rtab0qa6.js";
|
|
11
|
+
import"./cli-m9pfb7h9.js";
|
|
12
|
+
import"./cli-a05ws7rb.js";
|
|
10
13
|
export {
|
|
11
14
|
startProxyServer,
|
|
12
15
|
hashMessage,
|
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
* Inline HTML dashboard for telemetry.
|
|
3
3
|
* No framework, no build step, no CDN. Single self-contained page.
|
|
4
4
|
*/
|
|
5
|
-
export declare const dashboardHtml = "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Meridian \u2014 Telemetry</title>\n<style>\n :root {\n --bg: #0d1117; --surface: #161b22; --border: #30363d;\n --text: #e6edf3; --muted: #8b949e; --accent: #58a6ff;\n --green: #3fb950; --yellow: #d29922; --red: #f85149;\n --blue: #58a6ff; --purple: #bc8cff;\n --queue: #d29922; --ttfb: #58a6ff; --upstream: #3fb950; --total: #bc8cff;\n }\n * { box-sizing: border-box; margin: 0; padding: 0; }\n body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;\n background: var(--bg); color: var(--text); padding: 24px; line-height: 1.5; }\n h1 { font-size: 20px; font-weight: 600; margin-bottom: 4px; }\n .subtitle { color: var(--muted); font-size: 13px; margin-bottom: 24px; }\n .cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; margin-bottom: 24px; }\n .card { background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 16px; }\n .card-label { font-size: 12px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.5px; }\n .card-value { font-size: 28px; font-weight: 600; margin-top: 4px; font-variant-numeric: tabular-nums; }\n .card-detail { font-size: 12px; color: var(--muted); margin-top: 2px; }\n .section { margin-bottom: 24px; }\n .section-title { font-size: 14px; font-weight: 600; margin-bottom: 12px; color: var(--muted);\n text-transform: uppercase; letter-spacing: 0.5px; }\n table { width: 100%; border-collapse: collapse; background: var(--surface);\n border: 1px solid var(--border); border-radius: 8px; overflow: hidden; font-size: 13px; }\n th { text-align: left; padding: 10px 12px; background: var(--bg); color: var(--muted);\n font-weight: 500; font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; }\n td { padding: 8px 12px; border-top: 1px solid var(--border); font-variant-numeric: tabular-nums; }\n tr:hover td { background: rgba(88,166,255,0.04); }\n .waterfall { display: flex; align-items: center; height: 18px; min-width: 200px; position: relative; }\n .waterfall-seg { height: 100%; border-radius: 2px; min-width: 2px; }\n .waterfall-seg.queue { background: var(--queue); }\n .waterfall-seg.overhead { background: var(--yellow); }\n .waterfall-seg.ttfb { background: var(--ttfb); }\n .waterfall-seg.response { background: var(--upstream); }\n .legend { display: flex; gap: 16px; margin-bottom: 12px; font-size: 12px; color: var(--muted); }\n .legend-dot { width: 10px; height: 10px; border-radius: 2px; display: inline-block; margin-right: 4px; vertical-align: middle; }\n .status-ok { color: var(--green); }\n .status-err { color: var(--red); }\n .pct-table td:first-child { font-weight: 500; }\n .pct-table .phase-dot { display: inline-block; width: 8px; height: 8px; border-radius: 2px; margin-right: 6px; }\n .mono { font-family: 'SF Mono', SFMono-Regular, Consolas, monospace; font-size: 12px; }\n .refresh-bar { display: flex; align-items: center; gap: 8px; margin-bottom: 16px; }\n .refresh-bar select, .refresh-bar button {\n background: var(--surface); color: var(--text); border: 1px solid var(--border);\n border-radius: 6px; padding: 4px 10px; font-size: 12px; cursor: pointer;\n }\n .refresh-bar button:hover { border-color: var(--accent); }\n .refresh-indicator { font-size: 11px; color: var(--muted); }\n .empty { text-align: center; padding: 48px; color: var(--muted); }\n\n /* Tabs */\n .tabs { display: flex; gap: 0; margin-bottom: 20px; border-bottom: 1px solid var(--border); }\n .tab { padding: 10px 20px; font-size: 13px; font-weight: 500; color: var(--muted); cursor: pointer;\n border-bottom: 2px solid transparent; margin-bottom: -1px; transition: color 0.15s, border-color 0.15s;\n user-select: none; }\n .tab:hover { color: var(--text); }\n .tab.active { color: var(--accent); border-bottom-color: var(--accent); }\n .tab-badge { font-size: 10px; padding: 1px 6px; border-radius: 10px; margin-left: 6px;\n background: var(--border); color: var(--muted); font-variant-numeric: tabular-nums; }\n .tab.active .tab-badge { background: rgba(88,166,255,0.15); color: var(--accent); }\n .tab-panel { display: none; }\n .tab-panel.active { display: block; }\n\n /* Log filters */\n .log-filters { display: flex; gap: 8px; margin-bottom: 12px; }\n .log-filter { font-size: 11px; padding: 3px 10px; border-radius: 12px; cursor: pointer;\n border: 1px solid var(--border); background: var(--surface); color: var(--muted);\n transition: all 0.15s; }\n .log-filter:hover { border-color: var(--accent); color: var(--text); }\n .log-filter.active { background: rgba(88,166,255,0.1); border-color: var(--accent); color: var(--accent); }\n</style>\n</head>\n<body>\n<h1>Meridian</h1>\n<div class=\"subtitle\">Request Performance Telemetry</div>\n\n<div class=\"refresh-bar\">\n <select id=\"window\">\n <option value=\"300000\">Last 5 min</option>\n <option value=\"900000\">Last 15 min</option>\n <option value=\"3600000\" selected>Last 1 hour</option>\n <option value=\"86400000\">Last 24 hours</option>\n </select>\n <button onclick=\"refresh()\">Refresh</button>\n <label><input type=\"checkbox\" id=\"autoRefresh\" checked> Auto (5s)</label>\n <span class=\"refresh-indicator\" id=\"lastUpdate\"></span>\n</div>\n\n<div id=\"content\"><div class=\"empty\">Loading\u2026</div></div>\n\n<script>\nconst $ = s => document.querySelector(s);\nconst $$ = s => document.querySelectorAll(s);\nlet timer;\nlet activeTab = 'requests';\nlet activeLogFilter = 'all';\n\nfunction ms(v) {\n if (v == null) return '\u2014';\n if (v < 1000) return v + 'ms';\n return (v / 1000).toFixed(1) + 's';\n}\n\nfunction ago(ts) {\n const s = Math.floor((Date.now() - ts) / 1000);\n if (s < 60) return s + 's ago';\n if (s < 3600) return Math.floor(s/60) + 'm ago';\n return Math.floor(s/3600) + 'h ago';\n}\n\nfunction pctRow(label, color, phase) {\n return '<tr>'\n + '<td><span class=\"phase-dot\" style=\"background:' + color + '\"></span>' + label + '</td>'\n + '<td class=\"mono\">' + ms(phase.p50) + '</td>'\n + '<td class=\"mono\">' + ms(phase.p95) + '</td>'\n + '<td class=\"mono\">' + ms(phase.p99) + '</td>'\n + '<td class=\"mono\">' + ms(phase.min) + '</td>'\n + '<td class=\"mono\">' + ms(phase.max) + '</td>'\n + '<td class=\"mono\">' + ms(phase.avg) + '</td>'\n + '</tr>';\n}\n\nfunction switchTab(tab) {\n activeTab = tab;\n $$('.tab').forEach(t => t.classList.toggle('active', t.dataset.tab === tab));\n $$('.tab-panel').forEach(p => p.classList.toggle('active', p.id === 'panel-' + tab));\n}\n\nfunction setLogFilter(filter) {\n activeLogFilter = filter;\n $$('.log-filter').forEach(f => f.classList.toggle('active', f.dataset.filter === filter));\n $$('.log-row').forEach(r => {\n r.style.display = (filter === 'all' || r.dataset.category === filter) ? '' : 'none';\n });\n}\n\nasync function refresh() {\n const w = $('#window').value;\n try {\n const [summary, reqs, logs] = await Promise.all([\n fetch('/telemetry/summary?window=' + w).then(r => r.json()),\n fetch('/telemetry/requests?limit=50&since=' + (Date.now() - Number(w))).then(r => r.json()),\n fetch('/telemetry/logs?limit=200&since=' + (Date.now() - Number(w))).then(r => r.json()),\n ]);\n render(summary, reqs, logs);\n $('#lastUpdate').textContent = 'Updated ' + new Date().toLocaleTimeString();\n } catch (e) {\n $('#content').innerHTML = '<div class=\"empty\">Failed to load telemetry</div>';\n }\n}\n\nfunction render(s, reqs, logs) {\n if (s.totalRequests === 0 && (!logs || logs.length === 0)) {\n $('#content').innerHTML = '<div class=\"empty\">No requests recorded yet. Send a request through the proxy to see telemetry.</div>';\n return;\n }\n\n // Count lineage types for badges\n const lineageCounts = {};\n for (const r of reqs) { const t = r.lineageType || 'unknown'; lineageCounts[t] = (lineageCounts[t] || 0) + 1; }\n const logCounts = { session: 0, lineage: 0, error: 0 };\n for (const l of logs) { if (logCounts[l.category] !== undefined) logCounts[l.category]++; }\n\n // Tabs\n let html = '<div class=\"tabs\">'\n + '<div class=\"tab' + (activeTab === 'overview' ? ' active' : '') + '\" data-tab=\"overview\" onclick=\"switchTab('overview')\">Overview</div>'\n + '<div class=\"tab' + (activeTab === 'requests' ? ' active' : '') + '\" data-tab=\"requests\" onclick=\"switchTab('requests')\">'\n + 'Requests<span class=\"tab-badge\">' + reqs.length + '</span></div>'\n + '<div class=\"tab' + (activeTab === 'logs' ? ' active' : '') + '\" data-tab=\"logs\" onclick=\"switchTab('logs')\">'\n + 'Logs<span class=\"tab-badge\">' + logs.length + '</span></div>'\n + '</div>';\n\n // ==================== Overview tab ====================\n html += '<div id=\"panel-overview\" class=\"tab-panel' + (activeTab === 'overview' ? ' active' : '') + '\">';\n\n // Summary cards\n html += '<div class=\"cards\">'\n + card('Requests', s.totalRequests, s.requestsPerMinute.toFixed(1) + ' req/min')\n + card('Errors', s.errorCount, s.totalRequests > 0 ? ((s.errorCount/s.totalRequests)*100).toFixed(1) + '% error rate' : '')\n + card('Median Total', ms(s.totalDuration.p50), 'p95: ' + ms(s.totalDuration.p95))\n + card('Median TTFB', ms(s.ttfb.p50), 'p95: ' + ms(s.ttfb.p95))\n + card('Proxy Overhead', ms(s.proxyOverhead.p50), 'p95: ' + ms(s.proxyOverhead.p95))\n + card('Queue Wait', ms(s.queueWait.p50), 'p95: ' + ms(s.queueWait.p95))\n + '</div>';\n\n // Model breakdown\n const models = Object.entries(s.byModel);\n if (models.length > 0) {\n html += '<div class=\"cards\">';\n for (const [name, data] of models) {\n html += card(name, data.count + ' reqs', 'avg ' + ms(data.avgTotalMs));\n }\n html += '</div>';\n }\n\n // Lineage breakdown\n if (Object.keys(lineageCounts).length > 0) {\n html += '<div class=\"cards\">';\n const lineageColors = {continuation:'var(--green)',compaction:'var(--yellow)',undo:'var(--purple)',diverged:'var(--red)',new:'var(--muted)'};\n for (const [type, count] of Object.entries(lineageCounts)) {\n html += '<div class=\"card\"><div class=\"card-label\">Lineage: ' + type + '</div>'\n + '<div class=\"card-value\" style=\"color:' + (lineageColors[type] || 'var(--text)') + '\">' + count + '</div></div>';\n }\n html += '</div>';\n }\n\n // Percentile table\n html += '<div class=\"section\"><div class=\"section-title\">Percentiles</div>'\n + '<table class=\"pct-table\"><thead><tr><th>Phase</th><th>p50</th><th>p95</th><th>p99</th><th>Min</th><th>Max</th><th>Avg</th></tr></thead><tbody>'\n + pctRow('Queue Wait', 'var(--queue)', s.queueWait)\n + pctRow('Proxy Overhead', 'var(--yellow)', s.proxyOverhead)\n + pctRow('TTFB', 'var(--ttfb)', s.ttfb)\n + pctRow('Upstream', 'var(--upstream)', s.upstreamDuration)\n + pctRow('Total', 'var(--purple)', s.totalDuration)\n + '</tbody></table></div>';\n\n html += '</div>'; // end overview panel\n\n // ==================== Requests tab ====================\n html += '<div id=\"panel-requests\" class=\"tab-panel' + (activeTab === 'requests' ? ' active' : '') + '\">';\n\n html += '<div class=\"legend\">'\n + '<span><span class=\"legend-dot\" style=\"background:var(--queue)\"></span>Queue</span>'\n + '<span><span class=\"legend-dot\" style=\"background:var(--yellow)\"></span>Proxy</span>'\n + '<span><span class=\"legend-dot\" style=\"background:var(--ttfb)\"></span>TTFB</span>'\n + '<span><span class=\"legend-dot\" style=\"background:var(--upstream)\"></span>Response</span>'\n + '</div>'\n + '<table><thead><tr><th>Time</th><th>Model</th><th>Mode</th><th>Session</th><th>Status</th>'\n + '<th>Queue</th><th>Proxy</th><th>TTFB</th><th>Total</th><th>Waterfall</th></tr></thead><tbody>';\n\n const maxTotal = Math.max(...reqs.map(r => r.totalDurationMs), 1);\n\n for (const r of reqs) {\n const statusClass = r.error ? 'status-err' : 'status-ok';\n const statusText = r.error ? r.error : r.status;\n const scale = 280 / maxTotal;\n const qW = Math.max(r.queueWaitMs * scale, 2);\n const ohW = Math.max((r.proxyOverheadMs || 0) * scale, 0);\n const ttfbW = Math.max((r.ttfbMs || 0) * scale, 0);\n const respW = Math.max((r.upstreamDurationMs - (r.ttfbMs || 0)) * scale, 2);\n\n const lineageBadge = r.lineageType ? '<span style=\"font-size:10px;padding:1px 5px;border-radius:3px;background:' + ({continuation:'var(--green)',compaction:'var(--yellow)',undo:'var(--purple)',diverged:'var(--red)',new:'var(--muted)'}[r.lineageType] || 'var(--muted)') + ';color:var(--bg)\">' + r.lineageType + '</span>' : '';\n const sessionShort = r.sdkSessionId ? r.sdkSessionId.slice(0, 8) : '\u2014';\n const msgCount = r.messageCount != null ? r.messageCount : '?';\n\n html += '<tr>'\n + '<td class=\"mono\">' + ago(r.timestamp) + '</td>'\n + '<td>' + (r.requestModel || r.model) + '<br><span style=\"font-size:10px;color:var(--muted)\">' + r.model + '</span></td>'\n + '<td>' + r.mode + '</td>'\n + '<td class=\"mono\">' + sessionShort + ' ' + lineageBadge + '<br><span style=\"font-size:10px;color:var(--muted)\">' + msgCount + ' msgs</span></td>'\n + '<td class=\"' + statusClass + '\">' + statusText + '</td>'\n + '<td class=\"mono\">' + ms(r.queueWaitMs) + '</td>'\n + '<td class=\"mono\">' + ms(r.proxyOverheadMs) + '</td>'\n + '<td class=\"mono\">' + ms(r.ttfbMs) + '</td>'\n + '<td class=\"mono\">' + ms(r.totalDurationMs) + '</td>'\n + '<td><div class=\"waterfall\">'\n + '<div class=\"waterfall-seg queue\" style=\"width:' + qW + 'px\"></div>'\n + '<div class=\"waterfall-seg overhead\" style=\"width:' + ohW + 'px\"></div>'\n + '<div class=\"waterfall-seg ttfb\" style=\"width:' + ttfbW + 'px\"></div>'\n + '<div class=\"waterfall-seg response\" style=\"width:' + respW + 'px\"></div>'\n + '</div></td>'\n + '</tr>';\n }\n html += '</tbody></table>';\n html += '</div>'; // end requests panel\n\n // ==================== Logs tab ====================\n html += '<div id=\"panel-logs\" class=\"tab-panel' + (activeTab === 'logs' ? ' active' : '') + '\">';\n\n // Filter buttons\n html += '<div class=\"log-filters\">'\n + '<span class=\"log-filter' + (activeLogFilter === 'all' ? ' active' : '') + '\" data-filter=\"all\" onclick=\"setLogFilter('all')\">All<span class=\"tab-badge\">' + logs.length + '</span></span>'\n + '<span class=\"log-filter' + (activeLogFilter === 'session' ? ' active' : '') + '\" data-filter=\"session\" onclick=\"setLogFilter('session')\" style=\"--accent:var(--blue)\">Session<span class=\"tab-badge\">' + logCounts.session + '</span></span>'\n + '<span class=\"log-filter' + (activeLogFilter === 'lineage' ? ' active' : '') + '\" data-filter=\"lineage\" onclick=\"setLogFilter('lineage')\" style=\"--accent:var(--purple)\">Lineage<span class=\"tab-badge\">' + logCounts.lineage + '</span></span>'\n + '<span class=\"log-filter' + (activeLogFilter === 'error' ? ' active' : '') + '\" data-filter=\"error\" onclick=\"setLogFilter('error')\" style=\"--accent:var(--red)\">Error<span class=\"tab-badge\">' + logCounts.error + '</span></span>'\n + '</div>';\n\n if (logs.length === 0) {\n html += '<div class=\"empty\">No diagnostic logs in this time window.</div>';\n } else {\n html += '<table><thead><tr>'\n + '<th style=\"width:80px\">Time</th><th style=\"width:55px\">Level</th><th style=\"width:70px\">Category</th><th>Message</th>'\n + '</tr></thead><tbody>';\n\n for (const log of logs) {\n const levelColor = {info:'var(--green)',warn:'var(--yellow)',error:'var(--red)'}[log.level] || 'var(--muted)';\n const catColor = {session:'var(--blue)',lineage:'var(--purple)',error:'var(--red)',lifecycle:'var(--muted)'}[log.category] || 'var(--muted)';\n const display = (activeLogFilter === 'all' || log.category === activeLogFilter) ? '' : 'display:none';\n html += '<tr class=\"log-row\" data-category=\"' + log.category + '\" style=\"' + display + '\">'\n + '<td class=\"mono\">' + ago(log.timestamp) + '</td>'\n + '<td><span style=\"color:' + levelColor + '\">' + log.level + '</span></td>'\n + '<td><span style=\"color:' + catColor + '\">' + log.category + '</span></td>'\n + '<td class=\"mono\" style=\"word-break:break-all\">' + log.message + '</td>'\n + '</tr>';\n }\n html += '</tbody></table>';\n }\n html += '</div>'; // end logs panel\n\n $('#content').innerHTML = html;\n}\n\nfunction card(label, value, detail) {\n return '<div class=\"card\"><div class=\"card-label\">' + label + '</div>'\n + '<div class=\"card-value\">' + value + '</div>'\n + (detail ? '<div class=\"card-detail\">' + detail + '</div>' : '')\n + '</div>';\n}\n\n$('#autoRefresh').addEventListener('change', function() {\n clearInterval(timer);\n if (this.checked) timer = setInterval(refresh, 5000);\n});\n$('#window').addEventListener('change', refresh);\n\nrefresh();\ntimer = setInterval(refresh, 5000);\n</script>\n</body>\n</html>";
|
|
5
|
+
export declare const dashboardHtml = "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Meridian \u2014 Telemetry</title>\n<link rel=\"icon\" type=\"image/svg+xml\" href=\"/telemetry/icon.svg\">\n<style>\n :root {\n --bg: #0d1117; --surface: #161b22; --border: #30363d;\n --text: #e6edf3; --muted: #8b949e; --accent: #58a6ff;\n --green: #3fb950; --yellow: #d29922; --red: #f85149;\n --blue: #58a6ff; --purple: #bc8cff;\n --queue: #d29922; --ttfb: #58a6ff; --upstream: #3fb950; --total: #bc8cff;\n }\n * { box-sizing: border-box; margin: 0; padding: 0; }\n body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;\n background: var(--bg); color: var(--text); padding: 24px; line-height: 1.5; }\n h1 { font-size: 20px; font-weight: 600; margin-bottom: 4px; }\n .subtitle { color: var(--muted); font-size: 13px; margin-bottom: 24px; }\n .cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; margin-bottom: 24px; }\n .card { background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 16px; }\n .card-label { font-size: 12px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.5px; }\n .card-value { font-size: 28px; font-weight: 600; margin-top: 4px; font-variant-numeric: tabular-nums; }\n .card-detail { font-size: 12px; color: var(--muted); margin-top: 2px; }\n .section { margin-bottom: 24px; }\n .section-title { font-size: 14px; font-weight: 600; margin-bottom: 12px; color: var(--muted);\n text-transform: uppercase; letter-spacing: 0.5px; }\n table { width: 100%; border-collapse: collapse; background: var(--surface);\n border: 1px solid var(--border); border-radius: 8px; overflow: hidden; font-size: 13px; }\n th { text-align: left; padding: 10px 12px; background: var(--bg); color: var(--muted);\n font-weight: 500; font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; }\n td { padding: 8px 12px; border-top: 1px solid var(--border); font-variant-numeric: tabular-nums; }\n tr:hover td { background: rgba(88,166,255,0.04); }\n .waterfall { display: flex; align-items: center; height: 18px; min-width: 200px; position: relative; }\n .waterfall-seg { height: 100%; border-radius: 2px; min-width: 2px; }\n .waterfall-seg.queue { background: var(--queue); }\n .waterfall-seg.overhead { background: var(--yellow); }\n .waterfall-seg.ttfb { background: var(--ttfb); }\n .waterfall-seg.response { background: var(--upstream); }\n .legend { display: flex; gap: 16px; margin-bottom: 12px; font-size: 12px; color: var(--muted); }\n .legend-dot { width: 10px; height: 10px; border-radius: 2px; display: inline-block; margin-right: 4px; vertical-align: middle; }\n .status-ok { color: var(--green); }\n .status-err { color: var(--red); }\n .pct-table td:first-child { font-weight: 500; }\n .pct-table .phase-dot { display: inline-block; width: 8px; height: 8px; border-radius: 2px; margin-right: 6px; }\n .mono { font-family: 'SF Mono', SFMono-Regular, Consolas, monospace; font-size: 12px; }\n .refresh-bar { display: flex; align-items: center; gap: 8px; margin-bottom: 16px; }\n .refresh-bar select, .refresh-bar button {\n background: var(--surface); color: var(--text); border: 1px solid var(--border);\n border-radius: 6px; padding: 4px 10px; font-size: 12px; cursor: pointer;\n }\n .refresh-bar button:hover { border-color: var(--accent); }\n .refresh-indicator { font-size: 11px; color: var(--muted); }\n .empty { text-align: center; padding: 48px; color: var(--muted); }\n\n /* Tabs */\n .tabs { display: flex; gap: 0; margin-bottom: 20px; border-bottom: 1px solid var(--border); }\n .tab { padding: 10px 20px; font-size: 13px; font-weight: 500; color: var(--muted); cursor: pointer;\n border-bottom: 2px solid transparent; margin-bottom: -1px; transition: color 0.15s, border-color 0.15s;\n user-select: none; }\n .tab:hover { color: var(--text); }\n .tab.active { color: var(--accent); border-bottom-color: var(--accent); }\n .tab-badge { font-size: 10px; padding: 1px 6px; border-radius: 10px; margin-left: 6px;\n background: var(--border); color: var(--muted); font-variant-numeric: tabular-nums; }\n .tab.active .tab-badge { background: rgba(88,166,255,0.15); color: var(--accent); }\n .tab-panel { display: none; }\n .tab-panel.active { display: block; }\n\n /* Log filters */\n .log-filters { display: flex; gap: 8px; margin-bottom: 12px; }\n .log-filter { font-size: 11px; padding: 3px 10px; border-radius: 12px; cursor: pointer;\n border: 1px solid var(--border); background: var(--surface); color: var(--muted);\n transition: all 0.15s; }\n .log-filter:hover { border-color: var(--accent); color: var(--text); }\n .log-filter.active { background: rgba(88,166,255,0.1); border-color: var(--accent); color: var(--accent); }\n</style>\n</head>\n<body>\n<h1>Meridian</h1>\n<div class=\"subtitle\">Request Performance Telemetry</div>\n\n<div class=\"refresh-bar\">\n <select id=\"window\">\n <option value=\"300000\">Last 5 min</option>\n <option value=\"900000\">Last 15 min</option>\n <option value=\"3600000\" selected>Last 1 hour</option>\n <option value=\"86400000\">Last 24 hours</option>\n </select>\n <button onclick=\"refresh()\">Refresh</button>\n <label><input type=\"checkbox\" id=\"autoRefresh\" checked> Auto (5s)</label>\n <span class=\"refresh-indicator\" id=\"lastUpdate\"></span>\n</div>\n\n<div id=\"content\"><div class=\"empty\">Loading\u2026</div></div>\n\n<script>\nconst $ = s => document.querySelector(s);\nconst $$ = s => document.querySelectorAll(s);\nlet timer;\nlet activeTab = 'requests';\nlet activeLogFilter = 'all';\n\nfunction ms(v) {\n if (v == null) return '\u2014';\n if (v < 1000) return v + 'ms';\n return (v / 1000).toFixed(1) + 's';\n}\n\nfunction ago(ts) {\n const s = Math.floor((Date.now() - ts) / 1000);\n if (s < 60) return s + 's ago';\n if (s < 3600) return Math.floor(s/60) + 'm ago';\n return Math.floor(s/3600) + 'h ago';\n}\n\nfunction pctRow(label, color, phase) {\n return '<tr>'\n + '<td><span class=\"phase-dot\" style=\"background:' + color + '\"></span>' + label + '</td>'\n + '<td class=\"mono\">' + ms(phase.p50) + '</td>'\n + '<td class=\"mono\">' + ms(phase.p95) + '</td>'\n + '<td class=\"mono\">' + ms(phase.p99) + '</td>'\n + '<td class=\"mono\">' + ms(phase.min) + '</td>'\n + '<td class=\"mono\">' + ms(phase.max) + '</td>'\n + '<td class=\"mono\">' + ms(phase.avg) + '</td>'\n + '</tr>';\n}\n\nfunction switchTab(tab) {\n activeTab = tab;\n $$('.tab').forEach(t => t.classList.toggle('active', t.dataset.tab === tab));\n $$('.tab-panel').forEach(p => p.classList.toggle('active', p.id === 'panel-' + tab));\n}\n\nfunction setLogFilter(filter) {\n activeLogFilter = filter;\n $$('.log-filter').forEach(f => f.classList.toggle('active', f.dataset.filter === filter));\n $$('.log-row').forEach(r => {\n r.style.display = (filter === 'all' || r.dataset.category === filter) ? '' : 'none';\n });\n}\n\nasync function refresh() {\n const w = $('#window').value;\n try {\n const [summary, reqs, logs] = await Promise.all([\n fetch('/telemetry/summary?window=' + w).then(r => r.json()),\n fetch('/telemetry/requests?limit=50&since=' + (Date.now() - Number(w))).then(r => r.json()),\n fetch('/telemetry/logs?limit=200&since=' + (Date.now() - Number(w))).then(r => r.json()),\n ]);\n render(summary, reqs, logs);\n $('#lastUpdate').textContent = 'Updated ' + new Date().toLocaleTimeString();\n } catch (e) {\n $('#content').innerHTML = '<div class=\"empty\">Failed to load telemetry</div>';\n }\n}\n\nfunction render(s, reqs, logs) {\n if (s.totalRequests === 0 && (!logs || logs.length === 0)) {\n $('#content').innerHTML = '<div class=\"empty\">No requests recorded yet. Send a request through the proxy to see telemetry.</div>';\n return;\n }\n\n // Count lineage types for badges\n const lineageCounts = {};\n for (const r of reqs) { const t = r.lineageType || 'unknown'; lineageCounts[t] = (lineageCounts[t] || 0) + 1; }\n const logCounts = { session: 0, lineage: 0, error: 0 };\n for (const l of logs) { if (logCounts[l.category] !== undefined) logCounts[l.category]++; }\n\n // Tabs\n let html = '<div class=\"tabs\">'\n + '<div class=\"tab' + (activeTab === 'overview' ? ' active' : '') + '\" data-tab=\"overview\" onclick=\"switchTab('overview')\">Overview</div>'\n + '<div class=\"tab' + (activeTab === 'requests' ? ' active' : '') + '\" data-tab=\"requests\" onclick=\"switchTab('requests')\">'\n + 'Requests<span class=\"tab-badge\">' + reqs.length + '</span></div>'\n + '<div class=\"tab' + (activeTab === 'logs' ? ' active' : '') + '\" data-tab=\"logs\" onclick=\"switchTab('logs')\">'\n + 'Logs<span class=\"tab-badge\">' + logs.length + '</span></div>'\n + '</div>';\n\n // ==================== Overview tab ====================\n html += '<div id=\"panel-overview\" class=\"tab-panel' + (activeTab === 'overview' ? ' active' : '') + '\">';\n\n // Summary cards\n html += '<div class=\"cards\">'\n + card('Requests', s.totalRequests, s.requestsPerMinute.toFixed(1) + ' req/min')\n + card('Errors', s.errorCount, s.totalRequests > 0 ? ((s.errorCount/s.totalRequests)*100).toFixed(1) + '% error rate' : '')\n + card('Median Total', ms(s.totalDuration.p50), 'p95: ' + ms(s.totalDuration.p95))\n + card('Median TTFB', ms(s.ttfb.p50), 'p95: ' + ms(s.ttfb.p95))\n + card('Proxy Overhead', ms(s.proxyOverhead.p50), 'p95: ' + ms(s.proxyOverhead.p95))\n + card('Queue Wait', ms(s.queueWait.p50), 'p95: ' + ms(s.queueWait.p95))\n + '</div>';\n\n // Model breakdown\n const models = Object.entries(s.byModel);\n if (models.length > 0) {\n html += '<div class=\"cards\">';\n for (const [name, data] of models) {\n html += card(name, data.count + ' reqs', 'avg ' + ms(data.avgTotalMs));\n }\n html += '</div>';\n }\n\n // Lineage breakdown\n if (Object.keys(lineageCounts).length > 0) {\n html += '<div class=\"cards\">';\n const lineageColors = {continuation:'var(--green)',compaction:'var(--yellow)',undo:'var(--purple)',diverged:'var(--red)',new:'var(--muted)'};\n for (const [type, count] of Object.entries(lineageCounts)) {\n html += '<div class=\"card\"><div class=\"card-label\">Lineage: ' + type + '</div>'\n + '<div class=\"card-value\" style=\"color:' + (lineageColors[type] || 'var(--text)') + '\">' + count + '</div></div>';\n }\n html += '</div>';\n }\n\n // Percentile table\n html += '<div class=\"section\"><div class=\"section-title\">Percentiles</div>'\n + '<table class=\"pct-table\"><thead><tr><th>Phase</th><th>p50</th><th>p95</th><th>p99</th><th>Min</th><th>Max</th><th>Avg</th></tr></thead><tbody>'\n + pctRow('Queue Wait', 'var(--queue)', s.queueWait)\n + pctRow('Proxy Overhead', 'var(--yellow)', s.proxyOverhead)\n + pctRow('TTFB', 'var(--ttfb)', s.ttfb)\n + pctRow('Upstream', 'var(--upstream)', s.upstreamDuration)\n + pctRow('Total', 'var(--purple)', s.totalDuration)\n + '</tbody></table></div>';\n\n html += '</div>'; // end overview panel\n\n // ==================== Requests tab ====================\n html += '<div id=\"panel-requests\" class=\"tab-panel' + (activeTab === 'requests' ? ' active' : '') + '\">';\n\n html += '<div class=\"legend\">'\n + '<span><span class=\"legend-dot\" style=\"background:var(--queue)\"></span>Queue</span>'\n + '<span><span class=\"legend-dot\" style=\"background:var(--yellow)\"></span>Proxy</span>'\n + '<span><span class=\"legend-dot\" style=\"background:var(--ttfb)\"></span>TTFB</span>'\n + '<span><span class=\"legend-dot\" style=\"background:var(--upstream)\"></span>Response</span>'\n + '</div>'\n + '<table><thead><tr><th>Time</th><th>Model</th><th>Mode</th><th>Session</th><th>Status</th>'\n + '<th>Queue</th><th>Proxy</th><th>TTFB</th><th>Total</th><th>Waterfall</th></tr></thead><tbody>';\n\n const maxTotal = Math.max(...reqs.map(r => r.totalDurationMs), 1);\n\n for (const r of reqs) {\n const statusClass = r.error ? 'status-err' : 'status-ok';\n const statusText = r.error ? r.error : r.status;\n const scale = 280 / maxTotal;\n const qW = Math.max(r.queueWaitMs * scale, 2);\n const ohW = Math.max((r.proxyOverheadMs || 0) * scale, 0);\n const ttfbW = Math.max((r.ttfbMs || 0) * scale, 0);\n const respW = Math.max((r.upstreamDurationMs - (r.ttfbMs || 0)) * scale, 2);\n\n const lineageBadge = r.lineageType ? '<span style=\"font-size:10px;padding:1px 5px;border-radius:3px;background:' + ({continuation:'var(--green)',compaction:'var(--yellow)',undo:'var(--purple)',diverged:'var(--red)',new:'var(--muted)'}[r.lineageType] || 'var(--muted)') + ';color:var(--bg)\">' + r.lineageType + '</span>' : '';\n const sessionShort = r.sdkSessionId ? r.sdkSessionId.slice(0, 8) : '\u2014';\n const msgCount = r.messageCount != null ? r.messageCount : '?';\n\n html += '<tr>'\n + '<td class=\"mono\">' + ago(r.timestamp) + '</td>'\n + '<td>' + (r.requestModel || r.model) + '<br><span style=\"font-size:10px;color:var(--muted)\">' + r.model + '</span></td>'\n + '<td>' + r.mode + '</td>'\n + '<td class=\"mono\">' + sessionShort + ' ' + lineageBadge + '<br><span style=\"font-size:10px;color:var(--muted)\">' + msgCount + ' msgs</span></td>'\n + '<td class=\"' + statusClass + '\">' + statusText + '</td>'\n + '<td class=\"mono\">' + ms(r.queueWaitMs) + '</td>'\n + '<td class=\"mono\">' + ms(r.proxyOverheadMs) + '</td>'\n + '<td class=\"mono\">' + ms(r.ttfbMs) + '</td>'\n + '<td class=\"mono\">' + ms(r.totalDurationMs) + '</td>'\n + '<td><div class=\"waterfall\">'\n + '<div class=\"waterfall-seg queue\" style=\"width:' + qW + 'px\"></div>'\n + '<div class=\"waterfall-seg overhead\" style=\"width:' + ohW + 'px\"></div>'\n + '<div class=\"waterfall-seg ttfb\" style=\"width:' + ttfbW + 'px\"></div>'\n + '<div class=\"waterfall-seg response\" style=\"width:' + respW + 'px\"></div>'\n + '</div></td>'\n + '</tr>';\n }\n html += '</tbody></table>';\n html += '</div>'; // end requests panel\n\n // ==================== Logs tab ====================\n html += '<div id=\"panel-logs\" class=\"tab-panel' + (activeTab === 'logs' ? ' active' : '') + '\">';\n\n // Filter buttons\n html += '<div class=\"log-filters\">'\n + '<span class=\"log-filter' + (activeLogFilter === 'all' ? ' active' : '') + '\" data-filter=\"all\" onclick=\"setLogFilter('all')\">All<span class=\"tab-badge\">' + logs.length + '</span></span>'\n + '<span class=\"log-filter' + (activeLogFilter === 'session' ? ' active' : '') + '\" data-filter=\"session\" onclick=\"setLogFilter('session')\" style=\"--accent:var(--blue)\">Session<span class=\"tab-badge\">' + logCounts.session + '</span></span>'\n + '<span class=\"log-filter' + (activeLogFilter === 'lineage' ? ' active' : '') + '\" data-filter=\"lineage\" onclick=\"setLogFilter('lineage')\" style=\"--accent:var(--purple)\">Lineage<span class=\"tab-badge\">' + logCounts.lineage + '</span></span>'\n + '<span class=\"log-filter' + (activeLogFilter === 'error' ? ' active' : '') + '\" data-filter=\"error\" onclick=\"setLogFilter('error')\" style=\"--accent:var(--red)\">Error<span class=\"tab-badge\">' + logCounts.error + '</span></span>'\n + '</div>';\n\n if (logs.length === 0) {\n html += '<div class=\"empty\">No diagnostic logs in this time window.</div>';\n } else {\n html += '<table><thead><tr>'\n + '<th style=\"width:80px\">Time</th><th style=\"width:55px\">Level</th><th style=\"width:70px\">Category</th><th>Message</th>'\n + '</tr></thead><tbody>';\n\n for (const log of logs) {\n const levelColor = {info:'var(--green)',warn:'var(--yellow)',error:'var(--red)'}[log.level] || 'var(--muted)';\n const catColor = {session:'var(--blue)',lineage:'var(--purple)',error:'var(--red)',lifecycle:'var(--muted)'}[log.category] || 'var(--muted)';\n const display = (activeLogFilter === 'all' || log.category === activeLogFilter) ? '' : 'display:none';\n html += '<tr class=\"log-row\" data-category=\"' + log.category + '\" style=\"' + display + '\">'\n + '<td class=\"mono\">' + ago(log.timestamp) + '</td>'\n + '<td><span style=\"color:' + levelColor + '\">' + log.level + '</span></td>'\n + '<td><span style=\"color:' + catColor + '\">' + log.category + '</span></td>'\n + '<td class=\"mono\" style=\"word-break:break-all\">' + log.message + '</td>'\n + '</tr>';\n }\n html += '</tbody></table>';\n }\n html += '</div>'; // end logs panel\n\n $('#content').innerHTML = html;\n}\n\nfunction card(label, value, detail) {\n return '<div class=\"card\"><div class=\"card-label\">' + label + '</div>'\n + '<div class=\"card-value\">' + value + '</div>'\n + (detail ? '<div class=\"card-detail\">' + detail + '</div>' : '')\n + '</div>';\n}\n\n$('#autoRefresh').addEventListener('change', function() {\n clearInterval(timer);\n if (this.checked) timer = setInterval(refresh, 5000);\n});\n$('#window').addEventListener('change', refresh);\n\nrefresh();\ntimer = setInterval(refresh, 5000);\n</script>\n</body>\n</html>";
|
|
6
6
|
//# sourceMappingURL=dashboard.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dashboard.d.ts","sourceRoot":"","sources":["../../src/telemetry/dashboard.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,eAAO,MAAM,aAAa,
|
|
1
|
+
{"version":3,"file":"dashboard.d.ts","sourceRoot":"","sources":["../../src/telemetry/dashboard.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,eAAO,MAAM,aAAa,4niBAqUlB,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/telemetry/routes.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/telemetry/routes.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAS3B,wBAAgB,qBAAqB,+EAwDpC"}
|
package/package.json
CHANGED
|
@@ -1,77 +1,78 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
"
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
"
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
"
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
2
|
+
"name": "@rynfar/meridian",
|
|
3
|
+
"version": "1.25.1",
|
|
4
|
+
"description": "Local Anthropic API powered by your Claude Max subscription. One subscription, every agent.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/server.js",
|
|
7
|
+
"types": "./dist/proxy/server.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"meridian": "./dist/cli.js",
|
|
10
|
+
"claude-max-proxy": "./dist/cli.js"
|
|
11
|
+
},
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/proxy/server.d.ts",
|
|
15
|
+
"default": "./dist/server.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=22"
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"start": "./bin/claude-proxy-supervisor.sh",
|
|
23
|
+
"proxy": "./bin/claude-proxy-supervisor.sh",
|
|
24
|
+
"build": "rm -rf dist && bun build bin/cli.ts src/proxy/server.ts --outdir dist --target node --splitting --external @anthropic-ai/claude-agent-sdk --entry-naming '[name].js' && tsc -p tsconfig.build.json",
|
|
25
|
+
"postbuild": "node --check dist/cli.js && node --check dist/server.js && test -f dist/proxy/server.d.ts",
|
|
26
|
+
"prepublishOnly": "bun run build",
|
|
27
|
+
"test": "bun test --path-ignore-patterns '**/*session-store*' --path-ignore-patterns '**/*proxy-async-ops*' --path-ignore-patterns '**/*proxy-extra-usage-fallback*' --path-ignore-patterns '**/*models-auth-status*' && bun test src/__tests__/proxy-extra-usage-fallback.test.ts && bun test src/__tests__/proxy-async-ops.test.ts && bun test src/__tests__/proxy-session-store.test.ts && bun test src/__tests__/session-store-pruning.test.ts && bun test src/__tests__/proxy-session-store-locking.test.ts && bun test src/__tests__/models-auth-status.test.ts",
|
|
28
|
+
"typecheck": "tsc --noEmit",
|
|
29
|
+
"proxy:direct": "bun run ./bin/cli.ts"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@anthropic-ai/claude-agent-sdk": "^0.2.80"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@hono/node-server": "^1.19.11",
|
|
36
|
+
"@types/bun": "^1.3.11",
|
|
37
|
+
"@types/node": "^22.0.0",
|
|
38
|
+
"glob": "^13.0.0",
|
|
39
|
+
"hono": "^4.11.4",
|
|
40
|
+
"typescript": "^5.8.2"
|
|
41
|
+
},
|
|
42
|
+
"files": [
|
|
43
|
+
"dist/",
|
|
44
|
+
"plugin/",
|
|
45
|
+
"assets/",
|
|
46
|
+
"README.md"
|
|
47
|
+
],
|
|
48
|
+
"keywords": [
|
|
49
|
+
"meridian",
|
|
50
|
+
"claude",
|
|
51
|
+
"claude-max",
|
|
52
|
+
"claude-code",
|
|
53
|
+
"anthropic",
|
|
54
|
+
"anthropic-api",
|
|
55
|
+
"claude-agent-sdk",
|
|
56
|
+
"ai-coding",
|
|
57
|
+
"ai-assistant",
|
|
58
|
+
"proxy",
|
|
59
|
+
"opencode",
|
|
60
|
+
"cline",
|
|
61
|
+
"aider",
|
|
62
|
+
"llm",
|
|
63
|
+
"coding-assistant",
|
|
64
|
+
"opencode-claude-max-proxy"
|
|
65
|
+
],
|
|
66
|
+
"repository": {
|
|
67
|
+
"type": "git",
|
|
68
|
+
"url": "git+https://github.com/rynfar/meridian.git"
|
|
69
|
+
},
|
|
70
|
+
"homepage": "https://github.com/rynfar/meridian#readme",
|
|
71
|
+
"bugs": {
|
|
72
|
+
"url": "https://github.com/rynfar/meridian/issues"
|
|
73
|
+
},
|
|
74
|
+
"author": "rynfar",
|
|
75
|
+
"license": "MIT",
|
|
76
|
+
"private": false,
|
|
77
|
+
"packageManager": "bun@1.3.11"
|
|
77
78
|
}
|