@vendian/cli 0.0.28 → 0.0.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/cli-wrapper.mjs +210 -28
  2. package/package.json +1 -1
package/cli-wrapper.mjs CHANGED
@@ -1107,7 +1107,7 @@ import path8 from "node:path";
1107
1107
  import readlinePromises from "node:readline/promises";
1108
1108
 
1109
1109
  // src/version.js
1110
- var CLI_VERSION = true ? "0.0.28" : process.env.npm_package_version || "0.0.0-dev";
1110
+ var CLI_VERSION = true ? "0.0.29" : process.env.npm_package_version || "0.0.0-dev";
1111
1111
 
1112
1112
  // src/npm-update.js
1113
1113
  var NPM_CHECK_INTERVAL_MS = 30 * 60 * 1e3;
@@ -2399,6 +2399,87 @@ async function loadTerm() {
2399
2399
  }
2400
2400
  var HEADER_ROWS_BASE = 7;
2401
2401
  var SERVE_CONTENT_ROW = 10;
2402
+ function clampListIndex(index, count) {
2403
+ if (!Number.isFinite(count) || count <= 0) return 0;
2404
+ if (!Number.isFinite(index)) return 0;
2405
+ return Math.max(0, Math.min(count - 1, index));
2406
+ }
2407
+ function centeredWindowStart(selectedIdx, visibleCount, totalCount) {
2408
+ if (visibleCount >= totalCount) return 0;
2409
+ const half = Math.floor(visibleCount / 2);
2410
+ const maxStart = Math.max(0, totalCount - visibleCount);
2411
+ return Math.max(0, Math.min(maxStart, selectedIdx - half));
2412
+ }
2413
+ function computeServeDashboardViewport({
2414
+ termHeight = 24,
2415
+ agentCount = 0,
2416
+ selectedIdx = 0,
2417
+ activityCount = 0,
2418
+ diagnosticsCount = 0
2419
+ } = {}) {
2420
+ const availableRows = Math.max(4, (termHeight || 24) - SERVE_CONTENT_ROW + 1);
2421
+ const clampedSelectedIdx = clampListIndex(selectedIdx, agentCount);
2422
+ const viewport = {
2423
+ selectedIdx: clampedSelectedIdx,
2424
+ showBlankAfterStatus: false,
2425
+ showAgentHeader: agentCount > 0,
2426
+ visibleAgentCount: 0,
2427
+ agentWindowStart: 0,
2428
+ showAgentHint: false,
2429
+ showAgentRange: false,
2430
+ visibleActivityCount: 0,
2431
+ showActivityHeader: false,
2432
+ visibleDiagnosticsCount: 0,
2433
+ showDiagnosticsHeader: false,
2434
+ showInlineErrors: (termHeight || 24) >= 24
2435
+ };
2436
+ let remaining = availableRows;
2437
+ remaining -= 1;
2438
+ remaining -= 1;
2439
+ if (agentCount > 0) {
2440
+ remaining -= 1;
2441
+ viewport.visibleAgentCount = 1;
2442
+ remaining -= 1;
2443
+ } else {
2444
+ remaining -= 1;
2445
+ }
2446
+ if (remaining > 0) {
2447
+ viewport.showBlankAfterStatus = true;
2448
+ remaining -= 1;
2449
+ }
2450
+ if (agentCount > 1 && remaining > 0) {
2451
+ const extraAgentRows = Math.min(agentCount - 1, remaining);
2452
+ viewport.visibleAgentCount += extraAgentRows;
2453
+ remaining -= extraAgentRows;
2454
+ }
2455
+ if (agentCount > 0) {
2456
+ viewport.agentWindowStart = centeredWindowStart(
2457
+ clampedSelectedIdx,
2458
+ viewport.visibleAgentCount,
2459
+ agentCount
2460
+ );
2461
+ viewport.showAgentHint = remaining > 0;
2462
+ if (viewport.showAgentHint) remaining -= 1;
2463
+ viewport.showAgentRange = viewport.visibleAgentCount > 0 && viewport.visibleAgentCount < agentCount;
2464
+ }
2465
+ if (activityCount > 0 && remaining > 1) {
2466
+ viewport.showActivityHeader = true;
2467
+ remaining -= 1;
2468
+ viewport.visibleActivityCount = Math.min(activityCount, remaining);
2469
+ remaining -= viewport.visibleActivityCount;
2470
+ }
2471
+ if (diagnosticsCount > 0 && remaining > 1) {
2472
+ viewport.showDiagnosticsHeader = true;
2473
+ remaining -= 1;
2474
+ viewport.visibleDiagnosticsCount = Math.min(diagnosticsCount, remaining);
2475
+ }
2476
+ return viewport;
2477
+ }
2478
+ function stopSignalForAttempt(attempt = 0) {
2479
+ if (!Number.isFinite(attempt) || attempt <= 0) return "SIGINT";
2480
+ if (attempt === 1) return "SIGTERM";
2481
+ return "SIGKILL";
2482
+ }
2402
2483
  async function runTui({
2403
2484
  env = process.env,
2404
2485
  platform = process.platform,
@@ -2912,6 +2993,10 @@ async function runServeDashboard({ env, platform, agentsDir, collectionId }) {
2912
2993
  let dashboardClosed = false;
2913
2994
  let exitPromptActive = false;
2914
2995
  let selectedIdx = 0;
2996
+ let pressedAgentIdx = null;
2997
+ let stopRequested = false;
2998
+ let stopSignalAttempt = 0;
2999
+ let stopTimer = null;
2915
3000
  const agentRowMap = /* @__PURE__ */ new Map();
2916
3001
  function agentDisplayList() {
2917
3002
  return state.agents.map((agent, i) => {
@@ -2939,12 +3024,26 @@ async function runServeDashboard({ env, platform, agentsDir, collectionId }) {
2939
3024
  }
2940
3025
  function redraw() {
2941
3026
  if (dashboardClosed || overlayActive) return;
3027
+ const agents = agentDisplayList();
3028
+ const activity = recentActivityLines();
3029
+ const diagnostics = daemonDebugLines();
3030
+ const viewport = computeServeDashboardViewport({
3031
+ termHeight: term.height || 24,
3032
+ agentCount: agents.length,
3033
+ selectedIdx,
3034
+ activityCount: activity.length,
3035
+ diagnosticsCount: diagnostics.length
3036
+ });
3037
+ selectedIdx = viewport.selectedIdx;
2942
3038
  drawHeader({ env, platform, serveState: state });
2943
3039
  term.moveTo(1, SERVE_CONTENT_ROW);
2944
3040
  term.eraseDisplayBelow();
2945
3041
  agentRowMap.clear();
2946
3042
  if (startupError) {
2947
3043
  term.red(` ${fig.cross} ${clip(startupError, (term.width || 80) - 6)}
3044
+ `);
3045
+ } else if (stopRequested) {
3046
+ term.yellow(` ${fig.warning} ${clip(state.activity || "Stopping your agents\u2026", (term.width || 80) - 6)}
2948
3047
  `);
2949
3048
  } else if (state.stopped) {
2950
3049
  term.yellow(` ${fig.warning} Your agents have stopped.
@@ -2958,12 +3057,15 @@ async function runServeDashboard({ env, platform, agentsDir, collectionId }) {
2958
3057
  term.gray(`${clip(state.activity || "Starting up\u2026", 55)}
2959
3058
  `);
2960
3059
  }
2961
- const agents = agentDisplayList();
2962
3060
  if (agents.length > 0) {
2963
- term("\n");
3061
+ if (viewport.showBlankAfterStatus) term("\n");
2964
3062
  term.bold.gray(" Your agents:\n");
2965
- let lineOffset = SERVE_CONTENT_ROW + 3;
2966
- for (const ag of agents) {
3063
+ let lineOffset = SERVE_CONTENT_ROW + 2 + (viewport.showBlankAfterStatus ? 1 : 0);
3064
+ const visibleAgents = agents.slice(
3065
+ viewport.agentWindowStart,
3066
+ viewport.agentWindowStart + viewport.visibleAgentCount
3067
+ );
3068
+ for (const ag of visibleAgents) {
2967
3069
  const isSelected = ag.num - 1 === selectedIdx;
2968
3070
  agentRowMap.set(lineOffset, ag.num - 1);
2969
3071
  const numStr = String(ag.num).padStart(2);
@@ -3014,26 +3116,32 @@ async function runServeDashboard({ env, platform, agentsDir, collectionId }) {
3014
3116
  }
3015
3117
  term("\n");
3016
3118
  lineOffset++;
3017
- if (ag.errMsg) {
3119
+ if (viewport.showInlineErrors && isSelected && ag.errMsg) {
3018
3120
  term.red(` \u2514\u2500 ${clip(ag.errMsg, (term.width || 80) - 14)}
3019
3121
  `);
3020
3122
  lineOffset++;
3021
3123
  }
3022
3124
  }
3023
- term("\n");
3024
- term.gray(` Click an agent or use \u2191\u2193 + Enter to see its activity log
3125
+ if (viewport.showAgentRange) {
3126
+ const first = viewport.agentWindowStart + 1;
3127
+ const last = viewport.agentWindowStart + visibleAgents.length;
3128
+ term.gray(` Showing agents ${first}\u2013${last} of ${agents.length}
3025
3129
  `);
3130
+ }
3131
+ if (viewport.showAgentHint) {
3132
+ term.gray(` Click an agent or use \u2191\u2193 + Enter to see its activity log
3133
+ `);
3134
+ }
3026
3135
  } else {
3027
- term("\n");
3136
+ if (viewport.showBlankAfterStatus) term("\n");
3028
3137
  term.gray(" Looking for your agents\u2026\n");
3029
3138
  }
3030
- const activity = recentActivityLines();
3031
- if (activity.length > 0) {
3139
+ if (viewport.showActivityHeader) {
3140
+ const visibleActivity = activity.slice(-viewport.visibleActivityCount);
3032
3141
  const divW = Math.min((term.width || 80) - 4, 60);
3033
- term("\n");
3034
3142
  term.gray(` ${"\u2500".repeat(3)} Recent activity ${"\u2500".repeat(Math.max(0, divW - 18))}
3035
3143
  `);
3036
- for (const e of activity) {
3144
+ for (const e of visibleActivity) {
3037
3145
  const t = formatLogTime(e.timestamp);
3038
3146
  const name = clip(e._agentName || "Agent", 20);
3039
3147
  const msg = clip(friendlyActivityLine(e), (term.width || 80) - 30);
@@ -3055,13 +3163,12 @@ async function runServeDashboard({ env, platform, agentsDir, collectionId }) {
3055
3163
  }
3056
3164
  }
3057
3165
  }
3058
- const diagnostics = daemonDebugLines();
3059
- if (diagnostics.length > 0) {
3166
+ if (viewport.showDiagnosticsHeader) {
3167
+ const visibleDiagnostics = diagnostics.slice(-viewport.visibleDiagnosticsCount);
3060
3168
  const divW = Math.min((term.width || 80) - 4, 72);
3061
- term("\n");
3062
3169
  term.gray(` ${"\u2500".repeat(3)} Daemon diagnostics ${"\u2500".repeat(Math.max(0, divW - 23))}
3063
3170
  `);
3064
- for (const entry of diagnostics) {
3171
+ for (const entry of visibleDiagnostics) {
3065
3172
  const t = formatLogTime(entry.timestamp);
3066
3173
  const type = clip(entry.eventType || "event", 24).padEnd(24);
3067
3174
  const msg = clip(entry.message || "", (term.width || 80) - 38);
@@ -3081,6 +3188,10 @@ async function runServeDashboard({ env, platform, agentsDir, collectionId }) {
3081
3188
  term("\n");
3082
3189
  if (ctrlCArmed) {
3083
3190
  term.yellow.bold(" Press Ctrl+C again to exit.\n");
3191
+ } else if (stopRequested) {
3192
+ term.yellow(" Stopping your agents\u2026");
3193
+ if (stopSignalAttempt > 0) term.gray(" (retrying shutdown)");
3194
+ term("\n");
3084
3195
  } else {
3085
3196
  term.gray(" ");
3086
3197
  term.brightBlue.bold("S");
@@ -3137,9 +3248,11 @@ async function runServeDashboard({ env, platform, agentsDir, collectionId }) {
3137
3248
  }
3138
3249
  return new Promise((resolve) => {
3139
3250
  function openAgentLog(idx) {
3140
- if (dashboardClosed || exitPromptActive || idx < 0 || idx >= state.agents.length) return;
3251
+ const agents = agentDisplayList();
3252
+ const clampedIdx = clampListIndex(idx, agents.length);
3253
+ if (dashboardClosed || exitPromptActive || agents.length === 0 || idx !== clampedIdx) return;
3141
3254
  overlayActive = true;
3142
- const ag = agentDisplayList()[idx];
3255
+ const ag = agents[clampedIdx];
3143
3256
  showAgentLog({ agent: ag, state, env, platform }).then(() => {
3144
3257
  if (dashboardClosed) return;
3145
3258
  overlayActive = false;
@@ -3154,14 +3267,62 @@ async function runServeDashboard({ env, platform, agentsDir, collectionId }) {
3154
3267
  ctrlCArmTimer = null;
3155
3268
  }
3156
3269
  }
3270
+ function clearStopTimer() {
3271
+ if (stopTimer) {
3272
+ clearTimeout(stopTimer);
3273
+ stopTimer = null;
3274
+ }
3275
+ }
3276
+ function childIsRunning() {
3277
+ return Boolean(child) && child.exitCode == null && child.signalCode == null;
3278
+ }
3279
+ function scheduleStopEscalation() {
3280
+ clearStopTimer();
3281
+ if (!childIsRunning()) return;
3282
+ stopTimer = setTimeout(() => {
3283
+ if (!stopRequested || dashboardClosed || !childIsRunning()) return;
3284
+ stopSignalAttempt += 1;
3285
+ const signal = stopSignalForAttempt(stopSignalAttempt);
3286
+ state = {
3287
+ ...state,
3288
+ activity: signal === "SIGTERM" ? "Still stopping your agents\u2026" : "Force stopping your agents\u2026"
3289
+ };
3290
+ try {
3291
+ child.kill(signal);
3292
+ } catch {
3293
+ }
3294
+ if (!overlayActive) redraw();
3295
+ if (signal !== "SIGKILL") scheduleStopEscalation();
3296
+ }, 4e3);
3297
+ }
3298
+ function requestStop() {
3299
+ if (dashboardClosed) return;
3300
+ if (stopRequested) return;
3301
+ if (!childIsRunning()) {
3302
+ finish("home");
3303
+ return;
3304
+ }
3305
+ stopRequested = true;
3306
+ stopSignalAttempt = 0;
3307
+ state = { ...state, activity: "Stopping your agents\u2026" };
3308
+ clearCtrlCArm();
3309
+ try {
3310
+ child.kill(stopSignalForAttempt(stopSignalAttempt));
3311
+ } catch {
3312
+ }
3313
+ scheduleStopEscalation();
3314
+ if (!overlayActive) redraw();
3315
+ }
3157
3316
  function detachDashboardHandlers() {
3158
3317
  term.off("key", handleKey);
3159
3318
  term.off("mouse", handleMouse);
3319
+ term.off("resize", handleResize);
3160
3320
  }
3161
3321
  function finish(next) {
3162
3322
  if (dashboardClosed) return;
3163
3323
  dashboardClosed = true;
3164
3324
  clearCtrlCArm();
3325
+ clearStopTimer();
3165
3326
  detachDashboardHandlers();
3166
3327
  resolve(next);
3167
3328
  }
@@ -3181,41 +3342,62 @@ async function runServeDashboard({ env, platform, agentsDir, collectionId }) {
3181
3342
  return;
3182
3343
  }
3183
3344
  if (overlayActive) return;
3184
- const agCount = state.agents.length;
3345
+ if (stopRequested) return;
3346
+ const agCount = agentDisplayList().length;
3185
3347
  if (name >= "1" && name <= "9") {
3186
3348
  openAgentLog(parseInt(name, 10) - 1);
3187
3349
  return;
3188
3350
  }
3189
3351
  if (name === "UP" && agCount > 0) {
3190
- selectedIdx = (selectedIdx - 1 + agCount) % agCount;
3352
+ selectedIdx = clampListIndex(selectedIdx - 1, agCount);
3191
3353
  redraw();
3192
3354
  } else if (name === "DOWN" && agCount > 0) {
3193
- selectedIdx = (selectedIdx + 1) % agCount;
3355
+ selectedIdx = clampListIndex(selectedIdx + 1, agCount);
3194
3356
  redraw();
3195
3357
  } else if (name === "ENTER") {
3196
3358
  openAgentLog(selectedIdx);
3197
3359
  } else if (name === "s" || name === "S") {
3198
- child?.kill("SIGINT");
3199
- finish("home");
3360
+ requestStop();
3200
3361
  }
3201
3362
  }
3202
3363
  function handleMouse(name, data) {
3203
- if (overlayActive) return;
3364
+ if (overlayActive || stopRequested) return;
3204
3365
  const idx = agentRowMap.get(data.y);
3205
- if (idx === void 0) return;
3206
3366
  if (name === "MOUSE_LEFT_BUTTON_PRESSED") {
3207
- openAgentLog(idx);
3208
- } else if (name !== "MOUSE_LEFT_BUTTON_RELEASED") {
3367
+ pressedAgentIdx = idx ?? null;
3368
+ if (idx !== void 0 && idx !== selectedIdx) {
3369
+ selectedIdx = idx;
3370
+ redraw();
3371
+ }
3372
+ return;
3373
+ }
3374
+ if (name === "MOUSE_LEFT_BUTTON_RELEASED") {
3375
+ const shouldOpen = idx !== void 0 && idx === pressedAgentIdx;
3376
+ pressedAgentIdx = null;
3377
+ if (shouldOpen) openAgentLog(idx);
3378
+ return;
3379
+ }
3380
+ if (idx === void 0) return;
3381
+ if (name !== "MOUSE_LEFT_BUTTON_RELEASED") {
3209
3382
  if (idx !== selectedIdx) {
3210
3383
  selectedIdx = idx;
3211
3384
  redraw();
3212
3385
  }
3213
3386
  }
3214
3387
  }
3388
+ function handleResize() {
3389
+ if (!overlayActive) redraw();
3390
+ }
3215
3391
  term.on("key", handleKey);
3216
3392
  term.on("mouse", handleMouse);
3393
+ term.on("resize", handleResize);
3217
3394
  if (child) {
3218
3395
  child.once("exit", () => {
3396
+ clearStopTimer();
3397
+ if (stopRequested) {
3398
+ finish("home");
3399
+ return;
3400
+ }
3219
3401
  setTimeout(() => {
3220
3402
  if (dashboardClosed || overlayActive) return;
3221
3403
  exitPromptActive = true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vendian/cli",
3
- "version": "0.0.28",
3
+ "version": "0.0.29",
4
4
  "description": "Public Vendian CLI bootstrapper and launcher",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,