@hyperflow.fun/ghost 0.0.4 → 0.0.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 (26) hide show
  1. package/dist/index.js +926 -479
  2. package/dist/package.json +1 -1
  3. package/dist/skills/builtin/ask-user-questions/SKILL.md +48 -0
  4. package/dist/skills/builtin/event-judge/SKILL.md +51 -7
  5. package/dist/skills/builtin/trade-executor/SKILL.md +1 -1
  6. package/dist/web/dist/assets/{Chart-BgUhYrVH.js → Chart-cnNd398J.js} +1 -1
  7. package/dist/web/dist/assets/{Config-CMp84sNc.js → Config-Bui5kcW0.js} +1 -1
  8. package/dist/web/dist/assets/{Cost-BgKdnj4i.js → Cost-BDSIn5ub.js} +1 -1
  9. package/dist/web/dist/assets/{Cron-L4GEQwj0.js → Cron-C7gaZhMu.js} +1 -1
  10. package/dist/web/dist/assets/{Dashboard-D1UNPhTW.js → Dashboard-qwYsK3Gi.js} +1 -1
  11. package/dist/web/dist/assets/{Logs-DZkot-wQ.js → Logs-DyfS6kEe.js} +1 -1
  12. package/dist/web/dist/assets/{Memory-DsQackdo.js → Memory-BPMmbFbx.js} +1 -1
  13. package/dist/web/dist/assets/{Sessions-uqRIDn57.js → Sessions-nANl57gZ.js} +1 -1
  14. package/dist/web/dist/assets/{Skills-B9WQPgSe.js → Skills-BPXH_m1K.js} +1 -1
  15. package/dist/web/dist/assets/{Tools-Dyf8Zp-H.js → Tools-CypplonI.js} +1 -1
  16. package/dist/web/dist/assets/{activity-DRn1WSJo.js → activity-BLl_txfw.js} +1 -1
  17. package/dist/web/dist/assets/{clock-FMsaqykN.js → clock-CMLb2b5z.js} +1 -1
  18. package/dist/web/dist/assets/{highlighted-body-OFNGDK62-Ck5_e4wj.js → highlighted-body-OFNGDK62-CKxBaGN5.js} +1 -1
  19. package/dist/web/dist/assets/index-CKAK_IGv.js +59 -0
  20. package/dist/web/dist/assets/index-PX2emSv9.css +1 -0
  21. package/dist/web/dist/assets/{mermaid-GHXKKRXX-jlxJo7RH.js → mermaid-GHXKKRXX-BuHBmaw6.js} +3 -3
  22. package/dist/web/dist/assets/{refresh-cw-CRQa2SMT.js → refresh-cw-DHCgoaOK.js} +1 -1
  23. package/dist/web/dist/index.html +2 -2
  24. package/package.json +1 -1
  25. package/dist/web/dist/assets/index-CGNakXCn.css +0 -1
  26. package/dist/web/dist/assets/index-DNznoLXb.js +0 -54
package/dist/index.js CHANGED
@@ -4094,7 +4094,6 @@ var init_schema = __esm(() => {
4094
4094
  });
4095
4095
  cronSchema = exports_external.object({
4096
4096
  enableScheduler: exports_external.boolean().default(true),
4097
- timezone: exports_external.string().default("UTC"),
4098
4097
  maxConcurrentJobs: exports_external.coerce.number().int().positive().default(5)
4099
4098
  });
4100
4099
  telegramChannelSchema = exports_external.object({
@@ -4360,7 +4359,6 @@ __export(exports_paths, {
4360
4359
  getEvalConfigPath: () => getEvalConfigPath,
4361
4360
  getDbPath: () => getDbPath,
4362
4361
  getDaemonPidPath: () => getDaemonPidPath,
4363
- getCronStorePath: () => getCronStorePath,
4364
4362
  getCredentialsPath: () => getCredentialsPath,
4365
4363
  getConfigPath: () => getConfigPath,
4366
4364
  getCliWorkspacePath: () => getCliWorkspacePath,
@@ -4405,9 +4403,6 @@ function getModelsConfigPath() {
4405
4403
  function getEvalConfigPath() {
4406
4404
  return join(getGhostDir(), "eval.json");
4407
4405
  }
4408
- function getCronStorePath() {
4409
- return join(getWorkspaceDir(), "cron", "jobs.json");
4410
- }
4411
4406
  function getCliWorkspacePath() {
4412
4407
  return join(getGhostDir(), "cli-workspace");
4413
4408
  }
@@ -4513,7 +4508,6 @@ __export(exports_config, {
4513
4508
  getEvalConfigPath: () => getEvalConfigPath,
4514
4509
  getDbPath: () => getDbPath,
4515
4510
  getDaemonPidPath: () => getDaemonPidPath,
4516
- getCronStorePath: () => getCronStorePath,
4517
4511
  getCredentialsPath: () => getCredentialsPath,
4518
4512
  getConfigPath: () => getConfigPath,
4519
4513
  getCliWorkspacePath: () => getCliWorkspacePath,
@@ -151269,6 +151263,34 @@ function formatOrder(order) {
151269
151263
  return `${order.symbol} ${formatSide(order.side)} ${order.type} ${order.size}${priceStr}${triggerStr}`;
151270
151264
  }
151271
151265
 
151266
+ // src/services/wizard-data.ts
151267
+ function composeOpenPositionWizard(input) {
151268
+ const symbol5 = input.symbol?.toUpperCase();
151269
+ if (!symbol5)
151270
+ return;
151271
+ if (typeof input.size !== "number" || !Number.isFinite(input.size))
151272
+ return;
151273
+ const side = input.side === "buy" || input.side === "long" ? "long" : "short";
151274
+ const leverage = input.leverage && input.leverage > 0 ? input.leverage : 1;
151275
+ const orderType = input.orderType === "limit" ? "limit" : "market";
151276
+ const hasEntry = typeof input.entryPrice === "number" && Number.isFinite(input.entryPrice) && input.entryPrice > 0;
151277
+ const entryPrice = hasEntry ? input.entryPrice : undefined;
151278
+ return {
151279
+ kind: "open_position",
151280
+ symbol: symbol5,
151281
+ side,
151282
+ leverage,
151283
+ size: input.size,
151284
+ orderType,
151285
+ entryPrice,
151286
+ stopLoss: input.stopLoss,
151287
+ takeProfit: input.takeProfit
151288
+ };
151289
+ }
151290
+ function composeGenericWizard(groups) {
151291
+ return { kind: "generic", groups };
151292
+ }
151293
+
151272
151294
  // src/services/confirm-policy.ts
151273
151295
  function isConfirmable(toolName) {
151274
151296
  return CONFIRMABLE_TOOLS.has(toolName);
@@ -151313,15 +151335,25 @@ function describePlaceOrder(params) {
151313
151335
  const sizeStr = size !== undefined ? `${size}` : "";
151314
151336
  const levSuffix = leverage && leverage > 0 ? ` ${leverage}x` : "";
151315
151337
  const sideRow = leverage && leverage > 0 ? `Side: ${side} ${leverage}x` : `Side: ${side}`;
151338
+ const wizard = composeOpenPositionWizard({
151339
+ symbol: getString(params, "symbol") ?? "",
151340
+ side: getString(params, "side") ?? "buy",
151341
+ size: getNumber(params, "size") ?? 0,
151342
+ leverage: getNumber(params, "leverage"),
151343
+ orderType: getString(params, "orderType"),
151344
+ entryPrice: price
151345
+ });
151316
151346
  if (orderType === "limit" && price !== undefined) {
151317
151347
  return {
151318
151348
  title: `Place limit order: ${side} ${sizeStr} ${symbol5} @ ${formatUsd(price)}?`,
151319
- bullets: [sideRow]
151349
+ bullets: [sideRow],
151350
+ wizard
151320
151351
  };
151321
151352
  }
151322
151353
  return {
151323
151354
  title: `Place market order: ${side} ${sizeStr} ${symbol5}?`,
151324
- bullets: [sideRow]
151355
+ bullets: [sideRow],
151356
+ wizard
151325
151357
  };
151326
151358
  }
151327
151359
  function describeBracketOrder(params) {
@@ -151329,8 +151361,9 @@ function describeBracketOrder(params) {
151329
151361
  const side = sideLabel(getString(params, "side"));
151330
151362
  const leverage = getNumber(params, "leverage");
151331
151363
  const size = getNumber(params, "size");
151332
- const orderType = getString(params, "orderType")?.toLowerCase();
151364
+ const rawOrderType = getString(params, "orderType")?.toLowerCase();
151333
151365
  const entryPrice = getNumber(params, "entryPrice") ?? getNumber(params, "price");
151366
+ const orderType = rawOrderType ?? (entryPrice !== undefined ? "limit" : "market");
151334
151367
  const stopLoss = getNumber(params, "stopLoss");
151335
151368
  const takeProfit = getNumber(params, "takeProfit");
151336
151369
  const sizeStr = size !== undefined ? `${size}` : "";
@@ -151343,7 +151376,17 @@ function describeBracketOrder(params) {
151343
151376
  bullets.push(`SL: ${formatUsdCompact(stopLoss)}`);
151344
151377
  if (takeProfit !== undefined)
151345
151378
  bullets.push(`TP: ${formatUsdCompact(takeProfit)}`);
151346
- return { title, bullets };
151379
+ const wizard = composeOpenPositionWizard({
151380
+ symbol: getString(params, "symbol") ?? "",
151381
+ side: getString(params, "side") ?? "buy",
151382
+ size: getNumber(params, "size") ?? 0,
151383
+ leverage: getNumber(params, "leverage"),
151384
+ orderType,
151385
+ entryPrice,
151386
+ stopLoss: getNumber(params, "stopLoss"),
151387
+ takeProfit: getNumber(params, "takeProfit")
151388
+ });
151389
+ return { title, bullets, wizard };
151347
151390
  }
151348
151391
  function describeSetSlTp(params) {
151349
151392
  const symbol5 = upper(getString(params, "symbol"));
@@ -151414,14 +151457,26 @@ function describePartialClose(params) {
151414
151457
  const symbol5 = upper(getString(params, "symbol"));
151415
151458
  const pct = getNumber(params, "percentage");
151416
151459
  const size = getNumber(params, "size");
151460
+ const sizeBefore = getNumber(params, "sizeBefore");
151461
+ const rows = [];
151462
+ if (pct !== undefined)
151463
+ rows.push({ label: "Close %", value: `${pct}%` });
151464
+ if (size !== undefined)
151465
+ rows.push({ label: "Size", value: `${size} ${symbol5}` });
151466
+ if (sizeBefore !== undefined && pct !== undefined) {
151467
+ const sizeAfter = sizeBefore * (1 - pct / 100);
151468
+ rows.push({ label: "Size before", value: `${sizeBefore}`, tone: "muted" });
151469
+ rows.push({ label: "Size after", value: `${sizeAfter}` });
151470
+ }
151471
+ const wizard = rows.length > 0 ? composeGenericWizard([{ label: `${symbol5} partial close`, rows }]) : undefined;
151417
151472
  if (pct !== undefined) {
151418
151473
  const pctStr = Number.isInteger(pct) ? `${pct}` : pct.toFixed(0);
151419
- return { title: `Close ${pctStr}% of ${symbol5} position?`, bullets: [] };
151474
+ return { title: `Close ${pctStr}% of ${symbol5} position?`, bullets: [], wizard };
151420
151475
  }
151421
151476
  if (size !== undefined) {
151422
- return { title: `Close ${size} ${symbol5} position?`, bullets: [] };
151477
+ return { title: `Close ${size} ${symbol5} position?`, bullets: [], wizard };
151423
151478
  }
151424
- return { title: `Close part of ${symbol5} position?`, bullets: [] };
151479
+ return { title: `Close part of ${symbol5} position?`, bullets: [], wizard };
151425
151480
  }
151426
151481
  function describeAdjustMargin(params) {
151427
151482
  const symbol5 = upper(getString(params, "symbol"));
@@ -151429,17 +151484,26 @@ function describeAdjustMargin(params) {
151429
151484
  if (amount === undefined) {
151430
151485
  return { title: `Adjust margin on ${symbol5}?`, bullets: [] };
151431
151486
  }
151487
+ const sign = amount >= 0 ? "+" : "\u2212";
151488
+ const rows = [
151489
+ {
151490
+ label: "Margin change",
151491
+ value: `${sign}${formatUsd(Math.abs(amount))}`,
151492
+ tone: amount >= 0 ? "reward" : "risk"
151493
+ }
151494
+ ];
151495
+ const wizard = composeGenericWizard([{ label: `${symbol5} margin adjust`, rows }]);
151432
151496
  if (amount >= 0) {
151433
- return { title: `Add ${formatUsd(amount)} margin to ${symbol5}?`, bullets: [] };
151497
+ return { title: `Add ${formatUsd(amount)} margin to ${symbol5}?`, bullets: [], wizard };
151434
151498
  }
151435
- return { title: `Reduce ${formatUsd(Math.abs(amount))} margin on ${symbol5}?`, bullets: [] };
151499
+ return { title: `Reduce ${formatUsd(Math.abs(amount))} margin on ${symbol5}?`, bullets: [], wizard };
151436
151500
  }
151437
151501
  function describeConfirm(toolName, params) {
151438
151502
  const safeParams = params && typeof params === "object" ? params : {};
151439
151503
  const describer = CONFIRM_DESCRIBERS[toolName];
151440
151504
  if (describer)
151441
151505
  return describer(safeParams);
151442
- return { title: `Confirm ${toolName}?`, bullets: [] };
151506
+ return { title: `Run ${toolName}?`, bullets: [] };
151443
151507
  }
151444
151508
  var CONFIRMABLE_TOOLS, CONFIRM_DESCRIBERS;
151445
151509
  var init_confirm_policy = __esm(() => {
@@ -151487,7 +151551,7 @@ function createGhostSdkMcpServer(deps) {
151487
151551
  }
151488
151552
  if (decision.decision === "rejected") {
151489
151553
  const reasonMsg = decision.reason && decision.reason.length > 0 ? `User declined. Reason: ${decision.reason}` : "User declined. Do not retry.";
151490
- logger.debug({ tool: agentTool.name, reason: decision.reason }, "tool rejected by user");
151554
+ logger.debug({ tool: agentTool.name, reason: decision.reason }, "tool not approved");
151491
151555
  return { content: [{ type: "text", text: reasonMsg }], isError: true };
151492
151556
  }
151493
151557
  }
@@ -151532,7 +151596,36 @@ var init_constants2 = __esm(() => {
151532
151596
  "read_file",
151533
151597
  "list_dir",
151534
151598
  "web_fetch",
151535
- "web_search"
151599
+ "web_search",
151600
+ "ghost_chat_history",
151601
+ "ghost_get_trade_history",
151602
+ "ghost_get_balance",
151603
+ "ghost_get_positions",
151604
+ "ghost_get_orders",
151605
+ "ghost_list_wallets",
151606
+ "ghost_get_recent_orders",
151607
+ "ghost_get_price",
151608
+ "ghost_get_funding_rates",
151609
+ "ghost_get_orderbook",
151610
+ "ghost_get_klines",
151611
+ "ghost_get_indicators",
151612
+ "ghost_get_levels",
151613
+ "ghost_market_overview",
151614
+ "ghost_pre_trade_check",
151615
+ "ghost_morning_briefing",
151616
+ "ghost_session_info",
151617
+ "ghost_timing_risk",
151618
+ "ghost_cross_exchange_funding",
151619
+ "ghost_liquidation_map",
151620
+ "ghost_get_whale_activity",
151621
+ "ghost_tweets_search",
151622
+ "ghost_news_sources",
151623
+ "ghost_news_search",
151624
+ "ghost_news_discover_rss",
151625
+ "ghost_watchlist_list",
151626
+ "ghost_alert_list",
151627
+ "ghost_alert_history",
151628
+ "ghost_check_alerts"
151536
151629
  ]);
151537
151630
  });
151538
151631
 
@@ -156179,14 +156272,6 @@ function createClaudeCliModel(modelId) {
156179
156272
  maxTokens: known?.maxTokens ?? DEFAULT_SPECS.maxTokens
156180
156273
  };
156181
156274
  }
156182
- function getClaudeCliModels() {
156183
- return [
156184
- { id: "claude-opus-4-7", name: "Claude Opus 4.7" },
156185
- { id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6" },
156186
- { id: "claude-haiku-4-5", name: "Claude Haiku 4.5" },
156187
- { id: "claude-opus-4-6", name: "Claude Opus 4.6" }
156188
- ];
156189
- }
156190
156275
  var KNOWN_MODELS, DEFAULT_SPECS;
156191
156276
  var init_models6 = __esm(() => {
156192
156277
  KNOWN_MODELS = {
@@ -156485,7 +156570,7 @@ function assertValidVersions(sorted) {
156485
156570
  }
156486
156571
  }
156487
156572
  }
156488
- var baselineDbMigration, proactiveCooldownsMigration, dropProactiveCooldownsMigration, alertsKindPayloadMigration, addSettingsKvMigration, addTweetsAiRelevantMigration, addObserverStateMigration, splitAlertsMigration, addXFollowsEnabledSourceMigration, DB_MIGRATIONS, CONFIG_MIGRATIONS;
156573
+ var baselineDbMigration, proactiveCooldownsMigration, dropProactiveCooldownsMigration, alertsKindPayloadMigration, addSettingsKvMigration, addTweetsAiRelevantMigration, addObserverStateMigration, splitAlertsMigration, addXFollowsEnabledSourceMigration, addCronJobsMigration, DB_MIGRATIONS, CONFIG_MIGRATIONS;
156489
156574
  var init_registry2 = __esm(() => {
156490
156575
  baselineDbMigration = {
156491
156576
  version: 1,
@@ -156618,6 +156703,42 @@ var init_registry2 = __esm(() => {
156618
156703
  db2.run(`UPDATE x_follows SET user_disabled = 0 WHERE user_disabled IS NULL`);
156619
156704
  }
156620
156705
  };
156706
+ addCronJobsMigration = {
156707
+ version: 10,
156708
+ label: "add_cron_jobs",
156709
+ up: (db2) => {
156710
+ db2.run(`
156711
+ CREATE TABLE IF NOT EXISTS cron_jobs (
156712
+ id TEXT PRIMARY KEY,
156713
+ name TEXT NOT NULL,
156714
+ enabled INTEGER NOT NULL DEFAULT 1,
156715
+ schedule_kind TEXT NOT NULL,
156716
+ schedule_at_ms INTEGER,
156717
+ schedule_every_ms INTEGER,
156718
+ schedule_expr TEXT,
156719
+ schedule_tz TEXT,
156720
+ payload_kind TEXT NOT NULL DEFAULT 'agent_turn',
156721
+ payload_message TEXT NOT NULL,
156722
+ payload_deliver INTEGER NOT NULL DEFAULT 1,
156723
+ payload_channel TEXT,
156724
+ payload_to TEXT,
156725
+ next_run_at_ms INTEGER,
156726
+ last_run_at_ms INTEGER,
156727
+ last_status TEXT,
156728
+ last_error TEXT,
156729
+ run_history TEXT NOT NULL DEFAULT '[]',
156730
+ created_at_ms INTEGER NOT NULL,
156731
+ updated_at_ms INTEGER NOT NULL,
156732
+ delete_after_run INTEGER NOT NULL DEFAULT 0
156733
+ );
156734
+ `);
156735
+ db2.run(`CREATE UNIQUE INDEX IF NOT EXISTS idx_cron_jobs_name ON cron_jobs(name);`);
156736
+ db2.run(`
156737
+ CREATE INDEX IF NOT EXISTS idx_cron_jobs_enabled_next
156738
+ ON cron_jobs(enabled, next_run_at_ms) WHERE enabled = 1;
156739
+ `);
156740
+ }
156741
+ };
156621
156742
  DB_MIGRATIONS = [
156622
156743
  baselineDbMigration,
156623
156744
  proactiveCooldownsMigration,
@@ -156627,7 +156748,8 @@ var init_registry2 = __esm(() => {
156627
156748
  addTweetsAiRelevantMigration,
156628
156749
  addObserverStateMigration,
156629
156750
  splitAlertsMigration,
156630
- addXFollowsEnabledSourceMigration
156751
+ addXFollowsEnabledSourceMigration,
156752
+ addCronJobsMigration
156631
156753
  ];
156632
156754
  CONFIG_MIGRATIONS = [];
156633
156755
  });
@@ -157957,6 +158079,9 @@ class ToolRegistry {
157957
158079
  all() {
157958
158080
  return [...this.tools.values()];
157959
158081
  }
158082
+ taskAgentTools() {
158083
+ return [...this.tools.values()].filter((t2) => READ_TOOLS.has(t2.name) || TASK_AGENT_EXTRAS.has(t2.name));
158084
+ }
157960
158085
  names() {
157961
158086
  return [...this.tools.keys()];
157962
158087
  }
@@ -158003,7 +158128,11 @@ class ToolRegistry {
158003
158128
  return { ...result, content: truncated };
158004
158129
  }
158005
158130
  }
158006
- var MAX_RESULT_CHARS = 16000;
158131
+ var MAX_RESULT_CHARS = 16000, TASK_AGENT_EXTRAS;
158132
+ var init_registry3 = __esm(() => {
158133
+ init_constants2();
158134
+ TASK_AGENT_EXTRAS = new Set(["save_memory", "cron"]);
158135
+ });
158007
158136
 
158008
158137
  // src/tools/read-file.ts
158009
158138
  import { readFileSync as readFileSync11, statSync as statSync2 } from "fs";
@@ -158630,7 +158759,7 @@ var init_web_fetch = __esm(() => {
158630
158759
  // src/tools/cron.ts
158631
158760
  class CronTool {
158632
158761
  service;
158633
- defaultTz;
158762
+ tzService;
158634
158763
  name = "cron";
158635
158764
  label = "Cron";
158636
158765
  description = "Schedule reminders and recurring tasks. Actions: add, list, remove.";
@@ -158638,9 +158767,9 @@ class CronTool {
158638
158767
  _channel = null;
158639
158768
  _chatId = null;
158640
158769
  _inCron = false;
158641
- constructor(service, defaultTz = "UTC") {
158770
+ constructor(service, tzService) {
158642
158771
  this.service = service;
158643
- this.defaultTz = defaultTz;
158772
+ this.tzService = tzService;
158644
158773
  }
158645
158774
  setOrigin(channel, chatId) {
158646
158775
  this._channel = channel || null;
@@ -158674,7 +158803,7 @@ class CronTool {
158674
158803
  if (params.every_seconds) {
158675
158804
  schedule = { kind: "every", everyMs: params.every_seconds * 1000 };
158676
158805
  } else if (params.cron_expr) {
158677
- schedule = { kind: "cron", expr: params.cron_expr, tz: params.tz ?? this.defaultTz };
158806
+ schedule = { kind: "cron", expr: params.cron_expr, tz: params.tz ?? this.tzService.get() };
158678
158807
  } else if (params.at) {
158679
158808
  const atMs = new Date(params.at).getTime();
158680
158809
  if (isNaN(atMs))
@@ -158791,11 +158920,12 @@ function createToolRegistry(_security, options) {
158791
158920
  reg(new ExecTool);
158792
158921
  reg(new WebSearchTool(options.webSearchConfig));
158793
158922
  reg(new WebFetchTool);
158794
- reg(new CronTool(options.cronService, options.defaultTimezone));
158923
+ reg(new CronTool(options.cronService, options.timezoneService));
158795
158924
  reg(new SaveMemoryTool(options.memoryStore));
158796
158925
  return registry4;
158797
158926
  }
158798
158927
  var init_tools = __esm(() => {
158928
+ init_registry3();
158799
158929
  init_read_file();
158800
158930
  init_write_file();
158801
158931
  init_edit_file();
@@ -158805,6 +158935,7 @@ var init_tools = __esm(() => {
158805
158935
  init_web_fetch();
158806
158936
  init_cron();
158807
158937
  init_save_memory();
158938
+ init_registry3();
158808
158939
  init_read_file();
158809
158940
  init_write_file();
158810
158941
  init_edit_file();
@@ -159684,7 +159815,7 @@ class ContextBuilder {
159684
159815
  return prompt.toString();
159685
159816
  }
159686
159817
  buildRuntimeContext(channel, chatId) {
159687
- const tz = this.config.timezone ?? "UTC";
159818
+ const tz = this.config.getTimezone?.() ?? "UTC";
159688
159819
  const now = new Date;
159689
159820
  const timeStr = now.toLocaleString("en-US", {
159690
159821
  timeZone: tz,
@@ -168553,45 +168684,63 @@ var require_dist5 = __commonJS((exports) => {
168553
168684
  exports.default = CronExpressionParser_1.CronExpressionParser;
168554
168685
  });
168555
168686
 
168556
- // src/scheduler/defaults.ts
168557
- function detectUserTimezone() {
168558
- try {
168559
- return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
168560
- } catch {
168561
- return "UTC";
168687
+ // src/scheduler/storage.ts
168688
+ function scheduleFromRow(row) {
168689
+ switch (row.schedule_kind) {
168690
+ case "at":
168691
+ return { kind: "at", atMs: row.schedule_at_ms ?? undefined };
168692
+ case "every":
168693
+ return { kind: "every", everyMs: row.schedule_every_ms ?? undefined };
168694
+ case "cron":
168695
+ return {
168696
+ kind: "cron",
168697
+ expr: row.schedule_expr ?? undefined,
168698
+ tz: row.schedule_tz ?? undefined
168699
+ };
168700
+ default:
168701
+ return { kind: "cron" };
168562
168702
  }
168563
168703
  }
168564
- var BRIEFING_PROMPT, RECAP_PROMPT, BUILT_IN_JOBS;
168565
- var init_defaults = __esm(() => {
168566
- BRIEFING_PROMPT = "Run the morning briefing. Call tools to fetch the latest data: open " + "positions, recent fills / trade history, watchlist, news, and market " + "signals (funding, whale activity, fear & greed). Summarize in under 15 " + "sentences, in the language the user has been chatting in.";
168567
- RECAP_PROMPT = "Run the end-of-day recap. Call tools to fetch today's trade history and " + "current open positions. Summarize today's PnL, position changes (opened, " + "closed, scaled), and one notable market note. Brief one-liner if I had no " + "activity. Reply in the language the user has been chatting in.";
168568
- BUILT_IN_JOBS = [
168569
- {
168570
- name: "morning-briefing",
168571
- schedule: {
168572
- kind: "cron",
168573
- expr: "0 8 * * *",
168574
- tz: detectUserTimezone()
168575
- },
168576
- message: BRIEFING_PROMPT,
168577
- deliver: true
168704
+ function rowToJob(row) {
168705
+ let runHistory = [];
168706
+ try {
168707
+ runHistory = JSON.parse(row.run_history);
168708
+ } catch {}
168709
+ return {
168710
+ id: row.id,
168711
+ name: row.name,
168712
+ enabled: row.enabled === 1,
168713
+ schedule: scheduleFromRow(row),
168714
+ payload: {
168715
+ kind: "agent_turn",
168716
+ message: row.payload_message,
168717
+ deliver: row.payload_deliver === 1,
168718
+ channel: row.payload_channel ?? undefined,
168719
+ to: row.payload_to ?? undefined
168578
168720
  },
168579
- {
168580
- name: "evening-recap",
168581
- schedule: {
168582
- kind: "cron",
168583
- expr: "0 21 * * *",
168584
- tz: detectUserTimezone()
168585
- },
168586
- message: RECAP_PROMPT,
168587
- deliver: true
168588
- }
168589
- ];
168590
- });
168721
+ state: {
168722
+ nextRunAtMs: row.next_run_at_ms,
168723
+ lastRunAtMs: row.last_run_at_ms,
168724
+ lastStatus: row.last_status ?? null,
168725
+ lastError: row.last_error,
168726
+ runHistory
168727
+ },
168728
+ createdAtMs: row.created_at_ms,
168729
+ updatedAtMs: row.updated_at_ms,
168730
+ deleteAfterRun: row.delete_after_run === 1
168731
+ };
168732
+ }
168733
+ function scheduleBindings(s2) {
168734
+ return {
168735
+ schedule_kind: s2.kind,
168736
+ schedule_at_ms: s2.atMs ?? null,
168737
+ schedule_every_ms: s2.everyMs ?? null,
168738
+ schedule_expr: s2.expr ?? null,
168739
+ schedule_tz: s2.tz ?? null
168740
+ };
168741
+ }
168591
168742
 
168592
168743
  // src/scheduler/service.ts
168593
- import { existsSync as existsSync17, readFileSync as readFileSync16, writeFileSync as writeFileSync10, mkdirSync as mkdirSync13, statSync as statSync4 } from "fs";
168594
- import { dirname as dirname8 } from "path";
168595
168744
  function computeNextRun(schedule, nowMs) {
168596
168745
  switch (schedule.kind) {
168597
168746
  case "at":
@@ -168618,259 +168767,292 @@ function computeNextRun(schedule, nowMs) {
168618
168767
  return null;
168619
168768
  }
168620
168769
  }
168621
-
168622
- class CronService {
168623
- storePath;
168624
- store = { version: 1, jobs: [] };
168625
- lastMtime = 0;
168626
- timerHandle = null;
168627
- _running = false;
168628
- onJob;
168629
- constructor(storePath) {
168630
- this.storePath = storePath;
168631
- }
168632
- setOnJob(fn) {
168633
- this.onJob = fn;
168634
- }
168635
- start(opts = {}) {
168636
- this._running = true;
168637
- this.loadStore();
168638
- this.seedDefaultJobs(opts.defaults ?? BUILT_IN_JOBS);
168639
- const now = Date.now();
168640
- for (const job of this.store.jobs) {
168641
- if (!job.enabled)
168642
- continue;
168643
- if (job.state.nextRunAtMs !== null && job.state.nextRunAtMs <= now && (job.schedule.kind === "cron" || job.schedule.kind === "at")) {
168644
- job.state.nextRunAtMs = computeNextRun(job.schedule, now);
168645
- } else if (!job.state.nextRunAtMs) {
168646
- job.state.nextRunAtMs = computeNextRun(job.schedule, now);
168770
+ var import_cron_parser, MAX_HISTORY = 20, MIN_INTERVAL_MS = 1e4, CronService;
168771
+ var init_service = __esm(() => {
168772
+ import_cron_parser = __toESM(require_dist5(), 1);
168773
+ CronService = class CronService {
168774
+ db;
168775
+ stmts;
168776
+ timerHandle = null;
168777
+ _running = false;
168778
+ onJob;
168779
+ static DEFAULT_JOB_NAMES = ["morning-briefing", "evening-recap"];
168780
+ constructor(db2) {
168781
+ this.db = db2;
168782
+ this.stmts = {
168783
+ selectAll: db2.prepare("SELECT * FROM cron_jobs"),
168784
+ selectById: db2.prepare("SELECT * FROM cron_jobs WHERE id = ?"),
168785
+ selectByName: db2.prepare("SELECT * FROM cron_jobs WHERE name = ?"),
168786
+ insert: db2.prepare(`
168787
+ INSERT INTO cron_jobs (
168788
+ id, name, enabled,
168789
+ schedule_kind, schedule_at_ms, schedule_every_ms, schedule_expr, schedule_tz,
168790
+ payload_kind, payload_message, payload_deliver, payload_channel, payload_to,
168791
+ next_run_at_ms, last_run_at_ms, last_status, last_error,
168792
+ run_history, created_at_ms, updated_at_ms, delete_after_run
168793
+ ) VALUES (
168794
+ $id, $name, $enabled,
168795
+ $schedule_kind, $schedule_at_ms, $schedule_every_ms, $schedule_expr, $schedule_tz,
168796
+ $payload_kind, $payload_message, $payload_deliver, $payload_channel, $payload_to,
168797
+ $next_run_at_ms, NULL, NULL, NULL,
168798
+ '[]', $created_at_ms, $updated_at_ms, $delete_after_run
168799
+ )
168800
+ `),
168801
+ updateFull: db2.prepare(`
168802
+ UPDATE cron_jobs SET
168803
+ last_run_at_ms = $last_run_at_ms,
168804
+ last_status = $last_status,
168805
+ last_error = $last_error,
168806
+ next_run_at_ms = $next_run_at_ms,
168807
+ run_history = $run_history,
168808
+ enabled = $enabled,
168809
+ updated_at_ms = $updated_at_ms
168810
+ WHERE id = $id
168811
+ `),
168812
+ updateNext: db2.prepare("UPDATE cron_jobs SET next_run_at_ms = ?, updated_at_ms = ? WHERE id = ?"),
168813
+ updateTzAndNext: db2.prepare("UPDATE cron_jobs SET schedule_tz = ?, next_run_at_ms = ?, updated_at_ms = ? WHERE name = ?"),
168814
+ updateEnabled: db2.prepare("UPDATE cron_jobs SET enabled = ?, next_run_at_ms = ?, updated_at_ms = ? WHERE id = ?"),
168815
+ delete: db2.prepare("DELETE FROM cron_jobs WHERE id = ?")
168816
+ };
168817
+ }
168818
+ setOnJob(fn) {
168819
+ this.onJob = fn;
168820
+ }
168821
+ start(opts = {}) {
168822
+ this._running = true;
168823
+ this.seedDefaultJobs(opts.defaults ?? []);
168824
+ const now = Date.now();
168825
+ const rows = this.stmts.selectAll.all();
168826
+ this.db.transaction(() => {
168827
+ for (const row of rows) {
168828
+ if (row.enabled === 1 && row.next_run_at_ms !== null && row.next_run_at_ms <= now) {
168829
+ const schedule = scheduleFromRow(row);
168830
+ const next = computeNextRun(schedule, now);
168831
+ this.stmts.updateNext.run(next, now, row.id);
168832
+ } else if (row.next_run_at_ms === null && row.enabled) {
168833
+ const schedule = scheduleFromRow(row);
168834
+ const next = computeNextRun(schedule, now);
168835
+ this.stmts.updateNext.run(next, now, row.id);
168836
+ }
168837
+ }
168838
+ })();
168839
+ this.scheduleNextTick();
168840
+ }
168841
+ stop() {
168842
+ this._running = false;
168843
+ if (this.timerHandle) {
168844
+ clearTimeout(this.timerHandle);
168845
+ this.timerHandle = null;
168647
168846
  }
168648
168847
  }
168649
- this.saveStore();
168650
- this.armTimer();
168651
- }
168652
- stop() {
168653
- this._running = false;
168654
- if (this.timerHandle) {
168655
- clearTimeout(this.timerHandle);
168656
- this.timerHandle = null;
168848
+ listJobs(includeDisabled = false) {
168849
+ const rows = this.stmts.selectAll.all();
168850
+ const jobs = includeDisabled ? rows.map(rowToJob) : rows.filter((r2) => r2.enabled === 1).map(rowToJob);
168851
+ return jobs.sort((a, b5) => (a.state.nextRunAtMs ?? Infinity) - (b5.state.nextRunAtMs ?? Infinity));
168657
168852
  }
168658
- }
168659
- listJobs(includeDisabled = false) {
168660
- this.loadStore();
168661
- const jobs = includeDisabled ? this.store.jobs : this.store.jobs.filter((j9) => j9.enabled);
168662
- return jobs.sort((a, b5) => (a.state.nextRunAtMs ?? Infinity) - (b5.state.nextRunAtMs ?? Infinity));
168663
- }
168664
- addJob(opts) {
168665
- this.loadStore();
168666
- const now = Date.now();
168667
- const job = {
168668
- id: crypto.randomUUID().slice(0, 8),
168669
- name: opts.name,
168670
- enabled: true,
168671
- schedule: opts.schedule,
168672
- payload: {
168673
- kind: "agent_turn",
168674
- message: opts.message,
168675
- deliver: opts.deliver ?? true,
168676
- channel: opts.channel,
168677
- to: opts.to
168678
- },
168679
- state: {
168680
- nextRunAtMs: computeNextRun(opts.schedule, now),
168681
- lastRunAtMs: null,
168682
- lastStatus: null,
168683
- lastError: null,
168684
- runHistory: []
168685
- },
168686
- createdAtMs: now,
168687
- updatedAtMs: now,
168688
- deleteAfterRun: opts.deleteAfterRun ?? false
168689
- };
168690
- this.store.jobs.push(job);
168691
- this.saveStore();
168692
- this.armTimer();
168693
- return job;
168694
- }
168695
- removeJob(jobId) {
168696
- this.loadStore();
168697
- const idx = this.store.jobs.findIndex((j9) => j9.id === jobId);
168698
- if (idx === -1)
168699
- return false;
168700
- this.store.jobs.splice(idx, 1);
168701
- this.saveStore();
168702
- this.armTimer();
168703
- return true;
168704
- }
168705
- enableJob(jobId, enabled) {
168706
- this.loadStore();
168707
- const job = this.store.jobs.find((j9) => j9.id === jobId);
168708
- if (!job)
168709
- return;
168710
- job.enabled = enabled;
168711
- if (enabled) {
168712
- job.state.nextRunAtMs = computeNextRun(job.schedule, Date.now());
168713
- } else {
168714
- job.state.nextRunAtMs = null;
168853
+ addJob(opts) {
168854
+ const now = Date.now();
168855
+ const id = crypto.randomUUID().slice(0, 12);
168856
+ const sb = scheduleBindings(opts.schedule);
168857
+ try {
168858
+ this.stmts.insert.run({
168859
+ $id: id,
168860
+ $name: opts.name,
168861
+ $enabled: 1,
168862
+ ...Object.fromEntries(Object.entries(sb).map(([k, v4]) => [`$${k}`, v4])),
168863
+ $payload_kind: "agent_turn",
168864
+ $payload_message: opts.message,
168865
+ $payload_deliver: opts.deliver ?? true ? 1 : 0,
168866
+ $payload_channel: opts.channel ?? null,
168867
+ $payload_to: opts.to ?? null,
168868
+ $next_run_at_ms: computeNextRun(opts.schedule, now),
168869
+ $created_at_ms: now,
168870
+ $updated_at_ms: now,
168871
+ $delete_after_run: opts.deleteAfterRun ?? false ? 1 : 0
168872
+ });
168873
+ } catch (err) {
168874
+ const msg = err instanceof Error ? err.message : String(err);
168875
+ if (msg.includes("UNIQUE constraint failed") || msg.includes("SQLITE_CONSTRAINT")) {
168876
+ throw new Error(`Cron name already exists: ${opts.name}`);
168877
+ }
168878
+ throw err;
168879
+ }
168880
+ this.scheduleNextTick();
168881
+ const row = this.stmts.selectById.get(id);
168882
+ return rowToJob(row);
168715
168883
  }
168716
- job.updatedAtMs = Date.now();
168717
- this.saveStore();
168718
- this.armTimer();
168719
- }
168720
- async runJob(jobId, force = false) {
168721
- this.loadStore();
168722
- const job = this.store.jobs.find((j9) => j9.id === jobId);
168723
- if (!job)
168724
- return;
168725
- if (!job.enabled && !force)
168726
- return;
168727
- await this.executeJob(job);
168728
- this.saveStore();
168729
- this.armTimer();
168730
- }
168731
- getJob(jobId) {
168732
- this.loadStore();
168733
- return this.store.jobs.find((j9) => j9.id === jobId);
168734
- }
168735
- status() {
168736
- this.loadStore();
168737
- return {
168738
- enabled: this._running,
168739
- jobs: this.store.jobs.filter((j9) => j9.enabled).length,
168740
- nextWakeAtMs: this.getNextWakeMs()
168741
- };
168742
- }
168743
- seedDefaultJobs(specs) {
168744
- const existingNames = new Set(this.store.jobs.map((j9) => j9.name));
168745
- let dirty = false;
168746
- const now = Date.now();
168747
- for (const spec of specs) {
168748
- if (existingNames.has(spec.name))
168749
- continue;
168750
- const job = {
168751
- id: crypto.randomUUID().slice(0, 8),
168752
- name: spec.name,
168753
- enabled: true,
168754
- schedule: spec.schedule,
168755
- payload: {
168756
- kind: "agent_turn",
168757
- message: spec.message,
168758
- deliver: spec.deliver
168759
- },
168760
- state: {
168761
- nextRunAtMs: computeNextRun(spec.schedule, now),
168762
- lastRunAtMs: null,
168763
- lastStatus: null,
168764
- lastError: null,
168765
- runHistory: []
168766
- },
168767
- createdAtMs: now,
168768
- updatedAtMs: now,
168769
- deleteAfterRun: false
168884
+ removeJob(jobId) {
168885
+ const row = this.stmts.selectById.get(jobId);
168886
+ if (!row)
168887
+ return false;
168888
+ this.stmts.delete.run(jobId);
168889
+ this.scheduleNextTick();
168890
+ return true;
168891
+ }
168892
+ enableJob(jobId, enabled) {
168893
+ const now = Date.now();
168894
+ const row = this.stmts.selectById.get(jobId);
168895
+ if (!row)
168896
+ return;
168897
+ const nextRun = enabled ? computeNextRun(scheduleFromRow(row), now) : null;
168898
+ this.stmts.updateEnabled.run(enabled ? 1 : 0, nextRun, now, jobId);
168899
+ this.scheduleNextTick();
168900
+ }
168901
+ async runJob(jobId, force = false) {
168902
+ const row = this.stmts.selectById.get(jobId);
168903
+ if (!row)
168904
+ return;
168905
+ const job = rowToJob(row);
168906
+ if (!job.enabled && !force)
168907
+ return;
168908
+ await this.executeJob(job);
168909
+ }
168910
+ getJob(jobId) {
168911
+ const row = this.stmts.selectById.get(jobId);
168912
+ return row ? rowToJob(row) : undefined;
168913
+ }
168914
+ status() {
168915
+ const rows = this.stmts.selectAll.all();
168916
+ const enabled = rows.filter((r2) => r2.enabled === 1);
168917
+ return {
168918
+ enabled: this._running,
168919
+ jobs: enabled.length,
168920
+ nextTickAtMs: this.getNextTickMs()
168770
168921
  };
168771
- this.store.jobs.push(job);
168772
- dirty = true;
168773
168922
  }
168774
- if (dirty)
168775
- this.saveStore();
168776
- }
168777
- async executeJob(job) {
168778
- const startMs = Date.now();
168779
- let status = "ok";
168780
- let error45;
168781
- try {
168782
- if (this.onJob) {
168783
- await this.onJob(job);
168923
+ updateBuiltinJobsTimezone(tz) {
168924
+ const updated = [];
168925
+ const now = Date.now();
168926
+ this.db.transaction(() => {
168927
+ for (const name of CronService.DEFAULT_JOB_NAMES) {
168928
+ const row = this.stmts.selectByName.get(name);
168929
+ if (!row || row.schedule_kind !== "cron")
168930
+ continue;
168931
+ const nextRun = computeNextRun({ kind: "cron", expr: row.schedule_expr ?? undefined, tz }, now);
168932
+ this.stmts.updateTzAndNext.run(tz, nextRun, now, name);
168933
+ updated.push(name);
168934
+ }
168935
+ })();
168936
+ this.scheduleNextTick();
168937
+ return updated;
168938
+ }
168939
+ seedDefaultJobs(specs) {
168940
+ const now = Date.now();
168941
+ this.db.transaction(() => {
168942
+ for (const spec of specs) {
168943
+ const existing = this.stmts.selectByName.get(spec.name);
168944
+ if (existing)
168945
+ continue;
168946
+ const id = crypto.randomUUID().slice(0, 12);
168947
+ const sb = scheduleBindings(spec.schedule);
168948
+ this.stmts.insert.run({
168949
+ $id: id,
168950
+ $name: spec.name,
168951
+ $enabled: 1,
168952
+ ...Object.fromEntries(Object.entries(sb).map(([k, v4]) => [`$${k}`, v4])),
168953
+ $payload_kind: "agent_turn",
168954
+ $payload_message: spec.message,
168955
+ $payload_deliver: spec.deliver ? 1 : 0,
168956
+ $payload_channel: null,
168957
+ $payload_to: null,
168958
+ $next_run_at_ms: computeNextRun(spec.schedule, now),
168959
+ $created_at_ms: now,
168960
+ $updated_at_ms: now,
168961
+ $delete_after_run: 0
168962
+ });
168963
+ }
168964
+ })();
168965
+ }
168966
+ async executeJob(job) {
168967
+ const startMs = Date.now();
168968
+ let status = "ok";
168969
+ let error45;
168970
+ try {
168971
+ if (this.onJob) {
168972
+ await this.onJob(job);
168973
+ }
168974
+ } catch (err) {
168975
+ status = "error";
168976
+ error45 = err instanceof Error ? err.message : String(err);
168977
+ }
168978
+ const durationMs = Date.now() - startMs;
168979
+ const record6 = { runAtMs: startMs, status, durationMs, error: error45 };
168980
+ const currentRow = this.stmts.selectById.get(job.id);
168981
+ let runHistory = [];
168982
+ if (currentRow) {
168983
+ try {
168984
+ runHistory = JSON.parse(currentRow.run_history);
168985
+ } catch {}
168784
168986
  }
168785
- } catch (err) {
168786
- status = "error";
168787
- error45 = err instanceof Error ? err.message : String(err);
168788
- }
168789
- const durationMs = Date.now() - startMs;
168790
- const record6 = { runAtMs: startMs, status, durationMs, error: error45 };
168791
- job.state.lastRunAtMs = startMs;
168792
- job.state.lastStatus = status;
168793
- job.state.lastError = error45 ?? null;
168794
- job.state.runHistory.push(record6);
168795
- if (job.state.runHistory.length > MAX_HISTORY) {
168796
- job.state.runHistory = job.state.runHistory.slice(-MAX_HISTORY);
168797
- }
168798
- if (job.schedule.kind === "at") {
168799
- if (job.deleteAfterRun) {
168800
- const idx = this.store.jobs.indexOf(job);
168801
- if (idx >= 0)
168802
- this.store.jobs.splice(idx, 1);
168803
- } else {
168804
- job.enabled = false;
168805
- job.state.nextRunAtMs = null;
168987
+ runHistory.push(record6);
168988
+ if (runHistory.length > MAX_HISTORY) {
168989
+ runHistory = runHistory.slice(-MAX_HISTORY);
168806
168990
  }
168807
- } else {
168808
- job.state.nextRunAtMs = computeNextRun(job.schedule, Date.now());
168991
+ let nextRunAtMs;
168992
+ let enabled = 1;
168993
+ if (job.schedule.kind === "at") {
168994
+ if (job.deleteAfterRun) {
168995
+ this.stmts.delete.run(job.id);
168996
+ this.scheduleNextTick();
168997
+ return;
168998
+ }
168999
+ enabled = 0;
169000
+ nextRunAtMs = null;
169001
+ } else {
169002
+ const freshSchedule = currentRow ? scheduleFromRow(currentRow) : job.schedule;
169003
+ nextRunAtMs = computeNextRun(freshSchedule, Date.now());
169004
+ }
169005
+ this.db.transaction(() => {
169006
+ this.stmts.updateFull.run({
169007
+ $last_run_at_ms: startMs,
169008
+ $last_status: status,
169009
+ $last_error: error45 ?? null,
169010
+ $next_run_at_ms: nextRunAtMs,
169011
+ $run_history: JSON.stringify(runHistory),
169012
+ $enabled: enabled,
169013
+ $updated_at_ms: Date.now(),
169014
+ $id: job.id
169015
+ });
169016
+ })();
169017
+ this.scheduleNextTick();
168809
169018
  }
168810
- job.updatedAtMs = Date.now();
168811
- }
168812
- getNextWakeMs() {
168813
- let earliest = null;
168814
- for (const job of this.store.jobs) {
168815
- if (job.enabled && job.state.nextRunAtMs) {
168816
- if (earliest === null || job.state.nextRunAtMs < earliest) {
168817
- earliest = job.state.nextRunAtMs;
169019
+ getNextTickMs() {
169020
+ const rows = this.stmts.selectAll.all();
169021
+ let earliest = null;
169022
+ for (const row of rows) {
169023
+ if (row.enabled === 1 && row.next_run_at_ms !== null) {
169024
+ if (earliest === null || row.next_run_at_ms < earliest) {
169025
+ earliest = row.next_run_at_ms;
169026
+ }
168818
169027
  }
168819
169028
  }
169029
+ return earliest;
168820
169030
  }
168821
- return earliest;
168822
- }
168823
- armTimer() {
168824
- if (this.timerHandle) {
168825
- clearTimeout(this.timerHandle);
168826
- this.timerHandle = null;
168827
- }
168828
- if (!this._running)
168829
- return;
168830
- const nextMs = this.getNextWakeMs();
168831
- if (nextMs === null)
168832
- return;
168833
- const delayMs = Math.max(nextMs - Date.now(), 0);
168834
- this.timerHandle = setTimeout(() => this.onTimer(), delayMs);
168835
- }
168836
- async onTimer() {
168837
- if (!this._running)
168838
- return;
168839
- this.loadStore();
168840
- const now = Date.now();
168841
- const due = this.store.jobs.filter((j9) => j9.enabled && j9.state.nextRunAtMs !== null && j9.state.nextRunAtMs <= now);
168842
- for (const job of due) {
168843
- await this.executeJob(job);
169031
+ scheduleNextTick() {
169032
+ if (this.timerHandle) {
169033
+ clearTimeout(this.timerHandle);
169034
+ this.timerHandle = null;
169035
+ }
169036
+ if (!this._running)
169037
+ return;
169038
+ const nextMs = this.getNextTickMs();
169039
+ if (nextMs === null)
169040
+ return;
169041
+ const delayMs = Math.max(nextMs - Date.now(), 0);
169042
+ this.timerHandle = setTimeout(() => void this.onTimer(), delayMs);
168844
169043
  }
168845
- this.saveStore();
168846
- this.armTimer();
168847
- }
168848
- loadStore() {
168849
- if (!existsSync17(this.storePath))
168850
- return;
168851
- try {
168852
- const stat2 = statSync4(this.storePath);
168853
- const mtime = stat2.mtimeMs;
168854
- if (mtime === this.lastMtime && this.store.jobs.length > 0)
169044
+ async onTimer() {
169045
+ if (!this._running)
168855
169046
  return;
168856
- const raw = readFileSync16(this.storePath, "utf-8");
168857
- const parsed = JSON.parse(raw);
168858
- this.store = parsed;
168859
- this.lastMtime = mtime;
168860
- } catch {}
168861
- }
168862
- saveStore() {
168863
- mkdirSync13(dirname8(this.storePath), { recursive: true });
168864
- writeFileSync10(this.storePath, JSON.stringify(this.store, null, 2));
168865
- try {
168866
- this.lastMtime = statSync4(this.storePath).mtimeMs;
168867
- } catch {}
168868
- }
168869
- }
168870
- var import_cron_parser, MAX_HISTORY = 20, MIN_INTERVAL_MS = 1e4;
168871
- var init_service = __esm(() => {
168872
- init_defaults();
168873
- import_cron_parser = __toESM(require_dist5(), 1);
169047
+ const now = Date.now();
169048
+ const rows = this.stmts.selectAll.all();
169049
+ const due = rows.filter((r2) => r2.enabled === 1 && r2.next_run_at_ms !== null && r2.next_run_at_ms <= now);
169050
+ for (const row of due) {
169051
+ await this.executeJob(rowToJob(row));
169052
+ }
169053
+ this.scheduleNextTick();
169054
+ }
169055
+ };
168874
169056
  });
168875
169057
 
168876
169058
  // src/bus/queue.ts
@@ -169564,6 +169746,9 @@ class ApprovalManager {
169564
169746
  getReason(approvalId) {
169565
169747
  return this.pending.get(approvalId)?.reason ?? null;
169566
169748
  }
169749
+ getPreview(approvalId) {
169750
+ return this.pending.get(approvalId)?.preview;
169751
+ }
169567
169752
  getPending(sessionKey) {
169568
169753
  const id = this.bySession.get(sessionKey);
169569
169754
  if (!id)
@@ -169897,7 +170082,7 @@ var init_approval_events = __esm(() => {
169897
170082
  });
169898
170083
 
169899
170084
  // src/services/trading-confirm.ts
169900
- function buildPreview(title, body) {
170085
+ function buildPreview(title, body, extras) {
169901
170086
  const lines = (body.lines ?? []).filter((l2) => l2 !== undefined && l2 !== null);
169902
170087
  const steps = (body.steps ?? []).filter((s2) => s2 !== undefined && s2 !== null);
169903
170088
  const details = {};
@@ -169923,7 +170108,10 @@ function buildPreview(title, body) {
169923
170108
  summary,
169924
170109
  details,
169925
170110
  warnings: warnings.length > 0 ? warnings : undefined,
169926
- direction: blob.includes("buy") || blob.includes("long") ? "long" : blob.includes("sell") || blob.includes("short") ? "short" : undefined
170111
+ symbol: extras?.symbol,
170112
+ direction: blob.includes("buy") || blob.includes("long") ? "long" : blob.includes("sell") || blob.includes("short") ? "short" : undefined,
170113
+ wizard: extras?.wizard,
170114
+ suggestedValue: extras?.suggestedValue
169927
170115
  };
169928
170116
  }
169929
170117
 
@@ -169936,8 +170124,8 @@ class DaemonConfirmService {
169936
170124
  this.eventBus = eventBus;
169937
170125
  this.orchestrator = orchestrator;
169938
170126
  }
169939
- async confirm(title, body) {
169940
- const preview = buildPreview(title, body);
170127
+ async confirm(title, body, extras) {
170128
+ const preview = buildPreview(title, body, extras);
169941
170129
  const sessionKey = `trade:${crypto.randomUUID().slice(0, 8)}`;
169942
170130
  const preText = this.orchestrator.getCurrentTurnText();
169943
170131
  const origin = this.orchestrator.getCurrentTurnOrigin();
@@ -180846,7 +181034,7 @@ class HyperliquidClient {
180846
181034
  assetMap = new Map;
180847
181035
  szDecimals = new Map;
180848
181036
  maxLeverage = new Map;
180849
- assetNames = [];
181037
+ assets = [];
180850
181038
  metaLoaded = false;
180851
181039
  metaInFlight = null;
180852
181040
  dexUniverses = new Map;
@@ -180984,7 +181172,7 @@ class HyperliquidClient {
180984
181172
  this.log.warn({ err: result.reason, dex: dexes[i].name }, "HIP-3 dex meta fetch failed \u2014 skipping");
180985
181173
  }
180986
181174
  }
180987
- this.assetNames = merged.map((a) => a.name);
181175
+ this.assets = merged.map((a) => a.isDelisted ? { symbol: a.name, isDelisted: true } : { symbol: a.name });
180988
181176
  merged.forEach((a, idx) => {
180989
181177
  const key = this.resolveSymbol(a.name);
180990
181178
  this.assetMap.set(key, idx);
@@ -181000,7 +181188,10 @@ class HyperliquidClient {
181000
181188
  return this.maxLeverage.get(this.resolveSymbol(symbol5));
181001
181189
  }
181002
181190
  getAllAssetNames() {
181003
- return [...this.assetNames];
181191
+ return this.assets.map((a) => a.symbol);
181192
+ }
181193
+ getAllAssets() {
181194
+ return this.assets;
181004
181195
  }
181005
181196
  isKnownSymbol(symbol5) {
181006
181197
  return this.assetMap.has(this.resolveSymbol(symbol5));
@@ -181253,12 +181444,16 @@ class HyperliquidClient {
181253
181444
  const decimals = this.szDecimals.get(resolved) ?? 0;
181254
181445
  return size2.toFixed(decimals);
181255
181446
  }
181256
- formatPrice(price) {
181257
- return parseFloat(price.toPrecision(5)).toString();
181447
+ formatPrice(symbol5, price) {
181448
+ const resolved = this.resolveSymbol(symbol5);
181449
+ const szDecimals = this.szDecimals.get(resolved) ?? 0;
181450
+ const maxDecimals = Math.max(0, 6 - szDecimals);
181451
+ const trimmed = parseFloat(price.toPrecision(5));
181452
+ return parseFloat(trimmed.toFixed(maxDecimals)).toString();
181258
181453
  }
181259
- slippagePrice(midPrice, isBuy, slippagePct) {
181454
+ slippagePrice(symbol5, midPrice, isBuy, slippagePct) {
181260
181455
  const factor = isBuy ? 1 + slippagePct / 100 : 1 - slippagePct / 100;
181261
- return this.formatPrice(midPrice * factor);
181456
+ return this.formatPrice(symbol5, midPrice * factor);
181262
181457
  }
181263
181458
  async placeOrder(params) {
181264
181459
  const ex = this.requireExchange();
@@ -181274,42 +181469,42 @@ class HyperliquidClient {
181274
181469
  switch (params.orderType) {
181275
181470
  case "market": {
181276
181471
  const ticker = await this.getTicker(params.symbol);
181277
- price = this.slippagePrice(ticker.midPrice, isBuy, params.slippagePct ?? 0.5);
181472
+ price = this.slippagePrice(params.symbol, ticker.midPrice, isBuy, params.slippagePct ?? 0.5);
181278
181473
  orderType = { limit: { tif: "Ioc" } };
181279
181474
  break;
181280
181475
  }
181281
181476
  case "limit": {
181282
181477
  if (!params.price)
181283
181478
  throw new Error("Limit order requires price");
181284
- price = this.formatPrice(params.price);
181479
+ price = this.formatPrice(params.symbol, params.price);
181285
181480
  orderType = { limit: { tif: params.tif ?? "Gtc" } };
181286
181481
  break;
181287
181482
  }
181288
181483
  case "stop_market": {
181289
181484
  if (!params.price)
181290
181485
  throw new Error("Stop market order requires trigger price");
181291
- price = this.formatPrice(params.price);
181486
+ price = this.formatPrice(params.symbol, params.price);
181292
181487
  orderType = { trigger: { isMarket: true, triggerPx: price, tpsl: "sl" } };
181293
181488
  break;
181294
181489
  }
181295
181490
  case "stop_limit": {
181296
181491
  if (!params.price)
181297
181492
  throw new Error("Stop limit order requires trigger price");
181298
- price = this.formatPrice(params.price);
181493
+ price = this.formatPrice(params.symbol, params.price);
181299
181494
  orderType = { trigger: { isMarket: false, triggerPx: price, tpsl: "sl" } };
181300
181495
  break;
181301
181496
  }
181302
181497
  case "take_profit": {
181303
181498
  if (!params.price)
181304
181499
  throw new Error("Take profit order requires trigger price");
181305
- price = this.formatPrice(params.price);
181500
+ price = this.formatPrice(params.symbol, params.price);
181306
181501
  orderType = { trigger: { isMarket: true, triggerPx: price, tpsl: "tp" } };
181307
181502
  break;
181308
181503
  }
181309
181504
  case "take_profit_limit": {
181310
181505
  if (!params.price)
181311
181506
  throw new Error("Take profit limit order requires price");
181312
- price = this.formatPrice(params.price);
181507
+ price = this.formatPrice(params.symbol, params.price);
181313
181508
  orderType = { trigger: { isMarket: false, triggerPx: price, tpsl: "tp" } };
181314
181509
  break;
181315
181510
  }
@@ -181691,9 +181886,7 @@ class PaperEngine {
181691
181886
  }
181692
181887
  async executeMarketOrder(symbol5, params, isMaker = false) {
181693
181888
  const ticker = await this.marketClient.getTicker(symbol5);
181694
- const slippage = params.slippagePct ?? 0.5;
181695
- const slippageMul = params.side === "buy" ? 1 + slippage / 100 : 1 - slippage / 100;
181696
- const fillPrice = isMaker ? ticker.midPrice : ticker.midPrice * slippageMul;
181889
+ const fillPrice = ticker.midPrice;
181697
181890
  return this.db.transaction(() => {
181698
181891
  const existing = this.db.query("SELECT * FROM paper_positions WHERE symbol = ?").get(symbol5);
181699
181892
  const isBuy = params.side === "buy";
@@ -182043,6 +182236,9 @@ class PaperTradingClient {
182043
182236
  isKnownSymbol(symbol5) {
182044
182237
  return this.marketClient.isKnownSymbol(symbol5);
182045
182238
  }
182239
+ getAllAssets() {
182240
+ return this.marketClient.getAllAssets();
182241
+ }
182046
182242
  getDexUniverses() {
182047
182243
  return this.marketClient.getDexUniverses();
182048
182244
  }
@@ -182386,7 +182582,7 @@ var init_alert_rules = __esm(() => {
182386
182582
 
182387
182583
  // src/services/notifications.ts
182388
182584
  function isKind(raw) {
182389
- return raw === "price_target" || raw === "liquidation_risk" || raw === "position_closed" || raw === "tp_hit" || raw === "sl_hit" || raw === "order_filled" || raw === "order_canceled" || raw === "proactive";
182585
+ return raw === "price_target" || raw === "liquidation_risk" || raw === "position_closed" || raw === "tp_hit" || raw === "sl_hit" || raw === "order_filled" || raw === "order_canceled" || raw === "news" || raw === "proactive";
182390
182586
  }
182391
182587
  function rowToNotification(r2) {
182392
182588
  let payload;
@@ -183057,6 +183253,23 @@ class NewsService {
183057
183253
  ];
183058
183254
  return this.db.prepare(sql).all(...params).map((r2) => mapRow(r2));
183059
183255
  }
183256
+ listRecentRelevant(sinceTs, limit2 = 20) {
183257
+ const sql = `
183258
+ SELECT id, source_id, external_id, url, title, snippet, image_url, coins,
183259
+ importance, published_at, fetched_at, expires_at, full_summary,
183260
+ ai_relevant, ai_duplicate_of
183261
+ FROM articles
183262
+ WHERE ai_relevant = 1
183263
+ AND full_summary IS NOT NULL
183264
+ AND ai_duplicate_of IS NULL
183265
+ AND dismissed_at IS NULL
183266
+ AND expires_at > unixepoch()
183267
+ AND published_at > ?
183268
+ ORDER BY published_at DESC, id DESC
183269
+ LIMIT ?
183270
+ `;
183271
+ return this.db.prepare(sql).all(sinceTs, limit2).map((r2) => mapRow(r2));
183272
+ }
183060
183273
  searchArticles(opts = {}) {
183061
183274
  const limit2 = Math.min(opts.limit ?? 50, 100);
183062
183275
  const conditions = ["ai_duplicate_of IS NULL"];
@@ -183792,8 +184005,90 @@ class PreferenceStore {
183792
184005
  else
183793
184006
  this.set(NEWS_FILTER_PROMPT_KEY, prompt);
183794
184007
  }
184008
+ getTimezone() {
184009
+ return this.get(USER_TIMEZONE_KEY);
184010
+ }
184011
+ setTimezone(tz) {
184012
+ if (tz.length === 0)
184013
+ this.delete(USER_TIMEZONE_KEY);
184014
+ else
184015
+ this.set(USER_TIMEZONE_KEY, tz);
184016
+ }
184017
+ }
184018
+ var TWEET_FILTER_PROMPT_KEY = "tweets.filter_prompt", NEWS_FILTER_PROMPT_KEY = "news.filter_prompt", USER_TIMEZONE_KEY = "user.timezone";
184019
+
184020
+ // src/scheduler/defaults.ts
184021
+ function detectUserTimezone() {
184022
+ try {
184023
+ return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
184024
+ } catch {
184025
+ return "UTC";
184026
+ }
184027
+ }
184028
+ function buildBuiltInJobs(tz) {
184029
+ return [
184030
+ {
184031
+ name: "morning-briefing",
184032
+ schedule: { kind: "cron", expr: "0 8 * * *", tz },
184033
+ message: BRIEFING_PROMPT,
184034
+ deliver: true
184035
+ },
184036
+ {
184037
+ name: "evening-recap",
184038
+ schedule: { kind: "cron", expr: "0 21 * * *", tz },
184039
+ message: RECAP_PROMPT,
184040
+ deliver: true
184041
+ }
184042
+ ];
183795
184043
  }
183796
- var TWEET_FILTER_PROMPT_KEY = "tweets.filter_prompt", NEWS_FILTER_PROMPT_KEY = "news.filter_prompt";
184044
+ var BRIEFING_PROMPT, RECAP_PROMPT;
184045
+ var init_defaults = __esm(() => {
184046
+ BRIEFING_PROMPT = "Run the morning briefing. Call tools to fetch the latest data: open " + "positions, recent fills / trade history, watchlist, news, and market " + "signals (funding, whale activity, fear & greed). Summarize in under 15 " + "sentences, in the language the user has been chatting in.";
184047
+ RECAP_PROMPT = "Run the end-of-day recap. Call tools to fetch today's trade history and " + "current open positions. Summarize today's PnL, position changes (opened, " + "closed, scaled), and one notable market note. Brief one-liner if I had no " + "activity. Reply in the language the user has been chatting in.";
184048
+ });
184049
+
184050
+ // src/services/timezone.ts
184051
+ function validateTimezone(input) {
184052
+ if (typeof input !== "string") {
184053
+ return { ok: false, error: "Timezone must be a string" };
184054
+ }
184055
+ const trimmed = input.trim();
184056
+ if (!trimmed) {
184057
+ return { ok: false, error: "Timezone cannot be empty" };
184058
+ }
184059
+ if (trimmed.includes("\x00")) {
184060
+ return { ok: false, error: "Timezone contains invalid characters" };
184061
+ }
184062
+ if (trimmed.length > 64) {
184063
+ return { ok: false, error: "Timezone exceeds maximum length" };
184064
+ }
184065
+ try {
184066
+ const resolved = new Intl.DateTimeFormat(undefined, { timeZone: trimmed }).resolvedOptions().timeZone;
184067
+ return { ok: true, tz: resolved };
184068
+ } catch {
184069
+ return { ok: false, error: "Unknown timezone" };
184070
+ }
184071
+ }
184072
+ function detectHostTimezone() {
184073
+ return detectUserTimezone();
184074
+ }
184075
+ function createTimezoneService(prefs) {
184076
+ return {
184077
+ get() {
184078
+ return prefs.getTimezone() ?? "UTC";
184079
+ },
184080
+ set(input) {
184081
+ const result = validateTimezone(input);
184082
+ if (!result.ok)
184083
+ return result;
184084
+ prefs.setTimezone(result.tz);
184085
+ return result;
184086
+ }
184087
+ };
184088
+ }
184089
+ var init_timezone = __esm(() => {
184090
+ init_defaults();
184091
+ });
183797
184092
 
183798
184093
  // src/services/x-follows.ts
183799
184094
  function sleep8(ms2) {
@@ -188530,7 +188825,7 @@ class Runner {
188530
188825
  }
188531
188826
  async call(opts) {
188532
188827
  const next = this.inFlight.then(async () => {
188533
- const tools = this.registry.all();
188828
+ const tools = this.registry.taskAgentTools();
188534
188829
  this.agent.state.tools = tools;
188535
188830
  for (const tool2 of tools) {
188536
188831
  if (isOriginAware(tool2))
@@ -188722,7 +189017,9 @@ function emptySnapshot() {
188722
189017
  openOrderIds: [],
188723
189018
  lastRestSyncAtMs: 0,
188724
189019
  recentCancelOids: [],
188725
- recentEmittedFillIds: []
189020
+ recentEmittedFillIds: [],
189021
+ recentEmittedNewsIds: [],
189022
+ lastNewsScanTs: 0
188726
189023
  };
188727
189024
  }
188728
189025
 
@@ -188747,7 +189044,9 @@ class ObserverStateStore {
188747
189044
  openOrderIds: Array.isArray(parsed.openOrderIds) ? parsed.openOrderIds : [],
188748
189045
  lastRestSyncAtMs: typeof parsed.lastRestSyncAtMs === "number" ? parsed.lastRestSyncAtMs : 0,
188749
189046
  recentCancelOids: Array.isArray(parsed.recentCancelOids) ? parsed.recentCancelOids.filter((o10) => typeof o10 === "string") : [],
188750
- recentEmittedFillIds: Array.isArray(parsed.recentEmittedFillIds) ? parsed.recentEmittedFillIds.filter((o10) => typeof o10 === "string") : []
189047
+ recentEmittedFillIds: Array.isArray(parsed.recentEmittedFillIds) ? parsed.recentEmittedFillIds.filter((o10) => typeof o10 === "string") : [],
189048
+ recentEmittedNewsIds: Array.isArray(parsed.recentEmittedNewsIds) ? parsed.recentEmittedNewsIds.filter((o10) => typeof o10 === "string") : [],
189049
+ lastNewsScanTs: typeof parsed.lastNewsScanTs === "number" ? parsed.lastNewsScanTs : 0
188751
189050
  };
188752
189051
  } catch {
188753
189052
  return emptySnapshot();
@@ -188760,7 +189059,7 @@ class ObserverStateStore {
188760
189059
  this.write.run(KEY_SNAPSHOT, JSON.stringify(emptySnapshot()));
188761
189060
  }
188762
189061
  }
188763
- var RECENT_CANCEL_OIDS_CAP = 500, RECENT_FILL_IDS_CAP = 500, KEY_SNAPSHOT = "snapshot";
189062
+ var RECENT_CANCEL_OIDS_CAP = 500, RECENT_FILL_IDS_CAP = 500, RECENT_NEWS_IDS_CAP = 200, KEY_SNAPSHOT = "snapshot";
188764
189063
 
188765
189064
  // src/observer/detect/positions.ts
188766
189065
  function posKey(symbol5, side) {
@@ -189048,6 +189347,36 @@ function detectCanceledOrders(input) {
189048
189347
  return { events, emittedOids };
189049
189348
  }
189050
189349
 
189350
+ // src/observer/detect/news.ts
189351
+ function detectNews(input) {
189352
+ const events = [];
189353
+ const emittedIds = [];
189354
+ const seenThisTick = new Set;
189355
+ for (const article of input.articles) {
189356
+ if (input.priorEmittedIds.has(article.id))
189357
+ continue;
189358
+ if (seenThisTick.has(article.id))
189359
+ continue;
189360
+ if (article.fullSummary === null)
189361
+ continue;
189362
+ events.push({
189363
+ type: "news",
189364
+ detectedAt: input.nowMs,
189365
+ articleId: article.id,
189366
+ title: article.title,
189367
+ summary: article.fullSummary,
189368
+ source: article.sourceId,
189369
+ url: article.url,
189370
+ publishedAt: article.publishedAt * 1000,
189371
+ importance: article.importance,
189372
+ coins: [...article.coins]
189373
+ });
189374
+ emittedIds.push(article.id);
189375
+ seenThisTick.add(article.id);
189376
+ }
189377
+ return { events, emittedIds };
189378
+ }
189379
+
189051
189380
  // src/observer/diff.ts
189052
189381
  function diffSnapshot(input) {
189053
189382
  const positionsR = detectPositions({
@@ -189079,18 +189408,25 @@ function diffSnapshot(input) {
189079
189408
  prices: input.prices,
189080
189409
  nowMs: input.nowMs
189081
189410
  });
189411
+ const newsR = detectNews({
189412
+ articles: input.articles,
189413
+ priorEmittedIds: new Set(input.prior.recentEmittedNewsIds),
189414
+ nowMs: input.nowMs
189415
+ });
189082
189416
  return {
189083
189417
  events: [
189084
189418
  ...positionsR.events,
189085
189419
  ...fillsR.events,
189086
189420
  ...fallbackEvents,
189087
189421
  ...cancelR.events,
189088
- ...priceTargetR.events
189422
+ ...priceTargetR.events,
189423
+ ...newsR.events
189089
189424
  ],
189090
189425
  nextPositions: positionsR.nextPositions,
189091
189426
  firedAlertIds: priceTargetR.firedIds,
189092
189427
  emittedCancelOids: cancelR.emittedOids,
189093
- emittedFillIds: fillsR.emittedFillIds
189428
+ emittedFillIds: fillsR.emittedFillIds,
189429
+ emittedNewsIds: newsR.emittedIds
189094
189430
  };
189095
189431
  }
189096
189432
  var init_diff = () => {};
@@ -189206,7 +189542,8 @@ var init_judge2 = __esm(() => {
189206
189542
  Type.Literal("order_canceled"),
189207
189543
  Type.Literal("liquidation_risk"),
189208
189544
  Type.Literal("pnl_snapshot"),
189209
- Type.Literal("price_alert")
189545
+ Type.Literal("price_alert"),
189546
+ Type.Literal("news")
189210
189547
  ];
189211
189548
  JudgeResponseSchema = Type.Object({
189212
189549
  decision: Type.Union([Type.Literal("fire"), Type.Literal("silent")]),
@@ -194058,6 +194395,7 @@ function stripCustomTags(text) {
194058
194395
  out = out.replace(/<verdict\s*[^>]*>([\s\S]*?)<\/verdict>/gi, `${SENTINEL_I_OPEN}$1${SENTINEL_I_CLOSE}`);
194059
194396
  out = out.replace(/(\n)(\x00I_OPEN\x00(?:\uD83D\uDC02 |\uD83D\uDC3B |\u3030\uFE0F )?)/g, `$1
194060
194397
  $2`);
194398
+ out = out.replace(/<ask_user_question\s*>([\s\S]*?)<\/ask_user_question>/gi, (_full, inner) => formatAskFallback(inner));
194061
194399
  const chartHint = (attrs) => {
194062
194400
  const symMatch = /\bsymbol\s*=\s*"([^"]+)"/i.exec(attrs);
194063
194401
  const intMatch = /\binterval\s*=\s*"([^"]+)"/i.exec(attrs);
@@ -194078,6 +194416,25 @@ $2`);
194078
194416
  out = out.replace(/<[a-zA-Z][^>]*$/g, "");
194079
194417
  return out;
194080
194418
  }
194419
+ function formatAskFallback(inner) {
194420
+ const questionRe = /<question>([\s\S]*?)<\/question>/gi;
194421
+ const titleRe = /<title>([\s\S]*?)<\/title>/i;
194422
+ const lines = [];
194423
+ let m6;
194424
+ let i = 1;
194425
+ while ((m6 = questionRe.exec(inner)) !== null) {
194426
+ const title = titleRe.exec(m6[1])?.[1]?.trim();
194427
+ if (!title)
194428
+ continue;
194429
+ lines.push(`${i}. ${title}`);
194430
+ i++;
194431
+ }
194432
+ if (lines.length === 0)
194433
+ return "";
194434
+ return `
194435
+ ${lines.join(`
194436
+ `)}`;
194437
+ }
194081
194438
  var CHART_RE_PAIRED, CHART_RE_SELF;
194082
194439
  var init_tags = __esm(() => {
194083
194440
  CHART_RE_PAIRED = /<chart\s*([^>]*)>([\s\S]*?)<\/chart>/gi;
@@ -195524,6 +195881,7 @@ class ObserverLoop {
195524
195881
  const currentOpenOrderIds = rest4.openOrders.map((o10) => o10.orderId);
195525
195882
  const prices = this.deps.priceCache.snapshot();
195526
195883
  const alertRules = this.deps.alertRules.list();
195884
+ const articles = rest4.positions.length === 0 ? [] : this.deps.newsService.listRecentRelevant(newsScanSinceTs(prior.lastNewsScanTs, nowMs));
195527
195885
  const diff = diffSnapshot({
195528
195886
  prior,
195529
195887
  positions: rest4.positions,
@@ -195533,6 +195891,7 @@ class ObserverLoop {
195533
195891
  alertRules,
195534
195892
  prices,
195535
195893
  liqProgressThreshold: this.deps.config.liquidationProgressThreshold,
195894
+ articles,
195536
195895
  nowMs
195537
195896
  });
195538
195897
  for (const id of diff.firedAlertIds) {
@@ -195565,7 +195924,9 @@ class ObserverLoop {
195565
195924
  openOrderIds: currentOpenOrderIds,
195566
195925
  lastRestSyncAtMs: synced ? nowMs : prior.lastRestSyncAtMs,
195567
195926
  recentCancelOids: mergeCancelOids(prior.recentCancelOids, diff.emittedCancelOids),
195568
- recentEmittedFillIds: mergeBoundedIds(prior.recentEmittedFillIds, diff.emittedFillIds, RECENT_FILL_IDS_CAP)
195927
+ recentEmittedFillIds: mergeBoundedIds(prior.recentEmittedFillIds, diff.emittedFillIds, RECENT_FILL_IDS_CAP),
195928
+ recentEmittedNewsIds: mergeBoundedIds(prior.recentEmittedNewsIds, diff.emittedNewsIds, RECENT_NEWS_IDS_CAP),
195929
+ lastNewsScanTs: maxArticlePublishedAtSec(articles, prior.lastNewsScanTs)
195569
195930
  };
195570
195931
  const scanCounts = {
195571
195932
  positions: rest4.positions.length,
@@ -195654,6 +196015,7 @@ class ObserverLoop {
195654
196015
  alertRules: this.deps.alertRules.list(),
195655
196016
  prices: this.deps.priceCache.snapshot(),
195656
196017
  liqProgressThreshold: this.deps.config.liquidationProgressThreshold,
196018
+ articles: [],
195657
196019
  nowMs
195658
196020
  });
195659
196021
  this.store.save({
@@ -195662,7 +196024,9 @@ class ObserverLoop {
195662
196024
  openOrderIds: rest4.openOrders.map((o10) => o10.orderId),
195663
196025
  lastRestSyncAtMs: syncDue ? nowMs : prior.lastRestSyncAtMs,
195664
196026
  recentCancelOids: mergeCancelOids(prior.recentCancelOids, diff.emittedCancelOids),
195665
- recentEmittedFillIds: mergeBoundedIds(prior.recentEmittedFillIds, diff.emittedFillIds, RECENT_FILL_IDS_CAP)
196027
+ recentEmittedFillIds: mergeBoundedIds(prior.recentEmittedFillIds, diff.emittedFillIds, RECENT_FILL_IDS_CAP),
196028
+ recentEmittedNewsIds: prior.recentEmittedNewsIds,
196029
+ lastNewsScanTs: prior.lastNewsScanTs
195666
196030
  });
195667
196031
  } catch (err) {
195668
196032
  this.deps.logger.warn({ err }, "observer: baseline refresh failed under confirm gate");
@@ -195802,11 +196166,25 @@ function mapKindFromEventType(t2) {
195802
196166
  return "order_canceled";
195803
196167
  case "order_filled":
195804
196168
  return "order_filled";
196169
+ case "news":
196170
+ return "news";
195805
196171
  default:
195806
196172
  return "proactive";
195807
196173
  }
195808
196174
  }
195809
- var RECENT_CHAT_MAX = 20, MIN_PRICE_PCT_DELTA = 0.5, MIN_COOLDOWN_MS;
196175
+ function newsScanSinceTs(lastScanTs, nowMs) {
196176
+ const slidingFloor = Math.floor(nowMs / 1000) - NEWS_LOOKBACK_FLOOR_SEC;
196177
+ return lastScanTs > slidingFloor ? lastScanTs : slidingFloor;
196178
+ }
196179
+ function maxArticlePublishedAtSec(articles, prior) {
196180
+ let max = prior;
196181
+ for (const a of articles) {
196182
+ if (a.publishedAt > max)
196183
+ max = a.publishedAt;
196184
+ }
196185
+ return max;
196186
+ }
196187
+ var RECENT_CHAT_MAX = 20, MIN_PRICE_PCT_DELTA = 0.5, MIN_COOLDOWN_MS, NEWS_LOOKBACK_FLOOR_SEC;
195810
196188
  var init_loop = __esm(() => {
195811
196189
  init_diff();
195812
196190
  init_judge2();
@@ -195814,11 +196192,12 @@ var init_loop = __esm(() => {
195814
196192
  init_channels();
195815
196193
  init_types5();
195816
196194
  MIN_COOLDOWN_MS = 60 * 60 * 1000;
196195
+ NEWS_LOOKBACK_FLOOR_SEC = 30 * 60;
195817
196196
  });
195818
196197
 
195819
196198
  // src/runtime.ts
195820
196199
  import { join as join14 } from "path";
195821
- import { existsSync as existsSync18, mkdirSync as mkdirSync14, copyFileSync as copyFileSync2, readdirSync as readdirSync7 } from "fs";
196200
+ import { existsSync as existsSync17, mkdirSync as mkdirSync13, copyFileSync as copyFileSync2, readdirSync as readdirSync7 } from "fs";
195822
196201
  async function createRuntime(options) {
195823
196202
  const configPath = options.configPath ?? getConfigPath();
195824
196203
  const rawConfig = loadConfig(configPath);
@@ -195858,12 +196237,14 @@ async function createRuntime(options) {
195858
196237
  const skillsLoader = new SkillsLoader(workspaceDir, builtinSkillsDir);
195859
196238
  const skillService = new SkillService(db2, skillsLoader);
195860
196239
  skillService.syncState();
195861
- const contextBuilder = new ContextBuilder({ workspaceDir, model: config3.model }, memoryStore, skillsLoader);
196240
+ const preferenceStore = new PreferenceStore(db2, logger.child({ module: "prefs" }));
196241
+ const timezoneService = createTimezoneService(preferenceStore);
196242
+ const contextBuilder = new ContextBuilder({ workspaceDir, model: config3.model, getTimezone: () => timezoneService.get() }, memoryStore, skillsLoader);
195862
196243
  contextBuilder.setDisabledSkillsProvider(() => skillService.getDisabledNames());
195863
- const cronService = new CronService(getCronStorePath());
196244
+ const cronService = new CronService(db2);
195864
196245
  const tools = createToolRegistry(security2, {
195865
196246
  cronService,
195866
- defaultTimezone: config3.cron.timezone,
196247
+ timezoneService,
195867
196248
  memoryStore,
195868
196249
  logger: logger.child({ module: "tool" })
195869
196250
  });
@@ -195874,7 +196255,6 @@ async function createRuntime(options) {
195874
196255
  const watchlistService = new WatchlistService(db2);
195875
196256
  const newsService = new NewsService(db2, watchlistService, credentials2, logger.child({ module: "news" }));
195876
196257
  const tweetService = new TweetService(db2, logger.child({ module: "tweets" }));
195877
- const preferenceStore = new PreferenceStore(db2, logger.child({ module: "prefs" }));
195878
196258
  const xQueryIdCache = new XQueryIdCache;
195879
196259
  const xFollowService = new XFollowService(db2, credentials2, xQueryIdCache, logger.child({ module: "x-follows" }));
195880
196260
  const taIndicators = new TaIndicatorService(tradingClient);
@@ -195913,7 +196293,7 @@ async function createRuntime(options) {
195913
196293
  eventBus,
195914
196294
  logger,
195915
196295
  customModelRegistry,
195916
- confirmDeps: { getConfirmService: () => confirmServiceRef }
196296
+ confirmDeps: { getConfirmService: () => confirmServiceRef, approvalManager }
195917
196297
  });
195918
196298
  const taskAgent = createAgent({
195919
196299
  config: config3,
@@ -195930,7 +196310,8 @@ async function createRuntime(options) {
195930
196310
  logger: logger.child({ module: "task-agent" }),
195931
196311
  customModelRegistry,
195932
196312
  confirmDeps: { getConfirmService: () => null },
195933
- bypassConfirm: true
196313
+ bypassConfirm: true,
196314
+ taskMode: true
195934
196315
  });
195935
196316
  const runner = new Runner(taskAgent, sessionManager, tools, logger.child({ module: "runner" }));
195936
196317
  const rssDiscoveryService = new RssDiscoveryService(runner, logger.child({ module: "rss-discovery" }));
@@ -195997,7 +196378,7 @@ async function createRuntime(options) {
195997
196378
  tools.register(t2);
195998
196379
  contextBuilder.setTools(tools.all().map((t2) => ({ name: t2.name, description: t2.description })));
195999
196380
  agent3.state.tools = tools.all();
196000
- taskAgent.state.tools = tools.all();
196381
+ taskAgent.state.tools = tools.taskAgentTools();
196001
196382
  setupClaudeCliProvider({
196002
196383
  config: config3,
196003
196384
  logger,
@@ -196037,6 +196418,7 @@ async function createRuntime(options) {
196037
196418
  config: config3.observer,
196038
196419
  tradingClient,
196039
196420
  alertRules,
196421
+ newsService,
196040
196422
  notifications,
196041
196423
  priceCache,
196042
196424
  approvalManager,
@@ -196082,6 +196464,7 @@ async function createRuntime(options) {
196082
196464
  rssDiscoveryService,
196083
196465
  tweetService,
196084
196466
  preferenceStore,
196467
+ timezoneService,
196085
196468
  xFollowService,
196086
196469
  skillService,
196087
196470
  chartSeries,
@@ -196115,16 +196498,16 @@ function getApiKey(oauthManager, credentials2, customModelRegistry) {
196115
196498
  };
196116
196499
  }
196117
196500
  function createAgent(opts) {
196118
- const initialOptions = buildAgentOptions(opts.config, opts.model, opts.security, opts.leakDetector, opts.oauthManager, opts.tools, opts.systemPrompt, opts.credentials, opts.extraReadDirs, opts.approvalManager, opts.eventBus, opts.logger, opts.customModelRegistry, opts.confirmDeps, opts.bypassConfirm ?? false);
196501
+ const initialOptions = buildAgentOptions(opts.config, opts.model, opts.security, opts.leakDetector, opts.oauthManager, opts.tools, opts.systemPrompt, opts.credentials, opts.extraReadDirs, opts.approvalManager, opts.eventBus, opts.logger, opts.customModelRegistry, opts.confirmDeps, opts.bypassConfirm ?? false, opts.taskMode ?? false);
196119
196502
  return new Agent(initialOptions);
196120
196503
  }
196121
- function buildAgentOptions(config3, model, security2, leakDetector, oauthManager, tools, systemPrompt, credentials2, extraReadDirs, approvalManager, eventBus, logger, customModelRegistry, confirmDeps, bypassConfirm = false) {
196504
+ function buildAgentOptions(config3, model, security2, leakDetector, oauthManager, tools, systemPrompt, credentials2, extraReadDirs, approvalManager, eventBus, logger, customModelRegistry, confirmDeps, bypassConfirm = false, taskMode = false) {
196122
196505
  const effectiveThinkingLevel = shouldForceThinkingOff({ id: model.id, baseUrl: model.baseUrl ?? "" }) ? "off" : config3.agent.thinkingLevel;
196123
196506
  return {
196124
196507
  initialState: {
196125
196508
  systemPrompt,
196126
196509
  model,
196127
- tools: config3.provider === "claude-cli" ? [] : tools.all(),
196510
+ tools: config3.provider === "claude-cli" ? [] : taskMode ? tools.taskAgentTools() : tools.all(),
196128
196511
  thinkingLevel: effectiveThinkingLevel
196129
196512
  },
196130
196513
  getApiKey: config3.provider === "claude-cli" ? async () => "claude-cli-no-key-needed" : getApiKey(oauthManager, credentials2, customModelRegistry),
@@ -196213,6 +196596,7 @@ async function runBatchedConfirm(assistantMessage, callName, callArgs, batchCach
196213
196596
  let title;
196214
196597
  const lines = [];
196215
196598
  let steps;
196599
+ let extras;
196216
196600
  if (isMulti) {
196217
196601
  title = `Confirm ${confirmable.length} actions?`;
196218
196602
  const stepList = [];
@@ -196223,14 +196607,22 @@ async function runBatchedConfirm(assistantMessage, callName, callArgs, batchCach
196223
196607
  stepList.push(`${head}${tail}`);
196224
196608
  }
196225
196609
  steps = stepList;
196610
+ extras = undefined;
196226
196611
  } else {
196227
196612
  const only = confirmable[0];
196613
+ const args = only.args ?? {};
196614
+ const symbol5 = typeof args.symbol === "string" ? args.symbol : undefined;
196228
196615
  const desc = describeConfirm(only.name, only.args);
196229
196616
  title = desc.title;
196230
196617
  lines.push(...desc.bullets);
196618
+ extras = {
196619
+ wizard: desc.wizard,
196620
+ suggestedValue: desc.suggestedValue,
196621
+ symbol: symbol5
196622
+ };
196231
196623
  }
196232
196624
  try {
196233
- const res = await confirmService.confirm(title, { lines, steps });
196625
+ const res = await confirmService.confirm(title, { lines, steps }, extras);
196234
196626
  return { decision: res.decision, reason: res.reason };
196235
196627
  } catch (err) {
196236
196628
  logger.warn({ err }, "confirm service threw; treating as rejected");
@@ -196379,19 +196771,19 @@ function makeTransformContext(maxTokens) {
196379
196771
  }
196380
196772
  function resolveDefaultBuiltinSkillsDir() {
196381
196773
  const candidate = join14(import.meta.dir, "skills", "builtin");
196382
- return existsSync18(candidate) ? candidate : undefined;
196774
+ return existsSync17(candidate) ? candidate : undefined;
196383
196775
  }
196384
196776
  function seedWorkspaceTemplates(workspaceDir) {
196385
196777
  const templatesDir = join14(import.meta.dir, "templates");
196386
- if (!existsSync18(templatesDir))
196778
+ if (!existsSync17(templatesDir))
196387
196779
  return;
196388
196780
  try {
196389
- mkdirSync14(workspaceDir, { recursive: true });
196781
+ mkdirSync13(workspaceDir, { recursive: true });
196390
196782
  for (const entry of readdirSync7(templatesDir, { withFileTypes: true })) {
196391
196783
  if (!entry.isFile())
196392
196784
  continue;
196393
196785
  const target = join14(workspaceDir, entry.name);
196394
- if (!existsSync18(target))
196786
+ if (!existsSync17(target))
196395
196787
  copyFileSync2(join14(templatesDir, entry.name), target);
196396
196788
  }
196397
196789
  } catch {}
@@ -196443,6 +196835,7 @@ var init_runtime2 = __esm(() => {
196443
196835
  init_news();
196444
196836
  init_rss_discovery();
196445
196837
  init_tweets();
196838
+ init_timezone();
196446
196839
  init_x_follows();
196447
196840
  init_x_query_ids();
196448
196841
  init_ta_indicators();
@@ -196626,14 +197019,6 @@ function getProviderList() {
196626
197019
  });
196627
197020
  }
196628
197021
  }
196629
- list.push({
196630
- id: "claude-cli",
196631
- label: "Claude Code",
196632
- description: "Use Claude Code subscription (no API key)",
196633
- tier: 0,
196634
- tierLabel: TIER_LABELS[0] ?? "Other",
196635
- supportsOAuth: false
196636
- });
196637
197022
  list.push({
196638
197023
  id: "custom",
196639
197024
  label: "Custom",
@@ -196664,9 +197049,6 @@ function getProviderList() {
196664
197049
  return list;
196665
197050
  }
196666
197051
  function getModelList(providerId) {
196667
- if (providerId === "claude-cli") {
196668
- return getClaudeCliModels();
196669
- }
196670
197052
  try {
196671
197053
  const raw = getModels(providerId).map((m6) => ({ id: m6.id, name: m6.name || m6.id }));
196672
197054
  return filterModelCatalog(providerId, raw);
@@ -196677,7 +197059,6 @@ function getModelList(providerId) {
196677
197059
  var OAUTH_PROVIDERS2, PROVIDER_META, TIER_LABELS;
196678
197060
  var init_providers = __esm(() => {
196679
197061
  init_dist();
196680
- init_models6();
196681
197062
  init_model_catalog();
196682
197063
  OAUTH_PROVIDERS2 = new Set(["anthropic", "openai-codex", "github-copilot", "google-gemini-cli", "google-antigravity"]);
196683
197064
  PROVIDER_META = {
@@ -196685,7 +197066,6 @@ var init_providers = __esm(() => {
196685
197066
  anthropic: { label: "Anthropic", description: "Claude Sonnet & Opus (direct)", tier: 0, apiKeyUrl: "https://console.anthropic.com/settings/keys" },
196686
197067
  openai: { label: "OpenAI", description: "GPT-4o, GPT-5 (direct)", tier: 0, apiKeyUrl: "https://platform.openai.com/api-keys" },
196687
197068
  "openai-codex": { label: "OpenAI Codex", description: "ChatGPT subscription (OAuth, no API key)", tier: 0 },
196688
- "claude-cli": { label: "Claude Code", description: "Use Claude Code subscription (no API key)", tier: 0 },
196689
197069
  google: { label: "Google Gemini", description: "Gemini 2.0 Flash & Pro", tier: 0, apiKeyUrl: "https://aistudio.google.com/app/apikey" },
196690
197070
  "google-gemini-cli": { label: "Google Gemini CLI", description: "Gemini via CLI auth", tier: 0 },
196691
197071
  xai: { label: "xAI", description: "Grok 3 & 4", tier: 0, apiKeyUrl: "https://console.x.ai" },
@@ -196722,17 +197102,15 @@ __export(exports_cli_providers, {
196722
197102
  listModels: () => listModels
196723
197103
  });
196724
197104
  function requiresApiKey(provider) {
196725
- if (provider.id === "claude-cli")
196726
- return false;
196727
- if (provider.supportsOAuth)
196728
- return false;
196729
- return true;
197105
+ return !provider.supportsOAuth;
196730
197106
  }
196731
197107
  function listProviders() {
196732
197108
  const builtIn = getProviderList().map((p) => ({
196733
197109
  id: p.id,
196734
197110
  label: p.label,
196735
197111
  description: p.description,
197112
+ tier: p.tier,
197113
+ tierLabel: p.tierLabel,
196736
197114
  requiresApiKey: requiresApiKey(p),
196737
197115
  supportsOAuth: p.supportsOAuth,
196738
197116
  apiKeyUrl: p.apiKeyUrl ?? null
@@ -196743,6 +197121,8 @@ function listProviders() {
196743
197121
  id: name,
196744
197122
  label: name,
196745
197123
  description: "Custom provider (from ~/.ghost/models.json)",
197124
+ tier: 4,
197125
+ tierLabel: "\uD83D\uDD27 Custom",
196746
197126
  requiresApiKey: true,
196747
197127
  supportsOAuth: false,
196748
197128
  apiKeyUrl: null,
@@ -198796,11 +199176,11 @@ ${je2}${r2.trimStart()}`), s2 = 3 + ne(r2.trimStart()).length);
198796
199176
  });
198797
199177
 
198798
199178
  // src/services/os/utils.ts
198799
- import { accessSync, constants, existsSync as existsSync19, mkdirSync as mkdirSync15, realpathSync as realpathSync2 } from "fs";
199179
+ import { accessSync, constants, existsSync as existsSync18, mkdirSync as mkdirSync14, realpathSync as realpathSync2 } from "fs";
198800
199180
  import { join as join15 } from "path";
198801
199181
  import { homedir as homedir3 } from "os";
198802
199182
  function isExecutable(filePath) {
198803
- if (!existsSync19(filePath))
199183
+ if (!existsSync18(filePath))
198804
199184
  return false;
198805
199185
  try {
198806
199186
  accessSync(filePath, constants.X_OK);
@@ -198847,8 +199227,8 @@ function resolveBunPath() {
198847
199227
  return candidates[0];
198848
199228
  }
198849
199229
  function ensureLogDir(dir) {
198850
- if (!existsSync19(dir)) {
198851
- mkdirSync15(dir, { recursive: true });
199230
+ if (!existsSync18(dir)) {
199231
+ mkdirSync14(dir, { recursive: true });
198852
199232
  }
198853
199233
  }
198854
199234
  function defaultLogDir() {
@@ -198914,7 +199294,7 @@ __export(exports_launchd, {
198914
199294
  LaunchdController: () => LaunchdController
198915
199295
  });
198916
199296
  import { spawnSync } from "child_process";
198917
- import { existsSync as existsSync20, unlinkSync as unlinkSync6, writeFileSync as writeFileSync11, rmSync as rmSync5 } from "fs";
199297
+ import { existsSync as existsSync19, unlinkSync as unlinkSync6, writeFileSync as writeFileSync10, rmSync as rmSync5 } from "fs";
198918
199298
  import { join as join16 } from "path";
198919
199299
  import { homedir as homedir4 } from "os";
198920
199300
  function plistPath() {
@@ -198964,7 +199344,7 @@ class LaunchdController {
198964
199344
  stderrLog,
198965
199345
  env: opts.env ?? {}
198966
199346
  });
198967
- writeFileSync11(definitionPath, plist, { encoding: "utf8", mode: 420 });
199347
+ writeFileSync10(definitionPath, plist, { encoding: "utf8", mode: 420 });
198968
199348
  const domain2 = guiDomain();
198969
199349
  const uid = process.getuid?.() ?? 0;
198970
199350
  const serviceTarget = `${domain2}/${LABEL}`;
@@ -199014,12 +199394,12 @@ class LaunchdController {
199014
199394
  const domain2 = guiDomain();
199015
199395
  const definition = plistPath();
199016
199396
  launchctl(["bootout", domain2, definition]);
199017
- if (existsSync20(definition)) {
199397
+ if (existsSync19(definition)) {
199018
199398
  unlinkSync6(definition);
199019
199399
  }
199020
199400
  if (opts.purgeLogs) {
199021
199401
  const logDir = defaultLogDir();
199022
- if (existsSync20(logDir)) {
199402
+ if (existsSync19(logDir)) {
199023
199403
  rmSync5(logDir, { recursive: true, force: true });
199024
199404
  }
199025
199405
  }
@@ -199103,8 +199483,8 @@ __export(exports_systemd, {
199103
199483
  SystemdController: () => SystemdController
199104
199484
  });
199105
199485
  import { spawnSync as spawnSync2 } from "child_process";
199106
- import { copyFileSync as copyFileSync3, existsSync as existsSync21, mkdirSync as mkdirSync16, rmSync as rmSync6, unlinkSync as unlinkSync7, writeFileSync as writeFileSync12 } from "fs";
199107
- import { dirname as dirname9, join as join17 } from "path";
199486
+ import { copyFileSync as copyFileSync3, existsSync as existsSync20, mkdirSync as mkdirSync15, rmSync as rmSync6, unlinkSync as unlinkSync7, writeFileSync as writeFileSync11 } from "fs";
199487
+ import { dirname as dirname8, join as join17 } from "path";
199108
199488
  import { homedir as homedir5 } from "os";
199109
199489
  function unitPath() {
199110
199490
  return join17(homedir5(), ".config", "systemd", "user", SERVICE_NAME);
@@ -199148,11 +199528,11 @@ class SystemdController {
199148
199528
  assertSystemdAvailable();
199149
199529
  ensureLogDir(opts.logDir);
199150
199530
  const path4 = unitPath();
199151
- const dir = dirname9(path4);
199152
- if (!existsSync21(dir)) {
199153
- mkdirSync16(dir, { recursive: true });
199531
+ const dir = dirname8(path4);
199532
+ if (!existsSync20(dir)) {
199533
+ mkdirSync15(dir, { recursive: true });
199154
199534
  }
199155
- if (existsSync21(path4)) {
199535
+ if (existsSync20(path4)) {
199156
199536
  copyFileSync3(path4, `${path4}.bak`);
199157
199537
  }
199158
199538
  const unit = buildUnit({
@@ -199165,7 +199545,7 @@ class SystemdController {
199165
199545
  GHOST_LOG_DIR: opts.logDir
199166
199546
  }
199167
199547
  });
199168
- writeFileSync12(path4, unit, "utf8");
199548
+ writeFileSync11(path4, unit, "utf8");
199169
199549
  const reload = systemctlStrict("daemon-reload");
199170
199550
  if (!reload.ok) {
199171
199551
  const msg = `daemon-reload failed: ${reload.stderr || reload.stdout}`;
@@ -199211,7 +199591,7 @@ class SystemdController {
199211
199591
  }
199212
199592
  for (const file3 of [path4, `${path4}.bak`]) {
199213
199593
  try {
199214
- if (existsSync21(file3)) {
199594
+ if (existsSync20(file3)) {
199215
199595
  unlinkSync7(file3);
199216
199596
  }
199217
199597
  } catch {
@@ -199222,7 +199602,7 @@ class SystemdController {
199222
199602
  if (opts.purgeLogs) {
199223
199603
  const logDir = defaultLogDir();
199224
199604
  try {
199225
- if (existsSync21(logDir)) {
199605
+ if (existsSync20(logDir)) {
199226
199606
  rmSync6(logDir, { recursive: true, force: true });
199227
199607
  }
199228
199608
  } catch {
@@ -199240,7 +199620,7 @@ class SystemdController {
199240
199620
  if (stdout === "active") {
199241
199621
  return "running";
199242
199622
  }
199243
- if (existsSync21(unitPath())) {
199623
+ if (existsSync20(unitPath())) {
199244
199624
  return "stopped";
199245
199625
  }
199246
199626
  return "not-installed";
@@ -199262,7 +199642,7 @@ __export(exports_schtasks, {
199262
199642
  });
199263
199643
  import { spawnSync as spawnSync3 } from "child_process";
199264
199644
  import { Buffer as Buffer2 } from "buffer";
199265
- import { existsSync as existsSync22, mkdirSync as mkdirSync17, writeFileSync as writeFileSync13, rmSync as rmSync7 } from "fs";
199645
+ import { existsSync as existsSync21, mkdirSync as mkdirSync16, writeFileSync as writeFileSync12, rmSync as rmSync7 } from "fs";
199266
199646
  import { join as join18 } from "path";
199267
199647
  import { homedir as homedir6, tmpdir } from "os";
199268
199648
  function launcherPath() {
@@ -199404,7 +199784,7 @@ function buildScheduledTaskXml(invisibleVbs, userId) {
199404
199784
  function writeTaskXml(xml) {
199405
199785
  const xmlPath = join18(tmpdir(), `ghost-task-${process.pid}.xml`);
199406
199786
  const bom = Buffer2.from([255, 254]);
199407
- writeFileSync13(xmlPath, Buffer2.concat([bom, Buffer2.from(xml, "utf16le")]));
199787
+ writeFileSync12(xmlPath, Buffer2.concat([bom, Buffer2.from(xml, "utf16le")]));
199408
199788
  return xmlPath;
199409
199789
  }
199410
199790
  function killOrphanedSupervisorChain() {
@@ -199438,17 +199818,17 @@ class SchtasksController {
199438
199818
  }
199439
199819
  ensureLogDir(opts.logDir);
199440
199820
  const stateDir = join18(homedir6(), ".ghost", "state");
199441
- if (!existsSync22(stateDir)) {
199442
- mkdirSync17(stateDir, { recursive: true });
199821
+ if (!existsSync21(stateDir)) {
199822
+ mkdirSync16(stateDir, { recursive: true });
199443
199823
  }
199444
199824
  const legacy = legacyStartupPath();
199445
- if (existsSync22(legacy)) {
199825
+ if (existsSync21(legacy)) {
199446
199826
  rmSync7(legacy, RM_RETRY_OPTS);
199447
199827
  }
199448
199828
  const launcher = launcherPath();
199449
- writeFileSync13(launcher, buildLauncherCmd(opts.bunPath, opts.execPath, opts.env), { encoding: "utf8" });
199829
+ writeFileSync12(launcher, buildLauncherCmd(opts.bunPath, opts.execPath, opts.env), { encoding: "utf8" });
199450
199830
  const invisibleVbs = invisibleLauncherPath();
199451
- writeFileSync13(invisibleVbs, buildInvisibleVbs(launcher), { encoding: "utf8" });
199831
+ writeFileSync12(invisibleVbs, buildInvisibleVbs(launcher), { encoding: "utf8" });
199452
199832
  const create4 = createScheduledTask(invisibleVbs, taskUser);
199453
199833
  if (create4.status !== 0) {
199454
199834
  return {
@@ -199493,20 +199873,20 @@ class SchtasksController {
199493
199873
  await new Promise((resolve) => setTimeout(resolve, 500));
199494
199874
  schtasks(["/Delete", "/F", "/TN", TASK_NAME]);
199495
199875
  const launcher = launcherPath();
199496
- if (existsSync22(launcher)) {
199876
+ if (existsSync21(launcher)) {
199497
199877
  rmSync7(launcher, RM_RETRY_OPTS);
199498
199878
  }
199499
199879
  const invisibleVbs = invisibleLauncherPath();
199500
- if (existsSync22(invisibleVbs)) {
199880
+ if (existsSync21(invisibleVbs)) {
199501
199881
  rmSync7(invisibleVbs, RM_RETRY_OPTS);
199502
199882
  }
199503
199883
  const legacy = legacyStartupPath();
199504
- if (existsSync22(legacy)) {
199884
+ if (existsSync21(legacy)) {
199505
199885
  rmSync7(legacy, RM_RETRY_OPTS);
199506
199886
  }
199507
199887
  if (opts.purgeLogs) {
199508
199888
  const logDir = defaultLogDir();
199509
- if (existsSync22(logDir)) {
199889
+ if (existsSync21(logDir)) {
199510
199890
  rmSync7(logDir, { recursive: true, ...RM_RETRY_OPTS });
199511
199891
  }
199512
199892
  }
@@ -212398,7 +212778,7 @@ function handleHealth() {
212398
212778
  }
212399
212779
 
212400
212780
  // src/gateway/static.ts
212401
- import { existsSync as existsSync23 } from "fs";
212781
+ import { existsSync as existsSync22 } from "fs";
212402
212782
  import { join as join20, extname as extname3 } from "path";
212403
212783
  function mime2(path4) {
212404
212784
  return MIME[extname3(path4).toLowerCase()] ?? "application/octet-stream";
@@ -212406,7 +212786,7 @@ function mime2(path4) {
212406
212786
  function resolveWebDist(candidates) {
212407
212787
  const list = candidates ?? defaultWebDistCandidates();
212408
212788
  for (const c4 of list) {
212409
- if (existsSync23(join20(c4, "index.html")))
212789
+ if (existsSync22(join20(c4, "index.html")))
212410
212790
  return c4;
212411
212791
  }
212412
212792
  return null;
@@ -212661,7 +213041,7 @@ function semverGt(a, b5) {
212661
213041
  }
212662
213042
 
212663
213043
  // src/update/version.ts
212664
- import { readFileSync as readFileSync17, existsSync as existsSync24 } from "fs";
213044
+ import { readFileSync as readFileSync16, existsSync as existsSync23 } from "fs";
212665
213045
  import { join as join21 } from "path";
212666
213046
  function resolvePackageJsonPath(candidates) {
212667
213047
  const list = candidates ?? [
@@ -212670,7 +213050,7 @@ function resolvePackageJsonPath(candidates) {
212670
213050
  join21(process.cwd(), "package.json")
212671
213051
  ];
212672
213052
  for (const p2 of list) {
212673
- if (existsSync24(p2))
213053
+ if (existsSync23(p2))
212674
213054
  return p2;
212675
213055
  }
212676
213056
  return null;
@@ -212692,7 +213072,7 @@ function getCurrentVersion(pkgPath) {
212692
213072
  if (!resolved)
212693
213073
  return UNKNOWN_VERSION;
212694
213074
  try {
212695
- const pkg = JSON.parse(readFileSync17(resolved, "utf-8"));
213075
+ const pkg = JSON.parse(readFileSync16(resolved, "utf-8"));
212696
213076
  if (pkg.version && pkg.version.length > 0)
212697
213077
  return pkg.version;
212698
213078
  } catch {}
@@ -212749,14 +213129,14 @@ var init_status = __esm(() => {
212749
213129
  });
212750
213130
 
212751
213131
  // src/gateway/memory.ts
212752
- import { readFileSync as readFileSync18, existsSync as existsSync25 } from "fs";
213132
+ import { readFileSync as readFileSync17, existsSync as existsSync24 } from "fs";
212753
213133
  function registerMemoryMethods(register, deps) {
212754
213134
  register("memory.get", async () => {
212755
213135
  const memory = deps.memoryStore.readLongTerm();
212756
213136
  let history = "";
212757
- if (existsSync25(deps.memoryStore.historyFile)) {
213137
+ if (existsSync24(deps.memoryStore.historyFile)) {
212758
213138
  try {
212759
- history = readFileSync18(deps.memoryStore.historyFile, "utf-8");
213139
+ history = readFileSync17(deps.memoryStore.historyFile, "utf-8");
212760
213140
  } catch {}
212761
213141
  }
212762
213142
  return { memory, history };
@@ -212919,6 +213299,22 @@ function registerCronMethods(register, deps) {
212919
213299
  });
212920
213300
  }
212921
213301
 
213302
+ // src/gateway/config.ts
213303
+ function registerConfigMethods(register, deps) {
213304
+ register("config.timezone.get", async () => ({
213305
+ tz: deps.timezoneService.get()
213306
+ }));
213307
+ register("config.timezone.set", async (_ctx, payload) => {
213308
+ const p2 = payload;
213309
+ const result = deps.timezoneService.set(p2.tz);
213310
+ if (!result.ok) {
213311
+ return { ok: false, error: result.error };
213312
+ }
213313
+ const updatedJobs = deps.cronService.updateBuiltinJobsTimezone(result.tz);
213314
+ return { ok: true, tz: result.tz, updatedJobs };
213315
+ });
213316
+ }
213317
+
212922
213318
  // src/gateway/route-orchestrator-error.ts
212923
213319
  function routeOrchestratorError(runId, classified, emit) {
212924
213320
  if (classified.type === "TOOL_BLOCKED") {
@@ -214902,11 +215298,12 @@ class TokensSnapshotService {
214902
215298
  this.priceCache = priceCache;
214903
215299
  }
214904
215300
  build() {
214905
- const tokens = this.client.getAllAssetNames();
215301
+ const assets = this.client.getAllAssets();
214906
215302
  const prices = {};
214907
215303
  const prevDayPrices = {};
214908
215304
  const maxLeverages = {};
214909
- for (const symbol5 of tokens) {
215305
+ const tokens = [];
215306
+ for (const { symbol: symbol5, isDelisted } of assets) {
214910
215307
  const entry = this.priceCache.get(symbol5, 30000);
214911
215308
  if (entry) {
214912
215309
  prices[symbol5] = entry.price;
@@ -214916,8 +215313,9 @@ class TokensSnapshotService {
214916
215313
  const lev = this.client.getMaxLeverage(symbol5);
214917
215314
  if (typeof lev === "number" && lev > 0)
214918
215315
  maxLeverages[symbol5] = lev;
215316
+ tokens.push(isDelisted ? { symbol: symbol5, isDelisted: true } : { symbol: symbol5 });
214919
215317
  }
214920
- tokens.sort();
215318
+ tokens.sort((a, b5) => a.symbol.localeCompare(b5.symbol));
214921
215319
  return { tokens, prices, prevDayPrices, maxLeverages };
214922
215320
  }
214923
215321
  }
@@ -215045,6 +215443,10 @@ function createGateway(gatewayConfig, deps) {
215045
215443
  registerToolsMethods(registry4.register.bind(registry4), { tools: deps.tools });
215046
215444
  registerSessionsMethods(registry4.register.bind(registry4), { sessionManager: deps.sessionManager });
215047
215445
  registerCronMethods(registry4.register.bind(registry4), { cronService: deps.cronService });
215446
+ registerConfigMethods(registry4.register.bind(registry4), {
215447
+ timezoneService: deps.timezoneService,
215448
+ cronService: deps.cronService
215449
+ });
215048
215450
  const tokensSnapshot = new TokensSnapshotService(deps.tradingClient, deps.priceCache);
215049
215451
  registerTradingMethods(registry4.register.bind(registry4), { tradingClient: deps.tradingClient, walletStore: deps.walletStore, alertRules: deps.alertRules, notifications: deps.notifications, newsService: deps.newsService, rssDiscovery: deps.rssDiscoveryService, tweetService: deps.tweetService, xFollowService: deps.xFollowService, preferenceStore: deps.preferenceStore, watchlist: deps.watchlistService, logger: deps.logger, tokensSnapshot, priceCache: deps.priceCache });
215050
215452
  registerApprovalMethods(registry4.register.bind(registry4), { approvalManager: deps.approvalManager });
@@ -216077,6 +216479,7 @@ async function startDaemon(options) {
216077
216479
  tweetService,
216078
216480
  xFollowService,
216079
216481
  preferenceStore,
216482
+ timezoneService,
216080
216483
  security: security2,
216081
216484
  leakDetector,
216082
216485
  skillService,
@@ -216122,6 +216525,7 @@ async function startDaemon(options) {
216122
216525
  tools,
216123
216526
  sessionManager,
216124
216527
  cronService,
216528
+ timezoneService,
216125
216529
  configPath,
216126
216530
  channels: runtime.channelManager.listChannels().map((ch2) => ({ name: ch2.name })),
216127
216531
  tradingClient,
@@ -216163,7 +216567,7 @@ async function startDaemon(options) {
216163
216567
  }));
216164
216568
  runtime.channelManager.startAllChannels();
216165
216569
  if (config3.cron.enableScheduler) {
216166
- cronService.start();
216570
+ cronService.start({ defaults: buildBuiltInJobs(timezoneService.get()) });
216167
216571
  }
216168
216572
  const runner = new BackgroundJobRunner({
216169
216573
  taskAgent: runtime.taskAgent,
@@ -216206,6 +216610,7 @@ var init_daemon = __esm(() => {
216206
216610
  init_plugin();
216207
216611
  init_types5();
216208
216612
  init_pairing_events();
216613
+ init_defaults();
216209
216614
  });
216210
216615
 
216211
216616
  // node_modules/colorette/index.cjs
@@ -218831,7 +219236,7 @@ __export(exports_uninstall, {
218831
219236
  WIN_HANDLE_RELEASE_MS: () => WIN_HANDLE_RELEASE_MS,
218832
219237
  SIGKILL_DELAY_MS: () => SIGKILL_DELAY_MS
218833
219238
  });
218834
- import { existsSync as fsExistsSync, readFileSync as readFileSync20, writeFileSync as writeFileSync15, unlinkSync as unlinkSync9, rmSync as fsRmSync } from "fs";
219239
+ import { existsSync as fsExistsSync, readFileSync as readFileSync19, writeFileSync as writeFileSync14, unlinkSync as unlinkSync9, rmSync as fsRmSync } from "fs";
218835
219240
  import { join as join22 } from "path";
218836
219241
  import { homedir as homedir7 } from "os";
218837
219242
  async function runUninstall(deps) {
@@ -219157,8 +219562,8 @@ async function runUninstallCli() {
219157
219562
  return !isCancel(r2) && r2 === true;
219158
219563
  },
219159
219564
  existsSync: fsExistsSync,
219160
- readFile: (p2) => readFileSync20(p2, "utf8"),
219161
- writeFile: (p2, content) => writeFileSync15(p2, content, "utf8"),
219565
+ readFile: (p2) => readFileSync19(p2, "utf8"),
219566
+ writeFile: (p2, content) => writeFileSync14(p2, content, "utf8"),
219162
219567
  unlink: (p2) => unlinkSync9(p2),
219163
219568
  rmSync: (p2) => fsRmSync(p2, { recursive: true, force: true, maxRetries: 10, retryDelay: 200 }),
219164
219569
  spawn: (cmd, args) => {
@@ -219614,7 +220019,7 @@ init_cli_providers();
219614
220019
 
219615
220020
  // src/onboard/wizard.ts
219616
220021
  init_dist8();
219617
- import { existsSync as existsSync28 } from "fs";
220022
+ import { existsSync as existsSync27 } from "fs";
219618
220023
  init_providers();
219619
220024
  init_oauth3();
219620
220025
  init_secrets();
@@ -219628,7 +220033,7 @@ init_dist8();
219628
220033
  init_dist8();
219629
220034
  init_utils8();
219630
220035
  init_logs();
219631
- import { existsSync as existsSync26 } from "fs";
220036
+ import { existsSync as existsSync25 } from "fs";
219632
220037
 
219633
220038
  // src/onboard/steps/service.ts
219634
220039
  async function runServiceStep(deps) {
@@ -219723,7 +220128,7 @@ async function finalizeOnboard(opts) {
219723
220128
  }
219724
220129
  const execPath = resolveGhostExecPath();
219725
220130
  const bunPath = resolveBunPath();
219726
- if (!existsSync26(execPath)) {
220131
+ if (!existsSync25(execPath)) {
219727
220132
  O2.warn(`Ghost executable not found at ${execPath}. Service may fail to start.`);
219728
220133
  }
219729
220134
  const result = await runServiceStep({
@@ -219799,22 +220204,22 @@ async function startForegroundDaemon(logger) {
219799
220204
  init_models_config();
219800
220205
  import {
219801
220206
  chmodSync,
219802
- existsSync as existsSync27,
219803
- mkdirSync as mkdirSync18,
219804
- readFileSync as readFileSync19,
220207
+ existsSync as existsSync26,
220208
+ mkdirSync as mkdirSync17,
220209
+ readFileSync as readFileSync18,
219805
220210
  renameSync as renameSync4,
219806
220211
  unlinkSync as unlinkSync8,
219807
- writeFileSync as writeFileSync14
220212
+ writeFileSync as writeFileSync13
219808
220213
  } from "fs";
219809
- import { dirname as dirname10 } from "path";
220214
+ import { dirname as dirname9 } from "path";
219810
220215
  var MODELS_FILE_MODE = 384;
219811
220216
  var MODELS_DIR_MODE = 448;
219812
220217
  function readModelsConfig(path4) {
219813
- if (!existsSync27(path4))
220218
+ if (!existsSync26(path4))
219814
220219
  return { kind: "missing" };
219815
220220
  let raw;
219816
220221
  try {
219817
- raw = readFileSync19(path4, "utf-8");
220222
+ raw = readFileSync18(path4, "utf-8");
219818
220223
  } catch (err) {
219819
220224
  return { kind: "malformed", reason: describeError2(err) };
219820
220225
  }
@@ -219875,15 +220280,15 @@ function upsertModel(current, incoming) {
219875
220280
  return next;
219876
220281
  }
219877
220282
  function writeAtomic(path4, contents) {
219878
- mkdirSync18(dirname10(path4), { recursive: true, mode: MODELS_DIR_MODE });
220283
+ mkdirSync17(dirname9(path4), { recursive: true, mode: MODELS_DIR_MODE });
219879
220284
  const tmp = `${path4}.tmp-${process.pid}-${Date.now()}`;
219880
220285
  try {
219881
- writeFileSync14(tmp, contents, { mode: MODELS_FILE_MODE });
220286
+ writeFileSync13(tmp, contents, { mode: MODELS_FILE_MODE });
219882
220287
  chmodSync(tmp, MODELS_FILE_MODE);
219883
220288
  renameSync4(tmp, path4);
219884
220289
  } catch (err) {
219885
220290
  try {
219886
- if (existsSync27(tmp))
220291
+ if (existsSync26(tmp))
219887
220292
  unlinkSync8(tmp);
219888
220293
  } catch {}
219889
220294
  throw err;
@@ -219909,6 +220314,22 @@ function applyUpdateModeChanges(existing, overlay) {
219909
220314
  }
219910
220315
 
219911
220316
  // src/onboard/wizard.ts
220317
+ init_timezone();
220318
+ init_database();
220319
+ init_db();
220320
+ init_registry2();
220321
+ async function persistTimezone(tz, logger) {
220322
+ try {
220323
+ const db2 = initDatabase(getDbPath());
220324
+ await runDbMigrations(db2, DB_MIGRATIONS);
220325
+ const prefs = new PreferenceStore(db2, logger.child({ module: "prefs" }));
220326
+ const tzService = createTimezoneService(prefs);
220327
+ tzService.set(tz);
220328
+ db2.close();
220329
+ } catch (err) {
220330
+ logger.warn({ err }, "onboard: failed to persist timezone");
220331
+ }
220332
+ }
219912
220333
  function validateCustomProviderName(name) {
219913
220334
  if (!name)
219914
220335
  return "Provider name is required.";
@@ -219923,31 +220344,7 @@ function validateCustomProviderName(name) {
219923
220344
  function isLocalBaseUrl(url3) {
219924
220345
  return /^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?(\/|$)/u.test(url3);
219925
220346
  }
219926
- async function validateClaudeCli(binaryPath) {
219927
- const which = Bun.spawnSync({ cmd: ["which", binaryPath] });
219928
- if (which.exitCode !== 0) {
219929
- return { ok: false, error: `Claude Code not found at "${binaryPath}". Install: curl -fsSL https://claude.ai/install.sh | bash` };
219930
- }
219931
- const versionProc = Bun.spawnSync({ cmd: [binaryPath, "--version"] });
219932
- const version3 = versionProc.stdout.toString().trim();
219933
- const authProc = Bun.spawnSync({ cmd: [binaryPath, "auth", "status", "--json"] });
219934
- if (authProc.exitCode !== 0) {
219935
- return { ok: false, version: version3, error: "Not authenticated. Run: claude login" };
219936
- }
219937
- try {
219938
- const auth = JSON.parse(authProc.stdout.toString());
219939
- const authenticated = auth.authenticated ?? auth.loggedIn ?? false;
219940
- if (!authenticated) {
219941
- return { ok: false, version: version3, error: "Not authenticated. Run: claude login" };
219942
- }
219943
- return { ok: true, version: version3, authStatus: auth.plan ?? auth.subscription ?? "authenticated" };
219944
- } catch {
219945
- return { ok: true, version: version3, authStatus: "authenticated" };
219946
- }
219947
- }
219948
- function providerRequiresApiKey(providerId, supportsOAuth) {
219949
- if (providerId === "claude-cli")
219950
- return false;
220347
+ function providerRequiresApiKey(_providerId, supportsOAuth) {
219951
220348
  if (supportsOAuth)
219952
220349
  return false;
219953
220350
  return true;
@@ -219975,6 +220372,8 @@ async function runHeadless(headless, daemonOptions) {
219975
220372
  if (daemonOptions.paper)
219976
220373
  customConfig.paper = daemonOptions.paper;
219977
220374
  saveConfig(customConfig, configPath);
220375
+ const tzForCustom = resolveHeadlessTz(daemonOptions.logger);
220376
+ await persistTimezone(tzForCustom, daemonOptions.logger);
219978
220377
  console.log(`[ghost] Custom provider: ${headless.provider} (from models.json)`);
219979
220378
  console.log(`[ghost] Model: ${modelTrimmed}`);
219980
220379
  if (daemonOptions?.paper) {
@@ -220050,8 +220449,11 @@ async function runHeadless(headless, daemonOptions) {
220050
220449
  await credentials2.set("api_key", apiKey);
220051
220450
  }
220052
220451
  saveConfig(config3, configPath);
220452
+ const tz = resolveHeadlessTz(daemonOptions.logger);
220453
+ await persistTimezone(tz, daemonOptions.logger);
220053
220454
  console.log(`[ghost] Provider: ${providerInfo.label} (${headless.provider})`);
220054
220455
  console.log(`[ghost] Model: ${model}`);
220456
+ console.log(`[ghost] Timezone: ${tz}`);
220055
220457
  if (daemonOptions?.paper) {
220056
220458
  console.log(`[ghost] Mode: Paper trading (${daemonOptions.paper.initialBalance ?? 1e4} USDC)`);
220057
220459
  }
@@ -220059,6 +220461,18 @@ async function runHeadless(headless, daemonOptions) {
220059
220461
  console.log("[ghost] Config saved. Run 'ghost onboard --service' to register the auto-start service, or 'ghost daemon' to start manually.");
220060
220462
  console.log("[ghost] Onboard complete!");
220061
220463
  }
220464
+ function resolveHeadlessTz(logger) {
220465
+ const envTz = process.env["GHOST_TIMEZONE"];
220466
+ if (envTz) {
220467
+ const result = validateTimezone(envTz);
220468
+ if (!result.ok) {
220469
+ console.error(`[ghost] GHOST_TIMEZONE="${envTz}" is invalid: ${result.error}`);
220470
+ process.exit(1);
220471
+ }
220472
+ return result.tz;
220473
+ }
220474
+ return detectHostTimezone();
220475
+ }
220062
220476
  async function runWizard(daemonOptions) {
220063
220477
  if (daemonOptions.headless) {
220064
220478
  const { headless, ...rest4 } = daemonOptions;
@@ -220071,7 +220485,7 @@ async function runWizard(daemonOptions) {
220071
220485
  const credentials2 = new CredentialStore(getCredentialsPath(), secretStore, daemonOptions.logger.child({ module: "credentials" }));
220072
220486
  let config3 = configSchema.parse({});
220073
220487
  let mode = "full";
220074
- if (existsSync28(configPath)) {
220488
+ if (existsSync27(configPath)) {
220075
220489
  const modeAnswer = await _t({
220076
220490
  message: "Existing config found. What would you like to do?",
220077
220491
  options: [
@@ -220090,7 +220504,7 @@ async function runWizard(daemonOptions) {
220090
220504
  This wizard will configure your agent in under 60 seconds.`);
220091
220505
  if (mode === "full" && !daemonOptions.paper) {
220092
220506
  const tradingMode = await _t({
220093
- message: "Step 1/6 \u2014 Select trading mode",
220507
+ message: "Step 1/5 \u2014 Select trading mode",
220094
220508
  options: [
220095
220509
  { value: "paper", label: "Paper trading (simulated, safe to explore)", hint: "10,000 USDC starting balance" },
220096
220510
  { value: "live", label: "Live trading (real funds on Hyperliquid)" }
@@ -220108,11 +220522,11 @@ This wizard will configure your agent in under 60 seconds.`);
220108
220522
  const providers = getProviderList();
220109
220523
  const providerOptions = providers.map((p2) => ({
220110
220524
  value: p2.id,
220111
- label: p2.label,
220525
+ label: `${p2.label} \u2014 ${p2.tierLabel}`,
220112
220526
  hint: p2.description
220113
220527
  }));
220114
220528
  const providerId = await _t({
220115
- message: "Step 2/6 \u2014 Select your AI provider",
220529
+ message: "Step 2/5 \u2014 Select your AI provider",
220116
220530
  options: providerOptions
220117
220531
  });
220118
220532
  if (q(providerId)) {
@@ -220123,18 +220537,6 @@ This wizard will configure your agent in under 60 seconds.`);
220123
220537
  let authMethod = "apikey";
220124
220538
  let customProviderName = "";
220125
220539
  let customApiKey = "";
220126
- if (providerId === "claude-cli") {
220127
- const s1 = fe2();
220128
- s1.start("Validating Claude Code...");
220129
- const validation2 = await validateClaudeCli("claude");
220130
- if (!validation2.ok) {
220131
- s1.stop(`\u2717 ${validation2.error}`);
220132
- pt("Fix the issue above and try again.");
220133
- process.exit(1);
220134
- }
220135
- s1.stop(`\u2713 Claude Code ${validation2.version ?? ""} (${validation2.authStatus})`);
220136
- authMethod = "skip";
220137
- }
220138
220540
  if (providerId === "custom") {
220139
220541
  const nameAnswer = await Ot({
220140
220542
  message: "Provider name (identifier for ~/.ghost/models.json)",
@@ -220181,7 +220583,7 @@ This wizard will configure your agent in under 60 seconds.`);
220181
220583
  { value: "__custom__", label: "Custom model ID (type manually)" }
220182
220584
  ];
220183
220585
  const selected = await _t({
220184
- message: "Step 3/6 \u2014 Select your default model",
220586
+ message: "Step 3/5 \u2014 Select your default model",
220185
220587
  options: modelOptions
220186
220588
  });
220187
220589
  if (q(selected)) {
@@ -220200,7 +220602,7 @@ This wizard will configure your agent in under 60 seconds.`);
220200
220602
  }
220201
220603
  } else {
220202
220604
  const manual = await Ot({
220203
- message: "Step 3/6 \u2014 Enter model ID",
220605
+ message: "Step 3/5 \u2014 Enter model ID",
220204
220606
  placeholder: "e.g. claude-sonnet-4-6"
220205
220607
  });
220206
220608
  if (q(manual)) {
@@ -220213,7 +220615,7 @@ This wizard will configure your agent in under 60 seconds.`);
220213
220615
  let apiKey = "";
220214
220616
  if (providerInfo?.supportsOAuth && authMethod !== "skip") {
220215
220617
  const auth = await _t({
220216
- message: "Step 4/6 \u2014 How do you want to authenticate?",
220618
+ message: "Step 4/5 \u2014 How do you want to authenticate?",
220217
220619
  options: [
220218
220620
  { value: "oauth", label: "OAuth Login (authenticate in browser)", hint: "recommended" },
220219
220621
  { value: "apikey", label: "API Key (paste your key)" },
@@ -220249,7 +220651,7 @@ This wizard will configure your agent in under 60 seconds.`);
220249
220651
  const keyUrl = providerInfo?.apiKeyUrl ? `
220250
220652
  Get your key at: ${providerInfo.apiKeyUrl}` : "";
220251
220653
  const key = await Ot({
220252
- message: `Step 4/6 \u2014 Paste your ${providerInfo?.label ?? providerId} API key${keyUrl}`,
220654
+ message: `Step 4/5 \u2014 Paste your ${providerInfo?.label ?? providerId} API key${keyUrl}`,
220253
220655
  placeholder: "sk-...",
220254
220656
  validate: (v4) => !v4 || v4.length >= 5 ? undefined : "API key seems too short"
220255
220657
  });
@@ -220259,7 +220661,7 @@ This wizard will configure your agent in under 60 seconds.`);
220259
220661
  }
220260
220662
  apiKey = key;
220261
220663
  }
220262
- if (authMethod === "skip" && providerId !== "claude-cli" && providerId !== "custom") {
220664
+ if (authMethod === "skip" && providerId !== "custom") {
220263
220665
  O2.warn("No API key set. Export GHOST_API_KEY before running ghost daemon.");
220264
220666
  }
220265
220667
  if (mode === "update") {
@@ -220317,14 +220719,59 @@ This wizard will configure your agent in under 60 seconds.`);
220317
220719
  config3.paper = daemonOptions.paper;
220318
220720
  }
220319
220721
  saveConfig(config3, configPath);
220722
+ const chosenTz = await promptTimezone();
220723
+ await persistTimezone(chosenTz, daemonOptions.logger);
220320
220724
  O2.success("Configuration saved.");
220321
220725
  if (providerId === "custom") {
220322
220726
  O2.info(`Custom provider "${customProviderName}" written to ${getModelsConfigPath()}`);
220323
220727
  }
220728
+ O2.info(`Timezone: ${chosenTz}`);
220324
220729
  O2.info("Tip: connect channels from the dashboard after starting the daemon.");
220325
220730
  console.log("");
220326
220731
  await finalizeOnboard({ interactive: true, logger: daemonOptions.logger });
220327
220732
  }
220733
+ function listSupportedTimezones() {
220734
+ const fn = Intl.supportedValuesOf;
220735
+ if (typeof fn !== "function")
220736
+ return [];
220737
+ return [...fn("timeZone")].sort();
220738
+ }
220739
+ async function promptTimezone() {
220740
+ const detected = detectHostTimezone();
220741
+ const allZones = listSupportedTimezones();
220742
+ if (allZones.length === 0) {
220743
+ const entered = await Ot({
220744
+ message: "Step 5/5 \u2014 IANA timezone (e.g. America/New_York):",
220745
+ initialValue: detected,
220746
+ validate(value2) {
220747
+ const v4 = validateTimezone(value2);
220748
+ return v4.ok ? undefined : v4.error;
220749
+ }
220750
+ });
220751
+ if (q(entered)) {
220752
+ pt("Setup cancelled.");
220753
+ process.exit(0);
220754
+ }
220755
+ const result = validateTimezone(entered);
220756
+ return result.ok ? result.tz : detected;
220757
+ }
220758
+ const initialValue = allZones.includes(detected) ? detected : allZones[0];
220759
+ const choice = await Ae2({
220760
+ message: "Step 5/5 \u2014 Select your timezone (type to filter)",
220761
+ placeholder: "e.g. berlin, new_york, utc",
220762
+ options: allZones.map((tz) => ({
220763
+ value: tz,
220764
+ label: tz === detected ? `${tz} (detected)` : tz
220765
+ })),
220766
+ initialValue,
220767
+ maxItems: 10
220768
+ });
220769
+ if (q(choice)) {
220770
+ pt("Setup cancelled.");
220771
+ process.exit(0);
220772
+ }
220773
+ return choice;
220774
+ }
220328
220775
  // src/index.ts
220329
220776
  init_errors2();
220330
220777
  init_logger();
@@ -220555,14 +221002,14 @@ async function runSkills(subArgs, opts) {
220555
221002
  const configPath = opts.config ?? getPath();
220556
221003
  const config3 = loadConfig2(configPath);
220557
221004
  const workspaceDir = getWorkspaceDir2();
220558
- const { existsSync: existsSync29 } = await import("fs");
221005
+ const { existsSync: existsSync28 } = await import("fs");
220559
221006
  const { join: join23 } = await import("path");
220560
221007
  let builtinDir;
220561
221008
  if (config3.skills.builtinSkillsDir) {
220562
221009
  builtinDir = expandHome2(config3.skills.builtinSkillsDir);
220563
221010
  } else {
220564
221011
  const candidate = join23(import.meta.dir, "skills", "builtin");
220565
- if (existsSync29(candidate))
221012
+ if (existsSync28(candidate))
220566
221013
  builtinDir = candidate;
220567
221014
  }
220568
221015
  const loader2 = new SkillsLoader2(workspaceDir, builtinDir);