sentinelayer-cli 0.10.2 → 0.11.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sentinelayer-cli",
3
- "version": "0.10.2",
3
+ "version": "0.11.1",
4
4
  "description": "Scaffold Sentinelayer spec/prompt/guide artifacts with secure browser auth and token bootstrap.",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -2474,7 +2474,8 @@ export function registerSessionCommand(program) {
2474
2474
  const remote = await listSessionsFromApi({
2475
2475
  targetPath,
2476
2476
  includeArchived,
2477
- limit,
2477
+ limit: emitJson ? 200 : limit,
2478
+ fetchAll: emitJson,
2478
2479
  });
2479
2480
  const trimmed = emitJson ? remote.sessions : remote.sessions.slice(0, limit);
2480
2481
  const payload = {
@@ -2485,6 +2486,10 @@ export function registerSessionCommand(program) {
2485
2486
  ok: remote.ok,
2486
2487
  reason: remote.reason || "",
2487
2488
  count: remote.count,
2489
+ nextCursor: remote.nextCursor || null,
2490
+ hasMore: Boolean(remote.hasMore),
2491
+ truncated: Boolean(remote.truncated),
2492
+ warnings: Array.isArray(remote.warnings) ? remote.warnings : [],
2488
2493
  sessions: trimmed,
2489
2494
  };
2490
2495
  if (emitJson) {
@@ -2519,10 +2524,19 @@ export function registerSessionCommand(program) {
2519
2524
  `${item.sessionId} status=${item.status}${archive} created=${created}${lastActivity}`,
2520
2525
  );
2521
2526
  }
2522
- if (remote.count > trimmed.length) {
2527
+ if (remote.count > trimmed.length || remote.hasMore) {
2523
2528
  console.log(
2524
2529
  pc.gray(
2525
- `… ${remote.count - trimmed.length} more (raise --limit or use --json).`,
2530
+ remote.hasMore
2531
+ ? "… more sessions are available (raise --limit or use --json)."
2532
+ : `… ${remote.count - trimmed.length} more (raise --limit or use --json).`,
2533
+ ),
2534
+ );
2535
+ }
2536
+ if (remote.truncated) {
2537
+ console.log(
2538
+ pc.yellow(
2539
+ "Remote session listing is truncated by the page cap; JSON output includes nextCursor for resume.",
2526
2540
  ),
2527
2541
  );
2528
2542
  }
@@ -1243,21 +1243,27 @@ export async function pollSessionEventsBefore(
1243
1243
  *
1244
1244
  * Mirrors the failure shape of `pollHumanMessages` so callers can render
1245
1245
  * a single error path: `{ ok, reason, sessions, count }`. Sessions are
1246
- * returned in API order (newest-first per the server's contract); the
1246
+ * returned in API order (most-recently-active first per the server's contract); the
1247
1247
  * caller is responsible for any further sort or filter.
1248
1248
  *
1249
1249
  * @param {object} [options]
1250
1250
  * @param {string} [options.targetPath]
1251
1251
  * @param {boolean} [options.includeArchived]
1252
1252
  * @param {number} [options.limit]
1253
+ * @param {string|null} [options.cursor]
1254
+ * @param {boolean} [options.fetchAll]
1255
+ * @param {number} [options.maxPages]
1253
1256
  * @param {Function} [options.resolveAuthSession]
1254
1257
  * @param {Function} [options.fetchImpl]
1255
- * @returns {Promise<{ok: boolean, reason: string, sessions: Array<object>, count: number}>}
1258
+ * @returns {Promise<{ok: boolean, reason: string, sessions: Array<object>, count: number, nextCursor: string|null, hasMore: boolean, truncated: boolean, warnings: Array<object>}>}
1256
1259
  */
1257
1260
  export async function listSessionsFromApi({
1258
1261
  targetPath = process.cwd(),
1259
1262
  includeArchived = false,
1260
1263
  limit = 50,
1264
+ cursor = null,
1265
+ fetchAll = false,
1266
+ maxPages = 50,
1261
1267
  resolveAuthSession = resolveActiveAuthSession,
1262
1268
  fetchImpl = fetchWithTimeout,
1263
1269
  timeoutMs = DEFAULT_SYNC_TIMEOUT_MS,
@@ -1270,52 +1276,115 @@ export async function listSessionsFromApi({
1270
1276
  autoRotate: false,
1271
1277
  });
1272
1278
  } catch {
1273
- return { ok: false, reason: "no_session", sessions: [], count: 0 };
1274
- }
1275
- if (!session || !session.token) {
1276
- return { ok: false, reason: "not_authenticated", sessions: [], count: 0 };
1277
- }
1278
-
1279
- const apiBaseUrl = resolveApiBaseUrl(session);
1280
- const query = new URLSearchParams();
1281
- if (includeArchived) query.set("include_archived", "true");
1282
- const normalizedLimit = Math.max(1, Math.min(200, normalizePositiveInteger(limit, 50)));
1283
- query.set("limit", String(normalizedLimit));
1284
- const endpoint = `${apiBaseUrl}/api/v1/sessions?${query.toString()}`;
1285
-
1286
- let response;
1287
- try {
1288
- response = await fetchImpl(
1289
- endpoint,
1290
- {
1291
- method: "GET",
1292
- headers: { Authorization: `Bearer ${session.token}` },
1293
- },
1294
- normalizePositiveInteger(timeoutMs, DEFAULT_SYNC_TIMEOUT_MS),
1295
- );
1296
- } catch (err) {
1297
1279
  return {
1298
1280
  ok: false,
1299
- reason: normalizeString(err?.message) || "list_failed",
1281
+ reason: "no_session",
1300
1282
  sessions: [],
1301
1283
  count: 0,
1284
+ nextCursor: null,
1285
+ hasMore: false,
1286
+ truncated: false,
1287
+ warnings: [],
1302
1288
  };
1303
1289
  }
1304
- if (!response || !response.ok) {
1290
+ if (!session || !session.token) {
1305
1291
  return {
1306
1292
  ok: false,
1307
- reason: `api_${response ? response.status : "no_response"}`,
1293
+ reason: "not_authenticated",
1308
1294
  sessions: [],
1309
1295
  count: 0,
1296
+ nextCursor: null,
1297
+ hasMore: false,
1298
+ truncated: false,
1299
+ warnings: [],
1310
1300
  };
1311
1301
  }
1312
- const payload = await response.json().catch(() => ({}));
1313
- const sessions = Array.isArray(payload?.sessions) ? payload.sessions : [];
1302
+
1303
+ const apiBaseUrl = resolveApiBaseUrl(session);
1304
+ const normalizedLimit = Math.max(1, Math.min(200, normalizePositiveInteger(limit, 50)));
1305
+ const normalizedMaxPages = Math.max(1, Math.min(100, normalizePositiveInteger(maxPages, 50)));
1306
+ let nextCursor = normalizeString(cursor) || null;
1307
+ let hasMore = false;
1308
+ const sessions = [];
1309
+ const seenSessionIds = new Set();
1310
+ let count = 0;
1311
+
1312
+ for (let page = 0; page < normalizedMaxPages; page += 1) {
1313
+ const query = new URLSearchParams();
1314
+ if (includeArchived) query.set("include_archived", "true");
1315
+ query.set("limit", String(normalizedLimit));
1316
+ if (nextCursor) query.set("cursor", nextCursor);
1317
+ const endpoint = `${apiBaseUrl}/api/v1/sessions?${query.toString()}`;
1318
+
1319
+ let response;
1320
+ try {
1321
+ response = await fetchImpl(
1322
+ endpoint,
1323
+ {
1324
+ method: "GET",
1325
+ headers: { Authorization: `Bearer ${session.token}` },
1326
+ },
1327
+ normalizePositiveInteger(timeoutMs, DEFAULT_SYNC_TIMEOUT_MS),
1328
+ );
1329
+ } catch (err) {
1330
+ return {
1331
+ ok: false,
1332
+ reason: normalizeString(err?.message) || "list_failed",
1333
+ sessions: [],
1334
+ count: 0,
1335
+ nextCursor: null,
1336
+ hasMore: false,
1337
+ truncated: false,
1338
+ warnings: [],
1339
+ };
1340
+ }
1341
+ if (!response || !response.ok) {
1342
+ return {
1343
+ ok: false,
1344
+ reason: `api_${response ? response.status : "no_response"}`,
1345
+ sessions: [],
1346
+ count: 0,
1347
+ nextCursor: null,
1348
+ hasMore: false,
1349
+ truncated: false,
1350
+ warnings: [],
1351
+ };
1352
+ }
1353
+ const payload = await response.json().catch(() => ({}));
1354
+ const pageSessions = Array.isArray(payload?.sessions) ? payload.sessions : [];
1355
+ for (const item of pageSessions) {
1356
+ const sessionId = normalizeString(item?.sessionId || item?.id);
1357
+ if (sessionId && seenSessionIds.has(sessionId)) continue;
1358
+ if (sessionId) seenSessionIds.add(sessionId);
1359
+ sessions.push(item);
1360
+ }
1361
+ count = sessions.length;
1362
+ nextCursor = normalizeString(payload?.next_cursor || payload?.nextCursor);
1363
+ hasMore = Boolean(payload?.has_more && nextCursor);
1364
+ if (!fetchAll || !hasMore) break;
1365
+ }
1366
+
1367
+ const truncated = Boolean(fetchAll && hasMore && nextCursor);
1368
+ const warnings = truncated
1369
+ ? [
1370
+ {
1371
+ code: "SESSION_LIST_MAX_PAGES_REACHED",
1372
+ message: `Session list stopped after ${normalizedMaxPages} pages; output is partial and nextCursor can resume the listing.`,
1373
+ maxPages: normalizedMaxPages,
1374
+ nextCursor,
1375
+ },
1376
+ ]
1377
+ : [];
1378
+
1314
1379
  return {
1315
1380
  ok: true,
1316
1381
  reason: "",
1317
1382
  sessions,
1318
- count: typeof payload?.count === "number" ? payload.count : sessions.length,
1383
+ count,
1384
+ nextCursor: nextCursor || null,
1385
+ hasMore,
1386
+ truncated,
1387
+ warnings,
1319
1388
  };
1320
1389
  }
1321
1390