sentinelayer-cli 0.34.1 → 0.34.3

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.34.1",
3
+ "version": "0.34.3",
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/legacy-cli.js CHANGED
@@ -2521,14 +2521,14 @@ jobs:
2521
2521
  with:
2522
2522
  github_token: \${{ github.token }}
2523
2523
  sentinelayer_token: \${{ secrets.${normalizedSecret} }}${specIdBindingLine}
2524
- sentinelayer_managed_llm: "false"
2525
- openai_api_key: \${{ secrets.OPENAI_API_KEY }}
2526
- google_api_key: \${{ secrets.GOOGLE_API_KEY }}
2524
+ sentinelayer_managed_llm: "true"
2527
2525
  scan_mode: \${{ github.event_name == 'workflow_dispatch' && inputs.scan_mode || 'deep' }}
2528
2526
  severity_gate: \${{ github.event_name == 'workflow_dispatch' && inputs.severity_gate || 'P1' }}
2529
2527
  model: gpt-5.3-codex
2530
2528
  codex_model: gpt-5.3-codex
2531
- model_fallback: gemini-2.5-flash
2529
+ model_fallback: gpt-4.1-mini
2530
+ use_codex: "true"
2531
+ codex_only: "false"
2532
2532
  llm_failure_policy: block
2533
2533
  - name: Enforce Omar reviewer merge thresholds
2534
2534
  shell: bash
@@ -2612,7 +2612,7 @@ jobs:
2612
2612
  "scan": {
2613
2613
  "mode": env("OMAR_SCAN_MODE", "deep"),
2614
2614
  "action_ref": "mrrCarter/sentinelayer-v1-action@03d7369cba7de2e9f15b959275c982111f0ee493",
2615
- "managed_llm": False,
2615
+ "managed_llm": True,
2616
2616
  "llm_failure_policy": "block",
2617
2617
  },
2618
2618
  "github": {
@@ -293,11 +293,13 @@ export async function buildCliCommandMcpTools({
293
293
  generatedAt = "1970-01-01T00:00:00.000Z",
294
294
  program = null,
295
295
  includeHidden = false,
296
+ includeSensitive = false,
296
297
  } = {}) {
297
298
  const registry = await buildRegistryTemplateFn({
298
299
  generatedAt,
299
300
  program: program || (buildProgramFn ? await buildProgramFn() : null),
300
301
  includeHidden,
302
+ includeSensitive,
301
303
  });
302
304
  return (Array.isArray(registry?.tools) ? registry.tools : [])
303
305
  .map(normalizeRegistryTool)
@@ -1,231 +1,246 @@
1
- import { buildCliProgram } from "../cli.js";
2
- import { MCP_TOOL_REGISTRY_SCHEMA_VERSION } from "./registry.js";
3
-
4
- const CLI_TOOL_PREFIX = "sl";
5
- const CLI_TOOL_TIMEOUT_MS = 60_000;
6
- const CLI_TOOL_MAX_CALLS_PER_RUN = 5;
7
-
8
- function normalizeDescription(value, fallback) {
9
- const normalized = String(value || "").trim();
10
- return normalized || fallback;
11
- }
12
-
13
- function toTitle(value) {
14
- return String(value || "")
15
- .split(/[\s_.:-]+/g)
16
- .filter(Boolean)
17
- .map((part) => part.slice(0, 1).toUpperCase() + part.slice(1))
18
- .join(" ");
19
- }
20
-
21
- function optionPropertyName(option) {
22
- if (typeof option.attributeName === "function") {
23
- return option.attributeName();
24
- }
25
- const long = String(option.long || option.flags || "")
26
- .split(/[ ,|]+/g)
27
- .find((part) => part.startsWith("--"));
28
- return String(long || "")
29
- .replace(/^--no-/, "")
30
- .replace(/^--/, "")
31
- .replace(/-([a-zA-Z0-9])/g, (_, char) => char.toUpperCase());
32
- }
33
-
34
- function schemaForArgument(argument) {
35
- const description = normalizeDescription(argument.description, "");
36
- if (argument.variadic) {
37
- return {
38
- type: "array",
39
- items: { type: "string" },
40
- ...(description ? { description } : {}),
41
- };
42
- }
43
- return {
44
- type: "string",
45
- ...(description ? { description } : {}),
46
- };
47
- }
48
-
49
- function optionTakesValue(option) {
50
- return Boolean(option.required || option.optional || option.variadic);
51
- }
52
-
53
- function schemaForOption(option) {
54
- const description = normalizeDescription(option.description, "");
55
- let schema;
56
- if (option.variadic) {
57
- schema = {
58
- type: "array",
59
- items: { type: "string" },
60
- };
61
- } else if (optionTakesValue(option)) {
62
- schema = { type: "string" };
63
- } else {
64
- schema = { type: "boolean" };
65
- }
66
-
67
- if (description) {
68
- schema.description = description;
69
- }
70
- if (
71
- option.defaultValue !== undefined &&
72
- ["string", "number", "boolean"].includes(typeof option.defaultValue)
73
- ) {
74
- schema.default = option.defaultValue;
75
- }
76
- return schema;
77
- }
78
-
79
- function summarizeArgument(argument) {
80
- return {
81
- name: argument.name(),
82
- required: Boolean(argument.required),
83
- variadic: Boolean(argument.variadic),
84
- };
85
- }
86
-
87
- function summarizeOption(option) {
88
- return {
89
- name: optionPropertyName(option),
90
- flags: option.flags,
91
- required: Boolean(option.mandatory),
92
- takes_value: optionTakesValue(option),
93
- variadic: Boolean(option.variadic),
94
- negated: Boolean(option.negate),
95
- };
96
- }
97
-
98
- function isHiddenCommand(command) {
99
- return Boolean(command.hidden);
100
- }
101
-
102
- function commandPath(command) {
103
- const names = [];
104
- let current = command;
105
- while (current && current.parent) {
106
- names.unshift(current.name());
107
- current = current.parent;
108
- }
109
- return names;
110
- }
111
-
112
- function collectLeafCommands(command, { includeHidden = false } = {}) {
113
- const leaves = [];
114
- for (const child of command.commands || []) {
115
- if (!includeHidden && isHiddenCommand(child)) {
116
- continue;
117
- }
118
- if (child.commands && child.commands.length > 0) {
119
- leaves.push(...collectLeafCommands(child, { includeHidden }));
120
- continue;
121
- }
122
- leaves.push(child);
123
- }
124
- return leaves;
125
- }
126
-
127
- function buildToolInputSchema(command) {
128
- const properties = {};
129
- const required = [];
130
-
131
- for (const argument of command.registeredArguments || []) {
132
- const name = argument.name();
133
- properties[name] = schemaForArgument(argument);
134
- if (argument.required) {
135
- required.push(name);
136
- }
137
- }
138
-
139
- for (const option of command.options || []) {
140
- const name = optionPropertyName(option);
141
- if (!name || properties[name]) {
142
- continue;
143
- }
144
- properties[name] = schemaForOption(option);
145
- if (option.mandatory) {
146
- required.push(name);
147
- }
148
- }
149
-
150
- return {
151
- type: "object",
152
- additionalProperties: false,
153
- ...(required.length > 0 ? { required } : {}),
154
- properties,
155
- };
156
- }
157
-
158
- function buildCliTool(command) {
159
- const pathParts = commandPath(command);
160
- const commandName = pathParts.join(" ");
161
- const dottedName = pathParts.join(".");
162
- const toolName = `${CLI_TOOL_PREFIX}.${dottedName}`;
163
- return {
164
- name: toolName,
165
- title: toTitle(toolName),
166
- description: normalizeDescription(
167
- command.description(),
168
- `Run sentinelayer-cli ${commandName}.`
169
- ),
170
- input_schema: buildToolInputSchema(command),
171
- transport: {
172
- type: "bridge",
173
- method: "POST",
174
- url: `sentinelayer://cli/${dottedName}`,
175
- timeout_ms: CLI_TOOL_TIMEOUT_MS,
176
- auth: { mode: "none" },
177
- },
178
- budgets: {
179
- max_calls_per_run: CLI_TOOL_MAX_CALLS_PER_RUN,
180
- max_runtime_ms: CLI_TOOL_TIMEOUT_MS,
181
- },
182
- security: {
183
- requires_human_approval: true,
184
- kill_switch: "enabled",
185
- scopes: ["cli:execute"],
186
- },
187
- metadata: {
188
- provider: "sentinelayer",
189
- adapter: "sentinelayer-cli",
190
- command: commandName,
191
- argv: pathParts,
192
- generated_from: "commander",
193
- execution: "bridge",
194
- arguments: (command.registeredArguments || []).map(summarizeArgument),
195
- options: (command.options || []).map(summarizeOption),
196
- },
197
- };
198
- }
199
-
200
- /**
201
- * Build an MCP tool registry for the installed SentinelLayer CLI command tree.
202
- *
203
- * The generated registry is intentionally execution-policy-first: every tool is
204
- * marked as requiring human approval because this is the full CLI surface.
205
- *
206
- * @param {{ generatedAt?: string, program?: import("commander").Command, includeHidden?: boolean }} [options]
207
- * @returns {Promise<Record<string, any>>}
208
- */
209
- export async function buildSentinelayerCliRegistryTemplate({
210
- generatedAt = new Date().toISOString(),
211
- program = null,
212
- includeHidden = false,
213
- } = {}) {
214
- const cliProgram =
215
- program ||
216
- (await buildCliProgram({
217
- invokeLegacy: async () => {},
218
- }));
219
- const tools = collectLeafCommands(cliProgram, { includeHidden }).map(buildCliTool);
220
- return {
221
- version: MCP_TOOL_REGISTRY_SCHEMA_VERSION,
222
- generated_at: generatedAt,
223
- tools,
224
- };
225
- }
226
-
227
- export const __cliRegistryInternals = Object.freeze({
228
- collectLeafCommands,
229
- buildToolInputSchema,
230
- optionPropertyName,
231
- });
1
+ import { buildCliProgram } from "../cli.js";
2
+ import { MCP_TOOL_REGISTRY_SCHEMA_VERSION } from "./registry.js";
3
+
4
+ const CLI_TOOL_PREFIX = "sl";
5
+ const CLI_TOOL_TIMEOUT_MS = 60_000;
6
+ const CLI_TOOL_MAX_CALLS_PER_RUN = 5;
7
+
8
+ function normalizeDescription(value, fallback) {
9
+ const normalized = String(value || "").trim();
10
+ return normalized || fallback;
11
+ }
12
+
13
+ function toTitle(value) {
14
+ return String(value || "")
15
+ .split(/[\s_.:-]+/g)
16
+ .filter(Boolean)
17
+ .map((part) => part.slice(0, 1).toUpperCase() + part.slice(1))
18
+ .join(" ");
19
+ }
20
+
21
+ function optionPropertyName(option) {
22
+ if (typeof option.attributeName === "function") {
23
+ return option.attributeName();
24
+ }
25
+ const long = String(option.long || option.flags || "")
26
+ .split(/[ ,|]+/g)
27
+ .find((part) => part.startsWith("--"));
28
+ return String(long || "")
29
+ .replace(/^--no-/, "")
30
+ .replace(/^--/, "")
31
+ .replace(/-([a-zA-Z0-9])/g, (_, char) => char.toUpperCase());
32
+ }
33
+
34
+ function schemaForArgument(argument) {
35
+ const description = normalizeDescription(argument.description, "");
36
+ if (argument.variadic) {
37
+ return {
38
+ type: "array",
39
+ items: { type: "string" },
40
+ ...(description ? { description } : {}),
41
+ };
42
+ }
43
+ return {
44
+ type: "string",
45
+ ...(description ? { description } : {}),
46
+ };
47
+ }
48
+
49
+ function optionTakesValue(option) {
50
+ return Boolean(option.required || option.optional || option.variadic);
51
+ }
52
+
53
+ function schemaForOption(option) {
54
+ const description = normalizeDescription(option.description, "");
55
+ let schema;
56
+ if (option.variadic) {
57
+ schema = {
58
+ type: "array",
59
+ items: { type: "string" },
60
+ };
61
+ } else if (optionTakesValue(option)) {
62
+ schema = { type: "string" };
63
+ } else {
64
+ schema = { type: "boolean" };
65
+ }
66
+
67
+ if (description) {
68
+ schema.description = description;
69
+ }
70
+ if (
71
+ option.defaultValue !== undefined &&
72
+ ["string", "number", "boolean"].includes(typeof option.defaultValue)
73
+ ) {
74
+ schema.default = option.defaultValue;
75
+ }
76
+ return schema;
77
+ }
78
+
79
+ function summarizeArgument(argument) {
80
+ return {
81
+ name: argument.name(),
82
+ required: Boolean(argument.required),
83
+ variadic: Boolean(argument.variadic),
84
+ };
85
+ }
86
+
87
+ function summarizeOption(option) {
88
+ return {
89
+ name: optionPropertyName(option),
90
+ flags: option.flags,
91
+ required: Boolean(option.mandatory),
92
+ takes_value: optionTakesValue(option),
93
+ variadic: Boolean(option.variadic),
94
+ negated: Boolean(option.negate),
95
+ };
96
+ }
97
+
98
+ function isHiddenCommand(command) {
99
+ return Boolean(command.hidden);
100
+ }
101
+
102
+ function commandPath(command) {
103
+ const names = [];
104
+ let current = command;
105
+ while (current && current.parent) {
106
+ names.unshift(current.name());
107
+ current = current.parent;
108
+ }
109
+ return names;
110
+ }
111
+
112
+ // Command groups excluded from the generated MCP bridge tool set: they read or mutate
113
+ // credentials / config secrets and must not be remotely tool-callable without dedicated
114
+ // server-side auth. Defense-in-depth beyond per-command output redaction. Opt back in with
115
+ // includeSensitive for local/trusted contexts.
116
+ const SENSITIVE_COMMAND_GROUPS = new Set(["config", "auth"]);
117
+
118
+ function isSensitiveCommand(command) {
119
+ const parts = commandPath(command);
120
+ return parts.length > 0 && SENSITIVE_COMMAND_GROUPS.has(parts[0]);
121
+ }
122
+
123
+ function collectLeafCommands(command, { includeHidden = false, includeSensitive = false } = {}) {
124
+ const leaves = [];
125
+ for (const child of command.commands || []) {
126
+ if (!includeHidden && isHiddenCommand(child)) {
127
+ continue;
128
+ }
129
+ if (!includeSensitive && isSensitiveCommand(child)) {
130
+ continue;
131
+ }
132
+ if (child.commands && child.commands.length > 0) {
133
+ leaves.push(...collectLeafCommands(child, { includeHidden, includeSensitive }));
134
+ continue;
135
+ }
136
+ leaves.push(child);
137
+ }
138
+ return leaves;
139
+ }
140
+
141
+ function buildToolInputSchema(command) {
142
+ const properties = {};
143
+ const required = [];
144
+
145
+ for (const argument of command.registeredArguments || []) {
146
+ const name = argument.name();
147
+ properties[name] = schemaForArgument(argument);
148
+ if (argument.required) {
149
+ required.push(name);
150
+ }
151
+ }
152
+
153
+ for (const option of command.options || []) {
154
+ const name = optionPropertyName(option);
155
+ if (!name || properties[name]) {
156
+ continue;
157
+ }
158
+ properties[name] = schemaForOption(option);
159
+ if (option.mandatory) {
160
+ required.push(name);
161
+ }
162
+ }
163
+
164
+ return {
165
+ type: "object",
166
+ additionalProperties: false,
167
+ ...(required.length > 0 ? { required } : {}),
168
+ properties,
169
+ };
170
+ }
171
+
172
+ function buildCliTool(command) {
173
+ const pathParts = commandPath(command);
174
+ const commandName = pathParts.join(" ");
175
+ const dottedName = pathParts.join(".");
176
+ const toolName = `${CLI_TOOL_PREFIX}.${dottedName}`;
177
+ return {
178
+ name: toolName,
179
+ title: toTitle(toolName),
180
+ description: normalizeDescription(
181
+ command.description(),
182
+ `Run sentinelayer-cli ${commandName}.`
183
+ ),
184
+ input_schema: buildToolInputSchema(command),
185
+ transport: {
186
+ type: "bridge",
187
+ method: "POST",
188
+ url: `sentinelayer://cli/${dottedName}`,
189
+ timeout_ms: CLI_TOOL_TIMEOUT_MS,
190
+ auth: { mode: "none" },
191
+ },
192
+ budgets: {
193
+ max_calls_per_run: CLI_TOOL_MAX_CALLS_PER_RUN,
194
+ max_runtime_ms: CLI_TOOL_TIMEOUT_MS,
195
+ },
196
+ security: {
197
+ requires_human_approval: true,
198
+ kill_switch: "enabled",
199
+ scopes: ["cli:execute"],
200
+ },
201
+ metadata: {
202
+ provider: "sentinelayer",
203
+ adapter: "sentinelayer-cli",
204
+ command: commandName,
205
+ argv: pathParts,
206
+ generated_from: "commander",
207
+ execution: "bridge",
208
+ arguments: (command.registeredArguments || []).map(summarizeArgument),
209
+ options: (command.options || []).map(summarizeOption),
210
+ },
211
+ };
212
+ }
213
+
214
+ /**
215
+ * Build an MCP tool registry for the installed SentinelLayer CLI command tree.
216
+ *
217
+ * The generated registry is intentionally execution-policy-first: every tool is
218
+ * marked as requiring human approval because this is the full CLI surface.
219
+ *
220
+ * @param {{ generatedAt?: string, program?: import("commander").Command, includeHidden?: boolean, includeSensitive?: boolean }} [options]
221
+ * @returns {Promise<Record<string, any>>}
222
+ */
223
+ export async function buildSentinelayerCliRegistryTemplate({
224
+ generatedAt = new Date().toISOString(),
225
+ program = null,
226
+ includeHidden = false,
227
+ includeSensitive = false,
228
+ } = {}) {
229
+ const cliProgram =
230
+ program ||
231
+ (await buildCliProgram({
232
+ invokeLegacy: async () => {},
233
+ }));
234
+ const tools = collectLeafCommands(cliProgram, { includeHidden, includeSensitive }).map(buildCliTool);
235
+ return {
236
+ version: MCP_TOOL_REGISTRY_SCHEMA_VERSION,
237
+ generated_at: generatedAt,
238
+ tools,
239
+ };
240
+ }
241
+
242
+ export const __cliRegistryInternals = Object.freeze({
243
+ collectLeafCommands,
244
+ buildToolInputSchema,
245
+ optionPropertyName,
246
+ });
@@ -227,14 +227,14 @@ export function buildSecurityReviewWorkflow({ secretName = DEFAULT_SCAN_SECRET_N
227
227
  with: {
228
228
  github_token: "${{ github.token }}",
229
229
  sentinelayer_token: `\${{ secrets.${normalizedSecret} }}`,
230
- sentinelayer_managed_llm: "false",
231
- openai_api_key: "${{ secrets.OPENAI_API_KEY }}",
232
- google_api_key: "${{ secrets.GOOGLE_API_KEY }}",
230
+ sentinelayer_managed_llm: "true",
233
231
  scan_mode: profile.scanMode || "deep",
234
232
  severity_gate: profile.severityGate || "P1",
235
233
  model: "gpt-5.3-codex",
236
234
  codex_model: "gpt-5.3-codex",
237
- model_fallback: "gemini-2.5-flash",
235
+ model_fallback: "gpt-4.1-mini",
236
+ use_codex: "true",
237
+ codex_only: "false",
238
238
  llm_failure_policy: "block",
239
239
  playwright_mode: profile.playwrightMode || "off",
240
240
  sbom_mode: profile.sbomMode || "off",
@@ -326,7 +326,7 @@ export function buildSecurityReviewWorkflow({ secretName = DEFAULT_SCAN_SECRET_N
326
326
  " 'scan': {",
327
327
  " 'mode': env('OMAR_SCAN_MODE', 'deep'),",
328
328
  ` 'action_ref': '${SENTINELAYER_ACTION_REF}',`,
329
- " 'managed_llm': False,",
329
+ " 'managed_llm': True,",
330
330
  " 'llm_failure_policy': 'block',",
331
331
  " },",
332
332
  " 'github': {",