sentinelayer-cli 0.27.0 → 0.28.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/README.md CHANGED
@@ -259,6 +259,7 @@ The `review` command can now add budget-governed AI reasoning on top of determin
259
259
 
260
260
  - `sl review --ai --provider openai --model gpt-5.3-codex`
261
261
  - `sl review --ai --ai-dry-run` (no provider call; deterministic synthetic output)
262
+ - `sl review --ai --require-usage-ledger` (fail closed if billing-grade usage cannot be recorded)
262
263
  - `sl review --ai --max-cost 1.0 --max-tokens 0 --max-runtime-ms 0 --max-tool-calls 0`
263
264
 
264
265
  AI artifacts are persisted in the same run folder:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sentinelayer-cli",
3
- "version": "0.27.0",
3
+ "version": "0.28.0",
4
4
  "description": "Scaffold Sentinelayer spec/prompt/guide artifacts with secure browser auth and token bootstrap.",
5
5
  "type": "module",
6
6
  "scripts": {
package/src/ai/client.js CHANGED
@@ -413,10 +413,18 @@ export class MultiProviderApiClient {
413
413
  * prompt?: string,
414
414
  * stream?: boolean,
415
415
  * apiKey?: string,
416
+ * apiUrl?: string,
416
417
  * env?: NodeJS.ProcessEnv,
417
- * onChunk?: (chunk: string) => void
418
+ * onChunk?: (chunk: string) => void,
419
+ * sessionId?: string,
420
+ * agentId?: string,
421
+ * action?: string,
422
+ * usageIdempotencyKey?: string,
423
+ * billingTier?: string,
424
+ * customerPricingPolicy?: string,
425
+ * metadata?: object
418
426
  * }} [options]
419
- * @returns {Promise<{ provider: string, model: string, text: string }>}
427
+ * @returns {Promise<{ provider: string, model: string, text: string, usage?: object, usageLedger?: object | null }>}
420
428
  */
421
429
  async invoke({
422
430
  provider,
@@ -424,8 +432,16 @@ export class MultiProviderApiClient {
424
432
  prompt,
425
433
  stream = false,
426
434
  apiKey,
435
+ apiUrl = "",
427
436
  env = process.env,
428
437
  onChunk,
438
+ sessionId = "",
439
+ agentId = "",
440
+ action = "",
441
+ usageIdempotencyKey = "",
442
+ billingTier = "",
443
+ customerPricingPolicy = "",
444
+ metadata = null,
429
445
  } = {}) {
430
446
  const resolvedProvider = resolveProvider({ provider, env });
431
447
  const resolvedModel = resolveModel({ provider: resolvedProvider, model });
@@ -437,10 +453,21 @@ export class MultiProviderApiClient {
437
453
  systemPrompt: "",
438
454
  model: resolvedModel,
439
455
  maxTokens: 4096,
456
+ apiUrl,
457
+ token: apiKey,
458
+ sessionId,
459
+ agentId,
460
+ action,
461
+ usageIdempotencyKey,
462
+ billingTier,
463
+ customerPricingPolicy,
464
+ metadata,
465
+ fetchImpl: this.fetchImpl,
440
466
  });
441
467
  return {
442
468
  text: result.text,
443
469
  usage: result.usage,
470
+ usageLedger: result.usageLedger,
444
471
  provider: "sentinelayer",
445
472
  model: resolvedModel,
446
473
  };
@@ -8,7 +8,10 @@ export const PRICED_ACTIONS = Object.freeze([
8
8
  "audit_security",
9
9
  "audit_frontend",
10
10
  "chat_ask",
11
+ "investor_dd_devtestbot_planner",
12
+ "investor_dd_file_planner",
11
13
  "omargate_deep",
14
+ "proxy_llm",
12
15
  "scan_precheck",
13
16
  "spec_generate_ai",
14
17
  ]);
@@ -120,6 +120,7 @@ async function executeReviewRun({
120
120
  maxNoProgress: aiConfig.maxNoProgress,
121
121
  warningThresholdPercent: aiConfig.warnAtPercent,
122
122
  dryRun: Boolean(aiConfig.aiDryRun),
123
+ requireUsageLedger: Boolean(aiConfig.requireUsageLedger),
123
124
  env: process.env,
124
125
  });
125
126
  }
@@ -146,6 +147,7 @@ async function executeReviewRun({
146
147
  invocation: {
147
148
  aiEnabled: Boolean(aiConfig.enable),
148
149
  aiDryRun: Boolean(aiConfig.aiDryRun),
150
+ requireUsageLedger: Boolean(aiConfig.requireUsageLedger),
149
151
  provider: aiConfig.provider,
150
152
  model: aiConfig.model,
151
153
  sessionId: aiConfig.sessionId,
@@ -209,6 +211,7 @@ async function executeReviewRun({
209
211
  budget: aiLayer.budget,
210
212
  cost: aiLayer.cost,
211
213
  telemetry: aiLayer.telemetry,
214
+ billing: aiLayer.billing,
212
215
  }
213
216
  : {
214
217
  enabled: false,
@@ -283,6 +286,7 @@ export function registerReviewCommand(program) {
283
286
  .option("--staged", "Alias for --mode staged")
284
287
  .option("--ai", "Enable AI reasoning layer over deterministic findings")
285
288
  .option("--ai-dry-run", "Run AI layer in dry-run mode (no provider call)")
289
+ .option("--require-usage-ledger", "Fail --ai review if billing-grade session_usage cannot be recorded")
286
290
  .option("--provider <name>", "AI provider override (openai|anthropic|google)")
287
291
  .option("--model <id>", "AI model override")
288
292
  .option("--api-key <key>", "Optional explicit API key override")
@@ -314,6 +318,7 @@ export function registerReviewCommand(program) {
314
318
  aiConfig: {
315
319
  enable: Boolean(options.ai),
316
320
  aiDryRun: Boolean(options.aiDryRun),
321
+ requireUsageLedger: Boolean(options.requireUsageLedger),
317
322
  provider: options.provider,
318
323
  model: options.model,
319
324
  apiKey: options.apiKey,
@@ -486,6 +486,8 @@ export async function runAiReviewLayer({
486
486
  warningThresholdPercent = 80,
487
487
  systemPrompt = "",
488
488
  dryRun = false,
489
+ requireUsageLedger = false,
490
+ usageRecorder = recordSessionUsage,
489
491
  env = process.env,
490
492
  } = {}) {
491
493
  const normalizedTargetPath = path.resolve(String(targetPath || "."));
@@ -496,6 +498,7 @@ export async function runAiReviewLayer({
496
498
  Math.floor(Number(maxFindings || DEFAULT_AI_MAX_FINDINGS))
497
499
  );
498
500
  const normalizedRunId = normalizeString(runId) || "review-ai";
501
+ const normalizedRequireUsageLedger = Boolean(requireUsageLedger);
499
502
 
500
503
  const config = await loadConfig({ cwd: normalizedTargetPath, env });
501
504
  let resolvedProvider = resolveProvider({
@@ -663,7 +666,7 @@ export async function runAiReviewLayer({
663
666
  dryRun: Boolean(dryRun),
664
667
  }),
665
668
  });
666
- sessionUsageLedger = await recordSessionUsage(
669
+ sessionUsageLedger = await usageRecorder(
667
670
  normalizedSessionId,
668
671
  {
669
672
  agentId: "audit-orchestrator",
@@ -693,6 +696,11 @@ export async function runAiReviewLayer({
693
696
  ok: false,
694
697
  reason: error instanceof Error ? error.message : String(error || "session_usage_failed"),
695
698
  };
699
+ if (normalizedRequireUsageLedger) {
700
+ throw new Error(
701
+ `Review AI usage ledger recording failed: ${sessionUsageLedger.reason || "unknown"}.`
702
+ );
703
+ }
696
704
  }
697
705
 
698
706
  let stopTelemetry = null;
@@ -754,6 +762,7 @@ export async function runAiReviewLayer({
754
762
  provider: resolvedProvider,
755
763
  model: resolvedModel,
756
764
  dryRun: Boolean(dryRun),
765
+ requireUsageLedger: normalizedRequireUsageLedger,
757
766
  usage,
758
767
  pricingFound: modelCost.pricingFound,
759
768
  sessionUsageLedger,