@aliou/pi-guardrails 0.11.2 → 0.12.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 +72 -167
- package/extensions/guardrails/commands/examples/index.ts +520 -0
- package/extensions/guardrails/commands/onboarding/config.ts +54 -0
- package/{src/commands/onboarding-command.ts → extensions/guardrails/commands/onboarding/index.ts} +5 -31
- package/extensions/guardrails/commands/settings/add-rule-wizard.ts +267 -0
- package/extensions/guardrails/commands/settings/examples.ts +399 -0
- package/extensions/guardrails/commands/settings/index.ts +596 -0
- package/extensions/guardrails/commands/settings/path-list-editor.ts +158 -0
- package/extensions/guardrails/commands/settings/scope-picker-submenu.ts +69 -0
- package/extensions/guardrails/commands/settings/utils.ts +108 -0
- package/extensions/guardrails/components/onboarding-choice-step.ts +140 -0
- package/extensions/guardrails/components/onboarding-finish-step.ts +50 -0
- package/extensions/guardrails/components/onboarding-intro-step.ts +30 -0
- package/extensions/guardrails/components/onboarding-types.ts +10 -0
- package/extensions/guardrails/components/onboarding-wizard.ts +116 -0
- package/{src → extensions/guardrails}/components/pattern-editor.ts +11 -10
- package/extensions/guardrails/index.ts +106 -0
- package/extensions/guardrails/rules.test.ts +107 -0
- package/extensions/guardrails/rules.ts +119 -0
- package/extensions/guardrails/targets.test.ts +44 -0
- package/extensions/guardrails/targets.ts +66 -0
- package/extensions/path-access/grants.test.ts +47 -0
- package/extensions/path-access/grants.ts +68 -0
- package/extensions/path-access/index.ts +143 -0
- package/extensions/path-access/prompt.ts +196 -0
- package/extensions/path-access/rules.test.ts +46 -0
- package/extensions/path-access/rules.ts +37 -0
- package/extensions/path-access/targets.test.ts +40 -0
- package/extensions/path-access/targets.ts +19 -0
- package/extensions/permission-gate/grants.ts +21 -0
- package/extensions/permission-gate/index.ts +122 -0
- package/extensions/permission-gate/prompt.ts +222 -0
- package/extensions/permission-gate/rules.test.ts +132 -0
- package/extensions/permission-gate/rules.ts +72 -0
- package/package.json +18 -20
- package/schema.json +286 -0
- package/src/core/check.test.ts +169 -0
- package/src/core/check.ts +38 -0
- package/src/{hooks/permission-gate/dangerous-commands.test.ts → core/commands/dangerous.test.ts} +134 -2
- package/src/{hooks/permission-gate/dangerous-commands.ts → core/commands/dangerous.ts} +119 -1
- package/src/core/commands/index.ts +15 -0
- package/src/core/index.ts +13 -0
- package/src/{utils/path-access.test.ts → core/paths/access.test.ts} +1 -5
- package/src/core/paths/index.ts +14 -0
- package/src/{utils → core/shell}/command-args.test.ts +31 -20
- package/src/core/shell/index.ts +2 -0
- package/src/core/types.ts +55 -0
- package/src/shared/config/defaults.ts +118 -0
- package/src/shared/config/index.ts +17 -0
- package/src/shared/config/loader.ts +64 -0
- package/src/shared/config/migration/001-v0-format-upgrade.ts +107 -0
- package/src/shared/config/migration/002-strip-toolchain-fields.ts +39 -0
- package/src/shared/config/migration/003-strip-command-explainer-fields.ts +42 -0
- package/src/shared/config/migration/004-env-files-to-policies.ts +87 -0
- package/src/shared/config/migration/005-normalize-allowed-paths.ts +43 -0
- package/src/shared/config/migration/006-apply-builtin-defaults.ts +19 -0
- package/src/shared/config/migration/007-mark-onboarding-done.ts +25 -0
- package/src/shared/config/migration/index.ts +44 -0
- package/src/shared/config/migration/version.ts +7 -0
- package/src/shared/config/types.ts +141 -0
- package/src/shared/events.ts +100 -0
- package/src/shared/index.ts +6 -0
- package/src/shared/matching.test.ts +86 -0
- package/src/{utils → shared}/matching.ts +4 -4
- package/src/{utils → shared/paths}/bash-paths.test.ts +11 -2
- package/src/{utils → shared/paths}/bash-paths.ts +4 -4
- package/src/shared/paths/index.ts +1 -0
- package/src/shared/warnings.ts +17 -0
- package/docs/defaults.md +0 -140
- package/docs/examples.md +0 -170
- package/src/commands/onboarding.ts +0 -390
- package/src/commands/settings-command.ts +0 -1616
- package/src/config.ts +0 -392
- package/src/hooks/index.ts +0 -11
- package/src/hooks/path-access.ts +0 -395
- package/src/hooks/permission-gate/index.test.ts +0 -332
- package/src/hooks/permission-gate/index.ts +0 -595
- package/src/hooks/policies.ts +0 -322
- package/src/index.ts +0 -96
- package/src/lib/executor.ts +0 -280
- package/src/lib/index.ts +0 -16
- package/src/lib/model-resolver.ts +0 -47
- package/src/lib/timing.ts +0 -42
- package/src/lib/types.ts +0 -115
- package/src/utils/events.ts +0 -32
- package/src/utils/migration.test.ts +0 -58
- package/src/utils/migration.ts +0 -340
- package/src/utils/warnings.ts +0 -7
- /package/src/{utils/path-access.ts → core/paths/access.ts} +0 -0
- /package/src/{utils → core/paths}/path.test.ts +0 -0
- /package/src/{utils → core/paths}/path.ts +0 -0
- /package/src/{utils/shell-utils.ts → core/shell/ast.ts} +0 -0
- /package/src/{utils → core/shell}/command-args.ts +0 -0
- /package/src/{utils/glob-expander.ts → shared/glob.ts} +0 -0
|
@@ -1,390 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
getSettingsTheme,
|
|
3
|
-
type SettingsTheme,
|
|
4
|
-
Wizard,
|
|
5
|
-
} from "@aliou/pi-utils-settings";
|
|
6
|
-
import { getMarkdownTheme, type Theme } from "@mariozechner/pi-coding-agent";
|
|
7
|
-
import type { Component } from "@mariozechner/pi-tui";
|
|
8
|
-
import { Box, Key, Markdown, matchesKey, Text } from "@mariozechner/pi-tui";
|
|
9
|
-
import type { GuardrailsConfig } from "../config";
|
|
10
|
-
import { CURRENT_VERSION } from "../utils/migration";
|
|
11
|
-
|
|
12
|
-
interface OnboardingState {
|
|
13
|
-
applyBuiltinDefaults: boolean | null;
|
|
14
|
-
pathAccessEnabled: boolean | null;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export interface OnboardingResult {
|
|
18
|
-
completed: boolean;
|
|
19
|
-
applyBuiltinDefaults: boolean | null;
|
|
20
|
-
pathAccessEnabled: boolean | null;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
class IntroStep implements Component {
|
|
24
|
-
private readonly introText = new Text("", 2, 0);
|
|
25
|
-
|
|
26
|
-
constructor(private readonly onNext: () => void) {}
|
|
27
|
-
|
|
28
|
-
invalidate() {
|
|
29
|
-
this.introText.invalidate();
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
render(width: number): string[] {
|
|
33
|
-
this.introText.setText(
|
|
34
|
-
"Guardrails helps prevent accidental exposure of secrets and risky actions.\n\nIt gives you two protections:\n- Policies: file access rules (`noAccess` or `readOnly`)\n- Permission gate: confirmation before dangerous commands run\n\nYou are choosing the starting defaults now. You can change them later in `/guardrails:settings`.",
|
|
35
|
-
);
|
|
36
|
-
|
|
37
|
-
return [
|
|
38
|
-
" Welcome to Guardrails",
|
|
39
|
-
"",
|
|
40
|
-
...this.introText.render(Math.max(1, width)),
|
|
41
|
-
];
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
handleInput(data: string): void {
|
|
45
|
-
if (matchesKey(data, Key.enter)) {
|
|
46
|
-
this.onNext();
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
class DefaultsChoiceStep implements Component {
|
|
52
|
-
private selectedIndex = 0;
|
|
53
|
-
private readonly settingsTheme: SettingsTheme;
|
|
54
|
-
|
|
55
|
-
constructor(
|
|
56
|
-
private readonly theme: Theme,
|
|
57
|
-
private readonly state: OnboardingState,
|
|
58
|
-
private readonly onSelect: () => void,
|
|
59
|
-
) {
|
|
60
|
-
this.settingsTheme = getSettingsTheme(theme);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
invalidate() {}
|
|
64
|
-
|
|
65
|
-
render(width: number): string[] {
|
|
66
|
-
const options = ["Recommended defaults", "Minimal setup"];
|
|
67
|
-
const explanations = [
|
|
68
|
-
[
|
|
69
|
-
"Use built-ins for common safety needs:",
|
|
70
|
-
"",
|
|
71
|
-
"- Protect secret files like `.env`, `.env.local`, `.env.production`, and `.dev.vars`",
|
|
72
|
-
"- Keep safe exceptions like `.env.example` and `*.sample.env`",
|
|
73
|
-
"- Require confirmation before running dangerous commands like `rm -rf`, `sudo`, and `dd of=`",
|
|
74
|
-
].join("\n"),
|
|
75
|
-
[
|
|
76
|
-
"Start with no built-in file policy defaults.",
|
|
77
|
-
"",
|
|
78
|
-
"- Configure your own policies in `/guardrails:settings`",
|
|
79
|
-
"- Browse policy and command examples in `/guardrails:settings`",
|
|
80
|
-
].join("\n"),
|
|
81
|
-
];
|
|
82
|
-
|
|
83
|
-
const lines: string[] = [
|
|
84
|
-
" Pick how much built-in protection to start with.",
|
|
85
|
-
"",
|
|
86
|
-
];
|
|
87
|
-
|
|
88
|
-
for (let i = 0; i < options.length; i++) {
|
|
89
|
-
const option = options[i];
|
|
90
|
-
if (!option) continue;
|
|
91
|
-
const selected = i === this.selectedIndex;
|
|
92
|
-
const prefix = selected ? this.settingsTheme.cursor : " ";
|
|
93
|
-
const label = this.settingsTheme.value(` ${option}`, selected);
|
|
94
|
-
lines.push(`${prefix}${label}`);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
lines.push("");
|
|
98
|
-
|
|
99
|
-
const explanationBox = new Box(1, 0, (s: string) => s);
|
|
100
|
-
explanationBox.addChild(
|
|
101
|
-
new Markdown(
|
|
102
|
-
explanations[this.selectedIndex] ?? "",
|
|
103
|
-
0,
|
|
104
|
-
0,
|
|
105
|
-
getMarkdownTheme(),
|
|
106
|
-
{
|
|
107
|
-
color: (s: string) => this.theme.fg("text", s),
|
|
108
|
-
},
|
|
109
|
-
),
|
|
110
|
-
);
|
|
111
|
-
|
|
112
|
-
lines.push(...explanationBox.render(Math.max(1, width)));
|
|
113
|
-
|
|
114
|
-
return lines;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
handleInput(data: string): void {
|
|
118
|
-
if (matchesKey(data, Key.up) || data === "k") {
|
|
119
|
-
this.selectedIndex = this.selectedIndex === 0 ? 1 : 0;
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
if (matchesKey(data, Key.down) || data === "j") {
|
|
123
|
-
this.selectedIndex = this.selectedIndex === 1 ? 0 : 1;
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
if (matchesKey(data, Key.enter)) {
|
|
128
|
-
this.state.applyBuiltinDefaults = this.selectedIndex === 0;
|
|
129
|
-
this.onSelect();
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
class PathAccessStep implements Component {
|
|
135
|
-
private selectedIndex = 0;
|
|
136
|
-
private readonly settingsTheme: SettingsTheme;
|
|
137
|
-
|
|
138
|
-
constructor(
|
|
139
|
-
private readonly theme: Theme,
|
|
140
|
-
private readonly state: OnboardingState,
|
|
141
|
-
private readonly onSelect: () => void,
|
|
142
|
-
) {
|
|
143
|
-
this.settingsTheme = getSettingsTheme(theme);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
invalidate() {}
|
|
147
|
-
|
|
148
|
-
render(width: number): string[] {
|
|
149
|
-
const options = ["Ask before accessing outside files", "No restrictions"];
|
|
150
|
-
const explanations = [
|
|
151
|
-
[
|
|
152
|
-
"When enabled, guardrails will prompt you before the agent accesses files outside the current working directory.",
|
|
153
|
-
"",
|
|
154
|
-
"- You can grant access per-file or per-directory",
|
|
155
|
-
"- Grants can be session-only or permanent",
|
|
156
|
-
"- In non-interactive mode, outside access is blocked",
|
|
157
|
-
].join("\n"),
|
|
158
|
-
[
|
|
159
|
-
"The agent can access any path on your system without prompting.",
|
|
160
|
-
"",
|
|
161
|
-
"- You can enable path access later in `/guardrails:settings`",
|
|
162
|
-
].join("\n"),
|
|
163
|
-
];
|
|
164
|
-
|
|
165
|
-
const lines: string[] = [
|
|
166
|
-
" Restrict access to your project directory?",
|
|
167
|
-
"",
|
|
168
|
-
];
|
|
169
|
-
|
|
170
|
-
for (let i = 0; i < options.length; i++) {
|
|
171
|
-
const option = options[i];
|
|
172
|
-
if (!option) continue;
|
|
173
|
-
const selected = i === this.selectedIndex;
|
|
174
|
-
const prefix = selected ? this.settingsTheme.cursor : " ";
|
|
175
|
-
const label = this.settingsTheme.value(` ${option}`, selected);
|
|
176
|
-
lines.push(`${prefix}${label}`);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
lines.push("");
|
|
180
|
-
|
|
181
|
-
const explanationBox = new Box(1, 0, (s: string) => s);
|
|
182
|
-
explanationBox.addChild(
|
|
183
|
-
new Markdown(
|
|
184
|
-
explanations[this.selectedIndex] ?? "",
|
|
185
|
-
0,
|
|
186
|
-
0,
|
|
187
|
-
getMarkdownTheme(),
|
|
188
|
-
{
|
|
189
|
-
color: (s: string) => this.theme.fg("text", s),
|
|
190
|
-
},
|
|
191
|
-
),
|
|
192
|
-
);
|
|
193
|
-
|
|
194
|
-
lines.push(...explanationBox.render(Math.max(1, width)));
|
|
195
|
-
|
|
196
|
-
return lines;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
handleInput(data: string): void {
|
|
200
|
-
if (matchesKey(data, Key.up) || data === "k") {
|
|
201
|
-
this.selectedIndex = this.selectedIndex === 0 ? 1 : 0;
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
204
|
-
if (matchesKey(data, Key.down) || data === "j") {
|
|
205
|
-
this.selectedIndex = this.selectedIndex === 1 ? 0 : 1;
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
if (matchesKey(data, Key.enter)) {
|
|
210
|
-
this.state.pathAccessEnabled = this.selectedIndex === 0;
|
|
211
|
-
this.onSelect();
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
class FinishStep implements Component {
|
|
217
|
-
private readonly recapMarkdown = new Markdown("", 2, 0, getMarkdownTheme());
|
|
218
|
-
|
|
219
|
-
constructor(
|
|
220
|
-
private readonly state: OnboardingState,
|
|
221
|
-
private readonly onFinish: () => void,
|
|
222
|
-
) {}
|
|
223
|
-
|
|
224
|
-
invalidate() {
|
|
225
|
-
this.recapMarkdown.invalidate();
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
render(width: number): string[] {
|
|
229
|
-
const defaultsPart =
|
|
230
|
-
this.state.applyBuiltinDefaults === true
|
|
231
|
-
? [
|
|
232
|
-
"You selected **Recommended defaults**.",
|
|
233
|
-
"",
|
|
234
|
-
"Guardrails will start with built-in protection, including:",
|
|
235
|
-
"- secret files like `.env`, `.env.local`, `.env.production`, `.dev.vars`",
|
|
236
|
-
"- safe exceptions like `.env.example` and `*.sample.env`",
|
|
237
|
-
"- confirmation before running dangerous commands like `rm -rf`, `sudo`, `dd of=`",
|
|
238
|
-
].join("\n")
|
|
239
|
-
: [
|
|
240
|
-
"You selected **Minimal setup**.",
|
|
241
|
-
"",
|
|
242
|
-
"No built-in file policy defaults will be applied.",
|
|
243
|
-
"",
|
|
244
|
-
"You can configure policies later with `/guardrails:settings`.",
|
|
245
|
-
].join("\n");
|
|
246
|
-
|
|
247
|
-
const pathAccessPart = this.state.pathAccessEnabled
|
|
248
|
-
? "\n\n**Path access**: enabled (ask mode). The agent will prompt before accessing files outside the working directory."
|
|
249
|
-
: "\n\n**Path access**: disabled. No path restrictions.";
|
|
250
|
-
|
|
251
|
-
const content = defaultsPart + pathAccessPart;
|
|
252
|
-
|
|
253
|
-
this.recapMarkdown.setText(content);
|
|
254
|
-
return [...this.recapMarkdown.render(Math.max(1, width)), ""];
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
handleInput(data: string): void {
|
|
258
|
-
if (matchesKey(data, Key.enter)) {
|
|
259
|
-
this.onFinish();
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
export function createOnboardingWizard(
|
|
265
|
-
theme: Theme,
|
|
266
|
-
done: (result: OnboardingResult) => void,
|
|
267
|
-
): Component {
|
|
268
|
-
const state: OnboardingState = {
|
|
269
|
-
applyBuiltinDefaults: null,
|
|
270
|
-
pathAccessEnabled: null,
|
|
271
|
-
};
|
|
272
|
-
|
|
273
|
-
let markWelcomeComplete: (() => void) | null = null;
|
|
274
|
-
let settled = false;
|
|
275
|
-
|
|
276
|
-
const finalize = (result: OnboardingResult) => {
|
|
277
|
-
if (settled) return;
|
|
278
|
-
settled = true;
|
|
279
|
-
done(result);
|
|
280
|
-
};
|
|
281
|
-
|
|
282
|
-
const wizard = new Wizard({
|
|
283
|
-
title: "Guardrails onboarding",
|
|
284
|
-
theme,
|
|
285
|
-
steps: [
|
|
286
|
-
{
|
|
287
|
-
label: "Welcome",
|
|
288
|
-
build: (ctx) => {
|
|
289
|
-
markWelcomeComplete = ctx.markComplete;
|
|
290
|
-
return new IntroStep(() => {
|
|
291
|
-
ctx.markComplete();
|
|
292
|
-
ctx.goNext();
|
|
293
|
-
});
|
|
294
|
-
},
|
|
295
|
-
},
|
|
296
|
-
{
|
|
297
|
-
label: "Defaults",
|
|
298
|
-
build: (ctx) =>
|
|
299
|
-
new DefaultsChoiceStep(theme, state, () => {
|
|
300
|
-
ctx.markComplete();
|
|
301
|
-
ctx.goNext();
|
|
302
|
-
}),
|
|
303
|
-
},
|
|
304
|
-
{
|
|
305
|
-
label: "Path access",
|
|
306
|
-
build: (ctx) =>
|
|
307
|
-
new PathAccessStep(theme, state, () => {
|
|
308
|
-
ctx.markComplete();
|
|
309
|
-
ctx.goNext();
|
|
310
|
-
}),
|
|
311
|
-
},
|
|
312
|
-
{
|
|
313
|
-
label: "Recap",
|
|
314
|
-
build: (ctx) =>
|
|
315
|
-
new FinishStep(state, () => {
|
|
316
|
-
if (state.applyBuiltinDefaults === null) return;
|
|
317
|
-
ctx.markComplete();
|
|
318
|
-
finalize({
|
|
319
|
-
completed: true,
|
|
320
|
-
applyBuiltinDefaults: state.applyBuiltinDefaults,
|
|
321
|
-
pathAccessEnabled: state.pathAccessEnabled,
|
|
322
|
-
});
|
|
323
|
-
}),
|
|
324
|
-
},
|
|
325
|
-
],
|
|
326
|
-
onComplete: () => {
|
|
327
|
-
if (state.applyBuiltinDefaults === null) {
|
|
328
|
-
finalize({
|
|
329
|
-
completed: false,
|
|
330
|
-
applyBuiltinDefaults: null,
|
|
331
|
-
pathAccessEnabled: null,
|
|
332
|
-
});
|
|
333
|
-
return;
|
|
334
|
-
}
|
|
335
|
-
finalize({
|
|
336
|
-
completed: true,
|
|
337
|
-
applyBuiltinDefaults: state.applyBuiltinDefaults,
|
|
338
|
-
pathAccessEnabled: state.pathAccessEnabled,
|
|
339
|
-
});
|
|
340
|
-
},
|
|
341
|
-
onCancel: () =>
|
|
342
|
-
finalize({
|
|
343
|
-
completed: false,
|
|
344
|
-
applyBuiltinDefaults: null,
|
|
345
|
-
pathAccessEnabled: null,
|
|
346
|
-
}),
|
|
347
|
-
hintSuffix: "Enter select/continue",
|
|
348
|
-
minContentHeight: 12,
|
|
349
|
-
});
|
|
350
|
-
|
|
351
|
-
return {
|
|
352
|
-
render: (width) => wizard.render(width),
|
|
353
|
-
invalidate: () => wizard.invalidate(),
|
|
354
|
-
handleInput: (data: string) => {
|
|
355
|
-
if (
|
|
356
|
-
matchesKey(data, Key.tab) &&
|
|
357
|
-
wizard.getActiveIndex() === 0 &&
|
|
358
|
-
markWelcomeComplete
|
|
359
|
-
) {
|
|
360
|
-
markWelcomeComplete();
|
|
361
|
-
}
|
|
362
|
-
wizard.handleInput(data);
|
|
363
|
-
},
|
|
364
|
-
};
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
export function buildOnboardedConfig(
|
|
368
|
-
applyBuiltinDefaults: boolean,
|
|
369
|
-
pathAccessEnabled?: boolean | null,
|
|
370
|
-
): GuardrailsConfig {
|
|
371
|
-
const config: GuardrailsConfig = {
|
|
372
|
-
version: CURRENT_VERSION,
|
|
373
|
-
applyBuiltinDefaults,
|
|
374
|
-
onboarding: {
|
|
375
|
-
completed: true,
|
|
376
|
-
completedAt: new Date().toISOString(),
|
|
377
|
-
version: CURRENT_VERSION,
|
|
378
|
-
},
|
|
379
|
-
};
|
|
380
|
-
if (pathAccessEnabled) {
|
|
381
|
-
config.features = { ...config.features, pathAccess: true };
|
|
382
|
-
config.pathAccess = { mode: "ask" };
|
|
383
|
-
}
|
|
384
|
-
return config;
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
export function isOnboardingPending(config: GuardrailsConfig | null): boolean {
|
|
388
|
-
if (!config) return true;
|
|
389
|
-
return config.onboarding?.completed !== true;
|
|
390
|
-
}
|