1 line
42 KiB
JSON
1 line
42 KiB
JSON
{"repo": "changw98ic/n0vel", "files": {"tool/local_settings_cli.dart": {"f846a62ecdd03158dbd5bff34e1afb7b816d10f9": {"t": 1785745915.3325784, "n": 12719, "h": "9b923ea4c5ee0339", "c": "import 'dart:convert';\nimport 'dart:io';\n\nimport 'package:novel_writer/app/state/local_settings_file.dart';\n\nFuture<void> main(List<String> args) async {\n if (args.isEmpty || args.contains('--help') || args.contains('-h')) {\n _printUsage();\n return;\n }\n\n final file = File(_optionValue(args, '--file') ?? 'setting.json');\n switch (args.first) {\n case 'write-mimo':\n await _writeMimo(file);\n return;\n case 'write-zhipu':\n await _writeZhipu(file);\n return;\n case 'write-routed':\n await _writeRouted(file);\n return;\n case 'decrypt':\n await _decrypt(file, showSecrets: args.contains('--show-secrets'));\n return;\n case 'export-provider-env':\n await _exportProviderEnv(\n settingsFile: file,\n outputFile: File(\n _optionValue(args, '--output') ??\n '.auto-team/jianlai-refine/providers.env',\n ),\n );\n return;\n default:\n stderr.writeln('Unknown command: ${args.first}');\n _printUsage();\n exitCode = 64;\n }\n}\n\nFuture<void> _writeMimo(File file) async {\n final apiKey = Platform.environment['MIMO_API_KEY']?.trim() ?? '';\n if (apiKey.isEmpty) {\n stderr.writeln('MIMO_API_KEY environment variable is required.');\n exitCode = 64;\n return;\n }\n\n await saveEncryptedLocalSettingsFile(\n file: file,\n values: {\n 'providerName': 'Xiaomi MiMo',\n 'baseUrl': 'https://token-plan-cn.xiaomimimo.com/v1',\n 'model': 'mimo-v2.5-pro',\n 'apiKey': apiKey,\n 'maxTokens': 0,\n 'MIMO_API_KEY': apiKey,\n 'MIMO_BASE_URL': 'https://token-plan-cn.xiaomimimo.com/v1',\n 'MIMO_MODEL': 'mimo-v2.5-pro',\n 'REAL_AI_MAX_TOKENS': '0',\n 'REAL_AI_MODEL': 'mimo-v2.5-pro',\n 'REAL_AI_TIMEOUT_MS': '180000',\n 'REAL_AI_MAX_CONCURRENT_REQUESTS': '3',\n },\n );\n stdout.writeln('Encrypted MiMo settings written to ${file.path}.');\n}\n\nFuture<void> _writeZhipu(File file) async {\n final apiKey = Platform.environment['ZHIPU_API_KEY']?.trim() ?? '';\n if (apiKey.isEmpty) {\n stderr.writeln('ZHIPU_API_KEY environment variable is required.');\n exitCode = 64;\n return;\n }\n\n const baseUrl = 'https://api.z.ai/api/coding/paas/v4';\n const model = 'glm-5.1';\n await saveEncryptedLocalSettingsFile(\n file: file,\n values: {\n 'providerName': '智谱 GLM',\n 'baseUrl': baseUrl,\n 'model': model,\n 'apiKey': apiKey,\n 'maxTokens': 0,\n 'ZHIPU_API_KEY': apiKey,\n 'ZHIPU_BASE_URL': baseUrl,\n 'ZHIPU_MODEL': model,\n 'REAL_AI_MAX_TOKENS': '0',\n 'REAL_AI_MODEL': model,\n 'REAL_AI_TIMEOUT_MS': '180000',\n 'REAL_AI_MAX_CONCURRENT_REQUESTS': '3',\n },\n );\n stdout.writeln('Encrypted Zhipu settings written to ${file.path}.');\n}\n\nFuture<void> _writeRouted(File file) async {\n final existing = await loadLocalSettingsFile(file: file);\n final zhipuKey = _firstNonEmpty([\n Platform.environment['ZHIPU_API_KEY'],\n existing['ZHIPU_API_KEY'],\n existing['apiKey'],\n ]);\n if (zhipuKey.isEmpty) {\n stderr.writeln(\n 'ZHIPU_API_KEY environment variable or existing setting.json key is required.',\n );\n exitCode = 64;\n return;\n }\n\n final mimoKey = _firstNonEmpty([\n Platform.environment['MIMO_API_KEY'],\n existing['MIMO_API_KEY'],\n ]);\n final ollamaKey = _firstNonEmpty([\n Platform.environment['OLLAMA_API_KEY'],\n existing['OLLAMA_API_KEY'],\n ]);\n\n const zhipuBaseUrl = 'https://api.z.ai/api/coding/paas/v4';\n const zhipuModel = 'glm-5.1';\n const mimoBaseUrl = 'https://token-plan-cn.xiaomimimo.com/v1';\n const mimoModel = 'mimo-v2.5-pro';\n const ollamaBaseUrl = 'https://ollama.com/v1';\n const ollamaModel = 'kimi-k2.6';\n\n await saveEncryptedLocalSettingsFile(\n file: file,\n values: {\n 'providerName': '智谱 GLM',\n 'baseUrl': zhipuBaseUrl,\n 'model': zhipuModel,\n 'apiKey': zhipuKey,\n 'maxTokens': 0,\n 'ZHIPU_API_KEY': zhipuKey,\n 'ZHIPU_BASE_URL': zhipuBaseUrl,\n 'ZHIPU_MODEL': zhipuModel,\n if (mimoKey.isNotEmpty) 'MIMO_API_KEY': mimoKey,\n 'MIMO_BASE_URL': mimoBaseUrl,\n 'MIMO_MODEL': mimoModel,\n if (ollamaKey.isNotEmpty) 'OLLAMA_API_KEY': ollamaKey,\n 'OLLAMA_BASE_URL': ollamaBaseUrl,\n 'OLLAMA_MODEL': ollamaModel,\n 'REAL_AI_MAX_TOKENS': '0',\n 'REAL_AI_MODEL': zhipuModel,\n 'REAL_AI_TIMEOUT_MS': '180000',\n 'REAL_AI_MAX_CONCURRENT_REQUESTS': '3',\n 'providerProfiles': [\n {\n 'id': 'ollama-kimi',\n 'providerName': 'Ollama Cloud',\n 'baseUrl': ollamaBaseUrl,\n 'model': ollamaModel,\n 'apiKey': ollamaKey,\n },\n {\n 'id': 'mimo',\n 'providerName': 'Xiaomi MiMo',\n 'baseUrl': mimoBaseUrl,\n 'model': mimoModel,\n 'apiKey': mimoKey,\n },\n ],\n 'requestProviderRoutes': [\n {\n 'traceNamePattern': 'scene_director_polish',\n 'providerProfileId': 'ollama-kimi',\n },\n {\n 'traceNamePattern': 'scene_roleplay_turn',\n 'providerProfileId': 'ollama-kimi',\n },\n {\n 'traceNamePattern': 'scene_roleplay_arbitrate',\n 'providerProfileId': 'ollama-kimi',\n },\n {'traceNamePattern': 'scene_beat_resolve', 'providerProfileId': 'mimo'},\n {'traceNamePattern': 'scene_editorial', 'providerProfileId': 'mimo'},\n {'traceNamePattern': 'language_polish', 'providerProfileId': 'mimo'},\n {\n 'traceNamePattern': 'scene_combined_review',\n 'providerProfileId': 'mimo',\n },\n {'traceNamePattern': 'scene_review_*', 'providerProfileId': 'mimo'},\n {\n 'traceNamePattern': 'scene_quality_scoring',\n 'providerProfileId': 'mimo',\n },\n ],\n },\n );\n stdout.writeln('Encrypted routed provider settings written to ${file.path}.');\n if (ollamaKey.isEmpty) {\n stdout.writeln(\n 'OLLAMA_API_KEY is missing; short generation routes will fall back to the default provider until it is added.',\n );\n }\n}\n\nFuture<void> _decrypt(File file, {required bool showSecrets}) async {\n final values = await loadLocalSettingsObjectFile(file: file);\n final printable = showSecrets ? values : _redactSecrets(values);\n stdout.writeln(const JsonEncoder.withIndent(' ').convert(printable));\n}\n\nFuture<void> _exportProviderEnv({\n required File settingsFile,\n required File outputFile,\n}) async {\n final settings = await loadLocalSettingsObjectFile(file: settingsFile);\n final existing = await _loadExistingEnvFile(outputFile);\n final profiles = _providerProfiles(settings['providerProfiles']);\n\n final values = <String, String>{};\n void put(String key, List<Object?> candidates) {\n final value = _firstNonEmpty([\n Platform.environment[key],\n ...candidates.map((candidate) => candidate?.toString()),\n existing[key],\n ]);\n if (value.isNotEmpty) {\n values[key] = value;\n }\n }\n\n final ollamaProfile = profiles['ollama-kimi'] ?? profiles['kimi'];\n final mimoProfile = profiles['mimo'];\n put('ZHIPU_API_KEY', [settings['ZHIPU_API_KEY'], settings['apiKey']]);\n put('ZHIPU_BASE_URL', [settings['ZHIPU_BASE_URL']]);\n put('ZHIPU_MODEL', [settings['ZHIPU_MODEL']]);\n put('OLLAMA_API_KEY', [settings['OLLAMA_API_KEY'], ollamaProfile?['apiKey']]);\n put('OLLAMA_BASE_URL', [\n settings['OLLAMA_BASE_URL'],\n ollamaProfile?['baseUrl'],\n ]);\n put('OLLAMA_MODEL', [settings['OLLAMA_MODEL'], ollamaProfile?['model']]);\n put('MIMO_API_KEY', [settings['MIMO_API_KEY'], mimoProfile?['apiKey']]);\n put('MIMO_BASE_URL', [settings['MIMO_BASE_URL'], mimoProfile?['baseUrl']]);\n put('MIMO_MODEL', [settings['MIMO_MODEL'], mimoProfile?['model']]);\n put('DEEPSEEK_API_KEY', [settings['DEEPSEEK_API_KEY']]);\n put('DEEPSEEK_BASE_URL', [settings['DEEPSEEK_BASE_URL']]);\n put('DEEPSEEK_MODEL', [settings['DEEPSEEK_MODEL']]);\n\n await outputFile.parent.create(recursive: true);\n final keys = values.keys.toList()..sort();\n final buffer = StringBuffer()\n ..writeln(\n '# Generated by tool/local_settings_cli.dart export-provider-env.',\n )\n ..writeln('# This file is local/ignored and may contain API credentials.');\n for (final key in keys) {\n buffer.writeln(\"$key='${_shellSingleQuote(values[key]!)}'\");\n }\n await outputFile.writeAsString(buffer.toString());\n if (!Platform.isWindows) {\n await Process.run('chmod', ['600', outputFile.path]);\n }\n final summary = keys\n .where((key) => key.endsWith('_API_KEY'))\n .map((key) => '$key=set')\n .join(', ');\n stdout.writeln('Provider env written to ${outputFile.path}. $summary');\n}\n\nFuture<Map<String, String>> _loadExistingEnvFile(File file) async {\n if (!await file.exists()) {\n return const {};\n }\n final values = <String, String>{};\n for (final line in const LineSplitter().convert(await file.readAsString())) {\n var trimmed = line.trim();\n if (trimmed.isEmpty || trimmed.startsWith('#')) {\n continue;\n }\n if (trimmed.startsWith('export ')) {\n trimmed = trimmed.substring('export '.length).trimLeft();\n }\n final separator = trimmed.indexOf('=');\n if (separator <= 0) {\n continue;\n }\n final key = trimmed.substring(0, separator).trim();\n var value = trimmed.substring(separator + 1).trim();\n if (value.length >= 2 &&\n ((value.startsWith(\"'\") && value.endsWith(\"'\")) ||\n (value.startsWith('\"') && value.endsWith('\"')))) {\n value = value.substring(1, value.length - 1);\n }\n if (key.isNotEmpty) {\n values[key] = value.replaceAll(\"'\\\"'\\\"'\", \"'\");\n }\n }\n return values;\n}\n\nMap<String, Map<String, Object?>> _providerProfiles(Object? rawProfiles) {\n if (rawProfiles is! List) {\n return const {};\n }\n final profiles = <String, Map<String, Object?>>{};\n for (final rawProfile in rawProfiles) {\n if (rawProfile is! Map) {\n continue;\n }\n final normalized = rawProfile.map(\n (key, value) => MapEntry(key.toString(), value as Object?),\n );\n final id = normalized['id']?.toString().trim();\n if (id != null && id.isNotEmpty) {\n profiles[id] = normalized;\n }\n }\n return profiles;\n}\n\nMap<String, Object?> _redactSecrets(Map<String, Object?> values) {\n return values.map((key, value) {\n final lower = key.toLowerCase();\n if (lower.contains('key') || lower.contains('token')) {\n return MapEntry(key, _previewSecret(value?.toString() ?? ''));\n }\n return MapEntry(key, _redactNested(value));\n });\n}\n\nObject? _redactNested(Object? value) {\n if (value is Map) {\n return value.map((key, child) {\n final normalizedKey = key.toString();\n final lower = normalizedKey.toLowerCase();\n if (lower.contains('key') || lower.contains('token')) {\n return MapEntry(normalizedKey, _previewSecret(child?.toString() ?? ''));\n }\n return MapEntry(normalizedKey, _redactNested(child));\n });\n }\n if (value is List) {\n return [for (final item in value) _redactNested(item)];\n }\n return value;\n}\n\nString _firstNonEmpty(List<String?> values) {\n for (final value in values) {\n final trimmed = value?.trim() ?? '';\n if (trimmed.isNotEmpty) {\n return trimmed;\n }\n }\n return '';\n}\n\nString _previewSecret(String value) {\n if (value.length <= 8) {\n return value.isEmpty ? '' : '<redacted>';\n }\n return '${value.substring(0, 4)}...${value.substring(value.length - 4)}';\n}\n\nString _shellSingleQuote(String value) {\n return value.replaceAll(\"'\", \"'\\\"'\\\"'\");\n}\n\nString? _optionValue(List<String> args, String name) {\n final index = args.indexOf(name);\n if (index < 0 || index + 1 >= args.length) {\n return null;\n }\n return args[index + 1];\n}\n\nvoid _printUsage() {\n stdout.writeln('''\nUsage:\n dart run tool/local_settings_cli.dart write-mimo [--file setting.json]\n dart run tool/local_settings_cli.dart write-zhipu [--file setting.json]\n dart run tool/local_settings_cli.dart write-routed [--file setting.json]\n dart run tool/local_settings_cli.dart decrypt [--file setting.json] [--show-secrets]\n dart run tool/local_settings_cli.dart export-provider-env [--file setting.json] [--output .auto-team/jianlai-refine/providers.env]\n\nwrite-mimo reads MIMO_API_KEY from the environment and writes an AES-GCM encrypted setting.json.\nwrite-zhipu reads ZHIPU_API_KEY from the environment and writes an AES-GCM encrypted setting.json.\nwrite-routed writes Zhipu default routing plus Ollama Kimi and MiMo provider profiles; OLLAMA_API_KEY and MIMO_API_KEY are optional but required for their routes to activate.\ndecrypt prints decrypted values; secrets are redacted unless --show-secrets is present.\nexport-provider-env writes provider variables for batch workers without printing secret values.\n''');\n}\n"}, "a573257f6c4fd24de827aadce5b3522951a64fd7": {"t": 1785757877.4663382, "n": 12719, "h": "9b923ea4c5ee0339", "c": "import 'dart:convert';\nimport 'dart:io';\n\nimport 'package:novel_writer/app/state/local_settings_file.dart';\n\nFuture<void> main(List<String> args) async {\n if (args.isEmpty || args.contains('--help') || args.contains('-h')) {\n _printUsage();\n return;\n }\n\n final file = File(_optionValue(args, '--file') ?? 'setting.json');\n switch (args.first) {\n case 'write-mimo':\n await _writeMimo(file);\n return;\n case 'write-zhipu':\n await _writeZhipu(file);\n return;\n case 'write-routed':\n await _writeRouted(file);\n return;\n case 'decrypt':\n await _decrypt(file, showSecrets: args.contains('--show-secrets'));\n return;\n case 'export-provider-env':\n await _exportProviderEnv(\n settingsFile: file,\n outputFile: File(\n _optionValue(args, '--output') ??\n '.auto-team/jianlai-refine/providers.env',\n ),\n );\n return;\n default:\n stderr.writeln('Unknown command: ${args.first}');\n _printUsage();\n exitCode = 64;\n }\n}\n\nFuture<void> _writeMimo(File file) async {\n final apiKey = Platform.environment['MIMO_API_KEY']?.trim() ?? '';\n if (apiKey.isEmpty) {\n stderr.writeln('MIMO_API_KEY environment variable is required.');\n exitCode = 64;\n return;\n }\n\n await saveEncryptedLocalSettingsFile(\n file: file,\n values: {\n 'providerName': 'Xiaomi MiMo',\n 'baseUrl': 'https://token-plan-cn.xiaomimimo.com/v1',\n 'model': 'mimo-v2.5-pro',\n 'apiKey': apiKey,\n 'maxTokens': 0,\n 'MIMO_API_KEY': apiKey,\n 'MIMO_BASE_URL': 'https://token-plan-cn.xiaomimimo.com/v1',\n 'MIMO_MODEL': 'mimo-v2.5-pro',\n 'REAL_AI_MAX_TOKENS': '0',\n 'REAL_AI_MODEL': 'mimo-v2.5-pro',\n 'REAL_AI_TIMEOUT_MS': '180000',\n 'REAL_AI_MAX_CONCURRENT_REQUESTS': '3',\n },\n );\n stdout.writeln('Encrypted MiMo settings written to ${file.path}.');\n}\n\nFuture<void> _writeZhipu(File file) async {\n final apiKey = Platform.environment['ZHIPU_API_KEY']?.trim() ?? '';\n if (apiKey.isEmpty) {\n stderr.writeln('ZHIPU_API_KEY environment variable is required.');\n exitCode = 64;\n return;\n }\n\n const baseUrl = 'https://api.z.ai/api/coding/paas/v4';\n const model = 'glm-5.1';\n await saveEncryptedLocalSettingsFile(\n file: file,\n values: {\n 'providerName': '智谱 GLM',\n 'baseUrl': baseUrl,\n 'model': model,\n 'apiKey': apiKey,\n 'maxTokens': 0,\n 'ZHIPU_API_KEY': apiKey,\n 'ZHIPU_BASE_URL': baseUrl,\n 'ZHIPU_MODEL': model,\n 'REAL_AI_MAX_TOKENS': '0',\n 'REAL_AI_MODEL': model,\n 'REAL_AI_TIMEOUT_MS': '180000',\n 'REAL_AI_MAX_CONCURRENT_REQUESTS': '3',\n },\n );\n stdout.writeln('Encrypted Zhipu settings written to ${file.path}.');\n}\n\nFuture<void> _writeRouted(File file) async {\n final existing = await loadLocalSettingsFile(file: file);\n final zhipuKey = _firstNonEmpty([\n Platform.environment['ZHIPU_API_KEY'],\n existing['ZHIPU_API_KEY'],\n existing['apiKey'],\n ]);\n if (zhipuKey.isEmpty) {\n stderr.writeln(\n 'ZHIPU_API_KEY environment variable or existing setting.json key is required.',\n );\n exitCode = 64;\n return;\n }\n\n final mimoKey = _firstNonEmpty([\n Platform.environment['MIMO_API_KEY'],\n existing['MIMO_API_KEY'],\n ]);\n final ollamaKey = _firstNonEmpty([\n Platform.environment['OLLAMA_API_KEY'],\n existing['OLLAMA_API_KEY'],\n ]);\n\n const zhipuBaseUrl = 'https://api.z.ai/api/coding/paas/v4';\n const zhipuModel = 'glm-5.1';\n const mimoBaseUrl = 'https://token-plan-cn.xiaomimimo.com/v1';\n const mimoModel = 'mimo-v2.5-pro';\n const ollamaBaseUrl = 'https://ollama.com/v1';\n const ollamaModel = 'kimi-k2.6';\n\n await saveEncryptedLocalSettingsFile(\n file: file,\n values: {\n 'providerName': '智谱 GLM',\n 'baseUrl': zhipuBaseUrl,\n 'model': zhipuModel,\n 'apiKey': zhipuKey,\n 'maxTokens': 0,\n 'ZHIPU_API_KEY': zhipuKey,\n 'ZHIPU_BASE_URL': zhipuBaseUrl,\n 'ZHIPU_MODEL': zhipuModel,\n if (mimoKey.isNotEmpty) 'MIMO_API_KEY': mimoKey,\n 'MIMO_BASE_URL': mimoBaseUrl,\n 'MIMO_MODEL': mimoModel,\n if (ollamaKey.isNotEmpty) 'OLLAMA_API_KEY': ollamaKey,\n 'OLLAMA_BASE_URL': ollamaBaseUrl,\n 'OLLAMA_MODEL': ollamaModel,\n 'REAL_AI_MAX_TOKENS': '0',\n 'REAL_AI_MODEL': zhipuModel,\n 'REAL_AI_TIMEOUT_MS': '180000',\n 'REAL_AI_MAX_CONCURRENT_REQUESTS': '3',\n 'providerProfiles': [\n {\n 'id': 'ollama-kimi',\n 'providerName': 'Ollama Cloud',\n 'baseUrl': ollamaBaseUrl,\n 'model': ollamaModel,\n 'apiKey': ollamaKey,\n },\n {\n 'id': 'mimo',\n 'providerName': 'Xiaomi MiMo',\n 'baseUrl': mimoBaseUrl,\n 'model': mimoModel,\n 'apiKey': mimoKey,\n },\n ],\n 'requestProviderRoutes': [\n {\n 'traceNamePattern': 'scene_director_polish',\n 'providerProfileId': 'ollama-kimi',\n },\n {\n 'traceNamePattern': 'scene_roleplay_turn',\n 'providerProfileId': 'ollama-kimi',\n },\n {\n 'traceNamePattern': 'scene_roleplay_arbitrate',\n 'providerProfileId': 'ollama-kimi',\n },\n {'traceNamePattern': 'scene_beat_resolve', 'providerProfileId': 'mimo'},\n {'traceNamePattern': 'scene_editorial', 'providerProfileId': 'mimo'},\n {'traceNamePattern': 'language_polish', 'providerProfileId': 'mimo'},\n {\n 'traceNamePattern': 'scene_combined_review',\n 'providerProfileId': 'mimo',\n },\n {'traceNamePattern': 'scene_review_*', 'providerProfileId': 'mimo'},\n {\n 'traceNamePattern': 'scene_quality_scoring',\n 'providerProfileId': 'mimo',\n },\n ],\n },\n );\n stdout.writeln('Encrypted routed provider settings written to ${file.path}.');\n if (ollamaKey.isEmpty) {\n stdout.writeln(\n 'OLLAMA_API_KEY is missing; short generation routes will fall back to the default provider until it is added.',\n );\n }\n}\n\nFuture<void> _decrypt(File file, {required bool showSecrets}) async {\n final values = await loadLocalSettingsObjectFile(file: file);\n final printable = showSecrets ? values : _redactSecrets(values);\n stdout.writeln(const JsonEncoder.withIndent(' ').convert(printable));\n}\n\nFuture<void> _exportProviderEnv({\n required File settingsFile,\n required File outputFile,\n}) async {\n final settings = await loadLocalSettingsObjectFile(file: settingsFile);\n final existing = await _loadExistingEnvFile(outputFile);\n final profiles = _providerProfiles(settings['providerProfiles']);\n\n final values = <String, String>{};\n void put(String key, List<Object?> candidates) {\n final value = _firstNonEmpty([\n Platform.environment[key],\n ...candidates.map((candidate) => candidate?.toString()),\n existing[key],\n ]);\n if (value.isNotEmpty) {\n values[key] = value;\n }\n }\n\n final ollamaProfile = profiles['ollama-kimi'] ?? profiles['kimi'];\n final mimoProfile = profiles['mimo'];\n put('ZHIPU_API_KEY', [settings['ZHIPU_API_KEY'], settings['apiKey']]);\n put('ZHIPU_BASE_URL', [settings['ZHIPU_BASE_URL']]);\n put('ZHIPU_MODEL', [settings['ZHIPU_MODEL']]);\n put('OLLAMA_API_KEY', [settings['OLLAMA_API_KEY'], ollamaProfile?['apiKey']]);\n put('OLLAMA_BASE_URL', [\n settings['OLLAMA_BASE_URL'],\n ollamaProfile?['baseUrl'],\n ]);\n put('OLLAMA_MODEL', [settings['OLLAMA_MODEL'], ollamaProfile?['model']]);\n put('MIMO_API_KEY', [settings['MIMO_API_KEY'], mimoProfile?['apiKey']]);\n put('MIMO_BASE_URL', [settings['MIMO_BASE_URL'], mimoProfile?['baseUrl']]);\n put('MIMO_MODEL', [settings['MIMO_MODEL'], mimoProfile?['model']]);\n put('DEEPSEEK_API_KEY', [settings['DEEPSEEK_API_KEY']]);\n put('DEEPSEEK_BASE_URL', [settings['DEEPSEEK_BASE_URL']]);\n put('DEEPSEEK_MODEL', [settings['DEEPSEEK_MODEL']]);\n\n await outputFile.parent.create(recursive: true);\n final keys = values.keys.toList()..sort();\n final buffer = StringBuffer()\n ..writeln(\n '# Generated by tool/local_settings_cli.dart export-provider-env.',\n )\n ..writeln('# This file is local/ignored and may contain API credentials.');\n for (final key in keys) {\n buffer.writeln(\"$key='${_shellSingleQuote(values[key]!)}'\");\n }\n await outputFile.writeAsString(buffer.toString());\n if (!Platform.isWindows) {\n await Process.run('chmod', ['600', outputFile.path]);\n }\n final summary = keys\n .where((key) => key.endsWith('_API_KEY'))\n .map((key) => '$key=set')\n .join(', ');\n stdout.writeln('Provider env written to ${outputFile.path}. $summary');\n}\n\nFuture<Map<String, String>> _loadExistingEnvFile(File file) async {\n if (!await file.exists()) {\n return const {};\n }\n final values = <String, String>{};\n for (final line in const LineSplitter().convert(await file.readAsString())) {\n var trimmed = line.trim();\n if (trimmed.isEmpty || trimmed.startsWith('#')) {\n continue;\n }\n if (trimmed.startsWith('export ')) {\n trimmed = trimmed.substring('export '.length).trimLeft();\n }\n final separator = trimmed.indexOf('=');\n if (separator <= 0) {\n continue;\n }\n final key = trimmed.substring(0, separator).trim();\n var value = trimmed.substring(separator + 1).trim();\n if (value.length >= 2 &&\n ((value.startsWith(\"'\") && value.endsWith(\"'\")) ||\n (value.startsWith('\"') && value.endsWith('\"')))) {\n value = value.substring(1, value.length - 1);\n }\n if (key.isNotEmpty) {\n values[key] = value.replaceAll(\"'\\\"'\\\"'\", \"'\");\n }\n }\n return values;\n}\n\nMap<String, Map<String, Object?>> _providerProfiles(Object? rawProfiles) {\n if (rawProfiles is! List) {\n return const {};\n }\n final profiles = <String, Map<String, Object?>>{};\n for (final rawProfile in rawProfiles) {\n if (rawProfile is! Map) {\n continue;\n }\n final normalized = rawProfile.map(\n (key, value) => MapEntry(key.toString(), value as Object?),\n );\n final id = normalized['id']?.toString().trim();\n if (id != null && id.isNotEmpty) {\n profiles[id] = normalized;\n }\n }\n return profiles;\n}\n\nMap<String, Object?> _redactSecrets(Map<String, Object?> values) {\n return values.map((key, value) {\n final lower = key.toLowerCase();\n if (lower.contains('key') || lower.contains('token')) {\n return MapEntry(key, _previewSecret(value?.toString() ?? ''));\n }\n return MapEntry(key, _redactNested(value));\n });\n}\n\nObject? _redactNested(Object? value) {\n if (value is Map) {\n return value.map((key, child) {\n final normalizedKey = key.toString();\n final lower = normalizedKey.toLowerCase();\n if (lower.contains('key') || lower.contains('token')) {\n return MapEntry(normalizedKey, _previewSecret(child?.toString() ?? ''));\n }\n return MapEntry(normalizedKey, _redactNested(child));\n });\n }\n if (value is List) {\n return [for (final item in value) _redactNested(item)];\n }\n return value;\n}\n\nString _firstNonEmpty(List<String?> values) {\n for (final value in values) {\n final trimmed = value?.trim() ?? '';\n if (trimmed.isNotEmpty) {\n return trimmed;\n }\n }\n return '';\n}\n\nString _previewSecret(String value) {\n if (value.length <= 8) {\n return value.isEmpty ? '' : '<redacted>';\n }\n return '${value.substring(0, 4)}...${value.substring(value.length - 4)}';\n}\n\nString _shellSingleQuote(String value) {\n return value.replaceAll(\"'\", \"'\\\"'\\\"'\");\n}\n\nString? _optionValue(List<String> args, String name) {\n final index = args.indexOf(name);\n if (index < 0 || index + 1 >= args.length) {\n return null;\n }\n return args[index + 1];\n}\n\nvoid _printUsage() {\n stdout.writeln('''\nUsage:\n dart run tool/local_settings_cli.dart write-mimo [--file setting.json]\n dart run tool/local_settings_cli.dart write-zhipu [--file setting.json]\n dart run tool/local_settings_cli.dart write-routed [--file setting.json]\n dart run tool/local_settings_cli.dart decrypt [--file setting.json] [--show-secrets]\n dart run tool/local_settings_cli.dart export-provider-env [--file setting.json] [--output .auto-team/jianlai-refine/providers.env]\n\nwrite-mimo reads MIMO_API_KEY from the environment and writes an AES-GCM encrypted setting.json.\nwrite-zhipu reads ZHIPU_API_KEY from the environment and writes an AES-GCM encrypted setting.json.\nwrite-routed writes Zhipu default routing plus Ollama Kimi and MiMo provider profiles; OLLAMA_API_KEY and MIMO_API_KEY are optional but required for their routes to activate.\ndecrypt prints decrypted values; secrets are redacted unless --show-secrets is present.\nexport-provider-env writes provider variables for batch workers without printing secret values.\n''');\n}\n"}, "d6e6c2bd9f27a0c7c7055036d9cf7d6807b6add7": {"t": 1785757879.1615403, "n": 8324, "h": "26552407066bc975", "c": "import 'dart:convert';\nimport 'dart:io';\n\nimport 'package:novel_writer/app/state/local_settings_file.dart';\n\nFuture<void> main(List<String> args) async {\n if (args.isEmpty || args.contains('--help') || args.contains('-h')) {\n _printUsage();\n return;\n }\n\n final file = File(_optionValue(args, '--file') ?? 'setting.json');\n switch (args.first) {\n case 'write-mimo':\n await _writeMimo(file);\n return;\n case 'write-zhipu':\n await _writeZhipu(file);\n return;\n case 'write-routed':\n await _writeRouted(file);\n return;\n case 'decrypt':\n await _decrypt(file, showSecrets: args.contains('--show-secrets'));\n return;\n default:\n stderr.writeln('Unknown command: ${args.first}');\n _printUsage();\n exitCode = 64;\n }\n}\n\nFuture<void> _writeMimo(File file) async {\n final apiKey = Platform.environment['MIMO_API_KEY']?.trim() ?? '';\n if (apiKey.isEmpty) {\n stderr.writeln('MIMO_API_KEY environment variable is required.');\n exitCode = 64;\n return;\n }\n\n await saveEncryptedLocalSettingsFile(\n file: file,\n values: {\n 'providerName': 'Xiaomi MiMo',\n 'baseUrl': 'https://token-plan-cn.xiaomimimo.com/v1',\n 'model': 'mimo-v2.5-pro',\n 'apiKey': apiKey,\n 'maxTokens': 0,\n 'MIMO_API_KEY': apiKey,\n 'MIMO_BASE_URL': 'https://token-plan-cn.xiaomimimo.com/v1',\n 'MIMO_MODEL': 'mimo-v2.5-pro',\n 'REAL_AI_MAX_TOKENS': '0',\n 'REAL_AI_MODEL': 'mimo-v2.5-pro',\n 'REAL_AI_TIMEOUT_MS': '180000',\n 'REAL_AI_MAX_CONCURRENT_REQUESTS': '3',\n },\n );\n stdout.writeln('Encrypted MiMo settings written to ${file.path}.');\n}\n\nFuture<void> _writeZhipu(File file) async {\n final apiKey = Platform.environment['ZHIPU_API_KEY']?.trim() ?? '';\n if (apiKey.isEmpty) {\n stderr.writeln('ZHIPU_API_KEY environment variable is required.');\n exitCode = 64;\n return;\n }\n\n const baseUrl = 'https://open.bigmodel.cn/api/paas/v4';\n const model = 'glm-5.1';\n await saveEncryptedLocalSettingsFile(\n file: file,\n values: {\n 'providerName': '智谱 GLM',\n 'baseUrl': baseUrl,\n 'model': model,\n 'apiKey': apiKey,\n 'maxTokens': 0,\n 'ZHIPU_API_KEY': apiKey,\n 'ZHIPU_BASE_URL': baseUrl,\n 'ZHIPU_MODEL': model,\n 'REAL_AI_MAX_TOKENS': '0',\n 'REAL_AI_MODEL': model,\n 'REAL_AI_TIMEOUT_MS': '180000',\n 'REAL_AI_MAX_CONCURRENT_REQUESTS': '3',\n },\n );\n stdout.writeln('Encrypted Zhipu settings written to ${file.path}.');\n}\n\nFuture<void> _writeRouted(File file) async {\n final existing = await loadLocalSettingsFile(file: file);\n final zhipuKey = _firstNonEmpty([\n Platform.environment['ZHIPU_API_KEY'],\n existing['ZHIPU_API_KEY'],\n existing['apiKey'],\n ]);\n if (zhipuKey.isEmpty) {\n stderr.writeln(\n 'ZHIPU_API_KEY environment variable or existing setting.json key is required.',\n );\n exitCode = 64;\n return;\n }\n\n final mimoKey = _firstNonEmpty([\n Platform.environment['MIMO_API_KEY'],\n existing['MIMO_API_KEY'],\n ]);\n final ollamaKey = _firstNonEmpty([\n Platform.environment['OLLAMA_API_KEY'],\n existing['OLLAMA_API_KEY'],\n ]);\n\n const zhipuBaseUrl = 'https://open.bigmodel.cn/api/paas/v4';\n const zhipuModel = 'glm-5.1';\n const mimoBaseUrl = 'https://token-plan-cn.xiaomimimo.com/v1';\n const mimoModel = 'mimo-v2.5-pro';\n const ollamaBaseUrl = 'https://ollama.com/v1';\n const ollamaModel = 'kimi-k2.6';\n\n await saveEncryptedLocalSettingsFile(\n file: file,\n values: {\n 'providerName': '智谱 GLM',\n 'baseUrl': zhipuBaseUrl,\n 'model': zhipuModel,\n 'apiKey': zhipuKey,\n 'maxTokens': 0,\n 'ZHIPU_API_KEY': zhipuKey,\n 'ZHIPU_BASE_URL': zhipuBaseUrl,\n 'ZHIPU_MODEL': zhipuModel,\n if (mimoKey.isNotEmpty) 'MIMO_API_KEY': mimoKey,\n 'MIMO_BASE_URL': mimoBaseUrl,\n 'MIMO_MODEL': mimoModel,\n if (ollamaKey.isNotEmpty) 'OLLAMA_API_KEY': ollamaKey,\n 'OLLAMA_BASE_URL': ollamaBaseUrl,\n 'OLLAMA_MODEL': ollamaModel,\n 'REAL_AI_MAX_TOKENS': '0',\n 'REAL_AI_MODEL': zhipuModel,\n 'REAL_AI_TIMEOUT_MS': '180000',\n 'REAL_AI_MAX_CONCURRENT_REQUESTS': '3',\n 'providerProfiles': [\n {\n 'id': 'ollama-kimi',\n 'providerName': 'Ollama Cloud',\n 'baseUrl': ollamaBaseUrl,\n 'model': ollamaModel,\n 'apiKey': ollamaKey,\n },\n {\n 'id': 'mimo',\n 'providerName': 'Xiaomi MiMo',\n 'baseUrl': mimoBaseUrl,\n 'model': mimoModel,\n 'apiKey': mimoKey,\n },\n ],\n 'requestProviderRoutes': [\n {\n 'traceNamePattern': 'scene_director_polish',\n 'providerProfileId': 'ollama-kimi',\n },\n {\n 'traceNamePattern': 'scene_roleplay_turn',\n 'providerProfileId': 'ollama-kimi',\n },\n {\n 'traceNamePattern': 'scene_roleplay_arbitrate',\n 'providerProfileId': 'ollama-kimi',\n },\n {'traceNamePattern': 'scene_beat_resolve', 'providerProfileId': 'mimo'},\n {'traceNamePattern': 'scene_editorial', 'providerProfileId': 'mimo'},\n {'traceNamePattern': 'language_polish', 'providerProfileId': 'mimo'},\n {\n 'traceNamePattern': 'scene_combined_review',\n 'providerProfileId': 'mimo',\n },\n {'traceNamePattern': 'scene_review_*', 'providerProfileId': 'mimo'},\n {\n 'traceNamePattern': 'scene_quality_scoring',\n 'providerProfileId': 'mimo',\n },\n ],\n },\n );\n stdout.writeln('Encrypted routed provider settings written to ${file.path}.');\n if (ollamaKey.isEmpty) {\n stdout.writeln(\n 'OLLAMA_API_KEY is missing; short generation routes will fall back to the default provider until it is added.',\n );\n }\n}\n\nFuture<void> _decrypt(File file, {required bool showSecrets}) async {\n final values = await loadLocalSettingsObjectFile(file: file);\n final printable = showSecrets ? values : _redactSecrets(values);\n stdout.writeln(const JsonEncoder.withIndent(' ').convert(printable));\n}\n\nMap<String, Object?> _redactSecrets(Map<String, Object?> values) {\n return values.map((key, value) {\n final lower = key.toLowerCase();\n if (lower.contains('key') || lower.contains('token')) {\n return MapEntry(key, _previewSecret(value?.toString() ?? ''));\n }\n return MapEntry(key, _redactNested(value));\n });\n}\n\nObject? _redactNested(Object? value) {\n if (value is Map) {\n return value.map((key, child) {\n final normalizedKey = key.toString();\n final lower = normalizedKey.toLowerCase();\n if (lower.contains('key') || lower.contains('token')) {\n return MapEntry(normalizedKey, _previewSecret(child?.toString() ?? ''));\n }\n return MapEntry(normalizedKey, _redactNested(child));\n });\n }\n if (value is List) {\n return [for (final item in value) _redactNested(item)];\n }\n return value;\n}\n\nString _firstNonEmpty(List<String?> values) {\n for (final value in values) {\n final trimmed = value?.trim() ?? '';\n if (trimmed.isNotEmpty) {\n return trimmed;\n }\n }\n return '';\n}\n\nString _previewSecret(String value) {\n if (value.length <= 8) {\n return value.isEmpty ? '' : '<redacted>';\n }\n return '${value.substring(0, 4)}...${value.substring(value.length - 4)}';\n}\n\nString? _optionValue(List<String> args, String name) {\n final index = args.indexOf(name);\n if (index < 0 || index + 1 >= args.length) {\n return null;\n }\n return args[index + 1];\n}\n\nvoid _printUsage() {\n stdout.writeln('''\nUsage:\n dart run tool/local_settings_cli.dart write-mimo [--file setting.json]\n dart run tool/local_settings_cli.dart write-zhipu [--file setting.json]\n dart run tool/local_settings_cli.dart write-routed [--file setting.json]\n dart run tool/local_settings_cli.dart decrypt [--file setting.json] [--show-secrets]\n\nwrite-mimo reads MIMO_API_KEY from the environment and writes an AES-GCM encrypted setting.json.\nwrite-zhipu reads ZHIPU_API_KEY from the environment and writes an AES-GCM encrypted setting.json.\nwrite-routed writes Zhipu default routing plus Ollama Kimi and MiMo provider profiles; OLLAMA_API_KEY and MIMO_API_KEY are optional but required for their routes to activate.\ndecrypt prints decrypted values; secrets are redacted unless --show-secrets is present.\n''');\n}\n"}, "c904d53b36df1a16c40e20ec47a188b1d1f75f58": {"t": 1785757880.6848304, "n": 7634, "h": "f950babb5857edda", "c": "import 'dart:convert';\nimport 'dart:io';\n\nimport 'package:novel_writer/app/state/local_settings_file.dart';\n\nFuture<void> main(List<String> args) async {\n if (args.isEmpty || args.contains('--help') || args.contains('-h')) {\n _printUsage();\n return;\n }\n\n final file = File(_optionValue(args, '--file') ?? 'setting.json');\n switch (args.first) {\n case 'write-mimo':\n await _writeMimo(file);\n return;\n case 'write-zhipu':\n await _writeZhipu(file);\n return;\n case 'write-routed':\n await _writeRouted(file);\n return;\n case 'decrypt':\n await _decrypt(file, showSecrets: args.contains('--show-secrets'));\n return;\n default:\n stderr.writeln('Unknown command: ${args.first}');\n _printUsage();\n exitCode = 64;\n }\n}\n\nFuture<void> _writeMimo(File file) async {\n final apiKey = Platform.environment['MIMO_API_KEY']?.trim() ?? '';\n if (apiKey.isEmpty) {\n stderr.writeln('MIMO_API_KEY environment variable is required.');\n exitCode = 64;\n return;\n }\n\n await saveEncryptedLocalSettingsFile(\n file: file,\n values: {\n 'providerName': 'Xiaomi MiMo',\n 'baseUrl': 'https://token-plan-cn.xiaomimimo.com/v1',\n 'model': 'mimo-v2.5-pro',\n 'apiKey': apiKey,\n 'maxTokens': 0,\n 'MIMO_API_KEY': apiKey,\n 'MIMO_BASE_URL': 'https://token-plan-cn.xiaomimimo.com/v1',\n 'MIMO_MODEL': 'mimo-v2.5-pro',\n 'REAL_AI_MAX_TOKENS': '0',\n 'REAL_AI_MODEL': 'mimo-v2.5-pro',\n 'REAL_AI_TIMEOUT_MS': '180000',\n 'REAL_AI_MAX_CONCURRENT_REQUESTS': '3',\n },\n );\n stdout.writeln('Encrypted MiMo settings written to ${file.path}.');\n}\n\nFuture<void> _writeZhipu(File file) async {\n final apiKey = Platform.environment['ZHIPU_API_KEY']?.trim() ?? '';\n if (apiKey.isEmpty) {\n stderr.writeln('ZHIPU_API_KEY environment variable is required.');\n exitCode = 64;\n return;\n }\n\n const baseUrl = 'https://open.bigmodel.cn/api/paas/v4';\n const model = 'glm-5.1';\n await saveEncryptedLocalSettingsFile(\n file: file,\n values: {\n 'providerName': '智谱 GLM',\n 'baseUrl': baseUrl,\n 'model': model,\n 'apiKey': apiKey,\n 'maxTokens': 0,\n 'ZHIPU_API_KEY': apiKey,\n 'ZHIPU_BASE_URL': baseUrl,\n 'ZHIPU_MODEL': model,\n 'REAL_AI_MAX_TOKENS': '0',\n 'REAL_AI_MODEL': model,\n 'REAL_AI_TIMEOUT_MS': '180000',\n 'REAL_AI_MAX_CONCURRENT_REQUESTS': '3',\n },\n );\n stdout.writeln('Encrypted Zhipu settings written to ${file.path}.');\n}\n\nFuture<void> _writeRouted(File file) async {\n final existing = await loadLocalSettingsFile(file: file);\n final zhipuKey = _firstNonEmpty([\n Platform.environment['ZHIPU_API_KEY'],\n existing['ZHIPU_API_KEY'],\n existing['apiKey'],\n ]);\n if (zhipuKey.isEmpty) {\n stderr.writeln(\n 'ZHIPU_API_KEY environment variable or existing setting.json key is required.',\n );\n exitCode = 64;\n return;\n }\n\n final mimoKey = _firstNonEmpty([\n Platform.environment['MIMO_API_KEY'],\n existing['MIMO_API_KEY'],\n ]);\n final ollamaKey = _firstNonEmpty([\n Platform.environment['OLLAMA_API_KEY'],\n existing['OLLAMA_API_KEY'],\n ]);\n\n const zhipuBaseUrl = 'https://open.bigmodel.cn/api/paas/v4';\n const zhipuModel = 'glm-5.1';\n const mimoBaseUrl = 'https://token-plan-cn.xiaomimimo.com/v1';\n const mimoModel = 'mimo-v2.5-pro';\n const ollamaBaseUrl = 'https://ollama.com/v1';\n const ollamaModel = 'kimi-k2.6';\n\n await saveEncryptedLocalSettingsFile(\n file: file,\n values: {\n 'providerName': '智谱 GLM',\n 'baseUrl': zhipuBaseUrl,\n 'model': zhipuModel,\n 'apiKey': zhipuKey,\n 'maxTokens': 0,\n 'ZHIPU_API_KEY': zhipuKey,\n 'ZHIPU_BASE_URL': zhipuBaseUrl,\n 'ZHIPU_MODEL': zhipuModel,\n if (mimoKey.isNotEmpty) 'MIMO_API_KEY': mimoKey,\n 'MIMO_BASE_URL': mimoBaseUrl,\n 'MIMO_MODEL': mimoModel,\n if (ollamaKey.isNotEmpty) 'OLLAMA_API_KEY': ollamaKey,\n 'OLLAMA_BASE_URL': ollamaBaseUrl,\n 'OLLAMA_MODEL': ollamaModel,\n 'REAL_AI_MAX_TOKENS': '0',\n 'REAL_AI_MODEL': zhipuModel,\n 'REAL_AI_TIMEOUT_MS': '180000',\n 'REAL_AI_MAX_CONCURRENT_REQUESTS': '3',\n 'providerProfiles': [\n {\n 'id': 'ollama-kimi',\n 'providerName': 'Ollama Cloud',\n 'baseUrl': ollamaBaseUrl,\n 'model': ollamaModel,\n 'apiKey': ollamaKey,\n },\n {\n 'id': 'mimo',\n 'providerName': 'Xiaomi MiMo',\n 'baseUrl': mimoBaseUrl,\n 'model': mimoModel,\n 'apiKey': mimoKey,\n },\n ],\n 'requestProviderRoutes': [\n {\n 'traceNamePattern': 'scene_review_*',\n 'providerProfileId': 'ollama-kimi',\n },\n {\n 'traceNamePattern': 'scene_quality_scoring',\n 'providerProfileId': 'mimo',\n },\n ],\n },\n );\n stdout.writeln('Encrypted routed provider settings written to ${file.path}.');\n if (ollamaKey.isEmpty) {\n stdout.writeln(\n 'OLLAMA_API_KEY is missing; scene_review_* will fall back to the default provider until it is added.',\n );\n }\n}\n\nFuture<void> _decrypt(File file, {required bool showSecrets}) async {\n final values = await loadLocalSettingsObjectFile(file: file);\n final printable = showSecrets ? values : _redactSecrets(values);\n stdout.writeln(const JsonEncoder.withIndent(' ').convert(printable));\n}\n\nMap<String, Object?> _redactSecrets(Map<String, Object?> values) {\n return values.map((key, value) {\n final lower = key.toLowerCase();\n if (lower.contains('key') || lower.contains('token')) {\n return MapEntry(key, _previewSecret(value?.toString() ?? ''));\n }\n return MapEntry(key, _redactNested(value));\n });\n}\n\nObject? _redactNested(Object? value) {\n if (value is Map) {\n return value.map((key, child) {\n final normalizedKey = key.toString();\n final lower = normalizedKey.toLowerCase();\n if (lower.contains('key') || lower.contains('token')) {\n return MapEntry(normalizedKey, _previewSecret(child?.toString() ?? ''));\n }\n return MapEntry(normalizedKey, _redactNested(child));\n });\n }\n if (value is List) {\n return [for (final item in value) _redactNested(item)];\n }\n return value;\n}\n\nString _firstNonEmpty(List<String?> values) {\n for (final value in values) {\n final trimmed = value?.trim() ?? '';\n if (trimmed.isNotEmpty) {\n return trimmed;\n }\n }\n return '';\n}\n\nString _previewSecret(String value) {\n if (value.length <= 8) {\n return value.isEmpty ? '' : '<redacted>';\n }\n return '${value.substring(0, 4)}...${value.substring(value.length - 4)}';\n}\n\nString? _optionValue(List<String> args, String name) {\n final index = args.indexOf(name);\n if (index < 0 || index + 1 >= args.length) {\n return null;\n }\n return args[index + 1];\n}\n\nvoid _printUsage() {\n stdout.writeln('''\nUsage:\n dart run tool/local_settings_cli.dart write-mimo [--file setting.json]\n dart run tool/local_settings_cli.dart write-zhipu [--file setting.json]\n dart run tool/local_settings_cli.dart write-routed [--file setting.json]\n dart run tool/local_settings_cli.dart decrypt [--file setting.json] [--show-secrets]\n\nwrite-mimo reads MIMO_API_KEY from the environment and writes an AES-GCM encrypted setting.json.\nwrite-zhipu reads ZHIPU_API_KEY from the environment and writes an AES-GCM encrypted setting.json.\nwrite-routed writes Zhipu default routing plus Ollama Kimi and MiMo provider profiles; OLLAMA_API_KEY and MIMO_API_KEY are optional but required for their routes to activate.\ndecrypt prints decrypted values; secrets are redacted unless --show-secrets is present.\n''');\n}\n"}}}} |