kly 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of kly might be problematic. Click here for more details.
- package/README.md +14 -2
- package/dist/ai/context.mjs.map +1 -1
- package/dist/ai/storage.mjs.map +1 -1
- package/dist/bin/bin-registry-Cj1PddZ1.mjs +3 -0
- package/dist/bin/kly.mjs +56 -37
- package/dist/bin/kly.mjs.map +1 -1
- package/dist/bin/{permissions-extractor-ySRyhQut.mjs → permissions-extractor-B3XI8JLv.mjs} +2 -2
- package/dist/bin/{permissions-extractor-ySRyhQut.mjs.map → permissions-extractor-B3XI8JLv.mjs.map} +1 -1
- package/dist/cli.mjs.map +1 -1
- package/dist/define-app.mjs.map +1 -1
- package/dist/mcp/schema-converter.mjs.map +1 -1
- package/dist/mcp/server.d.mts.map +1 -1
- package/dist/mcp/server.mjs.map +1 -1
- package/dist/permissions/index.mjs.map +1 -1
- package/dist/sandbox/bundled-executor.mjs.map +1 -1
- package/dist/sandbox/ipc-client.mjs.map +1 -1
- package/dist/sandbox/sandboxed-context.mjs.map +1 -1
- package/dist/tool.d.mts.map +1 -1
- package/dist/tool.mjs.map +1 -1
- package/dist/types.d.mts.map +1 -1
- package/dist/types.mjs.map +1 -1
- package/dist/ui/components/confirm.d.mts.map +1 -1
- package/dist/ui/components/confirm.mjs.map +1 -1
- package/dist/ui/components/form.d.mts.map +1 -1
- package/dist/ui/components/form.mjs.map +1 -1
- package/dist/ui/components/table.d.mts.map +1 -1
- package/dist/ui/components/table.mjs.map +1 -1
- package/dist/ui/utils/colors.d.mts.map +1 -1
- package/dist/ui/utils/colors.mjs.map +1 -1
- package/dist/ui/utils/tty.mjs.map +1 -1
- package/package.json +17 -16
- package/dist/bin/bin-registry-DOBspniG.mjs +0 -3
- package/dist/bin/remote-tQeFZX9o.mjs +0 -3
- /package/dist/bin/{config-builder-VSUaGlaV.mjs → config-builder-CkkAqWMY.mjs} +0 -0
- /package/dist/bin/{launcher-DD6vpEFb.mjs → launcher-CSdy4_ZV.mjs} +0 -0
- /package/dist/bin/{permissions-D7-M2lRi.mjs → permissions-DOOzghc5.mjs} +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { N as log } from "./kly.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/remote/permissions-extractor.ts
|
|
4
4
|
/**
|
|
@@ -28,4 +28,4 @@ async function extractAppPermissions(entryPath) {
|
|
|
28
28
|
|
|
29
29
|
//#endregion
|
|
30
30
|
export { extractAppPermissions };
|
|
31
|
-
//# sourceMappingURL=permissions-extractor-
|
|
31
|
+
//# sourceMappingURL=permissions-extractor-B3XI8JLv.mjs.map
|
package/dist/bin/{permissions-extractor-ySRyhQut.mjs.map → permissions-extractor-B3XI8JLv.mjs.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"permissions-extractor-
|
|
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"}
|
package/dist/cli.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.mjs","names":["result: ParsedArgs","key","lines: string[]","missing: SchemaInputField[]","fieldType: SchemaInputField[\"type\"]"],"sources":["../src/cli.ts"],"sourcesContent":["import type { AnyTool, AppDefinition, StandardSchemaV1 } from \"./types\";\n\n/**\n * Parsed CLI arguments as key-value pairs\n */\nexport type ParsedArgs = Record<string, string | number | boolean>;\n\n/**\n * JSON Schema property info\n */\ninterface PropertyInfo {\n name: string;\n type: string;\n description?: string;\n default?: unknown;\n enum?: unknown[];\n required: boolean;\n}\n\n/**\n * Extract JSON Schema from a Standard Schema if available\n */\nfunction extractJsonSchema(\n schema: StandardSchemaV1,\n): Record<string, unknown> | null {\n const standard = schema[\"~standard\"] as unknown as Record<string, unknown>;\n if (\"jsonSchema\" in standard && standard.jsonSchema) {\n const jsonSchema = standard.jsonSchema as {\n output: (opts: { target: string }) => Record<string, unknown>;\n };\n try {\n return jsonSchema.output({ target: \"draft-07\" });\n } catch {\n return null;\n }\n }\n return null;\n}\n\n/**\n * Extract property info from JSON Schema\n */\nfunction extractProperties(\n jsonSchema: Record<string, unknown>,\n): PropertyInfo[] {\n const properties = (jsonSchema.properties ?? {}) as Record<\n string,\n Record<string, unknown>\n >;\n const required = (jsonSchema.required ?? []) as string[];\n\n return Object.entries(properties).map(([name, prop]) => {\n let type = (prop.type as string) ?? \"unknown\";\n\n // Handle enum\n if (prop.enum) {\n type = (prop.enum as unknown[]).join(\" | \");\n }\n\n return {\n name,\n type,\n description: prop.description as string | undefined,\n default: prop.default,\n enum: prop.enum as unknown[] | undefined,\n required: required.includes(name) && prop.default === undefined,\n };\n });\n}\n\n/**\n * Format a single option line for help text\n */\nfunction formatOptionLine(prop: PropertyInfo): string {\n const flag = `--${prop.name}`;\n const typeHint = prop.type !== \"boolean\" ? `=<${prop.type}>` : \"\";\n const flagPart = `${flag}${typeHint}`.padEnd(25);\n\n let descPart = prop.description ?? \"\";\n\n // Add default value hint\n if (prop.default !== undefined) {\n descPart += ` (default: ${JSON.stringify(prop.default)})`;\n }\n\n // Add required hint\n if (prop.required) {\n descPart += \" [required]\";\n }\n\n return ` ${flagPart} ${descPart}`;\n}\n\n/**\n * Generate usage string with required args\n */\nfunction generateUsage(command: string, properties: PropertyInfo[]): string {\n const requiredArgs = properties\n .filter((p) => p.required)\n .map((p) => `--${p.name}=<${p.type}>`)\n .join(\" \");\n\n const optionalHint = properties.some((p) => !p.required) ? \" [options]\" : \"\";\n\n return ` ${command} ${requiredArgs}${optionalHint}`.trim();\n}\n\n/**\n * Parse command line arguments into an object\n */\nexport function parseCliArgs(argv: string[]): ParsedArgs {\n const result: ParsedArgs = {};\n let i = 0;\n\n while (i < argv.length) {\n const arg = argv[i]!;\n\n // Skip non-flag arguments (positional)\n if (!arg.startsWith(\"-\")) {\n i++;\n continue;\n }\n\n // Handle --key=value format\n if (arg.includes(\"=\")) {\n const [key, ...valueParts] = arg.split(\"=\");\n const cleanKey = key!.replace(/^-+/, \"\");\n const value = valueParts.join(\"=\");\n result[cleanKey] = coerceValue(value);\n i++;\n continue;\n }\n\n // Handle --no-flag format (boolean false)\n if (arg.startsWith(\"--no-\")) {\n const key = arg.slice(5);\n result[key] = false;\n i++;\n continue;\n }\n\n // Handle --flag or --key value format\n const key = arg.replace(/^-+/, \"\");\n const nextArg = argv[i + 1];\n\n // Check if next arg is a value or another flag\n if (nextArg === undefined || nextArg.startsWith(\"-\")) {\n // It's a boolean flag\n result[key] = true;\n i++;\n } else {\n // It's a key-value pair\n result[key] = coerceValue(nextArg);\n i += 2;\n }\n }\n\n return result;\n}\n\n/**\n * Coerce string value to appropriate type\n */\nfunction coerceValue(value: string): string | number | boolean {\n // Boolean\n if (value === \"true\") return true;\n if (value === \"false\") return false;\n\n // Number\n const num = Number(value);\n if (!Number.isNaN(num) && value.trim() !== \"\") {\n return num;\n }\n\n // String\n return value;\n}\n\n/**\n * Check if --help or -h was requested\n */\nexport function isHelpRequested(argv: string[]): boolean {\n return argv.includes(\"--help\") || argv.includes(\"-h\");\n}\n\n/**\n * Check if --version or -v was requested\n */\nexport function isVersionRequested(argv: string[]): boolean {\n return argv.includes(\"--version\") || argv.includes(\"-v\");\n}\n\n/**\n * Parse subcommand from argv\n */\nexport function parseSubcommand(argv: string[]): {\n subcommand: string | null;\n args: string[];\n} {\n const firstNonFlag = argv.findIndex((arg) => !arg.startsWith(\"-\"));\n\n if (firstNonFlag === -1) {\n return { subcommand: null, args: argv };\n }\n\n return {\n subcommand: argv[firstNonFlag]!,\n args: [...argv.slice(0, firstNonFlag), ...argv.slice(firstNonFlag + 1)],\n };\n}\n\n/**\n * Generate help text for a single tool (used in multi-tools mode as subcommand help)\n */\nexport function generateToolHelp(appName: string, tool: AnyTool): string {\n const lines: string[] = [];\n\n // Header\n lines.push(`${appName} ${tool.name}`);\n lines.push(\"\");\n lines.push(tool.description ?? \"No description\");\n lines.push(\"\");\n\n // Try to extract JSON Schema for dynamic help\n const jsonSchema = extractJsonSchema(tool.inputSchema);\n const properties = jsonSchema ? extractProperties(jsonSchema) : [];\n\n // Usage\n lines.push(\"Usage:\");\n if (properties.length > 0) {\n lines.push(generateUsage(`bun run <file>.ts ${tool.name}`, properties));\n } else {\n lines.push(` bun run <file>.ts ${tool.name} [options]`);\n }\n lines.push(\"\");\n\n // Options from schema\n if (properties.length > 0) {\n lines.push(\"Options:\");\n for (const prop of properties) {\n lines.push(formatOptionLine(prop));\n }\n lines.push(\"\");\n }\n\n // Built-in options\n lines.push(\"Flags:\");\n lines.push(\" --help, -h Show this help message\");\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Generate help text for single tool app (no subcommand needed)\n */\nexport function generateSingleToolHelp(\n config: AppDefinition,\n tool: AnyTool,\n): string {\n const lines: string[] = [];\n\n // Header\n lines.push(`${config.name ?? \"unknown\"} v${config.version ?? \"0.0.0\"}`);\n lines.push(\"\");\n lines.push(config.description ?? \"\");\n lines.push(\"\");\n\n // Try to extract JSON Schema for dynamic help\n const jsonSchema = extractJsonSchema(tool.inputSchema);\n const properties = jsonSchema ? extractProperties(jsonSchema) : [];\n\n // Usage\n lines.push(\"Usage:\");\n if (properties.length > 0) {\n lines.push(generateUsage(\"bun run <file>.ts\", properties));\n } else {\n lines.push(\" bun run <file>.ts [options]\");\n }\n lines.push(\"\");\n\n // Options from schema\n if (properties.length > 0) {\n lines.push(\"Options:\");\n for (const prop of properties) {\n lines.push(formatOptionLine(prop));\n }\n lines.push(\"\");\n }\n\n // Built-in options\n lines.push(\"Flags:\");\n lines.push(\" --help, -h Show this help message\");\n lines.push(\" --version, -v Show version\");\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Generate help text from app config\n */\nexport function generateMultiToolsHelp(config: AppDefinition): string {\n const lines: string[] = [];\n\n // Header\n lines.push(`${config.name ?? \"unknown\"} v${config.version ?? \"0.0.0\"}`);\n lines.push(\"\");\n lines.push(config.description ?? \"\");\n lines.push(\"\");\n\n // Usage\n lines.push(\"Usage:\");\n lines.push(\" bun run <file>.ts <command> [options]\");\n lines.push(\"\");\n\n // Commands\n lines.push(\"Commands:\");\n for (const tool of config.tools) {\n const desc = tool.description ?? \"\";\n lines.push(` ${tool.name.padEnd(15)} ${desc}`);\n }\n lines.push(\"\");\n\n // Flags\n lines.push(\"Flags:\");\n lines.push(\" --help, -h Show this help message\");\n lines.push(\" --version, -v Show version\");\n lines.push(\"\");\n lines.push(`Run '${config.name} <command> --help' for more information.`);\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Input field for interactive prompts (duplicated to avoid circular deps)\n */\ninterface SchemaInputField {\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 min?: number;\n max?: number;\n}\n\n/**\n * Get missing required fields from a schema based on provided args\n */\nexport function getMissingRequiredFields(\n schema: StandardSchemaV1,\n providedArgs: Record<string, unknown>,\n): SchemaInputField[] {\n const jsonSchema = extractJsonSchema(schema);\n if (!jsonSchema) return [];\n\n const properties = extractProperties(jsonSchema);\n const missing: SchemaInputField[] = [];\n\n for (const prop of properties) {\n // Skip if already provided\n if (providedArgs[prop.name] !== undefined) continue;\n\n // Skip if not required (has default or optional)\n if (!prop.required) continue;\n\n // Convert to input field\n let fieldType: SchemaInputField[\"type\"] = \"string\";\n if (prop.type === \"number\" || prop.type === \"integer\") {\n fieldType = \"number\";\n } else if (prop.type === \"boolean\") {\n fieldType = \"boolean\";\n } else if (prop.enum && prop.enum.length > 0) {\n fieldType = \"enum\";\n }\n\n missing.push({\n name: prop.name,\n label: prop.description || prop.name,\n type: fieldType,\n required: true,\n defaultValue: prop.default,\n description: prop.description,\n enumValues: prop.enum?.map(String),\n });\n }\n\n return missing;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cli.mjs","names":["result: ParsedArgs","key","lines: string[]","missing: SchemaInputField[]","fieldType: SchemaInputField[\"type\"]"],"sources":["../src/cli.ts"],"sourcesContent":["import type { AnyTool, AppDefinition, StandardSchemaV1 } from \"./types\";\n\n/**\n * Parsed CLI arguments as key-value pairs\n */\nexport type ParsedArgs = Record<string, string | number | boolean>;\n\n/**\n * JSON Schema property info\n */\ninterface PropertyInfo {\n name: string;\n type: string;\n description?: string;\n default?: unknown;\n enum?: unknown[];\n required: boolean;\n}\n\n/**\n * Extract JSON Schema from a Standard Schema if available\n */\nfunction extractJsonSchema(schema: StandardSchemaV1): Record<string, unknown> | null {\n const standard = schema[\"~standard\"] as unknown as Record<string, unknown>;\n if (\"jsonSchema\" in standard && standard.jsonSchema) {\n const jsonSchema = standard.jsonSchema as {\n output: (opts: { target: string }) => Record<string, unknown>;\n };\n try {\n return jsonSchema.output({ target: \"draft-07\" });\n } catch {\n return null;\n }\n }\n return null;\n}\n\n/**\n * Extract property info from JSON Schema\n */\nfunction extractProperties(jsonSchema: Record<string, unknown>): PropertyInfo[] {\n const properties = (jsonSchema.properties ?? {}) as Record<string, Record<string, unknown>>;\n const required = (jsonSchema.required ?? []) as string[];\n\n return Object.entries(properties).map(([name, prop]) => {\n let type = (prop.type as string) ?? \"unknown\";\n\n // Handle enum\n if (prop.enum) {\n type = (prop.enum as unknown[]).join(\" | \");\n }\n\n return {\n name,\n type,\n description: prop.description as string | undefined,\n default: prop.default,\n enum: prop.enum as unknown[] | undefined,\n required: required.includes(name) && prop.default === undefined,\n };\n });\n}\n\n/**\n * Format a single option line for help text\n */\nfunction formatOptionLine(prop: PropertyInfo): string {\n const flag = `--${prop.name}`;\n const typeHint = prop.type !== \"boolean\" ? `=<${prop.type}>` : \"\";\n const flagPart = `${flag}${typeHint}`.padEnd(25);\n\n let descPart = prop.description ?? \"\";\n\n // Add default value hint\n if (prop.default !== undefined) {\n descPart += ` (default: ${JSON.stringify(prop.default)})`;\n }\n\n // Add required hint\n if (prop.required) {\n descPart += \" [required]\";\n }\n\n return ` ${flagPart} ${descPart}`;\n}\n\n/**\n * Generate usage string with required args\n */\nfunction generateUsage(command: string, properties: PropertyInfo[]): string {\n const requiredArgs = properties\n .filter((p) => p.required)\n .map((p) => `--${p.name}=<${p.type}>`)\n .join(\" \");\n\n const optionalHint = properties.some((p) => !p.required) ? \" [options]\" : \"\";\n\n return ` ${command} ${requiredArgs}${optionalHint}`.trim();\n}\n\n/**\n * Parse command line arguments into an object\n */\nexport function parseCliArgs(argv: string[]): ParsedArgs {\n const result: ParsedArgs = {};\n let i = 0;\n\n while (i < argv.length) {\n const arg = argv[i]!;\n\n // Skip non-flag arguments (positional)\n if (!arg.startsWith(\"-\")) {\n i++;\n continue;\n }\n\n // Handle --key=value format\n if (arg.includes(\"=\")) {\n const [key, ...valueParts] = arg.split(\"=\");\n const cleanKey = key!.replace(/^-+/, \"\");\n const value = valueParts.join(\"=\");\n result[cleanKey] = coerceValue(value);\n i++;\n continue;\n }\n\n // Handle --no-flag format (boolean false)\n if (arg.startsWith(\"--no-\")) {\n const key = arg.slice(5);\n result[key] = false;\n i++;\n continue;\n }\n\n // Handle --flag or --key value format\n const key = arg.replace(/^-+/, \"\");\n const nextArg = argv[i + 1];\n\n // Check if next arg is a value or another flag\n if (nextArg === undefined || nextArg.startsWith(\"-\")) {\n // It's a boolean flag\n result[key] = true;\n i++;\n } else {\n // It's a key-value pair\n result[key] = coerceValue(nextArg);\n i += 2;\n }\n }\n\n return result;\n}\n\n/**\n * Coerce string value to appropriate type\n */\nfunction coerceValue(value: string): string | number | boolean {\n // Boolean\n if (value === \"true\") return true;\n if (value === \"false\") return false;\n\n // Number\n const num = Number(value);\n if (!Number.isNaN(num) && value.trim() !== \"\") {\n return num;\n }\n\n // String\n return value;\n}\n\n/**\n * Check if --help or -h was requested\n */\nexport function isHelpRequested(argv: string[]): boolean {\n return argv.includes(\"--help\") || argv.includes(\"-h\");\n}\n\n/**\n * Check if --version or -v was requested\n */\nexport function isVersionRequested(argv: string[]): boolean {\n return argv.includes(\"--version\") || argv.includes(\"-v\");\n}\n\n/**\n * Parse subcommand from argv\n */\nexport function parseSubcommand(argv: string[]): {\n subcommand: string | null;\n args: string[];\n} {\n const firstNonFlag = argv.findIndex((arg) => !arg.startsWith(\"-\"));\n\n if (firstNonFlag === -1) {\n return { subcommand: null, args: argv };\n }\n\n return {\n subcommand: argv[firstNonFlag]!,\n args: [...argv.slice(0, firstNonFlag), ...argv.slice(firstNonFlag + 1)],\n };\n}\n\n/**\n * Generate help text for a single tool (used in multi-tools mode as subcommand help)\n */\nexport function generateToolHelp(appName: string, tool: AnyTool): string {\n const lines: string[] = [];\n\n // Header\n lines.push(`${appName} ${tool.name}`);\n lines.push(\"\");\n lines.push(tool.description ?? \"No description\");\n lines.push(\"\");\n\n // Try to extract JSON Schema for dynamic help\n const jsonSchema = extractJsonSchema(tool.inputSchema);\n const properties = jsonSchema ? extractProperties(jsonSchema) : [];\n\n // Usage\n lines.push(\"Usage:\");\n if (properties.length > 0) {\n lines.push(generateUsage(`bun run <file>.ts ${tool.name}`, properties));\n } else {\n lines.push(` bun run <file>.ts ${tool.name} [options]`);\n }\n lines.push(\"\");\n\n // Options from schema\n if (properties.length > 0) {\n lines.push(\"Options:\");\n for (const prop of properties) {\n lines.push(formatOptionLine(prop));\n }\n lines.push(\"\");\n }\n\n // Built-in options\n lines.push(\"Flags:\");\n lines.push(\" --help, -h Show this help message\");\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Generate help text for single tool app (no subcommand needed)\n */\nexport function generateSingleToolHelp(config: AppDefinition, tool: AnyTool): string {\n const lines: string[] = [];\n\n // Header\n lines.push(`${config.name ?? \"unknown\"} v${config.version ?? \"0.0.0\"}`);\n lines.push(\"\");\n lines.push(config.description ?? \"\");\n lines.push(\"\");\n\n // Try to extract JSON Schema for dynamic help\n const jsonSchema = extractJsonSchema(tool.inputSchema);\n const properties = jsonSchema ? extractProperties(jsonSchema) : [];\n\n // Usage\n lines.push(\"Usage:\");\n if (properties.length > 0) {\n lines.push(generateUsage(\"bun run <file>.ts\", properties));\n } else {\n lines.push(\" bun run <file>.ts [options]\");\n }\n lines.push(\"\");\n\n // Options from schema\n if (properties.length > 0) {\n lines.push(\"Options:\");\n for (const prop of properties) {\n lines.push(formatOptionLine(prop));\n }\n lines.push(\"\");\n }\n\n // Built-in options\n lines.push(\"Flags:\");\n lines.push(\" --help, -h Show this help message\");\n lines.push(\" --version, -v Show version\");\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Generate help text from app config\n */\nexport function generateMultiToolsHelp(config: AppDefinition): string {\n const lines: string[] = [];\n\n // Header\n lines.push(`${config.name ?? \"unknown\"} v${config.version ?? \"0.0.0\"}`);\n lines.push(\"\");\n lines.push(config.description ?? \"\");\n lines.push(\"\");\n\n // Usage\n lines.push(\"Usage:\");\n lines.push(\" bun run <file>.ts <command> [options]\");\n lines.push(\"\");\n\n // Commands\n lines.push(\"Commands:\");\n for (const tool of config.tools) {\n const desc = tool.description ?? \"\";\n lines.push(` ${tool.name.padEnd(15)} ${desc}`);\n }\n lines.push(\"\");\n\n // Flags\n lines.push(\"Flags:\");\n lines.push(\" --help, -h Show this help message\");\n lines.push(\" --version, -v Show version\");\n lines.push(\"\");\n lines.push(`Run '${config.name} <command> --help' for more information.`);\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Input field for interactive prompts (duplicated to avoid circular deps)\n */\ninterface SchemaInputField {\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 min?: number;\n max?: number;\n}\n\n/**\n * Get missing required fields from a schema based on provided args\n */\nexport function getMissingRequiredFields(\n schema: StandardSchemaV1,\n providedArgs: Record<string, unknown>,\n): SchemaInputField[] {\n const jsonSchema = extractJsonSchema(schema);\n if (!jsonSchema) return [];\n\n const properties = extractProperties(jsonSchema);\n const missing: SchemaInputField[] = [];\n\n for (const prop of properties) {\n // Skip if already provided\n if (providedArgs[prop.name] !== undefined) continue;\n\n // Skip if not required (has default or optional)\n if (!prop.required) continue;\n\n // Convert to input field\n let fieldType: SchemaInputField[\"type\"] = \"string\";\n if (prop.type === \"number\" || prop.type === \"integer\") {\n fieldType = \"number\";\n } else if (prop.type === \"boolean\") {\n fieldType = \"boolean\";\n } else if (prop.enum && prop.enum.length > 0) {\n fieldType = \"enum\";\n }\n\n missing.push({\n name: prop.name,\n label: prop.description || prop.name,\n type: fieldType,\n required: true,\n defaultValue: prop.default,\n description: prop.description,\n enumValues: prop.enum?.map(String),\n });\n }\n\n return missing;\n}\n"],"mappings":"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"}
|
package/dist/define-app.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"define-app.mjs","names":["fullDefinition: AppDefinition<TTools>","app: KlyApp<TTools>","mode"],"sources":["../src/define-app.ts"],"sourcesContent":["import { createModelsContext } from \"./ai/context\";\nimport {\n generateMultiToolsHelp,\n generateSingleToolHelp,\n generateToolHelp,\n getMissingRequiredFields,\n isHelpRequested,\n isVersionRequested,\n parseCliArgs,\n parseSubcommand,\n} from \"./cli\";\nimport { EXIT_CODES } from \"./shared/constants\";\nimport { ExitError, ExitWarning } from \"./shared/errors\";\nimport { detectMode, isSandbox } from \"./shared/runtime-mode\";\nimport type { AnyTool, AppDefinition, KlyApp } from \"./types\";\nimport { ValidationError } from \"./types\";\nimport { cancel, error, form, isTTY, output, select } from \"./ui\";\n\n/**\n * Get the appropriate models context based on runtime environment\n */\nasync function _getModelsContext() {\n if (isSandbox()) {\n // In sandbox: use IPC-based context\n // Dynamic import for runtime-specific module loading\n const m = await import(\"./sandbox/sandboxed-context\");\n return m.getSandboxedContext().modelsContext;\n }\n // Outside sandbox: use direct file access\n return createModelsContext();\n}\n\n/**\n * Get the invoke directory based on runtime environment\n */\nasync function _getInvokeDir(): Promise<string | undefined> {\n if (isSandbox()) {\n // In sandbox: get from sandboxed context\n const m = await import(\"./sandbox/sandboxed-context\");\n return m.getSandboxedContext().invokeDir;\n }\n // Outside sandbox: not available (programmatic mode)\n return undefined;\n}\n\n/**\n * Define a Kly app with tools\n *\n * @example\n * ```typescript\n * import { defineApp, tool } from \"kly\"\n * import { z } from \"zod\"\n *\n * const helloTool = tool({\n * name: \"hello\",\n * description: \"Say hello\",\n * inputSchema: z.object({ name: z.string() }),\n * execute: async ({ name }) => `Hello, ${name}!`,\n * })\n *\n * defineApp({\n * name: \"my-app\",\n * version: \"0.1.0\",\n * description: \"My CLI app\",\n * tools: [helloTool],\n * })\n * // CLI: `my-app hello --name=World`\n * // MCP: exposes tool as my-app_hello\n * ```\n */\nexport function defineApp<TTools extends AnyTool[]>(\n definition: AppDefinition<TTools>,\n): KlyApp<TTools> {\n // Normalize definition with defaults\n const fullDefinition: AppDefinition<TTools> = {\n name: definition.name ?? \"unknown\",\n version: definition.version ?? \"0.0.0\",\n description: definition.description ?? \"\",\n tools: definition.tools,\n instructions: definition.instructions,\n permissions: definition.permissions,\n };\n\n // Build tools map\n const toolsMap = new Map<string, AnyTool>();\n for (const tool of fullDefinition.tools) {\n toolsMap.set(tool.name, tool);\n }\n\n const app: KlyApp<TTools> = {\n definition: fullDefinition,\n tools: toolsMap,\n\n async execute(\n toolName: string,\n providedArgs?: Record<string, unknown>,\n ): Promise<unknown> {\n const mode = detectMode();\n const tool = toolsMap.get(toolName);\n\n if (!tool) {\n const available = Array.from(toolsMap.keys()).join(\", \");\n throw new Error(\n `Unknown tool: ${toolName}. Available tools: ${available}`,\n );\n }\n\n // Validate with schema\n const result = await tool.inputSchema[\"~standard\"].validate(\n providedArgs ?? {},\n );\n\n if (result.issues) {\n throw new ValidationError(result.issues);\n }\n\n // Execute with appropriate context (sandboxed or direct)\n const execResult = await tool.execute(result.value, {\n mode,\n models: await _getModelsContext(),\n invokeDir: await _getInvokeDir(),\n });\n return execResult;\n },\n };\n\n // Auto-run based on mode\n const mode = detectMode();\n if (mode === \"cli\") {\n runCli(app, fullDefinition).catch((err) => {\n // Check for ExitWarning (user cancellation - graceful exit)\n const isExitWarning =\n err instanceof ExitWarning || err?.name === \"ExitWarning\";\n if (isExitWarning) {\n if (err.message) {\n cancel(err.message);\n }\n process.exit(EXIT_CODES.CANCELLED);\n }\n\n // Check for ExitError\n const isExitError = err instanceof ExitError || err?.name === \"ExitError\";\n const exitCode = isExitError ? (err.exitCode ?? 1) : 1;\n const message =\n typeof err === \"string\" ? err : err?.message || String(err);\n\n if (isExitError) {\n if (message) {\n error(message);\n }\n } else {\n error(\"Fatal error:\", [message]);\n }\n\n process.exit(exitCode);\n });\n } else if (mode === \"mcp\") {\n // Dynamically import MCP server to avoid bundling it in CLI mode\n import(\"./mcp\").then(({ startMcpServer }) => {\n startMcpServer(app).catch((err) => {\n const message =\n typeof err === \"string\" ? err : err?.message || String(err);\n error(\"MCP server error:\", [message]);\n process.exit(1);\n });\n });\n }\n\n return app;\n}\n\n/**\n * Run app in CLI mode\n */\nasync function runCli<TTools extends AnyTool[]>(\n app: KlyApp<TTools>,\n definition: AppDefinition<TTools>,\n): Promise<void> {\n const argv = process.argv.slice(2);\n const isSingleTool = definition.tools.length === 1;\n\n // Handle --help and --version without TUI\n if (isHelpRequested(argv)) {\n if (isSingleTool) {\n output(generateSingleToolHelp(definition, definition.tools[0]!));\n } else {\n const { subcommand } = parseSubcommand(argv);\n if (subcommand) {\n const tool = app.tools.get(subcommand);\n if (tool) {\n output(generateToolHelp(definition.name ?? \"unknown\", tool));\n } else {\n output(generateMultiToolsHelp(definition));\n }\n } else {\n output(generateMultiToolsHelp(definition));\n }\n }\n return;\n }\n\n if (isVersionRequested(argv)) {\n output(`${definition.name ?? \"unknown\"} v${definition.version ?? \"0.0.0\"}`);\n return;\n }\n\n // Run the appropriate CLI mode\n if (isSingleTool) {\n await runSingleToolCli(app, definition);\n } else {\n await runMultiToolsCli(app, definition);\n }\n // Clean exit\n process.exit(0);\n}\n\n/**\n * Run single tool app in CLI mode (no subcommand needed)\n */\nasync function runSingleToolCli<TTools extends AnyTool[]>(\n app: KlyApp<TTools>,\n definition: AppDefinition<TTools>,\n): Promise<void> {\n const argv = process.argv.slice(2);\n const tool = definition.tools[0]!;\n const interactive = isTTY();\n\n // Parse args\n let parsedArgs = parseCliArgs(argv) as Record<string, unknown>;\n\n // Check for missing required fields and prompt if in TTY mode\n // In sandbox mode, form() will use IPC to prompt in the host process\n if (interactive) {\n const missingFields = getMissingRequiredFields(\n tool.inputSchema,\n parsedArgs,\n );\n\n if (missingFields.length > 0) {\n const additionalArgs = await form({ fields: missingFields });\n parsedArgs = { ...parsedArgs, ...additionalArgs };\n }\n }\n\n try {\n const result = await app.execute(tool.name, parsedArgs);\n if (result !== undefined) {\n output(result);\n }\n } catch (err) {\n if (err instanceof ValidationError) {\n throw new ExitError(\n `${err.message}\\nRun with --help for usage information.`,\n );\n }\n throw err;\n }\n}\n\n/**\n * Run multi tools app in CLI mode (with subcommands)\n */\nasync function runMultiToolsCli<TTools extends AnyTool[]>(\n app: KlyApp<TTools>,\n definition: AppDefinition<TTools>,\n): Promise<void> {\n const argv = process.argv.slice(2);\n let { subcommand } = parseSubcommand(argv);\n const interactive = isTTY();\n\n // Get rest args (subcommand already parsed above)\n const { args: restArgs } = parseSubcommand(argv);\n\n // Interactive tool selection if no subcommand and in TTY mode\n // In sandbox mode, select() will use IPC to prompt in the host process\n if (!subcommand) {\n if (interactive) {\n // Show interactive menu to select a tool\n const toolOptions = definition.tools.map((t) => ({\n name: t.name,\n description: t.description,\n value: t.name,\n }));\n subcommand = await select({\n options: toolOptions,\n prompt: `${definition.name} - Select a command`,\n });\n } else {\n throw new ExitError(\n `No subcommand provided.\\nAvailable commands: ${definition.tools.map((t) => t.name).join(\", \")}\\nRun '${definition.name} --help' for usage.`,\n );\n }\n }\n\n // At this point subcommand is guaranteed to be a string\n const selectedCommand = subcommand as string;\n\n const tool = app.tools.get(selectedCommand);\n if (!tool) {\n throw new ExitError(\n `Unknown command '${selectedCommand}'.\\nAvailable commands: ${definition.tools.map((t) => t.name).join(\", \")}\\nRun '${definition.name} --help' for usage.`,\n );\n }\n\n // Parse args\n let parsedArgs = parseCliArgs(restArgs) as Record<string, unknown>;\n\n // Check for missing required fields and prompt if in TTY mode\n // In sandbox mode, form() will use IPC to prompt in the host process\n if (interactive) {\n const missingFields = getMissingRequiredFields(\n tool.inputSchema,\n parsedArgs,\n );\n\n if (missingFields.length > 0) {\n const additionalArgs = await form({ fields: missingFields });\n parsedArgs = { ...parsedArgs, ...additionalArgs };\n }\n }\n\n try {\n const result = await app.execute(selectedCommand, parsedArgs);\n if (result !== undefined) {\n output(result);\n }\n } catch (err) {\n if (err instanceof ValidationError) {\n throw new ExitError(\n `${err.message}\\nRun '${definition.name} ${selectedCommand} --help' for usage.`,\n );\n }\n throw err;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"define-app.mjs","names":["fullDefinition: AppDefinition<TTools>","app: KlyApp<TTools>","mode"],"sources":["../src/define-app.ts"],"sourcesContent":["import { createModelsContext } from \"./ai/context\";\nimport {\n generateMultiToolsHelp,\n generateSingleToolHelp,\n generateToolHelp,\n getMissingRequiredFields,\n isHelpRequested,\n isVersionRequested,\n parseCliArgs,\n parseSubcommand,\n} from \"./cli\";\nimport { EXIT_CODES } from \"./shared/constants\";\nimport { ExitError, ExitWarning } from \"./shared/errors\";\nimport { detectMode, isSandbox } from \"./shared/runtime-mode\";\nimport type { AnyTool, AppDefinition, KlyApp } from \"./types\";\nimport { ValidationError } from \"./types\";\nimport { cancel, error, form, isTTY, output, select } from \"./ui\";\n\n/**\n * Get the appropriate models context based on runtime environment\n */\nasync function _getModelsContext() {\n if (isSandbox()) {\n // In sandbox: use IPC-based context\n // Dynamic import for runtime-specific module loading\n const m = await import(\"./sandbox/sandboxed-context\");\n return m.getSandboxedContext().modelsContext;\n }\n // Outside sandbox: use direct file access\n return createModelsContext();\n}\n\n/**\n * Get the invoke directory based on runtime environment\n */\nasync function _getInvokeDir(): Promise<string | undefined> {\n if (isSandbox()) {\n // In sandbox: get from sandboxed context\n const m = await import(\"./sandbox/sandboxed-context\");\n return m.getSandboxedContext().invokeDir;\n }\n // Outside sandbox: not available (programmatic mode)\n return undefined;\n}\n\n/**\n * Define a Kly app with tools\n *\n * @example\n * ```typescript\n * import { defineApp, tool } from \"kly\"\n * import { z } from \"zod\"\n *\n * const helloTool = tool({\n * name: \"hello\",\n * description: \"Say hello\",\n * inputSchema: z.object({ name: z.string() }),\n * execute: async ({ name }) => `Hello, ${name}!`,\n * })\n *\n * defineApp({\n * name: \"my-app\",\n * version: \"0.1.0\",\n * description: \"My CLI app\",\n * tools: [helloTool],\n * })\n * // CLI: `my-app hello --name=World`\n * // MCP: exposes tool as my-app_hello\n * ```\n */\nexport function defineApp<TTools extends AnyTool[]>(\n definition: AppDefinition<TTools>,\n): KlyApp<TTools> {\n // Normalize definition with defaults\n const fullDefinition: AppDefinition<TTools> = {\n name: definition.name ?? \"unknown\",\n version: definition.version ?? \"0.0.0\",\n description: definition.description ?? \"\",\n tools: definition.tools,\n instructions: definition.instructions,\n permissions: definition.permissions,\n };\n\n // Build tools map\n const toolsMap = new Map<string, AnyTool>();\n for (const tool of fullDefinition.tools) {\n toolsMap.set(tool.name, tool);\n }\n\n const app: KlyApp<TTools> = {\n definition: fullDefinition,\n tools: toolsMap,\n\n async execute(toolName: string, providedArgs?: Record<string, unknown>): Promise<unknown> {\n const mode = detectMode();\n const tool = toolsMap.get(toolName);\n\n if (!tool) {\n const available = Array.from(toolsMap.keys()).join(\", \");\n throw new Error(`Unknown tool: ${toolName}. Available tools: ${available}`);\n }\n\n // Validate with schema\n const result = await tool.inputSchema[\"~standard\"].validate(providedArgs ?? {});\n\n if (result.issues) {\n throw new ValidationError(result.issues);\n }\n\n // Execute with appropriate context (sandboxed or direct)\n const execResult = await tool.execute(result.value, {\n mode,\n models: await _getModelsContext(),\n invokeDir: await _getInvokeDir(),\n });\n return execResult;\n },\n };\n\n // Auto-run based on mode\n const mode = detectMode();\n if (mode === \"cli\") {\n runCli(app, fullDefinition).catch((err) => {\n // Check for ExitWarning (user cancellation - graceful exit)\n const isExitWarning = err instanceof ExitWarning || err?.name === \"ExitWarning\";\n if (isExitWarning) {\n if (err.message) {\n cancel(err.message);\n }\n process.exit(EXIT_CODES.CANCELLED);\n }\n\n // Check for ExitError\n const isExitError = err instanceof ExitError || err?.name === \"ExitError\";\n const exitCode = isExitError ? (err.exitCode ?? 1) : 1;\n const message = typeof err === \"string\" ? err : err?.message || String(err);\n\n if (isExitError) {\n if (message) {\n error(message);\n }\n } else {\n error(\"Fatal error:\", [message]);\n }\n\n process.exit(exitCode);\n });\n } else if (mode === \"mcp\") {\n // Dynamically import MCP server to avoid bundling it in CLI mode\n import(\"./mcp\").then(({ startMcpServer }) => {\n startMcpServer(app).catch((err) => {\n const message = typeof err === \"string\" ? err : err?.message || String(err);\n error(\"MCP server error:\", [message]);\n process.exit(1);\n });\n });\n }\n\n return app;\n}\n\n/**\n * Run app in CLI mode\n */\nasync function runCli<TTools extends AnyTool[]>(\n app: KlyApp<TTools>,\n definition: AppDefinition<TTools>,\n): Promise<void> {\n const argv = process.argv.slice(2);\n const isSingleTool = definition.tools.length === 1;\n\n // Handle --help and --version without TUI\n if (isHelpRequested(argv)) {\n if (isSingleTool) {\n output(generateSingleToolHelp(definition, definition.tools[0]!));\n } else {\n const { subcommand } = parseSubcommand(argv);\n if (subcommand) {\n const tool = app.tools.get(subcommand);\n if (tool) {\n output(generateToolHelp(definition.name ?? \"unknown\", tool));\n } else {\n output(generateMultiToolsHelp(definition));\n }\n } else {\n output(generateMultiToolsHelp(definition));\n }\n }\n return;\n }\n\n if (isVersionRequested(argv)) {\n output(`${definition.name ?? \"unknown\"} v${definition.version ?? \"0.0.0\"}`);\n return;\n }\n\n // Run the appropriate CLI mode\n if (isSingleTool) {\n await runSingleToolCli(app, definition);\n } else {\n await runMultiToolsCli(app, definition);\n }\n // Clean exit\n process.exit(0);\n}\n\n/**\n * Run single tool app in CLI mode (no subcommand needed)\n */\nasync function runSingleToolCli<TTools extends AnyTool[]>(\n app: KlyApp<TTools>,\n definition: AppDefinition<TTools>,\n): Promise<void> {\n const argv = process.argv.slice(2);\n const tool = definition.tools[0]!;\n const interactive = isTTY();\n\n // Parse args\n let parsedArgs = parseCliArgs(argv) as Record<string, unknown>;\n\n // Check for missing required fields and prompt if in TTY mode\n // In sandbox mode, form() will use IPC to prompt in the host process\n if (interactive) {\n const missingFields = getMissingRequiredFields(tool.inputSchema, parsedArgs);\n\n if (missingFields.length > 0) {\n const additionalArgs = await form({ fields: missingFields });\n parsedArgs = { ...parsedArgs, ...additionalArgs };\n }\n }\n\n try {\n const result = await app.execute(tool.name, parsedArgs);\n if (result !== undefined) {\n output(result);\n }\n } catch (err) {\n if (err instanceof ValidationError) {\n throw new ExitError(`${err.message}\\nRun with --help for usage information.`);\n }\n throw err;\n }\n}\n\n/**\n * Run multi tools app in CLI mode (with subcommands)\n */\nasync function runMultiToolsCli<TTools extends AnyTool[]>(\n app: KlyApp<TTools>,\n definition: AppDefinition<TTools>,\n): Promise<void> {\n const argv = process.argv.slice(2);\n let { subcommand } = parseSubcommand(argv);\n const interactive = isTTY();\n\n // Get rest args (subcommand already parsed above)\n const { args: restArgs } = parseSubcommand(argv);\n\n // Interactive tool selection if no subcommand and in TTY mode\n // In sandbox mode, select() will use IPC to prompt in the host process\n if (!subcommand) {\n if (interactive) {\n // Show interactive menu to select a tool\n const toolOptions = definition.tools.map((t) => ({\n name: t.name,\n description: t.description,\n value: t.name,\n }));\n subcommand = await select({\n options: toolOptions,\n prompt: `${definition.name} - Select a command`,\n });\n } else {\n throw new ExitError(\n `No subcommand provided.\\nAvailable commands: ${definition.tools.map((t) => t.name).join(\", \")}\\nRun '${definition.name} --help' for usage.`,\n );\n }\n }\n\n // At this point subcommand is guaranteed to be a string\n const selectedCommand = subcommand as string;\n\n const tool = app.tools.get(selectedCommand);\n if (!tool) {\n throw new ExitError(\n `Unknown command '${selectedCommand}'.\\nAvailable commands: ${definition.tools.map((t) => t.name).join(\", \")}\\nRun '${definition.name} --help' for usage.`,\n );\n }\n\n // Parse args\n let parsedArgs = parseCliArgs(restArgs) as Record<string, unknown>;\n\n // Check for missing required fields and prompt if in TTY mode\n // In sandbox mode, form() will use IPC to prompt in the host process\n if (interactive) {\n const missingFields = getMissingRequiredFields(tool.inputSchema, parsedArgs);\n\n if (missingFields.length > 0) {\n const additionalArgs = await form({ fields: missingFields });\n parsedArgs = { ...parsedArgs, ...additionalArgs };\n }\n }\n\n try {\n const result = await app.execute(selectedCommand, parsedArgs);\n if (result !== undefined) {\n output(result);\n }\n } catch (err) {\n if (err instanceof ValidationError) {\n throw new ExitError(\n `${err.message}\\nRun '${definition.name} ${selectedCommand} --help' for usage.`,\n );\n }\n throw err;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;AAqBA,eAAe,oBAAoB;AACjC,KAAI,WAAW,CAIb,SADU,MAAM,OAAO,oCACd,qBAAqB,CAAC;AAGjC,QAAO,qBAAqB;;;;;AAM9B,eAAe,gBAA6C;AAC1D,KAAI,WAAW,CAGb,SADU,MAAM,OAAO,oCACd,qBAAqB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BnC,SAAgB,UACd,YACgB;CAEhB,MAAMA,iBAAwC;EAC5C,MAAM,WAAW,QAAQ;EACzB,SAAS,WAAW,WAAW;EAC/B,aAAa,WAAW,eAAe;EACvC,OAAO,WAAW;EAClB,cAAc,WAAW;EACzB,aAAa,WAAW;EACzB;CAGD,MAAM,2BAAW,IAAI,KAAsB;AAC3C,MAAK,MAAM,QAAQ,eAAe,MAChC,UAAS,IAAI,KAAK,MAAM,KAAK;CAG/B,MAAMC,MAAsB;EAC1B,YAAY;EACZ,OAAO;EAEP,MAAM,QAAQ,UAAkB,cAA0D;GACxF,MAAMC,SAAO,YAAY;GACzB,MAAM,OAAO,SAAS,IAAI,SAAS;AAEnC,OAAI,CAAC,MAAM;IACT,MAAM,YAAY,MAAM,KAAK,SAAS,MAAM,CAAC,CAAC,KAAK,KAAK;AACxD,UAAM,IAAI,MAAM,iBAAiB,SAAS,qBAAqB,YAAY;;GAI7E,MAAM,SAAS,MAAM,KAAK,YAAY,aAAa,SAAS,gBAAgB,EAAE,CAAC;AAE/E,OAAI,OAAO,OACT,OAAM,IAAI,gBAAgB,OAAO,OAAO;AAS1C,UALmB,MAAM,KAAK,QAAQ,OAAO,OAAO;IAClD;IACA,QAAQ,MAAM,mBAAmB;IACjC,WAAW,MAAM,eAAe;IACjC,CAAC;;EAGL;CAGD,MAAM,OAAO,YAAY;AACzB,KAAI,SAAS,MACX,QAAO,KAAK,eAAe,CAAC,OAAO,QAAQ;AAGzC,MADsB,eAAe,eAAe,KAAK,SAAS,eAC/C;AACjB,OAAI,IAAI,QACN,QAAO,IAAI,QAAQ;AAErB,WAAQ,KAAK,WAAW,UAAU;;EAIpC,MAAM,cAAc,eAAe,aAAa,KAAK,SAAS;EAC9D,MAAM,WAAW,cAAe,IAAI,YAAY,IAAK;EACrD,MAAM,UAAU,OAAO,QAAQ,WAAW,MAAM,KAAK,WAAW,OAAO,IAAI;AAE3E,MAAI,aACF;OAAI,QACF,OAAM,QAAQ;QAGhB,OAAM,gBAAgB,CAAC,QAAQ,CAAC;AAGlC,UAAQ,KAAK,SAAS;GACtB;UACO,SAAS,MAElB,QAAO,mBAAS,MAAM,EAAE,qBAAqB;AAC3C,iBAAe,IAAI,CAAC,OAAO,QAAQ;AAEjC,SAAM,qBAAqB,CADX,OAAO,QAAQ,WAAW,MAAM,KAAK,WAAW,OAAO,IAAI,CACvC,CAAC;AACrC,WAAQ,KAAK,EAAE;IACf;GACF;AAGJ,QAAO;;;;;AAMT,eAAe,OACb,KACA,YACe;CACf,MAAM,OAAO,QAAQ,KAAK,MAAM,EAAE;CAClC,MAAM,eAAe,WAAW,MAAM,WAAW;AAGjD,KAAI,gBAAgB,KAAK,EAAE;AACzB,MAAI,aACF,QAAO,uBAAuB,YAAY,WAAW,MAAM,GAAI,CAAC;OAC3D;GACL,MAAM,EAAE,eAAe,gBAAgB,KAAK;AAC5C,OAAI,YAAY;IACd,MAAM,OAAO,IAAI,MAAM,IAAI,WAAW;AACtC,QAAI,KACF,QAAO,iBAAiB,WAAW,QAAQ,WAAW,KAAK,CAAC;QAE5D,QAAO,uBAAuB,WAAW,CAAC;SAG5C,QAAO,uBAAuB,WAAW,CAAC;;AAG9C;;AAGF,KAAI,mBAAmB,KAAK,EAAE;AAC5B,SAAO,GAAG,WAAW,QAAQ,UAAU,IAAI,WAAW,WAAW,UAAU;AAC3E;;AAIF,KAAI,aACF,OAAM,iBAAiB,KAAK,WAAW;KAEvC,OAAM,iBAAiB,KAAK,WAAW;AAGzC,SAAQ,KAAK,EAAE;;;;;AAMjB,eAAe,iBACb,KACA,YACe;CACf,MAAM,OAAO,QAAQ,KAAK,MAAM,EAAE;CAClC,MAAM,OAAO,WAAW,MAAM;CAC9B,MAAM,cAAc,OAAO;CAG3B,IAAI,aAAa,aAAa,KAAK;AAInC,KAAI,aAAa;EACf,MAAM,gBAAgB,yBAAyB,KAAK,aAAa,WAAW;AAE5E,MAAI,cAAc,SAAS,GAAG;GAC5B,MAAM,iBAAiB,MAAM,KAAK,EAAE,QAAQ,eAAe,CAAC;AAC5D,gBAAa;IAAE,GAAG;IAAY,GAAG;IAAgB;;;AAIrD,KAAI;EACF,MAAM,SAAS,MAAM,IAAI,QAAQ,KAAK,MAAM,WAAW;AACvD,MAAI,WAAW,OACb,QAAO,OAAO;UAET,KAAK;AACZ,MAAI,eAAe,gBACjB,OAAM,IAAI,UAAU,GAAG,IAAI,QAAQ,0CAA0C;AAE/E,QAAM;;;;;;AAOV,eAAe,iBACb,KACA,YACe;CACf,MAAM,OAAO,QAAQ,KAAK,MAAM,EAAE;CAClC,IAAI,EAAE,eAAe,gBAAgB,KAAK;CAC1C,MAAM,cAAc,OAAO;CAG3B,MAAM,EAAE,MAAM,aAAa,gBAAgB,KAAK;AAIhD,KAAI,CAAC,WACH,KAAI,YAOF,cAAa,MAAM,OAAO;EACxB,SANkB,WAAW,MAAM,KAAK,OAAO;GAC/C,MAAM,EAAE;GACR,aAAa,EAAE;GACf,OAAO,EAAE;GACV,EAAE;EAGD,QAAQ,GAAG,WAAW,KAAK;EAC5B,CAAC;KAEF,OAAM,IAAI,UACR,gDAAgD,WAAW,MAAM,KAAK,MAAM,EAAE,KAAK,CAAC,KAAK,KAAK,CAAC,SAAS,WAAW,KAAK,qBACzH;CAKL,MAAM,kBAAkB;CAExB,MAAM,OAAO,IAAI,MAAM,IAAI,gBAAgB;AAC3C,KAAI,CAAC,KACH,OAAM,IAAI,UACR,oBAAoB,gBAAgB,0BAA0B,WAAW,MAAM,KAAK,MAAM,EAAE,KAAK,CAAC,KAAK,KAAK,CAAC,SAAS,WAAW,KAAK,qBACvI;CAIH,IAAI,aAAa,aAAa,SAAS;AAIvC,KAAI,aAAa;EACf,MAAM,gBAAgB,yBAAyB,KAAK,aAAa,WAAW;AAE5E,MAAI,cAAc,SAAS,GAAG;GAC5B,MAAM,iBAAiB,MAAM,KAAK,EAAE,QAAQ,eAAe,CAAC;AAC5D,gBAAa;IAAE,GAAG;IAAY,GAAG;IAAgB;;;AAIrD,KAAI;EACF,MAAM,SAAS,MAAM,IAAI,QAAQ,iBAAiB,WAAW;AAC7D,MAAI,WAAW,OACb,QAAO,OAAO;UAET,KAAK;AACZ,MAAI,eAAe,gBACjB,OAAM,IAAI,UACR,GAAG,IAAI,QAAQ,SAAS,WAAW,KAAK,GAAG,gBAAgB,qBAC5D;AAEH,QAAM"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema-converter.mjs","names":[],"sources":["../../src/mcp/schema-converter.ts"],"sourcesContent":["import * as z from \"zod\";\nimport type { StandardSchemaV1 } from \"../types\";\nimport { log } from \"../ui\";\n\n/**\n * JSON Schema for MCP tool inputs\n */\nexport interface JsonSchema {\n type: string;\n properties?: Record<string, unknown>;\n required?: string[];\n [key: string]: unknown;\n}\n\n/**\n * Convert StandardSchema to JSON Schema for MCP\n *\n * Currently supports Zod schemas (v4+).\n * For other schema libraries (Valibot, ArkType, etc.), add conversion logic here.\n */\nexport function convertToJsonSchema(schema: StandardSchemaV1): JsonSchema {\n // Check if it's a Zod schema by looking for _def property\n const zodSchema = schema as any;\n\n if (zodSchema._def) {\n // It's a Zod 4+ schema - use z.toJSONSchema() function\n const jsonSchema = z.toJSONSchema(zodSchema as z.ZodType, {\n target: \"draft-07\",\n unrepresentable: \"any\",\n io: \"output\",\n });\n\n // Remove $schema property if present (MCP doesn't need it)\n const { $schema, ...rest } = jsonSchema as Record<string, unknown>;\n\n return rest as JsonSchema;\n }\n\n // For other StandardSchema implementations, try to extract schema info\n // This is a fallback that creates a basic JSON schema\n log.warn(\n \"Unknown schema type - using fallback conversion. Consider adding explicit support for your schema library.\",\n );\n\n return {\n type: \"object\",\n properties: {},\n required: [],\n };\n}\n"],"mappings":";;;;;;;;;;AAoBA,SAAgB,oBAAoB,QAAsC;CAExE,MAAM,YAAY;AAElB,KAAI,UAAU,MAAM;
|
|
1
|
+
{"version":3,"file":"schema-converter.mjs","names":[],"sources":["../../src/mcp/schema-converter.ts"],"sourcesContent":["import * as z from \"zod\";\nimport type { StandardSchemaV1 } from \"../types\";\nimport { log } from \"../ui\";\n\n/**\n * JSON Schema for MCP tool inputs\n */\nexport interface JsonSchema {\n type: string;\n properties?: Record<string, unknown>;\n required?: string[];\n [key: string]: unknown;\n}\n\n/**\n * Convert StandardSchema to JSON Schema for MCP\n *\n * Currently supports Zod schemas (v4+).\n * For other schema libraries (Valibot, ArkType, etc.), add conversion logic here.\n */\nexport function convertToJsonSchema(schema: StandardSchemaV1): JsonSchema {\n // Check if it's a Zod schema by looking for _def property\n const zodSchema = schema as any;\n\n if (zodSchema._def) {\n // It's a Zod 4+ schema - use z.toJSONSchema() function\n const jsonSchema = z.toJSONSchema(zodSchema as z.ZodType, {\n target: \"draft-07\",\n unrepresentable: \"any\",\n io: \"output\",\n });\n\n // Remove $schema property if present (MCP doesn't need it)\n // oxlint-disable-next-line no-unused-vars\n const { $schema, ...rest } = jsonSchema as Record<string, unknown>;\n\n return rest as JsonSchema;\n }\n\n // For other StandardSchema implementations, try to extract schema info\n // This is a fallback that creates a basic JSON schema\n log.warn(\n \"Unknown schema type - using fallback conversion. Consider adding explicit support for your schema library.\",\n );\n\n return {\n type: \"object\",\n properties: {},\n required: [],\n };\n}\n"],"mappings":";;;;;;;;;;AAoBA,SAAgB,oBAAoB,QAAsC;CAExE,MAAM,YAAY;AAElB,KAAI,UAAU,MAAM;EAUlB,MAAM,EAAE,SAAS,GAAG,SARD,EAAE,aAAa,WAAwB;GACxD,QAAQ;GACR,iBAAiB;GACjB,IAAI;GACL,CAAC;AAMF,SAAO;;AAKT,KAAI,KACF,6GACD;AAED,QAAO;EACL,MAAM;EACN,YAAY,EAAE;EACd,UAAU,EAAE;EACb"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.mts","names":[],"sources":["../../src/mcp/server.ts"],"sourcesContent":[],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"server.d.mts","names":[],"sources":["../../src/mcp/server.ts"],"sourcesContent":[],"mappings":";;;;;;AAgCA;;;;;;;;;;;;;;;;;;;;;;;iBAAsB,cAAA,MAAoB,SAAS"}
|
package/dist/mcp/server.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.mjs","names":["error"],"sources":["../../src/mcp/server.ts"],"sourcesContent":["import { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport {
|
|
1
|
+
{"version":3,"file":"server.mjs","names":["error"],"sources":["../../src/mcp/server.ts"],"sourcesContent":["import { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { CallToolRequestSchema, ListToolsRequestSchema } from \"@modelcontextprotocol/sdk/types.js\";\nimport type { KlyApp } from \"../types\";\nimport { error } from \"../ui\";\nimport { convertToJsonSchema } from \"./schema-converter\";\n\n/**\n * Start an MCP server for a Kly app\n *\n * This makes all tools from the app available to Claude Desktop/Code via the MCP protocol.\n *\n * @param app - The Kly app instance returned by defineApp()\n *\n * @example\n * ```typescript\n * import { defineApp, tool, startMcpServer } from \"kly\"\n * import { z } from \"zod\"\n *\n * const app = defineApp({\n * name: \"my-app\",\n * version: \"1.0.0\",\n * description: \"My app\",\n * tools: [myTool],\n * })\n *\n * // In MCP mode, start the server\n * if (process.env.KLY_MCP_MODE === \"true\") {\n * await startMcpServer(app)\n * }\n * ```\n */\nexport async function startMcpServer(app: KlyApp): Promise<void> {\n const { definition } = app;\n\n // Create MCP server instance\n const server = new Server(\n {\n name: definition.name ?? \"unknown\",\n version: definition.version ?? \"0.0.0\",\n },\n {\n capabilities: {\n tools: {},\n },\n },\n );\n\n // Handle tools/list - enumerate all tools from the app\n server.setRequestHandler(ListToolsRequestSchema, async () => {\n const tools = definition.tools.map((tool) => ({\n name: tool.name,\n description: tool.description ?? `Execute ${tool.name}`,\n inputSchema: convertToJsonSchema(tool.inputSchema),\n }));\n\n return { tools };\n });\n\n // Handle tools/call - execute a specific tool\n server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const { name, arguments: args } = request.params;\n\n try {\n // Execute the tool through the app's execute method\n const result = await app.execute(name, args as Record<string, unknown>);\n\n // Convert result to MCP content format\n const content = formatToolResult(result);\n\n return {\n content,\n isError: false,\n };\n } catch (error) {\n // Return error in MCP format\n const errorMessage = error instanceof Error ? error.message : String(error);\n\n return {\n content: [\n {\n type: \"text\",\n text: `Error executing tool '${name}': ${errorMessage}`,\n },\n ],\n isError: true,\n };\n }\n });\n\n // Error handling\n server.onerror = (err) => {\n error(`[MCP Error] ${err}`);\n };\n\n process.on(\"SIGINT\", async () => {\n await server.close();\n process.exit(0);\n });\n\n // Start the server with stdio transport\n const transport = new StdioServerTransport();\n await server.connect(transport);\n\n // Note: In MCP mode, we should not log to stdout as it interferes with JSON-RPC\n // The SDK handles logging internally via stderr\n}\n\n/**\n * Format tool result for MCP response\n */\nfunction formatToolResult(result: unknown): Array<{ type: string; text: string }> {\n if (result === undefined || result === null) {\n return [\n {\n type: \"text\",\n text: \"Success\",\n },\n ];\n }\n\n // If result is already a string, use it directly\n if (typeof result === \"string\") {\n return [\n {\n type: \"text\",\n text: result,\n },\n ];\n }\n\n // Otherwise, stringify the result as JSON\n return [\n {\n type: \"text\",\n text: JSON.stringify(result, null, 2),\n },\n ];\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCA,eAAsB,eAAe,KAA4B;CAC/D,MAAM,EAAE,eAAe;CAGvB,MAAM,SAAS,IAAI,OACjB;EACE,MAAM,WAAW,QAAQ;EACzB,SAAS,WAAW,WAAW;EAChC,EACD,EACE,cAAc,EACZ,OAAO,EAAE,EACV,EACF,CACF;AAGD,QAAO,kBAAkB,wBAAwB,YAAY;AAO3D,SAAO,EAAE,OANK,WAAW,MAAM,KAAK,UAAU;GAC5C,MAAM,KAAK;GACX,aAAa,KAAK,eAAe,WAAW,KAAK;GACjD,aAAa,oBAAoB,KAAK,YAAY;GACnD,EAAE,EAEa;GAChB;AAGF,QAAO,kBAAkB,uBAAuB,OAAO,YAAY;EACjE,MAAM,EAAE,MAAM,WAAW,SAAS,QAAQ;AAE1C,MAAI;AAOF,UAAO;IACL,SAHc,iBAHD,MAAM,IAAI,QAAQ,MAAM,KAAgC,CAG/B;IAItC,SAAS;IACV;WACMA,SAAO;AAId,UAAO;IACL,SAAS,CACP;KACE,MAAM;KACN,MAAM,yBAAyB,KAAK,KANrBA,mBAAiB,QAAQA,QAAM,UAAU,OAAOA,QAAM;KAOtE,CACF;IACD,SAAS;IACV;;GAEH;AAGF,QAAO,WAAW,QAAQ;AACxB,QAAM,eAAe,MAAM;;AAG7B,SAAQ,GAAG,UAAU,YAAY;AAC/B,QAAM,OAAO,OAAO;AACpB,UAAQ,KAAK,EAAE;GACf;CAGF,MAAM,YAAY,IAAI,sBAAsB;AAC5C,OAAM,OAAO,QAAQ,UAAU;;;;;AASjC,SAAS,iBAAiB,QAAwD;AAChF,KAAI,WAAW,UAAa,WAAW,KACrC,QAAO,CACL;EACE,MAAM;EACN,MAAM;EACP,CACF;AAIH,KAAI,OAAO,WAAW,SACpB,QAAO,CACL;EACE,MAAM;EACN,MAAM;EACP,CACF;AAIH,QAAO,CACL;EACE,MAAM;EACN,MAAM,KAAK,UAAU,QAAQ,MAAM,EAAE;EACtC,CACF"}
|
|
@@ -1 +1 @@
|
|
|
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(\n appId: string,\n appName: string,\n): Promise<boolean> {\n // Check if running in TTY mode\n if (!isTTY()) {\n error(\n `Permission required: App \"${appName}\" (${appId}) wants to access your API keys.`,\n );\n error(\n \"Set KLY_TRUST_ALL=true environment variable to grant access in non-interactive mode.\",\n );\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 = [\n \"api.openai.com\",\n \"*.anthropic.com\",\n \"generativelanguage.googleapis.com\",\n ];\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(\n appId: string,\n): 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,kBACb,OACA,SACkB;AAElB,KAAI,CAAC,OAAO,EAAE;AACZ,QACE,6BAA6B,QAAQ,KAAK,MAAM,kCACjD;AACD,QACE,uFACD;AACD,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
|
+
{"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 +1 @@
|
|
|
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 (\n typeof msg === \"object\" &&\n msg !== null &&\n \"type\" in msg &&\n msg.type === \"init\"\n );\n}\n\nexport function isExecutionCompleteMessage(\n msg: unknown,\n): msg is ExecutionCompleteMessage {\n return (\n typeof msg === \"object\" &&\n msg !== null &&\n \"type\" in msg &&\n msg.type === \"complete\"\n );\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 =\n | \"red\"\n | \"green\"\n | \"yellow\"\n | \"blue\"\n | \"magenta\"\n | \"cyan\"\n | \"white\"\n | \"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>(\n \"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 =\n 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 (\n global as { __KLY_SANDBOXED_CONTEXT__?: unknown }\n ).__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(\n success: boolean,\n result?: unknown,\n error?: string,\n): 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
|
+
{"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 +1 @@
|
|
|
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>(
|
|
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 +1 @@
|
|
|
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>(\
|
|
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"}
|
package/dist/tool.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool.d.mts","names":[],"sources":["../src/tool.ts"],"sourcesContent":[],"mappings":";;;;;;AAAsE;UAK5D,SAAyB,CAAA,eAAA,gBAAA,EAAA,UAAA,OAAA,CAAA,
|
|
1
|
+
{"version":3,"file":"tool.d.mts","names":[],"sources":["../src/tool.ts"],"sourcesContent":[],"mappings":";;;;;;AAAsE;UAK5D,SAAyB,CAAA,eAAA,gBAAA,EAAA,UAAA,OAAA,CAAA,SAA6C,cAA7C,CACjC,MADiC,EAEjC,OAFiC,CAAA,CAAA;EACjC;EACA,IAAA,EAAA,MAAA;;;AAiCF;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAgB,oBAAoB,iDACtB,UAAU,QAAQ,WAC7B,KAAK,QAAQ"}
|
package/dist/tool.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool.mjs","names":[],"sources":["../src/tool.ts"],"sourcesContent":["import type { StandardSchemaV1, Tool, ToolDefinition } from \"./types\";\n\n/**\n * Tool definition input with required name\n */\ninterface ToolInput<TInput extends StandardSchemaV1, TResult = unknown
|
|
1
|
+
{"version":3,"file":"tool.mjs","names":[],"sources":["../src/tool.ts"],"sourcesContent":["import type { StandardSchemaV1, Tool, ToolDefinition } from \"./types\";\n\n/**\n * Tool definition input with required name\n */\ninterface ToolInput<TInput extends StandardSchemaV1, TResult = unknown> extends ToolDefinition<\n TInput,\n TResult\n> {\n /** Tool name (used as subcommand in CLI, tool name in MCP) */\n name: string;\n}\n\n/**\n * Create a type-safe tool definition\n *\n * This is a helper function that enables TypeScript to infer the types\n * of execute arguments from the inputSchema. Without this helper,\n * TypeScript cannot establish the connection between schema and execute.\n *\n * @example\n * ```typescript\n * import { tool } from \"kly\"\n * import { z } from \"zod\"\n *\n * const weatherTool = tool({\n * name: \"weather\",\n * description: \"Get weather for a location\",\n * inputSchema: z.object({\n * city: z.string().describe(\"City name\"),\n * unit: z.enum([\"celsius\", \"fahrenheit\"]).default(\"celsius\"),\n * }),\n * execute: async ({ city, unit }) => {\n * // city is inferred as string\n * // unit is inferred as \"celsius\" | \"fahrenheit\"\n * return fetchWeather(city, unit)\n * },\n * })\n * ```\n */\nexport function tool<TInput extends StandardSchemaV1, TResult = unknown>(\n definition: ToolInput<TInput, TResult>,\n): Tool<TInput, TResult> {\n return {\n ...definition,\n _brand: \"Tool\" as const,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwCA,SAAgB,KACd,YACuB;AACvB,QAAO;EACL,GAAG;EACH,QAAQ;EACT"}
|
package/dist/types.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.mts","names":[],"sources":["../src/types.ts"],"sourcesContent":[],"mappings":";;;;;;AAA0E;KAKrE,WACS,CAAA,CAAA,CAAA,GAAA,QAAK,MAAL,CAAK,IAAA,CAAA,CAAE,CAAF,CAAA,SAAA,MAAA,GAAsB,WAAtB,CAAkC,CAAlC,CAAoC,CAApC,CAAA,CAAA,GAA0C,CAA1C,CAA4C,CAA5C,CAAA,EAAE;;;;;AAA0C,UAO9C,gBAP8C,CAAA,QAAA,OAAA,EAAA,SAOH,KAPG,CAAA,CAAA;EAAC,SAAA,WAAA,EAQxC,gBAAA,CAAiB,KARuB,CAQjB,KARiB,EAQV,MARU,CAAA;AAOhE;AAA4D,kBAInC,gBAAA,CAJmC;EACb,UAAA,KAAA,CAAA,QAAA,OAAA,EAAA,SAIH,KAJG,CAAA,CAAA;IAAO,SAAA,OAAA,EAAA,CAAA;IAA9B,SAAA,MAAiB,EAAA,MAAA;IAAK,SAAA,QAAA,EAAA,CAAA,KAAA,EAAA,OAAA,EAAA,
|
|
1
|
+
{"version":3,"file":"types.d.mts","names":[],"sources":["../src/types.ts"],"sourcesContent":[],"mappings":"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"}
|
package/dist/types.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.mjs","names":["issues: ReadonlyArray<StandardSchemaV1.Issue>"],"sources":["../src/types.ts"],"sourcesContent":["import type { SandboxRuntimeConfig } from \"@anthropic-ai/sandbox-runtime\";\n\n/**\n * Deep partial type - makes all properties optional recursively\n */\ntype DeepPartial<T> = {\n [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];\n};\n\n/**\n * Standard Schema V1 interface (inlined to avoid external dependency)\n * @see https://github.com/standard-schema/standard-schema\n */\nexport interface StandardSchemaV1<Input = unknown, Output = Input> {\n readonly \"~standard\": StandardSchemaV1.Props<Input, Output>;\n}\n\nexport declare namespace StandardSchemaV1 {\n interface Props<Input = unknown, Output = Input> {\n readonly version: 1;\n readonly vendor: string;\n readonly validate: (\n value: unknown,\n ) => Result<Output> | Promise<Result<Output>>;\n readonly types?: Types<Input, Output>;\n }\n\n type Result<Output> = SuccessResult<Output> | FailureResult;\n\n interface SuccessResult<Output> {\n readonly value: Output;\n readonly issues?: undefined;\n }\n\n interface FailureResult {\n readonly issues: ReadonlyArray<Issue>;\n }\n\n interface Issue {\n readonly message: string;\n readonly path?: ReadonlyArray<PropertyKey | PathSegment>;\n }\n\n interface PathSegment {\n readonly key: PropertyKey;\n }\n\n interface Types<Input = unknown, Output = Input> {\n readonly input: Input;\n readonly output: Output;\n }\n}\n\n/**\n * Extract output type from a Standard Schema\n */\nexport type InferOutput<TSchema> =\n TSchema extends StandardSchemaV1<unknown, infer Output> ? Output : never;\n\n/**\n * Runtime execution mode\n */\nexport type RuntimeMode = \"cli\" | \"mcp\" | \"programmatic\";\n\n/**\n * Model configuration information (basic info without secrets)\n */\nexport interface ModelInfo {\n /** Model configuration name */\n name: string;\n /** Provider (openai, anthropic, etc.) */\n provider: string;\n /** Model name (e.g., gpt-4, claude-3-opus) */\n model?: string;\n /** Whether this is the current active model */\n isCurrent: boolean;\n}\n\n/**\n * Full model configuration including API keys\n */\nexport interface ModelConfig {\n /** Provider (openai, anthropic, etc.) */\n provider: string;\n /** Model name (e.g., gpt-4, claude-3-opus) */\n model?: string;\n /** API key */\n apiKey?: string;\n /** Base URL */\n baseURL?: string;\n}\n\n/**\n * Models management interface\n * Provides access to configured LLM models and their credentials\n */\nexport interface ModelsContext {\n /**\n * List all configured models (without API keys)\n */\n list(): ModelInfo[];\n\n /**\n * Get the current active model info\n */\n getCurrent(): ModelInfo | null;\n\n /**\n * Get a specific model info by name\n */\n get(name: string): ModelInfo | null;\n\n /**\n * Get full configuration for a model (including API key)\n *\n * ⚠️ SECURITY: This method requires user permission\n * The first time this is called, the user will be prompted to grant permission.\n * Permission can be granted for:\n * - One time only\n * - Always for this app\n * - Denied\n *\n * @param name - Model name (if not specified, returns current model)\n * @returns Full config or null if not found/configured\n * @throws Error if permission is denied\n *\n * @example\n * ```typescript\n * const config = await context.models.getConfigAsync();\n * if (config) {\n * // Use config.provider, config.apiKey, config.model etc.\n * }\n * ```\n */\n getConfigAsync(name?: string): Promise<ModelConfig | null>;\n}\n\n/**\n * Context passed to the execute function\n */\nexport interface ExecuteContext {\n /** Current runtime mode */\n mode: RuntimeMode;\n /** Abort signal for cancellation */\n abortSignal?: AbortSignal;\n /**\n * Models management\n * Access configured LLM models and their credentials\n * @example\n * ```typescript\n * const config = context.models.getConfig();\n * if (config) {\n * // Use config.provider, config.apiKey, config.model etc.\n * // to create your own AI SDK provider instance\n * }\n * ```\n */\n models: ModelsContext;\n /**\n * Working directory where `kly run` was invoked\n * This is the directory from which the user ran the command,\n * not the directory where the script is located\n * @example\n * ```typescript\n * // User runs: cd /home/user/project && kly run /home/user/tools/my-tool.ts\n * // context.invokeDir === \"/home/user/project\"\n * // process.cwd() === \"/home/user/tools\" (script directory)\n * ```\n */\n invokeDir?: string;\n}\n\n/**\n * Tool definition (Vercel AI SDK style)\n * Core building block that can be used standalone or within defineApp\n */\nexport interface ToolDefinition<\n TInput extends StandardSchemaV1,\n TResult = unknown,\n> {\n /** Description of what the tool does (used by LLM for tool selection) */\n description?: string;\n /** Input schema (Standard Schema compliant: Zod, Valibot, ArkType, etc.) */\n inputSchema: TInput;\n /** Execute function that performs the tool's action */\n execute: (\n args: InferOutput<TInput>,\n context: ExecuteContext,\n ) => Promise<TResult>;\n}\n\n/**\n * Tool instance returned by tool()\n * Each tool has a name for CLI subcommand and MCP registration\n */\nexport interface Tool<TInput extends StandardSchemaV1, TResult = unknown>\n extends ToolDefinition<TInput, TResult> {\n /** Tool name (used as subcommand in CLI, tool name in MCP) */\n name: string;\n /** Type brand for tool identification */\n readonly _brand: \"Tool\";\n}\n\n/**\n * Any tool type for collections\n */\nexport type AnyTool = Tool<StandardSchemaV1, unknown>;\n\n/**\n * Base app metadata\n * All fields are optional - defaults will be used if not provided\n */\nexport interface AppMetadata {\n /** App name (defaults to \"unknown\" if not provided) */\n name?: string;\n /** Semantic version (defaults to \"0.0.0\" if not provided) */\n version?: string;\n /** App description (defaults to \"\" if not provided) */\n description?: string;\n}\n\n/**\n * App definition with tools array\n *\n * @example\n * ```typescript\n * defineApp({\n * name: \"my-app\",\n * version: \"0.1.0\",\n * description: \"My CLI app\",\n * tools: [greetTool, farewellTool],\n * instructions: \"When user expresses greeting intent, prefer the greet tool\",\n * })\n * ```\n */\n/**\n * App permissions declaration\n * Simplified user-friendly interface for declaring app permissions\n */\nexport interface AppPermissions {\n /**\n * API Keys access - needed to call LLM APIs\n *\n * When enabled:\n * - Grants access to models.getConfigAsync() to retrieve API keys\n * - Automatically allows network access to common LLM API domains:\n * • api.openai.com\n * • *.anthropic.com\n * • generativelanguage.googleapis.com\n * • api.deepseek.com\n *\n * @default false\n */\n apiKeys?: boolean;\n\n /**\n * Sandbox configuration\n * Uses @anthropic-ai/sandbox-runtime configuration directly\n *\n * Defaults when not specified:\n * - Network: No access (apiKeys=true adds LLM APIs automatically)\n * - Filesystem: Current directory read/write only\n * - Protected: ~/.kly, ~/.ssh, ~/.aws, ~/.gnupg always denied\n *\n * Special markers for filesystem paths:\n * - \"*\": User's home directory (allows access to all non-sensitive files)\n * Use this for apps that need broad filesystem access (like project generators)\n *\n * @example\n * ```typescript\n * // Minimal: just need GitHub API access\n * permissions: {\n * sandbox: {\n * network: { allowedDomains: [\"api.github.com\"] }\n * }\n * }\n *\n * // Allow access to all non-sensitive directories\n * permissions: {\n * sandbox: {\n * filesystem: {\n * allowWrite: [\"*\"] // Expands to user's home directory at runtime\n * }\n * }\n * }\n *\n * // Full control\n * permissions: {\n * apiKeys: true, // LLM domains added automatically\n * sandbox: {\n * network: {\n * allowedDomains: [\"api.github.com\"] // Add more domains\n * },\n * filesystem: {\n * allowWrite: [\"./output\", \"/tmp\"],\n * denyRead: [\"/etc\"]\n * }\n * }\n * }\n * ```\n */\n sandbox?: DeepPartial<SandboxRuntimeConfig>;\n}\n\nexport interface AppDefinition<TTools extends AnyTool[] = AnyTool[]>\n extends AppMetadata {\n /** Array of tools */\n tools: TTools;\n /**\n * AI instructions for Skill mode\n * Hints for AI routing when this app is composed with others\n */\n instructions?: string;\n /**\n * Permissions required by this app\n * Declare upfront what your app needs to access\n * @example\n * ```typescript\n * permissions: {\n * apiKeys: true,\n * network: { allow: \"llm-apis\" }\n * }\n * ```\n */\n permissions?: AppPermissions;\n}\n\n/**\n * Kly app instance returned by defineApp\n */\nexport interface KlyApp<TTools extends AnyTool[] = AnyTool[]> {\n /** Original app configuration */\n readonly definition: AppDefinition<TTools>;\n /** Execute a specific tool by name */\n execute(toolName: string, args?: Record<string, unknown>): Promise<unknown>;\n /** Get all available tools */\n readonly tools: Map<string, AnyTool>;\n}\n\n/**\n * Validation error with structured issues\n */\nexport class ValidationError extends Error {\n constructor(public readonly issues: ReadonlyArray<StandardSchemaV1.Issue>) {\n const message = issues\n .map((issue) => {\n const path = issue.path\n ?.map((p) => (typeof p === \"object\" ? p.key : p))\n .join(\".\");\n return path ? `${path}: ${issue.message}` : issue.message;\n })\n .join(\"\\n\");\n super(message);\n this.name = \"ValidationError\";\n }\n}\n"],"mappings":";;;;AAsVA,IAAa,kBAAb,cAAqC,MAAM;CACzC,YAAY,AAAgBA,QAA+C;EACzE,MAAM,UAAU,OACb,KAAK,UAAU;GACd,MAAM,OAAO,MAAM,MACf,KAAK,MAAO,OAAO,MAAM,WAAW,EAAE,MAAM,EAAG,CAChD,KAAK,IAAI;AACZ,UAAO,OAAO,GAAG,KAAK,IAAI,MAAM,YAAY,MAAM;IAClD,CACD,KAAK,KAAK;AACb,QAAM,QAAQ;EATY;AAU1B,OAAK,OAAO"}
|
|
1
|
+
{"version":3,"file":"types.mjs","names":["issues: ReadonlyArray<StandardSchemaV1.Issue>"],"sources":["../src/types.ts"],"sourcesContent":["import type { SandboxRuntimeConfig } from \"@anthropic-ai/sandbox-runtime\";\n\n/**\n * Deep partial type - makes all properties optional recursively\n */\ntype DeepPartial<T> = {\n [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];\n};\n\n/**\n * Standard Schema V1 interface (inlined to avoid external dependency)\n * @see https://github.com/standard-schema/standard-schema\n */\nexport interface StandardSchemaV1<Input = unknown, Output = Input> {\n readonly \"~standard\": StandardSchemaV1.Props<Input, Output>;\n}\n\nexport declare namespace StandardSchemaV1 {\n interface Props<Input = unknown, Output = Input> {\n readonly version: 1;\n readonly vendor: string;\n readonly validate: (value: unknown) => Result<Output> | Promise<Result<Output>>;\n readonly types?: Types<Input, Output>;\n }\n\n type Result<Output> = SuccessResult<Output> | FailureResult;\n\n interface SuccessResult<Output> {\n readonly value: Output;\n readonly issues?: undefined;\n }\n\n interface FailureResult {\n readonly issues: ReadonlyArray<Issue>;\n }\n\n interface Issue {\n readonly message: string;\n readonly path?: ReadonlyArray<PropertyKey | PathSegment>;\n }\n\n interface PathSegment {\n readonly key: PropertyKey;\n }\n\n interface Types<Input = unknown, Output = Input> {\n readonly input: Input;\n readonly output: Output;\n }\n}\n\n/**\n * Extract output type from a Standard Schema\n */\nexport type InferOutput<TSchema> =\n TSchema extends StandardSchemaV1<unknown, infer Output> ? Output : never;\n\n/**\n * Runtime execution mode\n */\nexport type RuntimeMode = \"cli\" | \"mcp\" | \"programmatic\";\n\n/**\n * Model configuration information (basic info without secrets)\n */\nexport interface ModelInfo {\n /** Model configuration name */\n name: string;\n /** Provider (openai, anthropic, etc.) */\n provider: string;\n /** Model name (e.g., gpt-4, claude-3-opus) */\n model?: string;\n /** Whether this is the current active model */\n isCurrent: boolean;\n}\n\n/**\n * Full model configuration including API keys\n */\nexport interface ModelConfig {\n /** Provider (openai, anthropic, etc.) */\n provider: string;\n /** Model name (e.g., gpt-4, claude-3-opus) */\n model?: string;\n /** API key */\n apiKey?: string;\n /** Base URL */\n baseURL?: string;\n}\n\n/**\n * Models management interface\n * Provides access to configured LLM models and their credentials\n */\nexport interface ModelsContext {\n /**\n * List all configured models (without API keys)\n */\n list(): ModelInfo[];\n\n /**\n * Get the current active model info\n */\n getCurrent(): ModelInfo | null;\n\n /**\n * Get a specific model info by name\n */\n get(name: string): ModelInfo | null;\n\n /**\n * Get full configuration for a model (including API key)\n *\n * ⚠️ SECURITY: This method requires user permission\n * The first time this is called, the user will be prompted to grant permission.\n * Permission can be granted for:\n * - One time only\n * - Always for this app\n * - Denied\n *\n * @param name - Model name (if not specified, returns current model)\n * @returns Full config or null if not found/configured\n * @throws Error if permission is denied\n *\n * @example\n * ```typescript\n * const config = await context.models.getConfigAsync();\n * if (config) {\n * // Use config.provider, config.apiKey, config.model etc.\n * }\n * ```\n */\n getConfigAsync(name?: string): Promise<ModelConfig | null>;\n}\n\n/**\n * Context passed to the execute function\n */\nexport interface ExecuteContext {\n /** Current runtime mode */\n mode: RuntimeMode;\n /** Abort signal for cancellation */\n abortSignal?: AbortSignal;\n /**\n * Models management\n * Access configured LLM models and their credentials\n * @example\n * ```typescript\n * const config = context.models.getConfig();\n * if (config) {\n * // Use config.provider, config.apiKey, config.model etc.\n * // to create your own AI SDK provider instance\n * }\n * ```\n */\n models: ModelsContext;\n /**\n * Working directory where `kly run` was invoked\n * This is the directory from which the user ran the command,\n * not the directory where the script is located\n * @example\n * ```typescript\n * // User runs: cd /home/user/project && kly run /home/user/tools/my-tool.ts\n * // context.invokeDir === \"/home/user/project\"\n * // process.cwd() === \"/home/user/tools\" (script directory)\n * ```\n */\n invokeDir?: string;\n}\n\n/**\n * Tool definition (Vercel AI SDK style)\n * Core building block that can be used standalone or within defineApp\n */\nexport interface ToolDefinition<TInput extends StandardSchemaV1, TResult = unknown> {\n /** Description of what the tool does (used by LLM for tool selection) */\n description?: string;\n /** Input schema (Standard Schema compliant: Zod, Valibot, ArkType, etc.) */\n inputSchema: TInput;\n /** Execute function that performs the tool's action */\n execute: (args: InferOutput<TInput>, context: ExecuteContext) => Promise<TResult>;\n}\n\n/**\n * Tool instance returned by tool()\n * Each tool has a name for CLI subcommand and MCP registration\n */\nexport interface Tool<TInput extends StandardSchemaV1, TResult = unknown> extends ToolDefinition<\n TInput,\n TResult\n> {\n /** Tool name (used as subcommand in CLI, tool name in MCP) */\n name: string;\n /** Type brand for tool identification */\n readonly _brand: \"Tool\";\n}\n\n/**\n * Any tool type for collections\n */\nexport type AnyTool = Tool<StandardSchemaV1, unknown>;\n\n/**\n * Base app metadata\n * All fields are optional - defaults will be used if not provided\n */\nexport interface AppMetadata {\n /** App name (defaults to \"unknown\" if not provided) */\n name?: string;\n /** Semantic version (defaults to \"0.0.0\" if not provided) */\n version?: string;\n /** App description (defaults to \"\" if not provided) */\n description?: string;\n}\n\n/**\n * App definition with tools array\n *\n * @example\n * ```typescript\n * defineApp({\n * name: \"my-app\",\n * version: \"0.1.0\",\n * description: \"My CLI app\",\n * tools: [greetTool, farewellTool],\n * instructions: \"When user expresses greeting intent, prefer the greet tool\",\n * })\n * ```\n */\n/**\n * App permissions declaration\n * Simplified user-friendly interface for declaring app permissions\n */\nexport interface AppPermissions {\n /**\n * API Keys access - needed to call LLM APIs\n *\n * When enabled:\n * - Grants access to models.getConfigAsync() to retrieve API keys\n * - Automatically allows network access to common LLM API domains:\n * • api.openai.com\n * • *.anthropic.com\n * • generativelanguage.googleapis.com\n * • api.deepseek.com\n *\n * @default false\n */\n apiKeys?: boolean;\n\n /**\n * Sandbox configuration\n * Uses @anthropic-ai/sandbox-runtime configuration directly\n *\n * Defaults when not specified:\n * - Network: No access (apiKeys=true adds LLM APIs automatically)\n * - Filesystem: Current directory read/write only\n * - Protected: ~/.kly, ~/.ssh, ~/.aws, ~/.gnupg always denied\n *\n * Special markers for filesystem paths:\n * - \"*\": User's home directory (allows access to all non-sensitive files)\n * Use this for apps that need broad filesystem access (like project generators)\n *\n * @example\n * ```typescript\n * // Minimal: just need GitHub API access\n * permissions: {\n * sandbox: {\n * network: { allowedDomains: [\"api.github.com\"] }\n * }\n * }\n *\n * // Allow access to all non-sensitive directories\n * permissions: {\n * sandbox: {\n * filesystem: {\n * allowWrite: [\"*\"] // Expands to user's home directory at runtime\n * }\n * }\n * }\n *\n * // Full control\n * permissions: {\n * apiKeys: true, // LLM domains added automatically\n * sandbox: {\n * network: {\n * allowedDomains: [\"api.github.com\"] // Add more domains\n * },\n * filesystem: {\n * allowWrite: [\"./output\", \"/tmp\"],\n * denyRead: [\"/etc\"]\n * }\n * }\n * }\n * ```\n */\n sandbox?: DeepPartial<SandboxRuntimeConfig>;\n}\n\nexport interface AppDefinition<TTools extends AnyTool[] = AnyTool[]> extends AppMetadata {\n /** Array of tools */\n tools: TTools;\n /**\n * AI instructions for Skill mode\n * Hints for AI routing when this app is composed with others\n */\n instructions?: string;\n /**\n * Permissions required by this app\n * Declare upfront what your app needs to access\n * @example\n * ```typescript\n * permissions: {\n * apiKeys: true,\n * network: { allow: \"llm-apis\" }\n * }\n * ```\n */\n permissions?: AppPermissions;\n}\n\n/**\n * Kly app instance returned by defineApp\n */\nexport interface KlyApp<TTools extends AnyTool[] = AnyTool[]> {\n /** Original app configuration */\n readonly definition: AppDefinition<TTools>;\n /** Execute a specific tool by name */\n execute(toolName: string, args?: Record<string, unknown>): Promise<unknown>;\n /** Get all available tools */\n readonly tools: Map<string, AnyTool>;\n}\n\n/**\n * Validation error with structured issues\n */\nexport class ValidationError extends Error {\n constructor(public readonly issues: ReadonlyArray<StandardSchemaV1.Issue>) {\n const message = issues\n .map((issue) => {\n const path = issue.path?.map((p) => (typeof p === \"object\" ? p.key : p)).join(\".\");\n return path ? `${path}: ${issue.message}` : issue.message;\n })\n .join(\"\\n\");\n super(message);\n this.name = \"ValidationError\";\n }\n}\n"],"mappings":";;;;AA+UA,IAAa,kBAAb,cAAqC,MAAM;CACzC,YAAY,AAAgBA,QAA+C;EACzE,MAAM,UAAU,OACb,KAAK,UAAU;GACd,MAAM,OAAO,MAAM,MAAM,KAAK,MAAO,OAAO,MAAM,WAAW,EAAE,MAAM,EAAG,CAAC,KAAK,IAAI;AAClF,UAAO,OAAO,GAAG,KAAK,IAAI,MAAM,YAAY,MAAM;IAClD,CACD,KAAK,KAAK;AACb,QAAM,QAAQ;EAPY;AAQ1B,OAAK,OAAO"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"confirm.d.mts","names":[],"sources":["../../../src/ui/components/confirm.ts"],"sourcesContent":[],"mappings":";;AAcA;;;;;;;iBAAsB,OAAA,
|
|
1
|
+
{"version":3,"file":"confirm.d.mts","names":[],"sources":["../../../src/ui/components/confirm.ts"],"sourcesContent":[],"mappings":";;AAcA;;;;;;;iBAAsB,OAAA,2CAAgD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"confirm.mjs","names":[],"sources":["../../../src/ui/components/confirm.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 * Simplified confirm function\n *\n * @example\n * ```typescript\n * const proceed = await confirm(\"Continue?\", true);\n * ```\n */\nexport async function confirm(
|
|
1
|
+
{"version":3,"file":"confirm.mjs","names":[],"sources":["../../../src/ui/components/confirm.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 * Simplified confirm function\n *\n * @example\n * ```typescript\n * const proceed = await confirm(\"Continue?\", true);\n * ```\n */\nexport async function confirm(message: string, defaultValue = false): Promise<boolean> {\n // Sandbox mode: use IPC to request confirm from host\n if (isSandbox()) {\n return sendIPCRequest<boolean>(\"prompt:confirm\", { message, defaultValue });\n }\n\n if (!isTTY()) {\n // In MCP mode, warn about using default value for confirmation\n if (isMCP()) {\n p.log.warn(\n `[MCP] Interactive confirmation not available. Using default value (${defaultValue}) for: ${message}`,\n );\n }\n return defaultValue;\n }\n\n const result = await p.confirm({\n message,\n initialValue: defaultValue,\n });\n\n return handleCancel(result);\n}\n"],"mappings":";;;;;;;;;;;;;;;AAcA,eAAsB,QAAQ,SAAiB,eAAe,OAAyB;AAErF,KAAI,WAAW,CACb,QAAO,eAAwB,kBAAkB;EAAE;EAAS;EAAc,CAAC;AAG7E,KAAI,CAAC,OAAO,EAAE;AAEZ,MAAI,OAAO,CACT,GAAE,IAAI,KACJ,sEAAsE,aAAa,SAAS,UAC7F;AAEH,SAAO;;AAQT,QAAO,aALQ,MAAM,EAAE,QAAQ;EAC7B;EACA,cAAc;EACf,CAAC,CAEyB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"form.d.mts","names":[],"sources":["../../../src/ui/components/form.ts"],"sourcesContent":[],"mappings":";;AAUA;AAoBA;AAwBsB,UA5CL,SAAA,CA4CS;
|
|
1
|
+
{"version":3,"file":"form.d.mts","names":[],"sources":["../../../src/ui/components/form.ts"],"sourcesContent":[],"mappings":";;AAUA;AAoBA;AAwBsB,UA5CL,SAAA,CA4CS;EAAS;EAAqB,IAAA,EAAA,MAAA;EAAR;EAAO,KAAA,EAAA,MAAA;;;;;;;;;;;;;;;UAxBtC,UAAA;;UAEP;;;;;;;;;;;;;;;;;;;;;iBAsBY,IAAA,SAAa,aAAa,QAAQ"}
|