kly 0.3.1 → 0.4.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.

Potentially problematic release.


This version of kly might be problematic. Click here for more details.

Files changed (46) hide show
  1. package/dist/ai/context.mjs +1 -17
  2. package/dist/ai/context.mjs.map +1 -1
  3. package/dist/ai/storage.mjs +1 -1
  4. package/dist/bin/bin-registry--T4tCIwo.mjs +3 -0
  5. package/dist/bin/kly.mjs +55 -1369
  6. package/dist/bin/kly.mjs.map +1 -1
  7. package/dist/define-app.d.mts.map +1 -1
  8. package/dist/define-app.mjs +20 -12
  9. package/dist/define-app.mjs.map +1 -1
  10. package/dist/index.d.mts +2 -2
  11. package/dist/shared/constants.mjs +1 -19
  12. package/dist/shared/constants.mjs.map +1 -1
  13. package/dist/shared/runtime-mode.mjs +1 -28
  14. package/dist/shared/runtime-mode.mjs.map +1 -1
  15. package/dist/types.d.mts +1 -87
  16. package/dist/types.d.mts.map +1 -1
  17. package/dist/types.mjs.map +1 -1
  18. package/dist/ui/components/confirm.d.mts.map +1 -1
  19. package/dist/ui/components/confirm.mjs +1 -6
  20. package/dist/ui/components/confirm.mjs.map +1 -1
  21. package/dist/ui/components/form.d.mts.map +1 -1
  22. package/dist/ui/components/form.mjs +1 -3
  23. package/dist/ui/components/form.mjs.map +1 -1
  24. package/dist/ui/components/input.d.mts.map +1 -1
  25. package/dist/ui/components/input.mjs +1 -3
  26. package/dist/ui/components/input.mjs.map +1 -1
  27. package/dist/ui/components/select.d.mts.map +1 -1
  28. package/dist/ui/components/select.mjs +1 -6
  29. package/dist/ui/components/select.mjs.map +1 -1
  30. package/package.json +1 -2
  31. package/dist/bin/bin-registry-Cj1PddZ1.mjs +0 -3
  32. package/dist/bin/config-builder-CkkAqWMY.mjs +0 -3
  33. package/dist/bin/launcher-CSdy4_ZV.mjs +0 -3
  34. package/dist/bin/permissions-DOOzghc5.mjs +0 -3
  35. package/dist/bin/permissions-extractor-B3XI8JLv.mjs +0 -31
  36. package/dist/bin/permissions-extractor-B3XI8JLv.mjs.map +0 -1
  37. package/dist/permissions/index.mjs +0 -121
  38. package/dist/permissions/index.mjs.map +0 -1
  39. package/dist/sandbox/bundled-executor.d.mts +0 -17
  40. package/dist/sandbox/bundled-executor.d.mts.map +0 -1
  41. package/dist/sandbox/bundled-executor.mjs +0 -235
  42. package/dist/sandbox/bundled-executor.mjs.map +0 -1
  43. package/dist/sandbox/ipc-client.mjs +0 -42
  44. package/dist/sandbox/ipc-client.mjs.map +0 -1
  45. package/dist/sandbox/sandboxed-context.mjs +0 -14
  46. package/dist/sandbox/sandboxed-context.mjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"select.mjs","names":[],"sources":["../../../src/ui/components/select.ts"],"sourcesContent":["import * as p from \"@clack/prompts\";\nimport { sendIPCRequest } from \"../../sandbox/ipc-client\";\nimport { isMCP, isSandbox } from \"../../shared/runtime-mode\";\nimport { handleCancel } from \"../utils/cancel\";\nimport { isTTY } from \"../utils/tty\";\n\n/**\n * Option for select menus\n */\nexport interface SelectOption<T = string> {\n /** Display name */\n name: string;\n /** Optional description shown below name */\n description?: string;\n /** Value returned when selected */\n value: T;\n /** Disable this option */\n disabled?: boolean;\n}\n\n/**\n * Configuration for select component\n */\nexport interface SelectConfig<T> {\n /** Options to choose from */\n options: SelectOption<T>[];\n /** Prompt message */\n prompt?: string;\n}\n\n/**\n * Show a selection menu and wait for user choice\n *\n * @example\n * ```typescript\n * const color = await select({\n * options: [\n * { name: \"Red\", value: \"red\" },\n * { name: \"Blue\", value: \"blue\", description: \"Ocean color\" },\n * ],\n * prompt: \"Pick a color\"\n * });\n * ```\n */\nexport async function select<T = string>(config: SelectConfig<T>): Promise<T> {\n // Sandbox mode: use IPC to request select from host\n if (isSandbox()) {\n return sendIPCRequest<T>(\"prompt:select\", {\n prompt: config.prompt ?? \"Select an option\",\n options: config.options,\n });\n }\n\n // Non-TTY fallback: auto-select first option or throw in MCP mode\n if (!isTTY()) {\n // In MCP mode, interactive selection is not allowed\n if (isMCP()) {\n throw new Error(\n `Interactive selection not available in MCP mode. All parameters must be defined in the tool's inputSchema. Selection prompt: ${config.prompt}`,\n );\n }\n\n const firstOption = config.options[0];\n if (!firstOption) {\n throw new Error(\"No options provided\");\n }\n return firstOption.value;\n }\n\n // Map to @clack/prompts Option format\n // Using type assertion due to complex Option<T> conditional types\n const mappedOptions = config.options.map((opt) => ({\n label: opt.name,\n value: opt.value as unknown,\n ...(opt.description && { hint: opt.description }),\n ...(opt.disabled !== undefined && { disabled: opt.disabled }),\n }));\n\n const result = await p.select({\n message: config.prompt ?? \"Select an option\",\n options: mappedOptions,\n });\n\n return handleCancel(result) as T;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AA4CA,eAAsB,OAAmB,QAAqC;AAE5E,KAAI,WAAW,CACb,QAAO,eAAkB,iBAAiB;EACxC,QAAQ,OAAO,UAAU;EACzB,SAAS,OAAO;EACjB,CAAC;AAIJ,KAAI,CAAC,OAAO,EAAE;AAEZ,MAAI,OAAO,CACT,OAAM,IAAI,MACR,gIAAgI,OAAO,SACxI;EAGH,MAAM,cAAc,OAAO,QAAQ;AACnC,MAAI,CAAC,YACH,OAAM,IAAI,MAAM,sBAAsB;AAExC,SAAO,YAAY;;CAKrB,MAAM,gBAAgB,OAAO,QAAQ,KAAK,SAAS;EACjD,OAAO,IAAI;EACX,OAAO,IAAI;EACX,GAAI,IAAI,eAAe,EAAE,MAAM,IAAI,aAAa;EAChD,GAAI,IAAI,aAAa,UAAa,EAAE,UAAU,IAAI,UAAU;EAC7D,EAAE;AAOH,QAAO,aALQ,MAAM,EAAE,OAAO;EAC5B,SAAS,OAAO,UAAU;EAC1B,SAAS;EACV,CAAC,CAEyB"}
1
+ {"version":3,"file":"select.mjs","names":[],"sources":["../../../src/ui/components/select.ts"],"sourcesContent":["import * as p from \"@clack/prompts\";\nimport { isMCP } from \"../../shared/runtime-mode\";\nimport { handleCancel } from \"../utils/cancel\";\nimport { isTTY } from \"../utils/tty\";\n\n/**\n * Option for select menus\n */\nexport interface SelectOption<T = string> {\n /** Display name */\n name: string;\n /** Optional description shown below name */\n description?: string;\n /** Value returned when selected */\n value: T;\n /** Disable this option */\n disabled?: boolean;\n}\n\n/**\n * Configuration for select component\n */\nexport interface SelectConfig<T> {\n /** Options to choose from */\n options: SelectOption<T>[];\n /** Prompt message */\n prompt?: string;\n}\n\n/**\n * Show a selection menu and wait for user choice\n *\n * @example\n * ```typescript\n * const color = await select({\n * options: [\n * { name: \"Red\", value: \"red\" },\n * { name: \"Blue\", value: \"blue\", description: \"Ocean color\" },\n * ],\n * prompt: \"Pick a color\"\n * });\n * ```\n */\nexport async function select<T = string>(config: SelectConfig<T>): Promise<T> {\n // Non-TTY fallback: auto-select first option or throw in MCP mode\n if (!isTTY()) {\n // In MCP mode, interactive selection is not allowed\n if (isMCP()) {\n throw new Error(\n `Interactive selection not available in MCP mode. All parameters must be defined in the tool's inputSchema. Selection prompt: ${config.prompt}`,\n );\n }\n\n const firstOption = config.options[0];\n if (!firstOption) {\n throw new Error(\"No options provided\");\n }\n return firstOption.value;\n }\n\n // Map to @clack/prompts Option format\n // Using type assertion due to complex Option<T> conditional types\n const mappedOptions = config.options.map((opt) => ({\n label: opt.name,\n value: opt.value as unknown,\n ...(opt.description && { hint: opt.description }),\n ...(opt.disabled !== undefined && { disabled: opt.disabled }),\n }));\n\n const result = await p.select({\n message: config.prompt ?? \"Select an option\",\n options: mappedOptions,\n });\n\n return handleCancel(result) as T;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AA2CA,eAAsB,OAAmB,QAAqC;AAE5E,KAAI,CAAC,OAAO,EAAE;AAEZ,MAAI,OAAO,CACT,OAAM,IAAI,MACR,gIAAgI,OAAO,SACxI;EAGH,MAAM,cAAc,OAAO,QAAQ;AACnC,MAAI,CAAC,YACH,OAAM,IAAI,MAAM,sBAAsB;AAExC,SAAO,YAAY;;CAKrB,MAAM,gBAAgB,OAAO,QAAQ,KAAK,SAAS;EACjD,OAAO,IAAI;EACX,OAAO,IAAI;EACX,GAAI,IAAI,eAAe,EAAE,MAAM,IAAI,aAAa;EAChD,GAAI,IAAI,aAAa,UAAa,EAAE,UAAU,IAAI,UAAU;EAC7D,EAAE;AAOH,QAAO,aALQ,MAAM,EAAE,OAAO;EAC5B,SAAS,OAAO,UAAU;EAC1B,SAAS;EACV,CAAC,CAEyB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kly",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "",
5
5
  "keywords": [],
6
6
  "homepage": "https://github.com/xinyao27/kly#readme",
@@ -52,7 +52,6 @@
52
52
  "@ai-sdk/google": "^3.0.2",
53
53
  "@ai-sdk/mistral": "^3.0.2",
54
54
  "@ai-sdk/openai": "^3.0.2",
55
- "@anthropic-ai/sandbox-runtime": "^0.0.23",
56
55
  "@clack/prompts": "1.0.0-alpha.9",
57
56
  "@modelcontextprotocol/sdk": "^1.25.1",
58
57
  "ai": "^6.0.6",
@@ -1,3 +0,0 @@
1
- import { A as writeRegistry, C as removeShim, D as listCommands, E as getRegistryPath, M as isCommandAvailable, O as readRegistry, S as getShimDir, T as getCommand, _ as autoRegisterBins, b as createShim, g as setupPath, h as isKlyBinInPath, j as detectBins, k as removeCommand, m as getShellConfigFile, p as checkPathSetup, v as shouldReregisterLocal, w as addCommand, x as generateShimPath, y as unregisterCommand } from "./kly.mjs";
2
-
3
- export { autoRegisterBins };
@@ -1,3 +0,0 @@
1
- import { n as formatPermissionsSummary, t as buildSandboxConfig } from "./kly.mjs";
2
-
3
- export { buildSandboxConfig, formatPermissionsSummary };
@@ -1,3 +0,0 @@
1
- import { f as launchSandbox } from "./kly.mjs";
2
-
3
- export { launchSandbox };
@@ -1,3 +0,0 @@
1
- import { a as getAppIdentifier, c as listPermissions, d as savePermissions, i as clearAllPermissions, l as loadPermissions, o as getAppName, r as checkApiKeyPermission, s as getAppSandboxConfig, u as revokePermission } from "./kly.mjs";
2
-
3
- export { checkApiKeyPermission, getAppIdentifier, getAppSandboxConfig };
@@ -1,31 +0,0 @@
1
- import { N as log } from "./kly.mjs";
2
-
3
- //#region src/remote/permissions-extractor.ts
4
- /**
5
- * Extract declared permissions from a remote app
6
- *
7
- * This function safely loads the app definition to read its declared permissions
8
- * WITHOUT executing the actual tool logic.
9
- *
10
- * @param entryPath - Absolute path to the app entry point
11
- * @returns Declared permissions or undefined if not specified
12
- */
13
- async function extractAppPermissions(entryPath) {
14
- try {
15
- const prevProgrammatic = process.env.KLY_PROGRAMMATIC;
16
- process.env.KLY_PROGRAMMATIC = "true";
17
- const appModule = await import(entryPath);
18
- if (prevProgrammatic === void 0) delete process.env.KLY_PROGRAMMATIC;
19
- else process.env.KLY_PROGRAMMATIC = prevProgrammatic;
20
- if (appModule.default?.definition) return appModule.default.definition.permissions;
21
- if (appModule.definition) return appModule.definition.permissions;
22
- return;
23
- } catch (error) {
24
- log.warn(`⚠️ Could not extract permissions from app (${error instanceof Error ? error.message : "unknown error"})`);
25
- return;
26
- }
27
- }
28
-
29
- //#endregion
30
- export { extractAppPermissions };
31
- //# sourceMappingURL=permissions-extractor-B3XI8JLv.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"permissions-extractor-B3XI8JLv.mjs","names":[],"sources":["../../src/remote/permissions-extractor.ts"],"sourcesContent":["import type { AppPermissions } from \"../types\";\nimport { log } from \"../ui\";\n\n/**\n * Extract declared permissions from a remote app\n *\n * This function safely loads the app definition to read its declared permissions\n * WITHOUT executing the actual tool logic.\n *\n * @param entryPath - Absolute path to the app entry point\n * @returns Declared permissions or undefined if not specified\n */\nexport async function extractAppPermissions(\n entryPath: string,\n): Promise<AppPermissions | undefined> {\n try {\n // Temporarily mark as programmatic mode to avoid triggering CLI logic\n const prevProgrammatic = process.env.KLY_PROGRAMMATIC;\n process.env.KLY_PROGRAMMATIC = \"true\";\n\n // Dynamic import to load the app module\n const appModule = await import(entryPath);\n\n // Restore environment\n if (prevProgrammatic === undefined) {\n delete process.env.KLY_PROGRAMMATIC;\n } else {\n process.env.KLY_PROGRAMMATIC = prevProgrammatic;\n }\n\n // The app module should export a KlyApp instance\n // which has a definition.permissions field\n if (appModule.default?.definition) {\n return appModule.default.definition.permissions;\n }\n\n // Some apps might export the app directly\n if (appModule.definition) {\n return appModule.definition.permissions;\n }\n\n return undefined;\n } catch (error) {\n // If we can't extract permissions, return undefined\n // The calling code will fall back to asking for all permissions\n log.warn(\n `⚠️ Could not extract permissions from app (${error instanceof Error ? error.message : \"unknown error\"})`,\n );\n return undefined;\n }\n}\n"],"mappings":";;;;;;;;;;;;AAYA,eAAsB,sBACpB,WACqC;AACrC,KAAI;EAEF,MAAM,mBAAmB,QAAQ,IAAI;AACrC,UAAQ,IAAI,mBAAmB;EAG/B,MAAM,YAAY,MAAM,OAAO;AAG/B,MAAI,qBAAqB,OACvB,QAAO,QAAQ,IAAI;MAEnB,SAAQ,IAAI,mBAAmB;AAKjC,MAAI,UAAU,SAAS,WACrB,QAAO,UAAU,QAAQ,WAAW;AAItC,MAAI,UAAU,WACZ,QAAO,UAAU,WAAW;AAG9B;UACO,OAAO;AAGd,MAAI,KACF,+CAA+C,iBAAiB,QAAQ,MAAM,UAAU,gBAAgB,GACzG;AACD"}
@@ -1,121 +0,0 @@
1
- import { PATHS } from "../shared/constants.mjs";
2
- import { getLocalRef, getRemoteRef, isTrustAll } from "../shared/runtime-mode.mjs";
3
- import { isTTY } from "../ui/utils/tty.mjs";
4
- import { error, output } from "../ui/components/log.mjs";
5
- import { select } from "../ui/components/select.mjs";
6
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
7
- import { homedir } from "node:os";
8
- import { join } from "node:path";
9
-
10
- //#region src/permissions/index.ts
11
- const CONFIG_DIR = join(homedir(), PATHS.CONFIG_DIR);
12
- const PERMISSIONS_FILE = join(CONFIG_DIR, PATHS.PERMISSIONS_FILE);
13
- /**
14
- * Get app identifier from script path
15
- */
16
- function getAppIdentifier() {
17
- const localRef = getLocalRef();
18
- if (localRef) return localRef;
19
- const remoteRef = getRemoteRef();
20
- if (remoteRef) return remoteRef;
21
- const scriptPath = process.argv[1] ?? "";
22
- if (scriptPath.startsWith("/") || scriptPath.startsWith("C:\\")) return `local:${scriptPath}`;
23
- return scriptPath || "unknown";
24
- }
25
- /**
26
- * Get friendly app name for display
27
- */
28
- function getAppName(appId) {
29
- if (appId.startsWith("local:")) {
30
- const path = appId.slice(6);
31
- const parts = path.split("/");
32
- return parts[parts.length - 1] || path;
33
- }
34
- if (appId.startsWith("github.com/")) return appId.split("/").slice(1, 3).join("/");
35
- return appId;
36
- }
37
- /**
38
- * Ensure permissions config directory exists
39
- */
40
- function ensurePermissionsDir() {
41
- if (!existsSync(CONFIG_DIR)) mkdirSync(CONFIG_DIR, { recursive: true });
42
- }
43
- /**
44
- * Load permissions configuration
45
- */
46
- function loadPermissions() {
47
- ensurePermissionsDir();
48
- if (!existsSync(PERMISSIONS_FILE)) return { trustedApps: {} };
49
- try {
50
- const content = readFileSync(PERMISSIONS_FILE, "utf-8");
51
- return JSON.parse(content);
52
- } catch (err) {
53
- error(`Failed to parse permissions file: ${err}`);
54
- return { trustedApps: {} };
55
- }
56
- }
57
- /**
58
- * Save permissions configuration
59
- */
60
- function savePermissions(config) {
61
- ensurePermissionsDir();
62
- writeFileSync(PERMISSIONS_FILE, JSON.stringify(config, null, 2), "utf-8");
63
- }
64
- /**
65
- * Request permission from user with interactive prompt
66
- */
67
- async function requestPermission(appId, appName) {
68
- if (!isTTY()) {
69
- error(`Permission required: App "${appName}" (${appId}) wants to access your API keys.`);
70
- error("Set KLY_TRUST_ALL=true environment variable to grant access in non-interactive mode.");
71
- return false;
72
- }
73
- output(`App "${appName}" is requesting access to your API keys.`);
74
- output(`Source: ${appId}`);
75
- output("This will allow the app to use your configured LLM models.");
76
- const choice = await select({
77
- prompt: "Do you want to allow this?",
78
- options: [
79
- {
80
- name: "Allow once",
81
- value: "once",
82
- description: "Allow for this session only"
83
- },
84
- {
85
- name: "Always allow",
86
- value: "always",
87
- description: "Remember this choice for future runs"
88
- },
89
- {
90
- name: "Cancel",
91
- value: "cancel",
92
- description: "Cancel and exit"
93
- }
94
- ]
95
- });
96
- if (choice === "cancel") return false;
97
- if (choice === "always") {
98
- const config = loadPermissions();
99
- config.trustedApps[appId] = {
100
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
101
- choice: "always"
102
- };
103
- savePermissions(config);
104
- return true;
105
- }
106
- return true;
107
- }
108
- /**
109
- * Check if an app has permission to access API keys
110
- * If not, prompt user for permission (in interactive mode)
111
- */
112
- async function checkApiKeyPermission(appId) {
113
- if (isTrustAll()) return true;
114
- const record = loadPermissions().trustedApps[appId];
115
- if (record && record.choice === "always") return true;
116
- return await requestPermission(appId, getAppName(appId));
117
- }
118
-
119
- //#endregion
120
- export { checkApiKeyPermission, getAppIdentifier };
121
- //# sourceMappingURL=index.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../src/permissions/index.ts"],"sourcesContent":["import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { SandboxRuntimeConfig } from \"@anthropic-ai/sandbox-runtime\";\nimport { PATHS } from \"../shared/constants\";\nimport { getLocalRef, getRemoteRef, isTrustAll } from \"../shared/runtime-mode\";\nimport { error, log, output, select } from \"../ui\";\nimport { isTTY } from \"../ui/utils/tty\";\n\nconst CONFIG_DIR = join(homedir(), PATHS.CONFIG_DIR);\nconst PERMISSIONS_FILE = join(CONFIG_DIR, PATHS.PERMISSIONS_FILE);\n\n/**\n * Permission record for an app\n * Only \"always\" choices are stored\n */\ninterface PermissionRecord {\n /** When the permission was granted */\n timestamp: string;\n /** User's choice: always \"always\" (only stored choice) */\n choice: \"always\";\n /** Sandbox configuration (optional, for sandboxed execution) */\n sandboxConfig?: SandboxRuntimeConfig;\n}\n\n/**\n * Permissions configuration\n */\ninterface PermissionsConfig {\n /** Trusted apps with their permissions */\n trustedApps: Record<string, PermissionRecord>;\n}\n\n/**\n * Get app identifier from script path\n */\nexport function getAppIdentifier(): string {\n // Check for explicit local file reference (set by kly run command)\n const localRef = getLocalRef();\n if (localRef) {\n return localRef;\n }\n\n // Remote app (from environment variable set by remote loader)\n const remoteRef = getRemoteRef();\n if (remoteRef) {\n return remoteRef;\n }\n\n // Fallback to script path for direct execution\n const scriptPath = process.argv[1] ?? \"\";\n if (scriptPath.startsWith(\"/\") || scriptPath.startsWith(\"C:\\\\\")) {\n return `local:${scriptPath}`;\n }\n\n // Default to script path\n return scriptPath || \"unknown\";\n}\n\n/**\n * Get friendly app name for display\n */\nexport function getAppName(appId: string): string {\n if (appId.startsWith(\"local:\")) {\n const path = appId.slice(6);\n const parts = path.split(\"/\");\n return parts[parts.length - 1] || path;\n }\n\n if (appId.startsWith(\"github.com/\")) {\n const parts = appId.split(\"/\");\n return parts.slice(1, 3).join(\"/\");\n }\n\n return appId;\n}\n\n/**\n * Ensure permissions config directory exists\n */\nfunction ensurePermissionsDir(): void {\n if (!existsSync(CONFIG_DIR)) {\n mkdirSync(CONFIG_DIR, { recursive: true });\n }\n}\n\n/**\n * Load permissions configuration\n */\nexport function loadPermissions(): PermissionsConfig {\n ensurePermissionsDir();\n\n if (!existsSync(PERMISSIONS_FILE)) {\n return { trustedApps: {} };\n }\n\n try {\n const content = readFileSync(PERMISSIONS_FILE, \"utf-8\");\n return JSON.parse(content);\n } catch (err) {\n error(`Failed to parse permissions file: ${err}`);\n return { trustedApps: {} };\n }\n}\n\n/**\n * Save permissions configuration\n */\nexport function savePermissions(config: PermissionsConfig): void {\n ensurePermissionsDir();\n writeFileSync(PERMISSIONS_FILE, JSON.stringify(config, null, 2), \"utf-8\");\n}\n\n/**\n * Request permission from user with interactive prompt\n */\nasync function requestPermission(appId: string, appName: string): Promise<boolean> {\n // Check if running in TTY mode\n if (!isTTY()) {\n error(`Permission required: App \"${appName}\" (${appId}) wants to access your API keys.`);\n error(\"Set KLY_TRUST_ALL=true environment variable to grant access in non-interactive mode.\");\n return false;\n }\n\n output(`App \"${appName}\" is requesting access to your API keys.`);\n output(`Source: ${appId}`);\n output(\"This will allow the app to use your configured LLM models.\");\n\n const choice = await select({\n prompt: \"Do you want to allow this?\",\n options: [\n {\n name: \"Allow once\",\n value: \"once\",\n description: \"Allow for this session only\",\n },\n {\n name: \"Always allow\",\n value: \"always\",\n description: \"Remember this choice for future runs\",\n },\n { name: \"Cancel\", value: \"cancel\", description: \"Cancel and exit\" },\n ],\n });\n\n // Cancel - don't save, just reject\n if (choice === \"cancel\") {\n return false;\n }\n\n // Always - save to config\n if (choice === \"always\") {\n const config = loadPermissions();\n config.trustedApps[appId] = {\n timestamp: new Date().toISOString(),\n choice: \"always\",\n };\n savePermissions(config);\n return true;\n }\n\n // Once - don't save, just allow for this run\n return true;\n}\n\n/**\n * Check if an app has permission to access API keys\n * If not, prompt user for permission (in interactive mode)\n */\nexport async function checkApiKeyPermission(appId: string): Promise<boolean> {\n // Allow bypass via environment variable (for CI/automation)\n if (isTrustAll()) {\n return true;\n }\n\n // Check stored permissions\n const config = loadPermissions();\n const record = config.trustedApps[appId];\n\n // If record exists, it's always \"always allow\" (we only store grants)\n if (record && record.choice === \"always\") {\n return true;\n }\n\n // No stored permission - need to request\n const appName = getAppName(appId);\n return await requestPermission(appId, appName);\n}\n\n/**\n * Revoke permission for an app\n */\nexport function revokePermission(appId: string): void {\n const config = loadPermissions();\n delete config.trustedApps[appId];\n savePermissions(config);\n}\n\n/**\n * List all granted permissions\n * Only \"always allow\" permissions are stored\n */\nexport function listPermissions(): Array<{\n appId: string;\n appName: string;\n timestamp: string;\n choice: string;\n}> {\n const config = loadPermissions();\n\n return Object.entries(config.trustedApps).map(([appId, record]) => ({\n appId,\n appName: getAppName(appId),\n timestamp: record.timestamp,\n choice: record.choice,\n }));\n}\n\n/**\n * Request sandbox configuration from user interactively\n * Returns SandboxRuntimeConfig directly (no conversion needed)\n */\nasync function requestSandboxConfig(\n appId: string,\n appName: string,\n): Promise<SandboxRuntimeConfig | null> {\n if (!isTTY()) {\n error(`Sandbox permission required for: \"${appName}\" (${appId})`);\n error(\n \"Set KLY_TRUST_ALL=true environment variable to run without sandboxing in non-interactive mode.\",\n );\n return null;\n }\n\n const homeDir = homedir();\n const currentDir = process.cwd();\n\n output(`🔐 Sandbox Permission Request from: ${appName}`);\n\n // Ask for filesystem read permissions\n output(\"📂 Filesystem Read Access:\");\n const fsReadChoice = await select({\n prompt: \"Which files should be denied for reading?\",\n options: [\n {\n name: \"Sensitive only\",\n value: \"sensitive\",\n description: \"Deny access to ~/.kly, ~/.ssh, ~/.aws, etc.\",\n },\n {\n name: \"All home directory\",\n value: \"all-home\",\n description: \"Deny access to entire home directory\",\n },\n {\n name: \"None (allow all)\",\n value: \"none\",\n description: \"No read restrictions (except ~/.kly)\",\n },\n ],\n });\n\n // Always deny reading sensitive directories (hardcoded for security)\n let denyRead: string[] = [join(homeDir, \".kly\")];\n\n if (fsReadChoice === \"sensitive\") {\n denyRead = [\n join(homeDir, \".kly\"),\n join(homeDir, \".ssh\"),\n join(homeDir, \".aws\"),\n join(homeDir, \".gnupg\"),\n ];\n } else if (fsReadChoice === \"all-home\") {\n denyRead = [homeDir];\n }\n // Note: Even if user chooses \"none\", .kly is still protected\n\n // Ask for filesystem write permissions\n output(\"📝 Filesystem Write Access:\");\n const fsWriteChoice = await select({\n prompt: \"Which directories should be allowed for writing?\",\n options: [\n {\n name: \"None\",\n value: \"none\",\n description: \"No write access\",\n },\n {\n name: \"Current directory only\",\n value: \"current\",\n description: `Allow write to ${currentDir}`,\n },\n {\n name: \"Temporary directory\",\n value: \"temp\",\n description: \"Allow write to system temp directory\",\n },\n ],\n });\n\n let allowWrite: string[] = [];\n if (fsWriteChoice === \"current\") {\n allowWrite = [currentDir];\n } else if (fsWriteChoice === \"temp\") {\n const tmpdir = process.env.TMPDIR || process.env.TEMP || \"/tmp\";\n allowWrite = [tmpdir];\n }\n\n // Always deny writing to sensitive directories (hardcoded for security)\n const denyWrite = [\n join(homeDir, \".kly\"), // KLY config and permissions\n join(homeDir, \".ssh\"), // SSH keys\n join(homeDir, \".aws\"), // AWS credentials\n join(homeDir, \".gnupg\"), // GPG keys\n ];\n\n // Ask for network permissions\n output(\"🌐 Network Access:\");\n const networkChoice = await select({\n prompt: \"Which network access should be allowed?\",\n options: [\n {\n name: \"None\",\n value: \"none\",\n description: \"No network access\",\n },\n {\n name: \"LLM APIs only\",\n value: \"llm-apis\",\n description: \"OpenAI, Anthropic, Google AI\",\n },\n {\n name: \"Common APIs\",\n value: \"common\",\n description: \"LLM + GitHub, npm, etc.\",\n },\n {\n name: \"All domains\",\n value: \"all\",\n description: \"Allow all network access\",\n },\n ],\n });\n\n let allowedDomains: string[] = [];\n if (networkChoice === \"llm-apis\") {\n allowedDomains = [\"api.openai.com\", \"*.anthropic.com\", \"generativelanguage.googleapis.com\"];\n } else if (networkChoice === \"common\") {\n allowedDomains = [\n \"api.openai.com\",\n \"*.anthropic.com\",\n \"generativelanguage.googleapis.com\",\n \"*.github.com\",\n \"registry.npmjs.org\",\n ];\n } else if (networkChoice === \"all\") {\n allowedDomains = [\"*\"];\n }\n\n // Ask how long to remember this choice\n const duration = await select({\n prompt: \"How long should these permissions last?\",\n options: [\n {\n name: \"One time only\",\n value: \"once\",\n description: \"Ask again next time\",\n },\n {\n name: \"Always allow\",\n value: \"always\",\n description: \"Remember for this app\",\n },\n {\n name: \"Cancel\",\n value: \"cancel\",\n description: \"Cancel and exit\",\n },\n ],\n });\n\n // Cancel - don't save, just reject\n if (duration === \"cancel\") {\n return null;\n }\n\n // Construct SandboxRuntimeConfig directly\n const sandboxConfig: SandboxRuntimeConfig = {\n network: {\n allowedDomains,\n deniedDomains: [],\n },\n filesystem: {\n denyRead,\n allowWrite,\n denyWrite,\n },\n };\n\n // Save permission record only if \"always\"\n if (duration === \"always\") {\n const config = loadPermissions();\n config.trustedApps[appId] = {\n sandboxConfig,\n timestamp: new Date().toISOString(),\n choice: \"always\",\n };\n savePermissions(config);\n }\n\n log.success(\"Sandbox permissions granted!\");\n return sandboxConfig;\n}\n\n/**\n * Get sandbox configuration for an app\n * Returns SandboxRuntimeConfig directly (no conversion needed)\n *\n * @param appId - App identifier\n * @returns SandboxRuntimeConfig or null if denied\n */\nexport async function getAppSandboxConfig(appId: string): Promise<SandboxRuntimeConfig | null> {\n const homeDir = homedir();\n\n // Check for trust-all bypass (for automation)\n if (isTrustAll()) {\n // Even in trust-all mode, protect sensitive directories\n return {\n network: { allowedDomains: [\"*\"], deniedDomains: [] },\n filesystem: {\n denyRead: [join(homeDir, \".kly\")], // ALWAYS deny reading KLY config\n allowWrite: [\"*\"],\n denyWrite: [\n join(homeDir, \".kly\"), // KLY config and permissions\n join(homeDir, \".ssh\"), // SSH keys\n join(homeDir, \".aws\"), // AWS credentials\n join(homeDir, \".gnupg\"), // GPG keys\n ],\n },\n };\n }\n\n const config = loadPermissions();\n const record = config.trustedApps[appId];\n\n // If permission already granted as \"always\", return cached config\n if (record?.choice === \"always\" && record.sandboxConfig) {\n return record.sandboxConfig;\n }\n\n // Request new sandbox permissions\n const appName = getAppName(appId);\n return await requestSandboxConfig(appId, appName);\n}\n\n/**\n * Clear all permissions\n */\nexport function clearAllPermissions(): void {\n savePermissions({ trustedApps: {} });\n}\n"],"mappings":";;;;;;;;;;AASA,MAAM,aAAa,KAAK,SAAS,EAAE,MAAM,WAAW;AACpD,MAAM,mBAAmB,KAAK,YAAY,MAAM,iBAAiB;;;;AA0BjE,SAAgB,mBAA2B;CAEzC,MAAM,WAAW,aAAa;AAC9B,KAAI,SACF,QAAO;CAIT,MAAM,YAAY,cAAc;AAChC,KAAI,UACF,QAAO;CAIT,MAAM,aAAa,QAAQ,KAAK,MAAM;AACtC,KAAI,WAAW,WAAW,IAAI,IAAI,WAAW,WAAW,OAAO,CAC7D,QAAO,SAAS;AAIlB,QAAO,cAAc;;;;;AAMvB,SAAgB,WAAW,OAAuB;AAChD,KAAI,MAAM,WAAW,SAAS,EAAE;EAC9B,MAAM,OAAO,MAAM,MAAM,EAAE;EAC3B,MAAM,QAAQ,KAAK,MAAM,IAAI;AAC7B,SAAO,MAAM,MAAM,SAAS,MAAM;;AAGpC,KAAI,MAAM,WAAW,cAAc,CAEjC,QADc,MAAM,MAAM,IAAI,CACjB,MAAM,GAAG,EAAE,CAAC,KAAK,IAAI;AAGpC,QAAO;;;;;AAMT,SAAS,uBAA6B;AACpC,KAAI,CAAC,WAAW,WAAW,CACzB,WAAU,YAAY,EAAE,WAAW,MAAM,CAAC;;;;;AAO9C,SAAgB,kBAAqC;AACnD,uBAAsB;AAEtB,KAAI,CAAC,WAAW,iBAAiB,CAC/B,QAAO,EAAE,aAAa,EAAE,EAAE;AAG5B,KAAI;EACF,MAAM,UAAU,aAAa,kBAAkB,QAAQ;AACvD,SAAO,KAAK,MAAM,QAAQ;UACnB,KAAK;AACZ,QAAM,qCAAqC,MAAM;AACjD,SAAO,EAAE,aAAa,EAAE,EAAE;;;;;;AAO9B,SAAgB,gBAAgB,QAAiC;AAC/D,uBAAsB;AACtB,eAAc,kBAAkB,KAAK,UAAU,QAAQ,MAAM,EAAE,EAAE,QAAQ;;;;;AAM3E,eAAe,kBAAkB,OAAe,SAAmC;AAEjF,KAAI,CAAC,OAAO,EAAE;AACZ,QAAM,6BAA6B,QAAQ,KAAK,MAAM,kCAAkC;AACxF,QAAM,uFAAuF;AAC7F,SAAO;;AAGT,QAAO,QAAQ,QAAQ,0CAA0C;AACjE,QAAO,WAAW,QAAQ;AAC1B,QAAO,6DAA6D;CAEpE,MAAM,SAAS,MAAM,OAAO;EAC1B,QAAQ;EACR,SAAS;GACP;IACE,MAAM;IACN,OAAO;IACP,aAAa;IACd;GACD;IACE,MAAM;IACN,OAAO;IACP,aAAa;IACd;GACD;IAAE,MAAM;IAAU,OAAO;IAAU,aAAa;IAAmB;GACpE;EACF,CAAC;AAGF,KAAI,WAAW,SACb,QAAO;AAIT,KAAI,WAAW,UAAU;EACvB,MAAM,SAAS,iBAAiB;AAChC,SAAO,YAAY,SAAS;GAC1B,4BAAW,IAAI,MAAM,EAAC,aAAa;GACnC,QAAQ;GACT;AACD,kBAAgB,OAAO;AACvB,SAAO;;AAIT,QAAO;;;;;;AAOT,eAAsB,sBAAsB,OAAiC;AAE3E,KAAI,YAAY,CACd,QAAO;CAKT,MAAM,SADS,iBAAiB,CACV,YAAY;AAGlC,KAAI,UAAU,OAAO,WAAW,SAC9B,QAAO;AAKT,QAAO,MAAM,kBAAkB,OADf,WAAW,MAAM,CACa"}
@@ -1,17 +0,0 @@
1
- #!/usr/bin/env bun
2
- //#region src/sandbox/executor.d.ts
3
- /**
4
- * Sandbox Executor - Entry point for sandboxed child process
5
- * This file runs inside the sandbox and:
6
- * 1. Receives initialization message from host via IPC
7
- * 2. Loads and executes the user's script
8
- * 3. Provides sandboxed context to the script
9
- * 4. Sends execution results back to host
10
- */
11
- /**
12
- * Send IPC request to host and wait for response
13
- */
14
- declare function sendIPCRequest<T>(type: string, payload: unknown): Promise<T>;
15
- //#endregion
16
- export { sendIPCRequest };
17
- //# sourceMappingURL=bundled-executor.d.mts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"bundled-executor.d.mts","names":[],"sources":["../../src/sandbox/executor.ts"],"sourcesContent":[],"mappings":";;;AAqEA;;;;;;;;;;iBAAgB,mDAAmD,QAAQ"}
@@ -1,235 +0,0 @@
1
- #!/usr/bin/env bun
2
- import * as p from "@clack/prompts";
3
- import * as colors from "xycolors";
4
-
5
- //#region src/shared/constants.ts
6
- /**
7
- * Timeout values in milliseconds
8
- */
9
- const TIMEOUTS = {
10
- IPC_REQUEST: 3e4,
11
- IPC_LONG_REQUEST: 6e4
12
- };
13
-
14
- //#endregion
15
- //#region src/shared/errors.ts
16
- /**
17
- * Used for user cancellation - not an error, just a graceful exit
18
- */
19
- var ExitWarning = class extends Error {
20
- constructor(message = "Operation cancelled") {
21
- super(message);
22
- this.name = "ExitWarning";
23
- }
24
- };
25
-
26
- //#endregion
27
- //#region src/shared/ipc-protocol.ts
28
- function isIPCResponse(msg) {
29
- return typeof msg === "object" && msg !== null && "type" in msg && msg.type === "response" && "id" in msg && typeof msg.id === "string";
30
- }
31
- function isSandboxInitMessage(msg) {
32
- return typeof msg === "object" && msg !== null && "type" in msg && msg.type === "init";
33
- }
34
-
35
- //#endregion
36
- //#region src/ui/utils/colors.ts
37
- /**
38
- * Color mapping for formatText function
39
- */
40
- const colorMap = {
41
- red: colors.red,
42
- green: colors.green,
43
- yellow: colors.yellow,
44
- blue: colors.blue,
45
- magenta: colors.magenta,
46
- cyan: colors.cyan,
47
- white: colors.white,
48
- gray: colors.gray
49
- };
50
-
51
- //#endregion
52
- //#region src/ui/components/log.ts
53
- /**
54
- * Display an error message with optional suggestions
55
- *
56
- * @param message - Error message
57
- * @param suggestions - Optional suggestions for fixing the error
58
- *
59
- * @example
60
- * ```typescript
61
- * error("Failed to load config", [
62
- * "Check if config.json exists",
63
- * "Verify JSON syntax"
64
- * ]);
65
- * ```
66
- */
67
- function error(message, suggestions) {
68
- p.log.error(message);
69
- if (suggestions?.length) {
70
- p.log.message("");
71
- p.log.message(colors.dim("Suggestions:"));
72
- for (const suggestion of suggestions) p.log.message(` ${colors.dim("•")} ${suggestion}`);
73
- }
74
- }
75
-
76
- //#endregion
77
- //#region src/sandbox/sandboxed-context.ts
78
- /**
79
- * Create sandboxed models context that communicates with host via IPC
80
- * This context is injected into user tools running in the sandbox
81
- * All API key access is controlled by the host process
82
- */
83
- function createSandboxedModelsContext() {
84
- return {
85
- list() {
86
- throw new Error("Synchronous list() not supported in sandbox. Use async methods or move this logic to host.");
87
- },
88
- getCurrent() {
89
- throw new Error("Synchronous getCurrent() not supported in sandbox. Use async methods or move this logic to host.");
90
- },
91
- get(_name) {
92
- throw new Error("Synchronous get() not supported in sandbox. Use async methods or move this logic to host.");
93
- },
94
- async getConfigAsync(name) {
95
- try {
96
- const response = await sendIPCRequest("getModelConfig", { name });
97
- if (!response) return null;
98
- return {
99
- provider: response.provider,
100
- model: response.model,
101
- apiKey: response.apiKey,
102
- baseURL: response.baseURL
103
- };
104
- } catch (error$1) {
105
- const message = error$1 instanceof Error ? error$1.message : "Failed to get model config";
106
- throw new Error(`Permission denied: ${message}`);
107
- }
108
- }
109
- };
110
- }
111
-
112
- //#endregion
113
- //#region src/sandbox/executor.ts
114
- /**
115
- * Sandbox Executor - Entry point for sandboxed child process
116
- * This file runs inside the sandbox and:
117
- * 1. Receives initialization message from host via IPC
118
- * 2. Loads and executes the user's script
119
- * 3. Provides sandboxed context to the script
120
- * 4. Sends execution results back to host
121
- */
122
- /**
123
- * Global state for the sandbox
124
- */
125
- let initMessage = null;
126
- const pendingIPCResponses = /* @__PURE__ */ new Map();
127
- /**
128
- * Set up IPC communication
129
- */
130
- function setupIPC() {
131
- if (!process.send) throw new Error("IPC channel not available");
132
- process.on("message", (message) => {
133
- if (isSandboxInitMessage(message)) {
134
- initMessage = message;
135
- executeUserScript().catch((error$1) => {
136
- sendExecutionComplete(false, void 0, error$1.message);
137
- process.exit(1);
138
- });
139
- return;
140
- }
141
- if (isIPCResponse(message)) {
142
- const pending = pendingIPCResponses.get(message.id);
143
- if (pending) {
144
- pendingIPCResponses.delete(message.id);
145
- pending.resolve(message);
146
- }
147
- return;
148
- }
149
- });
150
- }
151
- /**
152
- * Send IPC request to host and wait for response
153
- */
154
- function sendIPCRequest(type, payload) {
155
- return new Promise((resolve, reject) => {
156
- if (!process.send) {
157
- reject(/* @__PURE__ */ new Error("IPC channel not available"));
158
- return;
159
- }
160
- const id = `${type}-${Date.now()}-${Math.random()}`;
161
- const request = {
162
- type,
163
- id,
164
- payload
165
- };
166
- pendingIPCResponses.set(id, {
167
- resolve: (response) => {
168
- if (response.success) resolve(response.data);
169
- else if (response.cancelled) reject(new ExitWarning(response.error));
170
- else reject(new Error(response.error));
171
- },
172
- reject
173
- });
174
- process.send(request);
175
- setTimeout(() => {
176
- const pending = pendingIPCResponses.get(id);
177
- if (pending) {
178
- pendingIPCResponses.delete(id);
179
- pending.reject(/* @__PURE__ */ new Error("IPC request timeout"));
180
- }
181
- }, TIMEOUTS.IPC_REQUEST);
182
- });
183
- }
184
- /**
185
- * Execute the user's script
186
- */
187
- async function executeUserScript() {
188
- if (!initMessage) throw new Error("No initialization message received");
189
- const { scriptPath, args, invokeDir } = initMessage;
190
- try {
191
- process.argv = [
192
- "bun",
193
- scriptPath,
194
- ...args
195
- ];
196
- process.env.KLY_SANDBOX_MODE = "true";
197
- global.__KLY_SANDBOXED_CONTEXT__ = {
198
- modelsContext: createSandboxedModelsContext(),
199
- invokeDir
200
- };
201
- await import(scriptPath);
202
- sendExecutionComplete(true);
203
- } catch (error$1) {
204
- sendExecutionComplete(false, void 0, error$1 instanceof Error ? error$1.message : String(error$1));
205
- throw error$1;
206
- }
207
- }
208
- /**
209
- * Send execution complete message to host
210
- */
211
- function sendExecutionComplete(success, result, error$1) {
212
- if (!process.send) return;
213
- const message = {
214
- type: "complete",
215
- success,
216
- result,
217
- error: error$1
218
- };
219
- process.send(message);
220
- }
221
- /**
222
- * Main entry point
223
- */
224
- function main() {
225
- if (process.env.KLY_SANDBOX_MODE !== "true") {
226
- error("This script must be run in sandbox mode");
227
- process.exit(1);
228
- }
229
- setupIPC();
230
- }
231
- main();
232
-
233
- //#endregion
234
- export { sendIPCRequest };
235
- //# sourceMappingURL=bundled-executor.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"bundled-executor.mjs","names":["colorMap: Record<AnsiColor, (text: string) => string>","error","initMessage: SandboxInitMessage | null","error","message: ExecutionCompleteMessage"],"sources":["../../src/shared/constants.ts","../../src/shared/errors.ts","../../src/shared/ipc-protocol.ts","../../src/ui/utils/colors.ts","../../src/ui/components/log.ts","../../src/sandbox/sandboxed-context.ts","../../src/sandbox/executor.ts"],"sourcesContent":["/**\n * Centralized constants for the KLY project\n * Prevents magic strings and improves maintainability\n */\n\n/**\n * Environment variable names used throughout the application\n */\nexport const ENV_VARS = {\n SANDBOX_MODE: \"KLY_SANDBOX_MODE\",\n MCP_MODE: \"KLY_MCP_MODE\",\n PROGRAMMATIC: \"KLY_PROGRAMMATIC\",\n TRUST_ALL: \"KLY_TRUST_ALL\",\n LOCAL_REF: \"KLY_LOCAL_REF\",\n REMOTE_REF: \"KLY_REMOTE_REF\",\n} as const;\n\n/**\n * File and directory paths used for configuration and caching\n */\nexport const PATHS = {\n CONFIG_DIR: \".kly\",\n META_FILE: \".kly-meta.json\",\n PERMISSIONS_FILE: \"permissions.json\",\n CONFIG_FILE: \"config.json\",\n} as const;\n\n/**\n * Timeout values in milliseconds\n */\nexport const TIMEOUTS = {\n /** Standard IPC request timeout (30 seconds) */\n IPC_REQUEST: 30_000,\n /** Long-running IPC request timeout (60 seconds) */\n IPC_LONG_REQUEST: 60_000,\n} as const;\n\n/**\n * Exit codes\n */\nexport const EXIT_CODES = {\n /** User cancelled operation (similar to SIGINT) */\n CANCELLED: 130,\n} as const;\n\n/**\n * LLM API domains for network permission configuration\n */\nexport const LLM_API_DOMAINS = [\n \"api.openai.com\",\n \"*.anthropic.com\",\n \"generativelanguage.googleapis.com\",\n \"api.deepseek.com\",\n] as const;\n","export class ExitError extends Error {\n constructor(\n message: string,\n public exitCode: number = 1,\n ) {\n super(message);\n this.name = \"ExitError\";\n }\n}\n\n/**\n * Used for user cancellation - not an error, just a graceful exit\n */\nexport class ExitWarning extends Error {\n constructor(message: string = \"Operation cancelled\") {\n super(message);\n this.name = \"ExitWarning\";\n }\n}\n","import type { SandboxRuntimeConfig } from \"@anthropic-ai/sandbox-runtime\";\nimport type { ModelConfig } from \"../types\";\n\n/**\n * Message sent from Host to Sandbox on initialization\n */\nexport interface SandboxInitMessage {\n type: \"init\";\n scriptPath: string;\n args: string[];\n appId: string;\n /** Working directory where `kly run` was invoked */\n invokeDir: string;\n permissions: {\n allowApiKey: boolean;\n sandboxConfig: SandboxRuntimeConfig;\n };\n}\n\n/**\n * Request types sent from Sandbox to Host\n */\nexport type IPCRequest =\n | {\n type: \"getModelConfig\";\n id: string;\n payload: { name?: string };\n }\n | {\n type: \"listModels\";\n id: string;\n payload: Record<string, never>;\n }\n | {\n type: \"log\";\n id: string;\n payload: { level: \"info\" | \"warn\" | \"error\"; message: string };\n }\n | {\n type: \"prompt:input\";\n id: string;\n payload: {\n prompt: string;\n defaultValue?: string;\n placeholder?: string;\n maxLength?: number;\n };\n }\n | {\n type: \"prompt:select\";\n id: string;\n payload: {\n prompt: string;\n options: Array<{\n name: string;\n description?: string;\n value: string;\n }>;\n };\n }\n | {\n type: \"prompt:confirm\";\n id: string;\n payload: {\n message: string;\n defaultValue?: boolean;\n };\n }\n | {\n type: \"prompt:multiselect\";\n id: string;\n payload: {\n prompt: string;\n options: Array<{\n name: string;\n description?: string;\n value: string;\n }>;\n required?: boolean;\n };\n }\n | {\n type: \"prompt:form\";\n id: string;\n payload: {\n title?: string;\n fields: Array<{\n name: string;\n label: string;\n type: \"string\" | \"number\" | \"boolean\" | \"enum\";\n required?: boolean;\n defaultValue?: unknown;\n description?: string;\n enumValues?: string[];\n }>;\n };\n };\n\n/**\n * Response types sent from Host to Sandbox\n */\nexport type IPCResponse<T = unknown> =\n | {\n type: \"response\";\n id: string;\n success: true;\n data: T;\n }\n | {\n type: \"response\";\n id: string;\n success: false;\n error: string;\n /** True if this is a user cancellation, not an error */\n cancelled?: boolean;\n };\n\n/**\n * Model info response (without sensitive data)\n */\nexport interface ModelInfoResponse {\n name: string;\n provider: string;\n model?: string;\n isCurrent: boolean;\n}\n\n/**\n * Model config response (with sensitive data like API keys)\n */\nexport interface ModelConfigResponse extends ModelConfig {\n provider: string;\n model?: string;\n apiKey?: string;\n baseURL?: string;\n}\n\n/**\n * Message sent from Sandbox to Host when execution completes\n */\nexport interface ExecutionCompleteMessage {\n type: \"complete\";\n success: boolean;\n result?: unknown;\n error?: string;\n}\n\n/**\n * Type guard for IPC messages\n */\nexport function isIPCRequest(msg: unknown): msg is IPCRequest {\n return (\n typeof msg === \"object\" &&\n msg !== null &&\n \"type\" in msg &&\n \"id\" in msg &&\n typeof msg.id === \"string\"\n );\n}\n\nexport function isIPCResponse(msg: unknown): msg is IPCResponse {\n return (\n typeof msg === \"object\" &&\n msg !== null &&\n \"type\" in msg &&\n msg.type === \"response\" &&\n \"id\" in msg &&\n typeof msg.id === \"string\"\n );\n}\n\nexport function isSandboxInitMessage(msg: unknown): msg is SandboxInitMessage {\n return typeof msg === \"object\" && msg !== null && \"type\" in msg && msg.type === \"init\";\n}\n\nexport function isExecutionCompleteMessage(msg: unknown): msg is ExecutionCompleteMessage {\n return typeof msg === \"object\" && msg !== null && \"type\" in msg && msg.type === \"complete\";\n}\n","import * as colors from \"xycolors\";\n\n/**\n * Default color theme for UI components (hex values for reference)\n */\nexport const theme = {\n // Brand colors\n primary: \"#3b82f6\", // Blue\n success: \"#10b981\", // Green\n warning: \"#f59e0b\", // Orange\n error: \"#ef4444\", // Red\n info: \"#06b6d4\", // Cyan\n\n // UI colors\n background: \"#161b22\", // Dark gray\n surface: \"#1e293b\", // Lighter gray\n border: \"#30363d\", // Border gray\n text: \"#c9d1d9\", // Light text\n textDim: \"#8b949e\", // Dimmed text\n textBright: \"#ffffff\", // Bright text\n\n // Interactive states\n focused: \"#3b82f6\", // Blue\n selected: \"#3b82f6\", // Blue\n hover: \"#334155\", // Lighter gray\n disabled: \"#6e7681\", // Muted gray\n} as const;\n\nexport type AnsiColor = \"red\" | \"green\" | \"yellow\" | \"blue\" | \"magenta\" | \"cyan\" | \"white\" | \"gray\";\n\n/**\n * Color mapping for formatText function\n */\nconst colorMap: Record<AnsiColor, (text: string) => string> = {\n red: colors.red,\n green: colors.green,\n yellow: colors.yellow,\n blue: colors.blue,\n magenta: colors.magenta,\n cyan: colors.cyan,\n white: colors.white,\n gray: colors.gray,\n};\n\n/**\n * Format text with xycolors\n */\nexport function formatText(\n text: string,\n options?: {\n color?: AnsiColor;\n bold?: boolean;\n dim?: boolean;\n italic?: boolean;\n underline?: boolean;\n },\n): string {\n let result = text;\n\n // Apply color first\n if (options?.color) {\n result = colorMap[options.color](result);\n }\n\n // Apply styles\n if (options?.bold) result = colors.bold(result);\n if (options?.dim) result = colors.dim(result);\n if (options?.italic) result = colors.italic(result);\n if (options?.underline) result = colors.underline(result);\n\n return result;\n}\n\nexport { colors };\n","import * as p from \"@clack/prompts\";\nimport { colors } from \"../utils/colors\";\n\n/**\n * Log utilities for consistent CLI output\n *\n * Uses @clack/prompts log functions for styled output\n */\nexport const log = {\n /**\n * Display an info message\n *\n * @example\n * ```typescript\n * log.info(\"Processing files...\");\n * ```\n */\n info(message: string): void {\n p.log.info(message);\n },\n\n /**\n * Display a success message\n *\n * @example\n * ```typescript\n * log.success(\"Build completed successfully!\");\n * ```\n */\n success(message: string): void {\n p.log.success(message);\n },\n\n /**\n * Display a step message\n *\n * @example\n * ```typescript\n * log.step(\"Installing dependencies\");\n * ```\n */\n step(message: string): void {\n p.log.step(message);\n },\n\n /**\n * Display a warning message\n *\n * @example\n * ```typescript\n * log.warn(\"Config file not found, using defaults\");\n * ```\n */\n warn(message: string): void {\n p.log.warn(message);\n },\n\n /**\n * Display a general message\n *\n * @example\n * ```typescript\n * log.message(\"Welcome to the CLI!\");\n * ```\n */\n message(message: string): void {\n p.log.message(message);\n },\n};\n\n/**\n * Output a result to the console\n *\n * @param result - The result to display (string, object, etc.)\n *\n * @example\n * ```typescript\n * output(\"Hello, world!\");\n * output({ name: \"John\", age: 30 });\n * ```\n */\nexport function output(result: unknown): void {\n if (result === undefined || result === null) {\n return;\n }\n\n if (typeof result === \"string\") {\n p.log.message(result);\n } else {\n p.log.message(JSON.stringify(result, null, 2));\n }\n}\n\n/**\n * Display an error message with optional suggestions\n *\n * @param message - Error message\n * @param suggestions - Optional suggestions for fixing the error\n *\n * @example\n * ```typescript\n * error(\"Failed to load config\", [\n * \"Check if config.json exists\",\n * \"Verify JSON syntax\"\n * ]);\n * ```\n */\nexport function error(message: string, suggestions?: string[]): void {\n p.log.error(message);\n\n if (suggestions?.length) {\n p.log.message(\"\");\n p.log.message(colors.dim(\"Suggestions:\"));\n for (const suggestion of suggestions) {\n p.log.message(` ${colors.dim(\"•\")} ${suggestion}`);\n }\n }\n}\n\n/**\n * Display help text\n *\n * @param content - Help text content\n *\n * @example\n * ```typescript\n * help(\"Usage: myapp <command> [options]\");\n * ```\n */\nexport function help(content: string): void {\n p.log.message(content);\n}\n\n/**\n * Display an intro message at the start of your CLI\n *\n * @example\n * ```typescript\n * intro(\"Welcome to my-cli v1.0.0\");\n * ```\n */\nexport function intro(message?: string): void {\n p.intro(message);\n}\n\n/**\n * Display an outro message at the end of your CLI\n *\n * @example\n * ```typescript\n * outro(\"Thanks for using my-cli!\");\n * ```\n */\nexport function outro(message?: string): void {\n p.outro(message);\n}\n\n/**\n * Display a cancellation message and optionally exit\n *\n * @example\n * ```typescript\n * cancel(\"Operation cancelled\");\n * ```\n */\nexport function cancel(message?: string): void {\n p.cancel(message);\n}\n\n/**\n * Check if a value is a cancel symbol\n *\n * @example\n * ```typescript\n * const result = await text({ message: \"Enter name\" });\n * if (isCancel(result)) {\n * cancel(\"Cancelled\");\n * process.exit(0);\n * }\n * ```\n */\nexport function isCancel(value: unknown): value is symbol {\n return p.isCancel(value);\n}\n\n/**\n * Add a note/box with styled content\n *\n * @example\n * ```typescript\n * note(\"npm install my-package\", \"Next steps\");\n * ```\n */\nexport function note(message: string, title?: string): void {\n p.note(message, title);\n}\n","import type { ModelConfigResponse } from \"../shared/ipc-protocol\";\nimport type { ModelConfig, ModelInfo, ModelsContext } from \"../types\";\nimport { sendIPCRequest } from \"./executor\";\n\n/**\n * Create sandboxed models context that communicates with host via IPC\n * This context is injected into user tools running in the sandbox\n * All API key access is controlled by the host process\n */\nexport function createSandboxedModelsContext(): ModelsContext {\n return {\n /**\n * List available models (no permission required)\n */\n list(): ModelInfo[] {\n throw new Error(\n \"Synchronous list() not supported in sandbox. Use async methods or move this logic to host.\",\n );\n },\n\n /**\n * Get current model info (no permission required)\n */\n getCurrent(): ModelInfo | null {\n throw new Error(\n \"Synchronous getCurrent() not supported in sandbox. Use async methods or move this logic to host.\",\n );\n },\n\n /**\n * Get model info by name (no permission required)\n */\n get(_name: string): ModelInfo | null {\n throw new Error(\n \"Synchronous get() not supported in sandbox. Use async methods or move this logic to host.\",\n );\n },\n\n /**\n * Get model config with API key (requires permission, enforced by host)\n */\n async getConfigAsync(name?: string): Promise<ModelConfig | null> {\n try {\n const response = await sendIPCRequest<ModelConfigResponse | null>(\"getModelConfig\", {\n name,\n });\n\n if (!response) {\n return null;\n }\n\n return {\n provider: response.provider,\n model: response.model,\n apiKey: response.apiKey,\n baseURL: response.baseURL,\n };\n } catch (error) {\n // Re-throw with clear error message\n const message = error instanceof Error ? error.message : \"Failed to get model config\";\n throw new Error(`Permission denied: ${message}`);\n }\n },\n };\n}\n\n/**\n * Get the sandboxed context from global scope\n * This is injected by the executor before loading user scripts\n */\nexport function getSandboxedContext(): {\n modelsContext: ModelsContext;\n invokeDir?: string;\n} {\n const globalWithContext = global as {\n __KLY_SANDBOXED_CONTEXT__?: {\n modelsContext: ModelsContext;\n invokeDir?: string;\n };\n };\n\n if (!globalWithContext.__KLY_SANDBOXED_CONTEXT__) {\n throw new Error(\n \"Sandboxed context not available. This should only be called from within the sandbox.\",\n );\n }\n\n return globalWithContext.__KLY_SANDBOXED_CONTEXT__;\n}\n","#!/usr/bin/env bun\n\n/**\n * Sandbox Executor - Entry point for sandboxed child process\n * This file runs inside the sandbox and:\n * 1. Receives initialization message from host via IPC\n * 2. Loads and executes the user's script\n * 3. Provides sandboxed context to the script\n * 4. Sends execution results back to host\n */\n\nimport { TIMEOUTS } from \"../shared/constants\";\nimport { ExitWarning } from \"../shared/errors\";\nimport type {\n ExecutionCompleteMessage,\n IPCResponse,\n SandboxInitMessage,\n} from \"../shared/ipc-protocol\";\nimport { isIPCResponse, isSandboxInitMessage } from \"../shared/ipc-protocol\";\nimport { error } from \"../ui\";\nimport { createSandboxedModelsContext } from \"./sandboxed-context\";\n\n/**\n * Global state for the sandbox\n */\nlet initMessage: SandboxInitMessage | null = null;\nconst pendingIPCResponses = new Map<\n string,\n {\n resolve: (value: IPCResponse) => void;\n reject: (error: Error) => void;\n }\n>();\n\n/**\n * Set up IPC communication\n */\nfunction setupIPC() {\n if (!process.send) {\n throw new Error(\"IPC channel not available\");\n }\n\n process.on(\"message\", (message: unknown) => {\n // Handle init message\n if (isSandboxInitMessage(message)) {\n initMessage = message;\n // Start execution once we receive init\n executeUserScript().catch((error) => {\n sendExecutionComplete(false, undefined, error.message);\n process.exit(1);\n });\n return;\n }\n\n // Handle IPC responses\n if (isIPCResponse(message)) {\n const pending = pendingIPCResponses.get(message.id);\n if (pending) {\n pendingIPCResponses.delete(message.id);\n pending.resolve(message);\n }\n return;\n }\n });\n}\n\n/**\n * Send IPC request to host and wait for response\n */\nexport function sendIPCRequest<T>(type: string, payload: unknown): Promise<T> {\n return new Promise((resolve, reject) => {\n if (!process.send) {\n reject(new Error(\"IPC channel not available\"));\n return;\n }\n\n const id = `${type}-${Date.now()}-${Math.random()}`;\n const request = { type, id, payload };\n\n // Store pending promise\n pendingIPCResponses.set(id, {\n resolve: (response: IPCResponse) => {\n if (response.success) {\n resolve(response.data as T);\n } else if (response.cancelled) {\n reject(new ExitWarning(response.error));\n } else {\n reject(new Error(response.error));\n }\n },\n reject,\n });\n\n // Send request\n process.send(request);\n\n // Timeout for standard IPC requests\n setTimeout(() => {\n const pending = pendingIPCResponses.get(id);\n if (pending) {\n pendingIPCResponses.delete(id);\n pending.reject(new Error(\"IPC request timeout\"));\n }\n }, TIMEOUTS.IPC_REQUEST);\n });\n}\n\n/**\n * Execute the user's script\n */\nasync function executeUserScript(): Promise<void> {\n if (!initMessage) {\n throw new Error(\"No initialization message received\");\n }\n\n const { scriptPath, args, invokeDir } = initMessage;\n\n try {\n // Set environment for the script\n process.argv = [\"bun\", scriptPath, ...args];\n process.env.KLY_SANDBOX_MODE = \"true\";\n\n // Inject sandboxed context into global scope\n // This allows defineApp to access the sandboxed context\n (global as { __KLY_SANDBOXED_CONTEXT__?: unknown }).__KLY_SANDBOXED_CONTEXT__ = {\n modelsContext: createSandboxedModelsContext(),\n invokeDir,\n };\n\n // Import and execute the user's script\n // The script should use defineApp which will auto-execute in CLI mode\n // Bun will resolve modules relative to the script's directory automatically\n await import(scriptPath);\n\n // If we reach here without error, execution succeeded\n sendExecutionComplete(true);\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n sendExecutionComplete(false, undefined, errorMessage);\n throw error;\n }\n}\n\n/**\n * Send execution complete message to host\n */\nfunction sendExecutionComplete(success: boolean, result?: unknown, error?: string): void {\n if (!process.send) {\n return;\n }\n\n const message: ExecutionCompleteMessage = {\n type: \"complete\",\n success,\n result,\n error,\n };\n\n process.send(message);\n}\n\n/**\n * Main entry point\n */\nfunction main() {\n // Ensure we're in sandbox mode\n if (process.env.KLY_SANDBOX_MODE !== \"true\") {\n error(\"This script must be run in sandbox mode\");\n process.exit(1);\n }\n\n // Setup IPC\n setupIPC();\n\n // Wait for init message (handled by IPC listener)\n}\n\n// Start the executor\nmain();\n"],"mappings":"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"}
@@ -1,42 +0,0 @@
1
- import { TIMEOUTS } from "../shared/constants.mjs";
2
- import { ExitWarning } from "../shared/errors.mjs";
3
-
4
- //#region src/sandbox/ipc-client.ts
5
- /**
6
- * Send an IPC request to the host and wait for response
7
- * Used by UI components and other sandbox code to communicate with the host process
8
- */
9
- async function sendIPCRequest(type, payload) {
10
- if (!process.send) throw new Error("IPC not available - not running in sandbox mode");
11
- return new Promise((resolve, reject) => {
12
- const requestId = `${type}-${Date.now()}-${Math.random()}`;
13
- const request = {
14
- type,
15
- id: requestId,
16
- payload
17
- };
18
- const responseHandler = (message) => {
19
- if (typeof message === "object" && message !== null && "type" in message && message.type === "response" && "id" in message && message.id === requestId) {
20
- process.off("message", responseHandler);
21
- const response = message;
22
- if (response.success) resolve(response.data);
23
- else if (response.cancelled) reject(new ExitWarning(response.error));
24
- else reject(new Error(response.error));
25
- }
26
- };
27
- process.on("message", responseHandler);
28
- if (!process.send(request)) {
29
- process.off("message", responseHandler);
30
- reject(/* @__PURE__ */ new Error("Failed to send IPC message"));
31
- return;
32
- }
33
- setTimeout(() => {
34
- process.off("message", responseHandler);
35
- reject(/* @__PURE__ */ new Error(`IPC request timeout: ${type}`));
36
- }, TIMEOUTS.IPC_LONG_REQUEST);
37
- });
38
- }
39
-
40
- //#endregion
41
- export { sendIPCRequest };
42
- //# sourceMappingURL=ipc-client.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ipc-client.mjs","names":["request: IPCRequest"],"sources":["../../src/sandbox/ipc-client.ts"],"sourcesContent":["import { TIMEOUTS } from \"../shared/constants\";\nimport { ExitWarning } from \"../shared/errors\";\nimport type { IPCRequest, IPCResponse } from \"../shared/ipc-protocol\";\n\n/**\n * Send an IPC request to the host and wait for response\n * Used by UI components and other sandbox code to communicate with the host process\n */\nexport async function sendIPCRequest<T>(type: IPCRequest[\"type\"], payload: unknown): Promise<T> {\n if (!process.send) {\n throw new Error(\"IPC not available - not running in sandbox mode\");\n }\n\n return new Promise((resolve, reject) => {\n const requestId = `${type}-${Date.now()}-${Math.random()}`;\n\n const request: IPCRequest = {\n type,\n id: requestId,\n payload,\n } as IPCRequest;\n\n // Set up response listener\n const responseHandler = (message: unknown) => {\n if (\n typeof message === \"object\" &&\n message !== null &&\n \"type\" in message &&\n message.type === \"response\" &&\n \"id\" in message &&\n message.id === requestId\n ) {\n process.off(\"message\", responseHandler);\n\n const response = message as IPCResponse<T>;\n if (response.success) {\n resolve(response.data);\n } else if (response.cancelled) {\n reject(new ExitWarning(response.error));\n } else {\n reject(new Error(response.error));\n }\n }\n };\n\n process.on(\"message\", responseHandler);\n\n // Send request\n if (!process.send!(request)) {\n // Send failed immediately\n process.off(\"message\", responseHandler);\n reject(new Error(\"Failed to send IPC message\"));\n return;\n }\n\n // Timeout for long-running requests (prompts, etc.)\n setTimeout(() => {\n process.off(\"message\", responseHandler);\n reject(new Error(`IPC request timeout: ${type}`));\n }, TIMEOUTS.IPC_LONG_REQUEST);\n });\n}\n"],"mappings":";;;;;;;;AAQA,eAAsB,eAAkB,MAA0B,SAA8B;AAC9F,KAAI,CAAC,QAAQ,KACX,OAAM,IAAI,MAAM,kDAAkD;AAGpE,QAAO,IAAI,SAAS,SAAS,WAAW;EACtC,MAAM,YAAY,GAAG,KAAK,GAAG,KAAK,KAAK,CAAC,GAAG,KAAK,QAAQ;EAExD,MAAMA,UAAsB;GAC1B;GACA,IAAI;GACJ;GACD;EAGD,MAAM,mBAAmB,YAAqB;AAC5C,OACE,OAAO,YAAY,YACnB,YAAY,QACZ,UAAU,WACV,QAAQ,SAAS,cACjB,QAAQ,WACR,QAAQ,OAAO,WACf;AACA,YAAQ,IAAI,WAAW,gBAAgB;IAEvC,MAAM,WAAW;AACjB,QAAI,SAAS,QACX,SAAQ,SAAS,KAAK;aACb,SAAS,UAClB,QAAO,IAAI,YAAY,SAAS,MAAM,CAAC;QAEvC,QAAO,IAAI,MAAM,SAAS,MAAM,CAAC;;;AAKvC,UAAQ,GAAG,WAAW,gBAAgB;AAGtC,MAAI,CAAC,QAAQ,KAAM,QAAQ,EAAE;AAE3B,WAAQ,IAAI,WAAW,gBAAgB;AACvC,0BAAO,IAAI,MAAM,6BAA6B,CAAC;AAC/C;;AAIF,mBAAiB;AACf,WAAQ,IAAI,WAAW,gBAAgB;AACvC,0BAAO,IAAI,MAAM,wBAAwB,OAAO,CAAC;KAChD,SAAS,iBAAiB;GAC7B"}
@@ -1,14 +0,0 @@
1
- //#region src/sandbox/sandboxed-context.ts
2
- /**
3
- * Get the sandboxed context from global scope
4
- * This is injected by the executor before loading user scripts
5
- */
6
- function getSandboxedContext() {
7
- const globalWithContext = global;
8
- if (!globalWithContext.__KLY_SANDBOXED_CONTEXT__) throw new Error("Sandboxed context not available. This should only be called from within the sandbox.");
9
- return globalWithContext.__KLY_SANDBOXED_CONTEXT__;
10
- }
11
-
12
- //#endregion
13
- export { getSandboxedContext };
14
- //# sourceMappingURL=sandboxed-context.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"sandboxed-context.mjs","names":[],"sources":["../../src/sandbox/sandboxed-context.ts"],"sourcesContent":["import type { ModelConfigResponse } from \"../shared/ipc-protocol\";\nimport type { ModelConfig, ModelInfo, ModelsContext } from \"../types\";\nimport { sendIPCRequest } from \"./executor\";\n\n/**\n * Create sandboxed models context that communicates with host via IPC\n * This context is injected into user tools running in the sandbox\n * All API key access is controlled by the host process\n */\nexport function createSandboxedModelsContext(): ModelsContext {\n return {\n /**\n * List available models (no permission required)\n */\n list(): ModelInfo[] {\n throw new Error(\n \"Synchronous list() not supported in sandbox. Use async methods or move this logic to host.\",\n );\n },\n\n /**\n * Get current model info (no permission required)\n */\n getCurrent(): ModelInfo | null {\n throw new Error(\n \"Synchronous getCurrent() not supported in sandbox. Use async methods or move this logic to host.\",\n );\n },\n\n /**\n * Get model info by name (no permission required)\n */\n get(_name: string): ModelInfo | null {\n throw new Error(\n \"Synchronous get() not supported in sandbox. Use async methods or move this logic to host.\",\n );\n },\n\n /**\n * Get model config with API key (requires permission, enforced by host)\n */\n async getConfigAsync(name?: string): Promise<ModelConfig | null> {\n try {\n const response = await sendIPCRequest<ModelConfigResponse | null>(\"getModelConfig\", {\n name,\n });\n\n if (!response) {\n return null;\n }\n\n return {\n provider: response.provider,\n model: response.model,\n apiKey: response.apiKey,\n baseURL: response.baseURL,\n };\n } catch (error) {\n // Re-throw with clear error message\n const message = error instanceof Error ? error.message : \"Failed to get model config\";\n throw new Error(`Permission denied: ${message}`);\n }\n },\n };\n}\n\n/**\n * Get the sandboxed context from global scope\n * This is injected by the executor before loading user scripts\n */\nexport function getSandboxedContext(): {\n modelsContext: ModelsContext;\n invokeDir?: string;\n} {\n const globalWithContext = global as {\n __KLY_SANDBOXED_CONTEXT__?: {\n modelsContext: ModelsContext;\n invokeDir?: string;\n };\n };\n\n if (!globalWithContext.__KLY_SANDBOXED_CONTEXT__) {\n throw new Error(\n \"Sandboxed context not available. This should only be called from within the sandbox.\",\n );\n }\n\n return globalWithContext.__KLY_SANDBOXED_CONTEXT__;\n}\n"],"mappings":";;;;;AAsEA,SAAgB,sBAGd;CACA,MAAM,oBAAoB;AAO1B,KAAI,CAAC,kBAAkB,0BACrB,OAAM,IAAI,MACR,uFACD;AAGH,QAAO,kBAAkB"}