sentinelayer-cli 0.3.0 → 0.4.5

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/src/legacy-cli.js CHANGED
@@ -143,7 +143,7 @@ function parseCliArgs(argv) {
143
143
  for (let i = 0; i < argv.length; i += 1) {
144
144
  const arg = String(argv[i] || "").trim();
145
145
  if (!arg) continue;
146
- if (arg === "--help" || arg === "-h") {
146
+ if (arg === "--help" || arg === "-h" || arg === "help") {
147
147
  showHelp = true;
148
148
  continue;
149
149
  }
@@ -191,32 +191,66 @@ function parseCliArgs(argv) {
191
191
  function printUsage() {
192
192
  console.log(`sentinelayer-cli v${CLI_VERSION}`);
193
193
  console.log("");
194
- console.log("Usage:");
195
- console.log(" sentinelayer-cli [project-name] [options]");
196
- console.log(" sentinelayer-cli /omargate deep [--path PATH]");
197
- console.log(" sentinelayer-cli /audit [--path PATH]");
198
- console.log(" sentinelayer-cli /persona orchestrator [--mode MODE] [--path PATH]");
199
- console.log(" sentinelayer-cli /apply --plan <todo.md> [--path PATH]");
200
- console.log(" sentinelayer-cli config <list|get|set|edit> [options]");
201
- console.log(" sentinelayer-cli ingest map [--path PATH] [--output-file PATH]");
202
- console.log(" sentinelayer-cli spec <list-templates|show-template|generate> [options]");
203
- console.log(" sentinelayer-cli prompt <generate|preview> [options]");
194
+ console.log("Usage: sl <command> [options]");
195
+ console.log("");
196
+ console.log("Scaffold:");
197
+ console.log(" sl [project-name] Create a new project with SentinelLayer scaffolding");
198
+ console.log(" sl init [project-name] Same as above (interactive or --non-interactive)");
199
+ console.log("");
200
+ console.log("Authentication:");
201
+ console.log(" sl auth login Log in via browser (provisions SentinelLayer + AIdenID)");
202
+ console.log(" sl auth status Show authentication and AIdenID provisioning status");
203
+ console.log(" sl auth sessions List stored session metadata");
204
+ console.log(" sl auth logout Clear local session");
205
+ console.log("");
206
+ console.log("Security & Review:");
207
+ console.log(" sl review scan --path . --json Deterministic code review (full or --mode diff)");
208
+ console.log(" sl /omargate deep --path . --json Local Omar Gate security scan (P0/P1/P2 findings)");
209
+ console.log(" sl scan init Generate .github/workflows/omar-gate.yml from spec");
210
+ console.log(" sl scan setup-secrets --repo <slug> Inject SENTINELAYER_TOKEN into GitHub repo secrets");
211
+ console.log("");
212
+ console.log("Specification & Planning:");
213
+ console.log(" sl spec list-templates List available project templates");
214
+ console.log(" sl spec generate Generate SPEC.md from template or AI");
215
+ console.log(" sl prompt generate Generate agent execution prompt from spec");
216
+ console.log(" sl guide generate Generate BUILD_GUIDE.md from spec");
217
+ console.log(" sl ingest map --json Codebase AST ingest with framework detection");
218
+ console.log("");
219
+ console.log("Audit & Quality:");
220
+ console.log(" sl audit --path . --json Full 15-agent audit swarm");
221
+ console.log(" sl audit frontend --path . --json Jules frontend audit (--stream for NDJSON, --url for runtime)");
222
+ console.log(" sl audit security --path . --json Security-focused audit");
223
+ console.log("");
224
+ console.log("AIdenID (Identity Testing):");
225
+ console.log(" sl ai identity provision --execute Provision ephemeral test email (auto-credentials after login)");
226
+ console.log(" sl ai identity wait-for-otp <id> Poll for OTP extraction from provisioned email");
227
+ console.log(" sl ai identity list List tracked identities");
228
+ console.log(" sl ai identity lineage <id> Show identity parent/child tree");
229
+ console.log(" sl ai identity revoke <id> Revoke a provisioned identity");
230
+ console.log("");
231
+ console.log("Cost & Policy:");
232
+ console.log(" sl cost show --json Show accumulated cost tracking");
233
+ console.log(" sl policy list List available policy packs");
234
+ console.log(" sl policy use <pack> Switch active policy pack");
235
+ console.log("");
236
+ console.log("Advanced:");
237
+ console.log(" sl swarm plan --path . --json Multi-agent swarm planning");
238
+ console.log(" sl mcp list --json List MCP registries and adapters");
239
+ console.log(" sl telemetry show --json Show run event ledger");
240
+ console.log(" sl config list Show current configuration");
204
241
  console.log("");
205
242
  console.log("Options:");
206
- console.log(" -h, --help Show help");
243
+ console.log(" -h, --help Show this help");
207
244
  console.log(" -v, --version Show CLI version");
208
- console.log(" --non-interactive Disable prompts and require interview payload");
209
- console.log(" --interview-file PATH Load interview JSON from file");
210
- console.log(" --skip-browser-open Do not auto-open browser during auth");
211
- console.log(" --path PATH Target path for local command mode");
212
- console.log(" --output-dir PATH Artifact root for local command reports (default .sentinelayer)");
213
- console.log(" --json Emit machine-readable JSON for local command mode");
245
+ console.log(" --json Machine-readable JSON output");
246
+ console.log(" --path PATH Target workspace path");
247
+ console.log(" --non-interactive Disable prompts (require --interview-file)");
214
248
  console.log("");
215
- console.log("Environment:");
216
- console.log(" SENTINELAYER_CLI_NON_INTERACTIVE=1");
217
- console.log(" SENTINELAYER_CLI_SKIP_BROWSER_OPEN=1");
218
- console.log(" SENTINELAYER_CLI_INTERVIEW_JSON='{\"projectName\":\"my-app\",...}'");
219
- console.log(" SENTINELAYER_GITHUB_CLONE_BASE_URL=https://github.com");
249
+ console.log("Quickstart:");
250
+ console.log(" sl auth login && npx create-sentinelayer my-app && cd my-app");
251
+ console.log(" # Then hand docs/spec.md to your coding agent");
252
+ console.log("");
253
+ console.log("Docs: https://sentinelayer.com/docs");
220
254
  }
221
255
 
222
256
  function normalizeInterviewInput(
@@ -808,6 +842,12 @@ async function collectScanFiles(rootPath) {
808
842
  }
809
843
 
810
844
  async function runCredentialScan(targetPath) {
845
+ const testOrFixturePathPattern = /(?:^|[\\/])(?:test|tests|__tests__|fixtures?)(?:[\\/]|$)/i;
846
+ const localReviewSourcePathPattern = /(?:^|[\\/])src[\\/]review[\\/]local-review\.js$/i;
847
+ const workItemExcludePathPattern = new RegExp(
848
+ `${testOrFixturePathPattern.source}|${localReviewSourcePathPattern.source}`,
849
+ "i"
850
+ );
811
851
  const rules = [
812
852
  {
813
853
  severity: "P1",
@@ -828,11 +868,13 @@ async function runCredentialScan(targetPath) {
828
868
  severity: "P2",
829
869
  message: "Possible hardcoded credential literal.",
830
870
  regex: /(api[_-]?key|secret|token)\s*[:=]\s*['"][^'"]{20,}['"]/i,
871
+ excludePathPattern: testOrFixturePathPattern,
831
872
  },
832
873
  {
833
874
  severity: "P2",
834
875
  message: "Work-item marker found.",
835
876
  regex: /\b(?:\x54\x4f\x44\x4f|\x46\x49\x58\x4d\x45|\x48\x41\x43\x4b)\b/,
877
+ excludePathPattern: workItemExcludePathPattern,
836
878
  },
837
879
  ];
838
880
 
@@ -841,6 +883,7 @@ async function runCredentialScan(targetPath) {
841
883
  const maxFindings = 200;
842
884
 
843
885
  for (const filePath of files) {
886
+ const relativePath = path.relative(targetPath, filePath).replace(/\\/g, "/");
844
887
  let text = "";
845
888
  try {
846
889
  text = await fsp.readFile(filePath, "utf-8");
@@ -853,10 +896,11 @@ async function runCredentialScan(targetPath) {
853
896
  if (!line) continue;
854
897
  if (line.includes("<your-token>") || line.includes("example")) continue;
855
898
  for (const rule of rules) {
899
+ if (rule.excludePathPattern && rule.excludePathPattern.test(relativePath)) continue;
856
900
  if (!rule.regex.test(line)) continue;
857
901
  findings.push({
858
902
  severity: rule.severity,
859
- file: path.relative(targetPath, filePath).replace(/\\/g, "/"),
903
+ file: relativePath,
860
904
  line: lineIndex + 1,
861
905
  message: rule.message,
862
906
  excerpt: line.trim().slice(0, 180),
@@ -45,6 +45,13 @@ const SEVERITY_ORDER = new Map([
45
45
  ["P3", 3],
46
46
  ]);
47
47
 
48
+ const TEST_OR_FIXTURE_PATH_PATTERN = /(?:^|[\\/])(?:test|tests|__tests__|fixtures?)(?:[\\/]|$)/i;
49
+ const LOCAL_REVIEW_SOURCE_PATH_PATTERN = /(?:^|[\\/])src[\\/]review[\\/]local-review\.js$/i;
50
+ const WORK_ITEM_MARKER_EXCLUDE_PATH_PATTERN = new RegExp(
51
+ `${TEST_OR_FIXTURE_PATH_PATTERN.source}|${LOCAL_REVIEW_SOURCE_PATH_PATTERN.source}`,
52
+ "i"
53
+ );
54
+
48
55
  const REVIEW_RULES = Object.freeze([
49
56
  {
50
57
  severity: "P1",
@@ -65,11 +72,13 @@ const REVIEW_RULES = Object.freeze([
65
72
  severity: "P2",
66
73
  message: "Possible hardcoded credential literal.",
67
74
  regex: /(api[_-]?key|secret|token)\s*[:=]\s*['\"][^'\"]{20,}['\"]/i,
75
+ excludePathPattern: TEST_OR_FIXTURE_PATH_PATTERN,
68
76
  },
69
77
  {
70
78
  severity: "P2",
71
79
  message: "Work-item marker found.",
72
80
  regex: /\b(?:\x54\x4f\x44\x4f|\x46\x49\x58\x4d\x45|\x48\x41\x43\x4b)\b/,
81
+ excludePathPattern: WORK_ITEM_MARKER_EXCLUDE_PATH_PATTERN,
73
82
  },
74
83
  ]);
75
84
 
@@ -112,6 +121,7 @@ const DETERMINISTIC_REVIEW_RULES = Object.freeze([
112
121
  suggestedFix: "Replace literal with environment/secret-manager lookup.",
113
122
  regex: /(api[_-]?key|secret|token|password|passwd)\s*[:=]\s*['\"][^'\"]{12,}['\"]/i,
114
123
  sourceOnly: true,
124
+ excludePathPattern: TEST_OR_FIXTURE_PATH_PATTERN,
115
125
  },
116
126
  {
117
127
  id: "SL-SEC-006",
@@ -184,6 +194,7 @@ const DETERMINISTIC_REVIEW_RULES = Object.freeze([
184
194
  suggestedFix: "Resolve or scope pending work before release.",
185
195
  regex: /\b(?:TODO|FIXME|HACK)\b/,
186
196
  sourceOnly: true,
197
+ excludePathPattern: WORK_ITEM_MARKER_EXCLUDE_PATH_PATTERN,
187
198
  },
188
199
  {
189
200
  id: "SL-SEC-015",
@@ -86,7 +86,7 @@ export function getPackageJsonTemplate({ projectName, description }) {
86
86
  },
87
87
  devDependencies: {},
88
88
  engines: {
89
- node: ">=18.17.0",
89
+ node: ">=20.0.0",
90
90
  },
91
91
  };
92
92
  }
@@ -117,15 +117,14 @@ export async function syncRunToDashboard(runData) {
117
117
  },
118
118
  };
119
119
 
120
- const response = await fetch(apiUrl + "/api/v1/telemetry", {
120
+ const response = await fetchWithTimeout(apiUrl + "/api/v1/telemetry", {
121
121
  method: "POST",
122
122
  headers: {
123
123
  "Content-Type": "application/json",
124
124
  Authorization: "Bearer " + session.token,
125
125
  },
126
- signal: AbortSignal.timeout(SYNC_TIMEOUT_MS),
127
126
  body: JSON.stringify(payload),
128
- });
127
+ }, SYNC_TIMEOUT_MS);
129
128
 
130
129
  if (!response.ok) {
131
130
  consecutiveFailures++;
@@ -188,3 +187,13 @@ function detectGitRef() {
188
187
  return "main";
189
188
  }
190
189
  }
190
+
191
+ async function fetchWithTimeout(url, options, timeoutMs) {
192
+ const controller = new AbortController();
193
+ const timeoutHandle = setTimeout(() => controller.abort(), timeoutMs);
194
+ try {
195
+ return await fetch(url, { ...options, signal: controller.signal });
196
+ } finally {
197
+ clearTimeout(timeoutHandle);
198
+ }
199
+ }