@standardagents/code 0.5.0 → 0.5.1

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
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import os6 from 'os';
2
+ import os6, { homedir } from 'os';
3
3
  import fs4 from 'fs';
4
4
  import path3 from 'path';
5
5
  import readline2 from 'readline/promises';
@@ -2105,6 +2105,19 @@ var SUBAGENT_COLORS = [
2105
2105
  ];
2106
2106
  var COMPACTION_COLOR = "\x1B[38;5;208m";
2107
2107
  var COMPACTION_AGENT = "compaction_agent";
2108
+ function isWideCodePoint(cp) {
2109
+ return cp >= 4352 && cp <= 4447 || // Hangul Jamo
2110
+ cp >= 11904 && cp <= 42191 || // CJK radicals … Yi
2111
+ cp >= 44032 && cp <= 55203 || // Hangul syllables
2112
+ cp >= 63744 && cp <= 64255 || // CJK compatibility ideographs
2113
+ cp >= 65072 && cp <= 65103 || // CJK compatibility forms
2114
+ cp >= 65280 && cp <= 65376 || // fullwidth forms
2115
+ cp >= 65504 && cp <= 65510 || cp >= 127744 && cp <= 129791 || // emoji
2116
+ cp >= 131072;
2117
+ }
2118
+ function sanitizeHudRow(s) {
2119
+ return s.replace(/\x1b(?!\[)/g, "").replace(/[\x00-\x1a\x1c-\x1f\x7f]/g, " ");
2120
+ }
2108
2121
  var Tui = class _Tui {
2109
2122
  constructor(level = 1) {
2110
2123
  this.level = level;
@@ -2741,7 +2754,50 @@ var Tui = class _Tui {
2741
2754
  return `${q}${bg}${this.levelColor()}\u276F${C.reset} `;
2742
2755
  }
2743
2756
  visibleWidth(s) {
2744
- return s.replace(/\x1b\[[0-9;]*m/g, "").length;
2757
+ let w = 0;
2758
+ for (const ch of s.replace(/\x1b\[[0-9;]*m/g, "")) {
2759
+ w += isWideCodePoint(ch.codePointAt(0)) ? 2 : 1;
2760
+ }
2761
+ return w;
2762
+ }
2763
+ /**
2764
+ * Truncate a styled string to at most `max` VISIBLE columns, copying ANSI
2765
+ * escape sequences through without counting them and appending a reset if it
2766
+ * cut mid-style. The redraw's whole height math assumes every HUD row is
2767
+ * exactly ONE physical terminal row — true only while each row's visible
2768
+ * width is strictly less than `cols`. A row of width == cols hits the
2769
+ * terminal's auto-wrap boundary: many terminals push the cursor to the next
2770
+ * line, so the following CR/LF yields an EXTRA physical row we didn't count,
2771
+ * and the region marches down the screen leaving a trail on every tick. Every
2772
+ * HUD row is clamped through here to keep that invariant no matter what any
2773
+ * individual line builder produces.
2774
+ */
2775
+ clampVisible(s, max) {
2776
+ let out = "";
2777
+ let width = 0;
2778
+ let i = 0;
2779
+ let truncated = false;
2780
+ while (i < s.length) {
2781
+ if (s[i] === "\x1B" && s[i + 1] === "[") {
2782
+ let j = i + 2;
2783
+ while (j < s.length && !/[a-zA-Z]/.test(s[j])) j++;
2784
+ out += s.slice(i, j + 1);
2785
+ i = j + 1;
2786
+ continue;
2787
+ }
2788
+ const cp = s.codePointAt(i);
2789
+ const chLen = cp > 65535 ? 2 : 1;
2790
+ const chWidth = isWideCodePoint(cp) ? 2 : 1;
2791
+ if (width + chWidth > max) {
2792
+ truncated = true;
2793
+ break;
2794
+ }
2795
+ out += s.slice(i, i + chLen);
2796
+ width += chWidth;
2797
+ i += chLen;
2798
+ }
2799
+ if (truncated) out += C.reset;
2800
+ return out;
2745
2801
  }
2746
2802
  /**
2747
2803
  * The slash-palette block rendered BELOW the input. While the palette is open
@@ -2754,7 +2810,7 @@ var Tui = class _Tui {
2754
2810
  */
2755
2811
  paletteBlockLines(cols2) {
2756
2812
  if (!this.paletteOpen()) return [];
2757
- const rows = this.paletteLines(cols2);
2813
+ const rows = this.paletteLines(cols2).map((r) => this.clampVisible(r, Math.max(1, cols2 - 1)));
2758
2814
  const reserved = Math.max(this.commands.length, rows.length);
2759
2815
  while (rows.length < reserved) rows.push("");
2760
2816
  return rows;
@@ -2823,11 +2879,13 @@ var Tui = class _Tui {
2823
2879
  if (!this.started || this.takeoverHandler) return;
2824
2880
  const cols2 = process.stdout.columns || 80;
2825
2881
  this.moveToRegionTop();
2826
- process.stdout.write("\x1B[J");
2827
2882
  const hudWidths = [];
2883
+ const hudRows = [];
2884
+ const rowCap = Math.max(1, cols2 - 1);
2828
2885
  const writeHudRow = (line) => {
2829
- hudWidths.push(this.visibleWidth(line));
2830
- process.stdout.write(line + "\r\n");
2886
+ const row = this.clampVisible(sanitizeHudRow(line), rowCap);
2887
+ hudWidths.push(this.visibleWidth(row));
2888
+ hudRows.push(row);
2831
2889
  };
2832
2890
  const previewLines = this.streamPreviewLines(cols2);
2833
2891
  for (const line of previewLines) writeHudRow(line);
@@ -2858,8 +2916,8 @@ var Tui = class _Tui {
2858
2916
  const prefix = this.promptPrefix();
2859
2917
  const pw = this.visibleWidth(prefix);
2860
2918
  const lines = this.inputBuffer.split("\n");
2861
- process.stdout.write(prefix + lines[0]);
2862
- for (let i = 1; i < lines.length; i++) process.stdout.write("\r\n" + lines[i]);
2919
+ const tail = [prefix + lines[0]];
2920
+ for (let i = 1; i < lines.length; i++) tail.push("\r\n" + lines[i]);
2863
2921
  const rowsOf = (len, lead) => Math.max(1, Math.ceil((lead + len) / cols2));
2864
2922
  const lineRows = lines.map((l, i) => rowsOf(l.length, i === 0 ? pw : 0));
2865
2923
  const inputRows = lineRows.reduce((a, b) => a + b, 0);
@@ -2874,18 +2932,18 @@ var Tui = class _Tui {
2874
2932
  for (let i = 0; i < caretLine; i++) caretRow += lineRows[i];
2875
2933
  const caretCol = caretCell % cols2;
2876
2934
  const paletteBlock = this.paletteBlockLines(cols2);
2877
- for (const line of paletteBlock) process.stdout.write("\r\n" + line);
2935
+ for (const line of paletteBlock) tail.push("\r\n" + line);
2878
2936
  if (paletteBlock.length > 0) {
2879
- process.stdout.write("\r");
2937
+ tail.push("\r");
2880
2938
  const up = inputRows - 1 + paletteBlock.length - caretRow;
2881
- if (up > 0) process.stdout.write(`\x1B[${up}A`);
2882
- if (caretCol > 0) process.stdout.write(`\x1B[${caretCol}C`);
2939
+ if (up > 0) tail.push(`\x1B[${up}A`);
2940
+ if (caretCol > 0) tail.push(`\x1B[${caretCol}C`);
2883
2941
  this.lastCursorRow = aboveRows + caretRow;
2884
2942
  } else if (this.cursorPos < this.inputBuffer.length) {
2885
- process.stdout.write("\r");
2943
+ tail.push("\r");
2886
2944
  const up = inputRows - 1 - caretRow;
2887
- if (up > 0) process.stdout.write(`\x1B[${up}A`);
2888
- if (caretCol > 0) process.stdout.write(`\x1B[${caretCol}C`);
2945
+ if (up > 0) tail.push(`\x1B[${up}A`);
2946
+ if (caretCol > 0) tail.push(`\x1B[${caretCol}C`);
2889
2947
  this.lastCursorRow = aboveRows + caretRow;
2890
2948
  } else {
2891
2949
  this.lastCursorRow = aboveRows + (inputRows - 1);
@@ -2893,6 +2951,8 @@ var Tui = class _Tui {
2893
2951
  this.drawnHudWidths = hudWidths;
2894
2952
  this.lastDrawnCols = cols2;
2895
2953
  this.bottomDrawn = true;
2954
+ const out = "\x1B[J" + hudRows.join("\r\n") + "\r\n" + tail.join("");
2955
+ process.stdout.write(out);
2896
2956
  }
2897
2957
  clearBottom() {
2898
2958
  if (!this.bottomDrawn) return;
@@ -3206,7 +3266,7 @@ var Tui = class _Tui {
3206
3266
  * resets whenever the label changes.
3207
3267
  */
3208
3268
  setStep(label, outTokens) {
3209
- const next = label && label.trim() ? label.trim() : null;
3269
+ const next = label && label.trim() ? label.replace(/\s+/g, " ").trim() : null;
3210
3270
  if (next !== this.step) {
3211
3271
  this.step = next;
3212
3272
  this.stepStart = Date.now();
@@ -4032,6 +4092,107 @@ function saveDefaultEndpoint(endpoint) {
4032
4092
  } catch {
4033
4093
  }
4034
4094
  }
4095
+ var PKG_NAME = "@standardagents/code";
4096
+ var REGISTRY_URL = `https://registry.npmjs.org/${encodeURIComponent(PKG_NAME)}`;
4097
+ var CACHE_REL_DIR = ".config/standardagents-cli";
4098
+ var CACHE_FILE = "update-check.json";
4099
+ var CACHE_TTL_MS = 1e3 * 60 * 60 * 24;
4100
+ var CHECK_TIMEOUT_MS = 4e3;
4101
+ function cacheDir() {
4102
+ return path3.join(homedir(), CACHE_REL_DIR);
4103
+ }
4104
+ function cachePath() {
4105
+ return path3.join(cacheDir(), CACHE_FILE);
4106
+ }
4107
+ function readCache() {
4108
+ try {
4109
+ const raw = fs4.readFileSync(cachePath(), "utf-8");
4110
+ return JSON.parse(raw);
4111
+ } catch {
4112
+ return null;
4113
+ }
4114
+ }
4115
+ function writeCache(latest) {
4116
+ try {
4117
+ const dir = cacheDir();
4118
+ if (!fs4.existsSync(dir)) fs4.mkdirSync(dir, { recursive: true });
4119
+ fs4.writeFileSync(cachePath(), JSON.stringify({ latest, timestamp: Date.now() }));
4120
+ } catch {
4121
+ }
4122
+ }
4123
+ async function checkForUpdate(currentVersion) {
4124
+ if (!currentVersion) return null;
4125
+ if (process.env.NO_UPDATE_NOTIFIER || process.env.CI) return null;
4126
+ const cached = readCache();
4127
+ if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS) {
4128
+ if (cached.latest !== currentVersion) {
4129
+ return { current: currentVersion, latest: cached.latest };
4130
+ }
4131
+ return null;
4132
+ }
4133
+ const controller = new AbortController();
4134
+ const timeout = setTimeout(() => controller.abort(), CHECK_TIMEOUT_MS);
4135
+ try {
4136
+ const res = await fetch(REGISTRY_URL, {
4137
+ signal: controller.signal,
4138
+ headers: {
4139
+ Accept: "application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*",
4140
+ "User-Agent": `${PKG_NAME}/${currentVersion}`
4141
+ }
4142
+ });
4143
+ if (!res.ok) return null;
4144
+ const data = await res.json();
4145
+ const latest = data["dist-tags"]?.latest;
4146
+ if (!latest) return null;
4147
+ writeCache(latest);
4148
+ if (latest !== currentVersion) {
4149
+ return { current: currentVersion, latest };
4150
+ }
4151
+ return null;
4152
+ } catch {
4153
+ return null;
4154
+ } finally {
4155
+ clearTimeout(timeout);
4156
+ }
4157
+ }
4158
+ async function forceCheckForUpdate(currentVersion) {
4159
+ if (!currentVersion) return null;
4160
+ if (process.env.NO_UPDATE_NOTIFIER || process.env.CI) return null;
4161
+ const controller = new AbortController();
4162
+ const timeout = setTimeout(() => controller.abort(), CHECK_TIMEOUT_MS);
4163
+ try {
4164
+ const res = await fetch(REGISTRY_URL, {
4165
+ signal: controller.signal,
4166
+ headers: {
4167
+ Accept: "application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*",
4168
+ "User-Agent": `${PKG_NAME}/${currentVersion}`
4169
+ }
4170
+ });
4171
+ if (!res.ok) return null;
4172
+ const data = await res.json();
4173
+ const latest = data["dist-tags"]?.latest;
4174
+ if (!latest) return null;
4175
+ writeCache(latest);
4176
+ if (latest !== currentVersion) {
4177
+ return { current: currentVersion, latest };
4178
+ }
4179
+ return null;
4180
+ } catch {
4181
+ return null;
4182
+ } finally {
4183
+ clearTimeout(timeout);
4184
+ }
4185
+ }
4186
+ function runNpmUpdate() {
4187
+ return new Promise((resolve) => {
4188
+ const child = spawn("npm", ["i", "-g", `${PKG_NAME}@latest`], {
4189
+ stdio: "inherit",
4190
+ shell: true
4191
+ });
4192
+ child.on("close", (code) => resolve(code === 0));
4193
+ child.on("error", () => resolve(false));
4194
+ });
4195
+ }
4035
4196
 
4036
4197
  // src/index.ts
4037
4198
  var AGENT_ID = "standard_code_agent";
@@ -4316,6 +4477,21 @@ ${c.dim}Press Control-C again to exit${c.reset}
4316
4477
 
4317
4478
  `);
4318
4479
  }
4480
+ const version = readVersion();
4481
+ let updateAvailable = null;
4482
+ {
4483
+ const loading = startLoader("Checking for updates");
4484
+ updateAvailable = await checkForUpdate(version);
4485
+ loading.stop();
4486
+ if (updateAvailable) {
4487
+ stdout.write(
4488
+ ` ${c.teal}\u25C7${c.reset} ${c.dim}Update available:${c.reset} ${c.dim}v${updateAvailable.current}${c.reset} \u2192 ${c.bold}v${updateAvailable.latest}${c.reset}
4489
+ ${c.dim}Run ${c.reset}${c.bold}npm i -g @standardagents/code@latest${c.reset}${c.dim} to update${c.reset}
4490
+
4491
+ `
4492
+ );
4493
+ }
4494
+ }
4319
4495
  const stored = getCredential(endpoint);
4320
4496
  let api = stored ? new ApiClient(endpoint, stored.access_token) : null;
4321
4497
  let storedCheck = null;
@@ -4675,7 +4851,8 @@ ${c.bold}why: ${req.requestPermission}${c.reset}` : ""}`,
4675
4851
  for (const s of subs) {
4676
4852
  const status = (s.status || "").trim();
4677
4853
  if (status === "idle" || status === "terminated") continue;
4678
- const detail = status && status !== "running" ? ` \u2014 ${status.slice(0, 80)}` : "";
4854
+ const oneLineStatus = status.replace(/\s+/g, " ");
4855
+ const detail = oneLineStatus && oneLineStatus !== "running" ? ` \u2014 ${oneLineStatus.slice(0, 80)}` : "";
4679
4856
  activeSubagents.set(s.id, {
4680
4857
  label: `${subagentLabel(s, agentTitles)}${detail}`,
4681
4858
  agentName: s.agent_name ?? void 0
@@ -4871,6 +5048,7 @@ ${c.bold}why: ${req.requestPermission}${c.reset}` : ""}`,
4871
5048
  },
4872
5049
  { name: "background", label: "Background processes", hint: "list / stop", run: () => runProcessMenu(tui, bgMgr) },
4873
5050
  { name: "keybindings", label: "Keyboard shortcuts", run: () => showKeybindings(tui) },
5051
+ { name: "update", label: "Check for updates", hint: "check npm for a newer version", run: () => runUpdateCommand(tui) },
4874
5052
  { name: "logout", label: "Sign out", hint: "delete the saved token & quit", run: () => logout() },
4875
5053
  { name: "quit", label: "Quit", run: () => quit() }
4876
5054
  ]);
@@ -5127,6 +5305,36 @@ function showKeybindings(tui) {
5127
5305
  tui.print(`${c.gray} \u2190${c.reset} from the start of the input: select the [\u2699 n bg] badge (enter opens it)`);
5128
5306
  tui.print(`${c.gray} ctrl-c${c.reset} quit`);
5129
5307
  }
5308
+ async function runUpdateCommand(tui) {
5309
+ const version = readVersion();
5310
+ const result = await forceCheckForUpdate(version);
5311
+ if (!result) {
5312
+ tui.print(`${c.green}\u2713${c.reset} ${c.gray}@standardagents/code${c.reset} is up to date (v${version})`);
5313
+ return;
5314
+ }
5315
+ const { latest } = result;
5316
+ tui.print(`
5317
+ ${c.yellow}\u27F3${c.reset} Update available: ${c.gray}v${version}${c.reset} \u2192 ${c.green}v${latest}${c.reset}`);
5318
+ const choice = await tui.select(`Update now with \`npm i -g @standardagents/code@latest\`?`, [
5319
+ { label: "Yes, update now", value: "yes" },
5320
+ { label: "No, skip", value: "no" }
5321
+ ]);
5322
+ if (choice === "yes") {
5323
+ tui.print(` ${c.gray}Running npm i -g @standardagents/code@latest\u2026${c.reset}`);
5324
+ try {
5325
+ const ok = await runNpmUpdate();
5326
+ if (ok) {
5327
+ tui.print(` ${c.green}\u2713${c.reset} Updated to v${latest}. Restart to use the new version.`);
5328
+ } else {
5329
+ tui.print(` ${c.red}\u2717${c.reset} Update failed.`);
5330
+ }
5331
+ } catch (e) {
5332
+ tui.print(` ${c.red}\u2717${c.reset} Update failed: ${e}`);
5333
+ }
5334
+ } else {
5335
+ tui.print(` ${c.gray}Skipped. Run /update later.${c.reset}`);
5336
+ }
5337
+ }
5130
5338
  async function runProcessMenu(tui, bg) {
5131
5339
  const procs = await bg.list();
5132
5340
  if (!procs.length) {