sentinelayer-cli 0.10.2 → 0.11.0

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.0",
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,8 @@ 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),
2488
2491
  sessions: trimmed,
2489
2492
  };
2490
2493
  if (emitJson) {
@@ -2519,10 +2522,12 @@ export function registerSessionCommand(program) {
2519
2522
  `${item.sessionId} status=${item.status}${archive} created=${created}${lastActivity}`,
2520
2523
  );
2521
2524
  }
2522
- if (remote.count > trimmed.length) {
2525
+ if (remote.count > trimmed.length || remote.hasMore) {
2523
2526
  console.log(
2524
2527
  pc.gray(
2525
- `… ${remote.count - trimmed.length} more (raise --limit or use --json).`,
2528
+ remote.hasMore
2529
+ ? "… more sessions are available (raise --limit or use --json)."
2530
+ : `… ${remote.count - trimmed.length} more (raise --limit or use --json).`,
2526
2531
  ),
2527
2532
  );
2528
2533
  }
@@ -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}>}
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,93 @@ 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,
1302
1286
  };
1303
1287
  }
1304
- if (!response || !response.ok) {
1288
+ if (!session || !session.token) {
1305
1289
  return {
1306
1290
  ok: false,
1307
- reason: `api_${response ? response.status : "no_response"}`,
1291
+ reason: "not_authenticated",
1308
1292
  sessions: [],
1309
1293
  count: 0,
1294
+ nextCursor: null,
1295
+ hasMore: false,
1310
1296
  };
1311
1297
  }
1312
- const payload = await response.json().catch(() => ({}));
1313
- const sessions = Array.isArray(payload?.sessions) ? payload.sessions : [];
1298
+
1299
+ const apiBaseUrl = resolveApiBaseUrl(session);
1300
+ const normalizedLimit = Math.max(1, Math.min(200, normalizePositiveInteger(limit, 50)));
1301
+ const normalizedMaxPages = Math.max(1, Math.min(100, normalizePositiveInteger(maxPages, 50)));
1302
+ let nextCursor = normalizeString(cursor) || null;
1303
+ let hasMore = false;
1304
+ const sessions = [];
1305
+ const seenSessionIds = new Set();
1306
+ let count = 0;
1307
+
1308
+ for (let page = 0; page < normalizedMaxPages; page += 1) {
1309
+ const query = new URLSearchParams();
1310
+ if (includeArchived) query.set("include_archived", "true");
1311
+ query.set("limit", String(normalizedLimit));
1312
+ if (nextCursor) query.set("cursor", nextCursor);
1313
+ const endpoint = `${apiBaseUrl}/api/v1/sessions?${query.toString()}`;
1314
+
1315
+ let response;
1316
+ try {
1317
+ response = await fetchImpl(
1318
+ endpoint,
1319
+ {
1320
+ method: "GET",
1321
+ headers: { Authorization: `Bearer ${session.token}` },
1322
+ },
1323
+ normalizePositiveInteger(timeoutMs, DEFAULT_SYNC_TIMEOUT_MS),
1324
+ );
1325
+ } catch (err) {
1326
+ return {
1327
+ ok: false,
1328
+ reason: normalizeString(err?.message) || "list_failed",
1329
+ sessions: [],
1330
+ count: 0,
1331
+ nextCursor: null,
1332
+ hasMore: false,
1333
+ };
1334
+ }
1335
+ if (!response || !response.ok) {
1336
+ return {
1337
+ ok: false,
1338
+ reason: `api_${response ? response.status : "no_response"}`,
1339
+ sessions: [],
1340
+ count: 0,
1341
+ nextCursor: null,
1342
+ hasMore: false,
1343
+ };
1344
+ }
1345
+ const payload = await response.json().catch(() => ({}));
1346
+ const pageSessions = Array.isArray(payload?.sessions) ? payload.sessions : [];
1347
+ for (const item of pageSessions) {
1348
+ const sessionId = normalizeString(item?.sessionId || item?.id);
1349
+ if (sessionId && seenSessionIds.has(sessionId)) continue;
1350
+ if (sessionId) seenSessionIds.add(sessionId);
1351
+ sessions.push(item);
1352
+ }
1353
+ count = sessions.length;
1354
+ nextCursor = normalizeString(payload?.next_cursor || payload?.nextCursor);
1355
+ hasMore = Boolean(payload?.has_more && nextCursor);
1356
+ if (!fetchAll || !hasMore) break;
1357
+ }
1358
+
1314
1359
  return {
1315
1360
  ok: true,
1316
1361
  reason: "",
1317
1362
  sessions,
1318
- count: typeof payload?.count === "number" ? payload.count : sessions.length,
1363
+ count,
1364
+ nextCursor: nextCursor || null,
1365
+ hasMore,
1319
1366
  };
1320
1367
  }
1321
1368