@cortexkit/opencode-magic-context 0.29.1 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -163278,7 +163278,7 @@ var require_src = __commonJS((exports, module) => {
163278
163278
  };
163279
163279
  });
163280
163280
 
163281
- // ../../node_modules/.bun/comment-json@4.6.2/node_modules/comment-json/src/common.js
163281
+ // ../../node_modules/.bun/comment-json@5.0.0/node_modules/comment-json/src/common.js
163282
163282
  var require_common = __commonJS((exports, module) => {
163283
163283
  var PREFIX_BEFORE = "before";
163284
163284
  var PREFIX_AFTER_PROP = "after-prop";
@@ -163310,8 +163310,6 @@ var require_common = __commonJS((exports, module) => {
163310
163310
  var NON_PROP_SYMBOL_KEYS = NON_PROP_SYMBOL_PREFIXES.map(Symbol.for);
163311
163311
  var COLON = ":";
163312
163312
  var UNDEFINED = undefined;
163313
- var LINE_BREAKS_BEFORE = new WeakMap;
163314
- var LINE_BREAKS_AFTER = new WeakMap;
163315
163313
  var RAW_STRING_LITERALS = new WeakMap;
163316
163314
  var is_string = (subject) => typeof subject === "string";
163317
163315
  var is_number = (subject) => typeof subject === "number";
@@ -163414,17 +163412,49 @@ var require_common = __commonJS((exports, module) => {
163414
163412
  });
163415
163413
  return target;
163416
163414
  };
163417
- var is_raw_json = typeof JSON.isRawJSON === "function" ? JSON.isRawJSON : () => false;
163418
- var set_comment_line_breaks = (comment, before, after) => {
163419
- if (is_number(before) && before >= 0) {
163420
- LINE_BREAKS_BEFORE.set(comment, before);
163415
+ var is_comment_symbol = (subject) => {
163416
+ const key = Symbol.keyFor(subject);
163417
+ return is_string(key) && (NON_PROP_SYMBOL_PREFIXES.includes(key) || PROP_SYMBOL_PREFIXES.some((prefix) => key.indexOf(prefix + COLON) === 0));
163418
+ };
163419
+ var remove_blank_line_tokens = (comments) => {
163420
+ let write = 0;
163421
+ let removed = false;
163422
+ comments.forEach((comment) => {
163423
+ if (comment && comment.type === "BlankLine") {
163424
+ removed = true;
163425
+ return;
163426
+ }
163427
+ comments[write++] = comment;
163428
+ });
163429
+ comments.length = write;
163430
+ return removed;
163431
+ };
163432
+ var remove_blank_lines_from_prop = (target, prop) => {
163433
+ if (!Object.hasOwn(target, prop)) {
163434
+ return;
163421
163435
  }
163422
- if (is_number(after) && after >= 0) {
163423
- LINE_BREAKS_AFTER.set(comment, after);
163436
+ const comments = target[prop];
163437
+ if (!Array.isArray(comments) || !remove_blank_line_tokens(comments)) {
163438
+ return;
163439
+ }
163440
+ if (!comments.length) {
163441
+ delete target[prop];
163424
163442
  }
163425
163443
  };
163426
- var get_comment_line_breaks_before = (comment) => LINE_BREAKS_BEFORE.get(comment);
163427
- var get_comment_line_breaks_after = (comment) => LINE_BREAKS_AFTER.get(comment);
163444
+ var remove_blank_lines_deep = (target) => {
163445
+ Object.getOwnPropertySymbols(target).forEach((prop) => {
163446
+ if (is_comment_symbol(prop)) {
163447
+ remove_blank_lines_from_prop(target, prop);
163448
+ }
163449
+ });
163450
+ Object.keys(target).forEach((key) => {
163451
+ const value = target[key];
163452
+ if (is_object(value)) {
163453
+ remove_blank_lines_deep(value);
163454
+ }
163455
+ });
163456
+ };
163457
+ var is_raw_json = typeof JSON.isRawJSON === "function" ? JSON.isRawJSON : () => false;
163428
163458
  module.exports = {
163429
163459
  PROP_SYMBOL_PREFIXES,
163430
163460
  PREFIX_BEFORE,
@@ -163454,9 +163484,6 @@ var require_common = __commonJS((exports, module) => {
163454
163484
  is_raw_json,
163455
163485
  set_raw_string_literal,
163456
163486
  get_raw_string_literal,
163457
- set_comment_line_breaks,
163458
- get_comment_line_breaks_before,
163459
- get_comment_line_breaks_after,
163460
163487
  assign(target, source, keys) {
163461
163488
  if (!is_object(target)) {
163462
163489
  throw new TypeError("Cannot convert undefined or null to object");
@@ -163518,11 +163545,26 @@ var require_common = __commonJS((exports, module) => {
163518
163545
  return;
163519
163546
  }
163520
163547
  delete target[prop];
163548
+ },
163549
+ removeBlankLines(target, location) {
163550
+ if (!is_object(target)) {
163551
+ throw new TypeError("target must be an object");
163552
+ }
163553
+ if (location === UNDEFINED) {
163554
+ remove_blank_lines_deep(target);
163555
+ return;
163556
+ }
163557
+ const {
163558
+ where,
163559
+ key
163560
+ } = location;
163561
+ const prop = symbol_checked(where, key);
163562
+ remove_blank_lines_from_prop(target, prop);
163521
163563
  }
163522
163564
  };
163523
163565
  });
163524
163566
 
163525
- // ../../node_modules/.bun/comment-json@4.6.2/node_modules/comment-json/src/array.js
163567
+ // ../../node_modules/.bun/comment-json@5.0.0/node_modules/comment-json/src/array.js
163526
163568
  var require_array = __commonJS((exports, module) => {
163527
163569
  var { sort } = require_src();
163528
163570
  var {
@@ -163680,7 +163722,7 @@ var require_array = __commonJS((exports, module) => {
163680
163722
  };
163681
163723
  });
163682
163724
 
163683
- // ../../node_modules/.bun/comment-json@4.6.2/node_modules/comment-json/src/parse.js
163725
+ // ../../node_modules/.bun/comment-json@5.0.0/node_modules/comment-json/src/parse.js
163684
163726
  var require_parse = __commonJS((exports, module) => {
163685
163727
  var esprima = require_esprima();
163686
163728
  var {
@@ -163706,7 +163748,6 @@ var require_parse = __commonJS((exports, module) => {
163706
163748
  is_object,
163707
163749
  define: define2,
163708
163750
  set_raw_string_literal,
163709
- set_comment_line_breaks,
163710
163751
  assign_non_prop_comments
163711
163752
  } = require_common();
163712
163753
  var tokenize = (code) => esprima.tokenize(code, {
@@ -163720,23 +163761,27 @@ var require_parse = __commonJS((exports, module) => {
163720
163761
  var previous_props = [];
163721
163762
  var last_prop;
163722
163763
  var remove_comments = false;
163764
+ var remove_blank_lines = false;
163723
163765
  var inline = false;
163724
163766
  var tokens = null;
163725
163767
  var last = null;
163726
163768
  var current = null;
163727
163769
  var index;
163728
163770
  var reviver = null;
163771
+ var source_line_count = 0;
163729
163772
  var clean = () => {
163730
163773
  current_code = UNDEFINED;
163731
163774
  previous_props.length = previous_hosts.length = 0;
163732
163775
  last = null;
163733
163776
  last_prop = UNDEFINED;
163777
+ remove_blank_lines = false;
163734
163778
  };
163735
163779
  var free = () => {
163736
163780
  clean();
163737
163781
  tokens.length = 0;
163738
163782
  unassigned_comments = comments_host = tokens = last = current = reviver = null;
163739
163783
  current_code = UNDEFINED;
163784
+ source_line_count = 0;
163740
163785
  };
163741
163786
  var symbolFor = (prefix) => Symbol.for(last_prop !== UNDEFINED ? prefix + COLON + last_prop : prefix);
163742
163787
  var transform2 = (k, { value, context = {} }) => reviver ? reviver(k, value, context) : value;
@@ -163780,6 +163825,37 @@ var require_parse = __commonJS((exports, module) => {
163780
163825
  var restore_comments_host = () => {
163781
163826
  comments_host = previous_hosts.pop();
163782
163827
  };
163828
+ var create_blank_line = () => ({
163829
+ type: "BlankLine",
163830
+ inline: false
163831
+ });
163832
+ var append_blank_lines = (comments, from_line, to_line) => {
163833
+ if (remove_blank_lines) {
163834
+ return;
163835
+ }
163836
+ let blank_lines = Math.max(0, to_line - from_line - 1);
163837
+ while (blank_lines-- > 0) {
163838
+ comments.push(create_blank_line());
163839
+ }
163840
+ };
163841
+ var normalize_parse_options = (options) => {
163842
+ if (typeof options === "boolean") {
163843
+ return {
163844
+ no_comments: options,
163845
+ no_blank_lines: false
163846
+ };
163847
+ }
163848
+ if (!is_object(options)) {
163849
+ return {
163850
+ no_comments: false,
163851
+ no_blank_lines: false
163852
+ };
163853
+ }
163854
+ return {
163855
+ no_comments: !!options.no_comments,
163856
+ no_blank_lines: !!options.no_blank_lines
163857
+ };
163858
+ };
163783
163859
  var assign_after_comments = () => {
163784
163860
  if (!unassigned_comments) {
163785
163861
  return;
@@ -163812,26 +163888,26 @@ var require_parse = __commonJS((exports, module) => {
163812
163888
  };
163813
163889
  var parse_comments = (prefix) => {
163814
163890
  const comments = [];
163891
+ let previous_line = last ? last.loc.end.line : 0;
163815
163892
  while (current && (is("LineComment") || is("BlockComment"))) {
163893
+ append_blank_lines(comments, previous_line, current.loc.start.line);
163816
163894
  const comment = {
163817
163895
  ...current,
163818
163896
  inline
163819
163897
  };
163820
- const previous_line = last ? last.loc.end.line : 1;
163821
- set_comment_line_breaks(comment, Math.max(0, comment.loc.start.line - previous_line));
163822
163898
  comments.push(comment);
163899
+ previous_line = comment.loc.end.line;
163823
163900
  next();
163824
163901
  }
163825
- const { length } = comments;
163826
- if (length) {
163827
- const comment = comments[length - 1];
163828
- const current_line = current ? current.loc.start.line : comment.loc.end.line;
163829
- set_comment_line_breaks(comment, undefined, Math.max(0, current_line - comment.loc.end.line));
163830
- }
163902
+ append_blank_lines(comments, previous_line, current ? current.loc.start.line : source_line_count);
163831
163903
  if (remove_comments) {
163832
- return;
163904
+ for (let i = comments.length - 1;i >= 0; i--) {
163905
+ if (comments[i].type !== "BlankLine") {
163906
+ comments.splice(i, 1);
163907
+ }
163908
+ }
163833
163909
  }
163834
- if (!length) {
163910
+ if (!comments.length) {
163835
163911
  return;
163836
163912
  }
163837
163913
  if (prefix) {
@@ -163970,12 +164046,19 @@ var require_parse = __commonJS((exports, module) => {
163970
164046
  return {};
163971
164047
  }
163972
164048
  }
163973
- var parse5 = (code, rev, no_comments) => {
164049
+ var parse5 = (code, rev, options) => {
163974
164050
  clean();
164051
+ const {
164052
+ no_comments,
164053
+ no_blank_lines
164054
+ } = normalize_parse_options(options);
163975
164055
  current_code = code;
164056
+ source_line_count = code.split(`
164057
+ `).length;
163976
164058
  tokens = tokenize(code);
163977
164059
  reviver = rev;
163978
164060
  remove_comments = no_comments;
164061
+ remove_blank_lines = no_blank_lines;
163979
164062
  if (!tokens.length) {
163980
164063
  unexpected_end();
163981
164064
  }
@@ -164005,7 +164088,7 @@ var require_parse = __commonJS((exports, module) => {
164005
164088
  };
164006
164089
  });
164007
164090
 
164008
- // ../../node_modules/.bun/comment-json@4.6.2/node_modules/comment-json/src/stringify.js
164091
+ // ../../node_modules/.bun/comment-json@5.0.0/node_modules/comment-json/src/stringify.js
164009
164092
  var require_stringify = __commonJS((exports, module) => {
164010
164093
  var {
164011
164094
  PREFIX_BEFORE_ALL,
@@ -164027,8 +164110,6 @@ var require_stringify = __commonJS((exports, module) => {
164027
164110
  is_number,
164028
164111
  is_object,
164029
164112
  get_raw_string_literal,
164030
- get_comment_line_breaks_before,
164031
- get_comment_line_breaks_after,
164032
164113
  is_raw_json
164033
164114
  } = require_common();
164034
164115
  var SPACE = " ";
@@ -164043,26 +164124,18 @@ var require_stringify = __commonJS((exports, module) => {
164043
164124
  var quote = JSON.stringify;
164044
164125
  var comment_stringify = (value, line) => line ? `//${value}` : `/*${value}*/`;
164045
164126
  var repeat_line_breaks = (line_breaks, gap) => (LF + gap).repeat(line_breaks);
164046
- var read_line_breaks = (line_breaks) => is_number(line_breaks) && line_breaks >= 0 ? line_breaks : null;
164047
- var read_line_breaks_from_loc = (previous_comment, comment) => {
164048
- if (!previous_comment || !previous_comment.loc || !comment.loc) {
164049
- return null;
164050
- }
164051
- const { end } = previous_comment.loc;
164052
- const { start } = comment.loc;
164053
- if (!end || !start || !is_number(end.line) || !is_number(start.line)) {
164054
- return null;
164055
- }
164056
- const line_breaks = start.line - end.line;
164057
- return line_breaks >= 0 ? line_breaks : null;
164058
- };
164059
- var count_trailing_line_breaks = (str, gap) => {
164060
- const unit = LF + gap;
164061
- const { length } = unit;
164127
+ var is_inline_whitespace = (char) => char === SPACE || char === "\t";
164128
+ var count_trailing_line_breaks = (str) => {
164062
164129
  let i = str.length;
164063
164130
  let count = 0;
164064
- while (i >= length && str.slice(i - length, i) === unit) {
164065
- i -= length;
164131
+ while (i > 0) {
164132
+ while (i > 0 && is_inline_whitespace(str[i - 1])) {
164133
+ i--;
164134
+ }
164135
+ if (i === 0 || str[i - 1] !== LF) {
164136
+ return count;
164137
+ }
164138
+ i--;
164066
164139
  count++;
164067
164140
  }
164068
164141
  return count;
@@ -164073,27 +164146,30 @@ var require_stringify = __commonJS((exports, module) => {
164073
164146
  return EMPTY;
164074
164147
  }
164075
164148
  let str = EMPTY;
164149
+ let blank_lines = 0;
164076
164150
  let last_comment = null;
164077
164151
  comments.forEach((comment, i) => {
164152
+ if (comment.type === "BlankLine") {
164153
+ blank_lines++;
164154
+ return;
164155
+ }
164078
164156
  const {
164079
164157
  inline,
164080
164158
  type,
164081
164159
  value
164082
164160
  } = comment;
164083
- let line_breaks_before = read_line_breaks(get_comment_line_breaks_before(comment));
164084
- if (line_breaks_before === null) {
164085
- line_breaks_before = read_line_breaks_from_loc(last_comment, comment);
164086
- }
164087
- if (line_breaks_before === null) {
164088
- line_breaks_before = inline ? 0 : 1;
164089
- }
164090
- const delimiter = line_breaks_before > 0 ? repeat_line_breaks(line_breaks_before, deeper_gap) : inline ? SPACE : i === 0 ? EMPTY : LF + deeper_gap;
164161
+ const line_breaks_before = blank_lines > 0 ? blank_lines + (inline ? 0 : 1) : null;
164162
+ const delimiter = line_breaks_before > 0 ? repeat_line_breaks(line_breaks_before, deeper_gap) : inline ? SPACE : i === 0 && symbol_tag === PREFIX_BEFORE_ALL && deeper_gap === EMPTY ? EMPTY : LF + deeper_gap;
164091
164163
  const is_line_comment = type === "LineComment";
164092
164164
  str += delimiter + comment_stringify(value, is_line_comment);
164165
+ blank_lines = 0;
164093
164166
  last_comment = comment;
164094
164167
  });
164168
+ if (!last_comment) {
164169
+ return blank_lines ? repeat_line_breaks(blank_lines + 1, deeper_gap) : EMPTY;
164170
+ }
164095
164171
  const default_line_breaks_after = display_block || last_comment.type === "LineComment" ? 1 : 0;
164096
- const line_breaks_after = Math.max(default_line_breaks_after, read_line_breaks(get_comment_line_breaks_after(last_comment)) || 0);
164172
+ const line_breaks_after = blank_lines > 0 ? blank_lines + 1 : default_line_breaks_after;
164097
164173
  return str + repeat_line_breaks(line_breaks_after, deeper_gap);
164098
164174
  };
164099
164175
  var replacer = null;
@@ -164102,7 +164178,7 @@ var require_stringify = __commonJS((exports, module) => {
164102
164178
  replacer = null;
164103
164179
  indent = EMPTY;
164104
164180
  };
164105
- var join6 = (one, two, gap) => one ? two ? one + two.trim() + LF + gap : one.trimRight() + repeat_line_breaks(Math.max(1, count_trailing_line_breaks(one, gap)), gap) : two ? two.trimRight() + repeat_line_breaks(Math.max(1, count_trailing_line_breaks(two, gap)), gap) : EMPTY;
164181
+ var join6 = (one, two, gap) => one ? two ? one + two.trim() + LF + gap : one.trimRight() + repeat_line_breaks(Math.max(1, count_trailing_line_breaks(one)), gap) : two ? two.trimRight() + repeat_line_breaks(Math.max(1, count_trailing_line_breaks(two)), gap) : EMPTY;
164106
164182
  var join_content = (inside, value, gap) => {
164107
164183
  const comment = process_comments(value, PREFIX_BEFORE, gap + indent, true);
164108
164184
  return join6(comment, inside, gap);
@@ -164201,6 +164277,8 @@ var require_stringify = __commonJS((exports, module) => {
164201
164277
  const str = toString.call(subject);
164202
164278
  return PRIMITIVE_OBJECT_TYPES.includes(str);
164203
164279
  };
164280
+ var normalize_blank_lines = (str) => typeof str === "string" ? str.replace(/\n[ \t]+(?=\n)/g, `
164281
+ `) : str;
164204
164282
  module.exports = (value, replacer_, space) => {
164205
164283
  const indent_ = get_indent(space);
164206
164284
  if (!indent_) {
@@ -164213,11 +164291,12 @@ var require_stringify = __commonJS((exports, module) => {
164213
164291
  indent = indent_;
164214
164292
  const str = is_primitive_object(value) ? JSON.stringify(value) : stringify("", { "": value }, EMPTY);
164215
164293
  clean();
164216
- return is_object(value) ? process_comments(value, PREFIX_BEFORE_ALL, EMPTY, true).trimLeft() + str + process_comments(value, PREFIX_AFTER_ALL, EMPTY).trimRight() : str;
164294
+ const output = is_object(value) ? process_comments(value, PREFIX_BEFORE_ALL, EMPTY, true).trimLeft() + str + process_comments(value, PREFIX_AFTER_ALL, EMPTY).trimRight() : str;
164295
+ return normalize_blank_lines(output);
164217
164296
  };
164218
164297
  });
164219
164298
 
164220
- // ../../node_modules/.bun/comment-json@4.6.2/node_modules/comment-json/src/index.js
164299
+ // ../../node_modules/.bun/comment-json@5.0.0/node_modules/comment-json/src/index.js
164221
164300
  var require_src2 = __commonJS((exports, module) => {
164222
164301
  var { parse: parse5, tokenize } = require_parse();
164223
164302
  var stringify = require_stringify();
@@ -164232,7 +164311,8 @@ var require_src2 = __commonJS((exports, module) => {
164232
164311
  PREFIX_AFTER_ALL,
164233
164312
  assign,
164234
164313
  moveComments,
164235
- removeComments
164314
+ removeComments,
164315
+ removeBlankLines
164236
164316
  } = require_common();
164237
164317
  module.exports = {
164238
164318
  PREFIX_BEFORE,
@@ -164248,7 +164328,8 @@ var require_src2 = __commonJS((exports, module) => {
164248
164328
  CommentArray,
164249
164329
  assign,
164250
164330
  moveComments,
164251
- removeComments
164331
+ removeComments,
164332
+ removeBlankLines
164252
164333
  };
164253
164334
  });
164254
164335
 
@@ -184641,12 +184722,13 @@ function shouldShowAnnouncement() {
184641
184722
  }
184642
184723
  return ordering > 0;
184643
184724
  }
184644
- var ANNOUNCEMENT_VERSION = "0.29.0", ANNOUNCEMENT_FEATURES, ANNOUNCEMENT_FOOTER = "Join us on Discord: https://discord.gg/F2uWxjGnU", STATE_FILENAME = "last_announced_version";
184725
+ var ANNOUNCEMENT_VERSION = "0.30.0", ANNOUNCEMENT_FEATURES, ANNOUNCEMENT_FOOTER = "Join us on Discord: https://discord.gg/F2uWxjGnU", STATE_FILENAME = "last_announced_version";
184645
184726
  var init_announcement = __esm(() => {
184646
184727
  init_data_path();
184647
184728
  ANNOUNCEMENT_FEATURES = [
184648
- `New 'smart_drops' option (opt-in, off by default): on long, edit-heavy sessions it reclaims more context by dropping tool output a later call made obsolete (superseded edits, old todowrite/ctx_reduce, spent status calls) instead of only the oldest. When off, the prompt is byte-identical to before. Enable with "smart_drops": true.`,
184649
- "When memory is disabled, the ctx_memory tool and its guidance are no longer shown (they had nothing to write to). ctx_search stays, searching conversation history and git commits."
184729
+ "Setup and doctor now recognize an OpenCode Desktop install that has no CLI on PATH, instead of reporting OpenCode as not installed (#196). Desktop-only users continue setup with manual model entry.",
184730
+ "TUI sidebar badge color follows your theme again (#198): it uses the theme background like sibling badges, with a fallback so the label can't disappear on transparent themes.",
184731
+ "doctor clears a stale plugin cache more reliably (#199): it compares against the plugin's latest published version and preserves the cache when offline."
184650
184732
  ];
184651
184733
  });
184652
184734
 
@@ -18,7 +18,7 @@
18
18
  * Bump only when there are user-visible changes worth a startup dialog.
19
19
  * Does NOT need to match the published package version.
20
20
  */
21
- export declare const ANNOUNCEMENT_VERSION = "0.29.0";
21
+ export declare const ANNOUNCEMENT_VERSION = "0.30.0";
22
22
  /**
23
23
  * Short, user-facing bullet strings. Keep each line ~80 chars or shorter so the
24
24
  * TUI dialog renders cleanly without horizontal scroll on a typical terminal.
@@ -1 +1 @@
1
- {"version":3,"file":"announcement.d.ts","sourceRoot":"","sources":["../../src/shared/announcement.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAOH;;;GAGG;AACH,eAAO,MAAM,oBAAoB,WAAW,CAAC;AAE7C;;;GAGG;AACH,eAAO,MAAM,qBAAqB,EAAE,aAAa,CAAC,MAAM,CAGvD,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,qDAAqD,CAAC;AAyBtF;;;;GAIG;AACH,wBAAgB,wBAAwB,IAAI,MAAM,CAGjD;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAS1D;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,sBAAsB,IAAI,OAAO,CA0BhD"}
1
+ {"version":3,"file":"announcement.d.ts","sourceRoot":"","sources":["../../src/shared/announcement.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAOH;;;GAGG;AACH,eAAO,MAAM,oBAAoB,WAAW,CAAC;AAE7C;;;GAGG;AACH,eAAO,MAAM,qBAAqB,EAAE,aAAa,CAAC,MAAM,CAIvD,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,qDAAqD,CAAC;AAyBtF;;;;GAIG;AACH,wBAAgB,wBAAwB,IAAI,MAAM,CAGjD;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAS1D;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,sBAAsB,IAAI,OAAO,CA0BhD"}
@@ -1,31 +1,46 @@
1
1
  /**
2
- * Pick a readable text color (black or white) for text drawn ON TOP of a given
3
- * background color.
2
+ * Pick the text color for the sidebar header badge (a bold label drawn on a
3
+ * `theme.accent` background).
4
4
  *
5
- * The sidebar header badge previously drew its label with `fg={theme.background}`
6
- * on a `theme.accent` background. That breaks for themes that set
7
- * `background: "none"` (transparent) to respect terminal transparency: the
8
- * resolved background is `RGBA(0,0,0,0)`, so the badge text renders fully
9
- * transparent and disappears (issue #186). The badge background (`accent`) is
10
- * always opaque, so deriving the text color from it is transparency-proof.
5
+ * Primary rule (matches AFT's sidebar by construction): paint the theme's own
6
+ * `background` color as the label, the inverse-of-panel look. Because it is a
7
+ * fixed theme token rather than an accent-derived computation, MC's badge and
8
+ * AFT's badge agree on EVERY accent automatically, so the same theme can never
9
+ * make one badge black and the other white (issue #198).
11
10
  *
12
- * The pick is WHITE-BIASED off the accent's relative luminance: white for any
13
- * accent in the dark half (luminance < 0.5), black only for genuinely light
14
- * accents. A strict "higher-contrast-wins" pick (crossover at luminance 0.179)
15
- * flips ordinary mid-tone accents to black: a typical orange/amber sidebar
16
- * accent (luminance ~0.3) reads black ~5:1 vs white ~3.7:1, so contrast-wins
17
- * picks black even though white at ~3.7:1 is perfectly legible for a short bold
18
- * label. That looks heavy and clashes with the sibling status badges, so we
19
- * prefer white across the whole dark half and only fall to black once the accent
20
- * is actually light (pale/pastel/near-white), where white would be unreadable.
11
+ * Fallback rule (the reason a luminance pick exists at all): `theme.background`
12
+ * can be unusable as a label color in two degenerate cases, where it would
13
+ * render the label invisible on the accent:
14
+ * 1. Transparent background. Themes that respect terminal transparency set
15
+ * `background: "none"`, which resolves to `RGBA(0,0,0,0)`; drawing it as
16
+ * text renders fully transparent and the label disappears (issue #186).
17
+ * 2. Background ~= accent. If the theme's background and accent are nearly the
18
+ * same color, background-on-accent text has no contrast.
19
+ * In either case we fall back to a black/white pick that is guaranteed visible
20
+ * on the always-opaque accent.
21
21
  *
22
- * `RGBA` channels from @opentui/core are normalized 0..1 floats. We accept the
23
- * minimal `{ r, g, b }` shape so this stays a pure, trivially testable function
24
- * independent of the native color class.
22
+ * `RGBA` channels from @opentui/core are normalized 0..1 floats (alpha included).
23
+ * We accept the minimal `{ r, g, b, a? }` shape so this stays a pure, trivially
24
+ * testable function independent of the native color class, and we return the
25
+ * passed-in `background` object unchanged on the primary path so it stays the
26
+ * exact same theme token AFT uses.
25
27
  */
26
- export declare function readableTextColorOn(bg: {
28
+ type Color = {
27
29
  r: number;
28
30
  g: number;
29
31
  b: number;
30
- }): string;
32
+ a?: number;
33
+ };
34
+ /**
35
+ * Pure black/white pick by accent luminance. Used as the badge fallback and kept
36
+ * exported for callers that only have the accent.
37
+ */
38
+ export declare function readableTextColorOn(bg: Color): string;
39
+ /**
40
+ * Badge label color on the accent: the theme background (AFT parity) when it is
41
+ * usable, else a guaranteed-visible black/white fallback. Returns the passed-in
42
+ * `background` reference unchanged on the primary path.
43
+ */
44
+ export declare function badgeTextColor<T extends Color>(accent: T, background: T): T | string;
45
+ export {};
31
46
  //# sourceMappingURL=badge-contrast.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"badge-contrast.d.ts","sourceRoot":"","sources":["../../src/tui/badge-contrast.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAmBH,wBAAgB,mBAAmB,CAAC,EAAE,EAAE;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAEnF"}
1
+ {"version":3,"file":"badge-contrast.d.ts","sourceRoot":"","sources":["../../src/tui/badge-contrast.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,KAAK,KAAK,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAoC7D;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,KAAK,GAAG,MAAM,CAErD;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,GAAG,CAAC,GAAG,MAAM,CAMpF"}
package/package.json CHANGED
@@ -1,80 +1,80 @@
1
1
  {
2
- "name": "@cortexkit/opencode-magic-context",
3
- "version": "0.29.1",
4
- "type": "module",
5
- "description": "OpenCode plugin for Magic Context — cross-session memory and context management",
6
- "main": "dist/index.js",
7
- "types": "dist/index.d.ts",
8
- "author": "ualtinok",
9
- "license": "MIT",
10
- "keywords": [
11
- "opencode",
12
- "plugin",
13
- "context",
14
- "memory",
15
- "ai",
16
- "llm",
17
- "prompt-caching",
18
- "session-history"
19
- ],
20
- "repository": {
21
- "type": "git",
22
- "url": "https://github.com/cortexkit/magic-context",
23
- "directory": "packages/plugin"
24
- },
25
- "files": [
26
- "dist",
27
- "src/tui",
28
- "src/shared",
29
- "README.md"
30
- ],
31
- "scripts": {
32
- "build": "bun build src/index.ts --outdir dist --target node --format esm --external @opencode-ai/plugin --external @huggingface/transformers --external onnxruntime-web --external bun:sqlite --external node:sqlite && tsc --emitDeclarationOnly",
33
- "typecheck": "tsc --noEmit && tsc -p tsconfig.scripts.json",
34
- "test": "bun test",
35
- "lint": "biome check .",
36
- "lint:fix": "biome check --write .",
37
- "format": "biome format --write .",
38
- "format:check": "biome format .",
39
- "clean": "rm -rf dist",
40
- "prepublishOnly": "bun run build"
41
- },
42
- "dependencies": {
43
- "@huggingface/transformers": "^4.1.0",
44
- "@jitl/quickjs-singlefile-cjs-release-asyncify": "0.32.0",
45
- "@opencode-ai/plugin": "^1.15.13",
46
- "@opencode-ai/sdk": "^1.15.13",
47
- "@opentui/core": "^0.4.2",
48
- "@opentui/solid": "^0.4.2",
49
- "ai-tokenizer": "^1.0.6",
50
- "comment-json": "^4.2.5",
51
- "quickjs-emscripten": "^0.32.0",
52
- "solid-js": "1.9.12",
53
- "zod": "^4.1.8"
54
- },
55
- "devDependencies": {
56
- "@biomejs/biome": "^2.4.7",
57
- "@types/better-sqlite3": "^7.6.13",
58
- "@types/bun": "^1.3.10",
59
- "@types/node": "^22.0.0",
60
- "bun-types": "^1.3.10",
61
- "typescript": "^5.8.0"
62
- },
63
- "exports": {
64
- ".": {
65
- "types": "./dist/index.d.ts",
66
- "import": "./dist/index.js"
67
- },
68
- "./tui": {
69
- "types": "./src/tui/index.tsx",
70
- "import": "./src/tui/index.tsx"
71
- }
72
- },
73
- "oc-plugin": [
74
- "server",
75
- "tui"
76
- ],
77
- "peerDependencies": {
78
- "@opencode-ai/plugin": ">=1.15.0"
79
- }
2
+ "name": "@cortexkit/opencode-magic-context",
3
+ "version": "0.30.0",
4
+ "type": "module",
5
+ "description": "OpenCode plugin for Magic Context — cross-session memory and context management",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "author": "ualtinok",
9
+ "license": "MIT",
10
+ "keywords": [
11
+ "opencode",
12
+ "plugin",
13
+ "context",
14
+ "memory",
15
+ "ai",
16
+ "llm",
17
+ "prompt-caching",
18
+ "session-history"
19
+ ],
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "https://github.com/cortexkit/magic-context",
23
+ "directory": "packages/plugin"
24
+ },
25
+ "files": [
26
+ "dist",
27
+ "src/tui",
28
+ "src/shared",
29
+ "README.md"
30
+ ],
31
+ "scripts": {
32
+ "build": "bun build src/index.ts --outdir dist --target node --format esm --external @opencode-ai/plugin --external @huggingface/transformers --external onnxruntime-web --external bun:sqlite --external node:sqlite && tsc --emitDeclarationOnly",
33
+ "typecheck": "tsc --noEmit && tsc -p tsconfig.scripts.json",
34
+ "test": "bun test",
35
+ "lint": "biome check .",
36
+ "lint:fix": "biome check --write .",
37
+ "format": "biome format --write .",
38
+ "format:check": "biome format .",
39
+ "clean": "rm -rf dist",
40
+ "prepublishOnly": "bun run build"
41
+ },
42
+ "dependencies": {
43
+ "@huggingface/transformers": "^4.1.0",
44
+ "@jitl/quickjs-singlefile-cjs-release-asyncify": "0.32.0",
45
+ "@opencode-ai/plugin": "^1.17.11",
46
+ "@opencode-ai/sdk": "^1.17.11",
47
+ "@opentui/core": "^0.4.2",
48
+ "@opentui/solid": "^0.4.2",
49
+ "ai-tokenizer": "^1.0.6",
50
+ "comment-json": "^5.0.0",
51
+ "quickjs-emscripten": "^0.32.0",
52
+ "solid-js": "1.9.12",
53
+ "zod": "^4.1.8"
54
+ },
55
+ "devDependencies": {
56
+ "@biomejs/biome": "^2.5.1",
57
+ "@types/better-sqlite3": "^7.6.13",
58
+ "@types/bun": "^1.3.10",
59
+ "@types/node": "^22.20.0",
60
+ "bun-types": "^1.3.10",
61
+ "typescript": "^5.8.0"
62
+ },
63
+ "exports": {
64
+ ".": {
65
+ "types": "./dist/index.d.ts",
66
+ "import": "./dist/index.js"
67
+ },
68
+ "./tui": {
69
+ "types": "./src/tui/index.tsx",
70
+ "import": "./src/tui/index.tsx"
71
+ }
72
+ },
73
+ "oc-plugin": [
74
+ "server",
75
+ "tui"
76
+ ],
77
+ "peerDependencies": {
78
+ "@opencode-ai/plugin": ">=1.15.0"
79
+ }
80
80
  }
@@ -24,15 +24,16 @@ import { getMagicContextStorageDir } from "./data-path";
24
24
  * Bump only when there are user-visible changes worth a startup dialog.
25
25
  * Does NOT need to match the published package version.
26
26
  */
27
- export const ANNOUNCEMENT_VERSION = "0.29.0";
27
+ export const ANNOUNCEMENT_VERSION = "0.30.0";
28
28
 
29
29
  /**
30
30
  * Short, user-facing bullet strings. Keep each line ~80 chars or shorter so the
31
31
  * TUI dialog renders cleanly without horizontal scroll on a typical terminal.
32
32
  */
33
33
  export const ANNOUNCEMENT_FEATURES: ReadonlyArray<string> = [
34
- "New 'smart_drops' option (opt-in, off by default): on long, edit-heavy sessions it reclaims more context by dropping tool output a later call made obsolete (superseded edits, old todowrite/ctx_reduce, spent status calls) instead of only the oldest. When off, the prompt is byte-identical to before. Enable with \"smart_drops\": true.",
35
- "When memory is disabled, the ctx_memory tool and its guidance are no longer shown (they had nothing to write to). ctx_search stays, searching conversation history and git commits.",
34
+ "Setup and doctor now recognize an OpenCode Desktop install that has no CLI on PATH, instead of reporting OpenCode as not installed (#196). Desktop-only users continue setup with manual model entry.",
35
+ "TUI sidebar badge color follows your theme again (#198): it uses the theme background like sibling badges, with a fallback so the label can't disappear on transparent themes.",
36
+ "doctor clears a stale plugin cache more reliably (#199): it compares against the plugin's latest published version and preserves the cache when offline.",
36
37
  ];
37
38
 
38
39
  /**
@@ -1,5 +1,43 @@
1
1
  import { describe, expect, test } from "bun:test";
2
- import { readableTextColorOn } from "./badge-contrast";
2
+ import { badgeTextColor, readableTextColorOn } from "./badge-contrast";
3
+
4
+ describe("badgeTextColor (AFT parity with #186 safety net)", () => {
5
+ // A purple/lavender accent on a dark theme: the badge label should be the
6
+ // theme background (the inverse-of-panel look), the same fixed token the AFT
7
+ // sidebar uses, so two sibling badges never disagree on the same accent.
8
+ const accent = { r: 0.6, g: 0.5, b: 0.9, a: 1 };
9
+
10
+ test("opaque distinct background is used verbatim as the label", () => {
11
+ const background = { r: 0.05, g: 0.05, b: 0.07, a: 1 }; // near-black dark theme
12
+ // Returns the SAME background token rather than a computed color.
13
+ expect(badgeTextColor(accent, background)).toBe(background);
14
+ });
15
+
16
+ test("light theme: background is used verbatim too (near-white label inverse)", () => {
17
+ const background = { r: 0.97, g: 0.97, b: 0.95, a: 1 };
18
+ expect(badgeTextColor(accent, background)).toBe(background);
19
+ });
20
+
21
+ test("transparent background (alpha 0) falls back to a visible pick (#186)", () => {
22
+ // background:"none" resolves to RGBA(0,0,0,0); using it as text would
23
+ // render the label invisible, so fall back to a contrast pick on accent.
24
+ const transparent = { r: 0, g: 0, b: 0, a: 0 };
25
+ const result = badgeTextColor(accent, transparent);
26
+ expect(result).not.toBe(transparent);
27
+ expect(result).toBe(readableTextColorOn(accent));
28
+ });
29
+
30
+ test("background ~= accent falls back to a visible pick", () => {
31
+ const sameAsAccent = { r: 0.6, g: 0.5, b: 0.9, a: 1 };
32
+ const result = badgeTextColor(accent, sameAsAccent);
33
+ expect(result).toBe(readableTextColorOn(accent));
34
+ });
35
+
36
+ test("missing alpha is treated as opaque", () => {
37
+ const background = { r: 0.05, g: 0.05, b: 0.07 };
38
+ expect(badgeTextColor(accent, background)).toBe(background);
39
+ });
40
+ });
3
41
 
4
42
  describe("readableTextColorOn", () => {
5
43
  test("dark accent gets white text", () => {
@@ -1,39 +1,52 @@
1
1
  /**
2
- * Pick a readable text color (black or white) for text drawn ON TOP of a given
3
- * background color.
2
+ * Pick the text color for the sidebar header badge (a bold label drawn on a
3
+ * `theme.accent` background).
4
4
  *
5
- * The sidebar header badge previously drew its label with `fg={theme.background}`
6
- * on a `theme.accent` background. That breaks for themes that set
7
- * `background: "none"` (transparent) to respect terminal transparency: the
8
- * resolved background is `RGBA(0,0,0,0)`, so the badge text renders fully
9
- * transparent and disappears (issue #186). The badge background (`accent`) is
10
- * always opaque, so deriving the text color from it is transparency-proof.
5
+ * Primary rule (matches AFT's sidebar by construction): paint the theme's own
6
+ * `background` color as the label, the inverse-of-panel look. Because it is a
7
+ * fixed theme token rather than an accent-derived computation, MC's badge and
8
+ * AFT's badge agree on EVERY accent automatically, so the same theme can never
9
+ * make one badge black and the other white (issue #198).
11
10
  *
12
- * The pick is WHITE-BIASED off the accent's relative luminance: white for any
13
- * accent in the dark half (luminance < 0.5), black only for genuinely light
14
- * accents. A strict "higher-contrast-wins" pick (crossover at luminance 0.179)
15
- * flips ordinary mid-tone accents to black: a typical orange/amber sidebar
16
- * accent (luminance ~0.3) reads black ~5:1 vs white ~3.7:1, so contrast-wins
17
- * picks black even though white at ~3.7:1 is perfectly legible for a short bold
18
- * label. That looks heavy and clashes with the sibling status badges, so we
19
- * prefer white across the whole dark half and only fall to black once the accent
20
- * is actually light (pale/pastel/near-white), where white would be unreadable.
11
+ * Fallback rule (the reason a luminance pick exists at all): `theme.background`
12
+ * can be unusable as a label color in two degenerate cases, where it would
13
+ * render the label invisible on the accent:
14
+ * 1. Transparent background. Themes that respect terminal transparency set
15
+ * `background: "none"`, which resolves to `RGBA(0,0,0,0)`; drawing it as
16
+ * text renders fully transparent and the label disappears (issue #186).
17
+ * 2. Background ~= accent. If the theme's background and accent are nearly the
18
+ * same color, background-on-accent text has no contrast.
19
+ * In either case we fall back to a black/white pick that is guaranteed visible
20
+ * on the always-opaque accent.
21
21
  *
22
- * `RGBA` channels from @opentui/core are normalized 0..1 floats. We accept the
23
- * minimal `{ r, g, b }` shape so this stays a pure, trivially testable function
24
- * independent of the native color class.
22
+ * `RGBA` channels from @opentui/core are normalized 0..1 floats (alpha included).
23
+ * We accept the minimal `{ r, g, b, a? }` shape so this stays a pure, trivially
24
+ * testable function independent of the native color class, and we return the
25
+ * passed-in `background` object unchanged on the primary path so it stays the
26
+ * exact same theme token AFT uses.
25
27
  */
26
28
 
27
- // Luminance midpoint: accents below this keep white text, accents at/above it
28
- // (light/pastel/near-white) get black. White-biased relative to the strict
29
- // equal-contrast crossover (~0.179) so saturated mid-tone accents stay white.
29
+ type Color = { r: number; g: number; b: number; a?: number };
30
+
31
+ // Below this alpha the theme background is too transparent to read as a label on
32
+ // the accent (issue #186: background:"none" resolves to alpha 0).
33
+ const MIN_OPAQUE_ALPHA = 0.5;
34
+
35
+ // If the theme background and accent are within this per-channel distance they
36
+ // are effectively the same color, so background-on-accent text is unreadable.
37
+ const MIN_CHANNEL_DISTANCE = 0.06;
38
+
39
+ // Luminance midpoint for the fallback pick: accents below this keep white text,
40
+ // accents at/above it (light/pastel/near-white) get black. White-biased relative
41
+ // to the strict equal-contrast crossover (~0.179) so saturated mid-tone accents
42
+ // stay white. Only consulted on the degenerate fallback path.
30
43
  const LIGHT_ACCENT_LUMINANCE = 0.5;
31
44
 
32
45
  function srgbChannelToLinear(c: number): number {
33
46
  return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
34
47
  }
35
48
 
36
- function relativeLuminance(bg: { r: number; g: number; b: number }): number {
49
+ function relativeLuminance(bg: Color): number {
37
50
  return (
38
51
  0.2126 * srgbChannelToLinear(bg.r) +
39
52
  0.7152 * srgbChannelToLinear(bg.g) +
@@ -41,6 +54,31 @@ function relativeLuminance(bg: { r: number; g: number; b: number }): number {
41
54
  );
42
55
  }
43
56
 
44
- export function readableTextColorOn(bg: { r: number; g: number; b: number }): string {
57
+ function nearlyEqual(a: Color, b: Color): boolean {
58
+ return (
59
+ Math.abs(a.r - b.r) < MIN_CHANNEL_DISTANCE &&
60
+ Math.abs(a.g - b.g) < MIN_CHANNEL_DISTANCE &&
61
+ Math.abs(a.b - b.b) < MIN_CHANNEL_DISTANCE
62
+ );
63
+ }
64
+
65
+ /**
66
+ * Pure black/white pick by accent luminance. Used as the badge fallback and kept
67
+ * exported for callers that only have the accent.
68
+ */
69
+ export function readableTextColorOn(bg: Color): string {
45
70
  return relativeLuminance(bg) < LIGHT_ACCENT_LUMINANCE ? "#ffffff" : "#000000";
46
71
  }
72
+
73
+ /**
74
+ * Badge label color on the accent: the theme background (AFT parity) when it is
75
+ * usable, else a guaranteed-visible black/white fallback. Returns the passed-in
76
+ * `background` reference unchanged on the primary path.
77
+ */
78
+ export function badgeTextColor<T extends Color>(accent: T, background: T): T | string {
79
+ const alpha = background.a ?? 1;
80
+ if (alpha >= MIN_OPAQUE_ALPHA && !nearlyEqual(accent, background)) {
81
+ return background;
82
+ }
83
+ return readableTextColorOn(accent);
84
+ }
@@ -2,7 +2,7 @@
2
2
  import { Show, createEffect, createMemo, createSignal, on, onCleanup } from "solid-js"
3
3
  import type { TuiSlotPlugin, TuiPluginApi, TuiThemeCurrent } from "@opencode-ai/plugin/tui"
4
4
  import packageJson from "../../../package.json"
5
- import { readableTextColorOn } from '../badge-contrast';
5
+ import { badgeTextColor } from '../badge-contrast';
6
6
  import { loadSidebarSnapshot, type SidebarSnapshot } from "../data/context-db"
7
7
  import { formatThresholdPercent } from "../../shared/format-threshold"
8
8
  import {
@@ -695,7 +695,7 @@ const SidebarContent = (props: {
695
695
  onMouseDown={() => props.controller.toggleCollapsed()}
696
696
  >
697
697
  <box paddingLeft={1} paddingRight={1} backgroundColor={props.theme.accent}>
698
- <text fg={readableTextColorOn(props.theme.accent)}>
698
+ <text fg={badgeTextColor(props.theme.accent, props.theme.background)}>
699
699
  <b>{collapsed() ? "▶ " : "▼ "}{headerLabel()}</b>
700
700
  </text>
701
701
  </box>