@vendian/cli 0.0.28 → 0.0.30

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 +250 -36
  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.30" : 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}
3129
+ `);
3130
+ }
3131
+ if (viewport.showAgentHint) {
3132
+ term.gray(` Click an agent or use \u2191\u2193 + Enter to see its activity log
3025
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,10 +3248,12 @@ 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];
3143
- showAgentLog({ agent: ag, state, env, platform }).then(() => {
3255
+ const ag = agents[clampedIdx];
3256
+ showAgentLog({ agent: ag, getState: () => state, env, platform }).then(() => {
3144
3257
  if (dashboardClosed) return;
3145
3258
  overlayActive = false;
3146
3259
  term.clear();
@@ -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;
@@ -3229,14 +3411,23 @@ async function runServeDashboard({ env, platform, agentsDir, collectionId }) {
3229
3411
  }
3230
3412
  });
3231
3413
  }
3232
- async function showAgentLog({ agent, state, env, platform }) {
3233
- const logs = agentLogEntries(state.agentLogs, agent.path);
3234
- const agentObj = state.agents.find((a) => (a.relativePath || ".") === agent.path);
3235
- const runtime = agentObj ? agentRuntimeStatus(agentObj, state.agentRunState) : { status: "unknown", label: "offline" };
3236
- const status = friendlyStatus(runtime.status);
3414
+ async function showAgentLog({ agent, getState, env, platform }) {
3415
+ const liveState = typeof getState === "function" ? getState : () => getState;
3237
3416
  let scrollOffset = 0;
3417
+ let autoScroll = true;
3238
3418
  const visibleCount = Math.max(8, (term.height || 24) - 14);
3419
+ function currentLogs() {
3420
+ return agentLogEntries(liveState().agentLogs, agent.path);
3421
+ }
3239
3422
  function draw() {
3423
+ const state = liveState();
3424
+ const logs = currentLogs();
3425
+ const agentObj = state.agents.find((a) => (a.relativePath || ".") === agent.path);
3426
+ const runtime = agentObj ? agentRuntimeStatus(agentObj, state.agentRunState) : { status: "unknown", label: "offline" };
3427
+ const status = friendlyStatus(runtime.status);
3428
+ if (autoScroll) {
3429
+ scrollOffset = Math.max(0, logs.length - visibleCount);
3430
+ }
3240
3431
  term.clear();
3241
3432
  drawHeader({ env, platform, serveState: state });
3242
3433
  term("\n");
@@ -3301,27 +3492,50 @@ async function showAgentLog({ agent, state, env, platform }) {
3301
3492
  term.gray(" scroll ");
3302
3493
  term.brightBlue.bold("PgUp/PgDn");
3303
3494
  term.gray(" page ");
3304
- term.brightBlue.bold("Esc");
3305
- term.gray(" back\n");
3495
+ if (autoScroll) {
3496
+ term.brightBlue.bold("Esc");
3497
+ term.gray(" back ");
3498
+ term.cyan("\u2193 live\n");
3499
+ } else {
3500
+ term.brightBlue.bold("Esc");
3501
+ term.gray(" back ");
3502
+ term.gray("\u2193 ");
3503
+ term.brightBlue.bold("End");
3504
+ term.gray(" resume live\n");
3505
+ }
3306
3506
  }
3307
3507
  draw();
3508
+ let overlayDone = false;
3509
+ const redrawInterval = setInterval(() => {
3510
+ if (!overlayDone) draw();
3511
+ }, 500);
3308
3512
  await new Promise((resolve) => {
3309
3513
  function handler(name) {
3514
+ const logs = currentLogs();
3310
3515
  const maxOff = Math.max(0, logs.length - visibleCount);
3311
3516
  if (name === "ESCAPE") {
3312
3517
  term.off("key", handler);
3518
+ overlayDone = true;
3519
+ clearInterval(redrawInterval);
3313
3520
  resolve();
3314
3521
  } else if (name === "UP") {
3522
+ autoScroll = false;
3315
3523
  scrollOffset = Math.max(0, scrollOffset - 1);
3316
3524
  draw();
3317
3525
  } else if (name === "DOWN") {
3318
3526
  scrollOffset = Math.min(maxOff, scrollOffset + 1);
3527
+ if (scrollOffset >= maxOff) autoScroll = true;
3319
3528
  draw();
3320
3529
  } else if (name === "PAGE_UP") {
3530
+ autoScroll = false;
3321
3531
  scrollOffset = Math.max(0, scrollOffset - visibleCount);
3322
3532
  draw();
3323
3533
  } else if (name === "PAGE_DOWN") {
3324
3534
  scrollOffset = Math.min(maxOff, scrollOffset + visibleCount);
3535
+ if (scrollOffset >= maxOff) autoScroll = true;
3536
+ draw();
3537
+ } else if (name === "END") {
3538
+ autoScroll = true;
3325
3539
  draw();
3326
3540
  }
3327
3541
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vendian/cli",
3
- "version": "0.0.28",
3
+ "version": "0.0.30",
4
4
  "description": "Public Vendian CLI bootstrapper and launcher",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,