clixad 0.0.1-beta.44 → 0.0.1-beta.46

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.
@@ -30,22 +30,53 @@ function parseDirectLines(body) {
30
30
  seen.add(e.format);
31
31
  continue;
32
32
  }
33
- if (typeof e.text !== "string" || typeof e.serve_token !== "string" || typeof e.click_url !== "string") continue;
34
- const text = sanitizeAdText(e.text).slice(0, MAX_TEXT);
35
- if (!text || !e.serve_token || e.serve_token.length > 2e3) continue;
36
- let clickUrl;
37
- try {
38
- const url = new URL(e.click_url);
39
- if (url.protocol !== "https:" && url.protocol !== "http:") continue;
40
- clickUrl = url.toString();
41
- } catch {
42
- continue;
43
- }
44
- out.push({ format: e.format, text, serveToken: e.serve_token, clickUrl });
33
+ const line = readServedLine(e);
34
+ if (!line) continue;
35
+ out.push(line);
45
36
  seen.add(e.format);
46
37
  }
47
38
  return out;
48
39
  }
40
+ function parseFallbackLines(body) {
41
+ const raw = body?.fallbacks;
42
+ if (!Array.isArray(raw)) return [];
43
+ const out = [];
44
+ for (const item of raw) {
45
+ if (!item || typeof item !== "object") continue;
46
+ const line = readServedLine(item);
47
+ if (!line || out.some((o) => o.format === line.format)) continue;
48
+ out.push(line);
49
+ }
50
+ return out;
51
+ }
52
+ function readServedLine(e) {
53
+ if (!isAdFormat(e.format)) return null;
54
+ if (typeof e.text !== "string" || typeof e.serve_token !== "string" || typeof e.click_url !== "string") return null;
55
+ const text = sanitizeAdText(e.text).slice(0, MAX_TEXT);
56
+ if (!text || !e.serve_token || e.serve_token.length > 2e3) return null;
57
+ let clickUrl;
58
+ try {
59
+ const url = new URL(e.click_url);
60
+ if (url.protocol !== "https:" && url.protocol !== "http:") return null;
61
+ clickUrl = url.toString();
62
+ } catch {
63
+ return null;
64
+ }
65
+ return { format: e.format, text, serveToken: e.serve_token, clickUrl };
66
+ }
67
+ var MAX_LINK_TEXT = 40;
68
+ function shortLinkText(clickUrl) {
69
+ let url;
70
+ try {
71
+ url = new URL(clickUrl);
72
+ } catch {
73
+ return void 0;
74
+ }
75
+ if (url.protocol !== "https:" || url.search || url.hash || url.username || url.password) return void 0;
76
+ const text = `${url.host}${url.pathname === "/" ? "" : url.pathname}`;
77
+ if (text.length > MAX_LINK_TEXT || !/^[\x21-\x7e]+$/.test(text)) return void 0;
78
+ return text;
79
+ }
49
80
 
50
81
  // src/edge.ts
51
82
  function detectEdgeBlock(status, headers, body) {
@@ -607,16 +638,16 @@ async function httpFailure(res, what) {
607
638
  return detail ? `${what} failed: ${res.status} \u2014 ${detail}` : `${what} failed: ${res.status}`;
608
639
  }
609
640
  function sleep(ms, signal) {
610
- return new Promise((resolve4) => {
641
+ return new Promise((resolve5) => {
611
642
  if (!signal) {
612
- setTimeout(resolve4, ms);
643
+ setTimeout(resolve5, ms);
613
644
  return;
614
645
  }
615
- if (signal.aborted) return resolve4();
646
+ if (signal.aborted) return resolve5();
616
647
  const done = () => {
617
648
  clearTimeout(timer);
618
649
  signal.removeEventListener("abort", done);
619
- resolve4();
650
+ resolve5();
620
651
  };
621
652
  const timer = setTimeout(done, ms);
622
653
  signal.addEventListener("abort", done, { once: true });
@@ -1107,7 +1138,9 @@ var GatewayClient = class {
1107
1138
  });
1108
1139
  if (res.status === 404) return { supported: false, lines: [] };
1109
1140
  if (!res.ok) return { supported: true, lines: [], failed: true };
1110
- return { supported: true, lines: parseDirectLines(await res.json()) };
1141
+ const body = await res.json();
1142
+ const fallbacks = parseFallbackLines(body);
1143
+ return { supported: true, lines: parseDirectLines(body), ...fallbacks.length > 0 ? { fallbacks } : {} };
1111
1144
  } catch {
1112
1145
  return { supported: true, lines: [], failed: true };
1113
1146
  }
@@ -1133,6 +1166,27 @@ var GatewayClient = class {
1133
1166
  return 0;
1134
1167
  }
1135
1168
  }
1169
+ /**
1170
+ * Report the fallback lines a turn drew, in one call, after the turn is
1171
+ * over. They are counted and earn nothing, because nobody pays for them being
1172
+ * shown, so this answers how many were counted. Never throws.
1173
+ */
1174
+ async reportAffiliateImpressions(impressions, signal) {
1175
+ if (impressions.length === 0) return 0;
1176
+ try {
1177
+ const res = await gatewayFetch(`${this.config.gatewayUrl}/v1/sponsor/affiliate/impression`, {
1178
+ method: "POST",
1179
+ headers: this.headers(),
1180
+ ...signal ? { signal } : {},
1181
+ body: JSON.stringify({ impressions: impressions.map((i) => ({ format: i.format, serve_token: i.serveToken })) })
1182
+ });
1183
+ if (!res.ok) return 0;
1184
+ const body = await res.json();
1185
+ return typeof body.counted === "number" ? body.counted : 0;
1186
+ } catch {
1187
+ return 0;
1188
+ }
1189
+ }
1136
1190
  /**
1137
1191
  * Search the web, through the gateway.
1138
1192
  *
@@ -1939,16 +1993,16 @@ async function devLogin(client, config, email, invite) {
1939
1993
  };
1940
1994
  }
1941
1995
  function sleep2(ms, signal) {
1942
- return new Promise((resolve4) => {
1996
+ return new Promise((resolve5) => {
1943
1997
  if (!signal) {
1944
- setTimeout(resolve4, ms);
1998
+ setTimeout(resolve5, ms);
1945
1999
  return;
1946
2000
  }
1947
- if (signal.aborted) return resolve4();
2001
+ if (signal.aborted) return resolve5();
1948
2002
  const done = () => {
1949
2003
  clearTimeout(timer);
1950
2004
  signal.removeEventListener("abort", done);
1951
- resolve4();
2005
+ resolve5();
1952
2006
  };
1953
2007
  const timer = setTimeout(done, ms);
1954
2008
  signal.addEventListener("abort", done, { once: true });
@@ -3084,7 +3138,7 @@ var StdioTransport = class {
3084
3138
  if (this.exited) return Promise.reject(new McpError(this.exited));
3085
3139
  const id = ++this.seq;
3086
3140
  const payload = JSON.stringify({ jsonrpc: "2.0", id, method, params }) + "\n";
3087
- return new Promise((resolve4, reject) => {
3141
+ return new Promise((resolve5, reject) => {
3088
3142
  const settle = (fn) => {
3089
3143
  clearTimeout(timer);
3090
3144
  signal?.removeEventListener("abort", onAbort);
@@ -3098,7 +3152,7 @@ var StdioTransport = class {
3098
3152
  const onAbort = () => settle(() => reject(new McpError("cancelled")));
3099
3153
  signal?.addEventListener("abort", onAbort, { once: true });
3100
3154
  this.pending.set(id, {
3101
- resolve: (v) => settle(() => resolve4(v)),
3155
+ resolve: (v) => settle(() => resolve5(v)),
3102
3156
  reject: (e) => settle(() => reject(e))
3103
3157
  });
3104
3158
  this.child.stdin?.write(payload, (err) => {
@@ -5972,12 +6026,20 @@ import {
5972
6026
  writeFileSync as writeFileSync3
5973
6027
  } from "node:fs";
5974
6028
  import { homedir as homedir5 } from "node:os";
5975
- import { join as join5 } from "node:path";
6029
+ import { join as join5, resolve as resolve4 } from "node:path";
5976
6030
  var ROOT = join5(homedir5(), ".clixad");
5977
6031
  var SESSION_DIR = join5(ROOT, "sessions");
5978
6032
  var HISTORY_FILE = join5(ROOT, "history");
5979
6033
  var MAX_HISTORY = 200;
5980
- var MAX_SESSIONS = 50;
6034
+ var MAX_SESSIONS = 200;
6035
+ var SESSION_ID = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;
6036
+ function isSessionId(id) {
6037
+ return SESSION_ID.test(id);
6038
+ }
6039
+ function sameDirectory(a, b) {
6040
+ const norm = (p) => process.platform === "win32" ? resolve4(p).toLowerCase() : resolve4(p);
6041
+ return norm(a) === norm(b);
6042
+ }
5981
6043
  function newSessionId() {
5982
6044
  const now = (/* @__PURE__ */ new Date()).toISOString().replace(/[-:]/g, "").replace(/\.\d+Z$/, "");
5983
6045
  return `${now}-${Math.random().toString(16).slice(2, 6)}`;
@@ -5992,12 +6054,25 @@ function saveSession(session) {
5992
6054
  }
5993
6055
  }
5994
6056
  function loadSession(id) {
6057
+ if (!isSessionId(id)) return void 0;
5995
6058
  try {
5996
- return JSON.parse(readFileSync5(join5(SESSION_DIR, `${id}.json`), "utf8"));
6059
+ const s = JSON.parse(readFileSync5(join5(SESSION_DIR, `${id}.json`), "utf8"));
6060
+ return s && Array.isArray(s.messages) && typeof s.id === "string" ? s : void 0;
5997
6061
  } catch {
5998
6062
  return void 0;
5999
6063
  }
6000
6064
  }
6065
+ function deleteSession(id) {
6066
+ if (!isSessionId(id)) return false;
6067
+ const path = join5(SESSION_DIR, `${id}.json`);
6068
+ try {
6069
+ if (!existsSync4(path)) return false;
6070
+ rmSync(path);
6071
+ return true;
6072
+ } catch {
6073
+ return false;
6074
+ }
6075
+ }
6001
6076
  function listSessions(cwd, limit = 20) {
6002
6077
  if (!existsSync4(SESSION_DIR)) return [];
6003
6078
  const metas = [];
@@ -6005,16 +6080,15 @@ function listSessions(cwd, limit = 20) {
6005
6080
  if (!name.endsWith(".json")) continue;
6006
6081
  const s = loadSession(name.slice(0, -5));
6007
6082
  if (!s) continue;
6008
- if (cwd && s.cwd !== cwd) continue;
6009
- const { messages: _messages, ...meta } = s;
6010
- metas.push(meta);
6083
+ if (cwd && !sameDirectory(s.cwd ?? "", cwd)) continue;
6084
+ const { messages, ...meta } = s;
6085
+ metas.push({ ...meta, title: meta.title ?? "", updated: meta.updated ?? "", messageCount: messages.length });
6011
6086
  }
6012
6087
  metas.sort((a, b) => b.updated.localeCompare(a.updated));
6013
6088
  return metas.slice(0, limit);
6014
6089
  }
6015
6090
  function hasSessionIn(cwd) {
6016
6091
  if (!existsSync4(SESSION_DIR)) return false;
6017
- const needle = `"cwd":${JSON.stringify(cwd)}`;
6018
6092
  try {
6019
6093
  for (const name of readdirSync3(SESSION_DIR)) {
6020
6094
  if (!name.endsWith(".json")) continue;
@@ -6027,8 +6101,14 @@ function hasSessionIn(cwd) {
6027
6101
  } finally {
6028
6102
  closeSync2(fd);
6029
6103
  }
6030
- if (head.includes(needle)) return true;
6031
- if (!head.includes('"cwd":') && loadSession(name.slice(0, -5))?.cwd === cwd) return true;
6104
+ const field = /"cwd":("(?:[^"\\]|\\.)*")/.exec(head)?.[1];
6105
+ let stored;
6106
+ try {
6107
+ stored = field ? JSON.parse(field) : loadSession(name.slice(0, -5))?.cwd;
6108
+ } catch {
6109
+ stored = void 0;
6110
+ }
6111
+ if (stored && sameDirectory(stored, cwd)) return true;
6032
6112
  }
6033
6113
  } catch {
6034
6114
  }
@@ -6073,6 +6153,7 @@ var CONTEXT_TOKENS = {
6073
6153
  "claude-haiku-5.5": 24e3,
6074
6154
  "deepseek-v4-flash": 1048576,
6075
6155
  "gemini-3.5-flash-lite": 1048576,
6156
+ "glm-5.3-flash": 1048576,
6076
6157
  "kimi-k2.5": 262144,
6077
6158
  "gemini-3.8-flash": 1048576,
6078
6159
  "kimi-k2.7-code": 262144,
@@ -6160,14 +6241,14 @@ function runKimiCode(config, task, opts = {}) {
6160
6241
  OPENAI_BASE_URL: `${config.gatewayUrl.replace(/\/+$/, "")}/v1`,
6161
6242
  OPENAI_API_KEY: config.token
6162
6243
  };
6163
- return new Promise((resolve4) => {
6244
+ return new Promise((resolve5) => {
6164
6245
  const child = process.platform === "win32" ? spawn5([bin, ...args].map(quoteWin).join(" "), { stdio: "inherit", env, shell: true }) : spawn5(bin, args, { stdio: "inherit", env });
6165
6246
  child.on("error", (err) => {
6166
6247
  if (err.code === "ENOENT") console.log("\n" + INSTALL_HELP);
6167
6248
  else console.error(`failed to launch Kimi CLI: ${err.message}`);
6168
- resolve4(127);
6249
+ resolve5(127);
6169
6250
  });
6170
- child.on("exit", (code) => resolve4(code ?? 0));
6251
+ child.on("exit", (code) => resolve5(code ?? 0));
6171
6252
  });
6172
6253
  }
6173
6254
  function quoteWin(s) {
@@ -6427,6 +6508,8 @@ function cutPoint(messages, keepTurns = 2) {
6427
6508
  }
6428
6509
  return 0;
6429
6510
  }
6511
+ var SUMMARY_LEAD = "Summary of the earlier conversation:\n\n";
6512
+ var SUMMARY_ACK = "Understood \u2014 continuing from that summary.";
6430
6513
  var SUMMARY_PROMPT = "Summarise the conversation above for your future self, which will continue the work without seeing it. Cover: what the user asked for, what was already changed (files and the reasoning), what was learned about the codebase, and what is still open. Be specific about file paths and names. No preamble.";
6431
6514
  async function compact(client, model, messages, opts = {}) {
6432
6515
  const cut2 = cutPoint(messages, opts.keepTurns ?? 2);
@@ -6442,10 +6525,8 @@ async function compact(client, model, messages, opts = {}) {
6442
6525
  const summary = messageText(turn.message).trim() || "(no summary returned)";
6443
6526
  const compacted = [
6444
6527
  messages[0],
6445
- { role: "user", content: `Summary of the earlier conversation:
6446
-
6447
- ${summary}` },
6448
- { role: "assistant", content: "Understood \u2014 continuing from that summary." },
6528
+ { role: "user", content: `${SUMMARY_LEAD}${summary}` },
6529
+ { role: "assistant", content: SUMMARY_ACK },
6449
6530
  ...messages.slice(cut2)
6450
6531
  ];
6451
6532
  return {
@@ -6546,6 +6627,7 @@ export {
6546
6627
  AD_FORMATS,
6547
6628
  EXTRA_AD_FORMATS,
6548
6629
  DIRECT_ASK_TIMEOUT_MS,
6630
+ shortLinkText,
6549
6631
  accessColumn,
6550
6632
  premiumLocked,
6551
6633
  premiumUnlockHint,
@@ -6561,6 +6643,7 @@ export {
6561
6643
  referLines,
6562
6644
  redeemMessage,
6563
6645
  ledgerReasonLabel,
6646
+ messageText,
6564
6647
  PaywallError,
6565
6648
  PremiumLockedError,
6566
6649
  LimitRefusalError,
@@ -6603,6 +6686,7 @@ export {
6603
6686
  NO_SEARCH_INSTRUCTION,
6604
6687
  runAgent,
6605
6688
  interruptedConversation,
6689
+ INTERRUPTED_CALL,
6606
6690
  collectContext,
6607
6691
  INIT_PROMPT,
6608
6692
  INIT_HINT,
@@ -6615,9 +6699,12 @@ export {
6615
6699
  switchMode,
6616
6700
  createPermit,
6617
6701
  denyAll,
6702
+ MAX_SESSIONS,
6703
+ sameDirectory,
6618
6704
  newSessionId,
6619
6705
  saveSession,
6620
6706
  loadSession,
6707
+ deleteSession,
6621
6708
  listSessions,
6622
6709
  hasSessionIn,
6623
6710
  latestSession,
@@ -6642,6 +6729,8 @@ export {
6642
6729
  estimateTokens,
6643
6730
  conversationTokens,
6644
6731
  shouldCompact,
6732
+ SUMMARY_LEAD,
6733
+ SUMMARY_ACK,
6645
6734
  compact,
6646
6735
  VERSION,
6647
6736
  VERSION_LABEL,
package/dist/clixad.mjs CHANGED
@@ -43,6 +43,7 @@ import {
43
43
  rowTaskHint,
44
44
  runAgent,
45
45
  runKimiCode,
46
+ sameDirectory,
46
47
  saveConfig,
47
48
  savedEffort,
48
49
  shells,
@@ -53,7 +54,7 @@ import {
53
54
  takeConfigNotices,
54
55
  toolSchemaFor,
55
56
  usd
56
- } from "./chunk-JV44GYWT.mjs";
57
+ } from "./chunk-5GX5GUR4.mjs";
57
58
 
58
59
  // src/tui/load.ts
59
60
  import * as nodeModule from "node:module";
@@ -132,7 +133,7 @@ async function withProductionReact(load) {
132
133
  }
133
134
  function loadTui() {
134
135
  boundInkCaches();
135
- return withProductionReact(() => import("./tui-KA2P7O7W.mjs"));
136
+ return withProductionReact(() => import("./tui-2AEK7HJV.mjs"));
136
137
  }
137
138
 
138
139
  // src/title.ts
@@ -615,13 +616,18 @@ async function codeCmd(client, config, rest) {
615
616
  }
616
617
  async function repl(client, config, opts = {}) {
617
618
  if (opts.resume === "pick") {
618
- const sessions = listSessions(void 0, 10);
619
+ const sessions = listSessions(void 0, 20);
619
620
  if (!sessions.length) return console.log(c.yellow("no saved sessions yet"));
620
- console.log(c.bold("Sessions:"));
621
+ const here = process.cwd();
622
+ console.log(c.bold("Sessions (newest first, * = this directory):"));
621
623
  for (const s of sessions) {
622
- console.log(` ${c.cyan(s.id)} ${s.updated.slice(0, 16).replace("T", " ")} ${s.title}`);
624
+ const mark = sameDirectory(s.cwd, here) ? "*" : " ";
625
+ console.log(
626
+ `${mark} ${c.cyan(s.id)} ${s.updated.slice(0, 16).replace("T", " ")} ${String(s.messageCount).padStart(4)} msgs ${s.title}`
627
+ );
628
+ if (mark === " ") console.log(c.dim(` in ${s.cwd}`));
623
629
  }
624
- console.log(c.dim("\n resume one with: clixad --resume <id>"));
630
+ console.log(c.dim("\n resume one with: clixad --resume <id> \xB7 delete one in the REPL: /resume, then d twice"));
625
631
  return;
626
632
  }
627
633
  if (!process.stdin.isTTY) {
@@ -639,9 +645,10 @@ async function repl(client, config, opts = {}) {
639
645
  if (!config.token) return;
640
646
  }
641
647
  let session;
648
+ const notices = [...opts.notices ?? []];
642
649
  if (opts.resume === "latest") {
643
650
  session = latestSession(process.cwd());
644
- if (!session) console.log(c.dim(" no previous session here \u2014 starting a new one"));
651
+ if (!session) notices.push("no saved session in this directory, so this is a new one (/resume all lists every directory's)");
645
652
  } else if (opts.resume) {
646
653
  session = loadSession(opts.resume);
647
654
  if (!session) return console.log(c.red(`no such session: ${opts.resume}`));
@@ -661,7 +668,7 @@ async function repl(client, config, opts = {}) {
661
668
  newAccount,
662
669
  preferences,
663
670
  initialTask: opts.task,
664
- ...opts.notices?.length ? { notices: opts.notices } : {},
671
+ ...notices.length ? { notices } : {},
665
672
  ...initHint ? { initHint } : {}
666
673
  });
667
674
  }
@@ -6,7 +6,9 @@ import {
6
6
  EXTRA_AD_FORMATS,
7
7
  INIT_HINT,
8
8
  INIT_PROMPT,
9
+ INTERRUPTED_CALL,
9
10
  LimitRefusalError,
11
+ MAX_SESSIONS,
10
12
  MIN_BANNER_WIDTH,
11
13
  MODES,
12
14
  MODE_LABEL,
@@ -17,6 +19,8 @@ import {
17
19
  PaywallError,
18
20
  PremiumLockedError,
19
21
  SPINNER_TICKS_PER_FRAME,
22
+ SUMMARY_ACK,
23
+ SUMMARY_LEAD,
20
24
  TERMS_NOTICE,
21
25
  TERMS_URL,
22
26
  TOOLS,
@@ -35,6 +39,7 @@ import {
35
39
  conversationTokens,
36
40
  createPermit,
37
41
  createState,
42
+ deleteSession,
38
43
  diffTint,
39
44
  effortLabel,
40
45
  effortRaised,
@@ -52,6 +57,7 @@ import {
52
57
  loadHistory,
53
58
  loadMcpConfig,
54
59
  loadSession,
60
+ messageText,
55
61
  modeInstruction,
56
62
  newSessionId,
57
63
  nextMode,
@@ -83,6 +89,7 @@ import {
83
89
  savedEffort,
84
90
  shells,
85
91
  shortCredits,
92
+ shortLinkText,
86
93
  shortfallHeadline,
87
94
  shouldCompact,
88
95
  shouldSuggestInit,
@@ -98,7 +105,7 @@ import {
98
105
  visibleLength,
99
106
  webSearchCostLine,
100
107
  welcomeBanner
101
- } from "./chunk-JV44GYWT.mjs";
108
+ } from "./chunk-5GX5GUR4.mjs";
102
109
 
103
110
  // src/tui/index.tsx
104
111
  import "react";
@@ -111,6 +118,7 @@ import { Box as Box2, Static, Text as Text2, useApp, useInput, usePaste, useStdo
111
118
  // src/mentions.ts
112
119
  var TRAILING = /[),;:]+$/;
113
120
  var MAX_MENTION_CHARS = 12e4;
121
+ var MENTIONS_HEADING = "\n\n# Files the user referenced\n";
114
122
  function findMentions(text) {
115
123
  const out = [];
116
124
  for (const match of text.matchAll(/(?:^|\s)@([^\s]+)/g)) {
@@ -145,10 +153,7 @@ ${clipped}`);
145
153
  }
146
154
  if (blocks.length === 0) return { text, attached: [], images: [] };
147
155
  return {
148
- text: `${text}
149
-
150
- # Files the user referenced
151
- Their contents are below, already read for you \u2014 do not call read_file on them again.
156
+ text: `${text}${MENTIONS_HEADING}Their contents are below, already read for you \u2014 do not call read_file on them again.
152
157
 
153
158
  ` + blocks.join("\n\n"),
154
159
  attached,
@@ -272,21 +277,28 @@ var SIDEBAR_MIN_COLS = 50 + SIDEBAR_CARD_COLS;
272
277
  function adFitsFormat(format, cols) {
273
278
  return format === "sidebar_card" ? cols >= SIDEBAR_MIN_COLS : adFitsWidth(cols);
274
279
  }
275
- function sidebarCard(line2, cols) {
280
+ var LINK_SEPARATOR = " \xB7 ";
281
+ function sidebarCard(line2, cols, linkText) {
276
282
  if (!adFitsFormat("sidebar_card", cols)) return null;
277
283
  const text = line2.trim();
278
284
  if (!text) return null;
279
285
  const body = visibleLength(text) <= SIDEBAR_BODY_COLS ? text : `${sliceColumns(text, SIDEBAR_BODY_COLS - 1).trimEnd()}\u2026`;
280
- return { rule: SIDEBAR_RULE, label: `${SPONSOR_SIGIL} ${AD_LABEL}`, body };
286
+ const label = `${SPONSOR_SIGIL} ${AD_LABEL}`;
287
+ const link = linkText && visibleLength(label) + visibleLength(LINK_SEPARATOR) + visibleLength(linkText) <= SIDEBAR_BODY_COLS ? linkText : null;
288
+ return { rule: SIDEBAR_RULE, label, link, body };
281
289
  }
282
- function sponsorRow(line2, cols, origin) {
290
+ function sponsorRow(line2, cols, origin, linkText) {
283
291
  const marker = origin === "house" || cols >= AD_FULL_PREFIX_MIN_COLS ? HOUSE_MARKER_ROW : AD_MARKER_ROW;
284
292
  const label = origin === "network" ? LABEL_ROW : null;
285
293
  if (origin === "network" && !adFitsWidth(cols)) return null;
286
294
  const room = cols - visibleLength(marker) - (label ? visibleLength(label) : 0);
287
295
  if (room < MIN_BODY_COLS) return null;
296
+ if (origin === "network" && linkText) {
297
+ const whole = visibleLength(line2) + visibleLength(LINK_SEPARATOR) + visibleLength(linkText);
298
+ if (whole <= room) return { marker, label, body: line2, link: linkText, origin };
299
+ }
288
300
  const body = visibleLength(line2) <= room ? line2 : `${sliceColumns(line2, room - 1).trimEnd()}\u2026`;
289
- return { marker, label, body, origin };
301
+ return { marker, label, body, link: null, origin };
290
302
  }
291
303
  function adColorEnabled({ isTTY, env }) {
292
304
  if (!colorEnabled({ isTTY, env })) return false;
@@ -421,8 +433,8 @@ var COMMANDS = [
421
433
  { name: "clear", desc: "clear the screen and start a new session" },
422
434
  // Deliberately *no* `args` on /resume, for /login's reason: an arg hint makes
423
435
  // enter complete the command instead of running it, and the picker is the
424
- // point. `/resume <id>` still works when typed in full.
425
- { name: "resume", desc: "pick up an earlier session in this directory" },
436
+ // point. `/resume <id>`, `/resume all` and `/resume delete <id>` work typed in full.
437
+ { name: "resume", desc: "pick up or delete an earlier session (all: every directory)" },
426
438
  { name: "init", desc: "write a CLIXAD.md for this project" },
427
439
  // Deliberately *no* `args` on either, for /login's reason: an arg hint makes
428
440
  // enter complete the command instead of running it, and listing is what both
@@ -1462,9 +1474,9 @@ function presentResult(input, width, tint = diffTint()) {
1462
1474
  const [notes = "", ...rest] = result.split("\n\n");
1463
1475
  const out = nonEmpty(rest.join("\n\n")).filter((l) => l !== "(nothing new since the last read)");
1464
1476
  const { body, more } = rows(out);
1465
- const failed = /\] (exited with code (?!0\b)\d+|failed|killed)/.test(notes);
1477
+ const failed2 = /\] (exited with code (?!0\b)\d+|failed|killed)/.test(notes);
1466
1478
  return {
1467
- status: failed ? "error" : "ok",
1479
+ status: failed2 ? "error" : "ok",
1468
1480
  line: fit(nonEmpty(notes)[0] ?? "read"),
1469
1481
  body,
1470
1482
  more,
@@ -1604,6 +1616,69 @@ function askWords(req) {
1604
1616
  }
1605
1617
  }
1606
1618
 
1619
+ // src/tui/replay.ts
1620
+ function shellNote(command2, output) {
1621
+ return `I ran \`${command2}\` in the workspace myself. Its output:
1622
+
1623
+ ${output}`;
1624
+ }
1625
+ var SHELL_NOTE = /^I ran `([^\n]*)` in the workspace myself\. Its output:\n\n([\s\S]*)$/;
1626
+ var IMAGE_NOTE = /^Here (?:is the image|are the images) you read \(/;
1627
+ function failed(result) {
1628
+ return result.startsWith("error: ") || result === INTERRUPTED_CALL;
1629
+ }
1630
+ function parseArgs(raw) {
1631
+ if (!raw) return {};
1632
+ try {
1633
+ const parsed = JSON.parse(raw);
1634
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
1635
+ } catch {
1636
+ return {};
1637
+ }
1638
+ }
1639
+ function replayItems(messages) {
1640
+ const answers = /* @__PURE__ */ new Map();
1641
+ for (const m of messages) {
1642
+ if (m.role === "tool" && m.tool_call_id) answers.set(m.tool_call_id, messageText(m));
1643
+ }
1644
+ const out = [];
1645
+ for (const m of messages) {
1646
+ if (m.role === "user") {
1647
+ const text = messageText(m);
1648
+ if (text.startsWith(SUMMARY_LEAD)) {
1649
+ out.push({ kind: "notice", text: "earlier messages were summarised to save context; the summary is what the model has" });
1650
+ continue;
1651
+ }
1652
+ if (IMAGE_NOTE.test(text)) continue;
1653
+ const shell = SHELL_NOTE.exec(text);
1654
+ if (shell) {
1655
+ out.push({ kind: "user", text: `!${shell[1]}` });
1656
+ out.push({ kind: "tool", name: "!", args: { command: shell[1] }, result: shell[2] ?? "", ok: true });
1657
+ continue;
1658
+ }
1659
+ const cut = text.indexOf(MENTIONS_HEADING);
1660
+ const typed = (cut >= 0 ? text.slice(0, cut) : text).trim();
1661
+ if (typed) out.push({ kind: "user", text: typed });
1662
+ continue;
1663
+ }
1664
+ if (m.role === "assistant") {
1665
+ const text = messageText(m).trim();
1666
+ if (text && text !== SUMMARY_ACK) out.push({ kind: "assistant", text });
1667
+ for (const call of m.tool_calls ?? []) {
1668
+ const result = answers.get(call.id) ?? INTERRUPTED_CALL;
1669
+ out.push({
1670
+ kind: "tool",
1671
+ name: call.function.name,
1672
+ args: parseArgs(call.function.arguments),
1673
+ result,
1674
+ ok: !failed(result)
1675
+ });
1676
+ }
1677
+ }
1678
+ }
1679
+ return out;
1680
+ }
1681
+
1607
1682
  // src/tui/app.tsx
1608
1683
  import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
1609
1684
  var WORKING = "working\u2026";
@@ -1715,6 +1790,7 @@ var INIT_HINT_AFTER_TURNS = 3;
1715
1790
  var freshStream = () => ({ text: "", md: {}, committed: false, streamed: false });
1716
1791
  var EARN_ROW = "\0earn";
1717
1792
  var BUY_ROW = "\0buy";
1793
+ var PICKER_DELETE_CONFIRM = "Press d again to delete it for good \xB7 any other key keeps it";
1718
1794
  function App({
1719
1795
  client,
1720
1796
  config,
@@ -1805,17 +1881,15 @@ function App({
1805
1881
  const [plan, setPlan] = useState(null);
1806
1882
  const [picker, setPicker] = useState(null);
1807
1883
  const [pickerSel, setPickerSel] = useState(0);
1884
+ const [pickerArmed, setPickerArmed] = useState(null);
1885
+ const [pickerNote, setPickerNote] = useState(null);
1808
1886
  const [buy, setBuy] = useState(null);
1809
1887
  const [tick, setTick] = useState(0);
1810
1888
  const [startedAt, setStartedAt] = useState(0);
1811
1889
  const [quitHint, setQuitHint] = useState(false);
1812
1890
  const [busyLabel, setBusyLabel] = useState(WORKING);
1813
- const [sponsor, setSponsor] = useState(
1814
- null
1815
- );
1816
- const [adSlots, setAdSlots] = useState(
1817
- {}
1818
- );
1891
+ const [sponsor, setSponsor] = useState(null);
1892
+ const [adSlots, setAdSlots] = useState({});
1819
1893
  const messagesRef = useRef(messages);
1820
1894
  messagesRef.current = messages;
1821
1895
  const busyRef = useRef(false);
@@ -1940,6 +2014,7 @@ function App({
1940
2014
  const sponsorCallRef = useRef(0);
1941
2015
  const pendingImpressionRef = useRef(/* @__PURE__ */ new Map());
1942
2016
  const pendingDirectRef = useRef(/* @__PURE__ */ new Map());
2017
+ const pendingFallbackRef = useRef(/* @__PURE__ */ new Map());
1943
2018
  const directSupportedRef = useRef(true);
1944
2019
  const knownBookedRef = useRef(/* @__PURE__ */ new Set());
1945
2020
  const drawnRef = useRef(/* @__PURE__ */ new Set());
@@ -2113,9 +2188,6 @@ function App({
2113
2188
  if (freeLine) {
2114
2189
  push({ kind: "notice", tone: (wallet?.balance ?? 0) <= 0 ? "warn" : "good", text: ` ${freeLine}` });
2115
2190
  }
2116
- if (session?.messages.length) {
2117
- push({ kind: "notice", text: ` resumed session ${session.id} (${session.messages.length} messages)` });
2118
- }
2119
2191
  if (initHint?.returning) {
2120
2192
  initHintedRef.current = true;
2121
2193
  push({ kind: "notice", text: ` ${INIT_HINT}` });
@@ -2160,14 +2232,14 @@ function App({
2160
2232
  if (cancelled) return void hub.close();
2161
2233
  mcpRef.current = hub;
2162
2234
  const ready = hub.status().filter((s) => s.state === "ready");
2163
- const failed = hub.status().filter((s) => s.state === "failed");
2235
+ const failed2 = hub.status().filter((s) => s.state === "failed");
2164
2236
  if (ready.length) {
2165
2237
  push({
2166
2238
  kind: "notice",
2167
2239
  text: ` mcp: ${ready.map((s) => `${s.name} (${s.toolCount} tools)`).join(", ")}`
2168
2240
  });
2169
2241
  }
2170
- for (const server of failed) {
2242
+ for (const server of failed2) {
2171
2243
  push({ kind: "notice", tone: "warn", text: ` mcp: ${server.name} did not start \u2014 ${server.error}` });
2172
2244
  }
2173
2245
  });
@@ -2210,9 +2282,13 @@ function App({
2210
2282
  } else {
2211
2283
  setSponsor(null);
2212
2284
  }
2213
- const fetchNetwork = (format) => {
2285
+ const fetchNetwork = (format, fallback) => {
2214
2286
  void remoteSponsorRef.current[format].fetch().then((line2) => {
2215
- if (!line2 || call !== sponsorCallRef.current) return;
2287
+ if (call !== sponsorCallRef.current) return;
2288
+ if (!line2) {
2289
+ if (fallback) paintFallback(fallback);
2290
+ return;
2291
+ }
2216
2292
  if (!adFitsFormat(format, colsRef.current)) return;
2217
2293
  if (format !== "top_bar" && modalOpenRef.current) return;
2218
2294
  if (format === "top_bar") {
@@ -2244,10 +2320,28 @@ function App({
2244
2320
  }
2245
2321
  pendingDirectRef.current.set(format, { call, serveToken: line2.serveToken, drawnId });
2246
2322
  };
2323
+ const paintFallback = (line2) => {
2324
+ const { format } = line2;
2325
+ if (call !== sponsorCallRef.current) return;
2326
+ if (!adFitsFormat(format, colsRef.current)) return;
2327
+ if (format !== "top_bar" && modalOpenRef.current) return;
2328
+ const drawnId = `fallback:${call}:${format}`;
2329
+ const link = line2.clickUrl;
2330
+ const linkText = shortLinkText(link);
2331
+ const extra = linkText ? { link, linkText } : { link };
2332
+ if (format === "top_bar") {
2333
+ if (!sponsorRow(line2.text, colsRef.current, "network", linkText)) return;
2334
+ setSponsor({ text: line2.text, origin: "network", impressionId: drawnId, ...extra });
2335
+ } else {
2336
+ setAdSlots((slots) => ({ ...slots, [format]: { text: line2.text, impressionId: drawnId, ...extra } }));
2337
+ }
2338
+ pendingFallbackRef.current.set(format, { call, serveToken: line2.serveToken, drawnId });
2339
+ };
2247
2340
  const fitting = AD_FORMATS.filter((format) => adFitsFormat(format, colsRef.current));
2248
2341
  void (async () => {
2249
2342
  let direct = [];
2250
- let failed = false;
2343
+ let fallbacks = [];
2344
+ let failed2 = false;
2251
2345
  if (directSupportedRef.current && fitting.length > 0) {
2252
2346
  const ask2 = sponsorDirect ?? ((formats, cols2, signal) => client.directSponsor(formats, cols2, signal));
2253
2347
  let answer = { supported: true, lines: [] };
@@ -2257,9 +2351,10 @@ function App({
2257
2351
  answer = { supported: true, lines: [], failed: true };
2258
2352
  }
2259
2353
  if (!answer.supported) directSupportedRef.current = false;
2260
- failed = answer.failed === true;
2354
+ failed2 = answer.failed === true;
2261
2355
  direct = answer.lines;
2262
- if (!failed) knownBookedRef.current = new Set(direct.map((line2) => line2.format));
2356
+ fallbacks = answer.fallbacks ?? [];
2357
+ if (!failed2) knownBookedRef.current = new Set(direct.map((line2) => line2.format));
2263
2358
  }
2264
2359
  if (call !== sponsorCallRef.current) return;
2265
2360
  for (const format of ["top_bar", ...EXTRA_AD_FORMATS]) {
@@ -2267,8 +2362,8 @@ function App({
2267
2362
  const booked = direct.find((line2) => line2.format === format);
2268
2363
  if (booked && "hold" in booked) continue;
2269
2364
  if (booked) paintDirect(booked);
2270
- else if (failed && knownBookedRef.current.has(format)) continue;
2271
- else fetchNetwork(format);
2365
+ else if (failed2 && knownBookedRef.current.has(format)) continue;
2366
+ else fetchNetwork(format, fallbacks.find((line2) => line2.format === format));
2272
2367
  }
2273
2368
  })();
2274
2369
  },
@@ -2277,12 +2372,16 @@ function App({
2277
2372
  const reportSponsor = useCallback(() => {
2278
2373
  const pending = [...pendingImpressionRef.current.values()];
2279
2374
  const pendingDirect = [...pendingDirectRef.current.entries()];
2375
+ const pendingFallback = [...pendingFallbackRef.current.entries()];
2280
2376
  const drawn = drawnRef.current;
2281
2377
  pendingImpressionRef.current = /* @__PURE__ */ new Map();
2282
2378
  pendingDirectRef.current = /* @__PURE__ */ new Map();
2379
+ pendingFallbackRef.current = /* @__PURE__ */ new Map();
2283
2380
  drawnRef.current = /* @__PURE__ */ new Set();
2284
2381
  const directReports = pendingDirect.filter(([, p]) => p.call === sponsorCallRef.current && drawn.has(p.drawnId)).map(([format, p]) => ({ format, serveToken: p.serveToken }));
2285
2382
  if (directReports.length > 0) void client.reportDirectImpressions(directReports);
2383
+ const fallbackReports = pendingFallback.filter(([, p]) => p.call === sponsorCallRef.current && drawn.has(p.drawnId)).map(([format, p]) => ({ format, serveToken: p.serveToken }));
2384
+ if (fallbackReports.length > 0) void client.reportAffiliateImpressions(fallbackReports);
2286
2385
  const reports = pending.filter((p) => p.call === sponsorCallRef.current).filter((p) => drawn.has(p.impression.impressionId)).map((p) => p.impression);
2287
2386
  if (reports.length === 0) return;
2288
2387
  void (async () => {
@@ -2364,6 +2463,26 @@ function App({
2364
2463
  },
2365
2464
  [cols, keepOutput, push]
2366
2465
  );
2466
+ const replayConversation = useCallback(
2467
+ (stored) => {
2468
+ const title = stored.title ? ` \xB7 ${stored.title}` : "";
2469
+ push({
2470
+ kind: "notice",
2471
+ tone: "good",
2472
+ text: ` resumed session ${stored.id} (${stored.messages.length} messages)${title}`
2473
+ });
2474
+ for (const item of replayItems(stored.messages)) {
2475
+ if (item.kind === "tool") commitTool(item.name, item.args, { result: item.result, ok: item.ok });
2476
+ else if (item.kind === "notice") push({ kind: "notice", text: ` ${item.text}` });
2477
+ else push(item);
2478
+ }
2479
+ push({ kind: "notice", text: " the agent has all of this conversation; carry on below" });
2480
+ },
2481
+ [commitTool, push]
2482
+ );
2483
+ useEffect(() => {
2484
+ if (session?.messages.length) replayConversation(session);
2485
+ }, []);
2367
2486
  const handleEvent = useCallback(
2368
2487
  (event) => {
2369
2488
  if (event.type === "delta") {
@@ -3349,11 +3468,23 @@ ${preferencesText}` : ""),
3349
3468
  // earlier conversation was to quit the one you were in.
3350
3469
  case "resume":
3351
3470
  case "sessions": {
3352
- const metas = listSessions(root, 10);
3353
- if (!metas.length) {
3354
- push({ kind: "notice", tone: "warn", text: " no saved sessions in this directory yet" });
3471
+ const [verb, target] = arg.split(/\s+/);
3472
+ if (verb === "delete" || verb === "rm") {
3473
+ if (!target) {
3474
+ push({ kind: "notice", tone: "warn", text: " /resume delete <id>, or press d twice on a row in /resume" });
3475
+ return;
3476
+ }
3477
+ if (target === sessionRef.current.id) {
3478
+ push({ kind: "notice", tone: "warn", text: ` ${target} is the conversation you are in. /clear starts a new one first.` });
3479
+ return;
3480
+ }
3481
+ push(
3482
+ deleteSession(target) ? { kind: "notice", tone: "good", text: ` deleted session ${target}` } : { kind: "notice", tone: "error", text: ` no such session: ${target}` }
3483
+ );
3355
3484
  return;
3356
3485
  }
3486
+ const everywhere = verb === "all";
3487
+ const metas = listSessions(everywhere ? void 0 : root, MAX_SESSIONS);
3357
3488
  const load = (id) => {
3358
3489
  const found = loadSession(id);
3359
3490
  if (!found) {
@@ -3362,26 +3493,42 @@ ${preferencesText}` : ""),
3362
3493
  }
3363
3494
  setMessages(found.messages);
3364
3495
  sessionRef.current = { id: found.id, started: found.started, title: found.title };
3496
+ todosRef.current = new TodoList();
3497
+ lastTurnRef.current = null;
3498
+ replayConversation(found);
3499
+ };
3500
+ if (arg && !everywhere) return load(arg);
3501
+ if (!metas.length) {
3502
+ const elsewhere = everywhere ? 0 : listSessions(void 0, MAX_SESSIONS).length;
3365
3503
  push({
3366
3504
  kind: "notice",
3367
- tone: "good",
3368
- text: ` resumed ${found.id} \u2014 ${found.messages.length} messages
3369
- ${found.title}`
3505
+ tone: "warn",
3506
+ text: elsewhere ? ` no saved sessions in this directory yet. ${elsewhere} in other directories: /resume all lists them.` : " no saved sessions yet"
3370
3507
  });
3371
- };
3372
- if (arg) return load(arg);
3508
+ return;
3509
+ }
3373
3510
  const items = metas.map((m) => ({
3374
3511
  value: m.id,
3375
3512
  label: m.updated.slice(0, 16).replace("T", " "),
3376
- hint: m.title.slice(0, 60),
3513
+ hint: `${m.title.slice(0, 60)} \xB7 ${m.messageCount} messages${everywhere ? ` \xB7 ${m.cwd}` : ""}`,
3377
3514
  current: m.id === sessionRef.current.id
3378
3515
  }));
3379
3516
  setPickerSel(0);
3517
+ setPickerArmed(null);
3518
+ setPickerNote(null);
3380
3519
  setPicker({
3381
- title: "Resume a session",
3382
- subtitle: "Replaces the current conversation; nothing on screen is lost.",
3520
+ title: everywhere ? "Resume a session from any directory" : "Resume a session",
3521
+ subtitle: "Replaces the current conversation; nothing on screen is lost. d twice deletes one.",
3383
3522
  items,
3384
- onPick: (choice) => load(choice.value)
3523
+ onPick: (choice) => load(choice.value),
3524
+ onDelete: (choice) => {
3525
+ if (choice.value === sessionRef.current.id) {
3526
+ return "That is the conversation you are in. /clear starts a new one first.";
3527
+ }
3528
+ if (!deleteSession(choice.value)) return "It could not be deleted.";
3529
+ push({ kind: "notice", tone: "good", text: ` deleted session ${choice.value}` });
3530
+ return void 0;
3531
+ }
3385
3532
  });
3386
3533
  return;
3387
3534
  }
@@ -3480,7 +3627,7 @@ ${preferencesText}` : ""),
3480
3627
  push({ kind: "notice", tone: "warn", text: ` unknown command: /${cmd} \u2014 try /help` });
3481
3628
  }
3482
3629
  },
3483
- [client, cols, config, cycleMode, exit, model, modelTaskHints, premium, push, root, runAdWall, runBusy, runBuy, runLogin, runTurn, setPremium, writeToStdout]
3630
+ [client, cols, config, cycleMode, exit, model, modelTaskHints, premium, push, replayConversation, root, runAdWall, runBusy, runBuy, runLogin, runTurn, setPremium, writeToStdout]
3484
3631
  );
3485
3632
  const runShell = useCallback(
3486
3633
  async (command2) => {
@@ -3507,9 +3654,7 @@ ${preferencesText}` : ""),
3507
3654
  if (output) {
3508
3655
  setMessages((prev) => [
3509
3656
  ...prev,
3510
- { role: "user", content: `I ran \`${command2}\` in the workspace myself. Its output:
3511
-
3512
- ${output}` }
3657
+ { role: "user", content: shellNote(command2, output) }
3513
3658
  ]);
3514
3659
  }
3515
3660
  },
@@ -3718,6 +3863,22 @@ ${output}` }
3718
3863
  }
3719
3864
  if (picker) {
3720
3865
  const n = picker.items.length;
3866
+ if (picker.onDelete && (ch === "d" || ch === "D") && !key.ctrl && !key.meta) {
3867
+ const item = picker.items[Math.min(pickerSel, n - 1)];
3868
+ if (!item) return;
3869
+ setPickerNote(null);
3870
+ if (pickerArmed !== item.value) return setPickerArmed(item.value);
3871
+ setPickerArmed(null);
3872
+ const refused = picker.onDelete(item);
3873
+ if (refused) return setPickerNote(refused);
3874
+ const rest = picker.items.filter((i) => i !== item);
3875
+ if (!rest.length) return setPicker(null);
3876
+ setPicker({ ...picker, items: rest });
3877
+ setPickerSel((sel2) => Math.min(sel2, rest.length - 1));
3878
+ return;
3879
+ }
3880
+ if (pickerArmed !== null) setPickerArmed(null);
3881
+ if (pickerNote !== null) setPickerNote(null);
3721
3882
  if (key.escape) {
3722
3883
  const cancel = picker.onCancel;
3723
3884
  setPicker(null);
@@ -3862,13 +4023,13 @@ ${dropped.map((l) => ` \xB7 ${l}`).join("\n")}`
3862
4023
  });
3863
4024
  const elapsed = busy && startedAt ? Math.floor((Date.now() - startedAt) / 1e3) : 0;
3864
4025
  const spinner = spinnerRef.current[Math.floor(tick / SPINNER_TICKS_PER_FRAME) % spinnerRef.current.length];
3865
- const sponsorLine = busy && sponsor ? sponsorRow(sponsor.text, cols, sponsor.origin) : null;
4026
+ const sponsorLine = busy && sponsor ? sponsorRow(sponsor.text, cols, sponsor.origin, sponsor.linkText) : null;
3866
4027
  const adColor = adColorEnabled({ isTTY: stdout.isTTY, env: process.env });
3867
4028
  const linkable = hyperlinksEnabled({ isTTY: stdout.isTTY, env: process.env });
3868
4029
  const modalOpen = Boolean(picker || ask || plan || buy);
3869
4030
  modalOpenRef.current = modalOpen;
3870
- const card = busy && !modalOpen && adSlots.sidebar_card ? sidebarCard(adSlots.sidebar_card.text, cols) : null;
3871
- const statusBarLine = busy && !modalOpen && adSlots.status_bar ? sponsorRow(adSlots.status_bar.text, cols, "network") : null;
4031
+ const card = busy && !modalOpen && adSlots.sidebar_card ? sidebarCard(adSlots.sidebar_card.text, cols, adSlots.sidebar_card.linkText) : null;
4032
+ const statusBarLine = busy && !modalOpen && adSlots.status_bar ? sponsorRow(adSlots.status_bar.text, cols, "network", adSlots.status_bar.linkText) : null;
3872
4033
  const viewport = Math.max(1, rows - 1);
3873
4034
  const inputRows = Math.max(1, Math.min(MAX_INPUT_ROWS, editor.lines.length));
3874
4035
  const inputFrom = windowStart(editor.row, editor.lines.length, inputRows);
@@ -3932,16 +4093,17 @@ ${dropped.map((l) => ` \xB7 ${l}`).join("\n")}`
3932
4093
  };
3933
4094
  });
3934
4095
  const pickerTitle = picker ? fitRow(picker.title, inner) : "";
3935
- const pickerSubtitle = picker ? fitRow(picker.subtitle, inner) : "";
4096
+ const pickerSubtitle = picker ? fitRow(pickerArmed !== null ? PICKER_DELETE_CONFIRM : pickerNote ?? picker.subtitle, inner) : "";
4097
+ const pickerDelete = picker?.onDelete ? " \xB7 d delete" : "";
3936
4098
  const pickerHint = picker ? pickHint(
3937
4099
  picker.items.length > pickerItems.length ? [
3938
- `\u2191\u2193 choose (${pickerFrom + 1}-${pickerFrom + pickerItems.length} of ${picker.items.length}) \xB7 1-${pickerItems.length} jump \xB7 \u23CE confirm \xB7 esc cancel`,
3939
- `\u2191\u2193 ${pickerFrom + 1}-${pickerFrom + pickerItems.length}/${picker.items.length} \xB7 \u23CE confirm \xB7 esc cancel`,
3940
- "\u2191\u2193 \xB7 \u23CE \xB7 esc"
4100
+ `\u2191\u2193 choose (${pickerFrom + 1}-${pickerFrom + pickerItems.length} of ${picker.items.length}) \xB7 1-${pickerItems.length} jump \xB7 \u23CE confirm${pickerDelete} \xB7 esc cancel`,
4101
+ `\u2191\u2193 ${pickerFrom + 1}-${pickerFrom + pickerItems.length}/${picker.items.length} \xB7 \u23CE confirm${pickerDelete} \xB7 esc cancel`,
4102
+ picker.onDelete ? "\u2191\u2193 \xB7 \u23CE \xB7 d \xB7 esc" : "\u2191\u2193 \xB7 \u23CE \xB7 esc"
3941
4103
  ] : [
3942
- `\u2191\u2193 choose \xB7 1-${Math.min(9, pickerItems.length)} jump straight to a row \xB7 \u23CE confirm \xB7 esc cancel`,
3943
- `\u2191\u2193 choose \xB7 1-${Math.min(9, pickerItems.length)} jump \xB7 \u23CE confirm \xB7 esc cancel`,
3944
- "\u2191\u2193 \xB7 \u23CE \xB7 esc"
4104
+ `\u2191\u2193 choose \xB7 1-${Math.min(9, pickerItems.length)} jump straight to a row \xB7 \u23CE confirm${pickerDelete} \xB7 esc cancel`,
4105
+ `\u2191\u2193 choose \xB7 1-${Math.min(9, pickerItems.length)} jump \xB7 \u23CE confirm${pickerDelete} \xB7 esc cancel`,
4106
+ picker.onDelete ? "\u2191\u2193 \xB7 \u23CE \xB7 d \xB7 esc" : "\u2191\u2193 \xB7 \u23CE \xB7 esc"
3945
4107
  ],
3946
4108
  inner
3947
4109
  ) : "";
@@ -4146,7 +4308,11 @@ ${dropped.map((l) => ` \xB7 ${l}`).join("\n")}`
4146
4308
  /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: SIDEBAR_CARD_COLS, children: [
4147
4309
  /* @__PURE__ */ jsxs2(Box2, { children: [
4148
4310
  /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: card.rule }),
4149
- /* @__PURE__ */ jsx2(Text2, { ...adColor ? { color: AD_MARK } : { dimColor: true }, wrap: "truncate-end", children: card.label })
4311
+ /* @__PURE__ */ jsx2(Text2, { ...adColor ? { color: AD_MARK } : { dimColor: true }, wrap: "truncate-end", children: card.label }),
4312
+ card.link ? /* @__PURE__ */ jsxs2(Text2, { ...adColor ? { color: AD_TEXT } : { dimColor: true }, wrap: "truncate-end", children: [
4313
+ LINK_SEPARATOR,
4314
+ /* @__PURE__ */ jsx2(Text2, { underline: adColor, children: linkable && adSlots.sidebar_card?.link ? osc8(card.link, adSlots.sidebar_card.link) : card.link })
4315
+ ] }) : null
4150
4316
  ] }),
4151
4317
  /* @__PURE__ */ jsxs2(Box2, { children: [
4152
4318
  /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: card.rule }),
@@ -4235,7 +4401,13 @@ function SponsorRowView({ row, adColor, link }) {
4235
4401
  return /* @__PURE__ */ jsxs2(Box2, { children: [
4236
4402
  /* @__PURE__ */ jsx2(Text2, { dimColor: true, wrap: "truncate-end", children: row.marker }),
4237
4403
  row.label ? /* @__PURE__ */ jsx2(Text2, { ...adColor ? { color: AD_MARK } : { dimColor: true }, wrap: "truncate-end", children: row.label }) : null,
4238
- /* @__PURE__ */ jsx2(Text2, { ...adColor && row.origin === "network" ? { color: AD_TEXT } : { dimColor: true }, wrap: "truncate-end", children: link && row.origin === "network" ? osc8(row.body, link) : row.body })
4404
+ /* @__PURE__ */ jsxs2(Text2, { ...adColor && row.origin === "network" ? { color: AD_TEXT } : { dimColor: true }, wrap: "truncate-end", children: [
4405
+ link && row.origin === "network" ? osc8(row.body, link) : row.body,
4406
+ row.link ? /* @__PURE__ */ jsxs2(Fragment, { children: [
4407
+ LINK_SEPARATOR,
4408
+ /* @__PURE__ */ jsx2(Text2, { underline: adColor, children: link ? osc8(row.link, link) : row.link })
4409
+ ] }) : null
4410
+ ] })
4239
4411
  ] });
4240
4412
  }
4241
4413
  function sleep(ms, signal) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clixad",
3
- "version": "0.0.1-beta.44",
3
+ "version": "0.0.1-beta.46",
4
4
  "description": "Free AI coding agent in your terminal, funded by rewarded ads.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",