ly-workflow-codex 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,7 +10,7 @@ import { parse, stringify } from 'smol-toml';
10
10
  import i18next from 'i18next';
11
11
  import { fileURLToPath } from 'node:url';
12
12
 
13
- const version = "0.2.0";
13
+ const version = "0.3.0";
14
14
 
15
15
  const i18n = i18next;
16
16
  const zhCN = {
@@ -19,6 +19,7 @@ const zhCN = {
19
19
  no: "\u5426",
20
20
  preflight: {
21
21
  cliMissing: "\u672A\u68C0\u6D4B\u5230 openspec CLI\u2014\u2014OpenSpec \u751F\u547D\u5468\u671F\u547D\u4EE4\u4F9D\u8D56\u5B83\uFF08@fission-ai/openspec\uFF09",
22
+ cliUnhealthy: "\u68C0\u6D4B\u5230 openspec CLI \u4F46\u6267\u884C\u5F02\u5E38\uFF08\u8D85\u65F6/\u6302\u8D77\uFF09\u2014\u2014\u8BF7\u68C0\u67E5 openspec \u5B89\u88C5\u662F\u5426\u5065\u5EB7",
22
23
  installAsk: "\u662F\u5426\u73B0\u5728\u5168\u5C40\u5B89\u88C5 openspec\uFF1F(npm install -g @fission-ai/openspec@latest)",
23
24
  installSuccessWithSkills: "openspec \u5DF2\u5B89\u88C5\uFF0COpenSpec \u547D\u4EE4\u6280\u80FD\u5DF2\u5C31\u7EEA\uFF0C\u5168\u90E8 @lyx-* skills \u53EF\u7528",
24
25
  installSuccessNeedInit: "openspec \u5DF2\u5B89\u88C5\uFF1B\u8FD0\u884C @lyx-init \u5B8C\u6210 openspec init \u540E\uFF0COpenSpec \u751F\u547D\u5468\u671F\u547D\u4EE4\u53EF\u7528",
@@ -26,13 +27,24 @@ const zhCN = {
26
27
  installNotInPath: "openspec \u5DF2\u5B89\u88C5\u4F46\u5F53\u524D PATH \u4E2D\u672A\u68C0\u6D4B\u5230\u2014\u2014\u8BF7\u91CD\u5F00\u7EC8\u7AEF\uFF0C\u6216\u68C0\u67E5 npm \u5168\u5C40 bin \u76EE\u5F55\u662F\u5426\u5728 PATH \u4E2D",
27
28
  unavailableList: "\u4EE5\u4E0B\u547D\u4EE4\u4F9D\u8D56 openspec\uFF0C\u5F53\u524D\u4E0D\u53EF\u7528\uFF1A{{list}}",
28
29
  unaffectedNote: "@lyx-apply\u3001@lyx-review-code \u4EC5\u4F9D\u8D56\u9879\u76EE\u5185 change \u76EE\u5F55\u7ED3\u6784\uFF1BGit \u5DE5\u5177\u94FE\u4E0E\u8D28\u91CF\u5173\u5361\u6280\u80FD\u4E0D\u53D7\u5F71\u54CD",
29
- skillsMissing: "\u68C0\u6D4B\u5230 openspec CLI \u5DF2\u5B89\u88C5\uFF0C\u4F46 OpenSpec \u547D\u4EE4\u6280\u80FD\u672A\u521D\u59CB\u5316\u2014\u2014\u5B8C\u6210\u540E\u8FD0\u884C @lyx-init \u521D\u59CB\u5316",
30
- skillsMissingCodex: "codex \u5BBF\u4E3B\u7F3A\u5C11 openspec-* skills\u2014\u2014\u8FD0\u884C openspec init \u5B89\u88C5\u5230 ~/.agents/skills/ \u6216\u9879\u76EE .agents/skills/"
30
+ skillsMissing: "OpenSpec skills \u7F3A\u5931\uFF1A{{list}}\u2014\u2014\u8FD0\u884C @lyx-init \u6216\u4FEE\u590D\u540E\u91CD\u8BD5",
31
+ skillsGlobalOnly: "OpenSpec skills \u4EC5\u5168\u5C40\u53EF\u7528\uFF0C\u547D\u4EE4\u53EF\u7EE7\u7EED\u4F46\u672A\u56FA\u5316\u5230\u5F53\u524D\u9879\u76EE\uFF08WARN\uFF09",
32
+ skillsUnknown: "\u65E0\u6CD5\u8BFB\u53D6 OpenSpec workflow profile\uFF0Cskills \u72B6\u6001\u672A\u77E5\uFF08WARN\uFF09",
33
+ skillsMissingCodex: "codex \u5BBF\u4E3B\u7F3A\u5C11 openspec-* skills\u2014\u2014\u8FD0\u884C openspec init \u5B89\u88C5\u5230 ~/.agents/skills/ \u6216\u9879\u76EE .agents/skills/",
34
+ rootMissing: "OpenSpec root \u672A\u521D\u59CB\u5316\u2014\u2014\u8FD0\u884C @lyx-init \u5B8C\u6210 openspec init",
35
+ rootUnhealthy: "OpenSpec root \u4E0D\u5065\u5EB7\u2014\u2014\u8FD0\u884C openspec doctor --json \u67E5\u770B fix \u5EFA\u8BAE"
31
36
  },
32
37
  doctor: {
33
38
  openspecCliMissing: "\u672A\u5B89\u88C5 (npm install -g @fission-ai/openspec)",
39
+ openspecCliUnhealthy: "\u6267\u884C\u5F02\u5E38\uFF08\u8BF7\u68C0\u67E5 openspec \u5B89\u88C5\uFF09",
34
40
  skillsInitialized: "\u5DF2\u521D\u59CB\u5316",
35
- skillsMissing: "\u672A\u521D\u59CB\u5316\uFF08\u8FD0\u884C @lyx-init\uFF09"
41
+ skillsGlobalOnly: "\u4EC5\u5168\u5C40\u53EF\u7528\uFF08WARN\uFF1B\u8FD0\u884C @lyx-init \u53EF\u56FA\u5316\u5230\u9879\u76EE\uFF09",
42
+ skillsUnknown: "\u672A\u77E5\uFF08profile \u8BFB\u53D6\u5931\u8D25\uFF0CWARN\uFF09",
43
+ skillsMissing: "\u7F3A\u5931\uFF1A{{list}}",
44
+ rootHealthy: "\u5065\u5EB7",
45
+ rootMissing: "\u672A\u521D\u59CB\u5316\uFF08\u8FD0\u884C @lyx-init\uFF09",
46
+ rootUnhealthy: "\u4E0D\u5065\u5EB7\uFF08\u8FD0\u884C openspec doctor --json\uFF09",
47
+ rootNotChecked: "\u672A\u68C0\u67E5\uFF08openspec CLI \u4E0D\u53EF\u7528\uFF09"
36
48
  },
37
49
  confirm: "\u786E\u8BA4",
38
50
  cancel: "\u53D6\u6D88",
@@ -75,7 +87,8 @@ const zhCN = {
75
87
  skipAllPrompts: "\u8DF3\u8FC7\u6240\u6709\u4EA4\u4E92\u5F0F\u63D0\u793A\uFF08\u975E\u4EA4\u4E92\u6A21\u5F0F\uFF09",
76
88
  skipConfirmation: "\u8DF3\u8FC7\u5378\u8F7D\u786E\u8BA4",
77
89
  workflows: "\u8981\u5B89\u88C5\u7684\u5DE5\u4F5C\u6D41",
78
- installDir: "\u5B89\u88C5\u76EE\u5F55"
90
+ installDir: "\u5B89\u88C5\u76EE\u5F55",
91
+ initOpenspec: "\u521D\u59CB\u5316\u5F53\u524D\u9879\u76EE\u7684 OpenSpec root \u4E0E skills\uFF08\u663E\u5F0F\u9003\u751F\u53E3\uFF09"
79
92
  },
80
93
  nonInteractiveMode: "\u975E\u4EA4\u4E92\u6A21\u5F0F:",
81
94
  examples: "\u793A\u4F8B",
@@ -89,33 +102,47 @@ const zhCN = {
89
102
  init: {
90
103
  selectLanguage: "\u9009\u62E9\u8BED\u8A00 / Select language",
91
104
  host: {
92
- reviewModelPrompt: "\u5BA1\u67E5 agent A \u6A21\u578B\uFF08\u7559\u7A7A\u56DE\u9000\u5F53\u524D\u4F1A\u8BDD\u6A21\u578B\uFF1Bagent B / coding \u6A21\u578B\u8BF7\u8FD0\u884C lycx init \u914D\u7F6E\uFF09",
93
- reviewModelHint: "\u5E38\u89C1\u5982 gpt-5.1-codex / gpt-5.1 / o4-mini\uFF0C\u53EF\u81EA\u7531\u8F93\u5165\uFF1B\u7559\u7A7A\u56DE\u9000",
105
+ reviewModelPrompt: "\u5BA1\u67E5 agent A \u6A21\u578B\uFF08\u7559\u7A7A = \u9ED8\u8BA4\u7EE7\u627F\u5F53\u524D\u4F1A\u8BDD\u6A21\u578B\uFF1Bagent B / coding \u6A21\u578B\u8BF7\u8FD0\u884C lycx init \u914D\u7F6E\uFF09\u2014\u2014agent \u6A21\u578B\u9700\u989D\u5916\u914D\u7F6E\uFF0C\u80FD\u5426 spawn \u7531\u73AF\u5883\u51B3\u5B9A",
106
+ reviewModelHint: "\u5019\u9009 = \u9ED8\u8BA4\u7EE7\u627F\u5F53\u524D\u4F1A\u8BDD\u6A21\u578B\uFF08\u7559\u7A7A\uFF09 + \u81EA\u5B9A\u4E49\u8F93\u5165 + \u65E2\u6709\u503C\uFF08\u82E5\u6709\uFF0C\u9ED8\u8BA4\u8BE5\u9879\uFF0C\u4E0D\u9759\u9ED8\u4E22\u5F03\uFF09\uFF1B\u4E0D\u505A\u6E05\u5355\u5F3A\u6821\u9A8C\uFF0C\u53EF\u7528\u793A\u4F8B prompt \u5B9E\u6D4B\u6A21\u578B\u662F\u5426\u53EF spawn",
94
107
  codexSummaryTitle: "Codex \u914D\u7F6E\u6458\u8981:",
95
108
  reviewModelUnset: "\u672A\u914D\u7F6E\uFF08\u56DE\u9000\u5F53\u524D\u4F1A\u8BDD\u6A21\u578B\uFF09"
96
109
  },
97
110
  mode: {
98
111
  providerSelect: "\u9009\u62E9 API \u63D0\u4F9B\u65B9",
99
112
  providerExisting: "\u5DF2\u6709 provider\uFF1A{{name}}",
113
+ providerExistingSelected: "\u5DF2\u9009\u7528 provider: {{name}}",
100
114
  providerOfficial: "OpenAI \u5B98\u65B9",
101
- providerCustom: "\u81EA\u5B9A\u4E49\uFF08\u8F93\u5165\u540D\u79F0 / base_url / API key\uFF09",
115
+ providerCustom: "\u81EA\u5B9A\u4E49\uFF08\u8F93\u5165\u540D\u79F0 / base_url\uFF09",
102
116
  customNamePrompt: "provider \u540D\u79F0\uFF08\u5199\u5165 [model_providers.<name>]\uFF09",
103
117
  nameInvalid: "\u540D\u79F0\u4EC5\u5141\u8BB8\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u4E0B\u5212\u7EBF\u3001\u8FDE\u5B57\u7B26",
104
118
  customUrlPrompt: "base_url",
105
- customKeyPrompt: "API key\uFF08\u4EC5\u7528\u4E8E\u62C9\u53D6\u6A21\u578B\u5217\u8868\uFF0C\u4E0D\u5199\u5165 config.toml\uFF09",
106
- optional: "\u53EF\u9009",
107
119
  providerExists: "provider {{name}} \u5DF2\u5B58\u5728\u4E8E ~/.codex/config.toml\uFF0C\u76F4\u63A5\u9009\u7528",
108
120
  providerWritten: "\u5DF2\u589E\u91CF\u5199\u5165 ~/.codex/config.toml\uFF1Amodel_provider = {{name}}\uFF08\u539F\u6709\u6CE8\u91CA\u4FDD\u7559\uFF09",
109
- providerWriteFailed: "\u5199\u5165 ~/.codex/config.toml \u5931\u8D25\uFF1A{{error}}\uFF08\u672C\u6B21\u4E0D\u6301\u4E45\u5316\uFF0C\u4EC5\u7528\u4E8E\u62C9\u53D6\u6A21\u578B\u5217\u8868\uFF09",
110
- configParseFailed: "~/.codex/config.toml \u89E3\u6790\u5931\u8D25\uFF0C\u672A\u8BC6\u522B\u5230\u5DF2\u6709 provider\uFF08\u4E0D\u5F71\u54CD\u7EE7\u7EED\uFF09",
111
- modelFetching: "\u6B63\u5728\u4ECE {{baseUrl}}/models \u62C9\u53D6\u6A21\u578B\u5217\u8868...",
112
- modelFetchFailed: "\u62C9\u53D6\u6A21\u578B\u5217\u8868\u5931\u8D25\uFF1A{{reason}}",
113
- envKeyMissingPrompt: "\u73AF\u5883\u53D8\u91CF {{envKey}} \u672A\u8BBE\u7F6E\uFF0C\u8F93\u5165\u8BE5 provider \u7684 API key\uFF08\u76F4\u63A5\u56DE\u8F66\u8DF3\u8FC7\u9274\u6743\u62C9\u53D6\uFF09"
121
+ providerWriteFailed: "\u5199\u5165 ~/.codex/config.toml \u5931\u8D25\uFF1A{{error}}",
122
+ configParseFailed: "~/.codex/config.toml \u89E3\u6790\u5931\u8D25\uFF0C\u672A\u8BC6\u522B\u5230\u5DF2\u6709 provider\uFF08\u4E0D\u5F71\u54CD\u7EE7\u7EED\uFF09"
123
+ },
124
+ codexStatus: {
125
+ title: "Codex \u73B0\u72B6\u68C0\u6D4B\uFF08\u53EA\u8BFB\u5C55\u793A\uFF0C\u4E0D\u4FEE\u6539\u4EFB\u4F55 Codex \u914D\u7F6E\uFF09",
126
+ mainModelLabel: "\u4E3B\u4F1A\u8BDD\u6A21\u578B",
127
+ mainModel: '{{model}}\uFF08"\u9ED8\u8BA4\u7EE7\u627F"\u7684\u5B9E\u9645\u503C\uFF09',
128
+ providersLabel: "provider \u6761\u76EE",
129
+ noProvider: "\u65E0 [model_providers.*] \u6761\u76EE",
130
+ registeredLabel: "models.json \u6CE8\u518C\u6A21\u578B",
131
+ registeredModels: "{{count}} \u4E2A\uFF08\u4EC5\u5C55\u793A\uFF1B\u5B9E\u6D4B\u5176\u5185\u5BB9 \u2260 spawn \u53EF\u7528\u5217\u8868\uFF0C\u4E0D\u4F5C\u4E3A\u5019\u9009\uFF09",
132
+ notDetected: "\u672A\u68C0\u6D4B\u5230",
133
+ reasoningHint: "\u63D0\u793A\uFF1A\u90E8\u5206\u7B2C\u4E09\u65B9\u6A21\u578B\uFF08\u5982 glm-5.3-flash\uFF09\u9ED8\u8BA4\u63A8\u7406\u53C2\u6570\u4E0D\u53EF\u7528\uFF0C\u9700\u5728 [codexHost] \u7684 reviewReasoningEffort / codingReasoningEffort \u4E2D\u624B\u6539\u5BF9\u5E94\u6863\u4F4D\uFF08\u5982 low\uFF09\u3002\u672C\u5411\u5BFC\u4E0D\u91C7\u96C6\u63A8\u7406\u6863\uFF0C\u53D6\u503C\u4E0D\u505A\u679A\u4E3E\u5F3A\u6821\u9A8C\uFF0C\u76F8\u5173\u62A5\u9519\u5982\u5B9E\u5C55\u793A\u5373\u53EF\u3002",
134
+ spawnableHint: "agent \u6A21\u578B\u9700\u989D\u5916\u914D\u7F6E\uFF08\u4E0D\u505A\u6E05\u5355\u5F3A\u6821\u9A8C\uFF09\uFF1A\u80FD\u5426 spawn \u7531\u73AF\u5883\u5B9E\u9645\u80FD\u529B\u51B3\u5B9A\u3002\u9A8C\u8BC1\u793A\u4F8B\uFF1A\u5BF9\u8BDD\u4E2D\u8BA9 Codex \u7528\u76EE\u6807\u6A21\u578B spawn \u4E00\u4E2A\u5B50\u4EE3\u7406\u6267\u884C\u7B80\u5355\u4EFB\u52A1\uFF08\u5982\u56DE\u590D ok\uFF09\uFF0C\u62A5\u9519\u542B Unknown model ... Available models: ... \u5373\u5F53\u524D\u4E0D\u652F\u6301\uFF1B[codexHost] spawnableModels \u4EC5\u4F5C\u63D0\u793A\u53C2\u8003\uFF0C\u53EF\u624B\u6539 ~/.codex/lyx/config.toml"
114
135
  },
115
- promptsMigrated: "\u89D2\u8272\u8BCD\u5DF2\u8FC1\u79FB\u5230 ~/.ly/prompts/\uFF08codex \u5171\u4EAB\u4F4D\u7F6E\uFF09",
116
- promptsMigrationFailed: "\u65E7\u89D2\u8272\u8BCD\u8FC1\u79FB\u5931\u8D25\uFF08\u4E0D\u963B\u65AD\u5B89\u88C5\uFF09: {{error}}",
117
136
  selectWorkflows: "\u9009\u62E9\u8981\u5B89\u88C5\u7684\u5DE5\u4F5C\u6D41\uFF08\u53EF\u591A\u9009\uFF09",
118
137
  confirmInstall: "\u786E\u8BA4\u5B89\u88C5\u4EE5\u4E0A\u914D\u7F6E\uFF1F",
138
+ executor: {
139
+ title: "\u6267\u884C\u8005\u4E8C\u8FDE\uFF08\u5BA1\u67E5 / \u5B9E\u65BD\u7531\u8C01\u6267\u884C\uFF09",
140
+ hint: '\u9ED8\u8BA4"\u4E3B agent \u76F4\u63A5\u6267\u884C"\uFF1A\u4E0D spawn \u5B50\u4EE3\u7406\u3001\u4E0D\u63D0\u4F9B\u72EC\u7ACB\u5BA1\u67E5\u89C6\u89D2\u3002\u9009"spawn \u72EC\u7ACB\u5B50\u4EE3\u7406"\u53EF\u83B7\u5F97\u72EC\u7ACB\u89C6\u89D2\uFF0C\u4F46\u80FD\u5426 spawn \u7531\u5F53\u524D\u73AF\u5883\u5B9E\u9645\u80FD\u529B\u51B3\u5B9A\u3002',
141
+ reviewExecutor: "\u5BA1\u67E5\u6267\u884C\u8005\uFF08reviewExecutor\uFF09",
142
+ codingExecutor: "\u5B9E\u65BD\u6267\u884C\u8005\uFF08codingExecutor\uFF09",
143
+ main: "\u4E3B agent \u76F4\u63A5\u6267\u884C\uFF08\u9ED8\u8BA4\uFF09",
144
+ subagent: "spawn \u72EC\u7ACB\u5B50\u4EE3\u7406"
145
+ },
119
146
  installing: "\u6B63\u5728\u5B89\u88C5...",
120
147
  installSuccess: "\u5B89\u88C5\u6210\u529F\uFF01",
121
148
  installFailed: "\u5B89\u88C5\u5931\u8D25",
@@ -126,8 +153,12 @@ const zhCN = {
126
153
  summary: {
127
154
  title: "\u914D\u7F6E\u6458\u8981:",
128
155
  host: "\u5B89\u88C5\u5BBF\u4E3B",
156
+ reviewExecutor: "\u5BA1\u67E5\u6267\u884C\u8005",
157
+ codingExecutor: "\u5B9E\u65BD\u6267\u884C\u8005",
158
+ executorMain: "\u4E3B agent \u76F4\u63A5\u6267\u884C",
159
+ executorSubagent: "spawn \u72EC\u7ACB\u5B50\u4EE3\u7406",
160
+ modelIneffective: "{{model}}\uFF08\u4E3B agent \u6267\u884C\u65F6\u4E0D\u751F\u6548\uFF09",
129
161
  reviewModelA: "\u5BA1\u67E5 agent A \u6A21\u578B",
130
- reviewModelB: "\u5BA1\u67E5 agent B \u6A21\u578B",
131
162
  codingModel: "coding \u5B9E\u65BD\u6A21\u578B",
132
163
  reviewModelCodex: "\u5BA1\u67E5\u6A21\u578B",
133
164
  workflows: "\u5DE5\u4F5C\u6D41:",
@@ -140,16 +171,32 @@ const zhCN = {
140
171
  reinstalling: "\u6B63\u5728\u91CD\u88C5\u547D\u4EE4\u6A21\u677F\u4EE5\u5E94\u7528\u65B0\u914D\u7F6E...",
141
172
  reinstallDone: "\u6A21\u677F\u5DF2\u66F4\u65B0",
142
173
  reinstallFailed: "\u6A21\u677F\u66F4\u65B0\u5931\u8D25",
143
- trioTitle: "\u6A21\u578B\u4E09\u8FDE\uFF08\u5BA1\u67E5 agent A / \u5BA1\u67E5 agent B / coding \u5B9E\u65BD\uFF09",
174
+ trioTitle: "\u6A21\u578B\u4E8C\u8FDE\uFF08\u5BA1\u67E5 subagent / coding \u5B9E\u65BD\uFF09",
175
+ trioCandidatesHint: "\u5019\u9009 = \u9ED8\u8BA4\u7EE7\u627F\u5F53\u524D\u4F1A\u8BDD\u6A21\u578B\uFF08\u7559\u7A7A\uFF09 + \u81EA\u5B9A\u4E49\u8F93\u5165 + \u65E2\u6709\u503C\uFF08\u82E5\u6709\uFF0C\u9ED8\u8BA4\uFF09\uFF1Bagent \u6A21\u578B\u9700\u989D\u5916\u914D\u7F6E\uFF0C\u80FD\u5426 spawn \u7531\u73AF\u5883\u51B3\u5B9A",
144
176
  reviewModelA: "\u5BA1\u67E5 agent A \u6A21\u578B\uFF08reviewModel\uFF09",
145
177
  reviewModelB: "\u5BA1\u67E5 agent B \u6A21\u578B\uFF08reviewModelB\uFF0C\u53EF\u9009\uFF09",
146
178
  codingModel: "coding \u5B9E\u65BD\u6A21\u578B\uFF08codingModel\uFF0C\u53EF\u9009\uFF09",
147
- customChoice: "\u81EA\u5B9A\u4E49\u8F93\u5165\u2026",
148
- customPrompt: "\u8F93\u5165\u6A21\u578B\u540D\u79F0\uFF08\u7559\u7A7A = \u4E0D\u8BBE\u7F6E\uFF0C\u56DE\u9000\u5F53\u524D\u4F1A\u8BDD\u6A21\u578B\uFF09",
149
- unsetChoice: "\u4E0D\u8BBE\u7F6E\uFF08\u56DE\u9000\u5F53\u524D\u4F1A\u8BDD\u6A21\u578B\uFF09",
150
- reviewModelAPrompt: "\u5BA1\u67E5 agent A \u6A21\u578B\u540D\u79F0\uFF08\u7559\u7A7A = \u4E0D\u8BBE\u7F6E\uFF0C\u56DE\u9000\u5F53\u524D\u4F1A\u8BDD\u6A21\u578B\uFF09",
151
- reviewModelBPrompt: "\u5BA1\u67E5 agent B \u6A21\u578B\u540D\u79F0\uFF08\u7559\u7A7A = \u4E0D\u8BBE\u7F6E\uFF0C\u56DE\u9000\u5F53\u524D\u4F1A\u8BDD\u6A21\u578B\uFF09",
152
- codingModelPrompt: "coding \u5B9E\u65BD\u6A21\u578B\u540D\u79F0\uFF08\u7559\u7A7A = \u4E0D\u8BBE\u7F6E\uFF0C\u56DE\u9000\u5F53\u524D\u4F1A\u8BDD\u6A21\u578B\uFF09"
179
+ unsetChoice: "\u9ED8\u8BA4\u7EE7\u627F\u5F53\u524D\u4F1A\u8BDD\u6A21\u578B\uFF08\u7559\u7A7A\uFF09",
180
+ customChoice: "\u81EA\u5B9A\u4E49\u8F93\u5165\u6A21\u578B\u2026",
181
+ customPrompt: "\u8F93\u5165\u6A21\u578B\u540D\uFF08\u7559\u7A7A\u53EF\u53D6\u6D88\uFF1Bagent \u6A21\u578B\u9700\u989D\u5916\u914D\u7F6E\uFF0C\u53EF\u7528\u793A\u4F8B prompt \u9A8C\u8BC1\u662F\u5426\u53EF spawn\uFF09"
182
+ }
183
+ },
184
+ doctor: {
185
+ modelConfig: {
186
+ label: "Codex \u5B50\u4EE3\u7406\u6A21\u578B\u914D\u7F6E",
187
+ okUnset: "{{key}} \u672A\u914D\u7F6E\uFF08\u56DE\u9000\u5F53\u524D\u4F1A\u8BDD\u6A21\u578B\uFF09",
188
+ okConfigured: "{{key}}={{model}}\uFF08\u5DF2\u914D\u7F6E\uFF09",
189
+ executorMain: "{{key}}=\u4E3B agent \u76F4\u63A5\u6267\u884C\uFF08\u9ED8\u8BA4\uFF09",
190
+ executorSubagent: "{{key}}=spawn \u72EC\u7ACB\u5B50\u4EE3\u7406",
191
+ warnInvalidExecutor: "{{key}} \u53D6\u503C\u975E\u6CD5\uFF08{{value}}\uFF09\uFF0C\u6309 main \u5904\u7406",
192
+ warnIneffective: "{{key}}={{model}} \u5728\u4E3B agent \u6267\u884C\u65F6\u4E0D\u751F\u6548\uFF08\u4EC5\u5C55\u793A\uFF09",
193
+ removedNote: "{{key}} \u5DF2\u79FB\u9664\uFF08\u4E0D\u518D\u8BFB\u53D6\u6216\u4FDD\u7559\uFF09",
194
+ okReasoningConfigured: "{{key}}={{effort}}\uFF08\u5DF2\u914D\u7F6E\uFF0C\u4EC5\u5C55\u793A\uFF0C\u4E0D\u505A\u679A\u4E3E\u5F3A\u6821\u9A8C\uFF09",
195
+ okReasoningUnset: "{{key}} \u672A\u914D\u7F6E\uFF08\u4E0D\u4F20\u63A8\u7406\u6863\u53C2\u6570\uFF09",
196
+ warnInvalid: "spawnableModels \u5B57\u6BB5\u683C\u5F0F\u975E\u6CD5/\u6E05\u6D17\u540E\u4E3A\u7A7A\uFF08\u4EC5\u5F62\u6001\u63D0\u793A\uFF0C\u53EF\u8FD0\u884C lycx init/update \u4FEE\u590D\uFF09",
197
+ agentNeedConfig: "agent \u6A21\u578B\u9700\u989D\u5916\u914D\u7F6E\uFF1A\u80FD\u5426 spawn \u7531\u5F53\u524D\u73AF\u5883\u5B9E\u9645\u80FD\u529B\u51B3\u5B9A\uFF0C\u5DE5\u5177\u4E0D\u505A\u6E05\u5355\u5F3A\u6821\u9A8C",
198
+ inheritNote: "\u7559\u7A7A\u5B57\u6BB5\u5C06\u7EE7\u627F\u5F53\u524D\u4E3B\u4F1A\u8BDD\u6A21\u578B {{model}}",
199
+ verifyHint: "\u9A8C\u8BC1\u67D0\u6A21\u578B\u662F\u5426\u53EF spawn \u7684\u793A\u4F8B prompt\uFF1A\u8BA9 Codex \u7528\u76EE\u6807\u6A21\u578B spawn \u4E00\u4E2A\u5B50\u4EE3\u7406\u6267\u884C\u7B80\u5355\u4EFB\u52A1\uFF08\u5982\u56DE\u590D ok\uFF09\u2014\u2014\u82E5\u62A5\u9519\u539F\u6587\u542B Unknown model ... Available models: ...\uFF0C\u8BF4\u660E\u8BE5\u6A21\u578B\u5F53\u524D\u4E0D\u652F\u6301 spawn"
153
200
  }
154
201
  },
155
202
  update: {
@@ -214,13 +261,12 @@ const zhCN = {
214
261
  step2: "\u5378\u8F7D npm \u5168\u5C40\u5305",
215
262
  step2Hint: "\u9700\u8981\u624B\u52A8\u6267\u884C",
216
263
  continuePrompt: "\u7EE7\u7EED\u5378\u8F7D\u5DE5\u4F5C\u6D41\u6587\u4EF6\uFF1F",
217
- confirm: "\u786E\u5B9A\u8981\u5378\u8F7D ly-workflow-codex \u5417\uFF1F\u5C06\u79FB\u9664 ~/.agents/skills/lyx-*\uFF08\u542B\u65E7 ~/.agents/skills/ly-* \u4E0E ~/.codex/prompts/ly-*.md \u6B8B\u7559\uFF09\u4E0E\u5171\u4EAB\u89D2\u8272\u8BCD ~/.ly/prompts/codex/\uFF1B\u5171\u4EAB\u914D\u7F6E ~/.ly/config.toml \u4E0E ~/.ly/ \u5176\u4F59\u5185\u5BB9\uFF08\u542B worktrees\uFF09\u4FDD\u7559\u3002",
264
+ confirm: "\u786E\u5B9A\u8981\u5378\u8F7D ly-workflow-codex \u5417\uFF1F\u5C06\u79FB\u9664 ~/.agents/skills/lyx-*\uFF08\u542B\u65E7 ~/.agents/skills/ly-* \u4E0E ~/.codex/prompts/ly-*.md \u6B8B\u7559\uFF09\u4E0E ~/.codex/lyx/\uFF08\u914D\u7F6E config.toml\u3001\u89D2\u8272\u8BCD prompts/\u3001worktrees\uFF09\uFF1B\u82E5 ~/.codex/lyx/worktrees/ \u4E0B\u5B58\u5728\u672A\u6E05\u7406\u7684\u5B9E\u9645 git worktree\uFF0C\u8BE5\u5B50\u76EE\u5F55\u4F1A\u88AB\u4FDD\u7559\u5E76\u63D0\u793A\u9700\u5148\u624B\u52A8\u6E05\u7406\u3002",
218
265
  uninstalling: "\u6B63\u5728\u5378\u8F7D...",
219
266
  success: "\u5DE5\u4F5C\u6D41\u6587\u4EF6\u5DF2\u79FB\u9664",
220
267
  removedSkills: "\u5DF2\u79FB\u9664 lyx-* skills:",
221
268
  removedLegacyPrompts: "\u5DF2\u6E05\u7406\u65E7\u5B89\u88C5\u4F4D\u6B8B\u7559 (~/.agents/skills/ly-*\u3001~/.codex/prompts/ly-*.md):",
222
- removedSharedPrompts: "\u5DF2\u6E05\u7406\u5171\u4EAB\u89D2\u8272\u8BCD (~/.ly/prompts/codex/):",
223
- configTomlKept: "\u5171\u4EAB\u914D\u7F6E (~/.ly/config.toml) \u5DF2\u4FDD\u7559\uFF1A~/.ly \u4E3A ly-workflow \u5171\u4EAB\u547D\u540D\u7A7A\u95F4\uFF08\u542B worktrees \u7B49\u771F\u5B9E\u6570\u636E\uFF09\uFF0C\u5982\u9700\u6E05\u9664\u8BF7\u4EBA\u5DE5\u786E\u8BA4\u3002",
269
+ removedPrompts: "\u5DF2\u6E05\u7406\u89D2\u8272\u8BCD (~/.codex/lyx/prompts/codex/):",
224
270
  cancelled: "\u5378\u8F7D\u5DF2\u53D6\u6D88",
225
271
  failed: "\u5378\u8F7D\u5931\u8D25",
226
272
  lastStep: "\u6700\u540E\u4E00\u6B65\uFF1A\u5378\u8F7D npm \u5168\u5C40\u5305",
@@ -235,6 +281,7 @@ const en = {
235
281
  no: "No",
236
282
  preflight: {
237
283
  cliMissing: "openspec CLI not found \u2014 OpenSpec lifecycle commands depend on it (@fission-ai/openspec)",
284
+ cliUnhealthy: "openspec CLI detected but unhealthy (timeout/hang) \u2014 verify the installation",
238
285
  installAsk: "Install openspec globally now? (npm install -g @fission-ai/openspec@latest)",
239
286
  installSuccessWithSkills: "openspec installed and OpenSpec command skills ready \u2014 all @lyx-* skills available",
240
287
  installSuccessNeedInit: "openspec installed; run @lyx-init to complete openspec init before using OpenSpec lifecycle commands",
@@ -242,13 +289,24 @@ const en = {
242
289
  installNotInPath: "openspec was installed but not found on the current PATH \u2014 reopen your terminal or add the npm global bin directory to PATH",
243
290
  unavailableList: "These commands depend on openspec and are currently unavailable: {{list}}",
244
291
  unaffectedNote: "@lyx-apply and @lyx-review-code only need the in-project change directory; Git tools and quality-gate skills are unaffected",
245
- skillsMissing: "openspec CLI is installed but OpenSpec command skills are not initialized \u2014 run @lyx-init to initialize",
246
- skillsMissingCodex: "codex host is missing the openspec-* skills \u2014 run openspec init to install them into ~/.agents/skills/ or the project .agents/skills/"
292
+ skillsMissing: "OpenSpec skills missing: {{list}} \u2014 run @lyx-init or repair and retry",
293
+ skillsGlobalOnly: "OpenSpec skills are available only globally; commands can continue but are not pinned to this project (WARN)",
294
+ skillsUnknown: "Unable to read the OpenSpec workflow profile; skills status is unknown (WARN)",
295
+ skillsMissingCodex: "codex host is missing the openspec-* skills \u2014 run openspec init to install them into ~/.agents/skills/ or the project .agents/skills/",
296
+ rootMissing: "OpenSpec root is not initialized \u2014 run @lyx-init to complete openspec init",
297
+ rootUnhealthy: "OpenSpec root is unhealthy \u2014 run openspec doctor --json for fix guidance"
247
298
  },
248
299
  doctor: {
249
300
  openspecCliMissing: "Not found (npm install -g @fission-ai/openspec)",
301
+ openspecCliUnhealthy: "Unhealthy (verify openspec installation)",
250
302
  skillsInitialized: "initialized",
251
- skillsMissing: "not initialized (run @lyx-init)"
303
+ skillsGlobalOnly: "global only (WARN; run @lyx-init to pin to this project)",
304
+ skillsUnknown: "unknown (profile read failed, WARN)",
305
+ skillsMissing: "missing: {{list}}",
306
+ rootHealthy: "healthy",
307
+ rootMissing: "not initialized (run @lyx-init)",
308
+ rootUnhealthy: "unhealthy (run openspec doctor --json)",
309
+ rootNotChecked: "not checked (openspec CLI unavailable)"
252
310
  },
253
311
  confirm: "Confirm",
254
312
  cancel: "Cancel",
@@ -291,7 +349,8 @@ const en = {
291
349
  skipAllPrompts: "Skip all interactive prompts (non-interactive mode)",
292
350
  skipConfirmation: "Skip uninstall confirmation",
293
351
  workflows: "Workflows to install",
294
- installDir: "Installation directory"
352
+ installDir: "Installation directory",
353
+ initOpenspec: "Initialize the current project OpenSpec root and skills (explicit escape hatch)"
295
354
  },
296
355
  nonInteractiveMode: "Non-interactive mode:",
297
356
  examples: "Examples",
@@ -305,33 +364,47 @@ const en = {
305
364
  init: {
306
365
  selectLanguage: "Select language / \u9009\u62E9\u8BED\u8A00",
307
366
  host: {
308
- reviewModelPrompt: "Model for review agent A (empty = fall back to the current session model; agent B / coding models can be configured via lycx init)",
309
- reviewModelHint: "e.g. gpt-5.1-codex / gpt-5.1 / o4-mini, free-form input; leave empty to fall back",
367
+ reviewModelPrompt: "Review agent A model (empty = inherit the current session model; agent B / coding models can be configured via lycx init) \u2014 agent models need extra setup: spawn support depends on the environment",
368
+ reviewModelHint: "Candidates = inherit current session model (empty) + custom input + existing value (if any, defaulted, never silently dropped); no list-based validation \u2014 verify spawn support with a sample prompt",
310
369
  codexSummaryTitle: "Codex Configuration Summary:",
311
370
  reviewModelUnset: "not configured (fall back to the current session model)"
312
371
  },
313
372
  mode: {
314
373
  providerSelect: "Select API provider",
315
374
  providerExisting: "Existing provider: {{name}}",
375
+ providerExistingSelected: "Selected provider: {{name}}",
316
376
  providerOfficial: "OpenAI official",
317
- providerCustom: "Custom (name / base_url / API key)",
377
+ providerCustom: "Custom (name / base_url)",
318
378
  customNamePrompt: "Provider name (written to [model_providers.<name>])",
319
379
  nameInvalid: "Name allows letters, digits, underscores and hyphens only",
320
380
  customUrlPrompt: "base_url",
321
- customKeyPrompt: "API key (used only to fetch the model list, not written to config.toml)",
322
- optional: "optional",
323
381
  providerExists: "Provider {{name}} already exists in ~/.codex/config.toml \u2014 selected as-is",
324
382
  providerWritten: "Incrementally written to ~/.codex/config.toml: model_provider = {{name}} (existing comments preserved)",
325
- providerWriteFailed: "Failed to write ~/.codex/config.toml: {{error}} (not persisted this run; used only to fetch the model list)",
326
- configParseFailed: "Failed to parse ~/.codex/config.toml \u2014 no existing providers detected (non-blocking)",
327
- modelFetching: "Fetching model list from {{baseUrl}}/models...",
328
- modelFetchFailed: "Failed to fetch the model list: {{reason}}",
329
- envKeyMissingPrompt: "Environment variable {{envKey}} is not set \u2014 enter the API key for this provider (press Enter to skip authenticated fetching)"
383
+ providerWriteFailed: "Failed to write ~/.codex/config.toml: {{error}}",
384
+ configParseFailed: "Failed to parse ~/.codex/config.toml \u2014 no existing providers detected (non-blocking)"
385
+ },
386
+ codexStatus: {
387
+ title: "Codex status snapshot (read-only, modifies nothing in Codex config)",
388
+ mainModelLabel: "Main session model",
389
+ mainModel: '{{model}} (the actual value of "inherit default")',
390
+ providersLabel: "Provider entries",
391
+ noProvider: "No [model_providers.*] entries",
392
+ registeredLabel: "models.json registered models",
393
+ registeredModels: "{{count}} (display only; its content \u2260 the spawn-available list, not used as candidates)",
394
+ notDetected: "not detected",
395
+ reasoningHint: "Note: some third-party models (e.g. glm-5.3-flash) cannot spawn with the default reasoning parameters. Hand-edit the matching reviewReasoningEffort / codingReasoningEffort field under [codexHost] (for example low). The wizard does not collect reasoning effort, values are not enum-validated, and related errors are shown as-is.",
396
+ spawnableHint: "Agent models need extra setup (no list-based validation): spawn support depends on the environment. Verification sample: ask Codex in a conversation to spawn a subagent with the target model for a trivial task (e.g. reply ok); an error containing Unknown model ... Available models: ... means it is not currently supported. [codexHost] spawnableModels is reference-only; you may hand-edit ~/.codex/lyx/config.toml"
330
397
  },
331
- promptsMigrated: "Role prompts migrated to ~/.ly/prompts/ (codex shared location)",
332
- promptsMigrationFailed: "Legacy role prompt migration failed (non-blocking): {{error}}",
333
398
  selectWorkflows: "Select workflows to install (multi-select)",
334
399
  confirmInstall: "Confirm installation with above configuration?",
400
+ executor: {
401
+ title: "Executor duo (who runs review / implementation)",
402
+ hint: '"Main agent executes directly" is the default: no subagent spawn, no independent review perspective. Choosing "spawn a dedicated subagent" gives an independent perspective, but spawn support depends on the current environment.',
403
+ reviewExecutor: "Review executor (reviewExecutor)",
404
+ codingExecutor: "Implementation executor (codingExecutor)",
405
+ main: "Main agent executes directly (default)",
406
+ subagent: "Spawn a dedicated subagent"
407
+ },
335
408
  installing: "Installing...",
336
409
  installSuccess: "Installation successful!",
337
410
  installFailed: "Installation failed",
@@ -342,8 +415,12 @@ const en = {
342
415
  summary: {
343
416
  title: "Configuration summary:",
344
417
  host: "Install host",
418
+ reviewExecutor: "Review executor",
419
+ codingExecutor: "Implementation executor",
420
+ executorMain: "main agent executes directly",
421
+ executorSubagent: "spawns a dedicated subagent",
422
+ modelIneffective: "{{model}} (has no effect when the main agent executes)",
345
423
  reviewModelA: "Review agent A model",
346
- reviewModelB: "Review agent B model",
347
424
  codingModel: "Coding implementation model",
348
425
  reviewModelCodex: "Review model",
349
426
  workflows: "Workflows:",
@@ -356,16 +433,32 @@ const en = {
356
433
  reinstalling: "Reinstalling command templates to apply changes...",
357
434
  reinstallDone: "Templates updated",
358
435
  reinstallFailed: "Template update failed",
359
- trioTitle: "Model trio (review agent A / review agent B / coding implementation)",
436
+ trioTitle: "Model duo (review subagent / coding implementation)",
437
+ trioCandidatesHint: "Candidates = inherit current session model (empty) + custom input + existing value (if any, defaulted); agent models need extra setup \u2014 spawn support depends on the environment",
360
438
  reviewModelA: "Review agent A model (reviewModel)",
361
439
  reviewModelB: "Review agent B model (reviewModelB, optional)",
362
440
  codingModel: "Coding implementation model (codingModel, optional)",
363
- customChoice: "Custom input\u2026",
364
- customPrompt: "Enter a model name (empty = not set, fall back to the current session model)",
365
- unsetChoice: "Do not set (fall back to the current session model)",
366
- reviewModelAPrompt: "Model for review agent A (empty = not set, fall back to the current session model)",
367
- reviewModelBPrompt: "Model for review agent B (empty = not set, fall back to the current session model)",
368
- codingModelPrompt: "Model for coding implementation (empty = not set, fall back to the current session model)"
441
+ unsetChoice: "Inherit current session model (empty)",
442
+ customChoice: "Custom model input\u2026",
443
+ customPrompt: "Enter a model name (empty to cancel; agent models need extra setup \u2014 verify spawn support with a sample prompt)"
444
+ }
445
+ },
446
+ doctor: {
447
+ modelConfig: {
448
+ label: "Codex subagent model config",
449
+ okUnset: "{{key}} unset (falls back to the current session model)",
450
+ okConfigured: "{{key}}={{model}} (configured)",
451
+ executorMain: "{{key}}=main agent executes directly (default)",
452
+ executorSubagent: "{{key}}=spawns a dedicated subagent",
453
+ warnInvalidExecutor: "{{key}} has an invalid value ({{value}}); treated as main",
454
+ warnIneffective: "{{key}}={{model}} has no effect when the main agent executes (display only)",
455
+ removedNote: "{{key}} has been removed (no longer read or preserved)",
456
+ okReasoningConfigured: "{{key}}={{effort}} (configured, display only, no enum validation)",
457
+ okReasoningUnset: "{{key}} not configured (reasoning effort is not passed)",
458
+ warnInvalid: "spawnableModels is malformed / empty after cleaning (shape hint only; run lycx init/update to fix)",
459
+ agentNeedConfig: "Agent models need extra setup: spawn support depends on the current environment; this tool performs no list-based validation",
460
+ inheritNote: "Unset fields inherit the current main session model {{model}}",
461
+ verifyHint: "Sample prompt to check spawn support: ask Codex to spawn a subagent with the target model for a trivial task (e.g. reply ok) \u2014 an error containing Unknown model ... Available models: ... means the model is not currently spawnable"
369
462
  }
370
463
  },
371
464
  update: {
@@ -430,13 +523,12 @@ const en = {
430
523
  step2: "Uninstall npm global package",
431
524
  step2Hint: "requires manual execution",
432
525
  continuePrompt: "Continue removing workflow files?",
433
- confirm: "Uninstall ly-workflow-codex? This removes ~/.agents/skills/lyx-* (incl. legacy ~/.agents/skills/ly-* and ~/.codex/prompts/ly-*.md residue) and shared prompts (~/.ly/prompts/codex/); shared config ~/.ly/config.toml and the rest of ~/.ly/ (incl. worktrees) are kept.",
526
+ confirm: "Uninstall ly-workflow-codex? This removes ~/.agents/skills/lyx-* (incl. legacy ~/.agents/skills/ly-* and ~/.codex/prompts/ly-*.md residue) and ~/.codex/lyx/ (config.toml, prompts/, worktrees). If ~/.codex/lyx/worktrees/ still holds a live git worktree, that subdirectory is kept and a manual cleanup is suggested.",
434
527
  uninstalling: "Uninstalling...",
435
528
  success: "Workflow files removed",
436
529
  removedSkills: "Removed lyx-* skills:",
437
530
  removedLegacyPrompts: "Cleaned legacy install residue (~/.agents/skills/ly-*, ~/.codex/prompts/ly-*.md):",
438
- removedSharedPrompts: "Removed shared prompts (~/.ly/prompts/codex/):",
439
- configTomlKept: "Shared config (~/.ly/config.toml) kept: ~/.ly is the ly-workflow shared namespace (incl. worktrees); remove manually only after confirmation.",
531
+ removedPrompts: "Removed prompts (~/.codex/lyx/prompts/codex/):",
440
532
  cancelled: "Uninstallation cancelled",
441
533
  failed: "Uninstallation failed",
442
534
  lastStep: "Last step: Uninstall npm global package",
@@ -466,131 +558,12 @@ async function changeLanguage(lang) {
466
558
  await i18n.changeLanguage(lang);
467
559
  }
468
560
 
469
- const PACKAGE_NAME = "ly-workflow-codex";
470
- const BIN_NAME = "lycx";
471
- const CODE_PROMPTS_DIR = join(homedir(), ".codex", "prompts");
472
- const AGENTS_SKILLS_DIR = join(homedir(), ".agents", "skills");
473
- const LY_DIR = join(homedir(), ".ly");
474
- const CONFIG_FILE = join(LY_DIR, "config.toml");
475
- const PROMPTS_DIR = join(LY_DIR, "prompts");
476
- const ISSUES_URL = "https://github.com/FE-runner/ly-workflow-codex/issues";
477
-
478
- const LY_PROMPTS_DIR = PROMPTS_DIR;
479
- function getLyPromptsDir() {
480
- return LY_PROMPTS_DIR;
481
- }
482
- function getLyDir() {
483
- return LY_DIR;
484
- }
485
- function getConfigPath() {
486
- return CONFIG_FILE;
487
- }
488
- function isValidInstalledHost(value) {
489
- return value === "codex" || value === "claude";
490
- }
491
- function sanitizeInstalledHosts(value) {
492
- const arr = Array.isArray(value) ? value : [];
493
- return [...new Set(arr.filter(isValidInstalledHost))];
494
- }
495
- async function ensureLyDir() {
496
- await fs.ensureDir(LY_DIR);
497
- }
498
- async function hasCoexistingLegacyLyProducts(home = homedir()) {
499
- try {
500
- if (await fs.pathExists(join(home, ".claude", "commands", "ly")))
501
- return true;
502
- const legacyConfig = join(home, ".claude", ".ly", "config.toml");
503
- if (await fs.pathExists(legacyConfig)) {
504
- const content = await fs.readFile(legacyConfig, "utf-8");
505
- return /installedHosts\s*=.*claude/i.test(content) || content.includes(".claude");
506
- }
507
- } catch {
508
- }
509
- return false;
510
- }
511
- async function migrateLegacyConfig() {
512
- const legacyConfig = join(homedir(), ".claude", ".ly", "config.toml");
513
- try {
514
- if (!await fs.pathExists(legacyConfig))
515
- return false;
516
- if (await fs.pathExists(CONFIG_FILE))
517
- return false;
518
- if (await hasCoexistingLegacyLyProducts()) {
519
- console.warn("[lycx] \u68C0\u6D4B\u5230 ~/.claude/.ly/ \u4ECD\u88AB ly-workflow \u4F7F\u7528\uFF08\u5171\u5B58\u573A\u666F\uFF09\uFF0C\u8DF3\u8FC7\u914D\u7F6E\u8FC1\u79FB\uFF0C\u4FDD\u7559\u4ED6\u65B9\u5728\u7528\u914D\u7F6E");
520
- return false;
521
- }
522
- await fs.ensureDir(LY_DIR);
523
- await fs.move(legacyConfig, CONFIG_FILE, { overwrite: false });
524
- return true;
525
- } catch {
526
- return false;
527
- }
528
- }
529
- async function readLyConfig() {
530
- try {
531
- await migrateLegacyConfig();
532
- if (await fs.pathExists(CONFIG_FILE)) {
533
- const content = await fs.readFile(CONFIG_FILE, "utf-8");
534
- const parsed = parse(content);
535
- delete parsed.routing;
536
- delete parsed.performance;
537
- return parsed;
538
- }
539
- } catch {
540
- }
541
- return null;
542
- }
543
- async function writeLyConfig(config) {
544
- await migrateLegacyConfig();
545
- await ensureLyDir();
546
- const content = stringify(config);
547
- await fs.writeFile(CONFIG_FILE, content, "utf-8");
548
- }
549
- function createDefaultConfig(options) {
550
- const config = {
551
- general: {
552
- version: version,
553
- language: options.language,
554
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
555
- },
556
- workflows: {
557
- installed: options.installedWorkflows
558
- },
559
- installedHosts: sanitizeInstalledHosts(options.installedHosts).length > 0 ? sanitizeInstalledHosts(options.installedHosts) : ["codex"],
560
- paths: {
561
- commands: PROMPTS_DIR,
562
- prompts: LY_PROMPTS_DIR,
563
- backup: join(LY_DIR, "backup")
564
- }
565
- };
566
- const reviewModel = sanitizeReviewModel(options.codexHost?.reviewModel);
567
- const reviewModelB = sanitizeModelField(options.codexHost?.reviewModelB);
568
- const codingModel = sanitizeModelField(options.codexHost?.codingModel);
569
- if (reviewModel || reviewModelB || codingModel) {
570
- config.codexHost = {
571
- ...reviewModel ? { reviewModel } : {},
572
- ...reviewModelB ? { reviewModelB } : {},
573
- ...codingModel ? { codingModel } : {}
574
- };
575
- }
576
- return config;
577
- }
578
- function sanitizeReviewModel(value) {
579
- if (typeof value !== "string")
580
- return void 0;
581
- const cleaned = value.trim().replace(/[^\w.:/-]/g, "");
582
- return cleaned === "" ? void 0 : cleaned;
583
- }
584
- function sanitizeModelField(value) {
585
- if (typeof value !== "string")
586
- return void 0;
587
- const cleaned = value.trim();
588
- return cleaned === "" ? void 0 : cleaned;
589
- }
590
-
591
561
  function codexConfigPath() {
592
562
  return join(homedir(), ".codex", "config.toml");
593
563
  }
564
+ function codexModelsJsonPath() {
565
+ return join(homedir(), ".codex", "models.json");
566
+ }
594
567
  function sanitizeProviderName(value) {
595
568
  if (typeof value !== "string")
596
569
  return void 0;
@@ -624,6 +597,35 @@ async function listModelProviders(filePath = codexConfigPath()) {
624
597
  }
625
598
  return providers;
626
599
  }
600
+ async function readCodexCurrentModel(filePath = codexConfigPath()) {
601
+ const parsed = await readCodexConfigToml(filePath);
602
+ const model = parsed?.model;
603
+ return typeof model === "string" && model.trim() !== "" ? model.trim() : void 0;
604
+ }
605
+ async function readModelsJson(filePath = codexModelsJsonPath()) {
606
+ let json;
607
+ try {
608
+ if (!await fs.pathExists(filePath))
609
+ return void 0;
610
+ json = JSON.parse(await fs.readFile(filePath, "utf-8"));
611
+ } catch {
612
+ return void 0;
613
+ }
614
+ const collect = (items2) => {
615
+ if (!Array.isArray(items2))
616
+ return [];
617
+ const names = [];
618
+ for (const item of items2) {
619
+ const id = typeof item === "string" ? item : item && typeof item === "object" && typeof item.slug === "string" ? item.slug : "";
620
+ const trimmed = id.trim();
621
+ if (trimmed !== "" && !names.includes(trimmed))
622
+ names.push(trimmed);
623
+ }
624
+ return names;
625
+ };
626
+ const items = Array.isArray(json) ? json : json && typeof json === "object" && Array.isArray(json.models) ? json.models : null;
627
+ return items === null ? void 0 : collect(items);
628
+ }
627
629
  async function upsertModelProvider(input, filePath = codexConfigPath()) {
628
630
  const name = sanitizeProviderName(input.name);
629
631
  if (!name)
@@ -681,43 +683,155 @@ ${block.join("\n")}
681
683
  }
682
684
  return { status: "written", path: filePath };
683
685
  }
684
- async function fetchCodexModels(input) {
685
- const base = input.baseUrl.trim().replace(/\/+$/, "");
686
- const url = `${base}/models`;
687
- const headers = {};
688
- if (input.apiKey)
689
- headers.Authorization = `Bearer ${input.apiKey}`;
690
- let res;
691
- try {
692
- res = await fetch(url, {
693
- method: "GET",
694
- headers,
695
- signal: AbortSignal.timeout(input.timeoutMs ?? 1e4)
696
- });
697
- } catch (error) {
698
- return { ok: false, error: error instanceof Error ? error.message : String(error) };
699
- }
700
- if (!res.ok)
701
- return { ok: false, error: `HTTP ${res.status}` };
702
- let json;
686
+
687
+ const PACKAGE_NAME = "ly-workflow-codex";
688
+ const BIN_NAME = "lycx";
689
+ const CODE_PROMPTS_DIR = join(homedir(), ".codex", "prompts");
690
+ const AGENTS_SKILLS_DIR = join(homedir(), ".agents", "skills");
691
+ const LY_DIR = join(homedir(), ".codex", "lyx");
692
+ const CONFIG_FILE = join(LY_DIR, "config.toml");
693
+ const PROMPTS_DIR = join(LY_DIR, "prompts");
694
+ const ISSUES_URL = "https://github.com/FE-runner/ly-workflow-codex/issues";
695
+
696
+ const LY_PROMPTS_DIR = PROMPTS_DIR;
697
+ function getLyPromptsDir() {
698
+ return LY_PROMPTS_DIR;
699
+ }
700
+ function getLyDir() {
701
+ return LY_DIR;
702
+ }
703
+ function getConfigPath() {
704
+ return CONFIG_FILE;
705
+ }
706
+ const SPAWNABLE_MODELS_DEFAULT = [
707
+ "gpt-6-astra",
708
+ "gpt-5.6-sol",
709
+ "gpt-5.6-terra",
710
+ "gpt-5.6-luna",
711
+ "gpt-5.5"
712
+ ];
713
+ function isValidInstalledHost(value) {
714
+ return value === "codex" || value === "claude";
715
+ }
716
+ function sanitizeInstalledHosts(value) {
717
+ const arr = Array.isArray(value) ? value : [];
718
+ return [...new Set(arr.filter(isValidInstalledHost))];
719
+ }
720
+ async function ensureLyDir() {
721
+ await fs.ensureDir(LY_DIR);
722
+ }
723
+ async function readLyConfig() {
703
724
  try {
704
- json = await res.json();
725
+ if (await fs.pathExists(CONFIG_FILE)) {
726
+ const content = await fs.readFile(CONFIG_FILE, "utf-8");
727
+ const parsed = parse(content);
728
+ delete parsed.routing;
729
+ delete parsed.performance;
730
+ return parsed;
731
+ }
705
732
  } catch {
706
- return { ok: false, error: "response is not valid JSON" };
707
733
  }
708
- const arr = Array.isArray(json) ? json : Array.isArray(json?.data) ? json.data : null;
709
- if (!arr)
710
- return { ok: false, error: "unexpected response shape (missing data[])" };
734
+ return null;
735
+ }
736
+ async function writeLyConfig(config) {
737
+ await ensureLyDir();
738
+ const content = stringify(config);
739
+ await fs.writeFile(CONFIG_FILE, content, "utf-8");
740
+ }
741
+ function createDefaultConfig(options) {
742
+ const config = {
743
+ general: {
744
+ version: version,
745
+ language: options.language,
746
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
747
+ },
748
+ workflows: {
749
+ installed: options.installedWorkflows
750
+ },
751
+ installedHosts: sanitizeInstalledHosts(options.installedHosts).length > 0 ? sanitizeInstalledHosts(options.installedHosts) : ["codex"],
752
+ paths: {
753
+ commands: PROMPTS_DIR,
754
+ prompts: LY_PROMPTS_DIR,
755
+ backup: join(LY_DIR, "backup")
756
+ }
757
+ };
758
+ const reviewExecutor = sanitizeExecutor(options.codexHost?.reviewExecutor);
759
+ const codingExecutor = sanitizeExecutor(options.codexHost?.codingExecutor);
760
+ const reviewModel = sanitizeReviewModel(options.codexHost?.reviewModel);
761
+ const codingModel = sanitizeModelField(options.codexHost?.codingModel);
762
+ const reviewReasoningEffort = sanitizeReasoningEffort(options.codexHost?.reviewReasoningEffort);
763
+ const codingReasoningEffort = sanitizeReasoningEffort(options.codexHost?.codingReasoningEffort);
764
+ const spawnableModels = options.codexHost?.spawnableModels;
765
+ if (reviewExecutor || codingExecutor || reviewModel || codingModel || reviewReasoningEffort || codingReasoningEffort || spawnableModels !== void 0) {
766
+ config.codexHost = {
767
+ ...reviewExecutor ? { reviewExecutor } : {},
768
+ ...codingExecutor ? { codingExecutor } : {},
769
+ ...reviewModel ? { reviewModel } : {},
770
+ ...codingModel ? { codingModel } : {},
771
+ ...reviewReasoningEffort ? { reviewReasoningEffort } : {},
772
+ ...codingReasoningEffort ? { codingReasoningEffort } : {},
773
+ ...spawnableModels !== void 0 ? { spawnableModels } : {}
774
+ };
775
+ }
776
+ return config;
777
+ }
778
+ function sanitizeExecutor(value) {
779
+ if (typeof value !== "string")
780
+ return void 0;
781
+ const cleaned = value.trim();
782
+ return cleaned === "main" || cleaned === "subagent" ? cleaned : void 0;
783
+ }
784
+ function sanitizeReviewModel(value) {
785
+ if (typeof value !== "string")
786
+ return void 0;
787
+ const cleaned = value.trim();
788
+ return cleaned === "" ? void 0 : cleaned;
789
+ }
790
+ function sanitizeModelField(value) {
791
+ if (typeof value !== "string")
792
+ return void 0;
793
+ const cleaned = value.trim();
794
+ return cleaned === "" ? void 0 : cleaned;
795
+ }
796
+ function sanitizeReasoningEffort(value) {
797
+ if (typeof value !== "string")
798
+ return void 0;
799
+ const cleaned = value.trim();
800
+ return cleaned === "" ? void 0 : cleaned;
801
+ }
802
+ function sanitizeCodexHostExtras(codexHost) {
803
+ if (!codexHost)
804
+ return {};
805
+ const reviewExecutor = sanitizeExecutor(codexHost.reviewExecutor);
806
+ const codingExecutor = sanitizeExecutor(codexHost.codingExecutor);
807
+ const codingModel = sanitizeModelField(codexHost.codingModel);
808
+ const reviewReasoningEffort = sanitizeReasoningEffort(codexHost.reviewReasoningEffort);
809
+ const codingReasoningEffort = sanitizeReasoningEffort(codexHost.codingReasoningEffort);
810
+ return {
811
+ ...reviewExecutor ? { reviewExecutor } : {},
812
+ ...codingExecutor ? { codingExecutor } : {},
813
+ ...codingModel ? { codingModel } : {},
814
+ ...reviewReasoningEffort ? { reviewReasoningEffort } : {},
815
+ ...codingReasoningEffort ? { codingReasoningEffort } : {},
816
+ ...codexHost.spawnableModels !== void 0 ? { spawnableModels: codexHost.spawnableModels } : {}
817
+ };
818
+ }
819
+ function sanitizeSpawnableModels(value) {
820
+ if (value === void 0 || value === null)
821
+ return { state: "unset", models: [] };
822
+ if (!Array.isArray(value))
823
+ return { state: "invalid", models: [] };
711
824
  const models = [];
712
- for (const item of arr) {
713
- const id = typeof item === "string" ? item : typeof item?.id === "string" ? item.id : "";
714
- const trimmed = id.trim();
825
+ for (const item of value) {
826
+ if (typeof item !== "string")
827
+ continue;
828
+ const trimmed = item.trim();
715
829
  if (trimmed !== "" && !models.includes(trimmed))
716
830
  models.push(trimmed);
717
831
  }
718
832
  if (models.length === 0)
719
- return { ok: false, error: "empty model list" };
720
- return { ok: true, models };
833
+ return { state: "empty", models: [] };
834
+ return { state: "ok", models };
721
835
  }
722
836
 
723
837
  const __filename$2 = fileURLToPath(import.meta.url);
@@ -752,6 +866,7 @@ function injectConfigVariables(content, _config) {
752
866
  processed = processed.replace(/\n?<!--\s*LY:IF:IMPLEMENTER_EXTERNAL\s*-->[\s\S]*?<!--\s*LY:ENDIF\s*-->\n?/g, "");
753
867
  processed = processed.replace(/\n?<!--\s*LY:IF:IMPLEMENTER_CLAUDE\s*-->[\s\S]*?<!--\s*LY:ENDIF\s*-->\n?/g, "");
754
868
  processed = processed.replace(/\{\{LITE_MODE_FLAG\}\}/g, "");
869
+ processed = processed.replace(/\{\{SPAWNABLE_MODELS_DEFAULT\}\}/g, SPAWNABLE_MODELS_DEFAULT.join(" / "));
755
870
  return processed;
756
871
  }
757
872
  function replaceHomePathsInTemplate(content, _installDir) {
@@ -801,7 +916,7 @@ const codexAdapter = {
801
916
  for (const file of CODEX_ROLE_FILE_TARGETS) {
802
917
  const target = join(ctx.promptsDir, "codex", file);
803
918
  if (!await fs.pathExists(target)) {
804
- ctx.result.errors.push(`codex ROLE_FILE target missing: ${target} (shared prompts not installed)`);
919
+ ctx.result.errors.push(`codex ROLE_FILE target missing: ${target} (role prompts not installed)`);
805
920
  ctx.result.success = false;
806
921
  }
807
922
  }
@@ -838,8 +953,8 @@ const CORE_CONFIGS = [
838
953
  cmd("apply", 22, "opsx", "\u5B9E\u65BD\u65B9\u6848", "Apply", "coding subagent \u5B9E\u65BD tasks\uFF1A\u8BFB tasks.md \u9010\u4EFB\u52A1\u5B9E\u65BD+\u9A8C\u8BC1+\u52FE\u9009\uFF0C\u56DE\u4F20\u4E3B\u4F1A\u8BDD\u7EDF\u4E00 commit", "Implements tasks via a coding subagent: reads tasks.md, implements + verifies + checks, returns to the main session for commit"),
839
954
  cmd("archive", 23, "opsx", "\u5F52\u6863\u65B9\u6848", "Archive", "\u59D4\u6258 opsx:archive\uFF0C\u5B8C\u6210\u540E\u5F52\u6863", "Delegates to opsx:archive \u2014 archives a completed change"),
840
955
  // ── Review gates (Codex-backed) ──────────────────────
841
- cmd("review-plan", 30, "review", "\u65B9\u6848\u5BA1\u67E5", "Review Plan", "\u53CC\u5BA1\u67E5 subagent \u5BA1 proposal/design/tasks\uFF0C\u5206\u6B67\u4E3B\u4F1A\u8BDD\u62CD\u677F", "Dual-reviewer subagents review plan artifacts; main session arbitrates disagreements"),
842
- cmd("review-code", 31, "review", "\u4EE3\u7801\u5BA1\u67E5", "Review Code", "\u53CC\u5BA1\u67E5 subagent \u5BA1\u4EE3\u7801\u53D8\u66F4\uFF0C\u5206\u7EA7\u8F93\u51FA Critical/Warning/Info", "Dual-reviewer subagents review code changes with severity grading"),
956
+ cmd("review-plan", 30, "review", "\u65B9\u6848\u5BA1\u67E5", "Review Plan", "\u6309 reviewExecutor \u51B3\u5B9A\u5BA1\u67E5\u4E3B\u4F53\uFF08\u4E3B agent \u76F4\u63A5\u5BA1 / spawn \u72EC\u7ACB\u5B50\u4EE3\u7406\uFF09\uFF0C\u5206\u7EA7\u8F93\u51FA Critical/Warning/Info", "Review plan artifacts with a switchable executor (main agent or a dedicated subagent), graded Critical/Warning/Info"),
957
+ cmd("review-code", 31, "review", "\u4EE3\u7801\u5BA1\u67E5", "Review Code", "\u6309 reviewExecutor \u51B3\u5B9A\u5BA1\u67E5\u4E3B\u4F53\uFF08\u4E3B agent \u76F4\u63A5\u5BA1 / spawn \u72EC\u7ACB\u5B50\u4EE3\u7406\uFF09\uFF0C\u5206\u7EA7\u8F93\u51FA Critical/Warning/Info", "Review code changes with a switchable executor (main agent or a dedicated subagent), graded Critical/Warning/Info"),
843
958
  // ── Release pipeline ───────────────────────────────────
844
959
  cmd("release", 40, "release", "GitFlow \u53D1\u7248", "GitFlow Release", "GitFlow \u56DB\u573A\u666F\u53D1\u7248\u6D41\u7A0B\uFF0CSemVer \u81EA\u52A8\u63A8\u5BFC\u7248\u672C\u53F7\uFF0C\u4E0A\u7EBF\u5408\u5E76\u4E8C\u9009\u4E00 + \u4E3B\u5206\u652F\u540D\u68C0\u6D4B", "GitFlow branching workflow with SemVer auto-detection, dual merge options and master/main detection"),
845
960
  cmd("changelog", 41, "release", "\u751F\u6210 Changelog", "Generate Changelog", "Keep a Changelog \u683C\u5F0F\u751F\u6210 CHANGELOG.md\uFF0C\u6309 commit \u524D\u7F00\u5206\u7EC4", "Generate Keep a Changelog format CHANGELOG.md from commit history"),
@@ -954,35 +1069,6 @@ async function installPromptFiles(ctx) {
954
1069
  ctx.result.success = false;
955
1070
  }
956
1071
  }
957
- async function migrateLegacyPrompts(options) {
958
- const home = options?.homeDir ?? homedir();
959
- const oldDir = join(home, ".claude", ".ly", "prompts");
960
- const newDir = options?.promptsDir || LY_PROMPTS_DIR;
961
- if (!await fs.pathExists(oldDir)) {
962
- return { migrated: false };
963
- }
964
- try {
965
- if (await hasCoexistingLegacyLyProducts(home)) {
966
- console.warn(`[lycx] \u68C0\u6D4B\u5230 ${join(home, ".claude")} \u4ECD\u88AB ly-workflow \u4F7F\u7528\uFF08\u5171\u5B58\u573A\u666F\uFF09\uFF0C\u8DF3\u8FC7\u89D2\u8272\u8BCD\u8FC1\u79FB\uFF0C\u4FDD\u7559\u4ED6\u65B9\u5728\u7528\u6587\u4EF6`);
967
- return { migrated: false, from: oldDir, to: newDir };
968
- }
969
- await fs.ensureDir(newDir);
970
- const entries = await fs.readdir(oldDir);
971
- for (const entry of entries) {
972
- if (entry === ".DS_Store")
973
- continue;
974
- const src = join(oldDir, entry);
975
- const dst = join(newDir, entry);
976
- if (await fs.pathExists(dst))
977
- continue;
978
- await fs.move(src, dst, { overwrite: false });
979
- }
980
- await fs.remove(oldDir);
981
- return { migrated: true, from: oldDir, to: newDir };
982
- } catch (error) {
983
- return { migrated: false, from: oldDir, to: newDir, error: String(error) };
984
- }
985
- }
986
1072
  async function installWorkflows(workflowIds, installDir, force = false, config = {}) {
987
1073
  const ctx = {
988
1074
  installDir,
@@ -1033,8 +1119,7 @@ async function uninstallWorkflows(installDir, options) {
1033
1119
  success: true,
1034
1120
  removedSkills: [],
1035
1121
  removedLegacyPrompts: [],
1036
- removedSharedPrompts: false,
1037
- configTomlKept: false,
1122
+ removedPrompts: false,
1038
1123
  errors: []
1039
1124
  };
1040
1125
  const codexSkillsDir = options?.codexSkillsDir || AGENTS_SKILLS_DIR;
@@ -1095,21 +1180,23 @@ async function uninstallWorkflows(installDir, options) {
1095
1180
  } catch (error) {
1096
1181
  result.errors.push(`Failed to clean legacy codex prompts: ${error}`);
1097
1182
  }
1098
- const sharedPromptsDir = options?.lyPromptsDir || LY_PROMPTS_DIR;
1099
- const sharedCodexPromptsDir = join(sharedPromptsDir, "codex");
1100
- if (await fs.pathExists(sharedCodexPromptsDir)) {
1183
+ const codexPromptsDir = options?.lyPromptsDir || LY_PROMPTS_DIR;
1184
+ const codexPromptsSubDir = join(codexPromptsDir, "codex");
1185
+ if (await fs.pathExists(codexPromptsSubDir)) {
1101
1186
  try {
1102
- await fs.remove(sharedCodexPromptsDir);
1103
- result.removedSharedPrompts = true;
1187
+ await fs.remove(codexPromptsSubDir);
1188
+ result.removedPrompts = true;
1104
1189
  } catch (error) {
1105
- result.errors.push(`Failed to remove shared codex prompts directory: ${error}`);
1190
+ result.errors.push(`Failed to remove codex prompts directory: ${error}`);
1191
+ result.success = false;
1106
1192
  }
1107
1193
  }
1108
1194
  const lyDir = options?.lyDir || getLyDir();
1109
- const configToml = join(lyDir, "config.toml");
1110
- if (await fs.pathExists(configToml)) {
1111
- result.configTomlKept = true;
1112
- console.warn("[lycx] \u4FDD\u7559\u5171\u4EAB\u914D\u7F6E ~/.ly/config.toml\uFF1A~/.ly \u4E3A ly-workflow \u5171\u4EAB\u547D\u540D\u7A7A\u95F4\uFF08\u542B worktrees/ \u7B49\u771F\u5B9E\u6570\u636E\uFF09\uFF0C\u5378\u8F7D\u4EC5\u6E05\u7406\u672C\u5305\u5F52\u5C5E\u4EA7\u7269\u3002\u5982\u9700\u6E05\u9664\u8BE5\u914D\u7F6E\uFF0C\u8BF7\u4EBA\u5DE5\u786E\u8BA4\u540E\u81EA\u884C\u5904\u7406\u3002");
1195
+ try {
1196
+ await fs.remove(lyDir);
1197
+ } catch (error) {
1198
+ result.errors.push(`Failed to remove ~/.codex/lyx/: ${error}`);
1199
+ result.success = false;
1113
1200
  }
1114
1201
  try {
1115
1202
  const { cleanupLegacyArtifacts, reportCleanupResult } = await import('../chunks/legacy-cleanup.mjs');
@@ -1120,23 +1207,50 @@ async function uninstallWorkflows(installDir, options) {
1120
1207
  return result;
1121
1208
  }
1122
1209
 
1123
- const OPENAI_OFFICIAL_BASE_URL = "https://api.openai.com/v1";
1124
- const MODEL_CHOICE_CUSTOM = "\0lyx:custom";
1125
1210
  const MODEL_CHOICE_UNSET = "\0lyx:unset";
1211
+ const MODEL_CHOICE_CUSTOM = "\0lyx:custom";
1212
+ function buildModelFieldChoices(input) {
1213
+ const { current } = input;
1214
+ const currentClean = current?.trim();
1215
+ const hasCurrent = currentClean !== void 0 && currentClean !== "";
1216
+ const choices = [
1217
+ { name: ansis.gray(i18n.t("init:model.unsetChoice")), value: MODEL_CHOICE_UNSET },
1218
+ { name: ansis.cyan(i18n.t("init:model.customChoice")), value: MODEL_CHOICE_CUSTOM }
1219
+ ];
1220
+ let defaultChoice = MODEL_CHOICE_UNSET;
1221
+ if (hasCurrent) {
1222
+ choices.push({ name: currentClean, value: currentClean });
1223
+ defaultChoice = currentClean;
1224
+ }
1225
+ return { choices, defaultChoice };
1226
+ }
1227
+
1228
+ const OPENAI_OFFICIAL_BASE_URL = "https://api.openai.com/v1";
1126
1229
  const MODEL_FIELDS = [
1127
- { key: "reviewModel", labelKey: "init:model.reviewModelA", promptKey: "init:model.reviewModelAPrompt", sanitize: sanitizeReviewModel },
1128
- { key: "reviewModelB", labelKey: "init:model.reviewModelB", promptKey: "init:model.reviewModelBPrompt", sanitize: sanitizeModelField },
1129
- { key: "codingModel", labelKey: "init:model.codingModel", promptKey: "init:model.codingModelPrompt", sanitize: sanitizeModelField }
1230
+ { key: "reviewModel", labelKey: "init:model.reviewModelA", sanitize: sanitizeReviewModel },
1231
+ { key: "codingModel", labelKey: "init:model.codingModel", sanitize: sanitizeModelField }
1232
+ ];
1233
+ const EXECUTOR_FIELDS = [
1234
+ { key: "reviewExecutor", labelKey: "init:executor.reviewExecutor" },
1235
+ { key: "codingExecutor", labelKey: "init:executor.codingExecutor" }
1130
1236
  ];
1237
+ async function pickExecutorField(input) {
1238
+ const { field, current } = input;
1239
+ const { pick } = await inquirer.prompt([{
1240
+ type: "list",
1241
+ name: "pick",
1242
+ message: i18n.t(field.labelKey),
1243
+ choices: [
1244
+ { name: i18n.t("init:executor.main"), value: "main" },
1245
+ { name: i18n.t("init:executor.subagent"), value: "subagent" }
1246
+ ],
1247
+ default: current ?? "main"
1248
+ }]);
1249
+ return pick === "subagent" ? "subagent" : "main";
1250
+ }
1131
1251
  async function pickModelField(input) {
1132
- const { field, models, current } = input;
1133
- const inList = current !== void 0 && models.includes(current);
1134
- const choices = [
1135
- ...models.map((id) => ({ name: id, value: id })),
1136
- { name: ansis.cyan(`\u270F\uFE0F ${i18n.t("init:model.customChoice")}`), value: MODEL_CHOICE_CUSTOM },
1137
- { name: ansis.gray(i18n.t("init:model.unsetChoice")), value: MODEL_CHOICE_UNSET }
1138
- ];
1139
- const defaultChoice = inList ? current : current !== void 0 ? MODEL_CHOICE_CUSTOM : MODEL_CHOICE_UNSET;
1252
+ const { field, current } = input;
1253
+ const { choices, defaultChoice } = buildModelFieldChoices({ current });
1140
1254
  const { pick } = await inquirer.prompt([{
1141
1255
  type: "list",
1142
1256
  name: "pick",
@@ -1147,25 +1261,30 @@ async function pickModelField(input) {
1147
1261
  }]);
1148
1262
  if (pick === MODEL_CHOICE_UNSET)
1149
1263
  return void 0;
1150
- if (pick !== MODEL_CHOICE_CUSTOM)
1151
- return pick;
1152
- const { custom } = await inquirer.prompt([{
1153
- type: "input",
1154
- name: "custom",
1155
- message: i18n.t("init:model.customPrompt"),
1156
- default: current || ""
1157
- }]);
1158
- return custom?.trim() || void 0;
1264
+ if (pick === MODEL_CHOICE_CUSTOM) {
1265
+ const { custom } = await inquirer.prompt([{
1266
+ type: "input",
1267
+ name: "custom",
1268
+ message: i18n.t("init:model.customPrompt")
1269
+ }]);
1270
+ const model = custom?.trim();
1271
+ return model ? model : void 0;
1272
+ }
1273
+ return typeof pick === "string" ? pick.trim() : void 0;
1159
1274
  }
1160
- async function inputModelField(input) {
1161
- const { field, current } = input;
1162
- const { model } = await inquirer.prompt([{
1163
- type: "input",
1164
- name: "model",
1165
- message: i18n.t(field.promptKey),
1166
- default: current || ""
1167
- }]);
1168
- return model?.trim() || void 0;
1275
+ async function printCodexStatus() {
1276
+ console.log();
1277
+ console.log(ansis.cyan.bold(` \u{1F4CA} ${i18n.t("init:codexStatus.title")}`));
1278
+ console.log();
1279
+ const currentModel = await readCodexCurrentModel();
1280
+ console.log(` \xB7 ${ansis.cyan(i18n.t("init:codexStatus.mainModelLabel"))} ${currentModel ? i18n.t("init:codexStatus.mainModel", { model: currentModel }) : ansis.gray(i18n.t("init:codexStatus.notDetected"))}`);
1281
+ const providers = await listModelProviders();
1282
+ console.log(` \xB7 ${ansis.cyan(i18n.t("init:codexStatus.providersLabel"))} ${providers.length > 0 ? providers.map((p) => p.name).join(", ") : ansis.gray(i18n.t("init:codexStatus.noProvider"))}`);
1283
+ const models = await readModelsJson();
1284
+ console.log(` \xB7 ${ansis.cyan(i18n.t("init:codexStatus.registeredLabel"))} ${models === void 0 ? ansis.gray(i18n.t("init:codexStatus.notDetected")) : i18n.t("init:codexStatus.registeredModels", { count: models.length })}`);
1285
+ console.log();
1286
+ console.log(ansis.yellow(` \u26A0 ${i18n.t("init:codexStatus.reasoningHint")}`));
1287
+ console.log(ansis.gray(` ${i18n.t("init:codexStatus.spawnableHint")}`));
1169
1288
  }
1170
1289
  async function collectCodexHostConfig(options) {
1171
1290
  console.log();
@@ -1189,20 +1308,8 @@ async function collectCodexHostConfig(options) {
1189
1308
  { name: `${ansis.cyan("\u25CF")} ${i18n.t("init:mode.providerCustom")}`, value: { type: "custom" } }
1190
1309
  ]
1191
1310
  }]);
1192
- let baseUrl = OPENAI_OFFICIAL_BASE_URL;
1193
- let apiKey = "";
1194
1311
  if (provider.type === "existing") {
1195
- baseUrl = provider.provider.baseUrl?.trim() || OPENAI_OFFICIAL_BASE_URL;
1196
- const envKey = provider.provider.envKey;
1197
- if (envKey && !process.env[envKey]) {
1198
- const { key } = await inquirer.prompt([{
1199
- type: "password",
1200
- name: "key",
1201
- message: i18n.t("init:mode.envKeyMissingPrompt", { envKey }),
1202
- mask: "*"
1203
- }]);
1204
- apiKey = key?.trim() || "";
1205
- }
1312
+ console.log(ansis.gray(` \u2713 ${i18n.t("init:mode.providerExistingSelected", { name: provider.provider.name })}`));
1206
1313
  } else if (provider.type === "custom") {
1207
1314
  console.log();
1208
1315
  const { name } = await inquirer.prompt([{
@@ -1218,14 +1325,7 @@ async function collectCodexHostConfig(options) {
1218
1325
  message: i18n.t("init:mode.customUrlPrompt"),
1219
1326
  default: OPENAI_OFFICIAL_BASE_URL
1220
1327
  }]);
1221
- const { key } = await inquirer.prompt([{
1222
- type: "password",
1223
- name: "key",
1224
- message: `${i18n.t("init:mode.customKeyPrompt")} ${ansis.gray(`(${i18n.t("init:mode.optional")})`)}`,
1225
- mask: "*"
1226
- }]);
1227
- baseUrl = (url || OPENAI_OFFICIAL_BASE_URL).trim();
1228
- apiKey = key?.trim() || "";
1328
+ const baseUrl = (url || OPENAI_OFFICIAL_BASE_URL).trim();
1229
1329
  const writeResult = await upsertModelProvider({ name: providerName, baseUrl });
1230
1330
  if (writeResult.status === "written") {
1231
1331
  console.log(ansis.green(` \u2713 ${i18n.t("init:mode.providerWritten", { name: providerName })}`));
@@ -1235,34 +1335,57 @@ async function collectCodexHostConfig(options) {
1235
1335
  console.log(ansis.yellow(` \u26A0 ${i18n.t("init:mode.providerWriteFailed", { error: writeResult.error })}`));
1236
1336
  }
1237
1337
  }
1338
+ await printCodexStatus();
1238
1339
  console.log();
1239
- console.log(ansis.cyan.bold(` \u{1F9E0} ${i18n.t("init:model.trioTitle")}`));
1340
+ console.log(ansis.cyan.bold(` ${i18n.t("init:executor.title")}`));
1341
+ console.log();
1342
+ console.log(ansis.gray(` ${i18n.t("init:executor.hint")}`));
1240
1343
  console.log();
1241
- if (baseUrl) {
1242
- console.log(ansis.gray(` ${i18n.t("init:mode.modelFetching", { baseUrl })}`));
1243
- }
1244
- const fetchResult = await fetchCodexModels({ baseUrl, apiKey });
1245
- if (!fetchResult.ok) {
1246
- console.log(ansis.yellow(` \u26A0 ${i18n.t("init:mode.modelFetchFailed", { reason: fetchResult.error })}`));
1247
- }
1248
1344
  const collected = {};
1345
+ for (const field of EXECUTOR_FIELDS) {
1346
+ collected[field.key] = await pickExecutorField({
1347
+ field,
1348
+ current: options.defaults[field.key]
1349
+ });
1350
+ }
1351
+ console.log();
1352
+ console.log(ansis.cyan.bold(` \u{1F9E0} ${i18n.t("init:model.trioTitle")}`));
1353
+ console.log();
1354
+ console.log(ansis.gray(` ${i18n.t("init:model.trioCandidatesHint")}`));
1355
+ console.log();
1249
1356
  for (const field of MODEL_FIELDS) {
1357
+ const executorKey = field.key === "reviewModel" ? "reviewExecutor" : "codingExecutor";
1358
+ if (collected[executorKey] !== "subagent") {
1359
+ collected[field.key] = options.defaults[field.key]?.trim() || void 0;
1360
+ continue;
1361
+ }
1250
1362
  const current = options.defaults[field.key]?.trim() || void 0;
1251
- const raw = fetchResult.ok ? await pickModelField({ field, models: fetchResult.models, current }) : await inputModelField({ field, current });
1363
+ const raw = await pickModelField({ field, current });
1252
1364
  collected[field.key] = field.sanitize(raw);
1253
1365
  }
1254
1366
  return collected;
1255
1367
  }
1256
1368
  function printSummary(input) {
1257
1369
  const { models, commandCount } = input;
1370
+ const executorLabel = (kind) => kind === "subagent" ? ansis.green(i18n.t("init:summary.executorSubagent")) : ansis.gray(i18n.t("init:summary.executorMain"));
1371
+ const modelLabel = (value, effective) => {
1372
+ if (!value)
1373
+ return ansis.gray(i18n.t("init:host.reviewModelUnset"));
1374
+ if (!effective)
1375
+ return ansis.yellow(i18n.t("init:summary.modelIneffective", { model: value }));
1376
+ return ansis.green(value);
1377
+ };
1378
+ const reviewEffective = models.reviewExecutor === "subagent";
1379
+ const codingEffective = models.codingExecutor === "subagent";
1258
1380
  console.log();
1259
1381
  console.log(ansis.yellow("\u2501".repeat(50)));
1260
1382
  console.log(ansis.bold(` ${i18n.t("init:summary.title")}`));
1261
1383
  console.log();
1262
1384
  console.log(` ${ansis.cyan(i18n.t("init:summary.host"))} ${ansis.green("codex")}`);
1263
- console.log(` ${ansis.cyan(i18n.t("init:summary.reviewModelA"))} ${models.reviewModel ? ansis.green(models.reviewModel) : ansis.gray(i18n.t("init:host.reviewModelUnset"))}`);
1264
- console.log(` ${ansis.cyan(i18n.t("init:summary.reviewModelB"))} ${models.reviewModelB ? ansis.green(models.reviewModelB) : ansis.gray(i18n.t("init:host.reviewModelUnset"))}`);
1265
- console.log(` ${ansis.cyan(i18n.t("init:summary.codingModel"))} ${models.codingModel ? ansis.green(models.codingModel) : ansis.gray(i18n.t("init:host.reviewModelUnset"))}`);
1385
+ console.log(` ${ansis.cyan(i18n.t("init:summary.reviewExecutor"))} ${executorLabel(models.reviewExecutor)}`);
1386
+ console.log(` ${ansis.cyan(i18n.t("init:summary.codingExecutor"))} ${executorLabel(models.codingExecutor)}`);
1387
+ console.log(` ${ansis.cyan(i18n.t("init:summary.reviewModelA"))} ${modelLabel(models.reviewModel, reviewEffective)}`);
1388
+ console.log(` ${ansis.cyan(i18n.t("init:summary.codingModel"))} ${modelLabel(models.codingModel, codingEffective)}`);
1266
1389
  console.log(` ${ansis.cyan(i18n.t("init:summary.commandCount"))} ${ansis.yellow(commandCount.toString())}`);
1267
1390
  console.log(ansis.yellow("\u2501".repeat(50)));
1268
1391
  console.log();
@@ -1301,14 +1424,16 @@ async function init(options = {}) {
1301
1424
  await initI18n(language);
1302
1425
  }
1303
1426
  const selectedWorkflows = getCoreCommandIds();
1304
- const defaultModels = {
1427
+ const existingExtras = sanitizeCodexHostExtras(existingConfig?.codexHost);
1428
+ const defaultCollected = {
1429
+ reviewExecutor: sanitizeExecutor(existingConfig?.codexHost?.reviewExecutor),
1430
+ codingExecutor: sanitizeExecutor(existingConfig?.codexHost?.codingExecutor),
1305
1431
  reviewModel: sanitizeReviewModel(existingConfig?.codexHost?.reviewModel),
1306
- reviewModelB: sanitizeModelField(existingConfig?.codexHost?.reviewModelB),
1307
- codingModel: sanitizeModelField(existingConfig?.codexHost?.codingModel)
1432
+ codingModel: existingExtras.codingModel
1308
1433
  };
1309
- let collectedModels = { ...defaultModels };
1434
+ let collectedModels = { ...defaultCollected };
1310
1435
  if (!options.skipPrompt) {
1311
- collectedModels = await collectCodexHostConfig({ defaults: defaultModels });
1436
+ collectedModels = await collectCodexHostConfig({ defaults: defaultCollected });
1312
1437
  printSummary({ models: collectedModels, commandCount: selectedWorkflows.length });
1313
1438
  const { confirm } = await inquirer.prompt([{
1314
1439
  type: "confirm",
@@ -1330,24 +1455,15 @@ async function init(options = {}) {
1330
1455
  language,
1331
1456
  installedWorkflows: selectedWorkflows,
1332
1457
  codexHost: {
1458
+ ...existingExtras,
1459
+ reviewExecutor: collectedModels.reviewExecutor,
1460
+ codingExecutor: collectedModels.codingExecutor,
1333
1461
  reviewModel: collectedModels.reviewModel,
1334
- reviewModelB: collectedModels.reviewModelB,
1335
1462
  codingModel: collectedModels.codingModel
1336
1463
  },
1337
1464
  installedHosts: ["codex"]
1338
1465
  });
1339
1466
  await writeLyConfig(config);
1340
- try {
1341
- const migration = await migrateLegacyPrompts();
1342
- if (migration.migrated) {
1343
- console.log();
1344
- console.log(` ${ansis.green("\u2713")} ${i18n.t("init:promptsMigrated")}`);
1345
- } else if (migration.error) {
1346
- console.log();
1347
- console.log(` ${ansis.yellow("\u26A0")} ${i18n.t("init:promptsMigrationFailed", { error: migration.error })}`);
1348
- }
1349
- } catch {
1350
- }
1351
1467
  const result = await installWorkflows(selectedWorkflows, "", options.force, {
1352
1468
  reviewModel: collectedModels.reviewModel
1353
1469
  });
@@ -1374,7 +1490,7 @@ async function init(options = {}) {
1374
1490
  console.log();
1375
1491
  console.log(` ${ansis.cyan(i18n.t("init:installedPrompts"))}`);
1376
1492
  for (const name of result.installedPrompts) {
1377
- console.log(` ${ansis.green("\u2713")} ${name} ${ansis.gray("\u2192 ~/.ly/prompts/")}`);
1493
+ console.log(` ${ansis.green("\u2713")} ${name} ${ansis.gray("\u2192 ~/.codex/lyx/prompts/")}`);
1378
1494
  }
1379
1495
  }
1380
1496
  try {
@@ -1384,7 +1500,7 @@ async function init(options = {}) {
1384
1500
  }
1385
1501
  console.log();
1386
1502
  console.log(ansis.green(` \u2713 ${i18n.t("init:installSuccess")}`));
1387
- console.log(ansis.gray(` Config: ~/.ly/config.toml`));
1503
+ console.log(ansis.gray(` Config: ~/.codex/lyx/config.toml`));
1388
1504
  console.log();
1389
1505
  } catch (error) {
1390
1506
  spinner.fail(ansis.red(i18n.t("init:installFailed")));
@@ -1935,18 +2051,47 @@ function readLyConfigSync() {
1935
2051
  async function configReviewModel() {
1936
2052
  const config = await readLyConfig();
1937
2053
  const currentReviewModel = sanitizeReviewModel(config?.codexHost?.reviewModel);
2054
+ const currentExecutor = sanitizeExecutor(config?.codexHost?.reviewExecutor);
1938
2055
  console.log();
1939
2056
  console.log(ansis.cyan.bold(` ${i18n.t("init:model.title")}`));
1940
2057
  console.log();
1941
- console.log(ansis.gray(` ${i18n.t("init:host.reviewModelHint")}`));
1942
- const { model } = await inquirer.prompt([{
1943
- type: "input",
1944
- name: "model",
1945
- message: i18n.t("init:host.reviewModelPrompt"),
1946
- default: currentReviewModel || ""
2058
+ const { executor } = await inquirer.prompt([{
2059
+ type: "list",
2060
+ name: "executor",
2061
+ message: i18n.t("init:executor.reviewExecutor"),
2062
+ choices: [
2063
+ { name: i18n.t("init:executor.main"), value: "main" },
2064
+ { name: i18n.t("init:executor.subagent"), value: "subagent" }
2065
+ ],
2066
+ default: currentExecutor ?? "main"
1947
2067
  }]);
1948
- const next = sanitizeReviewModel(model);
1949
- if (next === currentReviewModel) {
2068
+ const nextExecutor = executor === "subagent" ? "subagent" : "main";
2069
+ let next = currentReviewModel;
2070
+ if (nextExecutor === "subagent") {
2071
+ console.log(ansis.gray(` ${i18n.t("init:host.reviewModelHint")}`));
2072
+ const { choices, defaultChoice } = buildModelFieldChoices({
2073
+ current: currentReviewModel
2074
+ });
2075
+ const { model } = await inquirer.prompt([{
2076
+ type: "list",
2077
+ name: "model",
2078
+ message: i18n.t("init:host.reviewModelPrompt"),
2079
+ choices,
2080
+ default: defaultChoice,
2081
+ pageSize: 15
2082
+ }]);
2083
+ if (model === MODEL_CHOICE_CUSTOM) {
2084
+ const { custom } = await inquirer.prompt([{
2085
+ type: "input",
2086
+ name: "custom",
2087
+ message: i18n.t("init:model.customPrompt")
2088
+ }]);
2089
+ next = custom?.trim() || void 0;
2090
+ } else {
2091
+ next = model === MODEL_CHOICE_UNSET ? void 0 : sanitizeReviewModel(model);
2092
+ }
2093
+ }
2094
+ if (next === currentReviewModel && nextExecutor === (currentExecutor ?? "main")) {
1950
2095
  console.log(ansis.gray(` ${i18n.t("common:configNotModified")}`));
1951
2096
  return;
1952
2097
  }
@@ -1955,16 +2100,13 @@ async function configReviewModel() {
1955
2100
  console.log(` ${ansis.yellow("\u26A0")} ${PACKAGE_NAME} config not initialized`);
1956
2101
  return;
1957
2102
  }
1958
- const existingB = sanitizeModelField(fresh.codexHost?.reviewModelB);
1959
- const existingCoding = sanitizeModelField(fresh.codexHost?.codingModel);
1960
- if (next || existingB || existingCoding) {
2103
+ const existingExtras = sanitizeCodexHostExtras(fresh.codexHost);
2104
+ {
1961
2105
  fresh.codexHost = {
1962
- ...next ? { reviewModel: next } : {},
1963
- ...existingB ? { reviewModelB: existingB } : {},
1964
- ...existingCoding ? { codingModel: existingCoding } : {}
2106
+ ...existingExtras,
2107
+ reviewExecutor: nextExecutor,
2108
+ ...next ? { reviewModel: next } : {}
1965
2109
  };
1966
- } else {
1967
- fresh.codexHost = void 0;
1968
2110
  }
1969
2111
  await writeLyConfig(fresh);
1970
2112
  console.log();
@@ -2038,13 +2180,9 @@ async function uninstall() {
2038
2180
  console.log(` ${ansis.gray("\u2022")} ${file}`);
2039
2181
  }
2040
2182
  }
2041
- if (result.removedSharedPrompts) {
2042
- console.log();
2043
- console.log(ansis.cyan(` ${i18n.t("menu:uninstall.removedSharedPrompts")}`));
2044
- }
2045
- if (result.configTomlKept) {
2183
+ if (result.removedPrompts) {
2046
2184
  console.log();
2047
- console.log(ansis.cyan(` ${i18n.t("menu:uninstall.configTomlKept")}`));
2185
+ console.log(ansis.cyan(` ${i18n.t("menu:uninstall.removedPrompts")}`));
2048
2186
  }
2049
2187
  if (isGlobalInstall) {
2050
2188
  console.log();
@@ -2065,4 +2203,4 @@ async function uninstall() {
2065
2203
  console.log();
2066
2204
  }
2067
2205
 
2068
- export { ADAPTERS as A, BIN_NAME as B, LY_PROMPTS_DIR as L, PACKAGE_NAME as P, i18n as a, initI18n as b, changeLanguage as c, createDefaultConfig as d, getLyDir as e, getLyPromptsDir as f, getConfigPath as g, getWorkflowById as h, init as i, getWorkflowConfigs as j, installWorkflows as k, uninstallWorkflows as l, migrateLegacyPrompts as m, checkForUpdates as n, compareVersions as o, getCurrentVersion as p, getLatestVersion as q, readLyConfig as r, showMainMenu as s, AGENTS_SKILLS_DIR as t, update as u, version as v, writeLyConfig as w };
2206
+ export { ADAPTERS as A, sanitizeModelField as B, sanitizeExecutor as C, BIN_NAME as D, LY_PROMPTS_DIR as L, PACKAGE_NAME as P, i18n as a, initI18n as b, changeLanguage as c, createDefaultConfig as d, getLyDir as e, getLyPromptsDir as f, getConfigPath as g, getWorkflowById as h, init as i, getWorkflowConfigs as j, installWorkflows as k, uninstallWorkflows as l, checkForUpdates as m, compareVersions as n, getCurrentVersion as o, getLatestVersion as p, AGENTS_SKILLS_DIR as q, readLyConfig as r, showMainMenu as s, readCodexCurrentModel as t, update as u, version as v, writeLyConfig as w, sanitizeSpawnableModels as x, sanitizeReasoningEffort as y, sanitizeReviewModel as z };