sentinelayer-cli 0.44.0 → 0.46.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.
Files changed (51) hide show
  1. package/README.md +10 -5
  2. package/package.json +1 -1
  3. package/src/agents/jules/tools/aidenid-email.js +7 -9
  4. package/src/agents/jules/tools/frontend-analyze.js +15 -3
  5. package/src/agents/jules/tools/runtime-audit.js +51 -21
  6. package/src/agents/shared-tools/shell.js +9 -0
  7. package/src/ai/aidenid.js +5 -4
  8. package/src/ai/proxy.js +15 -14
  9. package/src/audit/orchestrator.js +8 -5
  10. package/src/auth/admission-scope.js +5 -3
  11. package/src/auth/credential-destinations.js +221 -0
  12. package/src/auth/gate.js +4 -4
  13. package/src/auth/http.js +22 -5
  14. package/src/auth/service.js +52 -56
  15. package/src/cli.js +24 -6
  16. package/src/commands/ai/provision-governance.js +7 -6
  17. package/src/commands/init.js +25 -16
  18. package/src/commands/scan.js +6 -4
  19. package/src/commands/session.js +33 -45
  20. package/src/commands/spec.js +2 -2
  21. package/src/commands/watch.js +4 -3
  22. package/src/config/service.js +37 -0
  23. package/src/daemon/ast-parser-layer.js +8 -3
  24. package/src/daemon/callgraph-overlay.js +7 -2
  25. package/src/daemon/pulse.js +2 -1
  26. package/src/daemon/watchdog.js +117 -68
  27. package/src/legacy-cli.js +119 -39
  28. package/src/mcp/bridge-inputs.js +223 -0
  29. package/src/mcp/cli-command-tools.js +157 -7
  30. package/src/mcp/registry.js +2 -1
  31. package/src/mcp/session-stdio-server.js +114 -35
  32. package/src/mcp/smoke.js +27 -11
  33. package/src/mcp/token-service.js +7 -9
  34. package/src/memory/retrieval.js +14 -5
  35. package/src/pocket/ring-owner.js +13 -8
  36. package/src/program-lookup.js +54 -0
  37. package/src/review/ai-review.js +2 -2
  38. package/src/review/dd-report-email-client.js +4 -5
  39. package/src/review/local-review.js +11 -5
  40. package/src/session/admission-access.js +13 -10
  41. package/src/session/admission-auth.js +95 -16
  42. package/src/session/admission.js +44 -9
  43. package/src/session/checkpoints.js +11 -10
  44. package/src/session/file-locks.js +19 -18
  45. package/src/session/invitations.js +26 -19
  46. package/src/session/live-source.js +13 -13
  47. package/src/session/route-classes.js +107 -0
  48. package/src/session/sync.js +70 -56
  49. package/src/session/tickets.js +21 -19
  50. package/src/session/title-sync.js +4 -3
  51. package/src/telemetry/sync.js +8 -9
@@ -19,6 +19,7 @@ import os from "node:os";
19
19
  import path from "node:path";
20
20
  import process from "node:process";
21
21
 
22
+ import { checkedTransport, credentialFor, isAuthenticated } from "../auth/credential-destinations.js";
22
23
  import { requestJson, requestJsonMutation } from "../auth/http.js";
23
24
  import { resolveActiveAuthSession } from "../auth/service.js";
24
25
 
@@ -70,8 +71,8 @@ async function writeState(filePath, state) {
70
71
 
71
72
  async function auth(targetPath, resolveAuthSession) {
72
73
  const session = await resolveAuthSession({ cwd: targetPath, env: process.env, autoRotate: false });
73
- if (!session?.token || !session?.apiUrl) throw new Error("Not authenticated for this session.");
74
- return { token: session.token, apiUrl: normalizeString(session.apiUrl).replace(/\/+$/, "") };
74
+ if (!isAuthenticated(session) || !session?.apiUrl) throw new Error("Not authenticated for this session.");
75
+ return { credential: await credentialFor(session), apiUrl: normalizeString(session.apiUrl).replace(/\/+$/, "") };
75
76
  }
76
77
 
77
78
  function ticketsUrl(apiUrl, sessionId, suffix = "") {
@@ -155,7 +156,7 @@ export async function listTickets(
155
156
  maxPages = SNAPSHOT_MAX_PAGES,
156
157
  } = {}
157
158
  ) {
158
- const { token, apiUrl } = await auth(targetPath, resolveAuthSession);
159
+ const { credential, apiUrl } = await auth(targetPath, resolveAuthSession);
159
160
  const items = [];
160
161
  let cursor = null;
161
162
  let afterId = null;
@@ -164,9 +165,9 @@ export async function listTickets(
164
165
  const query = new URLSearchParams({ limit: String(pageSize) });
165
166
  if (afterId) query.set("afterId", afterId);
166
167
  const body = validatedSnapshotPage(
167
- await requestRead(`${ticketsUrl(apiUrl, sessionId, "/snapshot")}?${query}`, {
168
+ await checkedTransport(requestRead)(`${ticketsUrl(apiUrl, sessionId, "/snapshot")}?${query}`, {
168
169
  method: "GET",
169
- headers: { Authorization: `Bearer ${token}` },
170
+ credential,
170
171
  }),
171
172
  { first: page === 0, afterId },
172
173
  );
@@ -192,19 +193,20 @@ export async function ticketEvents(
192
193
  sessionId,
193
194
  { after = 0, targetPath = process.cwd(), resolveAuthSession = resolveActiveAuthSession, requestRead = requestJson } = {}
194
195
  ) {
195
- const { token, apiUrl } = await auth(targetPath, resolveAuthSession);
196
- return requestRead(`${ticketsUrl(apiUrl, sessionId, "/events")}?after=${encodeURIComponent(String(after))}`, {
196
+ const { credential, apiUrl } = await auth(targetPath, resolveAuthSession);
197
+ return checkedTransport(requestRead)(`${ticketsUrl(apiUrl, sessionId, "/events")}?after=${encodeURIComponent(String(after))}`, {
197
198
  method: "GET",
198
- headers: { Authorization: `Bearer ${token}` },
199
+ credential,
199
200
  });
200
201
  }
201
202
 
202
- async function mutate(url, { token, key, operation, body, requestMutation }) {
203
- return requestMutation(url, {
203
+ async function mutate(url, { credential, key, operation, body, requestMutation }) {
204
+ return checkedTransport(requestMutation)(url, {
204
205
  method: "POST",
205
206
  operationName: `session.ticket_${operation}`,
206
207
  idempotencyKey: key,
207
- headers: { Authorization: `Bearer ${token}`, "Idempotency-Key": key },
208
+ credential,
209
+ headers: { "Idempotency-Key": key },
208
210
  body,
209
211
  });
210
212
  }
@@ -222,10 +224,10 @@ export async function claimTicket(
222
224
  } = {}
223
225
  ) {
224
226
  const statePath = ticketLeasePath(sessionId, ticketId, identity, { homeDir });
225
- const { token, apiUrl } = await auth(targetPath, resolveAuthSession);
227
+ const { credential, apiUrl } = await auth(targetPath, resolveAuthSession);
226
228
  const { key } = await operationKey(statePath, "claim", String(expectedVersion ?? "any"));
227
229
  const response = await mutate(ticketsUrl(apiUrl, sessionId, `/${encodeURIComponent(ticketId)}/claim`), {
228
- token, key, operation: "claim", requestMutation,
230
+ credential, key, operation: "claim", requestMutation,
229
231
  body: expectedVersion == null ? {} : { expectedVersion: Number(expectedVersion) },
230
232
  });
231
233
  // The lease is recorded here only if the server says it is held by the identity that
@@ -258,10 +260,10 @@ export async function renewTicketLease(
258
260
  ) {
259
261
  const statePath = ticketLeasePath(sessionId, ticketId, identity, { homeDir });
260
262
  const held = await heldLease(statePath);
261
- const { token, apiUrl } = await auth(targetPath, resolveAuthSession);
263
+ const { credential, apiUrl } = await auth(targetPath, resolveAuthSession);
262
264
  const { key } = await operationKey(statePath, "renew", `${held.leaseId}:${held.fence}:${held.ticketVersion}`);
263
265
  const response = await mutate(ticketsUrl(apiUrl, sessionId, `/${encodeURIComponent(ticketId)}/lease/renew`), {
264
- token, key, operation: "renew", requestMutation, body: { leaseId: held.leaseId, fence: held.fence },
266
+ credential, key, operation: "renew", requestMutation, body: { leaseId: held.leaseId, fence: held.fence },
265
267
  });
266
268
  await writeState(statePath, leaseState(response));
267
269
  return response;
@@ -274,10 +276,10 @@ export async function releaseTicketLease(
274
276
  ) {
275
277
  const statePath = ticketLeasePath(sessionId, ticketId, identity, { homeDir });
276
278
  const held = await heldLease(statePath);
277
- const { token, apiUrl } = await auth(targetPath, resolveAuthSession);
279
+ const { credential, apiUrl } = await auth(targetPath, resolveAuthSession);
278
280
  const { key } = await operationKey(statePath, "release", `${held.leaseId}:${held.fence}:${held.ticketVersion}`);
279
281
  const response = await mutate(ticketsUrl(apiUrl, sessionId, `/${encodeURIComponent(ticketId)}/lease/release`), {
280
- token, key, operation: "release", requestMutation,
282
+ credential, key, operation: "release", requestMutation,
281
283
  body: { leaseId: held.leaseId, fence: held.fence, expectedVersion: held.ticketVersion, ...(reason ? { reason } : {}) },
282
284
  });
283
285
  await fsp.rm(statePath, { force: true });
@@ -299,10 +301,10 @@ export async function submitTicket(
299
301
  ) {
300
302
  const statePath = ticketLeasePath(sessionId, ticketId, identity, { homeDir });
301
303
  const held = await heldLease(statePath);
302
- const { token, apiUrl } = await auth(targetPath, resolveAuthSession);
304
+ const { credential, apiUrl } = await auth(targetPath, resolveAuthSession);
303
305
  const { key } = await operationKey(statePath, "submit", `${held.leaseId}:${held.fence}:${held.ticketVersion}`);
304
306
  const response = await mutate(ticketsUrl(apiUrl, sessionId, `/${encodeURIComponent(ticketId)}/submit`), {
305
- token, key, operation: "submit", requestMutation,
307
+ credential, key, operation: "submit", requestMutation,
306
308
  body: {
307
309
  leaseId: held.leaseId,
308
310
  fence: held.fence,
@@ -1,5 +1,6 @@
1
1
  import process from "node:process";
2
2
 
3
+ import { checkedTransport, credentialFor, isAuthenticated } from "../auth/credential-destinations.js";
3
4
  import { SentinelayerApiError, requestJsonMutation } from "../auth/http.js";
4
5
  import { resolveActiveAuthSession } from "../auth/service.js";
5
6
  import { recordSessionRemoteTitleSync } from "./store.js";
@@ -66,7 +67,7 @@ export async function pushSessionTitleToApi(
66
67
  env,
67
68
  autoRotate,
68
69
  });
69
- if (!session?.token || !session?.apiUrl) {
70
+ if (!isAuthenticated(session) || !session?.apiUrl) {
70
71
  await recordRemoteTitleSync(normalizedSessionId, {
71
72
  targetPath,
72
73
  title: normalizedTitle,
@@ -76,12 +77,12 @@ export async function pushSessionTitleToApi(
76
77
  return { synced: false, reason: "not_authenticated" };
77
78
  }
78
79
  const apiUrl = String(session.apiUrl).replace(/\/+$/, "");
79
- const result = await requestMutation(
80
+ const result = await checkedTransport(requestMutation)(
80
81
  `${apiUrl}/api/v1/sessions/${encodeURIComponent(normalizedSessionId)}/title`,
81
82
  {
82
83
  method: "POST",
83
84
  operationName: "session.set_title",
84
- headers: { Authorization: `Bearer ${session.token}` },
85
+ credential: await credentialFor(session, { env }),
85
86
  body: { title: normalizedTitle },
86
87
  timeoutMs,
87
88
  maxRetries,
@@ -3,6 +3,7 @@ import { randomUUID } from "node:crypto";
3
3
  import { readFileSync } from "node:fs";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { dirname, resolve } from "node:path";
6
+ import { credentialedRequest, credentialFor, isAuthenticated } from "../auth/credential-destinations.js";
6
7
  import { resolveActiveAuthSession } from "../auth/service.js";
7
8
 
8
9
  // Read CLI version from package.json at module load
@@ -71,7 +72,7 @@ export async function syncRunToDashboard(runData) {
71
72
  return { synced: false, reason: "no_session" };
72
73
  }
73
74
 
74
- if (!session || !session.token) {
75
+ if (!isAuthenticated(session)) {
75
76
  return { synced: false, reason: "not_authenticated" };
76
77
  }
77
78
 
@@ -121,14 +122,12 @@ export async function syncRunToDashboard(runData) {
121
122
  },
122
123
  };
123
124
 
124
- const response = await fetchWithTimeout(apiUrl + "/api/v1/telemetry", {
125
- method: "POST",
126
- headers: {
127
- "Content-Type": "application/json",
128
- Authorization: "Bearer " + session.token,
129
- },
130
- body: JSON.stringify(payload),
131
- }, SYNC_TIMEOUT_MS);
125
+ const response = await credentialedRequest(
126
+ await credentialFor(session),
127
+ apiUrl + "/api/v1/telemetry",
128
+ { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload) },
129
+ { fetchImpl: (target, init) => fetchWithTimeout(target, init, SYNC_TIMEOUT_MS) },
130
+ );
132
131
 
133
132
  if (!response.ok) {
134
133
  consecutiveFailures++;