zcf 1.1.3 → 1.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -75,9 +75,11 @@ npx zcf u # 仅导入工作流:快速添加 AI 工作流和命令系统
75
75
 
76
76
  ### 🔐 API 配置
77
77
 
78
- - 自定义 API 支持
79
- - API Key 自动配置
80
- - 支持稍后在 claude 命令中配置(如 OAuth)
78
+ - 支持两种认证方式:
79
+ - **Auth Token**:适用于通过 OAuth 或浏览器登录获取的令牌
80
+ - **API Key**:适用于从 Anthropic Console 获取的 API 密钥
81
+ - 自定义 API URL 支持
82
+ - 支持稍后在 claude 命令中配置
81
83
 
82
84
  ### 💾 配置管理
83
85
 
@@ -112,12 +114,15 @@ $ npx zcf
112
114
 
113
115
  ✔ Claude Code 安装成功
114
116
 
115
- ? 是否配置 API
116
- 配置 API
117
- 跳过(稍后在 claude 命令中自行配置,如 OAuth)
117
+ ? 选择 API 认证方式
118
+ 使用 Auth Token (OAuth 认证)
119
+ 适用于通过 OAuth 或浏览器登录获取的令牌
120
+ 使用 API Key (密钥认证)
121
+ 适用于从 Anthropic Console 获取的 API 密钥
122
+ 跳过(稍后手动配置)
118
123
 
119
124
  ? 请输入 API URL: https://api.anthropic.com
120
- ? 请输入 API Key: sk-xxx
125
+ ? 请输入 Auth Token 或 API Key: xxx
121
126
 
122
127
  ? 检测到已有配置文件,如何处理?
123
128
  ❯ 备份并覆盖全部
package/dist/cli.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import cac from 'cac';
3
3
  import ansis from 'ansis';
4
- import { t as displayBanner, u as selectScriptLanguage, v as readZcfConfig, I as I18N, S as SETTINGS_FILE, b as SUPPORTED_LANGS, L as LANG_LABELS, x as resolveAiOutputLanguage, y as updatePromptOnly, z as updateZcfConfig, B as version, i as init } from './shared/zcf.EL2e1-Wq.mjs';
4
+ import { t as displayBanner, u as selectScriptLanguage, v as readZcfConfig, I as I18N, S as SETTINGS_FILE, b as SUPPORTED_LANGS, L as LANG_LABELS, x as resolveAiOutputLanguage, y as updatePromptOnly, z as updateZcfConfig, B as version, i as init } from './shared/zcf.CeJug3gT.mjs';
5
5
  import prompts from '@posva/prompts';
6
6
  import { existsSync } from 'node:fs';
7
7
  import 'node:os';
package/dist/index.d.mts CHANGED
@@ -70,9 +70,13 @@ declare const I18N: {
70
70
  installFailed: string;
71
71
  npmNotFound: string;
72
72
  configureApi: string;
73
- customApi: string;
73
+ useAuthToken: string;
74
+ authTokenDesc: string;
75
+ useApiKey: string;
76
+ apiKeyDesc: string;
74
77
  skipApi: string;
75
78
  enterApiUrl: string;
79
+ enterAuthToken: string;
76
80
  enterApiKey: string;
77
81
  existingConfig: string;
78
82
  backupAndOverwrite: string;
@@ -96,6 +100,13 @@ declare const I18N: {
96
100
  yes: string;
97
101
  no: string;
98
102
  cancelled: string;
103
+ apiKeyValidation: {
104
+ empty: string;
105
+ invalid: string;
106
+ tooShort: string;
107
+ tooLong: string;
108
+ example: string;
109
+ };
99
110
  noExistingConfig: string;
100
111
  updatingPrompts: string;
101
112
  updateConfigLangPrompt: string;
@@ -120,9 +131,13 @@ declare const I18N: {
120
131
  installFailed: string;
121
132
  npmNotFound: string;
122
133
  configureApi: string;
123
- customApi: string;
134
+ useAuthToken: string;
135
+ authTokenDesc: string;
136
+ useApiKey: string;
137
+ apiKeyDesc: string;
124
138
  skipApi: string;
125
139
  enterApiUrl: string;
140
+ enterAuthToken: string;
126
141
  enterApiKey: string;
127
142
  existingConfig: string;
128
143
  backupAndOverwrite: string;
@@ -146,6 +161,13 @@ declare const I18N: {
146
161
  yes: string;
147
162
  no: string;
148
163
  cancelled: string;
164
+ apiKeyValidation: {
165
+ empty: string;
166
+ invalid: string;
167
+ tooShort: string;
168
+ tooLong: string;
169
+ example: string;
170
+ };
149
171
  noExistingConfig: string;
150
172
  updatingPrompts: string;
151
173
  updateConfigLangPrompt: string;
@@ -177,8 +199,9 @@ declare function copyConfigFiles(lang: SupportedLang, onlyMd?: boolean): void;
177
199
  interface ApiConfig {
178
200
  url: string;
179
201
  key: string;
202
+ authType?: 'auth_token' | 'api_key';
180
203
  }
181
- declare function configureApi(apiConfig: ApiConfig | null): void;
204
+ declare function configureApi(apiConfig: ApiConfig | null): ApiConfig | null;
182
205
  declare function mergeConfigs(sourceFile: string, targetFile: string): void;
183
206
  declare function applyAiLanguageDirective(aiOutputLang: AiOutputLanguage | string): void;
184
207
 
package/dist/index.d.ts CHANGED
@@ -70,9 +70,13 @@ declare const I18N: {
70
70
  installFailed: string;
71
71
  npmNotFound: string;
72
72
  configureApi: string;
73
- customApi: string;
73
+ useAuthToken: string;
74
+ authTokenDesc: string;
75
+ useApiKey: string;
76
+ apiKeyDesc: string;
74
77
  skipApi: string;
75
78
  enterApiUrl: string;
79
+ enterAuthToken: string;
76
80
  enterApiKey: string;
77
81
  existingConfig: string;
78
82
  backupAndOverwrite: string;
@@ -96,6 +100,13 @@ declare const I18N: {
96
100
  yes: string;
97
101
  no: string;
98
102
  cancelled: string;
103
+ apiKeyValidation: {
104
+ empty: string;
105
+ invalid: string;
106
+ tooShort: string;
107
+ tooLong: string;
108
+ example: string;
109
+ };
99
110
  noExistingConfig: string;
100
111
  updatingPrompts: string;
101
112
  updateConfigLangPrompt: string;
@@ -120,9 +131,13 @@ declare const I18N: {
120
131
  installFailed: string;
121
132
  npmNotFound: string;
122
133
  configureApi: string;
123
- customApi: string;
134
+ useAuthToken: string;
135
+ authTokenDesc: string;
136
+ useApiKey: string;
137
+ apiKeyDesc: string;
124
138
  skipApi: string;
125
139
  enterApiUrl: string;
140
+ enterAuthToken: string;
126
141
  enterApiKey: string;
127
142
  existingConfig: string;
128
143
  backupAndOverwrite: string;
@@ -146,6 +161,13 @@ declare const I18N: {
146
161
  yes: string;
147
162
  no: string;
148
163
  cancelled: string;
164
+ apiKeyValidation: {
165
+ empty: string;
166
+ invalid: string;
167
+ tooShort: string;
168
+ tooLong: string;
169
+ example: string;
170
+ };
149
171
  noExistingConfig: string;
150
172
  updatingPrompts: string;
151
173
  updateConfigLangPrompt: string;
@@ -177,8 +199,9 @@ declare function copyConfigFiles(lang: SupportedLang, onlyMd?: boolean): void;
177
199
  interface ApiConfig {
178
200
  url: string;
179
201
  key: string;
202
+ authType?: 'auth_token' | 'api_key';
180
203
  }
181
- declare function configureApi(apiConfig: ApiConfig | null): void;
204
+ declare function configureApi(apiConfig: ApiConfig | null): ApiConfig | null;
182
205
  declare function mergeConfigs(sourceFile: string, targetFile: string): void;
183
206
  declare function applyAiLanguageDirective(aiOutputLang: AiOutputLanguage | string): void;
184
207
 
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- export { A as AI_OUTPUT_LANGUAGES, C as CLAUDE_DIR, a as CLAUDE_MD_FILE, I as I18N, L as LANG_LABELS, M as MCP_CONFIG_FILE, d as MCP_SERVICES, S as SETTINGS_FILE, b as SUPPORTED_LANGS, Z as ZCF_CONFIG_FILE, n as applyAiLanguageDirective, j as backupExistingConfig, p as backupMcpConfig, s as buildMcpServerConfig, c as commandExists, l as configureApi, k as copyConfigFiles, h as ensureClaudeDir, o as getMcpConfigPath, g as getPlatform, i as init, f as installClaudeCode, e as isClaudeCodeInstalled, m as mergeConfigs, q as mergeMcpServers, r as readMcpConfig, w as writeMcpConfig } from './shared/zcf.EL2e1-Wq.mjs';
1
+ export { A as AI_OUTPUT_LANGUAGES, C as CLAUDE_DIR, a as CLAUDE_MD_FILE, I as I18N, L as LANG_LABELS, M as MCP_CONFIG_FILE, d as MCP_SERVICES, S as SETTINGS_FILE, b as SUPPORTED_LANGS, Z as ZCF_CONFIG_FILE, n as applyAiLanguageDirective, j as backupExistingConfig, p as backupMcpConfig, s as buildMcpServerConfig, c as commandExists, l as configureApi, k as copyConfigFiles, h as ensureClaudeDir, o as getMcpConfigPath, g as getPlatform, i as init, f as installClaudeCode, e as isClaudeCodeInstalled, m as mergeConfigs, q as mergeMcpServers, r as readMcpConfig, w as writeMcpConfig } from './shared/zcf.CeJug3gT.mjs';
2
2
  import '@posva/prompts';
3
3
  import 'ansis';
4
4
  import 'node:fs';
@@ -6,7 +6,7 @@ import { join, dirname } from 'pathe';
6
6
  import dayjs from 'dayjs';
7
7
  import { exec } from 'tinyexec';
8
8
 
9
- const version = "1.1.3";
9
+ const version = "1.1.5";
10
10
 
11
11
  const CLAUDE_DIR = join(homedir(), ".claude");
12
12
  const SETTINGS_FILE = join(CLAUDE_DIR, "settings.json");
@@ -39,10 +39,14 @@ const I18N = {
39
39
  installSuccess: "Claude Code \u5B89\u88C5\u6210\u529F",
40
40
  installFailed: "Claude Code \u5B89\u88C5\u5931\u8D25",
41
41
  npmNotFound: "npm \u672A\u5B89\u88C5\u3002\u8BF7\u5148\u5B89\u88C5 Node.js \u548C npm\u3002",
42
- configureApi: "\u662F\u5426\u914D\u7F6E API\uFF1F",
43
- customApi: "\u914D\u7F6E API",
44
- skipApi: "\u8DF3\u8FC7\uFF08\u7A0D\u540E\u5728 claude \u547D\u4EE4\u4E2D\u81EA\u884C\u914D\u7F6E\uFF0C\u5982 OAuth\uFF09",
42
+ configureApi: "\u9009\u62E9 API \u8BA4\u8BC1\u65B9\u5F0F",
43
+ useAuthToken: "\u4F7F\u7528 Auth Token (OAuth \u8BA4\u8BC1)",
44
+ authTokenDesc: "\u9002\u7528\u4E8E\u901A\u8FC7 OAuth \u6216\u6D4F\u89C8\u5668\u767B\u5F55\u83B7\u53D6\u7684\u4EE4\u724C",
45
+ useApiKey: "\u4F7F\u7528 API Key (\u5BC6\u94A5\u8BA4\u8BC1)",
46
+ apiKeyDesc: "\u9002\u7528\u4E8E\u4ECE Anthropic Console \u83B7\u53D6\u7684 API \u5BC6\u94A5",
47
+ skipApi: "\u8DF3\u8FC7\uFF08\u7A0D\u540E\u624B\u52A8\u914D\u7F6E\uFF09",
45
48
  enterApiUrl: "\u8BF7\u8F93\u5165 API URL",
49
+ enterAuthToken: "\u8BF7\u8F93\u5165 Auth Token",
46
50
  enterApiKey: "\u8BF7\u8F93\u5165 API Key",
47
51
  existingConfig: "\u68C0\u6D4B\u5230\u5DF2\u6709\u914D\u7F6E\u6587\u4EF6\uFF0C\u5982\u4F55\u5904\u7406\uFF1F",
48
52
  backupAndOverwrite: "\u5907\u4EFD\u5E76\u8986\u76D6\u5168\u90E8",
@@ -66,6 +70,13 @@ const I18N = {
66
70
  yes: "\u662F",
67
71
  no: "\u5426",
68
72
  cancelled: "\u64CD\u4F5C\u5DF2\u53D6\u6D88",
73
+ apiKeyValidation: {
74
+ empty: "API Key \u4E0D\u80FD\u4E3A\u7A7A",
75
+ invalid: "API Key \u683C\u5F0F\u65E0\u6548\u3002\u53EA\u80FD\u5305\u542B\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u4E0B\u5212\u7EBF\uFF08_\uFF09\u548C\u8FDE\u5B57\u7B26\uFF08-\uFF09",
76
+ tooShort: "API Key \u957F\u5EA6\u592A\u77ED\uFF08\u81F3\u5C11\u9700\u8981 1 \u4E2A\u5B57\u7B26\uFF09",
77
+ tooLong: "API Key \u957F\u5EA6\u592A\u957F\uFF08\u6700\u591A 256 \u4E2A\u5B57\u7B26\uFF09",
78
+ example: "\u793A\u4F8B\u683C\u5F0F: sk-abcdef123456_789xyz"
79
+ },
69
80
  noExistingConfig: "\u672A\u627E\u5230\u73B0\u6709\u914D\u7F6E\u3002\u8BF7\u5148\u8FD0\u884C `zcf`\u3002",
70
81
  updatingPrompts: "\u6B63\u5728\u66F4\u65B0 Claude Code Prompt \u6587\u6863...",
71
82
  updateConfigLangPrompt: "\u9009\u62E9\u914D\u7F6E\u8BED\u8A00",
@@ -89,10 +100,14 @@ const I18N = {
89
100
  installSuccess: "Claude Code installed successfully",
90
101
  installFailed: "Failed to install Claude Code",
91
102
  npmNotFound: "npm is not installed. Please install Node.js and npm first.",
92
- configureApi: "Configure API?",
93
- customApi: "Configure API",
94
- skipApi: "Skip (configure later in claude command, e.g., OAuth)",
103
+ configureApi: "Select API authentication method",
104
+ useAuthToken: "Use Auth Token (OAuth authentication)",
105
+ authTokenDesc: "For tokens obtained via OAuth or browser login",
106
+ useApiKey: "Use API Key (Key authentication)",
107
+ apiKeyDesc: "For API keys from Anthropic Console",
108
+ skipApi: "Skip (configure manually later)",
95
109
  enterApiUrl: "Enter API URL",
110
+ enterAuthToken: "Enter Auth Token",
96
111
  enterApiKey: "Enter API Key",
97
112
  existingConfig: "Existing config detected. How to proceed?",
98
113
  backupAndOverwrite: "Backup and overwrite all",
@@ -116,6 +131,13 @@ const I18N = {
116
131
  yes: "Yes",
117
132
  no: "No",
118
133
  cancelled: "Operation cancelled",
134
+ apiKeyValidation: {
135
+ empty: "API Key cannot be empty",
136
+ invalid: "Invalid API Key format. Only letters, numbers, underscores (_) and hyphens (-) are allowed",
137
+ tooShort: "API Key is too short (minimum 1 characters required)",
138
+ tooLong: "API Key is too long (maximum 256 characters allowed)",
139
+ example: "Example format: sk-abcdef123456_789xyz"
140
+ },
119
141
  noExistingConfig: "No existing configuration found. Please run `zcf` first.",
120
142
  updatingPrompts: "Updating Claude Code prompt documents...",
121
143
  updateConfigLangPrompt: "Select configuration language",
@@ -299,48 +321,42 @@ function copyDirectory(src, dest) {
299
321
  }
300
322
  }
301
323
  }
324
+ function getDefaultSettings() {
325
+ try {
326
+ const currentFileUrl = new URL(import.meta.url);
327
+ const currentFilePath = currentFileUrl.pathname;
328
+ const distDir = dirname(dirname(currentFilePath));
329
+ const rootDir = dirname(distDir);
330
+ const templateSettingsPath = join(rootDir, "templates", "settings.json");
331
+ if (existsSync(templateSettingsPath)) {
332
+ const content = readFileSync(templateSettingsPath, "utf-8");
333
+ return JSON.parse(content);
334
+ }
335
+ } catch (error) {
336
+ console.error("Failed to read template settings.json:", error);
337
+ return {};
338
+ }
339
+ }
302
340
  function configureApi(apiConfig) {
303
- if (!apiConfig) return;
304
- let settings = {
305
- $schema: "https://json.schemastore.org/claude-code-settings.json",
306
- env: {},
307
- includeCoAuthoredBy: false,
308
- permissions: {
309
- allow: [
310
- "Bash(*)",
311
- "LS(*)",
312
- "Read(*)",
313
- "Write(*)",
314
- "Edit(*)",
315
- "MultiEdit(*)",
316
- "Glob(*)",
317
- "Grep(*)",
318
- "WebFetch(*)",
319
- "WebSearch(*)",
320
- "TodoWrite(*)",
321
- "NotebookRead(*)",
322
- "NotebookEdit(*)"
323
- ],
324
- deny: []
325
- },
326
- hooks: {},
327
- model: "opus"
328
- };
341
+ if (!apiConfig) return null;
342
+ let settings = getDefaultSettings();
329
343
  if (existsSync(SETTINGS_FILE)) {
330
344
  const content = readFileSync(SETTINGS_FILE, "utf-8");
331
345
  try {
332
346
  const existingSettings = JSON.parse(content);
333
- settings = { ...settings, ...existingSettings };
334
- if (existingSettings.env) {
335
- settings.env = { ...settings.env, ...existingSettings.env };
336
- }
347
+ settings = deepMerge(settings, existingSettings);
337
348
  } catch (error) {
338
349
  console.error("Failed to parse existing settings.json, using defaults:", error);
339
350
  }
340
351
  }
341
- settings.env.ANTHROPIC_AUTH_TOKEN = apiConfig.key;
352
+ if (apiConfig.authType === "api_key") {
353
+ settings.env.ANTHROPIC_API_KEY = apiConfig.key;
354
+ } else {
355
+ settings.env.ANTHROPIC_AUTH_TOKEN = apiConfig.key;
356
+ }
342
357
  settings.env.ANTHROPIC_BASE_URL = apiConfig.url;
343
358
  writeFileSync(SETTINGS_FILE, JSON.stringify(settings, null, 2));
359
+ return apiConfig;
344
360
  }
345
361
  function mergeConfigs(sourceFile, targetFile) {
346
362
  if (!existsSync(sourceFile)) return;
@@ -597,6 +613,42 @@ async function resolveAiOutputLanguage(scriptLang, commandLineOption, savedConfi
597
613
  return await selectAiOutputLanguage(scriptLang, scriptLang);
598
614
  }
599
615
 
616
+ function validateApiKey(apiKey, lang = "zh-CN") {
617
+ const i18n = I18N[lang];
618
+ if (!apiKey || apiKey.trim() === "") {
619
+ return {
620
+ isValid: false,
621
+ error: i18n.apiKeyValidation.empty
622
+ };
623
+ }
624
+ const apiKeyPattern = /^[A-Za-z0-9_-]+$/;
625
+ if (!apiKeyPattern.test(apiKey)) {
626
+ return {
627
+ isValid: false,
628
+ error: i18n.apiKeyValidation.invalid
629
+ };
630
+ }
631
+ if (apiKey.length < 1) {
632
+ return {
633
+ isValid: false,
634
+ error: i18n.apiKeyValidation.tooShort
635
+ };
636
+ }
637
+ if (apiKey.length > 256) {
638
+ return {
639
+ isValid: false,
640
+ error: i18n.apiKeyValidation.tooLong
641
+ };
642
+ }
643
+ return { isValid: true };
644
+ }
645
+ function formatApiKeyDisplay(apiKey) {
646
+ if (!apiKey || apiKey.length < 12) {
647
+ return apiKey;
648
+ }
649
+ return `${apiKey.substring(0, 8)}...${apiKey.substring(apiKey.length - 4)}`;
650
+ }
651
+
600
652
  async function updatePromptOnly(configLang, scriptLang, aiOutputLang) {
601
653
  const i18n = I18N[scriptLang];
602
654
  const backupDir = backupExistingConfig();
@@ -692,8 +744,20 @@ async function init(options = {}) {
692
744
  name: "apiChoice",
693
745
  message: i18n.configureApi,
694
746
  choices: [
695
- { title: i18n.customApi, value: "custom" },
696
- { title: i18n.skipApi, value: "skip" }
747
+ {
748
+ title: i18n.useAuthToken,
749
+ value: "auth_token",
750
+ description: ansis.gray(i18n.authTokenDesc)
751
+ },
752
+ {
753
+ title: i18n.useApiKey,
754
+ value: "api_key",
755
+ description: ansis.gray(i18n.apiKeyDesc)
756
+ },
757
+ {
758
+ title: i18n.skipApi,
759
+ value: "skip"
760
+ }
697
761
  ]
698
762
  });
699
763
  if (!apiResponse.apiChoice) {
@@ -701,7 +765,7 @@ async function init(options = {}) {
701
765
  process.exit(0);
702
766
  }
703
767
  const apiChoice = apiResponse.apiChoice;
704
- if (apiChoice === "custom") {
768
+ if (apiChoice === "auth_token" || apiChoice === "api_key") {
705
769
  const urlResponse = await prompts({
706
770
  type: "text",
707
771
  name: "url",
@@ -721,18 +785,29 @@ async function init(options = {}) {
721
785
  process.exit(0);
722
786
  }
723
787
  const url = urlResponse.url;
788
+ const keyMessage = apiChoice === "auth_token" ? i18n.enterAuthToken : i18n.enterApiKey;
724
789
  const keyResponse = await prompts({
725
790
  type: "text",
726
791
  name: "key",
727
- message: i18n.enterApiKey,
728
- validate: (value) => !!value || "API Key is required"
792
+ message: keyMessage,
793
+ validate: (value) => {
794
+ if (!value) {
795
+ return `${apiChoice === "auth_token" ? "Auth Token" : "API Key"} is required`;
796
+ }
797
+ const validation = validateApiKey(value, scriptLang);
798
+ if (!validation.isValid) {
799
+ return validation.error || "Invalid API Key format";
800
+ }
801
+ return true;
802
+ }
729
803
  });
730
804
  if (keyResponse.key === void 0) {
731
805
  console.log(ansis.yellow(i18n.cancelled));
732
806
  process.exit(0);
733
807
  }
734
808
  const key = keyResponse.key;
735
- apiConfig = { url, key };
809
+ console.log(ansis.gray(` API Key: ${formatApiKeyDisplay(key)}`));
810
+ apiConfig = { url, key, authType: apiChoice };
736
811
  }
737
812
  }
738
813
  if (action === "backup") {
@@ -758,8 +833,12 @@ async function init(options = {}) {
758
833
  }
759
834
  applyAiLanguageDirective(aiOutputLang);
760
835
  if (apiConfig && !onlyUpdateDocs) {
761
- configureApi(apiConfig);
762
- console.log(ansis.green(`\u2714 ${i18n.apiConfigSuccess}`));
836
+ const configuredApi = configureApi(apiConfig);
837
+ if (configuredApi) {
838
+ console.log(ansis.green(`\u2714 ${i18n.apiConfigSuccess}`));
839
+ console.log(ansis.gray(` URL: ${configuredApi.url}`));
840
+ console.log(ansis.gray(` Key: ${formatApiKeyDisplay(configuredApi.key)}`));
841
+ }
763
842
  }
764
843
  if (!onlyUpdateDocs) {
765
844
  const mcpResponse = await prompts({
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "zcf",
3
3
  "type": "module",
4
- "version": "1.1.3",
4
+ "version": "1.1.5",
5
5
  "description": "Zero-Config Claude-Code Flow - One-click configuration tool for Claude Code",
6
6
  "license": "MIT",
7
7
  "homepage": "https://github.com/UfoMiao/zcf#readme",
@@ -24,6 +24,5 @@
24
24
  ],
25
25
  "deny": []
26
26
  },
27
- "hooks": {},
28
- "model": "opus"
29
- }
27
+ "hooks": {}
28
+ }