kandown 0.15.2 → 0.15.4

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/bin/kandown.js CHANGED
@@ -199,15 +199,14 @@ async function checkForUpdate(argv = process.argv) {
199
199
 
200
200
  if (!latest || semverGt(current, latest) >= 0) return; // up to date or offline
201
201
 
202
- log('');
203
- log(`${c.yellow}⚡ Update available:${c.reset} kandown ${c.dim}${current}${c.reset} → ${c.green}${latest}${c.reset}`);
204
- info('Auto-updating…');
202
+ tuiDone('', `Update available: ${c.dim}kandown ${current}${c.reset} → ${c.green}${latest}${c.reset}`);
205
203
 
206
204
  // 📖 Step 2: Create lock file to prevent concurrent updates.
207
205
  try { writeFileSync(lockFile, `${process.pid}\n${now}`, 'utf8'); } catch { /* ignore */ }
208
206
 
209
- // 📖 Step 3: Run the update via npm or pnpm.
210
- // Try npm first, fall back to pnpm.
207
+ // 📖 Step 3: Run the update via npm or pnpm with animated progress.
208
+ tuiProgress(`Updating to ${latest}…`, 25);
209
+
211
210
  const updateOk = await new Promise((resolve) => {
212
211
  const tryInstall = (cmd) => {
213
212
  return new Promise((res) => {
@@ -233,7 +232,7 @@ async function checkForUpdate(argv = process.argv) {
233
232
  try { if (existsSync(lockFile)) unlinkSync(lockFile); } catch { /* ignore */ }
234
233
 
235
234
  if (!updateOk) {
236
- warn('Auto-update failed — continuing with current version');
235
+ tuiDone('✗', `${c.yellow}Auto-update failed${c.reset} — continuing with current version`);
237
236
  log(` Run ${c.cyan}npm install -g kandown${c.reset} to upgrade manually`);
238
237
  log('');
239
238
  return;
@@ -257,14 +256,13 @@ async function checkForUpdate(argv = process.argv) {
257
256
  });
258
257
 
259
258
  if (!postVersion || semverGt(postVersion, latest) < 0) {
260
- // Install claimed success but version didn't change probably a permissions issue.
261
- warn('Update did not apply — continuing with current version');
259
+ tuiDone('✗', `${c.yellow}Update did not apply${c.reset}continuing with current version`);
262
260
  log(` Run ${c.cyan}npm install -g kandown${c.reset} to upgrade manually`);
263
261
  log('');
264
262
  return;
265
263
  }
266
264
 
267
- success(`Updated to v${postVersion} — restarting…`);
265
+ tuiDone('✓', `${c.green}Updated to v${postVersion}${c.reset} — restarting…`);
268
266
  log('');
269
267
 
270
268
  // 📖 Step 5: Respawn with the new version.
@@ -309,6 +307,64 @@ const info = (msg) => log(`${c.cyan}→${c.reset} ${msg}`);
309
307
  const warn = (msg) => log(`${c.yellow}⚠${c.reset} ${msg}`);
310
308
  const err = (msg) => log(`${c.red}✗${c.reset} ${msg}`);
311
309
 
310
+ // ─── TUI spinner & progress bar ─────────────────────────────────────────────
311
+ // 📖 Lightweight terminal animation helpers for the auto-updater.
312
+ // Uses ANSI escape codes (carriage return + clear line) to redraw in-place.
313
+ // Only animate when stdout is a TTY; otherwise fall back to plain text.
314
+
315
+ const _SP_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
316
+ let _spTimer = null;
317
+ let _spIdx = 0;
318
+ const _isTTY = process.stdout.isTTY;
319
+
320
+ function _tuiClear() {
321
+ process.stdout.write('\r\x1b[K');
322
+ }
323
+
324
+ /** Start an animated spinner with the given text. */
325
+ function tuiSpinner(text) {
326
+ if (_spTimer) clearInterval(_spTimer);
327
+ _spIdx = 0;
328
+ _tuiClear();
329
+ if (!_isTTY) { process.stdout.write(` ${text}\n`); return; }
330
+ _spTimer = setInterval(() => {
331
+ process.stdout.write(`\r\x1b[K${_SP_FRAMES[_spIdx % _SP_FRAMES.length]} ${text}`);
332
+ _spIdx++;
333
+ }, 100);
334
+ }
335
+
336
+ /**
337
+ * 📖 Start a time-estimated progress bar + spinner.
338
+ * Shows a filling bar with percentage based on elapsed time.
339
+ * Caps at 95% until the caller calls tuiDone().
340
+ */
341
+ function tuiProgress(text, estimateSec = 25, barWidth = 15) {
342
+ if (_spTimer) clearInterval(_spTimer);
343
+ _spIdx = 0;
344
+ _tuiClear();
345
+ if (!_isTTY) { process.stdout.write(` ${text}\n`); return; }
346
+ const start = Date.now();
347
+ _spTimer = setInterval(() => {
348
+ const elapsed = (Date.now() - start) / 1000;
349
+ const pct = Math.min(Math.round((elapsed / estimateSec) * 100), 95);
350
+ const filled = Math.round(barWidth * pct / 100);
351
+ const bar = '\u2588'.repeat(filled) + '\u2591'.repeat(barWidth - filled);
352
+ process.stdout.write(`\r\x1b[K${_SP_FRAMES[_spIdx % _SP_FRAMES.length]} ${text} ${bar} ${pct}%`);
353
+ _spIdx++;
354
+ }, 120);
355
+ }
356
+
357
+ /** Stop the current animation and print a final status line. */
358
+ function tuiDone(symbol, text) {
359
+ if (_spTimer) { clearInterval(_spTimer); _spTimer = null; }
360
+ if (_isTTY) {
361
+ _tuiClear();
362
+ process.stdout.write(`${symbol} ${text}\n`);
363
+ } else {
364
+ log(`${symbol} ${text}`);
365
+ }
366
+ }
367
+
312
368
  function help() {
313
369
  const v = getCurrentVersion() ?? '?';
314
370
  log(`
package/bin/tui.js CHANGED
@@ -54651,6 +54651,7 @@ function moveTaskToColumn(kandownDir, taskId, targetColumn) {
54651
54651
  import { existsSync as existsSync4, readFileSync as readFileSync4, unlinkSync } from "fs";
54652
54652
  import { dirname as dirname2, join as join3 } from "path";
54653
54653
  import { spawn } from "child_process";
54654
+ import { createConnection } from "net";
54654
54655
  function metadataPath(kandownDir) {
54655
54656
  return join3(kandownDir, "daemon.json");
54656
54657
  }
@@ -54708,6 +54709,20 @@ async function fetchDaemonInfo(port) {
54708
54709
  return null;
54709
54710
  }
54710
54711
  }
54712
+ function isPortListening(port, timeoutMs = 400) {
54713
+ return new Promise((resolve3) => {
54714
+ const socket = createConnection({ port, host: "127.0.0.1" }, () => {
54715
+ socket.destroy();
54716
+ resolve3(true);
54717
+ });
54718
+ socket.on("error", () => resolve3(false));
54719
+ socket.setTimeout(timeoutMs);
54720
+ socket.on("timeout", () => {
54721
+ socket.destroy();
54722
+ resolve3(false);
54723
+ });
54724
+ });
54725
+ }
54711
54726
  async function getDaemonStatus(kandownDir) {
54712
54727
  const metadata = readDaemonMetadata(kandownDir);
54713
54728
  if (!metadata) return { running: false, metadata: null };
@@ -54716,18 +54731,23 @@ async function getDaemonStatus(kandownDir) {
54716
54731
  return { running: false, metadata: null };
54717
54732
  }
54718
54733
  const remote = await fetchDaemonInfo(metadata.port);
54719
- if (!remote || remote.pid !== metadata.pid || remote.kandownDir !== kandownDir) {
54734
+ if (!remote) {
54735
+ return { running: false, metadata: null };
54736
+ }
54737
+ if (remote.pid !== metadata.pid || remote.kandownDir !== kandownDir) {
54720
54738
  removeDaemonMetadata(kandownDir);
54721
54739
  return { running: false, metadata: null };
54722
54740
  }
54723
54741
  return { running: true, metadata };
54724
54742
  }
54725
- async function waitForDaemon(kandownDir, timeoutMs = 5e3) {
54743
+ async function waitForDaemon(kandownDir, timeoutMs = 8e3) {
54726
54744
  const started = Date.now();
54727
54745
  while (Date.now() - started < timeoutMs) {
54728
- const status = await getDaemonStatus(kandownDir);
54729
- if (status.running) return status;
54730
- await new Promise((resolve3) => setTimeout(resolve3, 150));
54746
+ const metadata = readDaemonMetadata(kandownDir);
54747
+ if (metadata && isProcessAlive(metadata.pid) && await isPortListening(metadata.port)) {
54748
+ return { running: true, metadata };
54749
+ }
54750
+ await new Promise((resolve3) => setTimeout(resolve3, 120));
54731
54751
  }
54732
54752
  return { running: false, metadata: null };
54733
54753
  }
@@ -57082,8 +57102,15 @@ var MENU_HEIGHT = 2;
57082
57102
  var import_jsx_runtime4 = __toESM(require_jsx_runtime(), 1);
57083
57103
  var TASKS_START_Y = 5;
57084
57104
  function truncate(str, maxLen) {
57105
+ if (maxLen <= 0) return "";
57085
57106
  if (str.length <= maxLen) return str;
57086
- return str.slice(0, maxLen - 1) + "\u2026";
57107
+ return str.slice(0, Math.max(0, maxLen - 1)) + "\u2026";
57108
+ }
57109
+ function terminalHyperlink(label, url) {
57110
+ return `\x1B]8;;${url}\x07${label}\x1B]8;;\x07`;
57111
+ }
57112
+ function webLinkLabel(url) {
57113
+ return `\u2197 ${url.replace(/^https?:\/\//, "")}`;
57087
57114
  }
57088
57115
  function pad(str, len) {
57089
57116
  const t = truncate(str, len);
@@ -57258,11 +57285,15 @@ function BoardHeader({ title, inTmux, modeHint, version, daemonStatus, daemonBus
57258
57285
  const rightWidth = Math.max(0, width - leftWidth - daemonWidth);
57259
57286
  const left = pad(` \u25C6 KANDOWN${tmuxHint}${versionTag} ${title}`, leftWidth);
57260
57287
  const daemon = pad(daemonLabel, daemonWidth);
57261
- const right = truncate(hint, rightWidth).padStart(rightWidth, " ");
57288
+ const webUrl = daemonStatus.running ? daemonStatus.metadata?.url : null;
57289
+ const rightPlain = webUrl ? truncate(webLinkLabel(webUrl), rightWidth) : truncate(hint, rightWidth);
57290
+ const right = rightPlain.padStart(rightWidth, " ");
57291
+ const rightPadding = right.slice(0, Math.max(0, right.length - rightPlain.length));
57292
+ const rightContent = webUrl ? `${rightPadding}${terminalHyperlink(rightPlain, webUrl)}` : right;
57262
57293
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(Box_default, { marginBottom: 1, children: [
57263
57294
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Text, { bold: true, color: "cyan", children: left }),
57264
57295
  daemonWidth > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Text, { color: daemonStatus.running ? "green" : "yellow", bold: true, children: daemon }),
57265
- rightWidth > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Text, { color: "gray", dimColor: true, children: right })
57296
+ rightWidth > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Text, { color: webUrl ? "blue" : "gray", dimColor: !webUrl, underline: !!webUrl, children: rightContent })
57266
57297
  ] });
57267
57298
  }
57268
57299
  function StatusBar({ message, task, daemonStatus }) {
@@ -58006,7 +58037,7 @@ function Board({ kandownDir, version }) {
58006
58037
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(Box_default, { flexDirection: "column", children: [
58007
58038
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(Box_default, { marginBottom: 1, justifyContent: "space-between", children: [
58008
58039
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Text, { color: "gray", children: "Esc back \xB7 a agent \xB7 j/k scroll" }),
58009
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(Text, { color: "gray", dimColor: true, children: [
58040
+ daemonStatus.running && daemonStatus.metadata ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Text, { color: "blue", underline: true, children: terminalHyperlink(webLinkLabel(daemonStatus.metadata.url), daemonStatus.metadata.url) }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(Text, { color: "gray", dimColor: true, children: [
58010
58041
  "KANDOWN ",
58011
58042
  board.title
58012
58043
  ] })