sentinelayer-cli 0.4.5 → 0.8.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 +16 -18
- package/package.json +7 -6
- package/src/agents/jules/config/definition.js +13 -62
- package/src/agents/jules/config/system-prompt.js +8 -1
- package/src/agents/jules/fix-cycle.js +12 -372
- package/src/agents/jules/loop.js +116 -26
- package/src/agents/jules/pulse.js +10 -327
- package/src/agents/jules/stream.js +13 -12
- package/src/agents/jules/swarm/orchestrator.js +3 -3
- package/src/agents/jules/swarm/sub-agent.js +6 -3
- package/src/agents/jules/tools/aidenid-email.js +189 -0
- package/src/agents/jules/tools/auth-audit.js +1187 -45
- package/src/agents/jules/tools/dispatch.js +25 -12
- package/src/agents/jules/tools/file-edit.js +2 -180
- package/src/agents/jules/tools/file-read.js +2 -100
- package/src/agents/jules/tools/glob.js +2 -168
- package/src/agents/jules/tools/grep.js +2 -228
- package/src/agents/jules/tools/path-guards.js +2 -161
- package/src/agents/jules/tools/runtime-audit.js +6 -2
- package/src/agents/jules/tools/shell.js +2 -383
- package/src/agents/persona-visuals.js +64 -0
- package/src/agents/shared-tools/dispatch-core.js +320 -0
- package/src/agents/shared-tools/file-edit.js +180 -0
- package/src/agents/shared-tools/file-read.js +100 -0
- package/src/agents/shared-tools/glob.js +168 -0
- package/src/agents/shared-tools/grep.js +228 -0
- package/src/agents/shared-tools/index.js +46 -0
- package/src/agents/shared-tools/path-guards.js +161 -0
- package/src/agents/shared-tools/shell.js +383 -0
- package/src/ai/aidenid.js +56 -7
- package/src/ai/client.js +45 -0
- package/src/ai/proxy.js +137 -0
- package/src/auth/gate.js +290 -16
- package/src/auth/http.js +450 -39
- package/src/auth/service.js +262 -47
- package/src/auth/session-store.js +475 -21
- package/src/cli.js +5 -0
- package/src/commands/audit.js +13 -8
- package/src/commands/auth.js +53 -9
- package/src/commands/omargate.js +10 -2
- package/src/commands/scan.js +10 -4
- package/src/commands/session.js +590 -0
- package/src/commands/spec.js +62 -0
- package/src/commands/watch.js +3 -2
- package/src/daemon/assignment-ledger.js +196 -0
- package/src/daemon/error-worker.js +599 -16
- package/src/daemon/fix-cycle.js +384 -0
- package/src/daemon/ingest-refresh.js +10 -9
- package/src/daemon/jira-lifecycle.js +135 -0
- package/src/daemon/pulse.js +327 -0
- package/src/daemon/scope-engine.js +1068 -0
- package/src/events/schema.js +190 -0
- package/src/interactive/index.js +18 -16
- package/src/legacy-cli.js +606 -37
- package/src/prompt/generator.js +19 -1
- package/src/review/ai-review.js +11 -1
- package/src/review/local-review.js +75 -19
- package/src/review/omargate-interactive.js +68 -0
- package/src/review/omargate-orchestrator.js +404 -0
- package/src/review/persona-prompts.js +296 -0
- package/src/review/scan-modes.js +48 -0
- package/src/scan/generator.js +1 -1
- package/src/session/agent-registry.js +352 -0
- package/src/session/daemon.js +801 -0
- package/src/session/paths.js +33 -0
- package/src/session/runtime-bridge.js +739 -0
- package/src/session/store.js +388 -0
- package/src/session/stream.js +325 -0
- package/src/spec/generator.js +100 -0
- package/src/telemetry/session-tracker.js +148 -32
- package/src/telemetry/sync.js +6 -2
- package/src/ui/command-hints.js +13 -0
package/src/spec/generator.js
CHANGED
|
@@ -439,12 +439,102 @@ function deriveGlobalAcceptanceCriteria(projectType) {
|
|
|
439
439
|
];
|
|
440
440
|
}
|
|
441
441
|
|
|
442
|
+
function countAgentInstructions(agentsMarkdown) {
|
|
443
|
+
const markdown = String(agentsMarkdown || "");
|
|
444
|
+
if (!markdown) {
|
|
445
|
+
return 0;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
const lines = markdown.split(/\r?\n/);
|
|
449
|
+
let inAgentsSection = false;
|
|
450
|
+
let sectionCount = 0;
|
|
451
|
+
let globalCount = 0;
|
|
452
|
+
for (const rawLine of lines) {
|
|
453
|
+
const line = String(rawLine || "");
|
|
454
|
+
const trimmed = line.trim();
|
|
455
|
+
if (!trimmed) {
|
|
456
|
+
continue;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
if (/^#{1,6}\s+.*\bagents?\b/i.test(trimmed)) {
|
|
460
|
+
inAgentsSection = true;
|
|
461
|
+
continue;
|
|
462
|
+
}
|
|
463
|
+
if (inAgentsSection && /^#{1,6}\s+/.test(trimmed)) {
|
|
464
|
+
inAgentsSection = false;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
if (!/^\s*[-*]\s+/.test(line)) {
|
|
468
|
+
continue;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
if (/\b(agent|coder|reviewer|tester|observer|persona|daemon)\b/i.test(trimmed)) {
|
|
472
|
+
globalCount += 1;
|
|
473
|
+
if (inAgentsSection) {
|
|
474
|
+
sectionCount += 1;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
if (sectionCount >= 2) {
|
|
480
|
+
return sectionCount;
|
|
481
|
+
}
|
|
482
|
+
return globalCount;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function hasCollaborationSignals(description) {
|
|
486
|
+
const normalized = String(description || "").toLowerCase();
|
|
487
|
+
if (!normalized) {
|
|
488
|
+
return false;
|
|
489
|
+
}
|
|
490
|
+
return /\b(team|pair|paired|multi-agent|multi agent|swarm|collaborat)\b/.test(normalized);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
function shouldIncludeCoordinationPhase({
|
|
494
|
+
description = "",
|
|
495
|
+
agentsMarkdown = "",
|
|
496
|
+
sessionActive = false,
|
|
497
|
+
} = {}) {
|
|
498
|
+
if (sessionActive === true) {
|
|
499
|
+
return true;
|
|
500
|
+
}
|
|
501
|
+
if (hasCollaborationSignals(description)) {
|
|
502
|
+
return true;
|
|
503
|
+
}
|
|
504
|
+
return countAgentInstructions(agentsMarkdown) >= 2;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
function buildCoordinationPhase(phaseNumber, previousPhaseTitle = "") {
|
|
508
|
+
return {
|
|
509
|
+
title: `Phase ${phaseNumber}: Multi-Agent Coordination Protocol`,
|
|
510
|
+
items: [
|
|
511
|
+
"Check for active sessions: `sl session list`.",
|
|
512
|
+
"If a session exists, join it: `sl session join <id> --name <your-name> --role coder`.",
|
|
513
|
+
"Emit status updates every 5 minutes: `sl session say <id> \"status: <what you're doing>\"`.",
|
|
514
|
+
"Before modifying a shared file, check recent session activity for that file.",
|
|
515
|
+
"On unexpected file changes, ask in-session instead of stopping: `sl session say <id> \"help: <question>\"`.",
|
|
516
|
+
"Post findings in-session: `sl session say <id> \"finding: [P2] <title> in <file>:<line>\"`.",
|
|
517
|
+
"On completion, update `tasks/todo.md` and emit completion status in-session.",
|
|
518
|
+
"Leave the session when done: `sl session leave <id>`.",
|
|
519
|
+
],
|
|
520
|
+
dependencies: previousPhaseTitle ? [previousPhaseTitle] : [],
|
|
521
|
+
effort: "4-8 hours",
|
|
522
|
+
acceptanceCriteria: [
|
|
523
|
+
"Session participation path is explicit for all collaborating agents.",
|
|
524
|
+
"Unexpected file-change handling favors in-session coordination over stop-and-wait behavior.",
|
|
525
|
+
"Status and finding updates are emitted with actionable file-level context.",
|
|
526
|
+
],
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
|
|
442
530
|
export function generateSpecMarkdown({
|
|
443
531
|
template,
|
|
444
532
|
description,
|
|
445
533
|
ingest,
|
|
446
534
|
projectPath,
|
|
447
535
|
projectType,
|
|
536
|
+
agentsMarkdown = "",
|
|
537
|
+
sessionActive = false,
|
|
448
538
|
generatedAt = new Date().toISOString(),
|
|
449
539
|
} = {}) {
|
|
450
540
|
const resolvedTemplate = template || getDefaultTemplate();
|
|
@@ -471,6 +561,16 @@ export function generateSpecMarkdown({
|
|
|
471
561
|
description,
|
|
472
562
|
});
|
|
473
563
|
|
|
564
|
+
if (
|
|
565
|
+
shouldIncludeCoordinationPhase({
|
|
566
|
+
description,
|
|
567
|
+
agentsMarkdown,
|
|
568
|
+
sessionActive,
|
|
569
|
+
})
|
|
570
|
+
) {
|
|
571
|
+
phases.push(buildCoordinationPhase(phases.length + 1, phases[phases.length - 1]?.title || ""));
|
|
572
|
+
}
|
|
573
|
+
|
|
474
574
|
const phaseMarkdown = phases
|
|
475
575
|
.map(
|
|
476
576
|
(phase) =>
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import pc from "picocolors";
|
|
2
|
+
import crypto from "node:crypto";
|
|
3
|
+
import process from "node:process";
|
|
2
4
|
|
|
3
5
|
/**
|
|
4
6
|
* Session Tracker — tracks tokens, tool calls, cost, and time per CLI run.
|
|
@@ -10,14 +12,100 @@ import pc from "picocolors";
|
|
|
10
12
|
* - Print summary on completion
|
|
11
13
|
*/
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
const SESSIONS = new Map();
|
|
16
|
+
let ACTIVE_SESSION_ID = null;
|
|
17
|
+
const MAX_SESSIONS = 50;
|
|
18
|
+
const SESSION_TTL_MS = 60 * 60 * 1000;
|
|
19
|
+
const VERBOSE_TELEMETRY_ENV = "SENTINELAYER_VERBOSE_TELEMETRY";
|
|
20
|
+
const DEBUG_ERRORS_ENV = "SENTINELAYER_DEBUG_ERRORS";
|
|
21
|
+
const UNMASK_TRACE_ID_ENV = "SENTINELAYER_UNMASK_TRACE_ID";
|
|
22
|
+
const EMIT_TRACE_ID_ENV = "SENTINELAYER_EMIT_TRACE_ID";
|
|
23
|
+
|
|
24
|
+
function isTruthyEnvFlag(value) {
|
|
25
|
+
const normalized = String(value || "").trim().toLowerCase();
|
|
26
|
+
return normalized === "true" || normalized === "1" || normalized === "yes";
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function normalizeNonNegativeNumber(value) {
|
|
30
|
+
const normalized = Number(value);
|
|
31
|
+
if (!Number.isFinite(normalized) || normalized < 0) {
|
|
32
|
+
return 0;
|
|
33
|
+
}
|
|
34
|
+
return normalized;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function resolveSession(sessionId) {
|
|
38
|
+
const resolvedId = String(sessionId || ACTIVE_SESSION_ID || "").trim();
|
|
39
|
+
if (!resolvedId) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
return SESSIONS.get(resolvedId) || null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function pruneSessions(now = Date.now()) {
|
|
46
|
+
for (const [sessionId, session] of SESSIONS.entries()) {
|
|
47
|
+
if (!session || !session.startedAt) continue;
|
|
48
|
+
if (now - session.startedAt > SESSION_TTL_MS) {
|
|
49
|
+
SESSIONS.delete(sessionId);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
while (SESSIONS.size > MAX_SESSIONS) {
|
|
53
|
+
const oldestKey = SESSIONS.keys().next().value;
|
|
54
|
+
if (!oldestKey) break;
|
|
55
|
+
SESSIONS.delete(oldestKey);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function shouldExposeTraceId() {
|
|
60
|
+
const verbose = isTruthyEnvFlag(process.env[VERBOSE_TELEMETRY_ENV]);
|
|
61
|
+
const debug = isTruthyEnvFlag(process.env[DEBUG_ERRORS_ENV]);
|
|
62
|
+
if (!verbose && !debug) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
if (!isTruthyEnvFlag(process.env[UNMASK_TRACE_ID_ENV])) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
const nodeEnv = String(process.env.NODE_ENV || "").trim().toLowerCase();
|
|
69
|
+
if (nodeEnv !== "development" && nodeEnv !== "test") {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
return Boolean(process.stderr && process.stderr.isTTY);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function shouldEmitTraceId() {
|
|
76
|
+
if (!isTruthyEnvFlag(process.env[EMIT_TRACE_ID_ENV])) {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
return Boolean(process.stderr && process.stderr.isTTY);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function maskTraceId(traceId) {
|
|
83
|
+
const normalized = String(traceId || "").trim();
|
|
84
|
+
if (normalized.length <= 8) {
|
|
85
|
+
return "trace_id=****";
|
|
86
|
+
}
|
|
87
|
+
const prefix = normalized.slice(0, 4);
|
|
88
|
+
const suffix = normalized.slice(-4);
|
|
89
|
+
return `trace_id=${prefix}…${suffix}`;
|
|
90
|
+
}
|
|
14
91
|
|
|
15
92
|
/**
|
|
16
93
|
* Initialize a new tracking session.
|
|
17
94
|
* Call this at the start of any auditable command.
|
|
18
95
|
*/
|
|
19
96
|
export function startSession(command) {
|
|
20
|
-
|
|
97
|
+
pruneSessions();
|
|
98
|
+
let sessionId;
|
|
99
|
+
try {
|
|
100
|
+
sessionId = crypto.randomUUID();
|
|
101
|
+
} catch {
|
|
102
|
+
const ts = Date.now().toString(36);
|
|
103
|
+
const rand = crypto.randomBytes(16).toString("hex");
|
|
104
|
+
sessionId = `sess-${ts}-${rand}`;
|
|
105
|
+
}
|
|
106
|
+
const session = {
|
|
107
|
+
id: sessionId,
|
|
108
|
+
traceId: sessionId,
|
|
21
109
|
command: command || "unknown",
|
|
22
110
|
startedAt: Date.now(),
|
|
23
111
|
inputTokens: 0,
|
|
@@ -27,55 +115,79 @@ export function startSession(command) {
|
|
|
27
115
|
llmCalls: 0,
|
|
28
116
|
findings: { P0: 0, P1: 0, P2: 0, P3: 0 },
|
|
29
117
|
};
|
|
30
|
-
|
|
118
|
+
SESSIONS.set(sessionId, session);
|
|
119
|
+
ACTIVE_SESSION_ID = sessionId;
|
|
120
|
+
return session;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function endSession({ sessionId } = {}) {
|
|
124
|
+
const resolvedId = String(sessionId || ACTIVE_SESSION_ID || "").trim();
|
|
125
|
+
if (!resolvedId) return false;
|
|
126
|
+
const existed = SESSIONS.delete(resolvedId);
|
|
127
|
+
if (ACTIVE_SESSION_ID === resolvedId) {
|
|
128
|
+
ACTIVE_SESSION_ID = null;
|
|
129
|
+
}
|
|
130
|
+
return existed;
|
|
31
131
|
}
|
|
32
132
|
|
|
33
133
|
/**
|
|
34
134
|
* Record token usage from an LLM call.
|
|
35
135
|
*/
|
|
36
|
-
export function recordLlmUsage({ inputTokens = 0, outputTokens = 0, costUsd = 0 } = {}) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
136
|
+
export function recordLlmUsage({ inputTokens = 0, outputTokens = 0, costUsd = 0, sessionId } = {}) {
|
|
137
|
+
const session = resolveSession(sessionId);
|
|
138
|
+
if (!session) return;
|
|
139
|
+
const safeInput = normalizeNonNegativeNumber(inputTokens);
|
|
140
|
+
const safeOutput = normalizeNonNegativeNumber(outputTokens);
|
|
141
|
+
const safeCost = normalizeNonNegativeNumber(costUsd);
|
|
142
|
+
session.inputTokens += safeInput;
|
|
143
|
+
session.outputTokens += safeOutput;
|
|
144
|
+
session.costUsd += safeCost;
|
|
145
|
+
session.llmCalls += 1;
|
|
42
146
|
}
|
|
43
147
|
|
|
44
148
|
/**
|
|
45
149
|
* Record a tool call.
|
|
46
150
|
*/
|
|
47
|
-
export function recordToolCall() {
|
|
48
|
-
|
|
49
|
-
|
|
151
|
+
export function recordToolCall({ sessionId } = {}) {
|
|
152
|
+
const session = resolveSession(sessionId);
|
|
153
|
+
if (!session) return;
|
|
154
|
+
session.toolCalls += 1;
|
|
50
155
|
}
|
|
51
156
|
|
|
52
157
|
/**
|
|
53
158
|
* Record findings.
|
|
54
159
|
*/
|
|
55
|
-
export function recordFindings(summary) {
|
|
56
|
-
|
|
57
|
-
if (
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
160
|
+
export function recordFindings(summary, { sessionId } = {}) {
|
|
161
|
+
const session = resolveSession(sessionId);
|
|
162
|
+
if (!session) return;
|
|
163
|
+
const p0 = normalizeNonNegativeNumber(summary?.P0);
|
|
164
|
+
const p1 = normalizeNonNegativeNumber(summary?.P1);
|
|
165
|
+
const p2 = normalizeNonNegativeNumber(summary?.P2);
|
|
166
|
+
const p3 = normalizeNonNegativeNumber(summary?.P3);
|
|
167
|
+
if (p0) session.findings.P0 += p0;
|
|
168
|
+
if (p1) session.findings.P1 += p1;
|
|
169
|
+
if (p2) session.findings.P2 += p2;
|
|
170
|
+
if (p3) session.findings.P3 += p3;
|
|
61
171
|
}
|
|
62
172
|
|
|
63
173
|
/**
|
|
64
174
|
* Get the current session summary.
|
|
65
175
|
*/
|
|
66
|
-
export function getSessionSummary() {
|
|
67
|
-
|
|
68
|
-
|
|
176
|
+
export function getSessionSummary({ sessionId } = {}) {
|
|
177
|
+
const session = resolveSession(sessionId);
|
|
178
|
+
if (!session) return null;
|
|
179
|
+
const durationMs = Date.now() - session.startedAt;
|
|
69
180
|
return {
|
|
70
|
-
|
|
181
|
+
traceId: session.traceId || session.id,
|
|
182
|
+
command: session.command,
|
|
71
183
|
durationMs,
|
|
72
|
-
inputTokens:
|
|
73
|
-
outputTokens:
|
|
74
|
-
totalTokens:
|
|
75
|
-
costUsd:
|
|
76
|
-
toolCalls:
|
|
77
|
-
llmCalls:
|
|
78
|
-
findings: { ...
|
|
184
|
+
inputTokens: session.inputTokens,
|
|
185
|
+
outputTokens: session.outputTokens,
|
|
186
|
+
totalTokens: session.inputTokens + session.outputTokens,
|
|
187
|
+
costUsd: session.costUsd,
|
|
188
|
+
toolCalls: session.toolCalls,
|
|
189
|
+
llmCalls: session.llmCalls,
|
|
190
|
+
findings: { ...session.findings },
|
|
79
191
|
};
|
|
80
192
|
}
|
|
81
193
|
|
|
@@ -83,8 +195,8 @@ export function getSessionSummary() {
|
|
|
83
195
|
* Print the session summary to stderr.
|
|
84
196
|
* Called at the end of any auditable command.
|
|
85
197
|
*/
|
|
86
|
-
export function printSessionSummary() {
|
|
87
|
-
const summary = getSessionSummary();
|
|
198
|
+
export function printSessionSummary({ sessionId } = {}) {
|
|
199
|
+
const summary = getSessionSummary({ sessionId });
|
|
88
200
|
if (!summary) return;
|
|
89
201
|
|
|
90
202
|
const duration = summary.durationMs < 60000
|
|
@@ -101,6 +213,10 @@ export function printSessionSummary() {
|
|
|
101
213
|
parts.push(pc.white(summary.toolCalls + " tools"));
|
|
102
214
|
if (summary.costUsd > 0) parts.push(pc.white("$" + summary.costUsd.toFixed(2)));
|
|
103
215
|
parts.push(pc.white(duration));
|
|
216
|
+
if (summary.traceId && shouldEmitTraceId()) {
|
|
217
|
+
const traceLabel = shouldExposeTraceId() ? `trace_id=${summary.traceId}` : maskTraceId(summary.traceId);
|
|
218
|
+
parts.push(pc.gray(traceLabel));
|
|
219
|
+
}
|
|
104
220
|
|
|
105
221
|
const findingParts = [];
|
|
106
222
|
if (summary.findings.P0 > 0) findingParts.push(pc.red("P0=" + summary.findings.P0));
|
|
@@ -113,6 +229,6 @@ export function printSessionSummary() {
|
|
|
113
229
|
process.stderr.write(" | " + findingParts.join(" "));
|
|
114
230
|
}
|
|
115
231
|
process.stderr.write("\n");
|
|
116
|
-
|
|
232
|
+
endSession({ sessionId });
|
|
117
233
|
return summary;
|
|
118
234
|
}
|
package/src/telemetry/sync.js
CHANGED
|
@@ -3,7 +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 {
|
|
6
|
+
import { resolveActiveAuthSession } from "../auth/service.js";
|
|
7
7
|
|
|
8
8
|
// Read CLI version from package.json at module load
|
|
9
9
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -62,7 +62,11 @@ export async function syncRunToDashboard(runData) {
|
|
|
62
62
|
|
|
63
63
|
let session;
|
|
64
64
|
try {
|
|
65
|
-
session = await
|
|
65
|
+
session = await resolveActiveAuthSession({
|
|
66
|
+
cwd: process.cwd(),
|
|
67
|
+
env: process.env,
|
|
68
|
+
autoRotate: false,
|
|
69
|
+
});
|
|
66
70
|
} catch {
|
|
67
71
|
return { synced: false, reason: "no_session" };
|
|
68
72
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import process from "node:process";
|
|
2
|
+
|
|
3
|
+
export function preferredCliCommand({ platform = process.platform, env = process.env } = {}) {
|
|
4
|
+
const override = String(env?.SENTINELAYER_CLI_COMMAND || "").trim();
|
|
5
|
+
if (override) {
|
|
6
|
+
return override;
|
|
7
|
+
}
|
|
8
|
+
return platform === "win32" ? "sentinelayer-cli" : "sl";
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function authLoginHint({ platform = process.platform, env = process.env } = {}) {
|
|
12
|
+
return `${preferredCliCommand({ platform, env })} auth login`;
|
|
13
|
+
}
|