dexbot 1.6.4 → 1.6.6

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.
Files changed (240) hide show
  1. package/CHANGELOG.md +75 -3
  2. package/README.md +109 -40
  3. package/analysis/README.md +19 -17
  4. package/analysis/account_resolver.ts +1 -11
  5. package/analysis/analyze_dynamic_weight.ts +9 -3
  6. package/analysis/bot_usage/kibana_bot_queries.ts +1 -1
  7. package/analysis/chain_pool.ts +1 -1
  8. package/analysis/fills_source.ts +0 -6
  9. package/analysis/grid_correction_check.ts +20 -26
  10. package/analysis/resolve_source.ts +1 -1
  11. package/analysis/trade_profitability.ts +2 -5
  12. package/analysis/tradingview/analyze_tradingview.ts +5 -0
  13. package/analysis/tradingview/tradingview_uplot_chart_generator.ts +2 -2
  14. package/analysis/trend_detection/DYNAMIC_WEIGHT_RESEARCH.md +35 -2
  15. package/analysis/trend_detection/README.md +1 -3
  16. package/analysis/trend_detection/dynamic_weight_chart_generator.ts +82 -10
  17. package/analysis/trend_detection/package.json +1 -1
  18. package/claw/modules/dexbot_profiles.ts +4 -3
  19. package/claw/package.json +1 -1
  20. package/claw/runtimes/openclaw-plugin/openclaw.plugin.json +1 -1
  21. package/claw/runtimes/openclaw-plugin/package.json +1 -1
  22. package/claw/tests/test_claw_mcp_transport.ts +2 -2
  23. package/claw/tests/test_dexbot_profiles.ts +18 -1
  24. package/dist/analysis/account_resolver.d.ts +1 -4
  25. package/dist/analysis/account_resolver.d.ts.map +1 -1
  26. package/dist/analysis/account_resolver.js +1 -1
  27. package/dist/analysis/account_resolver.js.map +1 -1
  28. package/dist/analysis/analyze_dynamic_weight.js +9 -3
  29. package/dist/analysis/analyze_dynamic_weight.js.map +1 -1
  30. package/dist/analysis/bot_usage/kibana_bot_queries.d.ts +1 -2
  31. package/dist/analysis/bot_usage/kibana_bot_queries.d.ts.map +1 -1
  32. package/dist/analysis/bot_usage/kibana_bot_queries.js +1 -1
  33. package/dist/analysis/bot_usage/kibana_bot_queries.js.map +1 -1
  34. package/dist/analysis/chain_pool.d.ts +1 -3
  35. package/dist/analysis/chain_pool.d.ts.map +1 -1
  36. package/dist/analysis/chain_pool.js +1 -1
  37. package/dist/analysis/chain_pool.js.map +1 -1
  38. package/dist/analysis/fills_source.d.ts +1 -51
  39. package/dist/analysis/fills_source.d.ts.map +1 -1
  40. package/dist/analysis/fills_source.js +1 -1
  41. package/dist/analysis/fills_source.js.map +1 -1
  42. package/dist/analysis/grid_correction_check.d.ts +2 -3
  43. package/dist/analysis/grid_correction_check.d.ts.map +1 -1
  44. package/dist/analysis/grid_correction_check.js +18 -26
  45. package/dist/analysis/grid_correction_check.js.map +1 -1
  46. package/dist/analysis/resolve_source.d.ts +1 -1
  47. package/dist/analysis/resolve_source.d.ts.map +1 -1
  48. package/dist/analysis/trade_profitability.d.ts.map +1 -1
  49. package/dist/analysis/trade_profitability.js +2 -7
  50. package/dist/analysis/trade_profitability.js.map +1 -1
  51. package/dist/analysis/tradingview/analyze_tradingview.d.ts.map +1 -1
  52. package/dist/analysis/tradingview/analyze_tradingview.js +5 -0
  53. package/dist/analysis/tradingview/analyze_tradingview.js.map +1 -1
  54. package/dist/analysis/tradingview/tradingview_uplot_chart_generator.js +2 -2
  55. package/dist/analysis/trend_detection/dynamic_weight_chart_generator.d.ts.map +1 -1
  56. package/dist/analysis/trend_detection/dynamic_weight_chart_generator.js +82 -10
  57. package/dist/analysis/trend_detection/dynamic_weight_chart_generator.js.map +1 -1
  58. package/dist/dexbot.d.ts.map +1 -1
  59. package/dist/dexbot.js +85 -140
  60. package/dist/dexbot.js.map +1 -1
  61. package/dist/market_adapter/core/asymmetric_bounds.d.ts +2 -2
  62. package/dist/market_adapter/core/asymmetric_bounds.d.ts.map +1 -1
  63. package/dist/market_adapter/core/asymmetric_bounds.js +15 -12
  64. package/dist/market_adapter/core/asymmetric_bounds.js.map +1 -1
  65. package/dist/market_adapter/core/kibana_candles.d.ts +1 -15
  66. package/dist/market_adapter/core/kibana_candles.d.ts.map +1 -1
  67. package/dist/market_adapter/core/kibana_candles.js +1 -1
  68. package/dist/market_adapter/core/kibana_candles.js.map +1 -1
  69. package/dist/market_adapter/inputs/fetch_lp_data.d.ts +1 -35
  70. package/dist/market_adapter/inputs/fetch_lp_data.d.ts.map +1 -1
  71. package/dist/market_adapter/inputs/fetch_lp_data.js +10 -54
  72. package/dist/market_adapter/inputs/fetch_lp_data.js.map +1 -1
  73. package/dist/market_adapter/inputs/kibana_feed_source.d.ts +1 -8
  74. package/dist/market_adapter/inputs/kibana_feed_source.d.ts.map +1 -1
  75. package/dist/market_adapter/inputs/kibana_feed_source.js +1 -1
  76. package/dist/market_adapter/inputs/kibana_feed_source.js.map +1 -1
  77. package/dist/market_adapter/inputs/window_cache.d.ts +2 -63
  78. package/dist/market_adapter/inputs/window_cache.d.ts.map +1 -1
  79. package/dist/market_adapter/inputs/window_cache.js +22 -102
  80. package/dist/market_adapter/inputs/window_cache.js.map +1 -1
  81. package/dist/market_adapter/interval_utils.d.ts +1 -1
  82. package/dist/market_adapter/interval_utils.js +1 -1
  83. package/dist/modules/account_bots.d.ts +70 -6
  84. package/dist/modules/account_bots.d.ts.map +1 -1
  85. package/dist/modules/account_bots.js +353 -149
  86. package/dist/modules/account_bots.js.map +1 -1
  87. package/dist/modules/bitshares-native/crypto/ecc.browser.d.ts.map +1 -1
  88. package/dist/modules/bitshares-native/crypto/ecc.browser.js +0 -31
  89. package/dist/modules/bitshares-native/crypto/ecc.browser.js.map +1 -1
  90. package/dist/modules/bot_defaults.d.ts +98 -0
  91. package/dist/modules/bot_defaults.d.ts.map +1 -0
  92. package/dist/modules/bot_defaults.js +169 -0
  93. package/dist/modules/bot_defaults.js.map +1 -0
  94. package/dist/modules/bot_settings.d.ts.map +1 -1
  95. package/dist/modules/bot_settings.js +4 -1
  96. package/dist/modules/bot_settings.js.map +1 -1
  97. package/dist/modules/config.d.ts +1 -0
  98. package/dist/modules/config.d.ts.map +1 -1
  99. package/dist/modules/config.js +1 -0
  100. package/dist/modules/config.js.map +1 -1
  101. package/dist/modules/constants.d.ts +11 -2
  102. package/dist/modules/constants.d.ts.map +1 -1
  103. package/dist/modules/constants.js +40 -27
  104. package/dist/modules/constants.js.map +1 -1
  105. package/dist/modules/credit_runtime.d.ts +0 -2
  106. package/dist/modules/credit_runtime.d.ts.map +1 -1
  107. package/dist/modules/credit_runtime.js +1 -10
  108. package/dist/modules/credit_runtime.js.map +1 -1
  109. package/dist/modules/dexbot_class.d.ts +13 -10
  110. package/dist/modules/dexbot_class.d.ts.map +1 -1
  111. package/dist/modules/dexbot_class.js +0 -9
  112. package/dist/modules/dexbot_class.js.map +1 -1
  113. package/dist/modules/dexbot_cow_runtime.d.ts +13 -0
  114. package/dist/modules/dexbot_cow_runtime.d.ts.map +1 -1
  115. package/dist/modules/dexbot_cow_runtime.js +13 -1
  116. package/dist/modules/dexbot_cow_runtime.js.map +1 -1
  117. package/dist/modules/dexbot_maintenance_runtime.d.ts +1 -1
  118. package/dist/modules/dexbot_maintenance_runtime.d.ts.map +1 -1
  119. package/dist/modules/dexbot_maintenance_runtime.js +9 -4
  120. package/dist/modules/dexbot_maintenance_runtime.js.map +1 -1
  121. package/dist/modules/launcher/adapter_requirement.js +1 -1
  122. package/dist/modules/launcher/launch_modes.d.ts +17 -1
  123. package/dist/modules/launcher/launch_modes.d.ts.map +1 -1
  124. package/dist/modules/launcher/launch_modes.js +18 -1
  125. package/dist/modules/launcher/launch_modes.js.map +1 -1
  126. package/dist/modules/launcher/monolithic_runtime.d.ts.map +1 -1
  127. package/dist/modules/launcher/monolithic_runtime.js +8 -1
  128. package/dist/modules/launcher/monolithic_runtime.js.map +1 -1
  129. package/dist/modules/market_adapter_whitelist.d.ts +40 -1
  130. package/dist/modules/market_adapter_whitelist.d.ts.map +1 -1
  131. package/dist/modules/market_adapter_whitelist.js +161 -11
  132. package/dist/modules/market_adapter_whitelist.js.map +1 -1
  133. package/dist/modules/order/accounting.d.ts +4 -18
  134. package/dist/modules/order/accounting.d.ts.map +1 -1
  135. package/dist/modules/order/accounting.js +4 -34
  136. package/dist/modules/order/accounting.js.map +1 -1
  137. package/dist/modules/order/export.d.ts +9 -12
  138. package/dist/modules/order/export.d.ts.map +1 -1
  139. package/dist/modules/order/export.js +9 -12
  140. package/dist/modules/order/export.js.map +1 -1
  141. package/dist/modules/order/grid.d.ts +44 -40
  142. package/dist/modules/order/grid.d.ts.map +1 -1
  143. package/dist/modules/order/grid.js +66 -48
  144. package/dist/modules/order/grid.js.map +1 -1
  145. package/dist/modules/order/grid_reconcile.d.ts.map +1 -1
  146. package/dist/modules/order/grid_reconcile.js +111 -20
  147. package/dist/modules/order/grid_reconcile.js.map +1 -1
  148. package/dist/modules/order/grid_reconcile_internal.d.ts +5 -3
  149. package/dist/modules/order/grid_reconcile_internal.d.ts.map +1 -1
  150. package/dist/modules/order/grid_reconcile_internal.js +18 -3
  151. package/dist/modules/order/grid_reconcile_internal.js.map +1 -1
  152. package/dist/modules/order/manager.d.ts +0 -8
  153. package/dist/modules/order/manager.d.ts.map +1 -1
  154. package/dist/modules/order/manager.js +3 -12
  155. package/dist/modules/order/manager.js.map +1 -1
  156. package/dist/modules/order/utils/order.d.ts +22 -7
  157. package/dist/modules/order/utils/order.d.ts.map +1 -1
  158. package/dist/modules/order/utils/order.js +103 -21
  159. package/dist/modules/order/utils/order.js.map +1 -1
  160. package/dist/modules/order/utils/system.d.ts +5 -1
  161. package/dist/modules/order/utils/system.d.ts.map +1 -1
  162. package/dist/modules/order/utils/system.js +5 -2
  163. package/dist/modules/order/utils/system.js.map +1 -1
  164. package/dist/modules/order/utils/withPoolRef.d.ts +12 -0
  165. package/dist/modules/order/utils/withPoolRef.d.ts.map +1 -1
  166. package/dist/modules/order/utils/withPoolRef.js +17 -0
  167. package/dist/modules/order/utils/withPoolRef.js.map +1 -1
  168. package/dist/modules/order/working_grid.d.ts +8 -13
  169. package/dist/modules/order/working_grid.d.ts.map +1 -1
  170. package/dist/modules/order/working_grid.js +8 -29
  171. package/dist/modules/order/working_grid.js.map +1 -1
  172. package/dist/modules/settings_merge.d.ts +10 -1
  173. package/dist/modules/settings_merge.d.ts.map +1 -1
  174. package/dist/modules/settings_merge.js +27 -17
  175. package/dist/modules/settings_merge.js.map +1 -1
  176. package/dist/modules/validate_profiles.d.ts.map +1 -1
  177. package/dist/modules/validate_profiles.js +4 -3
  178. package/dist/modules/validate_profiles.js.map +1 -1
  179. package/dist/scripts/chart_command.d.ts +48 -0
  180. package/dist/scripts/chart_command.d.ts.map +1 -0
  181. package/dist/scripts/chart_command.js +504 -0
  182. package/dist/scripts/chart_command.js.map +1 -0
  183. package/dist/scripts/dw.d.ts +3 -0
  184. package/dist/scripts/dw.d.ts.map +1 -0
  185. package/dist/scripts/dw.js +30 -0
  186. package/dist/scripts/dw.js.map +1 -0
  187. package/dist/scripts/sync-version.js +0 -22
  188. package/dist/scripts/sync-version.js.map +1 -1
  189. package/dist/scripts/tv.d.ts +1 -31
  190. package/dist/scripts/tv.d.ts.map +1 -1
  191. package/dist/scripts/tv.js +10 -463
  192. package/dist/scripts/tv.js.map +1 -1
  193. package/dist/scripts/update.js +99 -62
  194. package/dist/scripts/update.js.map +1 -1
  195. package/dist/scripts/update_dist_freshness.d.ts +58 -0
  196. package/dist/scripts/update_dist_freshness.d.ts.map +1 -0
  197. package/dist/scripts/update_dist_freshness.js +144 -0
  198. package/dist/scripts/update_dist_freshness.js.map +1 -0
  199. package/dist/unlock.js +1 -1
  200. package/dist/unlock.js.map +1 -1
  201. package/docs/BITSHARES_ONBOARDING.md +16 -10
  202. package/docs/COW_INVARIANTS.md +1 -0
  203. package/docs/DEXBOT_COMPARISON.md +11 -11
  204. package/docs/EVOLUTION.md +9 -7
  205. package/docs/FUND_MOVEMENT_AND_ACCOUNTING.md +4 -4
  206. package/docs/GRID_PRICE_INVARIANT.md +518 -0
  207. package/docs/GRID_RECALCULATION.md +20 -24
  208. package/docs/GRID_RECONCILE.md +62 -41
  209. package/docs/LOGGING.md +1 -0
  210. package/docs/ORDER_ENGINE_POST_1.0_RETROSPECTIVE.md +556 -0
  211. package/docs/README.md +18 -18
  212. package/docs/WORKFLOW.md +13 -8
  213. package/docs/architecture.md +42 -40
  214. package/docs/developer_guide.md +5 -8
  215. package/market_adapter/README.md +45 -65
  216. package/modules/README.md +3 -2
  217. package/package.json +7 -4
  218. package/scripts/README.md +18 -34
  219. package/scripts/reset-settings.sh +1 -1
  220. package/analysis/ama_fitting/package.json +0 -12
  221. package/analysis/analyze_derivatives.ts +0 -320
  222. package/analysis/derivative_chart_generator.ts +0 -874
  223. package/analysis/trend_detection/SIGNAL_DOCUMENTATION.md +0 -403
  224. package/analysis/trend_detection/derivative_analyzer.ts +0 -1059
  225. package/dist/analysis/analyze_derivatives.d.ts +0 -68
  226. package/dist/analysis/analyze_derivatives.d.ts.map +0 -1
  227. package/dist/analysis/analyze_derivatives.js +0 -287
  228. package/dist/analysis/analyze_derivatives.js.map +0 -1
  229. package/dist/analysis/derivative_chart_generator.d.ts +0 -12
  230. package/dist/analysis/derivative_chart_generator.d.ts.map +0 -1
  231. package/dist/analysis/derivative_chart_generator.js +0 -891
  232. package/dist/analysis/derivative_chart_generator.js.map +0 -1
  233. package/dist/analysis/trend_detection/derivative_analyzer.d.ts +0 -248
  234. package/dist/analysis/trend_detection/derivative_analyzer.d.ts.map +0 -1
  235. package/dist/analysis/trend_detection/derivative_analyzer.js +0 -910
  236. package/dist/analysis/trend_detection/derivative_analyzer.js.map +0 -1
  237. package/dist/scripts/generate_market_adapter_whitelist.d.ts +0 -15
  238. package/dist/scripts/generate_market_adapter_whitelist.d.ts.map +0 -1
  239. package/dist/scripts/generate_market_adapter_whitelist.js +0 -191
  240. package/dist/scripts/generate_market_adapter_whitelist.js.map +0 -1
@@ -49,12 +49,13 @@
49
49
  * "active": true,
50
50
  * "dryRun": false,
51
51
  * "startPrice": "pool", // Price for order alignment: "pool", "book", or numeric
52
- * "gridPrice": null, // Reference price for x-factor bounds (3 options):
53
- * // "pool" / "book" = live pair price reference
52
+ * "gridPrice": "ama3", // Reference price for x-factor bounds (default "ama3"):
54
53
  * // "ama"/"ama1".."ama4" = market adapter writes a center snapshot to
55
54
  * // profiles/orders/<botKey>.dynamicgrid.json; grid reads the effective center on reset
56
- * // <number> = fixed numeric reference
57
- * // null = use startPrice
55
+ * // "pool" / "book" = live pair price reference (legacy, editor no longer offers it)
56
+ * // <number> = fixed numeric reference (legacy)
57
+ * // null = use startPrice (legacy); also accepted: false, empty/blank,
58
+ * // and the "none"/"null"/"start"/"startprice"/"s"/"n"/"no"/"f"/"0" spellings
58
59
  * "minPrice": "2x",
59
60
  * "maxPrice": "2x",
60
61
  * "incrementPercent": 0.5,
@@ -63,7 +64,8 @@
63
64
  * "botFunds": { "sell": "100%", "buy": "100%" },
64
65
  * "activeOrders": { "sell": 20, "buy": 20 },
65
66
  * "reserveOrders": { "buy": 0, "sell": 0 }, // Edge reserves: extra live orders (buy: floor, sell: ceiling)
66
- * "debtPolicy": "ignore", // Debt policy: "ignore", "warn", or "block"
67
+ * "poolRef": null, // Optional pinned pool ID for startPrice "pool"
68
+ * "debtPolicy": { ... }, // MPA/credit lending policy — docs/MPA_CREDIT_USAGE.md
67
69
  * "min_BTS_value": 0, // Minimum BTS value threshold for operations
68
70
  * }
69
71
  * ]
@@ -87,11 +89,13 @@ import { path } from './path_api.js';
87
89
  import { getStorage } from './storage/index.js';
88
90
  import { ensureProfilesDirectory, readInput, sleep } from './order/utils/system.js';
89
91
  import { setGlobalConsoleLevel, getGlobalConsoleLevel } from './order/logger.js';
90
- import { DEFAULT_CONFIG, GRID_LIMITS, TIMING, RANGE_QUALITY, LOG_LEVEL, UPDATER, MARKET_ADAPTER, NODE_MANAGEMENT, FILL_PROCESSING, PIPELINE_TIMING, CREDENTIAL_PROMPTS, MAINTENANCE, COW_PERFORMANCE, INCREMENT_BOUNDS, FEE_PARAMETERS, API_LIMITS, LOGGING_CONFIG, NATIVE_CLIENT, LAUNCHER } from './constants.js';
92
+ import { GRID_LIMITS, RANGE_QUALITY, MARKET_ADAPTER, NODE_MANAGEMENT, INCREMENT_BOUNDS, buildDefaultGeneralSettings } from './constants.js';
93
+ import { seedBotDraft, isUnsetGridPrice } from './bot_defaults.js';
91
94
  import { PATHS } from './paths.js';
92
95
  import { SETTINGS_FILE, readGeneralSettings, writeGeneralSettings } from './general_settings.js';
93
96
  import { parseJsonWithComments } from './order/utils/system.js';
94
- import { assertNoDuplicateBotKeys, loadSettingsFile } from './bot_settings.js';
97
+ import { assertNoDuplicateBotKeys, loadSettingsFile, normalizeBotEntry } from './bot_settings.js';
98
+ import { getWhitelistFlags, setWhitelistFlags, renameWhitelistEntry, removeWhitelistEntry, whitelistFile } from './market_adapter_whitelist.js';
95
99
  import { BOT_LIVE_CONFIG_KEYS } from './runtime_settings.js';
96
100
  import { mergeSettings } from './settings_merge.js';
97
101
  import { getErrorMessage } from './utils/errors.js';
@@ -157,26 +161,9 @@ function saveBotsConfig(config, filePath) {
157
161
  * @returns {Object} The loaded settings or default settings if the file doesn't exist.
158
162
  */
159
163
  function loadGeneralSettings() {
160
- const defaults = {
161
- LOG_LEVEL: LOG_LEVEL,
162
- GRID_LIMITS: { ...GRID_LIMITS, GRID_COMPARISON: { ...GRID_LIMITS.GRID_COMPARISON } },
163
- TIMING: { ...TIMING },
164
- UPDATER: { ...UPDATER },
165
- MARKET_ADAPTER: { ...MARKET_ADAPTER },
166
- NODE_MANAGEMENT: { ...NODE_MANAGEMENT },
167
- DEFAULT_CONFIG: { ...DEFAULT_CONFIG },
168
- FILL_PROCESSING: { ...FILL_PROCESSING },
169
- PIPELINE_TIMING: { ...PIPELINE_TIMING },
170
- CREDENTIAL_PROMPTS: { ...CREDENTIAL_PROMPTS },
171
- MAINTENANCE: { ...MAINTENANCE },
172
- COW_PERFORMANCE: { ...COW_PERFORMANCE },
173
- INCREMENT_BOUNDS: { ...INCREMENT_BOUNDS },
174
- FEE_PARAMETERS: { ...FEE_PARAMETERS },
175
- API_LIMITS: { ...API_LIMITS },
176
- LOGGING_CONFIG: { ...LOGGING_CONFIG },
177
- NATIVE_CLIENT: { ...NATIVE_CLIENT },
178
- LAUNCHER: { ...LAUNCHER },
179
- };
164
+ // Single canonical defaults document (modules/constants.ts) — same source
165
+ // the first-run generator and the local-overrides merge use.
166
+ const defaults = buildDefaultGeneralSettings();
180
167
  const settings = readGeneralSettings({
181
168
  fallback: null,
182
169
  onError: (err) => {
@@ -190,6 +177,14 @@ function loadGeneralSettings() {
190
177
  ? configuredDeltaPercent
191
178
  : MARKET_ADAPTER.AMA_DELTA_THRESHOLD_PERCENT;
192
179
  merged.MARKET_ADAPTER.AMA_DELTA_THRESHOLD_PERCENT = effectiveDeltaPercent;
180
+ // MARKET_ADAPTER validation for AMA_SLOPE_DELTA_THRESHOLD_PERCENT (the
181
+ // slope trigger factor: (value/100) x maxSlopePct). Same >0 guard so a
182
+ // hand-edited 0/negative/NaN can never disable the slope reset silently.
183
+ const configuredSlopeDeltaPercent = Number(merged.MARKET_ADAPTER.AMA_SLOPE_DELTA_THRESHOLD_PERCENT);
184
+ const effectiveSlopeDeltaPercent = Number.isFinite(configuredSlopeDeltaPercent) && configuredSlopeDeltaPercent > 0
185
+ ? configuredSlopeDeltaPercent
186
+ : MARKET_ADAPTER.AMA_SLOPE_DELTA_THRESHOLD_PERCENT;
187
+ merged.MARKET_ADAPTER.AMA_SLOPE_DELTA_THRESHOLD_PERCENT = effectiveSlopeDeltaPercent;
193
188
  return merged;
194
189
  }
195
190
  /**
@@ -214,12 +209,32 @@ function listBots(bots) {
214
209
  console.log(' (no bot entries defined yet)');
215
210
  return;
216
211
  }
217
- bots.forEach((bot, index) => {
218
- const name = bot.name || `<unnamed-${index + 1}>`;
219
- const inactiveSuffix = bot.active === false ? ' [inactive]' : '';
220
- const dryRunSuffix = bot.dryRun ? ' (dryRun)' : '';
221
- console.log(` ${index + 1}: ${name}${inactiveSuffix}${dryRunSuffix} ${bot.assetA || '?'} / ${bot.assetB || '?'}`);
222
- });
212
+ const rows = bots.map((bot, index) => ({
213
+ index: String(index + 1),
214
+ name: bot.name || `<unnamed-${index + 1}>`,
215
+ account: bot.preferredAccount || '?',
216
+ pair: `${bot.assetA || '?'}/${bot.assetB || '?'}`,
217
+ inactive: bot.active === false,
218
+ dryRun: !!bot.dryRun
219
+ }));
220
+ const indexWidth = Math.max(1, ...rows.map(r => r.index.length));
221
+ const nameWidth = Math.max('Name'.length, ...rows.map(r => r.name.length));
222
+ const accountWidth = Math.max('Account'.length, ...rows.map(r => r.account.length));
223
+ const pairWidth = Math.max('Pair'.length, ...rows.map(r => r.pair.length));
224
+ const header = [
225
+ '#'.padEnd(indexWidth),
226
+ 'Name'.padEnd(nameWidth),
227
+ 'Account'.padEnd(accountWidth),
228
+ 'Pair'.padEnd(pairWidth)
229
+ ].join(' ');
230
+ console.log(` ${COLORS.yellowBold}${header}${COLORS.reset}`);
231
+ for (const row of rows) {
232
+ const flags = `${row.inactive ? ` ${COLORS.red}[inactive]${COLORS.reset}` : ''}${row.dryRun ? ` ${COLORS.yellow}(dryRun)${COLORS.reset}` : ''}`;
233
+ console.log(` ${COLORS.gray}${row.index.padEnd(indexWidth)}${COLORS.reset} ` +
234
+ `${COLORS.green}${row.name.padEnd(nameWidth)}${COLORS.reset} ` +
235
+ `${COLORS.orange}${row.account.padEnd(accountWidth)}${COLORS.reset} ` +
236
+ `${COLORS.cyan}${row.pair.padEnd(pairWidth)}${COLORS.reset}${flags}`);
237
+ }
223
238
  }
224
239
  /**
225
240
  * Prompts the user to select a bot from the list.
@@ -559,6 +574,13 @@ function isDynamicPriceSource(value) {
559
574
  const lower = value.trim().toLowerCase();
560
575
  return lower === 'pool' || lower === 'book' || /^ama(?:[1-4])?$/.test(lower);
561
576
  }
577
+ /**
578
+ * AMA grid-price values: the four explicit presets ama1..ama4 plus bare
579
+ * "ama" (the pair's default preset). Bare "ama" is a valid green value but is
580
+ * deliberately not listed in the prompt label. These are the ONLY values that
581
+ * color green — pool/book/numeric/none stay accepted, just displayed red.
582
+ */
583
+ const AMA_GRID_PRICE_PATTERN = /^ama(?:[1-4])?$/;
562
584
  /**
563
585
  * Colors a start-price value for display: green when it is a dynamic source
564
586
  * ("pool"/"book"), red when it is a fixed numeric price (no live rescaling).
@@ -573,26 +595,67 @@ function colorStartPriceValue(value) {
573
595
  return `${COLORS.red}${text}${COLORS.reset}`;
574
596
  }
575
597
  /**
576
- * Colors a grid-price value for display: green for dynamic sources
577
- * ("pool"/"book"/AMA keywords), red for fixed numeric references. A null
578
- * gridPrice delegates to startPrice, so the "startPrice" label inherits the
579
- * start-price coloring.
598
+ * Colors a grid-price value for display: GREEN only for AMA values
599
+ * ("ama"/"ama1".."ama4"), RED for everything else — pool/book references,
600
+ * numeric values and any unset grid price (null, false, blank/whitespace, and
601
+ * the "none"/"s"/"no"/"f"/"start" aliases) which delegates to startPrice and is
602
+ * therefore labeled "startPrice", always in red: the delegation itself is the
603
+ * thing the editor wants replaced by an AMA preset.
580
604
  * @param {*} value - The grid price value to color.
581
- * @param {*} [startPrice] - The bot's current startPrice for null resolution.
582
605
  * @returns {string} ANSI-colored value string.
583
606
  */
584
- function colorGridPriceValue(value, startPrice) {
585
- if (value === null || value === undefined) {
586
- const label = isDynamicPriceSource(startPrice)
587
- ? `${COLORS.green}startPrice${COLORS.reset}`
588
- : `${COLORS.red}startPrice${COLORS.reset}`;
589
- return label;
607
+ function colorGridPriceValue(value) {
608
+ if (isUnsetGridPrice(value)) {
609
+ return `${COLORS.red}startPrice${COLORS.reset}`;
590
610
  }
591
611
  const text = String(value);
592
- if (isDynamicPriceSource(text))
612
+ if (AMA_GRID_PRICE_PATTERN.test(text.trim().toLowerCase()))
593
613
  return `${COLORS.green}${text}${COLORS.reset}`;
594
614
  return `${COLORS.red}${text}${COLORS.reset}`;
595
615
  }
616
+ /**
617
+ * True when a startPrice value selects the pair's automatically-resolved pool
618
+ * (the "default" pool source, as opposed to a hand-pinned poolRef).
619
+ * @param {*} value - A startPrice value (or any value to test).
620
+ * @returns {boolean}
621
+ */
622
+ function isPoolStartPrice(value) {
623
+ return String(value ?? '').trim().toLowerCase() === 'pool';
624
+ }
625
+ /**
626
+ * True when the draft's startPrice resolves to the pair's default pool and no
627
+ * poolRef is pinned, so the runtime auto-selects it for order alignment.
628
+ * Deliberately NOT keyed on gridPrice === "pool": anchoring the grid reference
629
+ * on the live market price is discouraged and stays red in the GridPrice
630
+ * field, so the Pool label must not read as a green healthy default for that
631
+ * case.
632
+ * @param {*} data - The bot draft.
633
+ * @returns {boolean}
634
+ */
635
+ function startPriceUsesDefaultPool(data) {
636
+ return isPoolStartPrice(data?.startPrice);
637
+ }
638
+ /**
639
+ * Colors the pool label for the editor summary. An unset poolRef does NOT
640
+ * mean "no pool": when startPrice is "pool" the runtime auto-selects the
641
+ * pair's default pool, so it reads green `default`. Otherwise the pin is
642
+ * inert, shown as grey `none` so it does not compete with live values (a
643
+ * gridPrice of "pool"/"book" is discouraged and stays red in its own field).
644
+ * Display only — the stored poolRef is never changed here.
645
+ * @param {*} data - The bot draft.
646
+ * @returns {string} ANSI-colored pinned pool ID, `default` (green), or `none` (grey).
647
+ */
648
+ function formatPoolRefLabel(data) {
649
+ // A pin only applies to startPrice "pool"; show it as ignored otherwise
650
+ // so the summary never displays a value the editor no longer asks for.
651
+ if (data?.poolRef && !startPriceUsesDefaultPool(data))
652
+ return `${COLORS.gray}ignored${COLORS.reset}`;
653
+ if (data?.poolRef)
654
+ return String(data.poolRef);
655
+ if (startPriceUsesDefaultPool(data))
656
+ return `${COLORS.green}default${COLORS.reset}`;
657
+ return `${COLORS.gray}none${COLORS.reset}`;
658
+ }
596
659
  /**
597
660
  * Colors a boolean flag value for display: green when the flag is in its
598
661
  * healthy state, red when it is not.
@@ -600,13 +663,15 @@ function colorGridPriceValue(value, startPrice) {
600
663
  * @param {boolean} greenWhenTrue - True when `true` is the healthy state
601
664
  * (e.g. Active), false when `false` is
602
665
  * healthy (e.g. DryRun off).
666
+ * @param {string} [offColor] - Color for the unhealthy value (default red);
667
+ * pass a warn color for flags that are optional.
603
668
  * @returns {string} ANSI-colored "true"/"false" string.
604
669
  */
605
- function colorBooleanFlag(value, greenWhenTrue) {
670
+ function colorBooleanFlag(value, greenWhenTrue, offColor = COLORS.red) {
606
671
  const isTrue = !!value;
607
672
  const healthy = greenWhenTrue ? isTrue : !isTrue;
608
673
  const text = String(isTrue);
609
- return healthy ? `${COLORS.green}${text}${COLORS.reset}` : `${COLORS.red}${text}${COLORS.reset}`;
674
+ return healthy ? `${COLORS.green}${text}${COLORS.reset}` : `${offColor}${text}${COLORS.reset}`;
610
675
  }
611
676
  /**
612
677
  * Converts a cron string to a readable format (days delta and time).
@@ -892,20 +957,46 @@ async function askNumberOrPercentage(promptText, defaultValue) {
892
957
  }
893
958
  return parsed;
894
959
  }
960
+ /**
961
+ * Parses a raw boolean answer.
962
+ * Accepted: y/yes/true/1/t → true, n/no/false/0/f → false (case-insensitive);
963
+ * empty input keeps the current/default value; anything else is rejected so
964
+ * the caller can re-prompt (the old `startsWith('y')` rule silently parsed
965
+ * "true" as false).
966
+ * @param {string} raw - Raw prompt input.
967
+ * @param {boolean} [defaultValue] - Value returned for empty input.
968
+ * @returns {{ ok: boolean, value?: boolean }} ok=false for unrecognized input.
969
+ */
970
+ function parseBooleanInput(raw, defaultValue) {
971
+ const value = String(raw ?? '').trim().toLowerCase();
972
+ if (!value)
973
+ return { ok: true, value: !!defaultValue };
974
+ if (value === 'y' || value === 'yes' || value === 'true' || value === '1' || value === 't')
975
+ return { ok: true, value: true };
976
+ if (value === 'n' || value === 'no' || value === 'false' || value === '0' || value === 'f')
977
+ return { ok: true, value: false };
978
+ return { ok: false };
979
+ }
895
980
  /**
896
981
  * Prompts the user for a boolean value (Y/n).
982
+ * Enter keeps the current/default value; unrecognized input re-prompts with
983
+ * a hint instead of silently coercing. See parseBooleanInput for the exact
984
+ * accepted spellings.
897
985
  * @param {string} promptText - The prompt text to display.
898
986
  * @param {boolean} [defaultValue] - The default value to use if input is empty.
899
987
  * @returns {Promise<boolean|string>} The boolean value or '\x1b' if ESC.
900
988
  */
901
989
  async function askBoolean(promptText, defaultValue) {
902
990
  const label = defaultValue ? 'Y/n' : 'y/N';
903
- const raw = (await readInput(`${promptText} (${label}): `)).trim().toLowerCase();
904
- if (raw === '\x1b')
905
- return '\x1b';
906
- if (!raw)
907
- return !!defaultValue;
908
- return raw.startsWith('y');
991
+ for (;;) {
992
+ const raw = (await readInput(`${promptText} (${label}): `)).trim().toLowerCase();
993
+ if (raw === '\x1b')
994
+ return '\x1b';
995
+ const parsed = parseBooleanInput(raw, !!defaultValue);
996
+ if (parsed.ok)
997
+ return parsed.value;
998
+ console.log('Please enter y/yes/true or n/no/false (Enter keeps the current value).');
999
+ }
909
1000
  }
910
1001
  /**
911
1002
  * Prompts the user for the start price (numeric or "pool"/"book").
@@ -940,24 +1031,46 @@ async function askStartPrice(promptText, defaultValue) {
940
1031
  console.log('Please enter "pool", "book", or a numeric value.');
941
1032
  }
942
1033
  }
1034
+ /**
1035
+ * Spellings that clear a `poolRef` pin, leaving the runtime to auto-select
1036
+ * the pair's default pool when the price mode asks for one. `default`/`pool`/
1037
+ * `auto` are accepted as aliases because the 3) Price summary labels the
1038
+ * unpinned pool state `default`, so typing what the summary shows must not be
1039
+ * rejected.
1040
+ */
1041
+ const POOL_REF_CLEAR_INPUTS = new Set(['none', 'clear', 'off', 'no', 'default', 'pool', 'auto']);
1042
+ /**
1043
+ * True when raw poolRef input is a clear/auto alias rather than a pool ID.
1044
+ * @param {*} value - Raw or normalized user input.
1045
+ * @returns {boolean}
1046
+ */
1047
+ function isPoolRefClearInput(value) {
1048
+ return POOL_REF_CLEAR_INPUTS.has(String(value ?? '').trim().toLowerCase());
1049
+ }
943
1050
  /**
944
1051
  * Prompts the user for an optional pool ID to pin price derivation.
945
- * Enter a pool ID (e.g. 48 or 1.19.48) to set/change the pin, or enter
946
- * "none" / "clear" to remove it. Blank input keeps the current value.
1052
+ * Enter a pool ID (e.g. 48 or 1.19.48) to set/change the pin, or one of the
1053
+ * clear aliases ("none", "clear", or "default"/"pool"/"auto") to remove it.
1054
+ * Blank input keeps the current value. When no pin is set, the bracketed
1055
+ * default shows what the runtime will actually use: `default` when startPrice
1056
+ * is "pool" (the pair's auto-selected pool), otherwise `none` (inert) — this
1057
+ * mirrors the 3) Price summary label instead of always reading `none`.
947
1058
  * @param {string} promptText - The prompt text to display.
948
1059
  * @param {string|null|undefined} [currentValue] - The current poolRef value.
1060
+ * @param {boolean} [usesDefaultPool=false] - True when startPrice is "pool",
1061
+ * so an unpinned value renders as `default` rather than `none`.
949
1062
  * @returns {Promise<string|null|symbol>} Pool ID, null (cleared), or '\x1b' on ESC.
950
1063
  */
951
- async function askPoolRef(promptText, currentValue) {
1064
+ async function askPoolRef(promptText, currentValue, usesDefaultPool = false) {
952
1065
  while (true) {
953
- const suffix = currentValue ? ` [${currentValue}]` : ' [none]';
954
- const raw = (await readInput(`${promptText}${suffix} (none to clear): `)).trim();
1066
+ const fallbackLabel = usesDefaultPool ? 'default' : 'none';
1067
+ const suffix = currentValue ? ` [${currentValue}]` : ` [${fallbackLabel}]`;
1068
+ const raw = (await readInput(`${promptText}${suffix}: `)).trim();
955
1069
  if (raw === '\x1b')
956
1070
  return '\x1b';
957
1071
  if (!raw)
958
1072
  return currentValue || null;
959
- const lower = raw.toLowerCase();
960
- if (lower === 'none' || lower === 'clear' || lower === 'off' || lower === 'no') {
1073
+ if (isPoolRefClearInput(raw)) {
961
1074
  return null;
962
1075
  }
963
1076
  const normalized = raw.startsWith('1.19.') ? raw : `1.19.${raw}`;
@@ -969,38 +1082,49 @@ async function askPoolRef(promptText, currentValue) {
969
1082
  }
970
1083
  }
971
1084
  /**
972
- * Prompts the user for the grid price mode (pool, book, ama, numeric, or startprice).
1085
+ * Prompts the user for the grid price mode.
1086
+ * The label lists ama1..ama4; bare "ama" is accepted as well (green) but not
1087
+ * listed. The live colorizer echoes AMA input green and every other value
1088
+ * (pool/book/number/none) red — red only flags "not an AMA value", it does not
1089
+ * reject: pool, book, numeric references, none/null (delegate to startPrice)
1090
+ * and bare "ama" all stay accepted, exactly as before. Only input that is not
1091
+ * a value at all is rejected, with a red error line.
973
1092
  * @param {string} promptText - The prompt text to display.
974
- * @param {string} [defaultValue] - The default value to use if input is empty.
975
- * @param {*} [startPrice] - The bot's current startPrice, used to color the
976
- * "startPrice" default label when gridPrice is null.
977
- * @returns {Promise<string>} The grid price mode or '\x1b' if ESC.
1093
+ * @param {string} [defaultValue] - The current value, offered as the Enter default.
1094
+ * @returns {Promise<any>} The normalized grid-price value, the unchanged
1095
+ * current value on Enter, or '\x1b' if ESC.
978
1096
  */
979
- async function askGridPriceMode(promptText, defaultValue, startPrice) {
1097
+ async function askGridPriceMode(promptText, defaultValue) {
980
1098
  while (true) {
981
- const coloredDefault = defaultValue === null || defaultValue === undefined
982
- ? colorGridPriceValue(null, startPrice)
983
- : colorGridPriceValue(defaultValue);
984
- const raw = (await readInput(`${promptText} [${coloredDefault}]: `, {
985
- colorize: (input) => colorGridPriceValue(input)
986
- })).trim();
1099
+ const coloredDefault = colorGridPriceValue(defaultValue ?? null);
1100
+ const raw = await readInput(`${promptText} [${coloredDefault}]: `, {
1101
+ colorize: (input) => colorGridPriceValue(input),
1102
+ // Keep whitespace so a deactivating space can be told apart from a
1103
+ // bare Enter that means "keep current".
1104
+ trimInput: false
1105
+ });
987
1106
  if (raw === '\x1b')
988
1107
  return '\x1b';
989
- if (!raw)
990
- return defaultValue === undefined ? null : defaultValue;
991
- const lower = raw.toLowerCase();
992
- if (lower === 'none' || lower === 'null' || lower === 'start' || lower === 'startprice')
1108
+ // A bare Enter keeps the current value (an already-unset value is
1109
+ // normalized to null so it is stored as delegation). Whitespace-only
1110
+ // input is NOT a bare Enter: the live echo already previews it as red
1111
+ // startPrice, so it must overwrite with null — otherwise the preview
1112
+ // would promise startPrice while the old value silently survived.
1113
+ if (raw === '')
1114
+ return isUnsetGridPrice(defaultValue) ? null : (defaultValue === undefined ? null : defaultValue);
1115
+ const lower = raw.trim().toLowerCase();
1116
+ if (isUnsetGridPrice(lower))
993
1117
  return null;
994
1118
  if (lower === 'pool')
995
1119
  return lower;
996
1120
  if (lower === 'book')
997
1121
  return 'book';
998
- if (/^ama(?:[1-4])?$/.test(lower))
1122
+ if (AMA_GRID_PRICE_PATTERN.test(lower))
999
1123
  return lower;
1000
1124
  const num = Number(raw);
1001
1125
  if (Number.isFinite(num) && num > 0)
1002
1126
  return num;
1003
- console.log('Please enter: pool, book, ama, ama1..ama4, a positive number, or none/startprice.');
1127
+ console.log(`${COLORS.red}Please enter: ama1, ama2, ama3 or ama4 — also allowed: pool, book, ama, a positive number, or none (s/start, n/no/false/0/f).${COLORS.reset}`);
1004
1128
  }
1005
1129
  }
1006
1130
  /**
@@ -1010,36 +1134,10 @@ async function askGridPriceMode(promptText, defaultValue, startPrice) {
1010
1134
  * @returns {Object} A normalized bot draft.
1011
1135
  */
1012
1136
  function normalizeBotDraft(base = {}) {
1013
- const data = JSON.parse(JSON.stringify(base));
1014
- if (!data.weightDistribution)
1015
- data.weightDistribution = { ...DEFAULT_CONFIG.weightDistribution };
1016
- if (!data.botFunds)
1017
- data.botFunds = { ...DEFAULT_CONFIG.botFunds };
1018
- if (!data.activeOrders)
1019
- data.activeOrders = { ...DEFAULT_CONFIG.activeOrders };
1020
- if (typeof data.reserveOrders === 'number')
1021
- data.reserveOrders = { buy: Math.max(0, Math.floor(data.reserveOrders)), sell: 0 };
1022
- if (data.reserveOrders === undefined || data.reserveOrders === null || typeof data.reserveOrders !== 'object' || Array.isArray(data.reserveOrders))
1023
- data.reserveOrders = { ...DEFAULT_CONFIG.reserveOrders };
1024
- if (data.active === undefined)
1025
- data.active = DEFAULT_CONFIG.active;
1026
- if (data.dryRun === undefined)
1027
- data.dryRun = DEFAULT_CONFIG.dryRun;
1028
- if (data.minPrice === undefined)
1029
- data.minPrice = DEFAULT_CONFIG.minPrice;
1030
- if (data.maxPrice === undefined)
1031
- data.maxPrice = DEFAULT_CONFIG.maxPrice;
1032
- if (data.incrementPercent === undefined)
1033
- data.incrementPercent = DEFAULT_CONFIG.incrementPercent;
1034
- if (data.targetSpreadPercent === undefined)
1035
- data.targetSpreadPercent = DEFAULT_CONFIG.targetSpreadPercent;
1036
- if (data.startPrice === undefined)
1037
- data.startPrice = data.startPrice || DEFAULT_CONFIG.startPrice || 'pool';
1038
- if (data.gridPrice === undefined)
1039
- data.gridPrice = null;
1040
- delete data.gridPriceOffsetPct;
1041
- delete data.gridPriceOffsetClampToBounds;
1042
- return data;
1137
+ // Seeding rules live in modules/bot_defaults.ts (single defaults source).
1138
+ // Return type stays `any`: the editor prompt-flow treats drafts as dynamic
1139
+ // bags and its inference depends on it.
1140
+ return seedBotDraft(base);
1043
1141
  }
1044
1142
  /**
1045
1143
  * Resolve data.preferredAccount to a chain account ID and stamp data.accountId
@@ -1162,28 +1260,59 @@ async function ensureBotAccountId(data, timeoutMs = 15000, quiet = false, force
1162
1260
  /**
1163
1261
  * Interactive menu to edit bot data.
1164
1262
  * @param {Object} [base={}] - The initial bot data to edit.
1165
- * @returns {Promise<Object|null>} The edited bot data or null if cancelled.
1263
+ * @param {number} [index=0] - Position the edited bot will occupy in the
1264
+ * bots array (used to derive the same botKey the runtime uses for unnamed
1265
+ * fallback entries).
1266
+ * @param {number} [baseIndex=index] - Position the base entry currently
1267
+ * occupies (differs from `index` for copies, whose source sits elsewhere).
1268
+ * @returns {Promise<{ data: any, commitAdapter: (() => void)|null }|null>}
1269
+ * The edited draft plus a commit hook for staged `6) Adapter` flags — the
1270
+ * caller MUST run `commitAdapter()` only after its bots.json save succeeded
1271
+ * (so a failed save never leaves the whitelist out of sync), or null when
1272
+ * cancelled.
1166
1273
  */
1167
- async function promptBotData(base = {}) {
1274
+ async function promptBotData(base = {}, index = 0, baseIndex = index) {
1168
1275
  const data = normalizeBotDraft(base);
1276
+ // Market-adapter flags (6) Adapter) live in
1277
+ // profiles/market_adapter_whitelist.json keyed by botKey — the same file
1278
+ // the market adapter reads. Stage edits here and expose them through the
1279
+ // commit hook so Cancel (or a failed bots.json save) discards them
1280
+ // together with the rest of the draft.
1281
+ const baseEntry = base && typeof base === 'object' && Object.keys(base).length > 0 ? base : null;
1282
+ const baseBotKey = baseEntry ? String(normalizeBotEntry(baseEntry, baseIndex).botKey || '') : '';
1283
+ let adapterStaged = null;
1284
+ const isAmaGridPriceDraft = () => /^ama(?:[1-4])?$/.test(String(data.gridPrice ?? '').trim().toLowerCase());
1285
+ const adapterFlags = () => adapterStaged
1286
+ || getWhitelistFlags(baseBotKey || String(normalizeBotEntry(data, index).botKey || ''));
1169
1287
  let finished = false;
1170
1288
  let cancelled = false;
1171
1289
  let showMenu = true;
1172
1290
  while (!finished) {
1173
1291
  if (showMenu) {
1174
1292
  console.log(`\n${COLORS.bold}--- Bot Editor: ` + (data.name || 'New Bot') + ` ---${COLORS.reset}`);
1175
- console.log(`${COLORS.yellowBold}1) Pair:${COLORS.reset} ${COLORS.cyan}${data.assetA || '?'} / ${data.assetB || '?'}${COLORS.reset}`);
1176
- console.log(`${COLORS.yellowBold}2) Identity:${COLORS.reset} ${COLORS.orange}Name:${COLORS.reset} ${data.name || '?'} , ${COLORS.orange}Account:${COLORS.reset} ${data.preferredAccount || '?'} , ${COLORS.orange}Active:${COLORS.reset} ${colorBooleanFlag(data.active, true)}, ${COLORS.orange}DryRun:${COLORS.reset} ${colorBooleanFlag(data.dryRun, false)}`);
1177
- console.log(`${COLORS.yellowBold}3) Price:${COLORS.reset} ${COLORS.orange}Range:${COLORS.reset} [${colorPriceRangeValue(data.minPrice)} - ${colorPriceRangeValue(data.maxPrice)}], ${COLORS.orange}Start:${COLORS.reset} ${colorStartPriceValue(data.startPrice)}, ${COLORS.orange}Pool:${COLORS.reset} ${data.poolRef || 'none'}, ${COLORS.orange}GridPrice:${COLORS.reset} ${colorGridPriceValue(data.gridPrice, data.startPrice)}`);
1178
- console.log(`${COLORS.yellowBold}4) Grid:${COLORS.reset} ${COLORS.orange}Weights:${COLORS.reset} (S:${data.weightDistribution.sell}, B:${data.weightDistribution.buy}), ${COLORS.orange}Incr:${COLORS.reset} ${data.incrementPercent}%, ${COLORS.orange}Spread:${COLORS.reset} ${data.targetSpreadPercent}%`);
1179
- console.log(`${COLORS.yellowBold}5) Funding:${COLORS.reset} ${COLORS.orange}Sell:${COLORS.reset} ${colorPercentageInput(data.botFunds.sell)}, ${COLORS.orange}Buy:${COLORS.reset} ${colorPercentageInput(data.botFunds.buy)} | ${COLORS.orange}Orders:${COLORS.reset} (S:${data.activeOrders.sell}, B:${data.activeOrders.buy}) | ${COLORS.orange}Reserve:${COLORS.reset} (S:${data.reserveOrders?.sell ?? 0}, B:${data.reserveOrders?.buy ?? 0})`);
1293
+ console.log(`${COLORS.yellowBold}1) Pair:${COLORS.reset} ${COLORS.cyan}${data.assetA || '?'} / ${data.assetB || '?'}${COLORS.reset}`);
1294
+ console.log(`${COLORS.yellowBold}2) Identity:${COLORS.reset} ${COLORS.orange}Name:${COLORS.reset} ${data.name || '?'}, ${COLORS.orange}Account:${COLORS.reset} ${data.preferredAccount || '?'} | ${COLORS.orange}Active:${COLORS.reset} ${colorBooleanFlag(data.active, true)}, ${COLORS.orange}DryRun:${COLORS.reset} ${colorBooleanFlag(data.dryRun, false)}`);
1295
+ console.log(`${COLORS.yellowBold}3) Price:${COLORS.reset} ${COLORS.orange}Range:${COLORS.reset} [${colorPriceRangeValue(data.minPrice)} - ${colorPriceRangeValue(data.maxPrice)}] | ${COLORS.orange}Start:${COLORS.reset} ${colorStartPriceValue(data.startPrice)}, ${COLORS.orange}Pool:${COLORS.reset} ${formatPoolRefLabel(data)} | ${COLORS.orange}GridPrice:${COLORS.reset} ${colorGridPriceValue(data.gridPrice)}`);
1296
+ console.log(`${COLORS.yellowBold}4) Grid:${COLORS.reset} ${COLORS.orange}Weights:${COLORS.reset} (S:${data.weightDistribution.sell}, B:${data.weightDistribution.buy}) | ${COLORS.orange}Increment:${COLORS.reset} ${data.incrementPercent}%, ${COLORS.orange}Spread:${COLORS.reset} ${data.targetSpreadPercent}%`);
1297
+ console.log(`${COLORS.yellowBold}5) Inventory:${COLORS.reset} ${COLORS.orange}Sell:${COLORS.reset} ${colorPercentageInput(data.botFunds.sell)}, ${COLORS.orange}Buy:${COLORS.reset} ${colorPercentageInput(data.botFunds.buy)} | ${COLORS.orange}MarketOrder:${COLORS.reset} (S:${data.activeOrders.sell}, B:${data.activeOrders.buy}) | ${COLORS.orange}EdgeOrder:${COLORS.reset} (S:${data.reserveOrders?.sell ?? 0}, B:${data.reserveOrders?.buy ?? 0})`);
1298
+ {
1299
+ const flags = adapterFlags();
1300
+ const inert = !isAmaGridPriceDraft() && (flags.ama || flags.dynamicWeight || flags.asymmetricBounds);
1301
+ const hint = inert ? ` ${COLORS.red}(needs gridPrice=ama)${COLORS.reset}` : '';
1302
+ // Adapter flags read as a health state, not a neutral toggle:
1303
+ // green = on (colorBooleanFlag's greenWhenTrue form, same
1304
+ // helper section 2 uses for Active). Price is the gate for the
1305
+ // other two, so off stays red; Weight/Range are optional
1306
+ // riders, so off is a bright-yellow warning rather than an error.
1307
+ console.log(`${COLORS.yellowBold}6) Adapter:${COLORS.reset} ${COLORS.orange}Price:${COLORS.reset} ${colorBooleanFlag(flags.ama, true)}, ${COLORS.orange}Weight:${COLORS.reset} ${colorBooleanFlag(flags.dynamicWeight, true, COLORS.yellowBold)}, ${COLORS.orange}Range:${COLORS.reset} ${colorBooleanFlag(flags.asymmetricBounds, true, COLORS.yellowBold)}${hint}`);
1308
+ }
1180
1309
  console.log('--------------------------------------------------');
1181
1310
  console.log(`${COLORS.greenBold}S) Save & Exit${COLORS.reset}`);
1182
1311
  console.log(`${COLORS.white}C) Cancel (Discard changes)${COLORS.reset}`);
1183
1312
  showMenu = false;
1184
1313
  }
1185
1314
  const choice = (await readInput('Select section to edit or action: ', {
1186
- validate: (input) => ['1', '2', '3', '4', '5', 's', 'c'].includes(input.toLowerCase())
1315
+ validate: (input) => ['1', '2', '3', '4', '5', '6', 's', 'c'].includes(input.toLowerCase())
1187
1316
  })).trim().toLowerCase();
1188
1317
  if (choice === '\x1b') {
1189
1318
  finished = true;
@@ -1206,7 +1335,7 @@ async function promptBotData(base = {}) {
1206
1335
  const name = await askRequiredString('Bot name', data.name);
1207
1336
  if (name === '\x1b')
1208
1337
  break;
1209
- let prefAcc = await askRequiredString('Preferred account', data.preferredAccount);
1338
+ let prefAcc = await askRequiredString('Blockchain account', data.preferredAccount);
1210
1339
  if (prefAcc === '\x1b')
1211
1340
  break;
1212
1341
  // Verify the account exists on the blockchain and stamp its ID.
@@ -1223,7 +1352,7 @@ async function promptBotData(base = {}) {
1223
1352
  ? `nodes unreachable (lookup timed out). Check your connection and try again, or press Esc to cancel.`
1224
1353
  : `account '${draft.preferredAccount}' not found on the blockchain. Check the name and try again, or press Esc to cancel.`;
1225
1354
  console.log(`${COLORS.red}Error: ${why}${COLORS.reset}`);
1226
- const retry = await askRequiredString('Preferred account', String(draft.preferredAccount ?? ''));
1355
+ const retry = await askRequiredString('Blockchain account', String(draft.preferredAccount ?? ''));
1227
1356
  if (retry === '\x1b') {
1228
1357
  prefAcc = retry;
1229
1358
  break;
@@ -1254,13 +1383,19 @@ async function promptBotData(base = {}) {
1254
1383
  const maxP = await askMaxPrice('maxPrice', data.maxPrice, minP);
1255
1384
  if (maxP === '\x1b')
1256
1385
  break;
1257
- const startP = await askStartPrice('startPrice (pool, book or A/B)', data.startPrice);
1386
+ const startP = await askStartPrice('startPrice (pool, book or price)', data.startPrice);
1258
1387
  if (startP === '\x1b')
1259
1388
  break;
1260
- const poolR = await askPoolRef('poolRef (pinned pool ID for price source)', data.poolRef);
1389
+ // poolRef only feeds startPrice "pool" (README: ignored for
1390
+ // book/fixed price), so don't ask when startPrice isn't pool —
1391
+ // an existing pin is kept dormant, not cleared, so switching
1392
+ // back to pool restores it.
1393
+ const poolR = isPoolStartPrice(startP)
1394
+ ? await askPoolRef('poolRef (pinned pool ID for price source)', data.poolRef, true)
1395
+ : (data.poolRef || null);
1261
1396
  if (poolR === '\x1b')
1262
1397
  break;
1263
- const gp = await askGridPriceMode('gridPrice (pool/book/ama/number/none)', data.gridPrice, data.startPrice);
1398
+ const gp = await askGridPriceMode('gridPrice (ama1/ama2/ama3/ama4)', data.gridPrice);
1264
1399
  if (gp === '\x1b')
1265
1400
  break;
1266
1401
  data.minPrice = minP;
@@ -1305,10 +1440,10 @@ async function promptBotData(base = {}) {
1305
1440
  const oBuy = await askIntegerInRange('activeOrders buy count', data.activeOrders.buy, 1, 100);
1306
1441
  if (oBuy === '\x1b')
1307
1442
  break;
1308
- const rBuy = await askIntegerInRange('reserveOrders buy floor count (0 disables)', data.reserveOrders?.buy ?? 0, 0, 20);
1443
+ const rBuy = await askIntegerInRange('reserveOrders buy floor count (0 disables)', data.reserveOrders?.buy ?? 0, 0, 100);
1309
1444
  if (rBuy === '\x1b')
1310
1445
  break;
1311
- const rSell = await askIntegerInRange('reserveOrders sell ceiling count (0 disables)', data.reserveOrders?.sell ?? 0, 0, 20);
1446
+ const rSell = await askIntegerInRange('reserveOrders sell ceiling count (0 disables)', data.reserveOrders?.sell ?? 0, 0, 100);
1312
1447
  if (rSell === '\x1b')
1313
1448
  break;
1314
1449
  data.botFunds.sell = fSell;
@@ -1318,6 +1453,29 @@ async function promptBotData(base = {}) {
1318
1453
  data.reserveOrders = { buy: rBuy, sell: rSell };
1319
1454
  showMenu = true;
1320
1455
  break;
1456
+ case '6': {
1457
+ // Per-bot market-adapter flags (Price = AMA pricing, Weight =
1458
+ // dynamic weights, Range = asymmetric range scaling).
1459
+ const flags = adapterFlags();
1460
+ if (!isAmaGridPriceDraft()) {
1461
+ console.log(`${COLORS.gray}Note: these flags only take effect when gridPrice is ama/ama1..ama4 (set in 3) Price).${COLORS.reset}`);
1462
+ }
1463
+ const price = await askBoolean('AMA pricing (Price)', flags.ama);
1464
+ if (price === '\x1b')
1465
+ break;
1466
+ const weight = await askBoolean('Dynamic weights (Weight)', flags.dynamicWeight);
1467
+ if (weight === '\x1b')
1468
+ break;
1469
+ const range = await askBoolean('Range scaling (Range)', flags.asymmetricBounds);
1470
+ if (range === '\x1b')
1471
+ break;
1472
+ adapterStaged = { ama: price, dynamicWeight: weight, asymmetricBounds: range };
1473
+ if (!price && (weight || range)) {
1474
+ console.log(`${COLORS.yellow}Note: Weight/Range only take effect while Price (AMA) is enabled.${COLORS.reset}`);
1475
+ }
1476
+ showMenu = true;
1477
+ break;
1478
+ }
1321
1479
  case 's':
1322
1480
  // Final basic validation before saving
1323
1481
  if (!data.name || !data.assetA || !data.assetB || !data.preferredAccount) {
@@ -1350,11 +1508,40 @@ async function promptBotData(base = {}) {
1350
1508
  }
1351
1509
  if (cancelled)
1352
1510
  return null;
1511
+ // Staged 6) Adapter flags become a commit hook instead of an immediate
1512
+ // write: the caller runs it only after its bots.json save succeeded, so
1513
+ // the whitelist can never end up updated by a save that was rejected
1514
+ // (duplicate bot keys, write error). Writes are skipped when the staged
1515
+ // flags already match what is stored — an unchanged save never creates a
1516
+ // placeholder entry.
1517
+ const stagedFlags = adapterStaged;
1518
+ const commitAdapter = stagedFlags ? () => {
1519
+ const newKey = String(normalizeBotEntry(data, index).botKey || '');
1520
+ const needRename = !!(baseBotKey && newKey && baseBotKey !== newKey);
1521
+ const current = getWhitelistFlags(baseBotKey || newKey);
1522
+ const unchanged = stagedFlags.ama === current.ama
1523
+ && stagedFlags.dynamicWeight === current.dynamicWeight
1524
+ && stagedFlags.asymmetricBounds === current.asymmetricBounds;
1525
+ if (needRename && !renameWhitelistEntry(baseBotKey, newKey)) {
1526
+ console.log(`${COLORS.yellow}Adapter flags not saved — see the warning above.${COLORS.reset}`);
1527
+ return;
1528
+ }
1529
+ // Unchanged flags need no write — a successful rename above already
1530
+ // migrated the entry under the new key.
1531
+ if (unchanged)
1532
+ return;
1533
+ if (!setWhitelistFlags(newKey, stagedFlags)) {
1534
+ console.log(`${COLORS.yellow}Warning: adapter flags not saved — could not write ${whitelistFile()}.${COLORS.reset}`);
1535
+ }
1536
+ else {
1537
+ console.log(`${COLORS.green}Adapter flags saved for '${newKey}'. Price/Weight are picked up on the adapter's next cycle; Range scaling applies after 'dexbot reset ${data.name}'.${COLORS.reset}`);
1538
+ }
1539
+ } : null;
1353
1540
  // Return the final data structure, preserving ALL fields from the normalized
1354
1541
  // draft (deep-copy of base + defaults, minus stripped runtime-managed fields).
1355
1542
  // A fixed whitelist here would silently drop custom overrides (logging, timing,
1356
1543
  // feeParams, gridLimits, poolRef, etc.) when the caller replaces the bot entry.
1357
- return { ...data };
1544
+ return { data: { ...data }, commitAdapter };
1358
1545
  }
1359
1546
  /**
1360
1547
  * Interactive menu to edit general settings.
@@ -1365,13 +1552,13 @@ async function promptGeneralSettings() {
1365
1552
  let finished = false;
1366
1553
  while (!finished) {
1367
1554
  console.log(`${COLORS.bold}--- General Settings (Global) ---${COLORS.reset}`);
1368
- console.log(`${COLORS.yellowBold}1) Grid Health:${COLORS.reset} ${COLORS.orange}Ratio:${COLORS.reset} ${settings.GRID_LIMITS.GRID_REGENERATION_PERCENTAGE}%, ${COLORS.orange}RMS:${COLORS.reset} ${settings.GRID_LIMITS.GRID_COMPARISON.RMS_PERCENTAGE}%, ${COLORS.orange}AMA Delta:${COLORS.reset} ${settings.MARKET_ADAPTER.AMA_DELTA_THRESHOLD_PERCENT}%`);
1369
- console.log(`${COLORS.yellowBold}2) Order Recovery:${COLORS.reset} ${COLORS.orange}Dust Threshold:${COLORS.reset} ${settings.GRID_LIMITS.PARTIAL_DUST_THRESHOLD_PERCENTAGE}%`);
1370
- const nodeCount = (settings.NODES.list || []).length;
1555
+ console.log(`${COLORS.yellowBold}1) Grid Drift:${COLORS.reset} ${COLORS.orange}Funds:${COLORS.reset} ${settings.GRID_LIMITS.GRID_REGENERATION_PERCENTAGE}%, ${COLORS.orange}RMS:${COLORS.reset} ${settings.GRID_LIMITS.GRID_COMPARISON.RMS_PERCENTAGE}% | ${COLORS.orange}AMA Δ:${COLORS.reset} ${settings.MARKET_ADAPTER.AMA_DELTA_THRESHOLD_PERCENT}%, ${COLORS.orange}AMA-Slope Δ:${COLORS.reset} ${settings.MARKET_ADAPTER.AMA_SLOPE_DELTA_THRESHOLD_PERCENT}%`);
1371
1556
  const hcIntervalMin = ((settings.NODES.healthCheck?.intervalMs || NODE_MANAGEMENT.HEALTH_CHECK_INTERVAL_MS) / 60000).toFixed(0);
1557
+ console.log(`${COLORS.yellowBold}2) Order Maint.:${COLORS.reset} ${COLORS.orange}Dust Threshold:${COLORS.reset} ${settings.GRID_LIMITS.PARTIAL_DUST_THRESHOLD_PERCENTAGE}% | ${COLORS.orange}Health Check:${COLORS.reset} ${hcIntervalMin}min`);
1558
+ const nodeCount = (settings.NODES.list || []).length;
1372
1559
  const prefNodeDisplay = settings.NODES.selection?.preferredNode || 'none';
1373
- console.log(`${COLORS.yellowBold}3) Node Config:${COLORS.reset} ${COLORS.orange}Nodes:${COLORS.reset} ${nodeCount}, ${COLORS.orange}HealthChk:${COLORS.reset} ${hcIntervalMin}min, ${COLORS.orange}PrefNode:${COLORS.reset} ${prefNodeDisplay}`);
1374
- console.log(`${COLORS.yellowBold}4) Log lvl:${COLORS.reset} ${COLORS.orange}${settings.LOG_LEVEL}${COLORS.reset} (debug, info, warn, error)`);
1560
+ console.log(`${COLORS.yellowBold}3) Node Config:${COLORS.reset} ${COLORS.orange}Nodes:${COLORS.reset} ${nodeCount}, ${COLORS.orange}Preferred Node:${COLORS.reset} ${prefNodeDisplay}`);
1561
+ console.log(`${COLORS.yellowBold}4) Log Level:${COLORS.reset} ${COLORS.orange}${settings.LOG_LEVEL}${COLORS.reset} (debug, info, warn, error)`);
1375
1562
  const updaterStatus = settings.UPDATER.ACTIVE ? `${COLORS.green}ON${COLORS.reset}` : `${COLORS.red}OFF${COLORS.reset}`;
1376
1563
  const currentSched = parseCronToDelta(settings.UPDATER.SCHEDULE || "0 0 * * *");
1377
1564
  console.log(`${COLORS.yellowBold}5) Updater:${COLORS.reset} [${updaterStatus}] ${COLORS.orange}Branch:${COLORS.reset} ${settings.UPDATER.BRANCH}, ${COLORS.orange}Interval:${COLORS.reset} ${currentSched.days}d, ${COLORS.orange}Time:${COLORS.reset} ${currentSched.time}`);
@@ -1387,24 +1574,34 @@ async function promptGeneralSettings() {
1387
1574
  }
1388
1575
  switch (choice) {
1389
1576
  case '1':
1390
- const gRegen = await askNumberWithBounds('Grid Ratio Regeneration %', settings.GRID_LIMITS.GRID_REGENERATION_PERCENTAGE, 0.1, 50);
1577
+ const gRegen = await askNumberWithBounds('Grid Funds Regeneration %', settings.GRID_LIMITS.GRID_REGENERATION_PERCENTAGE, 0.1, 50);
1391
1578
  if (gRegen === '\x1b')
1392
1579
  break;
1393
1580
  const rms = await askNumberWithBounds('RMS Divergence Threshold %', settings.GRID_LIMITS.GRID_COMPARISON.RMS_PERCENTAGE, 1, 100);
1394
1581
  if (rms === '\x1b')
1395
1582
  break;
1396
- const amaDelta = await askNumberWithBounds('AMA Delta Threshold %', settings.MARKET_ADAPTER.AMA_DELTA_THRESHOLD_PERCENT, 0.1, 50.0);
1583
+ const amaDelta = await askNumberWithBounds('AMA Δ', settings.MARKET_ADAPTER.AMA_DELTA_THRESHOLD_PERCENT, 0.1, 50.0);
1397
1584
  if (amaDelta === '\x1b')
1398
1585
  break;
1586
+ const amaSlopeDelta = await askNumberWithBounds('AMA-Slope Δ', settings.MARKET_ADAPTER.AMA_SLOPE_DELTA_THRESHOLD_PERCENT, 0.1, 100.0);
1587
+ if (amaSlopeDelta === '\x1b')
1588
+ break;
1399
1589
  settings.GRID_LIMITS.GRID_REGENERATION_PERCENTAGE = gRegen;
1400
1590
  settings.GRID_LIMITS.GRID_COMPARISON.RMS_PERCENTAGE = rms;
1401
1591
  settings.MARKET_ADAPTER.AMA_DELTA_THRESHOLD_PERCENT = amaDelta;
1592
+ settings.MARKET_ADAPTER.AMA_SLOPE_DELTA_THRESHOLD_PERCENT = amaSlopeDelta;
1402
1593
  break;
1403
1594
  case '2':
1404
1595
  const dust = await askNumberWithBounds('Partial Dust Threshold %', settings.GRID_LIMITS.PARTIAL_DUST_THRESHOLD_PERCENTAGE, 0.1, 50);
1405
1596
  if (dust === '\x1b')
1406
1597
  break;
1407
1598
  settings.GRID_LIMITS.PARTIAL_DUST_THRESHOLD_PERCENTAGE = dust;
1599
+ const hcInterval = await askIntegerInRange('Health Check Interval (min)', (settings.NODES.healthCheck?.intervalMs || NODE_MANAGEMENT.HEALTH_CHECK_INTERVAL_MS) / 60000, 1, 43200);
1600
+ if (hcInterval === '\x1b')
1601
+ break;
1602
+ if (!settings.NODES.healthCheck)
1603
+ settings.NODES.healthCheck = {};
1604
+ settings.NODES.healthCheck.intervalMs = hcInterval * 60000;
1408
1605
  break;
1409
1606
  case '3':
1410
1607
  settings.NODES.enabled = true;
@@ -1455,12 +1652,6 @@ async function promptGeneralSettings() {
1455
1652
  if (editorCancelled)
1456
1653
  break;
1457
1654
  }
1458
- const hcInterval = await askIntegerInRange('Health Check Interval (min)', (settings.NODES.healthCheck?.intervalMs || NODE_MANAGEMENT.HEALTH_CHECK_INTERVAL_MS) / 60000, 1, 43200);
1459
- if (hcInterval === '\x1b')
1460
- break;
1461
- if (!settings.NODES.healthCheck)
1462
- settings.NODES.healthCheck = {};
1463
- settings.NODES.healthCheck.intervalMs = hcInterval * 60000;
1464
1655
  const prefNode = await askString('Preferred Node URL (leave empty for automatic selection)', settings.NODES.selection?.preferredNode || '');
1465
1656
  if (prefNode === '\x1b')
1466
1657
  break;
@@ -1528,12 +1719,13 @@ async function main() {
1528
1719
  case '1': {
1529
1720
  while (true) {
1530
1721
  try {
1531
- const entry = await promptBotData();
1532
- if (!entry)
1722
+ const result = await promptBotData({}, config.bots.length);
1723
+ if (!result)
1533
1724
  break;
1534
- config.bots.push(entry);
1725
+ config.bots.push(result.data);
1535
1726
  saveBotsConfig(config, filePath);
1536
- console.log(`\nAdded bot '${entry.name}' to ${path.basename(filePath)}.`);
1727
+ result.commitAdapter?.();
1728
+ console.log(`\nAdded bot '${result.data.name}' to ${path.basename(filePath)}.`);
1537
1729
  }
1538
1730
  catch (err) {
1539
1731
  console.log(`\n❌ Invalid input: ${getErrorMessage(err)}\n`);
@@ -1548,10 +1740,12 @@ async function main() {
1548
1740
  if (idx === null || idx === '\x1b')
1549
1741
  break;
1550
1742
  try {
1551
- const entry = await promptBotData(config.bots[idx]);
1552
- if (entry) {
1553
- config.bots[idx] = entry;
1743
+ const result = await promptBotData(config.bots[idx], idx);
1744
+ if (result) {
1745
+ config.bots[idx] = result.data;
1554
1746
  saveBotsConfig(config, filePath);
1747
+ result.commitAdapter?.();
1748
+ const entry = result.data;
1555
1749
  console.log(`saved settings '${entry.name}' in ${path.basename(filePath)}.\n`);
1556
1750
  console.log(`Live pickup (~1min, no reload needed): ${BOT_LIVE_CONFIG_KEYS.join(' / ')}.`);
1557
1751
  console.log(`Grid geometry needs 'dexbot reset ${entry.name}' (or 'dexbot reload' for everything at once); market/account changes need 'dexbot reload'.\n`);
@@ -1573,8 +1767,17 @@ async function main() {
1573
1767
  if (confirm === '\x1b')
1574
1768
  break;
1575
1769
  if (confirm) {
1770
+ // Derive the whitelist key before splicing (createBotKey
1771
+ // uses the index only for unnamed fallback entries).
1772
+ const removedKey = String(normalizeBotEntry(config.bots[idx], idx).botKey || '');
1576
1773
  const removed = config.bots.splice(idx, 1)[0];
1577
1774
  saveBotsConfig(config, filePath);
1775
+ // Only after the bots.json save succeeded, drop the
1776
+ // matching whitelist entry so a deleted bot leaves no
1777
+ // stale flags behind (name reuse would inherit them).
1778
+ if (!removeWhitelistEntry(removedKey)) {
1779
+ console.log(`${COLORS.yellow}Warning: could not remove whitelist entry '${removedKey}' — check ${whitelistFile()}.${COLORS.reset}`);
1780
+ }
1578
1781
  console.log(`Removed bot '${removed.name || placeholderName}' from ${path.basename(filePath)}.\n`);
1579
1782
  }
1580
1783
  else {
@@ -1589,11 +1792,12 @@ async function main() {
1589
1792
  if (idx === null || idx === '\x1b')
1590
1793
  break;
1591
1794
  try {
1592
- const entry = await promptBotData(config.bots[idx]);
1593
- if (entry) {
1594
- config.bots.splice(idx + 1, 0, entry);
1795
+ const result = await promptBotData(config.bots[idx], idx + 1, idx);
1796
+ if (result) {
1797
+ config.bots.splice(idx + 1, 0, result.data);
1595
1798
  saveBotsConfig(config, filePath);
1596
- console.log(`Copied bot '${entry.name}' into ${path.basename(filePath)}.\n`);
1799
+ result.commitAdapter?.();
1800
+ console.log(`Copied bot '${result.data.name}' into ${path.basename(filePath)}.\n`);
1597
1801
  }
1598
1802
  }
1599
1803
  catch (err) {
@@ -1617,5 +1821,5 @@ async function main() {
1617
1821
  }
1618
1822
  console.log('Botmanager closed!');
1619
1823
  }
1620
- export { main, normalizeBotDraft, ensureBotAccountId, parseJsonWithComments };
1824
+ export { main, normalizeBotDraft, ensureBotAccountId, parseJsonWithComments, parseBooleanInput, colorGridPriceValue, formatPoolRefLabel, isPoolStartPrice, isPoolRefClearInput, loadGeneralSettings, saveGeneralSettings };
1621
1825
  //# sourceMappingURL=account_bots.js.map