@rynfar/meridian 1.59.0 → 1.60.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{cli-wqvrd0yy.js → cli-k1djafvr.js} +22 -12
- package/dist/cli.js +1 -1
- package/dist/proxy/messages.d.ts +1 -0
- package/dist/proxy/messages.d.ts.map +1 -1
- package/dist/proxy/query.d.ts +2 -0
- package/dist/proxy/query.d.ts.map +1 -1
- package/dist/proxy/sdkFeatures.d.ts +8 -0
- package/dist/proxy/sdkFeatures.d.ts.map +1 -1
- package/dist/proxy/server.d.ts.map +1 -1
- package/dist/server.js +1 -1
- package/dist/telemetry/settingsPage.d.ts +1 -1
- package/dist/telemetry/settingsPage.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -1949,7 +1949,15 @@ function describeToolCall(info) {
|
|
|
1949
1949
|
const head = info.target ? `your ${info.name} ${info.target}` : `your ${info.name}`;
|
|
1950
1950
|
return info.contentSummary ? `[${head} → "${info.contentSummary}"]` : `[${head}]`;
|
|
1951
1951
|
}
|
|
1952
|
-
|
|
1952
|
+
function extractSystemText(system) {
|
|
1953
|
+
if (typeof system === "string")
|
|
1954
|
+
return system;
|
|
1955
|
+
if (!Array.isArray(system))
|
|
1956
|
+
return "";
|
|
1957
|
+
return system.filter((b) => b?.type === "text" && typeof b.text === "string" && b.text).map((b) => b.text).filter((text) => !TRANSPORT_HEADER_BLOCK.test(text)).join(`
|
|
1958
|
+
`);
|
|
1959
|
+
}
|
|
1960
|
+
var HASH_IGNORED_BLOCK_TYPES, HASH_HANDLED_BLOCK_TYPES, HASH_SERIALIZED_BLOCK_TYPES, MULTIMODAL_TYPES, TOOL_TARGET_KEYS, CONTENT_SUMMARY_MAX = 120, TRANSPORT_HEADER_BLOCK;
|
|
1953
1961
|
var init_messages = __esm(() => {
|
|
1954
1962
|
HASH_IGNORED_BLOCK_TYPES = new Set(["thinking", "redacted_thinking"]);
|
|
1955
1963
|
HASH_HANDLED_BLOCK_TYPES = new Set(["text", "tool_use", "tool_result"]);
|
|
@@ -1968,6 +1976,7 @@ var init_messages = __esm(() => {
|
|
|
1968
1976
|
]);
|
|
1969
1977
|
MULTIMODAL_TYPES = new Set(["image", "document", "file"]);
|
|
1970
1978
|
TOOL_TARGET_KEYS = ["filePath", "file_path", "path", "command", "pattern", "query", "url"];
|
|
1979
|
+
TRANSPORT_HEADER_BLOCK = /^\s*x-anthropic-[a-z0-9-]*header\s*:/i;
|
|
1971
1980
|
});
|
|
1972
1981
|
|
|
1973
1982
|
// src/proxy/fileChanges.ts
|
|
@@ -3379,6 +3388,7 @@ var init_sdkFeatures = __esm(() => {
|
|
|
3379
3388
|
thinkingPassthrough: false,
|
|
3380
3389
|
sharedMemory: false,
|
|
3381
3390
|
webFetchPreflight: true,
|
|
3391
|
+
claudeAiConnectors: false,
|
|
3382
3392
|
maxBudgetUsd: 0,
|
|
3383
3393
|
fallbackModel: "",
|
|
3384
3394
|
sdkDebug: false,
|
|
@@ -3587,6 +3597,7 @@ const FEATURES = [
|
|
|
3587
3597
|
{ key: 'thinkingPassthrough', label: 'Thinking Passthrough', desc: 'Forward thinking blocks to the client', type: 'toggle' },
|
|
3588
3598
|
{ key: 'sharedMemory', label: 'Shared Memory', desc: 'Share memory with Claude Code (~/.claude) instead of isolated storage', type: 'toggle' },
|
|
3589
3599
|
{ key: 'webFetchPreflight', label: 'WebFetch Preflight', desc: 'Check each WebFetch hostname against the Anthropic blocklist before fetching — off keeps hostnames local but fetches any URL unchecked. Only affects Cherry Studio; other adapters never run the SDK\\'s built-in WebFetch', type: 'toggle' },
|
|
3600
|
+
{ key: 'claudeAiConnectors', label: 'claude.ai Connectors', desc: 'Load the MCP connectors attached to your claude.ai account (Drive, Gmail, Calendar). Off by default; always off in passthrough mode', type: 'toggle' },
|
|
3590
3601
|
{ key: 'maxBudgetUsd', label: 'Max Budget (USD)', desc: 'Per-request cost cap — query aborts if exceeded (0 = disabled)', type: 'number' },
|
|
3591
3602
|
{ key: 'fallbackModel', label: 'Fallback Model', desc: 'Auto-fallback model if primary fails', type: 'select', options: ['', 'sonnet', 'opus', 'haiku', 'sonnet[1m]', 'opus[1m]'] },
|
|
3592
3603
|
{ key: 'sdkDebug', label: 'SDK Debug Logging', desc: 'Enable verbose SDK debug output to proxy stderr', type: 'toggle' },
|
|
@@ -3645,7 +3656,8 @@ function showSaved() {
|
|
|
3645
3656
|
function hasAnyEnabled(features) {
|
|
3646
3657
|
return features.codeSystemPrompt || !features.clientSystemPrompt || features.claudeMd !== 'off' || features.memory || features.dreaming ||
|
|
3647
3658
|
features.thinking !== 'disabled' || features.thinkingPassthrough ||
|
|
3648
|
-
features.sharedMemory || features.webFetchPreflight === false ||
|
|
3659
|
+
features.sharedMemory || features.webFetchPreflight === false ||
|
|
3660
|
+
features.claudeAiConnectors || features.maxBudgetUsd > 0 ||
|
|
3649
3661
|
features.fallbackModel || features.sdkDebug ||
|
|
3650
3662
|
features.additionalDirectories;
|
|
3651
3663
|
}
|
|
@@ -18909,7 +18921,7 @@ function buildQueryOptions(ctx, abortController) {
|
|
|
18909
18921
|
...QUIET_SUBPROCESS_ENV,
|
|
18910
18922
|
...sharedMemory ? stripConfigDir(cleanEnv) : cleanEnv,
|
|
18911
18923
|
ENABLE_TOOL_SEARCH: hasDeferredTools ? "true" : "false",
|
|
18912
|
-
|
|
18924
|
+
ENABLE_CLAUDEAI_MCP_SERVERS: !passthrough && ctx.claudeAiConnectors === true ? "true" : "false",
|
|
18913
18925
|
...passthrough && process.env.MERIDIAN_SUPPRESS_SCRATCHPAD !== "0" ? { CLAUDE_CODE_SESSION_KIND: "bg" } : {},
|
|
18914
18926
|
...process.getuid?.() === 0 ? { IS_SANDBOX: "1" } : {},
|
|
18915
18927
|
...ctx.envOverrides
|
|
@@ -20472,15 +20484,7 @@ data: ${JSON.stringify(lastError)}
|
|
|
20472
20484
|
const sdkModelDefaults = resolveSdkModelDefaults();
|
|
20473
20485
|
const profileEnv = { ...sdkModelDefaults, ...cleanEnv, ...profile.env };
|
|
20474
20486
|
const profileCredentialStore = credentialStoreForProfile(profile);
|
|
20475
|
-
let systemContext =
|
|
20476
|
-
if (body.system) {
|
|
20477
|
-
if (typeof body.system === "string") {
|
|
20478
|
-
systemContext = body.system;
|
|
20479
|
-
} else if (Array.isArray(body.system)) {
|
|
20480
|
-
systemContext = body.system.filter((b) => b.type === "text" && b.text).map((b) => b.text).join(`
|
|
20481
|
-
`);
|
|
20482
|
-
}
|
|
20483
|
-
}
|
|
20487
|
+
let systemContext = extractSystemText(body.system);
|
|
20484
20488
|
const adapterBase = adapter.baseName ?? adapter.name;
|
|
20485
20489
|
const adapterTransforms = getAdapterTransforms(adapterBase);
|
|
20486
20490
|
const pipeline = buildPipeline(adapterTransforms, pluginTransforms);
|
|
@@ -20894,6 +20898,7 @@ data: ${JSON.stringify(lastError)}
|
|
|
20894
20898
|
dreaming: sdkFeatures.dreaming,
|
|
20895
20899
|
sharedMemory: sdkFeatures.sharedMemory,
|
|
20896
20900
|
webFetchPreflight: sdkFeatures.webFetchPreflight,
|
|
20901
|
+
claudeAiConnectors: sdkFeatures.claudeAiConnectors,
|
|
20897
20902
|
maxBudgetUsd: sdkFeatures.maxBudgetUsd,
|
|
20898
20903
|
fallbackModel: sdkFeatures.fallbackModel,
|
|
20899
20904
|
sdkDebug: sdkFeatures.sdkDebug,
|
|
@@ -20976,6 +20981,7 @@ data: ${JSON.stringify(lastError)}
|
|
|
20976
20981
|
dreaming: sdkFeatures.dreaming,
|
|
20977
20982
|
sharedMemory: sdkFeatures.sharedMemory,
|
|
20978
20983
|
webFetchPreflight: sdkFeatures.webFetchPreflight,
|
|
20984
|
+
claudeAiConnectors: sdkFeatures.claudeAiConnectors,
|
|
20979
20985
|
maxBudgetUsd: sdkFeatures.maxBudgetUsd,
|
|
20980
20986
|
fallbackModel: sdkFeatures.fallbackModel,
|
|
20981
20987
|
sdkDebug: sdkFeatures.sdkDebug,
|
|
@@ -21044,6 +21050,7 @@ data: ${JSON.stringify(lastError)}
|
|
|
21044
21050
|
dreaming: sdkFeatures.dreaming,
|
|
21045
21051
|
sharedMemory: sdkFeatures.sharedMemory,
|
|
21046
21052
|
webFetchPreflight: sdkFeatures.webFetchPreflight,
|
|
21053
|
+
claudeAiConnectors: sdkFeatures.claudeAiConnectors,
|
|
21047
21054
|
maxBudgetUsd: sdkFeatures.maxBudgetUsd,
|
|
21048
21055
|
fallbackModel: sdkFeatures.fallbackModel,
|
|
21049
21056
|
sdkDebug: sdkFeatures.sdkDebug,
|
|
@@ -21509,6 +21516,7 @@ data: ${JSON.stringify({ type: "content_block_stop", index: idx })}
|
|
|
21509
21516
|
dreaming: sdkFeatures.dreaming,
|
|
21510
21517
|
sharedMemory: sdkFeatures.sharedMemory,
|
|
21511
21518
|
webFetchPreflight: sdkFeatures.webFetchPreflight,
|
|
21519
|
+
claudeAiConnectors: sdkFeatures.claudeAiConnectors,
|
|
21512
21520
|
maxBudgetUsd: sdkFeatures.maxBudgetUsd,
|
|
21513
21521
|
fallbackModel: sdkFeatures.fallbackModel,
|
|
21514
21522
|
sdkDebug: sdkFeatures.sdkDebug,
|
|
@@ -21590,6 +21598,7 @@ data: ${JSON.stringify({ type: "content_block_stop", index: idx })}
|
|
|
21590
21598
|
dreaming: sdkFeatures.dreaming,
|
|
21591
21599
|
sharedMemory: sdkFeatures.sharedMemory,
|
|
21592
21600
|
webFetchPreflight: sdkFeatures.webFetchPreflight,
|
|
21601
|
+
claudeAiConnectors: sdkFeatures.claudeAiConnectors,
|
|
21593
21602
|
maxBudgetUsd: sdkFeatures.maxBudgetUsd,
|
|
21594
21603
|
fallbackModel: sdkFeatures.fallbackModel,
|
|
21595
21604
|
sdkDebug: sdkFeatures.sdkDebug,
|
|
@@ -21658,6 +21667,7 @@ data: ${JSON.stringify({ type: "content_block_stop", index: idx })}
|
|
|
21658
21667
|
dreaming: sdkFeatures.dreaming,
|
|
21659
21668
|
sharedMemory: sdkFeatures.sharedMemory,
|
|
21660
21669
|
webFetchPreflight: sdkFeatures.webFetchPreflight,
|
|
21670
|
+
claudeAiConnectors: sdkFeatures.claudeAiConnectors,
|
|
21661
21671
|
maxBudgetUsd: sdkFeatures.maxBudgetUsd,
|
|
21662
21672
|
fallbackModel: sdkFeatures.fallbackModel,
|
|
21663
21673
|
sdkDebug: sdkFeatures.sdkDebug,
|
package/dist/cli.js
CHANGED
package/dist/proxy/messages.d.ts
CHANGED
|
@@ -183,4 +183,5 @@ export declare function buildToolUseIndex(messages: Array<{
|
|
|
183
183
|
* imitate.
|
|
184
184
|
*/
|
|
185
185
|
export declare function describeToolCall(info: ToolCallInfo): string;
|
|
186
|
+
export declare function extractSystemText(system: unknown): string;
|
|
186
187
|
//# sourceMappingURL=messages.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../src/proxy/messages.ts"],"names":[],"mappings":"AAAA;;GAEG;AAcH;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,wBAAwB,aAA6C,CAAA;AAElF;;;GAGG;AACH,eAAO,MAAM,wBAAwB,aAA+C,CAAA;AAEpF;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,2BAA2B,aAYtC,CAAA;AAEF;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,GAAG,GAAG,MAAM,CAmBrD;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAUtE;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,OAAO,EAAE,CAM7D;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,GAAG,CAAA;CAAE,CAAC,GAAG,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,GAAG,CAAA;CAAE,CAAC,CAKzH;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG,MAAM,CAerF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,4BAA4B,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,CAU3D;AAED,yEAAyE;AACzE,eAAO,MAAM,gBAAgB,aAAyC,CAAA;AAEtE;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,iCAAiC,CAC/C,CAAC,SAAS;IAAE,OAAO,EAAE;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAA;CAAE,EAC3C,UAAU,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,CAyCtB;AAED,kEAAkE;AAClE,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,8EAA8E;IAC9E,MAAM,EAAE,MAAM,GAAG,SAAS,CAAA;IAC1B;;;;;;;OAOG;IACH,cAAc,EAAE,MAAM,GAAG,SAAS,CAAA;CACnC;AAyCD;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,GAAG,CAAA;CAAE,CAAC,GAC9C,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAyB3B;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM,CAO3D"}
|
|
1
|
+
{"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../src/proxy/messages.ts"],"names":[],"mappings":"AAAA;;GAEG;AAcH;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,wBAAwB,aAA6C,CAAA;AAElF;;;GAGG;AACH,eAAO,MAAM,wBAAwB,aAA+C,CAAA;AAEpF;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,2BAA2B,aAYtC,CAAA;AAEF;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,GAAG,GAAG,MAAM,CAmBrD;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAUtE;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,OAAO,EAAE,CAM7D;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,GAAG,CAAA;CAAE,CAAC,GAAG,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,GAAG,CAAA;CAAE,CAAC,CAKzH;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG,MAAM,CAerF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,4BAA4B,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,CAU3D;AAED,yEAAyE;AACzE,eAAO,MAAM,gBAAgB,aAAyC,CAAA;AAEtE;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,iCAAiC,CAC/C,CAAC,SAAS;IAAE,OAAO,EAAE;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAA;CAAE,EAC3C,UAAU,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,CAyCtB;AAED,kEAAkE;AAClE,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,8EAA8E;IAC9E,MAAM,EAAE,MAAM,GAAG,SAAS,CAAA;IAC1B;;;;;;;OAOG;IACH,cAAc,EAAE,MAAM,GAAG,SAAS,CAAA;CACnC;AAyCD;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,GAAG,CAAA;CAAE,CAAC,GAC9C,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAyB3B;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM,CAO3D;AA6BD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,CAQzD"}
|
package/dist/proxy/query.d.ts
CHANGED
|
@@ -94,6 +94,8 @@ export interface QueryContext {
|
|
|
94
94
|
sharedMemory?: boolean;
|
|
95
95
|
/** Run the WebFetch domain safety check (hostname sent to api.anthropic.com) */
|
|
96
96
|
webFetchPreflight?: boolean;
|
|
97
|
+
/** Load the account's claude.ai MCP connectors (ignored in passthrough) */
|
|
98
|
+
claudeAiConnectors?: boolean;
|
|
97
99
|
/** Per-request cost cap in USD */
|
|
98
100
|
maxBudgetUsd?: number;
|
|
99
101
|
/** Fallback model when primary fails */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../src/proxy/query.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../src/proxy/query.ts"],"names":[],"mappings":"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"}
|
|
@@ -28,6 +28,14 @@ export interface AdapterFeatures {
|
|
|
28
28
|
* URL unchecked, so pair it with tool permissions if that matters.
|
|
29
29
|
*/
|
|
30
30
|
webFetchPreflight: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Load the MCP connectors attached to the account's claude.ai profile
|
|
33
|
+
* (Drive, Gmail, Calendar, …). Off by default: the calling agent never
|
|
34
|
+
* negotiated those tools, and they reach third parties through
|
|
35
|
+
* mcp-proxy.anthropic.com. Ignored in passthrough mode, where the client
|
|
36
|
+
* executes tools and cannot run one that exists only in the subprocess.
|
|
37
|
+
*/
|
|
38
|
+
claudeAiConnectors: boolean;
|
|
31
39
|
/** Per-request cost cap in USD (0 = disabled) */
|
|
32
40
|
maxBudgetUsd: number;
|
|
33
41
|
/** Fallback model when primary fails (empty = disabled) */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sdkFeatures.d.ts","sourceRoot":"","sources":["../../src/proxy/sdkFeatures.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,MAAM,WAAW,eAAe;IAC9B,iFAAiF;IACjF,gBAAgB,EAAE,OAAO,CAAA;IACzB,kFAAkF;IAClF,kBAAkB,EAAE,OAAO,CAAA;IAC3B,4DAA4D;IAC5D,QAAQ,EAAE,KAAK,GAAG,SAAS,GAAG,MAAM,CAAA;IACpC,wDAAwD;IACxD,MAAM,EAAE,OAAO,CAAA;IACf,6CAA6C;IAC7C,QAAQ,EAAE,OAAO,CAAA;IACjB,0DAA0D;IAC1D,QAAQ,EAAE,UAAU,GAAG,SAAS,GAAG,UAAU,CAAA;IAC7C,4CAA4C;IAC5C,mBAAmB,EAAE,OAAO,CAAA;IAC5B,iFAAiF;IACjF,YAAY,EAAE,OAAO,CAAA;IACrB;;;;;OAKG;IACH,iBAAiB,EAAE,OAAO,CAAA;IAC1B,iDAAiD;IACjD,YAAY,EAAE,MAAM,CAAA;IACpB,2DAA2D;IAC3D,aAAa,EAAE,MAAM,CAAA;IACrB,+CAA+C;IAC/C,QAAQ,EAAE,OAAO,CAAA;IACjB,uEAAuE;IACvE,qBAAqB,EAAE,MAAM,CAAA;CAC9B;AAED,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"sdkFeatures.d.ts","sourceRoot":"","sources":["../../src/proxy/sdkFeatures.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,MAAM,WAAW,eAAe;IAC9B,iFAAiF;IACjF,gBAAgB,EAAE,OAAO,CAAA;IACzB,kFAAkF;IAClF,kBAAkB,EAAE,OAAO,CAAA;IAC3B,4DAA4D;IAC5D,QAAQ,EAAE,KAAK,GAAG,SAAS,GAAG,MAAM,CAAA;IACpC,wDAAwD;IACxD,MAAM,EAAE,OAAO,CAAA;IACf,6CAA6C;IAC7C,QAAQ,EAAE,OAAO,CAAA;IACjB,0DAA0D;IAC1D,QAAQ,EAAE,UAAU,GAAG,SAAS,GAAG,UAAU,CAAA;IAC7C,4CAA4C;IAC5C,mBAAmB,EAAE,OAAO,CAAA;IAC5B,iFAAiF;IACjF,YAAY,EAAE,OAAO,CAAA;IACrB;;;;;OAKG;IACH,iBAAiB,EAAE,OAAO,CAAA;IAC1B;;;;;;OAMG;IACH,kBAAkB,EAAE,OAAO,CAAA;IAC3B,iDAAiD;IACjD,YAAY,EAAE,MAAM,CAAA;IACpB,2DAA2D;IAC3D,aAAa,EAAE,MAAM,CAAA;IACrB,+CAA+C;IAC/C,QAAQ,EAAE,OAAO,CAAA;IACjB,uEAAuE;IACvE,qBAAqB,EAAE,MAAM,CAAA;CAC9B;AAED,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC,CAAA;AAqGpE;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,MAAM,GAAG,eAAe,CAU1E;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,eAAe,CAAC,UAAU,CAAC,GAAG,SAAS,CAKhG;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAOtE;AAKD;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,eAAe,CAAC,CAgC5E;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC,eAAe,CAAC,GAAG,IAAI,CAInG;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAI9D"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/proxy/server.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AACtE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,CAAA;AAGvD,YAAY,EACV,SAAS,EACT,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,cAAc,EACd,cAAc,EACd,iBAAiB,EACjB,YAAY,EACZ,aAAa,EACb,WAAW,GACZ,MAAM,aAAa,CAAA;AAKpB,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAkDnG,OAAO,EACL,kBAAkB,EAClB,WAAW,EACX,oBAAoB,EAEpB,KAAK,aAAa,EAGnB,MAAM,mBAAmB,CAAA;AAI1B,OAAO,EAA+B,iBAAiB,EAAE,mBAAmB,EAAsC,MAAM,iBAAiB,CAAA;AAGzI,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,oBAAoB,EAAE,CAAA;AAChE,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,CAAA;AACjD,YAAY,EAAE,aAAa,EAAE,CAAA;AAqR7B,wBAAgB,iBAAiB,CAAC,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM,GAAG,WAAW,
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/proxy/server.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AACtE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,CAAA;AAGvD,YAAY,EACV,SAAS,EACT,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,cAAc,EACd,cAAc,EACd,iBAAiB,EACjB,YAAY,EACZ,aAAa,EACb,WAAW,GACZ,MAAM,aAAa,CAAA;AAKpB,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAkDnG,OAAO,EACL,kBAAkB,EAClB,WAAW,EACX,oBAAoB,EAEpB,KAAK,aAAa,EAGnB,MAAM,mBAAmB,CAAA;AAI1B,OAAO,EAA+B,iBAAiB,EAAE,mBAAmB,EAAsC,MAAM,iBAAiB,CAAA;AAGzI,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,oBAAoB,EAAE,CAAA;AAChE,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,CAAA;AACjD,YAAY,EAAE,aAAa,EAAE,CAAA;AAqR7B,wBAAgB,iBAAiB,CAAC,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM,GAAG,WAAW,CAilIhF;AAWD,wBAAgB,gCAAgC,IAAI,IAAI,CAavD;AAED,wBAAsB,gBAAgB,CAAC,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAmGhG"}
|
package/dist/server.js
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
* SDK Features settings page — per-adapter toggle UI.
|
|
3
3
|
* Same dark theme as the telemetry dashboard. No framework, no CDN.
|
|
4
4
|
*/
|
|
5
|
-
export declare const settingsPageHtml = "<!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 SDK Features</title>\n<link rel=\"icon\" type=\"image/svg+xml\" href=\"/telemetry/icon.svg\">\n<style>\n \n :root {\n /* Cool-gray neutral palette. High contrast, surface/border separation,\n no color cast muddying the text. Blue is the primary accent; violet\n is the fixed secondary \u2014 used together in the brand gradient and\n individually for hover states and a handful of telemetry badges. */\n --bg: #0d1117;\n --surface: #161b22;\n --surface2: #1c2128;\n --border: #30363d;\n /* Text */\n --text: #e6edf3;\n --muted: #8b949e;\n /* Brand \u2014 blue primary, violet secondary */\n --accent: #58a6ff;\n --accent2: #bc8cff;\n --violet: #bc8cff;\n --lavender: #d2a8ff;\n /* Semantic */\n --green: #3fb950;\n --yellow: #d29922;\n --red: #f85149;\n /* Telemetry-specific aliases (waterfall + lineage badges) */\n --blue: #58a6ff;\n --purple: #bc8cff;\n --queue: #d29922;\n --ttfb: #58a6ff;\n --upstream: #3fb950;\n }\n /* Banner backsplash \u2014 the brand look: a gentle diagonal wash with soft\n blue (top-left) and violet (bottom-right) glows. Pages must not set\n their own body background so this shows through everywhere. */\n body {\n background:\n radial-gradient(1200px 800px at 12% -8%, rgba(88,166,255,0.07), transparent 60%),\n radial-gradient(1100px 800px at 92% 108%, rgba(188,140,255,0.06), transparent 60%),\n linear-gradient(135deg, #0d1117 0%, #10151d 55%, #161b22 100%);\n background-attachment: fixed;\n background-color: var(--bg);\n }\n\n * { box-sizing: border-box; margin: 0; padding: 0; }\n body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;\n color: var(--text); padding: 0; line-height: 1.5; }\n \n .meridian-header {\n position: sticky; top: 0; z-index: 100;\n display: flex; align-items: center; gap: 20px;\n padding: 10px 24px;\n background: rgba(13, 17, 23, 0.92);\n backdrop-filter: blur(12px);\n border-bottom: 1px solid var(--border, #30363d);\n }\n .meridian-header .mh-brand {\n display: flex; align-items: center; gap: 10px;\n text-decoration: none; color: var(--text, #e6edf3);\n }\n .meridian-header .mh-logo { display: block; }\n .meridian-header .mh-name {\n font-size: 15px; font-weight: 700; letter-spacing: 2px;\n text-transform: uppercase;\n }\n .meridian-header .mh-nav { display: flex; align-items: center; gap: 2px; }\n .meridian-header .mh-nav a {\n color: var(--muted, #8b949e); text-decoration: none; font-size: 12px;\n font-weight: 500; padding: 5px 10px; border-radius: 6px;\n transition: color 0.15s, background 0.15s;\n }\n .meridian-header .mh-nav a:hover { color: var(--text, #e6edf3); background: var(--surface, #161b22); }\n .meridian-header .mh-nav a.active { color: var(--accent, #58a6ff); background: var(--surface, #161b22); }\n .meridian-header .mh-right {\n margin-left: auto; display: flex; align-items: center; gap: 10px;\n }\n .meridian-header .mh-profile {\n display: none; align-items: center; gap: 6px;\n font-size: 11px; font-weight: 500; color: var(--text, #e6edf3);\n padding: 3px 10px; border-radius: 20px;\n background: var(--surface, #161b22); border: 1px solid var(--border, #30363d);\n text-decoration: none; transition: border-color 0.15s;\n }\n .meridian-header .mh-profile:hover { border-color: var(--accent, #58a6ff); }\n .meridian-header .mh-profile.visible { display: inline-flex; }\n .meridian-header .mh-profile .mh-profile-type {\n color: var(--muted, #8b949e); font-size: 10px;\n }\n .meridian-header .mh-status {\n display: inline-flex; align-items: center; gap: 6px;\n font-size: 11px; color: var(--muted, #8b949e); white-space: nowrap;\n }\n .meridian-header .mh-dot {\n width: 8px; height: 8px; border-radius: 50%;\n background: var(--muted, #8b949e); flex-shrink: 0;\n }\n .meridian-header .mh-dot.healthy { background: var(--green, #3fb950); box-shadow: 0 0 6px rgba(63,185,80,0.5); }\n .meridian-header .mh-dot.degraded { background: var(--yellow, #d29922); }\n .meridian-header .mh-dot.unhealthy { background: var(--red, #f85149); }\n @media (max-width: 720px) {\n .meridian-header { gap: 10px; padding: 10px 16px; flex-wrap: wrap; }\n .meridian-header .mh-name { display: none; }\n .meridian-header .mh-status .mh-status-text { display: none; }\n }\n\n .content { max-width: 900px; margin: 0 auto; padding: 24px; }\n h1 { font-size: 20px; font-weight: 600; margin-bottom: 4px; }\n .subtitle { color: var(--muted); font-size: 13px; margin-bottom: 24px; }\n .nav { display: flex; gap: 16px; margin-bottom: 24px; font-size: 13px; }\n .nav a { color: var(--muted); text-decoration: none; }\n .nav a:hover { color: var(--accent); }\n .nav a.active { color: var(--accent); }\n\n .adapter-card {\n background: var(--surface); border: 1px solid var(--border); border-radius: 8px;\n padding: 20px; margin-bottom: 16px;\n }\n .adapter-header {\n display: flex; align-items: center; justify-content: space-between;\n margin-bottom: 16px;\n }\n .adapter-name { font-size: 16px; font-weight: 600; }\n .adapter-badge {\n font-size: 10px; padding: 2px 8px; border-radius: 10px;\n text-transform: uppercase; letter-spacing: 0.5px;\n }\n .badge-active { background: rgba(63, 185, 80, 0.15); color: var(--green); }\n .badge-inactive { background: rgba(139, 148, 158, 0.15); color: var(--muted); }\n\n .feature-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }\n @media (max-width: 600px) { .feature-grid { grid-template-columns: 1fr; } }\n\n .feature-row {\n display: flex; align-items: center; justify-content: space-between;\n padding: 10px 14px; border-radius: 6px;\n background: var(--bg); border: 1px solid var(--border);\n }\n .feature-info { display: flex; flex-direction: column; }\n .feature-label { font-size: 13px; font-weight: 500; }\n .feature-desc { font-size: 11px; color: var(--muted); margin-top: 2px; }\n\n /* Toggle switch */\n .toggle { position: relative; width: 36px; height: 20px; flex-shrink: 0; }\n .toggle input { opacity: 0; width: 0; height: 0; }\n .toggle-track {\n position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0;\n background: var(--border); border-radius: 10px; transition: background 0.2s;\n }\n .toggle-track::after {\n content: \"\"; position: absolute; height: 14px; width: 14px;\n left: 3px; bottom: 3px; background: var(--muted); border-radius: 50%;\n transition: transform 0.2s, background 0.2s;\n }\n .toggle input:checked + .toggle-track { background: var(--accent); }\n .toggle input:checked + .toggle-track::after {\n transform: translateX(16px); background: var(--text);\n }\n\n /* Select dropdown */\n .feature-select {\n background: var(--surface); color: var(--text); border: 1px solid var(--border);\n border-radius: 6px; padding: 4px 8px; font-size: 12px; cursor: pointer;\n }\n\n .save-indicator {\n position: fixed; bottom: 24px; right: 24px;\n background: var(--green); color: #000; padding: 8px 16px;\n border-radius: 6px; font-size: 13px; font-weight: 500;\n opacity: 0; transition: opacity 0.3s; pointer-events: none;\n }\n .save-indicator.visible { opacity: 1; }\n\n .reset-btn {\n background: none; border: 1px solid var(--border); color: var(--muted);\n border-radius: 6px; padding: 4px 12px; font-size: 11px; cursor: pointer;\n }\n .reset-btn:hover { border-color: var(--red); color: var(--red); }\n\n /* Model pricing */\n .pricing-table { width: 100%; border-collapse: collapse; font-size: 12px; }\n .pricing-table th { text-align: left; padding: 8px 10px; color: var(--muted); font-weight: 500;\n font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; border-bottom: 1px solid var(--border); }\n .pricing-table td { padding: 6px 10px; border-bottom: 1px solid var(--border); }\n .pricing-table tr:last-child td { border-bottom: none; }\n .pricing-model { font-family: 'SF Mono', SFMono-Regular, Consolas, monospace; font-size: 12px; word-break: break-all; }\n .pricing-input { background: var(--bg); color: var(--text); border: 1px solid var(--border);\n border-radius: 6px; padding: 4px 8px; font-size: 12px; width: 84px; text-align: right;\n font-variant-numeric: tabular-nums; }\n .pricing-input:focus { border-color: var(--accent); outline: none; }\n .pricing-badge { font-size: 10px; padding: 2px 8px; border-radius: 10px;\n text-transform: uppercase; letter-spacing: 0.5px; white-space: nowrap; }\n .badge-override { background: rgba(210, 153, 34, 0.15); color: var(--yellow); }\n .badge-builtin { background: rgba(139, 148, 158, 0.15); color: var(--muted); }\n .pricing-add { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin-top: 14px;\n padding-top: 14px; border-top: 1px solid var(--border); }\n .pricing-add input[type=\"text\"] { width: 240px; text-align: left; }\n .add-btn { background: var(--accent); border: none; color: #fff; border-radius: 6px;\n padding: 5px 14px; font-size: 12px; font-weight: 500; cursor: pointer; }\n .pricing-note { font-size: 11px; color: var(--muted); margin-top: 12px; line-height: 1.6; }\n</style>\n</head>\n<body>\n\n<header class=\"meridian-header\" id=\"meridianHeader\">\n <a class=\"mh-brand\" href=\"/\">\n <svg class=\"mh-logo\" width=\"24\" height=\"24\" viewBox=\"0 0 64 64\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <defs>\n <linearGradient id=\"mhGrad\" x1=\"32\" y1=\"4\" x2=\"32\" y2=\"60\" gradientUnits=\"userSpaceOnUse\">\n <stop stop-color=\"#58a6ff\"/>\n <stop offset=\"1\" stop-color=\"#bc8cff\"/>\n </linearGradient>\n </defs>\n <circle cx=\"32\" cy=\"32\" r=\"25\" stroke=\"url(#mhGrad)\" stroke-width=\"3.5\"/>\n <ellipse cx=\"32\" cy=\"32\" rx=\"10.5\" ry=\"25\" stroke=\"url(#mhGrad)\" stroke-width=\"2.5\" opacity=\"0.8\"/>\n <path d=\"M7 32h50\" stroke=\"url(#mhGrad)\" stroke-width=\"2\" opacity=\"0.4\"/>\n <circle cx=\"32\" cy=\"7\" r=\"4.5\" fill=\"#58a6ff\"/>\n <circle cx=\"32\" cy=\"57\" r=\"4.5\" fill=\"#bc8cff\"/>\n</svg>\n <span class=\"mh-name\">Meridian</span>\n </a>\n <nav class=\"mh-nav\">\n <a href=\"/\" id=\"nav-home\">Home</a>\n <a href=\"/telemetry\" id=\"nav-telemetry\">Telemetry</a>\n <a href=\"/profiles\" id=\"nav-profiles\">Profiles</a>\n <a href=\"/settings\" id=\"nav-settings\">Settings</a>\n <a href=\"/plugins\" id=\"nav-plugins\">Plugins</a>\n </nav>\n <div class=\"mh-right\">\n <a class=\"mh-profile\" id=\"mhProfile\" href=\"/\" title=\"Active profile \u2014 switch from the home page\"></a>\n <span class=\"mh-status\" id=\"mhStatus\"><span class=\"mh-dot\" id=\"mhDot\"></span><span class=\"mh-status-text\" id=\"mhStatusText\"></span></span>\n </div>\n</header>\n\n<div class=\"content\">\n <h1>SDK Features <span style=\"font-size:11px;padding:2px 8px;border-radius:10px;background:rgba(210,153,34,0.15);color:var(--yellow);vertical-align:middle;margin-left:8px\">Experimental</span></h1>\n <p class=\"subtitle\" style=\"max-width:720px;line-height:1.6\">\n Unlock Claude Code features for any connected agent. Capabilities like auto-memory, dreaming, and CLAUDE.md \u2014 normally\n exclusive to Claude Code \u2014 become available to OpenCode, Crush, Droid, and any other harness routed through Meridian.\n Each agent keeps its own toolchain while gaining access to these additional features.<br><br>\n <strong style=\"color:var(--text)\">System prompts:</strong> For these features to work correctly, both the Claude Code prompt and your client prompt\n should be enabled. When both are active, they are appended together \u2014 Claude Code's base instructions come first,\n followed by your agent's specific instructions.\n </p>\n\n <div id=\"adapters\"></div>\n\n <h1 style=\"margin-top:40px\">Routing</h1>\n <p class=\"subtitle\" style=\"max-width:720px;line-height:1.6\">\n How unpinned requests choose an account. <strong style=\"color:var(--text)\">Active</strong> uses the manually\n selected profile. <strong style=\"color:var(--text)\">Sticky</strong> distributes sessions across profiles evenly\n (cache-affine). <strong style=\"color:var(--text)\">Priority</strong> drains the pool in order \u2014 highest first \u2014\n and fails over per request when an account runs out; conversations keep their account, and new sessions\n return to the preferred account after its window resets. An explicit <code>x-meridian-profile</code> header\n always overrides. Changes apply to the next request \u2014 no restart needed.\n </p>\n <div class=\"adapter-card\" id=\"routing-card\">\n <div id=\"routing-body\">Loading\u2026</div>\n </div>\n\n <h1 style=\"margin-top:40px\">Model Pricing</h1>\n <p class=\"subtitle\" style=\"max-width:720px;line-height:1.6\">\n Rates used by the telemetry cost estimate, in USD per million tokens. Edit a value to override the\n built-in rate, or add models the built-in table doesn't know about (they show as \"no pricing\" on the\n dashboard until defined here). Changes apply on the next dashboard refresh.\n </p>\n <div class=\"adapter-card\">\n <table class=\"pricing-table\">\n <thead><tr><th>Model</th><th>Input</th><th>Output</th><th>Cache Read</th><th>Cache Write</th><th>Source</th><th></th></tr></thead>\n <tbody id=\"pricingRows\"></tbody>\n </table>\n <div class=\"pricing-add\">\n <input type=\"text\" class=\"pricing-input\" id=\"newModelName\" placeholder=\"model id (e.g. claude-opus-9)\">\n <input type=\"number\" class=\"pricing-input\" id=\"newModelInput\" placeholder=\"input\" min=\"0\" step=\"0.01\">\n <input type=\"number\" class=\"pricing-input\" id=\"newModelOutput\" placeholder=\"output\" min=\"0\" step=\"0.01\">\n <input type=\"number\" class=\"pricing-input\" id=\"newModelCacheRead\" placeholder=\"cache read\" min=\"0\" step=\"0.01\">\n <input type=\"number\" class=\"pricing-input\" id=\"newModelCacheWrite\" placeholder=\"cache write\" min=\"0\" step=\"0.01\">\n <button class=\"add-btn\" onclick=\"addPricingModel()\">Add Model</button>\n </div>\n <div class=\"pricing-note\">\n Cache read and cache write are optional; when left blank they default to 0.1x and 1.25x of the\n input rate (the 5-minute cache TTL multipliers). Verify current list prices at\n <a href=\"https://claude.com/pricing\" target=\"_blank\" rel=\"noreferrer\" style=\"color:var(--accent)\">claude.com/pricing</a>.\n </div>\n </div>\n</div>\n\n<div class=\"save-indicator\" id=\"saveIndicator\">Saved</div>\n\n<script>\nconst FEATURES = [\n { key: 'codeSystemPrompt', label: 'Claude Code Prompt', desc: 'Include the built-in Claude Code system prompt (tool usage rules, safety guidelines, coding best practices)', type: 'toggle' },\n { key: 'clientSystemPrompt', label: 'Client Prompt', desc: 'Include the system prompt sent by the connecting agent (e.g. OpenCode or Crush instructions)', type: 'toggle' },\n { key: 'claudeMd', label: 'CLAUDE.md', desc: 'Load CLAUDE.md instruction files \u2014 Off: none, Project: ./CLAUDE.md only, Full: ~/.claude/CLAUDE.md + ./CLAUDE.md', type: 'select', options: ['off', 'project', 'full'] },\n { key: 'memory', label: 'Memory', desc: 'Read and write memories across sessions', type: 'toggle' },\n { key: 'dreaming', label: 'Auto-Dream', desc: 'Background memory consolidation', type: 'toggle' },\n { key: 'thinking', label: 'Thinking', desc: 'Extended thinking mode', type: 'select', options: ['disabled', 'adaptive', 'enabled'] },\n { key: 'thinkingPassthrough', label: 'Thinking Passthrough', desc: 'Forward thinking blocks to the client', type: 'toggle' },\n { key: 'sharedMemory', label: 'Shared Memory', desc: 'Share memory with Claude Code (~/.claude) instead of isolated storage', type: 'toggle' },\n { key: 'webFetchPreflight', label: 'WebFetch Preflight', desc: 'Check each WebFetch hostname against the Anthropic blocklist before fetching \u2014 off keeps hostnames local but fetches any URL unchecked. Only affects Cherry Studio; other adapters never run the SDK\\'s built-in WebFetch', type: 'toggle' },\n { key: 'maxBudgetUsd', label: 'Max Budget (USD)', desc: 'Per-request cost cap \u2014 query aborts if exceeded (0 = disabled)', type: 'number' },\n { key: 'fallbackModel', label: 'Fallback Model', desc: 'Auto-fallback model if primary fails', type: 'select', options: ['', 'sonnet', 'opus', 'haiku', 'sonnet[1m]', 'opus[1m]'] },\n { key: 'sdkDebug', label: 'SDK Debug Logging', desc: 'Enable verbose SDK debug output to proxy stderr', type: 'toggle' },\n { key: 'additionalDirectories', label: 'Additional Directories', desc: 'Comma-separated extra paths Claude can access (monorepo libs, etc.)', type: 'text' },\n];\n\n// Display names only \u2014 NOT the list of adapters to render. The page renders\n// whatever /settings/api/features returns, so a new adapter shows up here the\n// moment it exists, using its raw name until someone gives it a pretty one.\n// A hardcoded list is what hid codex and cherry from this page entirely.\nconst ADAPTER_LABELS = {\n opencode: 'OpenCode',\n openai: 'OpenAI (/v1/chat/completions)',\n crush: 'Crush',\n forgecode: 'ForgeCode',\n pi: 'Pi',\n droid: 'Droid',\n passthrough: 'LiteLLM / Passthrough',\n codex: 'Codex CLI (/v1/responses)',\n cherry: 'Cherry Studio',\n 'claude-code': 'Claude Code',\n};\n\nlet currentConfig = {};\n\nasync function loadConfig() {\n const res = await fetch('/settings/api/features');\n currentConfig = await res.json();\n render();\n}\n\nasync function saveFeature(adapter, key, value) {\n const patch = {};\n patch[key] = value;\n await fetch('/settings/api/features/' + adapter, {\n method: 'PATCH',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(patch),\n });\n currentConfig[adapter][key] = value;\n showSaved();\n}\n\nasync function resetAdapter(adapter) {\n await fetch('/settings/api/features/' + adapter, { method: 'DELETE' });\n await loadConfig();\n showSaved();\n}\n\nfunction showSaved() {\n const el = document.getElementById('saveIndicator');\n el.classList.add('visible');\n setTimeout(() => el.classList.remove('visible'), 1500);\n}\n\nfunction hasAnyEnabled(features) {\n return features.codeSystemPrompt || !features.clientSystemPrompt || features.claudeMd !== 'off' || features.memory || features.dreaming ||\n features.thinking !== 'disabled' || features.thinkingPassthrough ||\n features.sharedMemory || features.webFetchPreflight === false || features.maxBudgetUsd > 0 ||\n features.fallbackModel || features.sdkDebug ||\n features.additionalDirectories;\n}\n\nfunction render() {\n const container = document.getElementById('adapters');\n container.innerHTML = '';\n\n for (const adapter of Object.keys(currentConfig)) {\n const label = ADAPTER_LABELS[adapter] || adapter;\n const features = currentConfig[adapter] || {};\n const active = hasAnyEnabled(features);\n\n const card = document.createElement('div');\n card.className = 'adapter-card';\n card.innerHTML = '<div class=\"adapter-header\">' +\n '<span class=\"adapter-name\">' + label + '</span>' +\n '<div style=\"display:flex;gap:8px;align-items:center\">' +\n '<span class=\"adapter-badge ' + (active ? 'badge-active' : 'badge-inactive') + '\">' +\n (active ? 'Active' : 'Default') +\n '</span>' +\n '<button class=\"reset-btn\" onclick=\"resetAdapter(\\''+adapter+'\\')\">Reset</button>' +\n '</div>' +\n '</div>';\n\n const grid = document.createElement('div');\n grid.className = 'feature-grid';\n\n for (const feat of FEATURES) {\n const row = document.createElement('div');\n row.className = 'feature-row';\n\n const info = '<div class=\"feature-info\"><span class=\"feature-label\">' +\n feat.label + '</span><span class=\"feature-desc\">' + feat.desc + '</span></div>';\n\n if (feat.type === 'toggle') {\n const checked = features[feat.key] ? 'checked' : '';\n row.innerHTML = info +\n '<label class=\"toggle\"><input type=\"checkbox\" ' + checked +\n ' onchange=\"saveFeature(\\''+adapter+'\\', \\''+feat.key+'\\', this.checked)\">' +\n '<span class=\"toggle-track\"></span></label>';\n } else if (feat.type === 'select') {\n const options = feat.options.map(o => {\n const label = o === '' ? '(None)' : o.charAt(0).toUpperCase()+o.slice(1);\n return '<option value=\"'+o+'\"'+(features[feat.key]===o?' selected':'')+'>'+label+'</option>';\n }).join('');\n row.innerHTML = info +\n '<select class=\"feature-select\" onchange=\"saveFeature(\\''+adapter+'\\', \\''+feat.key+'\\', this.value)\">' +\n options + '</select>';\n } else if (feat.type === 'number') {\n const value = features[feat.key] ?? 0;\n row.innerHTML = info +\n '<input type=\"number\" class=\"feature-select\" style=\"width:80px;text-align:right\" min=\"0\" step=\"0.01\" value=\"'+value+'\"' +\n ' onchange=\"saveFeature(\\''+adapter+'\\', \\''+feat.key+'\\', parseFloat(this.value)||0)\">';\n } else if (feat.type === 'text') {\n const value = (features[feat.key] ?? '').toString().replace(/\"/g, '"');\n row.innerHTML = info +\n '<input type=\"text\" class=\"feature-select\" style=\"width:180px\" value=\"'+value+'\"' +\n ' onchange=\"saveFeature(\\''+adapter+'\\', \\''+feat.key+'\\', this.value)\">';\n }\n\n grid.appendChild(row);\n }\n\n card.appendChild(grid);\n container.appendChild(card);\n }\n}\n\n// ---- Model pricing (telemetry cost estimate) ----\nlet pricingData = { builtin: {}, overrides: {} };\n\nfunction fmtRate(v) { return String(Math.round(v * 10000) / 10000); }\n\nasync function loadPricing() {\n const res = await fetch('/settings/api/pricing');\n pricingData = await res.json();\n renderPricing();\n}\n\nasync function putPricing(model, rates) {\n const res = await fetch('/settings/api/pricing/' + encodeURIComponent(model), {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(rates),\n });\n if (!res.ok) {\n const err = await res.json().catch(function () { return {}; });\n alert('Could not save pricing: ' + (err.error || ('HTTP ' + res.status)));\n return false;\n }\n showSaved();\n return true;\n}\n\nasync function removePricing(model) {\n await fetch('/settings/api/pricing/' + encodeURIComponent(model), { method: 'DELETE' });\n showSaved();\n await loadPricing();\n}\n\nfunction rateCell(value, onCommit) {\n const td = document.createElement('td');\n const input = document.createElement('input');\n input.type = 'number';\n input.min = '0';\n input.step = '0.01';\n input.className = 'pricing-input';\n input.value = fmtRate(value);\n input.addEventListener('change', onCommit);\n td.appendChild(input);\n return { td: td, input: input };\n}\n\nfunction renderPricing() {\n const tbody = document.getElementById('pricingRows');\n tbody.innerHTML = '';\n const models = Array.from(new Set(\n Object.keys(pricingData.builtin).concat(Object.keys(pricingData.overrides))\n )).sort();\n\n for (const model of models) {\n const override = pricingData.overrides[model];\n const effective = override || pricingData.builtin[model];\n const tr = document.createElement('tr');\n\n const nameTd = document.createElement('td');\n nameTd.className = 'pricing-model';\n nameTd.textContent = model;\n tr.appendChild(nameTd);\n\n const cells = [];\n const commit = async function () {\n const rates = {\n inputPerMTok: parseFloat(cells[0].input.value),\n outputPerMTok: parseFloat(cells[1].input.value),\n cacheReadPerMTok: parseFloat(cells[2].input.value),\n cacheWritePerMTok: parseFloat(cells[3].input.value),\n };\n for (const k in rates) { if (!isFinite(rates[k]) || rates[k] < 0) return; }\n if (await putPricing(model, rates)) await loadPricing();\n };\n ['inputPerMTok', 'outputPerMTok', 'cacheReadPerMTok', 'cacheWritePerMTok'].forEach(function (key) {\n const cell = rateCell(effective[key], commit);\n cells.push(cell);\n tr.appendChild(cell.td);\n });\n\n const badgeTd = document.createElement('td');\n const badge = document.createElement('span');\n badge.className = 'pricing-badge ' + (override ? 'badge-override' : 'badge-builtin');\n badge.textContent = override ? 'Override' : 'Built-in';\n badgeTd.appendChild(badge);\n tr.appendChild(badgeTd);\n\n const actionTd = document.createElement('td');\n if (override) {\n const btn = document.createElement('button');\n btn.className = 'reset-btn';\n btn.textContent = pricingData.builtin[model] ? 'Reset' : 'Remove';\n btn.addEventListener('click', function () { removePricing(model); });\n actionTd.appendChild(btn);\n }\n tr.appendChild(actionTd);\n\n tbody.appendChild(tr);\n }\n}\n\nasync function addPricingModel() {\n const name = document.getElementById('newModelName').value.trim();\n const inputRate = parseFloat(document.getElementById('newModelInput').value);\n const outputRate = parseFloat(document.getElementById('newModelOutput').value);\n if (!name) { alert('Enter a model id'); return; }\n if (!isFinite(inputRate) || !isFinite(outputRate)) { alert('Enter input and output rates (USD per million tokens)'); return; }\n const rates = { inputPerMTok: inputRate, outputPerMTok: outputRate };\n const cacheRead = parseFloat(document.getElementById('newModelCacheRead').value);\n const cacheWrite = parseFloat(document.getElementById('newModelCacheWrite').value);\n if (isFinite(cacheRead)) rates.cacheReadPerMTok = cacheRead;\n if (isFinite(cacheWrite)) rates.cacheWritePerMTok = cacheWrite;\n if (await putPricing(name, rates)) {\n ['newModelName', 'newModelInput', 'newModelOutput', 'newModelCacheRead', 'newModelCacheWrite'].forEach(function (id) {\n document.getElementById(id).value = '';\n });\n await loadPricing();\n }\n}\n\nasync function loadRouting() {\n const res = await fetch('/settings/api/routing');\n const cfg = await res.json();\n const el = document.getElementById('routing-body');\n const envNote = (on) => on ? ' <span style=\"font-size:11px;color:var(--yellow)\">(env override active \u2014 setting saved but env wins)</span>' : '';\n let h = '<div style=\"display:flex;align-items:center;gap:12px;margin-bottom:14px\">'\n + '<label style=\"color:var(--muted);font-size:13px;width:90px\">Mode</label>'\n + '<select id=\"routing-mode\" style=\"background:var(--surface);color:var(--text);border:1px solid var(--border);border-radius:6px;padding:6px 10px\">'\n + ['active','sticky','priority'].map(m => '<option value=\"'+m+'\"'+(cfg.routing===m?' selected':'')+'>'+m+'</option>').join('')\n + '</select>' + envNote(cfg.envOverride.routing) + '</div>';\n h += '<div id=\"routing-order-wrap\" style=\"'+(cfg.routing==='priority'?'':'display:none')+'\">'\n + '<div style=\"color:var(--muted);font-size:13px;margin-bottom:8px\">Pool order \u2014 highest priority first. Drained top to bottom.'\n + envNote(cfg.envOverride.profileOrder) + '</div>'\n + '<ol id=\"routing-order\" style=\"margin:0;padding-left:22px\">'\n + cfg.profileOrder.map((id, i) =>\n '<li style=\"padding:4px 0;color:var(--text)\">'\n + '<span style=\"font-family:var(--mono, monospace)\">'+id+'</span>'\n + ' <button data-move=\"up\" data-i=\"'+i+'\" style=\"margin-left:10px;background:var(--surface);color:var(--muted);border:1px solid var(--border);border-radius:4px;padding:1px 8px;cursor:pointer\"'+(i===0?' disabled':'')+'>↑</button>'\n + ' <button data-move=\"down\" data-i=\"'+i+'\" style=\"background:var(--surface);color:var(--muted);border:1px solid var(--border);border-radius:4px;padding:1px 8px;cursor:pointer\"'+(i===cfg.profileOrder.length-1?' disabled':'')+'>↓</button>'\n + '</li>').join('')\n + '</ol></div>';\n el.innerHTML = h;\n document.getElementById('routing-mode').addEventListener('change', async (e) => {\n await fetch('/settings/api/routing', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ routing: e.target.value }) });\n await loadRouting();\n });\n el.querySelectorAll('button[data-move]').forEach(btn => btn.addEventListener('click', async () => {\n const i = Number(btn.dataset.i);\n const j = btn.dataset.move === 'up' ? i - 1 : i + 1;\n const order = cfg.profileOrder.slice();\n const tmp = order[i]; order[i] = order[j]; order[j] = tmp;\n await fetch('/settings/api/routing', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ profileOrder: order }) });\n await loadRouting();\n }));\n}\n\nloadConfig();\nloadPricing();\nloadRouting();\n\n(function() {\n var profileChip = document.getElementById('mhProfile');\n var statusDot = document.getElementById('mhDot');\n var statusText = document.getElementById('mhStatusText');\n\n // Highlight active nav link\n var path = location.pathname;\n var navLinks = document.querySelectorAll('.mh-nav a');\n navLinks.forEach(function(a) {\n if (a.getAttribute('href') === path || (path === '/telemetry' && a.id === 'nav-telemetry') || (path === '/' && a.id === 'nav-home')) {\n a.classList.add('active');\n }\n });\n\n function esc(s) { var d = document.createElement('div'); d.textContent = s; return d.innerHTML; }\n\n function loadHeader() {\n fetch('/health').then(function(r) { return r.json(); }).then(function(h) {\n var st = h.status === 'healthy' ? 'healthy' : h.status === 'degraded' ? 'degraded' : 'unhealthy';\n statusDot.className = 'mh-dot ' + st;\n statusText.textContent = st === 'healthy' ? 'Operational' : st === 'degraded' ? 'Degraded' : 'Offline';\n }).catch(function() {\n statusDot.className = 'mh-dot unhealthy';\n statusText.textContent = 'Offline';\n });\n\n fetch('/profiles/list').then(function(r) { return r.json(); }).then(function(data) {\n var current = (data.profiles || []).find(function(p) { return p.isActive; });\n if (!current) { profileChip.classList.remove('visible'); return; }\n profileChip.innerHTML = esc(current.id) + ' <span class=\"mh-profile-type\">' + esc(current.type || '') + '</span>';\n profileChip.classList.add('visible');\n }).catch(function() {});\n }\n\n loadHeader();\n setInterval(loadHeader, 10000);\n // Pages call this after mutating state (e.g. switching the active profile)\n // so the header chip updates immediately instead of on the next poll.\n window.meridianHeaderRefresh = loadHeader;\n})();\n\n</script>\n</body>\n</html>";
|
|
5
|
+
export declare const settingsPageHtml = "<!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 SDK Features</title>\n<link rel=\"icon\" type=\"image/svg+xml\" href=\"/telemetry/icon.svg\">\n<style>\n \n :root {\n /* Cool-gray neutral palette. High contrast, surface/border separation,\n no color cast muddying the text. Blue is the primary accent; violet\n is the fixed secondary \u2014 used together in the brand gradient and\n individually for hover states and a handful of telemetry badges. */\n --bg: #0d1117;\n --surface: #161b22;\n --surface2: #1c2128;\n --border: #30363d;\n /* Text */\n --text: #e6edf3;\n --muted: #8b949e;\n /* Brand \u2014 blue primary, violet secondary */\n --accent: #58a6ff;\n --accent2: #bc8cff;\n --violet: #bc8cff;\n --lavender: #d2a8ff;\n /* Semantic */\n --green: #3fb950;\n --yellow: #d29922;\n --red: #f85149;\n /* Telemetry-specific aliases (waterfall + lineage badges) */\n --blue: #58a6ff;\n --purple: #bc8cff;\n --queue: #d29922;\n --ttfb: #58a6ff;\n --upstream: #3fb950;\n }\n /* Banner backsplash \u2014 the brand look: a gentle diagonal wash with soft\n blue (top-left) and violet (bottom-right) glows. Pages must not set\n their own body background so this shows through everywhere. */\n body {\n background:\n radial-gradient(1200px 800px at 12% -8%, rgba(88,166,255,0.07), transparent 60%),\n radial-gradient(1100px 800px at 92% 108%, rgba(188,140,255,0.06), transparent 60%),\n linear-gradient(135deg, #0d1117 0%, #10151d 55%, #161b22 100%);\n background-attachment: fixed;\n background-color: var(--bg);\n }\n\n * { box-sizing: border-box; margin: 0; padding: 0; }\n body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;\n color: var(--text); padding: 0; line-height: 1.5; }\n \n .meridian-header {\n position: sticky; top: 0; z-index: 100;\n display: flex; align-items: center; gap: 20px;\n padding: 10px 24px;\n background: rgba(13, 17, 23, 0.92);\n backdrop-filter: blur(12px);\n border-bottom: 1px solid var(--border, #30363d);\n }\n .meridian-header .mh-brand {\n display: flex; align-items: center; gap: 10px;\n text-decoration: none; color: var(--text, #e6edf3);\n }\n .meridian-header .mh-logo { display: block; }\n .meridian-header .mh-name {\n font-size: 15px; font-weight: 700; letter-spacing: 2px;\n text-transform: uppercase;\n }\n .meridian-header .mh-nav { display: flex; align-items: center; gap: 2px; }\n .meridian-header .mh-nav a {\n color: var(--muted, #8b949e); text-decoration: none; font-size: 12px;\n font-weight: 500; padding: 5px 10px; border-radius: 6px;\n transition: color 0.15s, background 0.15s;\n }\n .meridian-header .mh-nav a:hover { color: var(--text, #e6edf3); background: var(--surface, #161b22); }\n .meridian-header .mh-nav a.active { color: var(--accent, #58a6ff); background: var(--surface, #161b22); }\n .meridian-header .mh-right {\n margin-left: auto; display: flex; align-items: center; gap: 10px;\n }\n .meridian-header .mh-profile {\n display: none; align-items: center; gap: 6px;\n font-size: 11px; font-weight: 500; color: var(--text, #e6edf3);\n padding: 3px 10px; border-radius: 20px;\n background: var(--surface, #161b22); border: 1px solid var(--border, #30363d);\n text-decoration: none; transition: border-color 0.15s;\n }\n .meridian-header .mh-profile:hover { border-color: var(--accent, #58a6ff); }\n .meridian-header .mh-profile.visible { display: inline-flex; }\n .meridian-header .mh-profile .mh-profile-type {\n color: var(--muted, #8b949e); font-size: 10px;\n }\n .meridian-header .mh-status {\n display: inline-flex; align-items: center; gap: 6px;\n font-size: 11px; color: var(--muted, #8b949e); white-space: nowrap;\n }\n .meridian-header .mh-dot {\n width: 8px; height: 8px; border-radius: 50%;\n background: var(--muted, #8b949e); flex-shrink: 0;\n }\n .meridian-header .mh-dot.healthy { background: var(--green, #3fb950); box-shadow: 0 0 6px rgba(63,185,80,0.5); }\n .meridian-header .mh-dot.degraded { background: var(--yellow, #d29922); }\n .meridian-header .mh-dot.unhealthy { background: var(--red, #f85149); }\n @media (max-width: 720px) {\n .meridian-header { gap: 10px; padding: 10px 16px; flex-wrap: wrap; }\n .meridian-header .mh-name { display: none; }\n .meridian-header .mh-status .mh-status-text { display: none; }\n }\n\n .content { max-width: 900px; margin: 0 auto; padding: 24px; }\n h1 { font-size: 20px; font-weight: 600; margin-bottom: 4px; }\n .subtitle { color: var(--muted); font-size: 13px; margin-bottom: 24px; }\n .nav { display: flex; gap: 16px; margin-bottom: 24px; font-size: 13px; }\n .nav a { color: var(--muted); text-decoration: none; }\n .nav a:hover { color: var(--accent); }\n .nav a.active { color: var(--accent); }\n\n .adapter-card {\n background: var(--surface); border: 1px solid var(--border); border-radius: 8px;\n padding: 20px; margin-bottom: 16px;\n }\n .adapter-header {\n display: flex; align-items: center; justify-content: space-between;\n margin-bottom: 16px;\n }\n .adapter-name { font-size: 16px; font-weight: 600; }\n .adapter-badge {\n font-size: 10px; padding: 2px 8px; border-radius: 10px;\n text-transform: uppercase; letter-spacing: 0.5px;\n }\n .badge-active { background: rgba(63, 185, 80, 0.15); color: var(--green); }\n .badge-inactive { background: rgba(139, 148, 158, 0.15); color: var(--muted); }\n\n .feature-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }\n @media (max-width: 600px) { .feature-grid { grid-template-columns: 1fr; } }\n\n .feature-row {\n display: flex; align-items: center; justify-content: space-between;\n padding: 10px 14px; border-radius: 6px;\n background: var(--bg); border: 1px solid var(--border);\n }\n .feature-info { display: flex; flex-direction: column; }\n .feature-label { font-size: 13px; font-weight: 500; }\n .feature-desc { font-size: 11px; color: var(--muted); margin-top: 2px; }\n\n /* Toggle switch */\n .toggle { position: relative; width: 36px; height: 20px; flex-shrink: 0; }\n .toggle input { opacity: 0; width: 0; height: 0; }\n .toggle-track {\n position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0;\n background: var(--border); border-radius: 10px; transition: background 0.2s;\n }\n .toggle-track::after {\n content: \"\"; position: absolute; height: 14px; width: 14px;\n left: 3px; bottom: 3px; background: var(--muted); border-radius: 50%;\n transition: transform 0.2s, background 0.2s;\n }\n .toggle input:checked + .toggle-track { background: var(--accent); }\n .toggle input:checked + .toggle-track::after {\n transform: translateX(16px); background: var(--text);\n }\n\n /* Select dropdown */\n .feature-select {\n background: var(--surface); color: var(--text); border: 1px solid var(--border);\n border-radius: 6px; padding: 4px 8px; font-size: 12px; cursor: pointer;\n }\n\n .save-indicator {\n position: fixed; bottom: 24px; right: 24px;\n background: var(--green); color: #000; padding: 8px 16px;\n border-radius: 6px; font-size: 13px; font-weight: 500;\n opacity: 0; transition: opacity 0.3s; pointer-events: none;\n }\n .save-indicator.visible { opacity: 1; }\n\n .reset-btn {\n background: none; border: 1px solid var(--border); color: var(--muted);\n border-radius: 6px; padding: 4px 12px; font-size: 11px; cursor: pointer;\n }\n .reset-btn:hover { border-color: var(--red); color: var(--red); }\n\n /* Model pricing */\n .pricing-table { width: 100%; border-collapse: collapse; font-size: 12px; }\n .pricing-table th { text-align: left; padding: 8px 10px; color: var(--muted); font-weight: 500;\n font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; border-bottom: 1px solid var(--border); }\n .pricing-table td { padding: 6px 10px; border-bottom: 1px solid var(--border); }\n .pricing-table tr:last-child td { border-bottom: none; }\n .pricing-model { font-family: 'SF Mono', SFMono-Regular, Consolas, monospace; font-size: 12px; word-break: break-all; }\n .pricing-input { background: var(--bg); color: var(--text); border: 1px solid var(--border);\n border-radius: 6px; padding: 4px 8px; font-size: 12px; width: 84px; text-align: right;\n font-variant-numeric: tabular-nums; }\n .pricing-input:focus { border-color: var(--accent); outline: none; }\n .pricing-badge { font-size: 10px; padding: 2px 8px; border-radius: 10px;\n text-transform: uppercase; letter-spacing: 0.5px; white-space: nowrap; }\n .badge-override { background: rgba(210, 153, 34, 0.15); color: var(--yellow); }\n .badge-builtin { background: rgba(139, 148, 158, 0.15); color: var(--muted); }\n .pricing-add { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin-top: 14px;\n padding-top: 14px; border-top: 1px solid var(--border); }\n .pricing-add input[type=\"text\"] { width: 240px; text-align: left; }\n .add-btn { background: var(--accent); border: none; color: #fff; border-radius: 6px;\n padding: 5px 14px; font-size: 12px; font-weight: 500; cursor: pointer; }\n .pricing-note { font-size: 11px; color: var(--muted); margin-top: 12px; line-height: 1.6; }\n</style>\n</head>\n<body>\n\n<header class=\"meridian-header\" id=\"meridianHeader\">\n <a class=\"mh-brand\" href=\"/\">\n <svg class=\"mh-logo\" width=\"24\" height=\"24\" viewBox=\"0 0 64 64\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <defs>\n <linearGradient id=\"mhGrad\" x1=\"32\" y1=\"4\" x2=\"32\" y2=\"60\" gradientUnits=\"userSpaceOnUse\">\n <stop stop-color=\"#58a6ff\"/>\n <stop offset=\"1\" stop-color=\"#bc8cff\"/>\n </linearGradient>\n </defs>\n <circle cx=\"32\" cy=\"32\" r=\"25\" stroke=\"url(#mhGrad)\" stroke-width=\"3.5\"/>\n <ellipse cx=\"32\" cy=\"32\" rx=\"10.5\" ry=\"25\" stroke=\"url(#mhGrad)\" stroke-width=\"2.5\" opacity=\"0.8\"/>\n <path d=\"M7 32h50\" stroke=\"url(#mhGrad)\" stroke-width=\"2\" opacity=\"0.4\"/>\n <circle cx=\"32\" cy=\"7\" r=\"4.5\" fill=\"#58a6ff\"/>\n <circle cx=\"32\" cy=\"57\" r=\"4.5\" fill=\"#bc8cff\"/>\n</svg>\n <span class=\"mh-name\">Meridian</span>\n </a>\n <nav class=\"mh-nav\">\n <a href=\"/\" id=\"nav-home\">Home</a>\n <a href=\"/telemetry\" id=\"nav-telemetry\">Telemetry</a>\n <a href=\"/profiles\" id=\"nav-profiles\">Profiles</a>\n <a href=\"/settings\" id=\"nav-settings\">Settings</a>\n <a href=\"/plugins\" id=\"nav-plugins\">Plugins</a>\n </nav>\n <div class=\"mh-right\">\n <a class=\"mh-profile\" id=\"mhProfile\" href=\"/\" title=\"Active profile \u2014 switch from the home page\"></a>\n <span class=\"mh-status\" id=\"mhStatus\"><span class=\"mh-dot\" id=\"mhDot\"></span><span class=\"mh-status-text\" id=\"mhStatusText\"></span></span>\n </div>\n</header>\n\n<div class=\"content\">\n <h1>SDK Features <span style=\"font-size:11px;padding:2px 8px;border-radius:10px;background:rgba(210,153,34,0.15);color:var(--yellow);vertical-align:middle;margin-left:8px\">Experimental</span></h1>\n <p class=\"subtitle\" style=\"max-width:720px;line-height:1.6\">\n Unlock Claude Code features for any connected agent. Capabilities like auto-memory, dreaming, and CLAUDE.md \u2014 normally\n exclusive to Claude Code \u2014 become available to OpenCode, Crush, Droid, and any other harness routed through Meridian.\n Each agent keeps its own toolchain while gaining access to these additional features.<br><br>\n <strong style=\"color:var(--text)\">System prompts:</strong> For these features to work correctly, both the Claude Code prompt and your client prompt\n should be enabled. When both are active, they are appended together \u2014 Claude Code's base instructions come first,\n followed by your agent's specific instructions.\n </p>\n\n <div id=\"adapters\"></div>\n\n <h1 style=\"margin-top:40px\">Routing</h1>\n <p class=\"subtitle\" style=\"max-width:720px;line-height:1.6\">\n How unpinned requests choose an account. <strong style=\"color:var(--text)\">Active</strong> uses the manually\n selected profile. <strong style=\"color:var(--text)\">Sticky</strong> distributes sessions across profiles evenly\n (cache-affine). <strong style=\"color:var(--text)\">Priority</strong> drains the pool in order \u2014 highest first \u2014\n and fails over per request when an account runs out; conversations keep their account, and new sessions\n return to the preferred account after its window resets. An explicit <code>x-meridian-profile</code> header\n always overrides. Changes apply to the next request \u2014 no restart needed.\n </p>\n <div class=\"adapter-card\" id=\"routing-card\">\n <div id=\"routing-body\">Loading\u2026</div>\n </div>\n\n <h1 style=\"margin-top:40px\">Model Pricing</h1>\n <p class=\"subtitle\" style=\"max-width:720px;line-height:1.6\">\n Rates used by the telemetry cost estimate, in USD per million tokens. Edit a value to override the\n built-in rate, or add models the built-in table doesn't know about (they show as \"no pricing\" on the\n dashboard until defined here). Changes apply on the next dashboard refresh.\n </p>\n <div class=\"adapter-card\">\n <table class=\"pricing-table\">\n <thead><tr><th>Model</th><th>Input</th><th>Output</th><th>Cache Read</th><th>Cache Write</th><th>Source</th><th></th></tr></thead>\n <tbody id=\"pricingRows\"></tbody>\n </table>\n <div class=\"pricing-add\">\n <input type=\"text\" class=\"pricing-input\" id=\"newModelName\" placeholder=\"model id (e.g. claude-opus-9)\">\n <input type=\"number\" class=\"pricing-input\" id=\"newModelInput\" placeholder=\"input\" min=\"0\" step=\"0.01\">\n <input type=\"number\" class=\"pricing-input\" id=\"newModelOutput\" placeholder=\"output\" min=\"0\" step=\"0.01\">\n <input type=\"number\" class=\"pricing-input\" id=\"newModelCacheRead\" placeholder=\"cache read\" min=\"0\" step=\"0.01\">\n <input type=\"number\" class=\"pricing-input\" id=\"newModelCacheWrite\" placeholder=\"cache write\" min=\"0\" step=\"0.01\">\n <button class=\"add-btn\" onclick=\"addPricingModel()\">Add Model</button>\n </div>\n <div class=\"pricing-note\">\n Cache read and cache write are optional; when left blank they default to 0.1x and 1.25x of the\n input rate (the 5-minute cache TTL multipliers). Verify current list prices at\n <a href=\"https://claude.com/pricing\" target=\"_blank\" rel=\"noreferrer\" style=\"color:var(--accent)\">claude.com/pricing</a>.\n </div>\n </div>\n</div>\n\n<div class=\"save-indicator\" id=\"saveIndicator\">Saved</div>\n\n<script>\nconst FEATURES = [\n { key: 'codeSystemPrompt', label: 'Claude Code Prompt', desc: 'Include the built-in Claude Code system prompt (tool usage rules, safety guidelines, coding best practices)', type: 'toggle' },\n { key: 'clientSystemPrompt', label: 'Client Prompt', desc: 'Include the system prompt sent by the connecting agent (e.g. OpenCode or Crush instructions)', type: 'toggle' },\n { key: 'claudeMd', label: 'CLAUDE.md', desc: 'Load CLAUDE.md instruction files \u2014 Off: none, Project: ./CLAUDE.md only, Full: ~/.claude/CLAUDE.md + ./CLAUDE.md', type: 'select', options: ['off', 'project', 'full'] },\n { key: 'memory', label: 'Memory', desc: 'Read and write memories across sessions', type: 'toggle' },\n { key: 'dreaming', label: 'Auto-Dream', desc: 'Background memory consolidation', type: 'toggle' },\n { key: 'thinking', label: 'Thinking', desc: 'Extended thinking mode', type: 'select', options: ['disabled', 'adaptive', 'enabled'] },\n { key: 'thinkingPassthrough', label: 'Thinking Passthrough', desc: 'Forward thinking blocks to the client', type: 'toggle' },\n { key: 'sharedMemory', label: 'Shared Memory', desc: 'Share memory with Claude Code (~/.claude) instead of isolated storage', type: 'toggle' },\n { key: 'webFetchPreflight', label: 'WebFetch Preflight', desc: 'Check each WebFetch hostname against the Anthropic blocklist before fetching \u2014 off keeps hostnames local but fetches any URL unchecked. Only affects Cherry Studio; other adapters never run the SDK\\'s built-in WebFetch', type: 'toggle' },\n { key: 'claudeAiConnectors', label: 'claude.ai Connectors', desc: 'Load the MCP connectors attached to your claude.ai account (Drive, Gmail, Calendar). Off by default; always off in passthrough mode', type: 'toggle' },\n { key: 'maxBudgetUsd', label: 'Max Budget (USD)', desc: 'Per-request cost cap \u2014 query aborts if exceeded (0 = disabled)', type: 'number' },\n { key: 'fallbackModel', label: 'Fallback Model', desc: 'Auto-fallback model if primary fails', type: 'select', options: ['', 'sonnet', 'opus', 'haiku', 'sonnet[1m]', 'opus[1m]'] },\n { key: 'sdkDebug', label: 'SDK Debug Logging', desc: 'Enable verbose SDK debug output to proxy stderr', type: 'toggle' },\n { key: 'additionalDirectories', label: 'Additional Directories', desc: 'Comma-separated extra paths Claude can access (monorepo libs, etc.)', type: 'text' },\n];\n\n// Display names only \u2014 NOT the list of adapters to render. The page renders\n// whatever /settings/api/features returns, so a new adapter shows up here the\n// moment it exists, using its raw name until someone gives it a pretty one.\n// A hardcoded list is what hid codex and cherry from this page entirely.\nconst ADAPTER_LABELS = {\n opencode: 'OpenCode',\n openai: 'OpenAI (/v1/chat/completions)',\n crush: 'Crush',\n forgecode: 'ForgeCode',\n pi: 'Pi',\n droid: 'Droid',\n passthrough: 'LiteLLM / Passthrough',\n codex: 'Codex CLI (/v1/responses)',\n cherry: 'Cherry Studio',\n 'claude-code': 'Claude Code',\n};\n\nlet currentConfig = {};\n\nasync function loadConfig() {\n const res = await fetch('/settings/api/features');\n currentConfig = await res.json();\n render();\n}\n\nasync function saveFeature(adapter, key, value) {\n const patch = {};\n patch[key] = value;\n await fetch('/settings/api/features/' + adapter, {\n method: 'PATCH',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(patch),\n });\n currentConfig[adapter][key] = value;\n showSaved();\n}\n\nasync function resetAdapter(adapter) {\n await fetch('/settings/api/features/' + adapter, { method: 'DELETE' });\n await loadConfig();\n showSaved();\n}\n\nfunction showSaved() {\n const el = document.getElementById('saveIndicator');\n el.classList.add('visible');\n setTimeout(() => el.classList.remove('visible'), 1500);\n}\n\nfunction hasAnyEnabled(features) {\n return features.codeSystemPrompt || !features.clientSystemPrompt || features.claudeMd !== 'off' || features.memory || features.dreaming ||\n features.thinking !== 'disabled' || features.thinkingPassthrough ||\n features.sharedMemory || features.webFetchPreflight === false ||\n features.claudeAiConnectors || features.maxBudgetUsd > 0 ||\n features.fallbackModel || features.sdkDebug ||\n features.additionalDirectories;\n}\n\nfunction render() {\n const container = document.getElementById('adapters');\n container.innerHTML = '';\n\n for (const adapter of Object.keys(currentConfig)) {\n const label = ADAPTER_LABELS[adapter] || adapter;\n const features = currentConfig[adapter] || {};\n const active = hasAnyEnabled(features);\n\n const card = document.createElement('div');\n card.className = 'adapter-card';\n card.innerHTML = '<div class=\"adapter-header\">' +\n '<span class=\"adapter-name\">' + label + '</span>' +\n '<div style=\"display:flex;gap:8px;align-items:center\">' +\n '<span class=\"adapter-badge ' + (active ? 'badge-active' : 'badge-inactive') + '\">' +\n (active ? 'Active' : 'Default') +\n '</span>' +\n '<button class=\"reset-btn\" onclick=\"resetAdapter(\\''+adapter+'\\')\">Reset</button>' +\n '</div>' +\n '</div>';\n\n const grid = document.createElement('div');\n grid.className = 'feature-grid';\n\n for (const feat of FEATURES) {\n const row = document.createElement('div');\n row.className = 'feature-row';\n\n const info = '<div class=\"feature-info\"><span class=\"feature-label\">' +\n feat.label + '</span><span class=\"feature-desc\">' + feat.desc + '</span></div>';\n\n if (feat.type === 'toggle') {\n const checked = features[feat.key] ? 'checked' : '';\n row.innerHTML = info +\n '<label class=\"toggle\"><input type=\"checkbox\" ' + checked +\n ' onchange=\"saveFeature(\\''+adapter+'\\', \\''+feat.key+'\\', this.checked)\">' +\n '<span class=\"toggle-track\"></span></label>';\n } else if (feat.type === 'select') {\n const options = feat.options.map(o => {\n const label = o === '' ? '(None)' : o.charAt(0).toUpperCase()+o.slice(1);\n return '<option value=\"'+o+'\"'+(features[feat.key]===o?' selected':'')+'>'+label+'</option>';\n }).join('');\n row.innerHTML = info +\n '<select class=\"feature-select\" onchange=\"saveFeature(\\''+adapter+'\\', \\''+feat.key+'\\', this.value)\">' +\n options + '</select>';\n } else if (feat.type === 'number') {\n const value = features[feat.key] ?? 0;\n row.innerHTML = info +\n '<input type=\"number\" class=\"feature-select\" style=\"width:80px;text-align:right\" min=\"0\" step=\"0.01\" value=\"'+value+'\"' +\n ' onchange=\"saveFeature(\\''+adapter+'\\', \\''+feat.key+'\\', parseFloat(this.value)||0)\">';\n } else if (feat.type === 'text') {\n const value = (features[feat.key] ?? '').toString().replace(/\"/g, '"');\n row.innerHTML = info +\n '<input type=\"text\" class=\"feature-select\" style=\"width:180px\" value=\"'+value+'\"' +\n ' onchange=\"saveFeature(\\''+adapter+'\\', \\''+feat.key+'\\', this.value)\">';\n }\n\n grid.appendChild(row);\n }\n\n card.appendChild(grid);\n container.appendChild(card);\n }\n}\n\n// ---- Model pricing (telemetry cost estimate) ----\nlet pricingData = { builtin: {}, overrides: {} };\n\nfunction fmtRate(v) { return String(Math.round(v * 10000) / 10000); }\n\nasync function loadPricing() {\n const res = await fetch('/settings/api/pricing');\n pricingData = await res.json();\n renderPricing();\n}\n\nasync function putPricing(model, rates) {\n const res = await fetch('/settings/api/pricing/' + encodeURIComponent(model), {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(rates),\n });\n if (!res.ok) {\n const err = await res.json().catch(function () { return {}; });\n alert('Could not save pricing: ' + (err.error || ('HTTP ' + res.status)));\n return false;\n }\n showSaved();\n return true;\n}\n\nasync function removePricing(model) {\n await fetch('/settings/api/pricing/' + encodeURIComponent(model), { method: 'DELETE' });\n showSaved();\n await loadPricing();\n}\n\nfunction rateCell(value, onCommit) {\n const td = document.createElement('td');\n const input = document.createElement('input');\n input.type = 'number';\n input.min = '0';\n input.step = '0.01';\n input.className = 'pricing-input';\n input.value = fmtRate(value);\n input.addEventListener('change', onCommit);\n td.appendChild(input);\n return { td: td, input: input };\n}\n\nfunction renderPricing() {\n const tbody = document.getElementById('pricingRows');\n tbody.innerHTML = '';\n const models = Array.from(new Set(\n Object.keys(pricingData.builtin).concat(Object.keys(pricingData.overrides))\n )).sort();\n\n for (const model of models) {\n const override = pricingData.overrides[model];\n const effective = override || pricingData.builtin[model];\n const tr = document.createElement('tr');\n\n const nameTd = document.createElement('td');\n nameTd.className = 'pricing-model';\n nameTd.textContent = model;\n tr.appendChild(nameTd);\n\n const cells = [];\n const commit = async function () {\n const rates = {\n inputPerMTok: parseFloat(cells[0].input.value),\n outputPerMTok: parseFloat(cells[1].input.value),\n cacheReadPerMTok: parseFloat(cells[2].input.value),\n cacheWritePerMTok: parseFloat(cells[3].input.value),\n };\n for (const k in rates) { if (!isFinite(rates[k]) || rates[k] < 0) return; }\n if (await putPricing(model, rates)) await loadPricing();\n };\n ['inputPerMTok', 'outputPerMTok', 'cacheReadPerMTok', 'cacheWritePerMTok'].forEach(function (key) {\n const cell = rateCell(effective[key], commit);\n cells.push(cell);\n tr.appendChild(cell.td);\n });\n\n const badgeTd = document.createElement('td');\n const badge = document.createElement('span');\n badge.className = 'pricing-badge ' + (override ? 'badge-override' : 'badge-builtin');\n badge.textContent = override ? 'Override' : 'Built-in';\n badgeTd.appendChild(badge);\n tr.appendChild(badgeTd);\n\n const actionTd = document.createElement('td');\n if (override) {\n const btn = document.createElement('button');\n btn.className = 'reset-btn';\n btn.textContent = pricingData.builtin[model] ? 'Reset' : 'Remove';\n btn.addEventListener('click', function () { removePricing(model); });\n actionTd.appendChild(btn);\n }\n tr.appendChild(actionTd);\n\n tbody.appendChild(tr);\n }\n}\n\nasync function addPricingModel() {\n const name = document.getElementById('newModelName').value.trim();\n const inputRate = parseFloat(document.getElementById('newModelInput').value);\n const outputRate = parseFloat(document.getElementById('newModelOutput').value);\n if (!name) { alert('Enter a model id'); return; }\n if (!isFinite(inputRate) || !isFinite(outputRate)) { alert('Enter input and output rates (USD per million tokens)'); return; }\n const rates = { inputPerMTok: inputRate, outputPerMTok: outputRate };\n const cacheRead = parseFloat(document.getElementById('newModelCacheRead').value);\n const cacheWrite = parseFloat(document.getElementById('newModelCacheWrite').value);\n if (isFinite(cacheRead)) rates.cacheReadPerMTok = cacheRead;\n if (isFinite(cacheWrite)) rates.cacheWritePerMTok = cacheWrite;\n if (await putPricing(name, rates)) {\n ['newModelName', 'newModelInput', 'newModelOutput', 'newModelCacheRead', 'newModelCacheWrite'].forEach(function (id) {\n document.getElementById(id).value = '';\n });\n await loadPricing();\n }\n}\n\nasync function loadRouting() {\n const res = await fetch('/settings/api/routing');\n const cfg = await res.json();\n const el = document.getElementById('routing-body');\n const envNote = (on) => on ? ' <span style=\"font-size:11px;color:var(--yellow)\">(env override active \u2014 setting saved but env wins)</span>' : '';\n let h = '<div style=\"display:flex;align-items:center;gap:12px;margin-bottom:14px\">'\n + '<label style=\"color:var(--muted);font-size:13px;width:90px\">Mode</label>'\n + '<select id=\"routing-mode\" style=\"background:var(--surface);color:var(--text);border:1px solid var(--border);border-radius:6px;padding:6px 10px\">'\n + ['active','sticky','priority'].map(m => '<option value=\"'+m+'\"'+(cfg.routing===m?' selected':'')+'>'+m+'</option>').join('')\n + '</select>' + envNote(cfg.envOverride.routing) + '</div>';\n h += '<div id=\"routing-order-wrap\" style=\"'+(cfg.routing==='priority'?'':'display:none')+'\">'\n + '<div style=\"color:var(--muted);font-size:13px;margin-bottom:8px\">Pool order \u2014 highest priority first. Drained top to bottom.'\n + envNote(cfg.envOverride.profileOrder) + '</div>'\n + '<ol id=\"routing-order\" style=\"margin:0;padding-left:22px\">'\n + cfg.profileOrder.map((id, i) =>\n '<li style=\"padding:4px 0;color:var(--text)\">'\n + '<span style=\"font-family:var(--mono, monospace)\">'+id+'</span>'\n + ' <button data-move=\"up\" data-i=\"'+i+'\" style=\"margin-left:10px;background:var(--surface);color:var(--muted);border:1px solid var(--border);border-radius:4px;padding:1px 8px;cursor:pointer\"'+(i===0?' disabled':'')+'>↑</button>'\n + ' <button data-move=\"down\" data-i=\"'+i+'\" style=\"background:var(--surface);color:var(--muted);border:1px solid var(--border);border-radius:4px;padding:1px 8px;cursor:pointer\"'+(i===cfg.profileOrder.length-1?' disabled':'')+'>↓</button>'\n + '</li>').join('')\n + '</ol></div>';\n el.innerHTML = h;\n document.getElementById('routing-mode').addEventListener('change', async (e) => {\n await fetch('/settings/api/routing', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ routing: e.target.value }) });\n await loadRouting();\n });\n el.querySelectorAll('button[data-move]').forEach(btn => btn.addEventListener('click', async () => {\n const i = Number(btn.dataset.i);\n const j = btn.dataset.move === 'up' ? i - 1 : i + 1;\n const order = cfg.profileOrder.slice();\n const tmp = order[i]; order[i] = order[j]; order[j] = tmp;\n await fetch('/settings/api/routing', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ profileOrder: order }) });\n await loadRouting();\n }));\n}\n\nloadConfig();\nloadPricing();\nloadRouting();\n\n(function() {\n var profileChip = document.getElementById('mhProfile');\n var statusDot = document.getElementById('mhDot');\n var statusText = document.getElementById('mhStatusText');\n\n // Highlight active nav link\n var path = location.pathname;\n var navLinks = document.querySelectorAll('.mh-nav a');\n navLinks.forEach(function(a) {\n if (a.getAttribute('href') === path || (path === '/telemetry' && a.id === 'nav-telemetry') || (path === '/' && a.id === 'nav-home')) {\n a.classList.add('active');\n }\n });\n\n function esc(s) { var d = document.createElement('div'); d.textContent = s; return d.innerHTML; }\n\n function loadHeader() {\n fetch('/health').then(function(r) { return r.json(); }).then(function(h) {\n var st = h.status === 'healthy' ? 'healthy' : h.status === 'degraded' ? 'degraded' : 'unhealthy';\n statusDot.className = 'mh-dot ' + st;\n statusText.textContent = st === 'healthy' ? 'Operational' : st === 'degraded' ? 'Degraded' : 'Offline';\n }).catch(function() {\n statusDot.className = 'mh-dot unhealthy';\n statusText.textContent = 'Offline';\n });\n\n fetch('/profiles/list').then(function(r) { return r.json(); }).then(function(data) {\n var current = (data.profiles || []).find(function(p) { return p.isActive; });\n if (!current) { profileChip.classList.remove('visible'); return; }\n profileChip.innerHTML = esc(current.id) + ' <span class=\"mh-profile-type\">' + esc(current.type || '') + '</span>';\n profileChip.classList.add('visible');\n }).catch(function() {});\n }\n\n loadHeader();\n setInterval(loadHeader, 10000);\n // Pages call this after mutating state (e.g. switching the active profile)\n // so the header chip updates immediately instead of on the next poll.\n window.meridianHeaderRefresh = loadHeader;\n})();\n\n</script>\n</body>\n</html>";
|
|
6
6
|
//# sourceMappingURL=settingsPage.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"settingsPage.d.ts","sourceRoot":"","sources":["../../src/telemetry/settingsPage.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,eAAO,MAAM,gBAAgB,
|
|
1
|
+
{"version":3,"file":"settingsPage.d.ts","sourceRoot":"","sources":["../../src/telemetry/settingsPage.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,eAAO,MAAM,gBAAgB,yz9BAodrB,CAAA"}
|
package/package.json
CHANGED