@hyperflow.fun/ghost 0.0.4 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. package/dist/index.js +943 -476
  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
  }
@@ -182043,6 +182238,9 @@ class PaperTradingClient {
182043
182238
  isKnownSymbol(symbol5) {
182044
182239
  return this.marketClient.isKnownSymbol(symbol5);
182045
182240
  }
182241
+ getAllAssets() {
182242
+ return this.marketClient.getAllAssets();
182243
+ }
182046
182244
  getDexUniverses() {
182047
182245
  return this.marketClient.getDexUniverses();
182048
182246
  }
@@ -182386,7 +182584,7 @@ var init_alert_rules = __esm(() => {
182386
182584
 
182387
182585
  // src/services/notifications.ts
182388
182586
  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";
182587
+ 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
182588
  }
182391
182589
  function rowToNotification(r2) {
182392
182590
  let payload;
@@ -183057,6 +183255,23 @@ class NewsService {
183057
183255
  ];
183058
183256
  return this.db.prepare(sql).all(...params).map((r2) => mapRow(r2));
183059
183257
  }
183258
+ listRecentRelevant(sinceTs, limit2 = 20) {
183259
+ const sql = `
183260
+ SELECT id, source_id, external_id, url, title, snippet, image_url, coins,
183261
+ importance, published_at, fetched_at, expires_at, full_summary,
183262
+ ai_relevant, ai_duplicate_of
183263
+ FROM articles
183264
+ WHERE ai_relevant = 1
183265
+ AND full_summary IS NOT NULL
183266
+ AND ai_duplicate_of IS NULL
183267
+ AND dismissed_at IS NULL
183268
+ AND expires_at > unixepoch()
183269
+ AND published_at > ?
183270
+ ORDER BY published_at DESC, id DESC
183271
+ LIMIT ?
183272
+ `;
183273
+ return this.db.prepare(sql).all(sinceTs, limit2).map((r2) => mapRow(r2));
183274
+ }
183060
183275
  searchArticles(opts = {}) {
183061
183276
  const limit2 = Math.min(opts.limit ?? 50, 100);
183062
183277
  const conditions = ["ai_duplicate_of IS NULL"];
@@ -183792,8 +184007,90 @@ class PreferenceStore {
183792
184007
  else
183793
184008
  this.set(NEWS_FILTER_PROMPT_KEY, prompt);
183794
184009
  }
184010
+ getTimezone() {
184011
+ return this.get(USER_TIMEZONE_KEY);
184012
+ }
184013
+ setTimezone(tz) {
184014
+ if (tz.length === 0)
184015
+ this.delete(USER_TIMEZONE_KEY);
184016
+ else
184017
+ this.set(USER_TIMEZONE_KEY, tz);
184018
+ }
184019
+ }
184020
+ var TWEET_FILTER_PROMPT_KEY = "tweets.filter_prompt", NEWS_FILTER_PROMPT_KEY = "news.filter_prompt", USER_TIMEZONE_KEY = "user.timezone";
184021
+
184022
+ // src/scheduler/defaults.ts
184023
+ function detectUserTimezone() {
184024
+ try {
184025
+ return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
184026
+ } catch {
184027
+ return "UTC";
184028
+ }
184029
+ }
184030
+ function buildBuiltInJobs(tz) {
184031
+ return [
184032
+ {
184033
+ name: "morning-briefing",
184034
+ schedule: { kind: "cron", expr: "0 8 * * *", tz },
184035
+ message: BRIEFING_PROMPT,
184036
+ deliver: true
184037
+ },
184038
+ {
184039
+ name: "evening-recap",
184040
+ schedule: { kind: "cron", expr: "0 21 * * *", tz },
184041
+ message: RECAP_PROMPT,
184042
+ deliver: true
184043
+ }
184044
+ ];
183795
184045
  }
183796
- var TWEET_FILTER_PROMPT_KEY = "tweets.filter_prompt", NEWS_FILTER_PROMPT_KEY = "news.filter_prompt";
184046
+ var BRIEFING_PROMPT, RECAP_PROMPT;
184047
+ var init_defaults = __esm(() => {
184048
+ 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.";
184049
+ 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.";
184050
+ });
184051
+
184052
+ // src/services/timezone.ts
184053
+ function validateTimezone(input) {
184054
+ if (typeof input !== "string") {
184055
+ return { ok: false, error: "Timezone must be a string" };
184056
+ }
184057
+ const trimmed = input.trim();
184058
+ if (!trimmed) {
184059
+ return { ok: false, error: "Timezone cannot be empty" };
184060
+ }
184061
+ if (trimmed.includes("\x00")) {
184062
+ return { ok: false, error: "Timezone contains invalid characters" };
184063
+ }
184064
+ if (trimmed.length > 64) {
184065
+ return { ok: false, error: "Timezone exceeds maximum length" };
184066
+ }
184067
+ try {
184068
+ const resolved = new Intl.DateTimeFormat(undefined, { timeZone: trimmed }).resolvedOptions().timeZone;
184069
+ return { ok: true, tz: resolved };
184070
+ } catch {
184071
+ return { ok: false, error: "Unknown timezone" };
184072
+ }
184073
+ }
184074
+ function detectHostTimezone() {
184075
+ return detectUserTimezone();
184076
+ }
184077
+ function createTimezoneService(prefs) {
184078
+ return {
184079
+ get() {
184080
+ return prefs.getTimezone() ?? "UTC";
184081
+ },
184082
+ set(input) {
184083
+ const result = validateTimezone(input);
184084
+ if (!result.ok)
184085
+ return result;
184086
+ prefs.setTimezone(result.tz);
184087
+ return result;
184088
+ }
184089
+ };
184090
+ }
184091
+ var init_timezone = __esm(() => {
184092
+ init_defaults();
184093
+ });
183797
184094
 
183798
184095
  // src/services/x-follows.ts
183799
184096
  function sleep8(ms2) {
@@ -188530,7 +188827,7 @@ class Runner {
188530
188827
  }
188531
188828
  async call(opts) {
188532
188829
  const next = this.inFlight.then(async () => {
188533
- const tools = this.registry.all();
188830
+ const tools = this.registry.taskAgentTools();
188534
188831
  this.agent.state.tools = tools;
188535
188832
  for (const tool2 of tools) {
188536
188833
  if (isOriginAware(tool2))
@@ -188722,7 +189019,9 @@ function emptySnapshot() {
188722
189019
  openOrderIds: [],
188723
189020
  lastRestSyncAtMs: 0,
188724
189021
  recentCancelOids: [],
188725
- recentEmittedFillIds: []
189022
+ recentEmittedFillIds: [],
189023
+ recentEmittedNewsIds: [],
189024
+ lastNewsScanTs: 0
188726
189025
  };
188727
189026
  }
188728
189027
 
@@ -188747,7 +189046,9 @@ class ObserverStateStore {
188747
189046
  openOrderIds: Array.isArray(parsed.openOrderIds) ? parsed.openOrderIds : [],
188748
189047
  lastRestSyncAtMs: typeof parsed.lastRestSyncAtMs === "number" ? parsed.lastRestSyncAtMs : 0,
188749
189048
  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") : []
189049
+ recentEmittedFillIds: Array.isArray(parsed.recentEmittedFillIds) ? parsed.recentEmittedFillIds.filter((o10) => typeof o10 === "string") : [],
189050
+ recentEmittedNewsIds: Array.isArray(parsed.recentEmittedNewsIds) ? parsed.recentEmittedNewsIds.filter((o10) => typeof o10 === "string") : [],
189051
+ lastNewsScanTs: typeof parsed.lastNewsScanTs === "number" ? parsed.lastNewsScanTs : 0
188751
189052
  };
188752
189053
  } catch {
188753
189054
  return emptySnapshot();
@@ -188760,7 +189061,7 @@ class ObserverStateStore {
188760
189061
  this.write.run(KEY_SNAPSHOT, JSON.stringify(emptySnapshot()));
188761
189062
  }
188762
189063
  }
188763
- var RECENT_CANCEL_OIDS_CAP = 500, RECENT_FILL_IDS_CAP = 500, KEY_SNAPSHOT = "snapshot";
189064
+ var RECENT_CANCEL_OIDS_CAP = 500, RECENT_FILL_IDS_CAP = 500, RECENT_NEWS_IDS_CAP = 200, KEY_SNAPSHOT = "snapshot";
188764
189065
 
188765
189066
  // src/observer/detect/positions.ts
188766
189067
  function posKey(symbol5, side) {
@@ -189048,6 +189349,36 @@ function detectCanceledOrders(input) {
189048
189349
  return { events, emittedOids };
189049
189350
  }
189050
189351
 
189352
+ // src/observer/detect/news.ts
189353
+ function detectNews(input) {
189354
+ const events = [];
189355
+ const emittedIds = [];
189356
+ const seenThisTick = new Set;
189357
+ for (const article of input.articles) {
189358
+ if (input.priorEmittedIds.has(article.id))
189359
+ continue;
189360
+ if (seenThisTick.has(article.id))
189361
+ continue;
189362
+ if (article.fullSummary === null)
189363
+ continue;
189364
+ events.push({
189365
+ type: "news",
189366
+ detectedAt: input.nowMs,
189367
+ articleId: article.id,
189368
+ title: article.title,
189369
+ summary: article.fullSummary,
189370
+ source: article.sourceId,
189371
+ url: article.url,
189372
+ publishedAt: article.publishedAt * 1000,
189373
+ importance: article.importance,
189374
+ coins: [...article.coins]
189375
+ });
189376
+ emittedIds.push(article.id);
189377
+ seenThisTick.add(article.id);
189378
+ }
189379
+ return { events, emittedIds };
189380
+ }
189381
+
189051
189382
  // src/observer/diff.ts
189052
189383
  function diffSnapshot(input) {
189053
189384
  const positionsR = detectPositions({
@@ -189079,18 +189410,25 @@ function diffSnapshot(input) {
189079
189410
  prices: input.prices,
189080
189411
  nowMs: input.nowMs
189081
189412
  });
189413
+ const newsR = detectNews({
189414
+ articles: input.articles,
189415
+ priorEmittedIds: new Set(input.prior.recentEmittedNewsIds),
189416
+ nowMs: input.nowMs
189417
+ });
189082
189418
  return {
189083
189419
  events: [
189084
189420
  ...positionsR.events,
189085
189421
  ...fillsR.events,
189086
189422
  ...fallbackEvents,
189087
189423
  ...cancelR.events,
189088
- ...priceTargetR.events
189424
+ ...priceTargetR.events,
189425
+ ...newsR.events
189089
189426
  ],
189090
189427
  nextPositions: positionsR.nextPositions,
189091
189428
  firedAlertIds: priceTargetR.firedIds,
189092
189429
  emittedCancelOids: cancelR.emittedOids,
189093
- emittedFillIds: fillsR.emittedFillIds
189430
+ emittedFillIds: fillsR.emittedFillIds,
189431
+ emittedNewsIds: newsR.emittedIds
189094
189432
  };
189095
189433
  }
189096
189434
  var init_diff = () => {};
@@ -189206,7 +189544,8 @@ var init_judge2 = __esm(() => {
189206
189544
  Type.Literal("order_canceled"),
189207
189545
  Type.Literal("liquidation_risk"),
189208
189546
  Type.Literal("pnl_snapshot"),
189209
- Type.Literal("price_alert")
189547
+ Type.Literal("price_alert"),
189548
+ Type.Literal("news")
189210
189549
  ];
189211
189550
  JudgeResponseSchema = Type.Object({
189212
189551
  decision: Type.Union([Type.Literal("fire"), Type.Literal("silent")]),
@@ -194058,6 +194397,7 @@ function stripCustomTags(text) {
194058
194397
  out = out.replace(/<verdict\s*[^>]*>([\s\S]*?)<\/verdict>/gi, `${SENTINEL_I_OPEN}$1${SENTINEL_I_CLOSE}`);
194059
194398
  out = out.replace(/(\n)(\x00I_OPEN\x00(?:\uD83D\uDC02 |\uD83D\uDC3B |\u3030\uFE0F )?)/g, `$1
194060
194399
  $2`);
194400
+ out = out.replace(/<ask_user_question\s*>([\s\S]*?)<\/ask_user_question>/gi, (_full, inner) => formatAskFallback(inner));
194061
194401
  const chartHint = (attrs) => {
194062
194402
  const symMatch = /\bsymbol\s*=\s*"([^"]+)"/i.exec(attrs);
194063
194403
  const intMatch = /\binterval\s*=\s*"([^"]+)"/i.exec(attrs);
@@ -194078,6 +194418,43 @@ $2`);
194078
194418
  out = out.replace(/<[a-zA-Z][^>]*$/g, "");
194079
194419
  return out;
194080
194420
  }
194421
+ function formatAskFallback(inner) {
194422
+ const questionRe = /<question>([\s\S]*?)<\/question>/gi;
194423
+ const titleRe = /<title>([\s\S]*?)<\/title>/i;
194424
+ const optionsRe = /<options>([\s\S]*?)<\/options>/i;
194425
+ const optionRe = /<option>([\s\S]*?)<\/option>/gi;
194426
+ const lines = [];
194427
+ let m6;
194428
+ let i = 1;
194429
+ while ((m6 = questionRe.exec(inner)) !== null) {
194430
+ const body = m6[1];
194431
+ const title = titleRe.exec(body)?.[1]?.trim();
194432
+ if (!title)
194433
+ continue;
194434
+ let entry = `${i}. ${title}`;
194435
+ const optionsBlock = optionsRe.exec(body)?.[1];
194436
+ if (optionsBlock) {
194437
+ const opts = [];
194438
+ optionRe.lastIndex = 0;
194439
+ let om2;
194440
+ while ((om2 = optionRe.exec(optionsBlock)) !== null) {
194441
+ const v4 = om2[1].trim();
194442
+ if (v4)
194443
+ opts.push(v4);
194444
+ }
194445
+ if (opts.length > 0)
194446
+ entry += `
194447
+ Options: ${opts.join(" / ")}`;
194448
+ }
194449
+ lines.push(entry);
194450
+ i++;
194451
+ }
194452
+ if (lines.length === 0)
194453
+ return "";
194454
+ return `
194455
+ ${lines.join(`
194456
+ `)}`;
194457
+ }
194081
194458
  var CHART_RE_PAIRED, CHART_RE_SELF;
194082
194459
  var init_tags = __esm(() => {
194083
194460
  CHART_RE_PAIRED = /<chart\s*([^>]*)>([\s\S]*?)<\/chart>/gi;
@@ -195524,6 +195901,7 @@ class ObserverLoop {
195524
195901
  const currentOpenOrderIds = rest4.openOrders.map((o10) => o10.orderId);
195525
195902
  const prices = this.deps.priceCache.snapshot();
195526
195903
  const alertRules = this.deps.alertRules.list();
195904
+ const articles = rest4.positions.length === 0 ? [] : this.deps.newsService.listRecentRelevant(newsScanSinceTs(prior.lastNewsScanTs, nowMs));
195527
195905
  const diff = diffSnapshot({
195528
195906
  prior,
195529
195907
  positions: rest4.positions,
@@ -195533,6 +195911,7 @@ class ObserverLoop {
195533
195911
  alertRules,
195534
195912
  prices,
195535
195913
  liqProgressThreshold: this.deps.config.liquidationProgressThreshold,
195914
+ articles,
195536
195915
  nowMs
195537
195916
  });
195538
195917
  for (const id of diff.firedAlertIds) {
@@ -195565,7 +195944,9 @@ class ObserverLoop {
195565
195944
  openOrderIds: currentOpenOrderIds,
195566
195945
  lastRestSyncAtMs: synced ? nowMs : prior.lastRestSyncAtMs,
195567
195946
  recentCancelOids: mergeCancelOids(prior.recentCancelOids, diff.emittedCancelOids),
195568
- recentEmittedFillIds: mergeBoundedIds(prior.recentEmittedFillIds, diff.emittedFillIds, RECENT_FILL_IDS_CAP)
195947
+ recentEmittedFillIds: mergeBoundedIds(prior.recentEmittedFillIds, diff.emittedFillIds, RECENT_FILL_IDS_CAP),
195948
+ recentEmittedNewsIds: mergeBoundedIds(prior.recentEmittedNewsIds, diff.emittedNewsIds, RECENT_NEWS_IDS_CAP),
195949
+ lastNewsScanTs: maxArticlePublishedAtSec(articles, prior.lastNewsScanTs)
195569
195950
  };
195570
195951
  const scanCounts = {
195571
195952
  positions: rest4.positions.length,
@@ -195654,6 +196035,7 @@ class ObserverLoop {
195654
196035
  alertRules: this.deps.alertRules.list(),
195655
196036
  prices: this.deps.priceCache.snapshot(),
195656
196037
  liqProgressThreshold: this.deps.config.liquidationProgressThreshold,
196038
+ articles: [],
195657
196039
  nowMs
195658
196040
  });
195659
196041
  this.store.save({
@@ -195662,7 +196044,9 @@ class ObserverLoop {
195662
196044
  openOrderIds: rest4.openOrders.map((o10) => o10.orderId),
195663
196045
  lastRestSyncAtMs: syncDue ? nowMs : prior.lastRestSyncAtMs,
195664
196046
  recentCancelOids: mergeCancelOids(prior.recentCancelOids, diff.emittedCancelOids),
195665
- recentEmittedFillIds: mergeBoundedIds(prior.recentEmittedFillIds, diff.emittedFillIds, RECENT_FILL_IDS_CAP)
196047
+ recentEmittedFillIds: mergeBoundedIds(prior.recentEmittedFillIds, diff.emittedFillIds, RECENT_FILL_IDS_CAP),
196048
+ recentEmittedNewsIds: prior.recentEmittedNewsIds,
196049
+ lastNewsScanTs: prior.lastNewsScanTs
195666
196050
  });
195667
196051
  } catch (err) {
195668
196052
  this.deps.logger.warn({ err }, "observer: baseline refresh failed under confirm gate");
@@ -195802,11 +196186,25 @@ function mapKindFromEventType(t2) {
195802
196186
  return "order_canceled";
195803
196187
  case "order_filled":
195804
196188
  return "order_filled";
196189
+ case "news":
196190
+ return "news";
195805
196191
  default:
195806
196192
  return "proactive";
195807
196193
  }
195808
196194
  }
195809
- var RECENT_CHAT_MAX = 20, MIN_PRICE_PCT_DELTA = 0.5, MIN_COOLDOWN_MS;
196195
+ function newsScanSinceTs(lastScanTs, nowMs) {
196196
+ const slidingFloor = Math.floor(nowMs / 1000) - NEWS_LOOKBACK_FLOOR_SEC;
196197
+ return lastScanTs > slidingFloor ? lastScanTs : slidingFloor;
196198
+ }
196199
+ function maxArticlePublishedAtSec(articles, prior) {
196200
+ let max = prior;
196201
+ for (const a of articles) {
196202
+ if (a.publishedAt > max)
196203
+ max = a.publishedAt;
196204
+ }
196205
+ return max;
196206
+ }
196207
+ var RECENT_CHAT_MAX = 20, MIN_PRICE_PCT_DELTA = 0.5, MIN_COOLDOWN_MS, NEWS_LOOKBACK_FLOOR_SEC;
195810
196208
  var init_loop = __esm(() => {
195811
196209
  init_diff();
195812
196210
  init_judge2();
@@ -195814,11 +196212,12 @@ var init_loop = __esm(() => {
195814
196212
  init_channels();
195815
196213
  init_types5();
195816
196214
  MIN_COOLDOWN_MS = 60 * 60 * 1000;
196215
+ NEWS_LOOKBACK_FLOOR_SEC = 30 * 60;
195817
196216
  });
195818
196217
 
195819
196218
  // src/runtime.ts
195820
196219
  import { join as join14 } from "path";
195821
- import { existsSync as existsSync18, mkdirSync as mkdirSync14, copyFileSync as copyFileSync2, readdirSync as readdirSync7 } from "fs";
196220
+ import { existsSync as existsSync17, mkdirSync as mkdirSync13, copyFileSync as copyFileSync2, readdirSync as readdirSync7 } from "fs";
195822
196221
  async function createRuntime(options) {
195823
196222
  const configPath = options.configPath ?? getConfigPath();
195824
196223
  const rawConfig = loadConfig(configPath);
@@ -195858,12 +196257,14 @@ async function createRuntime(options) {
195858
196257
  const skillsLoader = new SkillsLoader(workspaceDir, builtinSkillsDir);
195859
196258
  const skillService = new SkillService(db2, skillsLoader);
195860
196259
  skillService.syncState();
195861
- const contextBuilder = new ContextBuilder({ workspaceDir, model: config3.model }, memoryStore, skillsLoader);
196260
+ const preferenceStore = new PreferenceStore(db2, logger.child({ module: "prefs" }));
196261
+ const timezoneService = createTimezoneService(preferenceStore);
196262
+ const contextBuilder = new ContextBuilder({ workspaceDir, model: config3.model, getTimezone: () => timezoneService.get() }, memoryStore, skillsLoader);
195862
196263
  contextBuilder.setDisabledSkillsProvider(() => skillService.getDisabledNames());
195863
- const cronService = new CronService(getCronStorePath());
196264
+ const cronService = new CronService(db2);
195864
196265
  const tools = createToolRegistry(security2, {
195865
196266
  cronService,
195866
- defaultTimezone: config3.cron.timezone,
196267
+ timezoneService,
195867
196268
  memoryStore,
195868
196269
  logger: logger.child({ module: "tool" })
195869
196270
  });
@@ -195874,7 +196275,6 @@ async function createRuntime(options) {
195874
196275
  const watchlistService = new WatchlistService(db2);
195875
196276
  const newsService = new NewsService(db2, watchlistService, credentials2, logger.child({ module: "news" }));
195876
196277
  const tweetService = new TweetService(db2, logger.child({ module: "tweets" }));
195877
- const preferenceStore = new PreferenceStore(db2, logger.child({ module: "prefs" }));
195878
196278
  const xQueryIdCache = new XQueryIdCache;
195879
196279
  const xFollowService = new XFollowService(db2, credentials2, xQueryIdCache, logger.child({ module: "x-follows" }));
195880
196280
  const taIndicators = new TaIndicatorService(tradingClient);
@@ -195913,7 +196313,7 @@ async function createRuntime(options) {
195913
196313
  eventBus,
195914
196314
  logger,
195915
196315
  customModelRegistry,
195916
- confirmDeps: { getConfirmService: () => confirmServiceRef }
196316
+ confirmDeps: { getConfirmService: () => confirmServiceRef, approvalManager }
195917
196317
  });
195918
196318
  const taskAgent = createAgent({
195919
196319
  config: config3,
@@ -195930,7 +196330,8 @@ async function createRuntime(options) {
195930
196330
  logger: logger.child({ module: "task-agent" }),
195931
196331
  customModelRegistry,
195932
196332
  confirmDeps: { getConfirmService: () => null },
195933
- bypassConfirm: true
196333
+ bypassConfirm: true,
196334
+ taskMode: true
195934
196335
  });
195935
196336
  const runner = new Runner(taskAgent, sessionManager, tools, logger.child({ module: "runner" }));
195936
196337
  const rssDiscoveryService = new RssDiscoveryService(runner, logger.child({ module: "rss-discovery" }));
@@ -195997,7 +196398,7 @@ async function createRuntime(options) {
195997
196398
  tools.register(t2);
195998
196399
  contextBuilder.setTools(tools.all().map((t2) => ({ name: t2.name, description: t2.description })));
195999
196400
  agent3.state.tools = tools.all();
196000
- taskAgent.state.tools = tools.all();
196401
+ taskAgent.state.tools = tools.taskAgentTools();
196001
196402
  setupClaudeCliProvider({
196002
196403
  config: config3,
196003
196404
  logger,
@@ -196037,6 +196438,7 @@ async function createRuntime(options) {
196037
196438
  config: config3.observer,
196038
196439
  tradingClient,
196039
196440
  alertRules,
196441
+ newsService,
196040
196442
  notifications,
196041
196443
  priceCache,
196042
196444
  approvalManager,
@@ -196082,6 +196484,7 @@ async function createRuntime(options) {
196082
196484
  rssDiscoveryService,
196083
196485
  tweetService,
196084
196486
  preferenceStore,
196487
+ timezoneService,
196085
196488
  xFollowService,
196086
196489
  skillService,
196087
196490
  chartSeries,
@@ -196115,16 +196518,16 @@ function getApiKey(oauthManager, credentials2, customModelRegistry) {
196115
196518
  };
196116
196519
  }
196117
196520
  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);
196521
+ 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
196522
  return new Agent(initialOptions);
196120
196523
  }
196121
- function buildAgentOptions(config3, model, security2, leakDetector, oauthManager, tools, systemPrompt, credentials2, extraReadDirs, approvalManager, eventBus, logger, customModelRegistry, confirmDeps, bypassConfirm = false) {
196524
+ function buildAgentOptions(config3, model, security2, leakDetector, oauthManager, tools, systemPrompt, credentials2, extraReadDirs, approvalManager, eventBus, logger, customModelRegistry, confirmDeps, bypassConfirm = false, taskMode = false) {
196122
196525
  const effectiveThinkingLevel = shouldForceThinkingOff({ id: model.id, baseUrl: model.baseUrl ?? "" }) ? "off" : config3.agent.thinkingLevel;
196123
196526
  return {
196124
196527
  initialState: {
196125
196528
  systemPrompt,
196126
196529
  model,
196127
- tools: config3.provider === "claude-cli" ? [] : tools.all(),
196530
+ tools: config3.provider === "claude-cli" ? [] : taskMode ? tools.taskAgentTools() : tools.all(),
196128
196531
  thinkingLevel: effectiveThinkingLevel
196129
196532
  },
196130
196533
  getApiKey: config3.provider === "claude-cli" ? async () => "claude-cli-no-key-needed" : getApiKey(oauthManager, credentials2, customModelRegistry),
@@ -196213,6 +196616,7 @@ async function runBatchedConfirm(assistantMessage, callName, callArgs, batchCach
196213
196616
  let title;
196214
196617
  const lines = [];
196215
196618
  let steps;
196619
+ let extras;
196216
196620
  if (isMulti) {
196217
196621
  title = `Confirm ${confirmable.length} actions?`;
196218
196622
  const stepList = [];
@@ -196223,14 +196627,22 @@ async function runBatchedConfirm(assistantMessage, callName, callArgs, batchCach
196223
196627
  stepList.push(`${head}${tail}`);
196224
196628
  }
196225
196629
  steps = stepList;
196630
+ extras = undefined;
196226
196631
  } else {
196227
196632
  const only = confirmable[0];
196633
+ const args = only.args ?? {};
196634
+ const symbol5 = typeof args.symbol === "string" ? args.symbol : undefined;
196228
196635
  const desc = describeConfirm(only.name, only.args);
196229
196636
  title = desc.title;
196230
196637
  lines.push(...desc.bullets);
196638
+ extras = {
196639
+ wizard: desc.wizard,
196640
+ suggestedValue: desc.suggestedValue,
196641
+ symbol: symbol5
196642
+ };
196231
196643
  }
196232
196644
  try {
196233
- const res = await confirmService.confirm(title, { lines, steps });
196645
+ const res = await confirmService.confirm(title, { lines, steps }, extras);
196234
196646
  return { decision: res.decision, reason: res.reason };
196235
196647
  } catch (err) {
196236
196648
  logger.warn({ err }, "confirm service threw; treating as rejected");
@@ -196379,19 +196791,19 @@ function makeTransformContext(maxTokens) {
196379
196791
  }
196380
196792
  function resolveDefaultBuiltinSkillsDir() {
196381
196793
  const candidate = join14(import.meta.dir, "skills", "builtin");
196382
- return existsSync18(candidate) ? candidate : undefined;
196794
+ return existsSync17(candidate) ? candidate : undefined;
196383
196795
  }
196384
196796
  function seedWorkspaceTemplates(workspaceDir) {
196385
196797
  const templatesDir = join14(import.meta.dir, "templates");
196386
- if (!existsSync18(templatesDir))
196798
+ if (!existsSync17(templatesDir))
196387
196799
  return;
196388
196800
  try {
196389
- mkdirSync14(workspaceDir, { recursive: true });
196801
+ mkdirSync13(workspaceDir, { recursive: true });
196390
196802
  for (const entry of readdirSync7(templatesDir, { withFileTypes: true })) {
196391
196803
  if (!entry.isFile())
196392
196804
  continue;
196393
196805
  const target = join14(workspaceDir, entry.name);
196394
- if (!existsSync18(target))
196806
+ if (!existsSync17(target))
196395
196807
  copyFileSync2(join14(templatesDir, entry.name), target);
196396
196808
  }
196397
196809
  } catch {}
@@ -196443,6 +196855,7 @@ var init_runtime2 = __esm(() => {
196443
196855
  init_news();
196444
196856
  init_rss_discovery();
196445
196857
  init_tweets();
196858
+ init_timezone();
196446
196859
  init_x_follows();
196447
196860
  init_x_query_ids();
196448
196861
  init_ta_indicators();
@@ -196626,14 +197039,6 @@ function getProviderList() {
196626
197039
  });
196627
197040
  }
196628
197041
  }
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
197042
  list.push({
196638
197043
  id: "custom",
196639
197044
  label: "Custom",
@@ -196664,9 +197069,6 @@ function getProviderList() {
196664
197069
  return list;
196665
197070
  }
196666
197071
  function getModelList(providerId) {
196667
- if (providerId === "claude-cli") {
196668
- return getClaudeCliModels();
196669
- }
196670
197072
  try {
196671
197073
  const raw = getModels(providerId).map((m6) => ({ id: m6.id, name: m6.name || m6.id }));
196672
197074
  return filterModelCatalog(providerId, raw);
@@ -196677,7 +197079,6 @@ function getModelList(providerId) {
196677
197079
  var OAUTH_PROVIDERS2, PROVIDER_META, TIER_LABELS;
196678
197080
  var init_providers = __esm(() => {
196679
197081
  init_dist();
196680
- init_models6();
196681
197082
  init_model_catalog();
196682
197083
  OAUTH_PROVIDERS2 = new Set(["anthropic", "openai-codex", "github-copilot", "google-gemini-cli", "google-antigravity"]);
196683
197084
  PROVIDER_META = {
@@ -196685,7 +197086,6 @@ var init_providers = __esm(() => {
196685
197086
  anthropic: { label: "Anthropic", description: "Claude Sonnet & Opus (direct)", tier: 0, apiKeyUrl: "https://console.anthropic.com/settings/keys" },
196686
197087
  openai: { label: "OpenAI", description: "GPT-4o, GPT-5 (direct)", tier: 0, apiKeyUrl: "https://platform.openai.com/api-keys" },
196687
197088
  "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
197089
  google: { label: "Google Gemini", description: "Gemini 2.0 Flash & Pro", tier: 0, apiKeyUrl: "https://aistudio.google.com/app/apikey" },
196690
197090
  "google-gemini-cli": { label: "Google Gemini CLI", description: "Gemini via CLI auth", tier: 0 },
196691
197091
  xai: { label: "xAI", description: "Grok 3 & 4", tier: 0, apiKeyUrl: "https://console.x.ai" },
@@ -196722,17 +197122,15 @@ __export(exports_cli_providers, {
196722
197122
  listModels: () => listModels
196723
197123
  });
196724
197124
  function requiresApiKey(provider) {
196725
- if (provider.id === "claude-cli")
196726
- return false;
196727
- if (provider.supportsOAuth)
196728
- return false;
196729
- return true;
197125
+ return !provider.supportsOAuth;
196730
197126
  }
196731
197127
  function listProviders() {
196732
197128
  const builtIn = getProviderList().map((p) => ({
196733
197129
  id: p.id,
196734
197130
  label: p.label,
196735
197131
  description: p.description,
197132
+ tier: p.tier,
197133
+ tierLabel: p.tierLabel,
196736
197134
  requiresApiKey: requiresApiKey(p),
196737
197135
  supportsOAuth: p.supportsOAuth,
196738
197136
  apiKeyUrl: p.apiKeyUrl ?? null
@@ -196743,6 +197141,8 @@ function listProviders() {
196743
197141
  id: name,
196744
197142
  label: name,
196745
197143
  description: "Custom provider (from ~/.ghost/models.json)",
197144
+ tier: 4,
197145
+ tierLabel: "\uD83D\uDD27 Custom",
196746
197146
  requiresApiKey: true,
196747
197147
  supportsOAuth: false,
196748
197148
  apiKeyUrl: null,
@@ -198796,11 +199196,11 @@ ${je2}${r2.trimStart()}`), s2 = 3 + ne(r2.trimStart()).length);
198796
199196
  });
198797
199197
 
198798
199198
  // src/services/os/utils.ts
198799
- import { accessSync, constants, existsSync as existsSync19, mkdirSync as mkdirSync15, realpathSync as realpathSync2 } from "fs";
199199
+ import { accessSync, constants, existsSync as existsSync18, mkdirSync as mkdirSync14, realpathSync as realpathSync2 } from "fs";
198800
199200
  import { join as join15 } from "path";
198801
199201
  import { homedir as homedir3 } from "os";
198802
199202
  function isExecutable(filePath) {
198803
- if (!existsSync19(filePath))
199203
+ if (!existsSync18(filePath))
198804
199204
  return false;
198805
199205
  try {
198806
199206
  accessSync(filePath, constants.X_OK);
@@ -198847,8 +199247,8 @@ function resolveBunPath() {
198847
199247
  return candidates[0];
198848
199248
  }
198849
199249
  function ensureLogDir(dir) {
198850
- if (!existsSync19(dir)) {
198851
- mkdirSync15(dir, { recursive: true });
199250
+ if (!existsSync18(dir)) {
199251
+ mkdirSync14(dir, { recursive: true });
198852
199252
  }
198853
199253
  }
198854
199254
  function defaultLogDir() {
@@ -198914,7 +199314,7 @@ __export(exports_launchd, {
198914
199314
  LaunchdController: () => LaunchdController
198915
199315
  });
198916
199316
  import { spawnSync } from "child_process";
198917
- import { existsSync as existsSync20, unlinkSync as unlinkSync6, writeFileSync as writeFileSync11, rmSync as rmSync5 } from "fs";
199317
+ import { existsSync as existsSync19, unlinkSync as unlinkSync6, writeFileSync as writeFileSync10, rmSync as rmSync5 } from "fs";
198918
199318
  import { join as join16 } from "path";
198919
199319
  import { homedir as homedir4 } from "os";
198920
199320
  function plistPath() {
@@ -198964,7 +199364,7 @@ class LaunchdController {
198964
199364
  stderrLog,
198965
199365
  env: opts.env ?? {}
198966
199366
  });
198967
- writeFileSync11(definitionPath, plist, { encoding: "utf8", mode: 420 });
199367
+ writeFileSync10(definitionPath, plist, { encoding: "utf8", mode: 420 });
198968
199368
  const domain2 = guiDomain();
198969
199369
  const uid = process.getuid?.() ?? 0;
198970
199370
  const serviceTarget = `${domain2}/${LABEL}`;
@@ -199014,12 +199414,12 @@ class LaunchdController {
199014
199414
  const domain2 = guiDomain();
199015
199415
  const definition = plistPath();
199016
199416
  launchctl(["bootout", domain2, definition]);
199017
- if (existsSync20(definition)) {
199417
+ if (existsSync19(definition)) {
199018
199418
  unlinkSync6(definition);
199019
199419
  }
199020
199420
  if (opts.purgeLogs) {
199021
199421
  const logDir = defaultLogDir();
199022
- if (existsSync20(logDir)) {
199422
+ if (existsSync19(logDir)) {
199023
199423
  rmSync5(logDir, { recursive: true, force: true });
199024
199424
  }
199025
199425
  }
@@ -199103,8 +199503,8 @@ __export(exports_systemd, {
199103
199503
  SystemdController: () => SystemdController
199104
199504
  });
199105
199505
  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";
199506
+ import { copyFileSync as copyFileSync3, existsSync as existsSync20, mkdirSync as mkdirSync15, rmSync as rmSync6, unlinkSync as unlinkSync7, writeFileSync as writeFileSync11 } from "fs";
199507
+ import { dirname as dirname8, join as join17 } from "path";
199108
199508
  import { homedir as homedir5 } from "os";
199109
199509
  function unitPath() {
199110
199510
  return join17(homedir5(), ".config", "systemd", "user", SERVICE_NAME);
@@ -199148,11 +199548,11 @@ class SystemdController {
199148
199548
  assertSystemdAvailable();
199149
199549
  ensureLogDir(opts.logDir);
199150
199550
  const path4 = unitPath();
199151
- const dir = dirname9(path4);
199152
- if (!existsSync21(dir)) {
199153
- mkdirSync16(dir, { recursive: true });
199551
+ const dir = dirname8(path4);
199552
+ if (!existsSync20(dir)) {
199553
+ mkdirSync15(dir, { recursive: true });
199154
199554
  }
199155
- if (existsSync21(path4)) {
199555
+ if (existsSync20(path4)) {
199156
199556
  copyFileSync3(path4, `${path4}.bak`);
199157
199557
  }
199158
199558
  const unit = buildUnit({
@@ -199165,7 +199565,7 @@ class SystemdController {
199165
199565
  GHOST_LOG_DIR: opts.logDir
199166
199566
  }
199167
199567
  });
199168
- writeFileSync12(path4, unit, "utf8");
199568
+ writeFileSync11(path4, unit, "utf8");
199169
199569
  const reload = systemctlStrict("daemon-reload");
199170
199570
  if (!reload.ok) {
199171
199571
  const msg = `daemon-reload failed: ${reload.stderr || reload.stdout}`;
@@ -199211,7 +199611,7 @@ class SystemdController {
199211
199611
  }
199212
199612
  for (const file3 of [path4, `${path4}.bak`]) {
199213
199613
  try {
199214
- if (existsSync21(file3)) {
199614
+ if (existsSync20(file3)) {
199215
199615
  unlinkSync7(file3);
199216
199616
  }
199217
199617
  } catch {
@@ -199222,7 +199622,7 @@ class SystemdController {
199222
199622
  if (opts.purgeLogs) {
199223
199623
  const logDir = defaultLogDir();
199224
199624
  try {
199225
- if (existsSync21(logDir)) {
199625
+ if (existsSync20(logDir)) {
199226
199626
  rmSync6(logDir, { recursive: true, force: true });
199227
199627
  }
199228
199628
  } catch {
@@ -199240,7 +199640,7 @@ class SystemdController {
199240
199640
  if (stdout === "active") {
199241
199641
  return "running";
199242
199642
  }
199243
- if (existsSync21(unitPath())) {
199643
+ if (existsSync20(unitPath())) {
199244
199644
  return "stopped";
199245
199645
  }
199246
199646
  return "not-installed";
@@ -199262,7 +199662,7 @@ __export(exports_schtasks, {
199262
199662
  });
199263
199663
  import { spawnSync as spawnSync3 } from "child_process";
199264
199664
  import { Buffer as Buffer2 } from "buffer";
199265
- import { existsSync as existsSync22, mkdirSync as mkdirSync17, writeFileSync as writeFileSync13, rmSync as rmSync7 } from "fs";
199665
+ import { existsSync as existsSync21, mkdirSync as mkdirSync16, writeFileSync as writeFileSync12, rmSync as rmSync7 } from "fs";
199266
199666
  import { join as join18 } from "path";
199267
199667
  import { homedir as homedir6, tmpdir } from "os";
199268
199668
  function launcherPath() {
@@ -199404,7 +199804,7 @@ function buildScheduledTaskXml(invisibleVbs, userId) {
199404
199804
  function writeTaskXml(xml) {
199405
199805
  const xmlPath = join18(tmpdir(), `ghost-task-${process.pid}.xml`);
199406
199806
  const bom = Buffer2.from([255, 254]);
199407
- writeFileSync13(xmlPath, Buffer2.concat([bom, Buffer2.from(xml, "utf16le")]));
199807
+ writeFileSync12(xmlPath, Buffer2.concat([bom, Buffer2.from(xml, "utf16le")]));
199408
199808
  return xmlPath;
199409
199809
  }
199410
199810
  function killOrphanedSupervisorChain() {
@@ -199438,17 +199838,17 @@ class SchtasksController {
199438
199838
  }
199439
199839
  ensureLogDir(opts.logDir);
199440
199840
  const stateDir = join18(homedir6(), ".ghost", "state");
199441
- if (!existsSync22(stateDir)) {
199442
- mkdirSync17(stateDir, { recursive: true });
199841
+ if (!existsSync21(stateDir)) {
199842
+ mkdirSync16(stateDir, { recursive: true });
199443
199843
  }
199444
199844
  const legacy = legacyStartupPath();
199445
- if (existsSync22(legacy)) {
199845
+ if (existsSync21(legacy)) {
199446
199846
  rmSync7(legacy, RM_RETRY_OPTS);
199447
199847
  }
199448
199848
  const launcher = launcherPath();
199449
- writeFileSync13(launcher, buildLauncherCmd(opts.bunPath, opts.execPath, opts.env), { encoding: "utf8" });
199849
+ writeFileSync12(launcher, buildLauncherCmd(opts.bunPath, opts.execPath, opts.env), { encoding: "utf8" });
199450
199850
  const invisibleVbs = invisibleLauncherPath();
199451
- writeFileSync13(invisibleVbs, buildInvisibleVbs(launcher), { encoding: "utf8" });
199851
+ writeFileSync12(invisibleVbs, buildInvisibleVbs(launcher), { encoding: "utf8" });
199452
199852
  const create4 = createScheduledTask(invisibleVbs, taskUser);
199453
199853
  if (create4.status !== 0) {
199454
199854
  return {
@@ -199493,20 +199893,20 @@ class SchtasksController {
199493
199893
  await new Promise((resolve) => setTimeout(resolve, 500));
199494
199894
  schtasks(["/Delete", "/F", "/TN", TASK_NAME]);
199495
199895
  const launcher = launcherPath();
199496
- if (existsSync22(launcher)) {
199896
+ if (existsSync21(launcher)) {
199497
199897
  rmSync7(launcher, RM_RETRY_OPTS);
199498
199898
  }
199499
199899
  const invisibleVbs = invisibleLauncherPath();
199500
- if (existsSync22(invisibleVbs)) {
199900
+ if (existsSync21(invisibleVbs)) {
199501
199901
  rmSync7(invisibleVbs, RM_RETRY_OPTS);
199502
199902
  }
199503
199903
  const legacy = legacyStartupPath();
199504
- if (existsSync22(legacy)) {
199904
+ if (existsSync21(legacy)) {
199505
199905
  rmSync7(legacy, RM_RETRY_OPTS);
199506
199906
  }
199507
199907
  if (opts.purgeLogs) {
199508
199908
  const logDir = defaultLogDir();
199509
- if (existsSync22(logDir)) {
199909
+ if (existsSync21(logDir)) {
199510
199910
  rmSync7(logDir, { recursive: true, ...RM_RETRY_OPTS });
199511
199911
  }
199512
199912
  }
@@ -212398,7 +212798,7 @@ function handleHealth() {
212398
212798
  }
212399
212799
 
212400
212800
  // src/gateway/static.ts
212401
- import { existsSync as existsSync23 } from "fs";
212801
+ import { existsSync as existsSync22 } from "fs";
212402
212802
  import { join as join20, extname as extname3 } from "path";
212403
212803
  function mime2(path4) {
212404
212804
  return MIME[extname3(path4).toLowerCase()] ?? "application/octet-stream";
@@ -212406,7 +212806,7 @@ function mime2(path4) {
212406
212806
  function resolveWebDist(candidates) {
212407
212807
  const list = candidates ?? defaultWebDistCandidates();
212408
212808
  for (const c4 of list) {
212409
- if (existsSync23(join20(c4, "index.html")))
212809
+ if (existsSync22(join20(c4, "index.html")))
212410
212810
  return c4;
212411
212811
  }
212412
212812
  return null;
@@ -212661,7 +213061,7 @@ function semverGt(a, b5) {
212661
213061
  }
212662
213062
 
212663
213063
  // src/update/version.ts
212664
- import { readFileSync as readFileSync17, existsSync as existsSync24 } from "fs";
213064
+ import { readFileSync as readFileSync16, existsSync as existsSync23 } from "fs";
212665
213065
  import { join as join21 } from "path";
212666
213066
  function resolvePackageJsonPath(candidates) {
212667
213067
  const list = candidates ?? [
@@ -212670,7 +213070,7 @@ function resolvePackageJsonPath(candidates) {
212670
213070
  join21(process.cwd(), "package.json")
212671
213071
  ];
212672
213072
  for (const p2 of list) {
212673
- if (existsSync24(p2))
213073
+ if (existsSync23(p2))
212674
213074
  return p2;
212675
213075
  }
212676
213076
  return null;
@@ -212692,7 +213092,7 @@ function getCurrentVersion(pkgPath) {
212692
213092
  if (!resolved)
212693
213093
  return UNKNOWN_VERSION;
212694
213094
  try {
212695
- const pkg = JSON.parse(readFileSync17(resolved, "utf-8"));
213095
+ const pkg = JSON.parse(readFileSync16(resolved, "utf-8"));
212696
213096
  if (pkg.version && pkg.version.length > 0)
212697
213097
  return pkg.version;
212698
213098
  } catch {}
@@ -212749,14 +213149,14 @@ var init_status = __esm(() => {
212749
213149
  });
212750
213150
 
212751
213151
  // src/gateway/memory.ts
212752
- import { readFileSync as readFileSync18, existsSync as existsSync25 } from "fs";
213152
+ import { readFileSync as readFileSync17, existsSync as existsSync24 } from "fs";
212753
213153
  function registerMemoryMethods(register, deps) {
212754
213154
  register("memory.get", async () => {
212755
213155
  const memory = deps.memoryStore.readLongTerm();
212756
213156
  let history = "";
212757
- if (existsSync25(deps.memoryStore.historyFile)) {
213157
+ if (existsSync24(deps.memoryStore.historyFile)) {
212758
213158
  try {
212759
- history = readFileSync18(deps.memoryStore.historyFile, "utf-8");
213159
+ history = readFileSync17(deps.memoryStore.historyFile, "utf-8");
212760
213160
  } catch {}
212761
213161
  }
212762
213162
  return { memory, history };
@@ -212919,6 +213319,22 @@ function registerCronMethods(register, deps) {
212919
213319
  });
212920
213320
  }
212921
213321
 
213322
+ // src/gateway/config.ts
213323
+ function registerConfigMethods(register, deps) {
213324
+ register("config.timezone.get", async () => ({
213325
+ tz: deps.timezoneService.get()
213326
+ }));
213327
+ register("config.timezone.set", async (_ctx, payload) => {
213328
+ const p2 = payload;
213329
+ const result = deps.timezoneService.set(p2.tz);
213330
+ if (!result.ok) {
213331
+ return { ok: false, error: result.error };
213332
+ }
213333
+ const updatedJobs = deps.cronService.updateBuiltinJobsTimezone(result.tz);
213334
+ return { ok: true, tz: result.tz, updatedJobs };
213335
+ });
213336
+ }
213337
+
212922
213338
  // src/gateway/route-orchestrator-error.ts
212923
213339
  function routeOrchestratorError(runId, classified, emit) {
212924
213340
  if (classified.type === "TOOL_BLOCKED") {
@@ -214902,11 +215318,12 @@ class TokensSnapshotService {
214902
215318
  this.priceCache = priceCache;
214903
215319
  }
214904
215320
  build() {
214905
- const tokens = this.client.getAllAssetNames();
215321
+ const assets = this.client.getAllAssets();
214906
215322
  const prices = {};
214907
215323
  const prevDayPrices = {};
214908
215324
  const maxLeverages = {};
214909
- for (const symbol5 of tokens) {
215325
+ const tokens = [];
215326
+ for (const { symbol: symbol5, isDelisted } of assets) {
214910
215327
  const entry = this.priceCache.get(symbol5, 30000);
214911
215328
  if (entry) {
214912
215329
  prices[symbol5] = entry.price;
@@ -214916,8 +215333,9 @@ class TokensSnapshotService {
214916
215333
  const lev = this.client.getMaxLeverage(symbol5);
214917
215334
  if (typeof lev === "number" && lev > 0)
214918
215335
  maxLeverages[symbol5] = lev;
215336
+ tokens.push(isDelisted ? { symbol: symbol5, isDelisted: true } : { symbol: symbol5 });
214919
215337
  }
214920
- tokens.sort();
215338
+ tokens.sort((a, b5) => a.symbol.localeCompare(b5.symbol));
214921
215339
  return { tokens, prices, prevDayPrices, maxLeverages };
214922
215340
  }
214923
215341
  }
@@ -215045,6 +215463,10 @@ function createGateway(gatewayConfig, deps) {
215045
215463
  registerToolsMethods(registry4.register.bind(registry4), { tools: deps.tools });
215046
215464
  registerSessionsMethods(registry4.register.bind(registry4), { sessionManager: deps.sessionManager });
215047
215465
  registerCronMethods(registry4.register.bind(registry4), { cronService: deps.cronService });
215466
+ registerConfigMethods(registry4.register.bind(registry4), {
215467
+ timezoneService: deps.timezoneService,
215468
+ cronService: deps.cronService
215469
+ });
215048
215470
  const tokensSnapshot = new TokensSnapshotService(deps.tradingClient, deps.priceCache);
215049
215471
  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
215472
  registerApprovalMethods(registry4.register.bind(registry4), { approvalManager: deps.approvalManager });
@@ -216077,6 +216499,7 @@ async function startDaemon(options) {
216077
216499
  tweetService,
216078
216500
  xFollowService,
216079
216501
  preferenceStore,
216502
+ timezoneService,
216080
216503
  security: security2,
216081
216504
  leakDetector,
216082
216505
  skillService,
@@ -216122,6 +216545,7 @@ async function startDaemon(options) {
216122
216545
  tools,
216123
216546
  sessionManager,
216124
216547
  cronService,
216548
+ timezoneService,
216125
216549
  configPath,
216126
216550
  channels: runtime.channelManager.listChannels().map((ch2) => ({ name: ch2.name })),
216127
216551
  tradingClient,
@@ -216163,7 +216587,7 @@ async function startDaemon(options) {
216163
216587
  }));
216164
216588
  runtime.channelManager.startAllChannels();
216165
216589
  if (config3.cron.enableScheduler) {
216166
- cronService.start();
216590
+ cronService.start({ defaults: buildBuiltInJobs(timezoneService.get()) });
216167
216591
  }
216168
216592
  const runner = new BackgroundJobRunner({
216169
216593
  taskAgent: runtime.taskAgent,
@@ -216206,6 +216630,7 @@ var init_daemon = __esm(() => {
216206
216630
  init_plugin();
216207
216631
  init_types5();
216208
216632
  init_pairing_events();
216633
+ init_defaults();
216209
216634
  });
216210
216635
 
216211
216636
  // node_modules/colorette/index.cjs
@@ -218831,7 +219256,7 @@ __export(exports_uninstall, {
218831
219256
  WIN_HANDLE_RELEASE_MS: () => WIN_HANDLE_RELEASE_MS,
218832
219257
  SIGKILL_DELAY_MS: () => SIGKILL_DELAY_MS
218833
219258
  });
218834
- import { existsSync as fsExistsSync, readFileSync as readFileSync20, writeFileSync as writeFileSync15, unlinkSync as unlinkSync9, rmSync as fsRmSync } from "fs";
219259
+ import { existsSync as fsExistsSync, readFileSync as readFileSync19, writeFileSync as writeFileSync14, unlinkSync as unlinkSync9, rmSync as fsRmSync } from "fs";
218835
219260
  import { join as join22 } from "path";
218836
219261
  import { homedir as homedir7 } from "os";
218837
219262
  async function runUninstall(deps) {
@@ -219157,8 +219582,8 @@ async function runUninstallCli() {
219157
219582
  return !isCancel(r2) && r2 === true;
219158
219583
  },
219159
219584
  existsSync: fsExistsSync,
219160
- readFile: (p2) => readFileSync20(p2, "utf8"),
219161
- writeFile: (p2, content) => writeFileSync15(p2, content, "utf8"),
219585
+ readFile: (p2) => readFileSync19(p2, "utf8"),
219586
+ writeFile: (p2, content) => writeFileSync14(p2, content, "utf8"),
219162
219587
  unlink: (p2) => unlinkSync9(p2),
219163
219588
  rmSync: (p2) => fsRmSync(p2, { recursive: true, force: true, maxRetries: 10, retryDelay: 200 }),
219164
219589
  spawn: (cmd, args) => {
@@ -219614,7 +220039,7 @@ init_cli_providers();
219614
220039
 
219615
220040
  // src/onboard/wizard.ts
219616
220041
  init_dist8();
219617
- import { existsSync as existsSync28 } from "fs";
220042
+ import { existsSync as existsSync27 } from "fs";
219618
220043
  init_providers();
219619
220044
  init_oauth3();
219620
220045
  init_secrets();
@@ -219628,7 +220053,7 @@ init_dist8();
219628
220053
  init_dist8();
219629
220054
  init_utils8();
219630
220055
  init_logs();
219631
- import { existsSync as existsSync26 } from "fs";
220056
+ import { existsSync as existsSync25 } from "fs";
219632
220057
 
219633
220058
  // src/onboard/steps/service.ts
219634
220059
  async function runServiceStep(deps) {
@@ -219723,7 +220148,7 @@ async function finalizeOnboard(opts) {
219723
220148
  }
219724
220149
  const execPath = resolveGhostExecPath();
219725
220150
  const bunPath = resolveBunPath();
219726
- if (!existsSync26(execPath)) {
220151
+ if (!existsSync25(execPath)) {
219727
220152
  O2.warn(`Ghost executable not found at ${execPath}. Service may fail to start.`);
219728
220153
  }
219729
220154
  const result = await runServiceStep({
@@ -219799,22 +220224,22 @@ async function startForegroundDaemon(logger) {
219799
220224
  init_models_config();
219800
220225
  import {
219801
220226
  chmodSync,
219802
- existsSync as existsSync27,
219803
- mkdirSync as mkdirSync18,
219804
- readFileSync as readFileSync19,
220227
+ existsSync as existsSync26,
220228
+ mkdirSync as mkdirSync17,
220229
+ readFileSync as readFileSync18,
219805
220230
  renameSync as renameSync4,
219806
220231
  unlinkSync as unlinkSync8,
219807
- writeFileSync as writeFileSync14
220232
+ writeFileSync as writeFileSync13
219808
220233
  } from "fs";
219809
- import { dirname as dirname10 } from "path";
220234
+ import { dirname as dirname9 } from "path";
219810
220235
  var MODELS_FILE_MODE = 384;
219811
220236
  var MODELS_DIR_MODE = 448;
219812
220237
  function readModelsConfig(path4) {
219813
- if (!existsSync27(path4))
220238
+ if (!existsSync26(path4))
219814
220239
  return { kind: "missing" };
219815
220240
  let raw;
219816
220241
  try {
219817
- raw = readFileSync19(path4, "utf-8");
220242
+ raw = readFileSync18(path4, "utf-8");
219818
220243
  } catch (err) {
219819
220244
  return { kind: "malformed", reason: describeError2(err) };
219820
220245
  }
@@ -219875,15 +220300,15 @@ function upsertModel(current, incoming) {
219875
220300
  return next;
219876
220301
  }
219877
220302
  function writeAtomic(path4, contents) {
219878
- mkdirSync18(dirname10(path4), { recursive: true, mode: MODELS_DIR_MODE });
220303
+ mkdirSync17(dirname9(path4), { recursive: true, mode: MODELS_DIR_MODE });
219879
220304
  const tmp = `${path4}.tmp-${process.pid}-${Date.now()}`;
219880
220305
  try {
219881
- writeFileSync14(tmp, contents, { mode: MODELS_FILE_MODE });
220306
+ writeFileSync13(tmp, contents, { mode: MODELS_FILE_MODE });
219882
220307
  chmodSync(tmp, MODELS_FILE_MODE);
219883
220308
  renameSync4(tmp, path4);
219884
220309
  } catch (err) {
219885
220310
  try {
219886
- if (existsSync27(tmp))
220311
+ if (existsSync26(tmp))
219887
220312
  unlinkSync8(tmp);
219888
220313
  } catch {}
219889
220314
  throw err;
@@ -219909,6 +220334,22 @@ function applyUpdateModeChanges(existing, overlay) {
219909
220334
  }
219910
220335
 
219911
220336
  // src/onboard/wizard.ts
220337
+ init_timezone();
220338
+ init_database();
220339
+ init_db();
220340
+ init_registry2();
220341
+ async function persistTimezone(tz, logger) {
220342
+ try {
220343
+ const db2 = initDatabase(getDbPath());
220344
+ await runDbMigrations(db2, DB_MIGRATIONS);
220345
+ const prefs = new PreferenceStore(db2, logger.child({ module: "prefs" }));
220346
+ const tzService = createTimezoneService(prefs);
220347
+ tzService.set(tz);
220348
+ db2.close();
220349
+ } catch (err) {
220350
+ logger.warn({ err }, "onboard: failed to persist timezone");
220351
+ }
220352
+ }
219912
220353
  function validateCustomProviderName(name) {
219913
220354
  if (!name)
219914
220355
  return "Provider name is required.";
@@ -219923,31 +220364,7 @@ function validateCustomProviderName(name) {
219923
220364
  function isLocalBaseUrl(url3) {
219924
220365
  return /^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?(\/|$)/u.test(url3);
219925
220366
  }
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;
220367
+ function providerRequiresApiKey(_providerId, supportsOAuth) {
219951
220368
  if (supportsOAuth)
219952
220369
  return false;
219953
220370
  return true;
@@ -219975,6 +220392,8 @@ async function runHeadless(headless, daemonOptions) {
219975
220392
  if (daemonOptions.paper)
219976
220393
  customConfig.paper = daemonOptions.paper;
219977
220394
  saveConfig(customConfig, configPath);
220395
+ const tzForCustom = resolveHeadlessTz(daemonOptions.logger);
220396
+ await persistTimezone(tzForCustom, daemonOptions.logger);
219978
220397
  console.log(`[ghost] Custom provider: ${headless.provider} (from models.json)`);
219979
220398
  console.log(`[ghost] Model: ${modelTrimmed}`);
219980
220399
  if (daemonOptions?.paper) {
@@ -220050,8 +220469,11 @@ async function runHeadless(headless, daemonOptions) {
220050
220469
  await credentials2.set("api_key", apiKey);
220051
220470
  }
220052
220471
  saveConfig(config3, configPath);
220472
+ const tz = resolveHeadlessTz(daemonOptions.logger);
220473
+ await persistTimezone(tz, daemonOptions.logger);
220053
220474
  console.log(`[ghost] Provider: ${providerInfo.label} (${headless.provider})`);
220054
220475
  console.log(`[ghost] Model: ${model}`);
220476
+ console.log(`[ghost] Timezone: ${tz}`);
220055
220477
  if (daemonOptions?.paper) {
220056
220478
  console.log(`[ghost] Mode: Paper trading (${daemonOptions.paper.initialBalance ?? 1e4} USDC)`);
220057
220479
  }
@@ -220059,6 +220481,18 @@ async function runHeadless(headless, daemonOptions) {
220059
220481
  console.log("[ghost] Config saved. Run 'ghost onboard --service' to register the auto-start service, or 'ghost daemon' to start manually.");
220060
220482
  console.log("[ghost] Onboard complete!");
220061
220483
  }
220484
+ function resolveHeadlessTz(logger) {
220485
+ const envTz = process.env["GHOST_TIMEZONE"];
220486
+ if (envTz) {
220487
+ const result = validateTimezone(envTz);
220488
+ if (!result.ok) {
220489
+ console.error(`[ghost] GHOST_TIMEZONE="${envTz}" is invalid: ${result.error}`);
220490
+ process.exit(1);
220491
+ }
220492
+ return result.tz;
220493
+ }
220494
+ return detectHostTimezone();
220495
+ }
220062
220496
  async function runWizard(daemonOptions) {
220063
220497
  if (daemonOptions.headless) {
220064
220498
  const { headless, ...rest4 } = daemonOptions;
@@ -220071,7 +220505,7 @@ async function runWizard(daemonOptions) {
220071
220505
  const credentials2 = new CredentialStore(getCredentialsPath(), secretStore, daemonOptions.logger.child({ module: "credentials" }));
220072
220506
  let config3 = configSchema.parse({});
220073
220507
  let mode = "full";
220074
- if (existsSync28(configPath)) {
220508
+ if (existsSync27(configPath)) {
220075
220509
  const modeAnswer = await _t({
220076
220510
  message: "Existing config found. What would you like to do?",
220077
220511
  options: [
@@ -220090,7 +220524,7 @@ async function runWizard(daemonOptions) {
220090
220524
  This wizard will configure your agent in under 60 seconds.`);
220091
220525
  if (mode === "full" && !daemonOptions.paper) {
220092
220526
  const tradingMode = await _t({
220093
- message: "Step 1/6 \u2014 Select trading mode",
220527
+ message: "Step 1/5 \u2014 Select trading mode",
220094
220528
  options: [
220095
220529
  { value: "paper", label: "Paper trading (simulated, safe to explore)", hint: "10,000 USDC starting balance" },
220096
220530
  { value: "live", label: "Live trading (real funds on Hyperliquid)" }
@@ -220108,11 +220542,11 @@ This wizard will configure your agent in under 60 seconds.`);
220108
220542
  const providers = getProviderList();
220109
220543
  const providerOptions = providers.map((p2) => ({
220110
220544
  value: p2.id,
220111
- label: p2.label,
220545
+ label: `${p2.label} \u2014 ${p2.tierLabel}`,
220112
220546
  hint: p2.description
220113
220547
  }));
220114
220548
  const providerId = await _t({
220115
- message: "Step 2/6 \u2014 Select your AI provider",
220549
+ message: "Step 2/5 \u2014 Select your AI provider",
220116
220550
  options: providerOptions
220117
220551
  });
220118
220552
  if (q(providerId)) {
@@ -220123,18 +220557,6 @@ This wizard will configure your agent in under 60 seconds.`);
220123
220557
  let authMethod = "apikey";
220124
220558
  let customProviderName = "";
220125
220559
  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
220560
  if (providerId === "custom") {
220139
220561
  const nameAnswer = await Ot({
220140
220562
  message: "Provider name (identifier for ~/.ghost/models.json)",
@@ -220181,7 +220603,7 @@ This wizard will configure your agent in under 60 seconds.`);
220181
220603
  { value: "__custom__", label: "Custom model ID (type manually)" }
220182
220604
  ];
220183
220605
  const selected = await _t({
220184
- message: "Step 3/6 \u2014 Select your default model",
220606
+ message: "Step 3/5 \u2014 Select your default model",
220185
220607
  options: modelOptions
220186
220608
  });
220187
220609
  if (q(selected)) {
@@ -220200,7 +220622,7 @@ This wizard will configure your agent in under 60 seconds.`);
220200
220622
  }
220201
220623
  } else {
220202
220624
  const manual = await Ot({
220203
- message: "Step 3/6 \u2014 Enter model ID",
220625
+ message: "Step 3/5 \u2014 Enter model ID",
220204
220626
  placeholder: "e.g. claude-sonnet-4-6"
220205
220627
  });
220206
220628
  if (q(manual)) {
@@ -220213,7 +220635,7 @@ This wizard will configure your agent in under 60 seconds.`);
220213
220635
  let apiKey = "";
220214
220636
  if (providerInfo?.supportsOAuth && authMethod !== "skip") {
220215
220637
  const auth = await _t({
220216
- message: "Step 4/6 \u2014 How do you want to authenticate?",
220638
+ message: "Step 4/5 \u2014 How do you want to authenticate?",
220217
220639
  options: [
220218
220640
  { value: "oauth", label: "OAuth Login (authenticate in browser)", hint: "recommended" },
220219
220641
  { value: "apikey", label: "API Key (paste your key)" },
@@ -220249,7 +220671,7 @@ This wizard will configure your agent in under 60 seconds.`);
220249
220671
  const keyUrl = providerInfo?.apiKeyUrl ? `
220250
220672
  Get your key at: ${providerInfo.apiKeyUrl}` : "";
220251
220673
  const key = await Ot({
220252
- message: `Step 4/6 \u2014 Paste your ${providerInfo?.label ?? providerId} API key${keyUrl}`,
220674
+ message: `Step 4/5 \u2014 Paste your ${providerInfo?.label ?? providerId} API key${keyUrl}`,
220253
220675
  placeholder: "sk-...",
220254
220676
  validate: (v4) => !v4 || v4.length >= 5 ? undefined : "API key seems too short"
220255
220677
  });
@@ -220259,7 +220681,7 @@ This wizard will configure your agent in under 60 seconds.`);
220259
220681
  }
220260
220682
  apiKey = key;
220261
220683
  }
220262
- if (authMethod === "skip" && providerId !== "claude-cli" && providerId !== "custom") {
220684
+ if (authMethod === "skip" && providerId !== "custom") {
220263
220685
  O2.warn("No API key set. Export GHOST_API_KEY before running ghost daemon.");
220264
220686
  }
220265
220687
  if (mode === "update") {
@@ -220317,14 +220739,59 @@ This wizard will configure your agent in under 60 seconds.`);
220317
220739
  config3.paper = daemonOptions.paper;
220318
220740
  }
220319
220741
  saveConfig(config3, configPath);
220742
+ const chosenTz = await promptTimezone();
220743
+ await persistTimezone(chosenTz, daemonOptions.logger);
220320
220744
  O2.success("Configuration saved.");
220321
220745
  if (providerId === "custom") {
220322
220746
  O2.info(`Custom provider "${customProviderName}" written to ${getModelsConfigPath()}`);
220323
220747
  }
220748
+ O2.info(`Timezone: ${chosenTz}`);
220324
220749
  O2.info("Tip: connect channels from the dashboard after starting the daemon.");
220325
220750
  console.log("");
220326
220751
  await finalizeOnboard({ interactive: true, logger: daemonOptions.logger });
220327
220752
  }
220753
+ function listSupportedTimezones() {
220754
+ const fn = Intl.supportedValuesOf;
220755
+ if (typeof fn !== "function")
220756
+ return [];
220757
+ return [...fn("timeZone")].sort();
220758
+ }
220759
+ async function promptTimezone() {
220760
+ const detected = detectHostTimezone();
220761
+ const allZones = listSupportedTimezones();
220762
+ if (allZones.length === 0) {
220763
+ const entered = await Ot({
220764
+ message: "Step 5/5 \u2014 IANA timezone (e.g. America/New_York):",
220765
+ initialValue: detected,
220766
+ validate(value2) {
220767
+ const v4 = validateTimezone(value2);
220768
+ return v4.ok ? undefined : v4.error;
220769
+ }
220770
+ });
220771
+ if (q(entered)) {
220772
+ pt("Setup cancelled.");
220773
+ process.exit(0);
220774
+ }
220775
+ const result = validateTimezone(entered);
220776
+ return result.ok ? result.tz : detected;
220777
+ }
220778
+ const initialValue = allZones.includes(detected) ? detected : allZones[0];
220779
+ const choice = await Ae2({
220780
+ message: "Step 5/5 \u2014 Select your timezone (type to filter)",
220781
+ placeholder: "e.g. berlin, new_york, utc",
220782
+ options: allZones.map((tz) => ({
220783
+ value: tz,
220784
+ label: tz === detected ? `${tz} (detected)` : tz
220785
+ })),
220786
+ initialValue,
220787
+ maxItems: 10
220788
+ });
220789
+ if (q(choice)) {
220790
+ pt("Setup cancelled.");
220791
+ process.exit(0);
220792
+ }
220793
+ return choice;
220794
+ }
220328
220795
  // src/index.ts
220329
220796
  init_errors2();
220330
220797
  init_logger();
@@ -220555,14 +221022,14 @@ async function runSkills(subArgs, opts) {
220555
221022
  const configPath = opts.config ?? getPath();
220556
221023
  const config3 = loadConfig2(configPath);
220557
221024
  const workspaceDir = getWorkspaceDir2();
220558
- const { existsSync: existsSync29 } = await import("fs");
221025
+ const { existsSync: existsSync28 } = await import("fs");
220559
221026
  const { join: join23 } = await import("path");
220560
221027
  let builtinDir;
220561
221028
  if (config3.skills.builtinSkillsDir) {
220562
221029
  builtinDir = expandHome2(config3.skills.builtinSkillsDir);
220563
221030
  } else {
220564
221031
  const candidate = join23(import.meta.dir, "skills", "builtin");
220565
- if (existsSync29(candidate))
221032
+ if (existsSync28(candidate))
220566
221033
  builtinDir = candidate;
220567
221034
  }
220568
221035
  const loader2 = new SkillsLoader2(workspaceDir, builtinDir);