@jonit-dev/night-watch-cli 1.7.87 → 1.7.89

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/dist/cli.js CHANGED
@@ -31,7 +31,7 @@ function resolveProviderBucketKey(provider, providerEnv) {
31
31
  return `claude-proxy:${baseUrl}`;
32
32
  }
33
33
  }
34
- var DEFAULT_DEFAULT_BRANCH, DEFAULT_PRD_DIR, DEFAULT_MAX_RUNTIME, DEFAULT_REVIEWER_MAX_RUNTIME, DEFAULT_CRON_SCHEDULE, DEFAULT_REVIEWER_SCHEDULE, DEFAULT_CRON_SCHEDULE_OFFSET, DEFAULT_MAX_RETRIES, DEFAULT_REVIEWER_MAX_RETRIES, DEFAULT_REVIEWER_RETRY_DELAY, DEFAULT_BRANCH_PREFIX, DEFAULT_BRANCH_PATTERNS, DEFAULT_MIN_REVIEW_SCORE, DEFAULT_MAX_LOG_SIZE, DEFAULT_PROVIDER, DEFAULT_EXECUTOR_ENABLED, DEFAULT_REVIEWER_ENABLED, DEFAULT_PROVIDER_ENV, DEFAULT_FALLBACK_ON_RATE_LIMIT, DEFAULT_CLAUDE_MODEL, DEFAULT_PRIMARY_FALLBACK_MODEL, DEFAULT_SECONDARY_FALLBACK_MODEL, VALID_CLAUDE_MODELS, CLAUDE_MODEL_IDS, DEFAULT_NOTIFICATIONS, DEFAULT_PRD_PRIORITY, DEFAULT_SLICER_SCHEDULE, DEFAULT_SLICER_MAX_RUNTIME, DEFAULT_ROADMAP_SCANNER, DEFAULT_TEMPLATES_DIR, DEFAULT_BOARD_PROVIDER, DEFAULT_LOCAL_BOARD_INFO, DEFAULT_AUTO_MERGE, DEFAULT_AUTO_MERGE_METHOD, VALID_MERGE_METHODS, DEFAULT_QA_ENABLED, DEFAULT_QA_SCHEDULE, DEFAULT_QA_MAX_RUNTIME, DEFAULT_QA_ARTIFACTS, DEFAULT_QA_SKIP_LABEL, DEFAULT_QA_AUTO_INSTALL_PLAYWRIGHT, DEFAULT_QA, QA_LOG_NAME, DEFAULT_AUDIT_ENABLED, DEFAULT_AUDIT_SCHEDULE, DEFAULT_AUDIT_MAX_RUNTIME, DEFAULT_AUDIT, AUDIT_LOG_NAME, PLANNER_LOG_NAME, VALID_PROVIDERS, VALID_JOB_TYPES, DEFAULT_JOB_PROVIDERS, PROVIDER_COMMANDS, CONFIG_FILE_NAME, LOCK_FILE_PREFIX, LOG_DIR, CLAIM_FILE_EXTENSION, EXECUTOR_LOG_NAME, REVIEWER_LOG_NAME, EXECUTOR_LOG_FILE, REVIEWER_LOG_FILE, LOG_FILE_NAMES, GLOBAL_CONFIG_DIR, REGISTRY_FILE_NAME, HISTORY_FILE_NAME, PRD_STATES_FILE_NAME, STATE_DB_FILE_NAME, MAX_HISTORY_RECORDS_PER_PRD, DEFAULT_QUEUE_ENABLED, DEFAULT_QUEUE_MODE, DEFAULT_QUEUE_MAX_CONCURRENCY, DEFAULT_QUEUE_MAX_WAIT_TIME, DEFAULT_QUEUE_PRIORITY, DEFAULT_QUEUE, DEFAULT_SCHEDULING_PRIORITY, QUEUE_LOCK_FILE_NAME;
34
+ var DEFAULT_DEFAULT_BRANCH, DEFAULT_PRD_DIR, DEFAULT_MAX_RUNTIME, DEFAULT_REVIEWER_MAX_RUNTIME, DEFAULT_CRON_SCHEDULE, DEFAULT_REVIEWER_SCHEDULE, DEFAULT_CRON_SCHEDULE_OFFSET, DEFAULT_MAX_RETRIES, DEFAULT_REVIEWER_MAX_RETRIES, DEFAULT_REVIEWER_RETRY_DELAY, DEFAULT_BRANCH_PREFIX, DEFAULT_BRANCH_PATTERNS, DEFAULT_MIN_REVIEW_SCORE, DEFAULT_MAX_LOG_SIZE, DEFAULT_PROVIDER, DEFAULT_EXECUTOR_ENABLED, DEFAULT_REVIEWER_ENABLED, DEFAULT_PROVIDER_ENV, DEFAULT_FALLBACK_ON_RATE_LIMIT, DEFAULT_CLAUDE_MODEL, DEFAULT_PRIMARY_FALLBACK_MODEL, DEFAULT_SECONDARY_FALLBACK_MODEL, VALID_CLAUDE_MODELS, CLAUDE_MODEL_IDS, DEFAULT_NOTIFICATIONS, DEFAULT_PRD_PRIORITY, DEFAULT_SLICER_SCHEDULE, DEFAULT_SLICER_MAX_RUNTIME, DEFAULT_ROADMAP_SCANNER, DEFAULT_TEMPLATES_DIR, DEFAULT_BOARD_PROVIDER, DEFAULT_LOCAL_BOARD_INFO, DEFAULT_AUTO_MERGE, DEFAULT_AUTO_MERGE_METHOD, VALID_MERGE_METHODS, DEFAULT_QA_ENABLED, DEFAULT_QA_SCHEDULE, DEFAULT_QA_MAX_RUNTIME, DEFAULT_QA_ARTIFACTS, DEFAULT_QA_SKIP_LABEL, DEFAULT_QA_AUTO_INSTALL_PLAYWRIGHT, DEFAULT_QA, QA_LOG_NAME, DEFAULT_AUDIT_ENABLED, DEFAULT_AUDIT_SCHEDULE, DEFAULT_AUDIT_MAX_RUNTIME, DEFAULT_AUDIT, AUDIT_LOG_NAME, PLANNER_LOG_NAME, VALID_PROVIDERS, VALID_JOB_TYPES, DEFAULT_JOB_PROVIDERS, BUILT_IN_PRESETS, BUILT_IN_PRESET_IDS, PROVIDER_COMMANDS, CONFIG_FILE_NAME, LOCK_FILE_PREFIX, LOG_DIR, CLAIM_FILE_EXTENSION, EXECUTOR_LOG_NAME, REVIEWER_LOG_NAME, EXECUTOR_LOG_FILE, REVIEWER_LOG_FILE, LOG_FILE_NAMES, GLOBAL_CONFIG_DIR, REGISTRY_FILE_NAME, HISTORY_FILE_NAME, PRD_STATES_FILE_NAME, STATE_DB_FILE_NAME, MAX_HISTORY_RECORDS_PER_PRD, DEFAULT_QUEUE_ENABLED, DEFAULT_QUEUE_MODE, DEFAULT_QUEUE_MAX_CONCURRENCY, DEFAULT_QUEUE_MAX_WAIT_TIME, DEFAULT_QUEUE_PRIORITY, DEFAULT_QUEUE, DEFAULT_SCHEDULING_PRIORITY, QUEUE_LOCK_FILE_NAME;
35
35
  var init_constants = __esm({
36
36
  "../core/dist/constants.js"() {
37
37
  "use strict";
@@ -113,9 +113,69 @@ var init_constants = __esm({
113
113
  VALID_PROVIDERS = ["claude", "codex"];
114
114
  VALID_JOB_TYPES = ["executor", "reviewer", "qa", "audit", "slicer"];
115
115
  DEFAULT_JOB_PROVIDERS = {};
116
+ BUILT_IN_PRESETS = {
117
+ claude: {
118
+ name: "Claude",
119
+ command: "claude",
120
+ promptFlag: "-p",
121
+ autoApproveFlag: "--dangerously-skip-permissions"
122
+ },
123
+ "claude-sonnet-4-6": {
124
+ name: "Claude Sonnet 4.6",
125
+ command: "claude",
126
+ promptFlag: "-p",
127
+ autoApproveFlag: "--dangerously-skip-permissions",
128
+ modelFlag: "--model",
129
+ model: "claude-sonnet-4-6"
130
+ },
131
+ "claude-opus-4-6": {
132
+ name: "Claude Opus 4.6",
133
+ command: "claude",
134
+ promptFlag: "-p",
135
+ autoApproveFlag: "--dangerously-skip-permissions",
136
+ modelFlag: "--model",
137
+ model: "claude-opus-4-6"
138
+ },
139
+ codex: {
140
+ name: "Codex",
141
+ command: "codex",
142
+ subcommand: "exec",
143
+ autoApproveFlag: "--yolo",
144
+ workdirFlag: "-C"
145
+ },
146
+ "glm-47": {
147
+ name: "GLM-4.7",
148
+ command: "claude",
149
+ promptFlag: "-p",
150
+ autoApproveFlag: "--dangerously-skip-permissions",
151
+ modelFlag: "--model",
152
+ model: "glm-4.7",
153
+ envVars: {
154
+ ANTHROPIC_BASE_URL: "https://api.z.ai/api/anthropic",
155
+ API_TIMEOUT_MS: "3000000",
156
+ ANTHROPIC_DEFAULT_OPUS_MODEL: "glm-4.7",
157
+ ANTHROPIC_DEFAULT_SONNET_MODEL: "glm-4.7"
158
+ }
159
+ },
160
+ "glm-5": {
161
+ name: "GLM-5",
162
+ command: "claude",
163
+ promptFlag: "-p",
164
+ autoApproveFlag: "--dangerously-skip-permissions",
165
+ modelFlag: "--model",
166
+ model: "glm-5",
167
+ envVars: {
168
+ ANTHROPIC_BASE_URL: "https://api.z.ai/api/anthropic",
169
+ API_TIMEOUT_MS: "3000000",
170
+ ANTHROPIC_DEFAULT_OPUS_MODEL: "glm-5",
171
+ ANTHROPIC_DEFAULT_SONNET_MODEL: "glm-5"
172
+ }
173
+ }
174
+ };
175
+ BUILT_IN_PRESET_IDS = Object.keys(BUILT_IN_PRESETS);
116
176
  PROVIDER_COMMANDS = {
117
- claude: "claude",
118
- codex: "codex"
177
+ claude: BUILT_IN_PRESETS.claude.command,
178
+ codex: BUILT_IN_PRESETS.codex.command
119
179
  };
120
180
  CONFIG_FILE_NAME = "night-watch.config.json";
121
181
  LOCK_FILE_PREFIX = "/tmp/night-watch-";
@@ -164,8 +224,9 @@ var init_constants = __esm({
164
224
 
165
225
  // ../core/dist/config-normalize.js
166
226
  function validateProvider(value) {
167
- if (VALID_PROVIDERS.includes(value)) {
168
- return value;
227
+ const trimmed = value.trim();
228
+ if (trimmed.length > 0) {
229
+ return trimmed;
169
230
  }
170
231
  return null;
171
232
  }
@@ -221,6 +282,45 @@ function normalizeConfig(rawConfig) {
221
282
  normalized.providerEnv = env;
222
283
  }
223
284
  }
285
+ const rawProviderPresets = readObject(rawConfig.providerPresets);
286
+ if (rawProviderPresets) {
287
+ const presets = {};
288
+ for (const [presetId, presetVal] of Object.entries(rawProviderPresets)) {
289
+ const rawPreset = readObject(presetVal);
290
+ if (rawPreset) {
291
+ const name = readString(rawPreset.name);
292
+ const command = readString(rawPreset.command);
293
+ if (name && command) {
294
+ const preset = {
295
+ name,
296
+ command,
297
+ subcommand: readString(rawPreset.subcommand),
298
+ promptFlag: readString(rawPreset.promptFlag),
299
+ autoApproveFlag: readString(rawPreset.autoApproveFlag),
300
+ workdirFlag: readString(rawPreset.workdirFlag),
301
+ modelFlag: readString(rawPreset.modelFlag),
302
+ model: readString(rawPreset.model)
303
+ };
304
+ const rawEnvVars = readObject(rawPreset.envVars);
305
+ if (rawEnvVars) {
306
+ const envVars = {};
307
+ for (const [envKey, envVal] of Object.entries(rawEnvVars)) {
308
+ if (typeof envVal === "string") {
309
+ envVars[envKey] = envVal;
310
+ }
311
+ }
312
+ if (Object.keys(envVars).length > 0) {
313
+ preset.envVars = envVars;
314
+ }
315
+ }
316
+ presets[presetId] = preset;
317
+ }
318
+ }
319
+ }
320
+ if (Object.keys(presets).length > 0) {
321
+ normalized.providerPresets = presets;
322
+ }
323
+ }
224
324
  const rawNotifications = readObject(rawConfig.notifications);
225
325
  if (rawNotifications) {
226
326
  const rawWebhooks = Array.isArray(rawNotifications.webhooks) ? rawNotifications.webhooks : [];
@@ -285,6 +385,8 @@ function normalizeConfig(rawConfig) {
285
385
  if (secondaryFallbackModelRaw && VALID_CLAUDE_MODELS.includes(secondaryFallbackModelRaw)) {
286
386
  normalized.secondaryFallbackModel = secondaryFallbackModelRaw;
287
387
  }
388
+ normalized.primaryFallbackPreset = readString(rawConfig.primaryFallbackPreset);
389
+ normalized.secondaryFallbackPreset = readString(rawConfig.secondaryFallbackPreset);
288
390
  normalized.autoMerge = readBoolean(rawConfig.autoMerge);
289
391
  const mergeMethod = readString(rawConfig.autoMergeMethod);
290
392
  if (mergeMethod && VALID_MERGE_METHODS.includes(mergeMethod)) {
@@ -319,8 +421,8 @@ function normalizeConfig(rawConfig) {
319
421
  const jobProviders = {};
320
422
  for (const jobType of VALID_JOB_TYPES) {
321
423
  const providerValue = readString(rawJobProviders[jobType]);
322
- if (providerValue && VALID_PROVIDERS.includes(providerValue)) {
323
- jobProviders[jobType] = providerValue;
424
+ if (providerValue && providerValue.trim().length > 0) {
425
+ jobProviders[jobType] = providerValue.trim();
324
426
  }
325
427
  }
326
428
  if (Object.keys(jobProviders).length > 0) {
@@ -779,6 +881,15 @@ function resolveJobProvider(config, jobType) {
779
881
  return config.jobProviders[jobType];
780
882
  return config.provider;
781
883
  }
884
+ function resolvePreset(config, presetId) {
885
+ if (config.providerPresets?.[presetId]) {
886
+ return config.providerPresets[presetId];
887
+ }
888
+ if (BUILT_IN_PRESETS[presetId]) {
889
+ return BUILT_IN_PRESETS[presetId];
890
+ }
891
+ throw new Error(`Unknown provider preset: "${presetId}"`);
892
+ }
782
893
  function getScriptPath(scriptName) {
783
894
  const configFilePath = fileURLToPath(import.meta.url);
784
895
  const baseDir = path.dirname(configFilePath);
@@ -3976,7 +4087,7 @@ var init_config_writer = __esm({
3976
4087
  "../core/dist/utils/config-writer.js"() {
3977
4088
  "use strict";
3978
4089
  init_constants();
3979
- PARTIAL_MERGE_KEYS = /* @__PURE__ */ new Set(["notifications", "qa", "audit", "roadmapScanner", "queue"]);
4090
+ PARTIAL_MERGE_KEYS = /* @__PURE__ */ new Set(["notifications", "qa", "audit", "roadmapScanner", "queue", "providerPresets", "jobProviders"]);
3980
4091
  }
3981
4092
  });
3982
4093
 
@@ -6741,6 +6852,8 @@ var dist_exports = {};
6741
6852
  __export(dist_exports, {
6742
6853
  AUDIT_LOG_NAME: () => AUDIT_LOG_NAME,
6743
6854
  BOARD_COLUMNS: () => BOARD_COLUMNS,
6855
+ BUILT_IN_PRESETS: () => BUILT_IN_PRESETS,
6856
+ BUILT_IN_PRESET_IDS: () => BUILT_IN_PRESET_IDS,
6744
6857
  CATEGORY_LABELS: () => CATEGORY_LABELS,
6745
6858
  CATEGORY_LABEL_INFO: () => CATEGORY_LABEL_INFO,
6746
6859
  CLAIM_FILE_EXTENSION: () => CLAIM_FILE_EXTENSION,
@@ -6985,6 +7098,7 @@ __export(dist_exports, {
6985
7098
  renderSlicerPrompt: () => renderSlicerPrompt,
6986
7099
  resetRepositories: () => resetRepositories,
6987
7100
  resolveJobProvider: () => resolveJobProvider,
7101
+ resolvePreset: () => resolvePreset,
6988
7102
  resolveProviderBucketKey: () => resolveProviderBucketKey,
6989
7103
  resolveWorktreeBaseRef: () => resolveWorktreeBaseRef,
6990
7104
  reviewerLockPath: () => reviewerLockPath,
@@ -7385,9 +7499,9 @@ function initCommand(program2) {
7385
7499
  step(3, totalSteps, "Detecting AI providers...");
7386
7500
  let selectedProvider;
7387
7501
  if (options.provider) {
7388
- if (!VALID_PROVIDERS.includes(options.provider)) {
7502
+ if (!BUILT_IN_PRESET_IDS.includes(options.provider)) {
7389
7503
  error(`Invalid provider "${options.provider}".`);
7390
- console.log(`Valid providers: ${VALID_PROVIDERS.join(", ")}`);
7504
+ console.log(`Valid providers: ${BUILT_IN_PRESET_IDS.join(", ")}`);
7391
7505
  process.exit(1);
7392
7506
  }
7393
7507
  selectedProvider = options.provider;
@@ -7615,23 +7729,34 @@ init_dist();
7615
7729
 
7616
7730
  // src/commands/shared/env-builder.ts
7617
7731
  init_dist();
7618
- function deriveProviderLabel(config, jobType) {
7732
+ function deriveProviderLabel(config, preset) {
7733
+ if (preset.name) return preset.name;
7619
7734
  if (config.providerLabel) return config.providerLabel;
7620
- const provider = resolveJobProvider(config, jobType);
7621
- if (provider === "codex") return "Codex";
7735
+ if (preset.command === "codex") return "Codex";
7622
7736
  if (config.providerEnv?.ANTHROPIC_BASE_URL) return "Claude (proxy)";
7623
7737
  return "Claude";
7624
7738
  }
7625
7739
  function buildBaseEnvVars(config, jobType, isDryRun) {
7626
7740
  const env = {};
7627
- env.NW_PROVIDER_CMD = PROVIDER_COMMANDS[resolveJobProvider(config, jobType)];
7628
- env.NW_PROVIDER_LABEL = deriveProviderLabel(config, jobType);
7741
+ const presetId = resolveJobProvider(config, jobType);
7742
+ const preset = resolvePreset(config, presetId);
7743
+ env.NW_PROVIDER_CMD = preset.command;
7744
+ env.NW_PROVIDER_SUBCOMMAND = preset.subcommand ?? "";
7745
+ env.NW_PROVIDER_PROMPT_FLAG = preset.promptFlag ?? "";
7746
+ env.NW_PROVIDER_APPROVE_FLAG = preset.autoApproveFlag ?? "";
7747
+ env.NW_PROVIDER_WORKDIR_FLAG = preset.workdirFlag ?? "";
7748
+ env.NW_PROVIDER_MODEL_FLAG = preset.modelFlag ?? "";
7749
+ env.NW_PROVIDER_MODEL = preset.model ?? "";
7750
+ env.NW_PROVIDER_LABEL = deriveProviderLabel(config, preset);
7629
7751
  if (config.defaultBranch) {
7630
7752
  env.NW_DEFAULT_BRANCH = config.defaultBranch;
7631
7753
  }
7632
7754
  if (config.providerEnv) {
7633
7755
  Object.assign(env, config.providerEnv);
7634
7756
  }
7757
+ if (preset.envVars) {
7758
+ Object.assign(env, preset.envVars);
7759
+ }
7635
7760
  const queueConfig = config.queue ?? DEFAULT_QUEUE;
7636
7761
  env.NW_QUEUE_ENABLED = queueConfig.enabled ? "1" : "0";
7637
7762
  env.NW_QUEUE_MAX_CONCURRENCY = String(queueConfig.maxConcurrency);
@@ -7852,6 +7977,34 @@ function buildEnvVars(config, options) {
7852
7977
  env.NW_CLAUDE_PRIMARY_MODEL_ID = CLAUDE_MODEL_IDS[primaryFallbackModel];
7853
7978
  env.NW_CLAUDE_SECONDARY_MODEL_ID = CLAUDE_MODEL_IDS[secondaryFallbackModel];
7854
7979
  env.NW_CLAUDE_MODEL_ID = env.NW_CLAUDE_PRIMARY_MODEL_ID;
7980
+ if (config.primaryFallbackPreset) {
7981
+ try {
7982
+ const fallbackPreset = resolvePreset(config, config.primaryFallbackPreset);
7983
+ env.NW_FALLBACK_PRIMARY_PRESET_CMD = fallbackPreset.command;
7984
+ if (fallbackPreset.promptFlag) env.NW_FALLBACK_PRIMARY_PRESET_PROMPT_FLAG = fallbackPreset.promptFlag;
7985
+ if (fallbackPreset.autoApproveFlag) env.NW_FALLBACK_PRIMARY_PRESET_AUTO_APPROVE_FLAG = fallbackPreset.autoApproveFlag;
7986
+ if (fallbackPreset.modelFlag) env.NW_FALLBACK_PRIMARY_PRESET_MODEL_FLAG = fallbackPreset.modelFlag;
7987
+ if (fallbackPreset.model) env.NW_FALLBACK_PRIMARY_PRESET_MODEL = fallbackPreset.model;
7988
+ if (fallbackPreset.envVars && Object.keys(fallbackPreset.envVars).length > 0) {
7989
+ env.NW_FALLBACK_PRIMARY_PRESET_ENV = JSON.stringify(fallbackPreset.envVars);
7990
+ }
7991
+ } catch {
7992
+ }
7993
+ }
7994
+ if (config.secondaryFallbackPreset) {
7995
+ try {
7996
+ const fallbackPreset = resolvePreset(config, config.secondaryFallbackPreset);
7997
+ env.NW_FALLBACK_SECONDARY_PRESET_CMD = fallbackPreset.command;
7998
+ if (fallbackPreset.promptFlag) env.NW_FALLBACK_SECONDARY_PRESET_PROMPT_FLAG = fallbackPreset.promptFlag;
7999
+ if (fallbackPreset.autoApproveFlag) env.NW_FALLBACK_SECONDARY_PRESET_AUTO_APPROVE_FLAG = fallbackPreset.autoApproveFlag;
8000
+ if (fallbackPreset.modelFlag) env.NW_FALLBACK_SECONDARY_PRESET_MODEL_FLAG = fallbackPreset.modelFlag;
8001
+ if (fallbackPreset.model) env.NW_FALLBACK_SECONDARY_PRESET_MODEL = fallbackPreset.model;
8002
+ if (fallbackPreset.envVars && Object.keys(fallbackPreset.envVars).length > 0) {
8003
+ env.NW_FALLBACK_SECONDARY_PRESET_ENV = JSON.stringify(fallbackPreset.envVars);
8004
+ }
8005
+ } catch {
8006
+ }
8007
+ }
7855
8008
  const fallbackTelegramWebhooks = getRateLimitFallbackTelegramWebhooks(config);
7856
8009
  if (fallbackTelegramWebhooks.length > 0) {
7857
8010
  env.NW_TELEGRAM_RATE_LIMIT_WEBHOOKS = JSON.stringify(fallbackTelegramWebhooks);
@@ -9899,14 +10052,8 @@ var NOTIFICATION_EVENTS = [
9899
10052
  "rate_limit_fallback",
9900
10053
  "qa_completed"
9901
10054
  ];
9902
- var GLM5_DEFAULTS = {
9903
- ANTHROPIC_BASE_URL: "https://api.z.ai/api/anthropic",
9904
- API_TIMEOUT_MS: "3000000",
9905
- ANTHROPIC_DEFAULT_OPUS_MODEL: "glm-5",
9906
- ANTHROPIC_DEFAULT_SONNET_MODEL: "glm-5"
9907
- };
9908
10055
  var CONFIG_FIELDS = [
9909
- { key: "provider", label: "Provider", type: "enum", options: [...VALID_PROVIDERS] },
10056
+ { key: "provider", label: "Provider", type: "enum", options: [...BUILT_IN_PRESET_IDS] },
9910
10057
  { key: "reviewerEnabled", label: "Reviewer Enabled", type: "boolean" },
9911
10058
  { key: "defaultBranch", label: "Default Branch", type: "string" },
9912
10059
  { key: "prdDir", label: "PRD Directory", type: "string" },
@@ -10437,10 +10584,16 @@ function createConfigTab() {
10437
10584
  ctx.screen.render();
10438
10585
  return;
10439
10586
  }
10440
- pendingChanges.providerEnv = {
10441
- ANTHROPIC_API_KEY: apiKey,
10442
- ANTHROPIC_AUTH_TOKEN: apiKey,
10443
- ...GLM5_DEFAULTS
10587
+ pendingChanges.provider = "glm-5";
10588
+ pendingChanges.providerPresets = {
10589
+ "glm-5": {
10590
+ ...BUILT_IN_PRESETS["glm-5"],
10591
+ envVars: {
10592
+ ...BUILT_IN_PRESETS["glm-5"].envVars,
10593
+ ANTHROPIC_API_KEY: apiKey,
10594
+ ANTHROPIC_AUTH_TOKEN: apiKey
10595
+ }
10596
+ }
10444
10597
  };
10445
10598
  ctx.showMessage("GLM-5 configured. Press s to save.", "success");
10446
10599
  if (currentConfig) refreshList(currentConfig);
@@ -12437,14 +12590,13 @@ function validateCronField(fieldName, value) {
12437
12590
  }
12438
12591
  return null;
12439
12592
  }
12440
- function validateConfigChanges(changes) {
12593
+ function validateConfigChanges(changes, currentConfig) {
12441
12594
  if (typeof changes !== "object" || changes === null) {
12442
12595
  return "Invalid request body";
12443
12596
  }
12444
12597
  if (changes.provider !== void 0) {
12445
- const validProviders = ["claude", "codex"];
12446
- if (!validProviders.includes(changes.provider)) {
12447
- return `Invalid provider. Must be one of: ${validProviders.join(", ")}`;
12598
+ if (typeof changes.provider !== "string" || changes.provider.trim().length === 0) {
12599
+ return "provider must be a non-empty string (preset ID)";
12448
12600
  }
12449
12601
  }
12450
12602
  if (changes.providerLabel !== void 0 && typeof changes.providerLabel !== "string") {
@@ -12571,8 +12723,41 @@ function validateConfigChanges(changes) {
12571
12723
  if (!VALID_JOB_TYPES.includes(jobType)) {
12572
12724
  return `Invalid job type in jobProviders: ${jobType}. Must be one of: ${VALID_JOB_TYPES.join(", ")}`;
12573
12725
  }
12574
- if (provider !== null && provider !== void 0 && !VALID_PROVIDERS.includes(provider)) {
12575
- return `Invalid provider in jobProviders.${jobType}: ${provider}. Must be one of: ${VALID_PROVIDERS.join(", ")}`;
12726
+ if (provider !== null && provider !== void 0 && (typeof provider !== "string" || provider.trim().length === 0)) {
12727
+ return `Invalid provider in jobProviders.${jobType}: ${provider}. Must be a non-empty string (preset ID)`;
12728
+ }
12729
+ }
12730
+ }
12731
+ if (changes.providerPresets !== void 0) {
12732
+ if (typeof changes.providerPresets !== "object" || changes.providerPresets === null) {
12733
+ return "providerPresets must be an object";
12734
+ }
12735
+ for (const [presetId, presetVal] of Object.entries(changes.providerPresets)) {
12736
+ if (typeof presetId !== "string" || presetId.trim().length === 0) {
12737
+ return "providerPresets keys must be non-empty strings";
12738
+ }
12739
+ if (presetVal === null || presetVal === void 0) {
12740
+ continue;
12741
+ }
12742
+ if (typeof presetVal !== "object") {
12743
+ return `providerPresets.${presetId} must be an object`;
12744
+ }
12745
+ const preset = presetVal;
12746
+ if (typeof preset.name !== "string" || preset.name.trim().length === 0) {
12747
+ return `providerPresets.${presetId}.name must be a non-empty string`;
12748
+ }
12749
+ if (typeof preset.command !== "string" || preset.command.trim().length === 0) {
12750
+ return `providerPresets.${presetId}.command must be a non-empty string`;
12751
+ }
12752
+ if (preset.envVars !== void 0) {
12753
+ if (typeof preset.envVars !== "object" || preset.envVars === null) {
12754
+ return `providerPresets.${presetId}.envVars must be an object`;
12755
+ }
12756
+ for (const [envKey, envVal] of Object.entries(preset.envVars)) {
12757
+ if (typeof envVal !== "string") {
12758
+ return `providerPresets.${presetId}.envVars.${envKey} must be a string`;
12759
+ }
12760
+ }
12576
12761
  }
12577
12762
  }
12578
12763
  }
@@ -12591,6 +12776,16 @@ function validateConfigChanges(changes) {
12591
12776
  if (changes.fallbackOnRateLimit !== void 0 && typeof changes.fallbackOnRateLimit !== "boolean") {
12592
12777
  return "fallbackOnRateLimit must be a boolean";
12593
12778
  }
12779
+ if (changes.primaryFallbackPreset !== void 0 && changes.primaryFallbackPreset !== null) {
12780
+ if (typeof changes.primaryFallbackPreset !== "string" || changes.primaryFallbackPreset.trim().length === 0) {
12781
+ return "primaryFallbackPreset must be a non-empty string (preset ID)";
12782
+ }
12783
+ }
12784
+ if (changes.secondaryFallbackPreset !== void 0 && changes.secondaryFallbackPreset !== null) {
12785
+ if (typeof changes.secondaryFallbackPreset !== "string" || changes.secondaryFallbackPreset.trim().length === 0) {
12786
+ return "secondaryFallbackPreset must be a non-empty string (preset ID)";
12787
+ }
12788
+ }
12594
12789
  if (changes.claudeModel !== void 0 && !VALID_CLAUDE_MODELS.includes(changes.claudeModel)) {
12595
12790
  return `Invalid claudeModel. Must be one of: ${VALID_CLAUDE_MODELS.join(", ")}`;
12596
12791
  }
@@ -12695,6 +12890,47 @@ function validateConfigChanges(changes) {
12695
12890
  return "boardProvider.enabled must be a boolean";
12696
12891
  }
12697
12892
  }
12893
+ if (changes.providerPresets !== void 0 && currentConfig) {
12894
+ const currentPresetIds = new Set(Object.keys(currentConfig.providerPresets || {}));
12895
+ const newPresetIds = /* @__PURE__ */ new Set();
12896
+ for (const [presetId, presetVal] of Object.entries(changes.providerPresets)) {
12897
+ if (presetVal !== null && presetVal !== void 0) {
12898
+ newPresetIds.add(presetId);
12899
+ }
12900
+ }
12901
+ for (const presetId of currentPresetIds) {
12902
+ if (!(presetId in changes.providerPresets)) {
12903
+ newPresetIds.add(presetId);
12904
+ }
12905
+ }
12906
+ const deletedPresetIds = [...currentPresetIds].filter((id) => !newPresetIds.has(id));
12907
+ if (deletedPresetIds.length > 0) {
12908
+ const presetReferences = [];
12909
+ for (const deletedId of deletedPresetIds) {
12910
+ const references = [];
12911
+ const effectiveProvider = changes.provider ?? currentConfig.provider;
12912
+ if (effectiveProvider === deletedId) {
12913
+ references.push("provider (global)");
12914
+ }
12915
+ const effectiveJobProviders = {
12916
+ ...currentConfig.jobProviders,
12917
+ ...changes.jobProviders || {}
12918
+ };
12919
+ for (const [jobType, provider] of Object.entries(effectiveJobProviders)) {
12920
+ if (provider === deletedId) {
12921
+ references.push(`jobProviders.${jobType}`);
12922
+ }
12923
+ }
12924
+ if (references.length > 0) {
12925
+ presetReferences.push({ presetId: deletedId, references });
12926
+ }
12927
+ }
12928
+ if (presetReferences.length > 0) {
12929
+ const details = presetReferences.map(({ presetId, references }) => `"${presetId}" is used by: ${references.join(", ")}`).join("; ");
12930
+ return `Cannot delete preset(s) that are still in use. ${details}`;
12931
+ }
12932
+ }
12933
+ }
12698
12934
  return null;
12699
12935
  }
12700
12936
  function createConfigRoutes(deps) {
@@ -12702,6 +12938,7 @@ function createConfigRoutes(deps) {
12702
12938
  const router = Router3();
12703
12939
  router.get("/", (_req, res) => {
12704
12940
  try {
12941
+ reloadConfig();
12705
12942
  res.json(getConfig());
12706
12943
  } catch (error2) {
12707
12944
  res.status(500).json({ error: error2 instanceof Error ? error2.message : String(error2) });
@@ -12710,7 +12947,7 @@ function createConfigRoutes(deps) {
12710
12947
  router.put("/", (req, res) => {
12711
12948
  try {
12712
12949
  const changes = req.body;
12713
- const validationError = validateConfigChanges(changes);
12950
+ const validationError = validateConfigChanges(changes, getConfig());
12714
12951
  if (validationError) {
12715
12952
  res.status(400).json({ error: validationError });
12716
12953
  return;
@@ -12741,7 +12978,7 @@ function createProjectConfigRoutes() {
12741
12978
  const projectDir = req.projectDir;
12742
12979
  try {
12743
12980
  const changes = req.body;
12744
- const validationError = validateConfigChanges(changes);
12981
+ const validationError = validateConfigChanges(changes, req.projectConfig);
12745
12982
  if (validationError) {
12746
12983
  res.status(400).json({ error: validationError });
12747
12984
  return;
@@ -12777,17 +13014,21 @@ function runDoctorChecks(projectDir, config) {
12777
13014
  checks.push({ name: "git", status: "fail", detail: "Not a git repository" });
12778
13015
  }
12779
13016
  try {
12780
- execSync6(`which ${config.provider}`, { stdio: "pipe" });
13017
+ const preset = resolvePreset(config, config.provider);
13018
+ const command = preset?.command ?? config.provider;
13019
+ execSync6(`which ${command}`, { stdio: "pipe" });
12781
13020
  checks.push({
12782
13021
  name: "provider",
12783
13022
  status: "pass",
12784
- detail: `Provider CLI found: ${config.provider}`
13023
+ detail: `Provider CLI found: ${command} (preset: ${config.provider})`
12785
13024
  });
12786
13025
  } catch {
13026
+ const preset = resolvePreset(config, config.provider);
13027
+ const command = preset?.command ?? config.provider;
12787
13028
  checks.push({
12788
13029
  name: "provider",
12789
13030
  status: "fail",
12790
- detail: `Provider CLI not found: ${config.provider}`
13031
+ detail: `Provider CLI not found: ${command} (preset: ${config.provider})`
12791
13032
  });
12792
13033
  }
12793
13034
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"tab-config.d.ts","sourceRoot":"","sources":["../../../src/commands/dashboard/tab-config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EACL,iBAAiB,EAMlB,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EAAE,IAAI,EAAe,MAAM,YAAY,CAAC;AAE/C,KAAK,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,GAAG,UAAU,GAAG,UAAU,GAAG,UAAU,CAAC;AAEjG,UAAU,YAAY;IACpB,GAAG,EAAE,MAAM,iBAAiB,CAAC;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,SAAS,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;CAC7C;AA2DD,eAAO,MAAM,aAAa,EAAE,YAAY,EA+CvC,CAAC;AA0BF;;GAEG;AACH,wBAAgB,eAAe,IAAI,IAAI,CAmzBtC"}
1
+ {"version":3,"file":"tab-config.d.ts","sourceRoot":"","sources":["../../../src/commands/dashboard/tab-config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAGL,iBAAiB,EAKlB,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EAAE,IAAI,EAAe,MAAM,YAAY,CAAC;AAE/C,KAAK,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,GAAG,UAAU,GAAG,UAAU,GAAG,UAAU,CAAC;AAEjG,UAAU,YAAY;IACpB,GAAG,EAAE,MAAM,iBAAiB,CAAC;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,SAAS,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;CAC7C;AAiDD,eAAO,MAAM,aAAa,EAAE,YAAY,EA+CvC,CAAC;AA0BF;;GAEG;AACH,wBAAgB,eAAe,IAAI,IAAI,CAyzBtC"}
@@ -3,7 +3,7 @@
3
3
  * Allows viewing and editing all configuration fields
4
4
  */
5
5
  import blessed from 'blessed';
6
- import { VALID_PROVIDERS, saveConfig, } from '@night-watch/core';
6
+ import { BUILT_IN_PRESETS, BUILT_IN_PRESET_IDS, saveConfig, } from '@night-watch/core';
7
7
  import { performUninstall } from '../uninstall.js';
8
8
  import { performInstall } from '../install.js';
9
9
  const SENSITIVE_PATTERNS = /TOKEN|KEY|SECRET|PASSWORD/i;
@@ -43,17 +43,8 @@ const NOTIFICATION_EVENTS = [
43
43
  'rate_limit_fallback',
44
44
  'qa_completed',
45
45
  ];
46
- /**
47
- * GLM-5 default provider environment configuration
48
- */
49
- const GLM5_DEFAULTS = {
50
- ANTHROPIC_BASE_URL: 'https://api.z.ai/api/anthropic',
51
- API_TIMEOUT_MS: '3000000',
52
- ANTHROPIC_DEFAULT_OPUS_MODEL: 'glm-5',
53
- ANTHROPIC_DEFAULT_SONNET_MODEL: 'glm-5',
54
- };
55
46
  export const CONFIG_FIELDS = [
56
- { key: 'provider', label: 'Provider', type: 'enum', options: [...VALID_PROVIDERS] },
47
+ { key: 'provider', label: 'Provider', type: 'enum', options: [...BUILT_IN_PRESET_IDS] },
57
48
  { key: 'reviewerEnabled', label: 'Reviewer Enabled', type: 'boolean' },
58
49
  { key: 'defaultBranch', label: 'Default Branch', type: 'string' },
59
50
  { key: 'prdDir', label: 'PRD Directory', type: 'string' },
@@ -612,10 +603,16 @@ export function createConfigTab() {
612
603
  ctx.screen.render();
613
604
  return;
614
605
  }
615
- pendingChanges.providerEnv = {
616
- ANTHROPIC_API_KEY: apiKey,
617
- ANTHROPIC_AUTH_TOKEN: apiKey,
618
- ...GLM5_DEFAULTS,
606
+ pendingChanges.provider = 'glm-5';
607
+ pendingChanges.providerPresets = {
608
+ 'glm-5': {
609
+ ...BUILT_IN_PRESETS['glm-5'],
610
+ envVars: {
611
+ ...BUILT_IN_PRESETS['glm-5'].envVars,
612
+ ANTHROPIC_API_KEY: apiKey,
613
+ ANTHROPIC_AUTH_TOKEN: apiKey,
614
+ },
615
+ },
619
616
  };
620
617
  ctx.showMessage('GLM-5 configured. Press s to save.', 'success');
621
618
  if (currentConfig)