@vellumai/cli 0.4.35 → 0.4.37

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.
@@ -1,320 +0,0 @@
1
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
- import { homedir } from "node:os";
3
- import { dirname, join } from "node:path";
4
-
5
- // ---------------------------------------------------------------------------
6
- // Types & constants (ported from assistant/src/autonomy/types.ts)
7
- // ---------------------------------------------------------------------------
8
-
9
- type AutonomyTier = "auto" | "draft" | "notify";
10
-
11
- const AUTONOMY_TIERS: readonly AutonomyTier[] = ["auto", "draft", "notify"];
12
-
13
- interface AutonomyConfig {
14
- defaultTier: AutonomyTier;
15
- channelDefaults: Record<string, AutonomyTier>;
16
- categoryOverrides: Record<string, AutonomyTier>;
17
- contactOverrides: Record<string, AutonomyTier>;
18
- }
19
-
20
- const DEFAULT_AUTONOMY_CONFIG: AutonomyConfig = {
21
- defaultTier: "notify",
22
- channelDefaults: {},
23
- categoryOverrides: {},
24
- contactOverrides: {},
25
- };
26
-
27
- // ---------------------------------------------------------------------------
28
- // Config persistence (ported from assistant/src/autonomy/autonomy-store.ts)
29
- // ---------------------------------------------------------------------------
30
-
31
- function getConfigPath(): string {
32
- const root = join(process.env.BASE_DATA_DIR?.trim() || homedir(), ".vellum");
33
- return join(root, "workspace", "autonomy.json");
34
- }
35
-
36
- function isValidTier(value: unknown): value is AutonomyTier {
37
- return (
38
- typeof value === "string" && AUTONOMY_TIERS.includes(value as AutonomyTier)
39
- );
40
- }
41
-
42
- function validateTierRecord(raw: unknown): Record<string, AutonomyTier> {
43
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) return {};
44
- const result: Record<string, AutonomyTier> = {};
45
- for (const [key, value] of Object.entries(raw as Record<string, unknown>)) {
46
- if (isValidTier(value)) {
47
- result[key] = value;
48
- }
49
- }
50
- return result;
51
- }
52
-
53
- function validateConfig(raw: unknown): AutonomyConfig {
54
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
55
- return structuredClone(DEFAULT_AUTONOMY_CONFIG);
56
- }
57
- const obj = raw as Record<string, unknown>;
58
- return {
59
- defaultTier: isValidTier(obj.defaultTier)
60
- ? obj.defaultTier
61
- : DEFAULT_AUTONOMY_CONFIG.defaultTier,
62
- channelDefaults: validateTierRecord(obj.channelDefaults),
63
- categoryOverrides: validateTierRecord(obj.categoryOverrides),
64
- contactOverrides: validateTierRecord(obj.contactOverrides),
65
- };
66
- }
67
-
68
- function loadConfig(): AutonomyConfig {
69
- const configPath = getConfigPath();
70
- if (!existsSync(configPath)) {
71
- return structuredClone(DEFAULT_AUTONOMY_CONFIG);
72
- }
73
- try {
74
- const raw = readFileSync(configPath, "utf-8");
75
- return validateConfig(JSON.parse(raw));
76
- } catch {
77
- console.error("Warning: failed to parse autonomy config; using defaults");
78
- return structuredClone(DEFAULT_AUTONOMY_CONFIG);
79
- }
80
- }
81
-
82
- function saveConfig(config: AutonomyConfig): void {
83
- const configPath = getConfigPath();
84
- mkdirSync(dirname(configPath), { recursive: true });
85
- writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
86
- }
87
-
88
- function applyUpdate(updates: Partial<AutonomyConfig>): AutonomyConfig {
89
- const current = loadConfig();
90
- if (updates.defaultTier !== undefined) {
91
- current.defaultTier = updates.defaultTier;
92
- }
93
- if (updates.channelDefaults !== undefined) {
94
- current.channelDefaults = {
95
- ...current.channelDefaults,
96
- ...updates.channelDefaults,
97
- };
98
- }
99
- if (updates.categoryOverrides !== undefined) {
100
- current.categoryOverrides = {
101
- ...current.categoryOverrides,
102
- ...updates.categoryOverrides,
103
- };
104
- }
105
- if (updates.contactOverrides !== undefined) {
106
- current.contactOverrides = {
107
- ...current.contactOverrides,
108
- ...updates.contactOverrides,
109
- };
110
- }
111
- saveConfig(current);
112
- return current;
113
- }
114
-
115
- // ---------------------------------------------------------------------------
116
- // Output helpers
117
- // ---------------------------------------------------------------------------
118
-
119
- function output(data: unknown, json: boolean): void {
120
- process.stdout.write(
121
- json ? JSON.stringify(data) + "\n" : JSON.stringify(data, null, 2) + "\n",
122
- );
123
- }
124
-
125
- function formatConfigForHuman(config: AutonomyConfig): string {
126
- const lines: string[] = [` Default tier: ${config.defaultTier}`];
127
-
128
- const channelEntries = Object.entries(config.channelDefaults);
129
- if (channelEntries.length > 0) {
130
- lines.push(" Channel defaults:");
131
- for (const [channel, tier] of channelEntries) {
132
- lines.push(` ${channel}: ${tier}`);
133
- }
134
- } else {
135
- lines.push(" Channel defaults: (none)");
136
- }
137
-
138
- const categoryEntries = Object.entries(config.categoryOverrides);
139
- if (categoryEntries.length > 0) {
140
- lines.push(" Category overrides:");
141
- for (const [category, tier] of categoryEntries) {
142
- lines.push(` ${category}: ${tier}`);
143
- }
144
- } else {
145
- lines.push(" Category overrides: (none)");
146
- }
147
-
148
- const contactEntries = Object.entries(config.contactOverrides);
149
- if (contactEntries.length > 0) {
150
- lines.push(" Contact overrides:");
151
- for (const [contactId, tier] of contactEntries) {
152
- lines.push(` ${contactId}: ${tier}`);
153
- }
154
- } else {
155
- lines.push(" Contact overrides: (none)");
156
- }
157
-
158
- return lines.join("\n");
159
- }
160
-
161
- // ---------------------------------------------------------------------------
162
- // Arg parsing helpers
163
- // ---------------------------------------------------------------------------
164
-
165
- function hasFlag(args: string[], flag: string): boolean {
166
- return args.includes(flag);
167
- }
168
-
169
- function getFlagValue(args: string[], flag: string): string | undefined {
170
- const idx = args.indexOf(flag);
171
- if (idx === -1 || idx + 1 >= args.length) return undefined;
172
- return args[idx + 1];
173
- }
174
-
175
- // ---------------------------------------------------------------------------
176
- // Usage
177
- // ---------------------------------------------------------------------------
178
-
179
- function printUsage(): void {
180
- console.log("Usage: vellum autonomy <subcommand> [options]");
181
- console.log("");
182
- console.log("Subcommands:");
183
- console.log(
184
- " get Show current autonomy configuration",
185
- );
186
- console.log(" set --default <tier> Set the global default tier");
187
- console.log(" set --channel <ch> --tier <t> Set tier for a channel");
188
- console.log(" set --category <cat> --tier <t> Set tier for a category");
189
- console.log(" set --contact <id> --tier <t> Set tier for a contact");
190
- console.log("");
191
- console.log("Options:");
192
- console.log(" --json Machine-readable JSON output");
193
- console.log("");
194
- console.log("Tiers: auto, draft, notify");
195
- }
196
-
197
- // ---------------------------------------------------------------------------
198
- // Command entry point
199
- // ---------------------------------------------------------------------------
200
-
201
- export function autonomy(): void {
202
- const args = process.argv.slice(3);
203
- const subcommand = args[0];
204
- const json = hasFlag(args, "--json");
205
-
206
- if (!subcommand || subcommand === "--help" || subcommand === "-h") {
207
- printUsage();
208
- return;
209
- }
210
-
211
- switch (subcommand) {
212
- case "get": {
213
- const config = loadConfig();
214
- if (json) {
215
- output({ ok: true, config }, true);
216
- } else {
217
- process.stdout.write("Autonomy configuration:\n\n");
218
- process.stdout.write(formatConfigForHuman(config) + "\n");
219
- }
220
- break;
221
- }
222
-
223
- case "set": {
224
- const defaultTier = getFlagValue(args, "--default");
225
- const channel = getFlagValue(args, "--channel");
226
- const category = getFlagValue(args, "--category");
227
- const contact = getFlagValue(args, "--contact");
228
- const tier = getFlagValue(args, "--tier");
229
-
230
- if (defaultTier) {
231
- if (!isValidTier(defaultTier)) {
232
- output(
233
- {
234
- ok: false,
235
- error: `Invalid tier "${defaultTier}". Must be one of: ${AUTONOMY_TIERS.join(", ")}`,
236
- },
237
- true,
238
- );
239
- process.exitCode = 1;
240
- return;
241
- }
242
- const config = applyUpdate({ defaultTier });
243
- if (json) {
244
- output({ ok: true, config }, true);
245
- } else {
246
- console.log(`Set global default tier to "${defaultTier}".`);
247
- }
248
- return;
249
- }
250
-
251
- if (!tier) {
252
- output(
253
- {
254
- ok: false,
255
- error: "Missing --tier. Use --tier <auto|draft|notify>.",
256
- },
257
- true,
258
- );
259
- process.exitCode = 1;
260
- return;
261
- }
262
- if (!isValidTier(tier)) {
263
- output(
264
- {
265
- ok: false,
266
- error: `Invalid tier "${tier}". Must be one of: ${AUTONOMY_TIERS.join(", ")}`,
267
- },
268
- true,
269
- );
270
- process.exitCode = 1;
271
- return;
272
- }
273
-
274
- if (channel) {
275
- const config = applyUpdate({ channelDefaults: { [channel]: tier } });
276
- if (json) {
277
- output({ ok: true, config }, true);
278
- } else {
279
- console.log(`Set channel "${channel}" default to "${tier}".`);
280
- }
281
- return;
282
- }
283
-
284
- if (category) {
285
- const config = applyUpdate({
286
- categoryOverrides: { [category]: tier },
287
- });
288
- if (json) {
289
- output({ ok: true, config }, true);
290
- } else {
291
- console.log(`Set category "${category}" override to "${tier}".`);
292
- }
293
- return;
294
- }
295
-
296
- if (contact) {
297
- const config = applyUpdate({ contactOverrides: { [contact]: tier } });
298
- if (json) {
299
- output({ ok: true, config }, true);
300
- } else {
301
- console.log(`Set contact "${contact}" override to "${tier}".`);
302
- }
303
- return;
304
- }
305
-
306
- console.error(
307
- "Specify one of: --default <tier>, --channel <channel> --tier <tier>, " +
308
- "--category <category> --tier <tier>, or --contact <contactId> --tier <tier>.",
309
- );
310
- process.exitCode = 1;
311
- break;
312
- }
313
-
314
- default: {
315
- console.error(`Unknown autonomy subcommand: ${subcommand}`);
316
- printUsage();
317
- process.exit(1);
318
- }
319
- }
320
- }
@@ -1,178 +0,0 @@
1
- import { existsSync, readFileSync } from "node:fs";
2
-
3
- import { syncConfigToLockfile } from "../lib/assistant-config";
4
- import {
5
- getAllowlistPath,
6
- getNestedValue,
7
- loadRawConfig,
8
- saveRawConfig,
9
- setNestedValue,
10
- } from "../lib/config";
11
-
12
- interface AllowlistConfig {
13
- values?: string[];
14
- prefixes?: string[];
15
- patterns?: string[];
16
- }
17
-
18
- interface AllowlistValidationError {
19
- index: number;
20
- pattern: string;
21
- message: string;
22
- }
23
-
24
- function validateAllowlist(
25
- allowlistConfig: AllowlistConfig,
26
- ): AllowlistValidationError[] {
27
- const errors: AllowlistValidationError[] = [];
28
- if (!allowlistConfig.patterns) return errors;
29
- if (!Array.isArray(allowlistConfig.patterns)) {
30
- errors.push({
31
- index: -1,
32
- pattern: String(allowlistConfig.patterns),
33
- message: '"patterns" must be an array',
34
- });
35
- return errors;
36
- }
37
-
38
- for (let i = 0; i < allowlistConfig.patterns.length; i++) {
39
- const p = allowlistConfig.patterns[i];
40
- if (typeof p !== "string") {
41
- errors.push({
42
- index: i,
43
- pattern: String(p),
44
- message: "Pattern is not a string",
45
- });
46
- continue;
47
- }
48
- try {
49
- new RegExp(p);
50
- } catch (err) {
51
- errors.push({
52
- index: i,
53
- pattern: p,
54
- message: (err as Error).message,
55
- });
56
- }
57
- }
58
- return errors;
59
- }
60
-
61
- function validateAllowlistFile(): AllowlistValidationError[] | null {
62
- const filePath = getAllowlistPath();
63
- if (!existsSync(filePath)) return null;
64
-
65
- const raw = readFileSync(filePath, "utf-8");
66
- const allowlistConfig: AllowlistConfig = JSON.parse(raw) as AllowlistConfig;
67
- return validateAllowlist(allowlistConfig);
68
- }
69
-
70
- function printUsage(): void {
71
- console.log("Usage: vellum config <subcommand> [options]");
72
- console.log("");
73
- console.log("Subcommands:");
74
- console.log(
75
- " get <key> Get a config value (supports dotted paths)",
76
- );
77
- console.log(
78
- " set <key> <value> Set a config value (supports dotted paths like apiKeys.anthropic)",
79
- );
80
- console.log(" list List all config values");
81
- console.log(
82
- " validate-allowlist Validate regex patterns in secret-allowlist.json",
83
- );
84
- }
85
-
86
- export function config(): void {
87
- const args = process.argv.slice(3);
88
- const subcommand = args[0];
89
-
90
- if (!subcommand || subcommand === "--help" || subcommand === "-h") {
91
- printUsage();
92
- return;
93
- }
94
-
95
- switch (subcommand) {
96
- case "set": {
97
- const key = args[1];
98
- const value = args[2];
99
- if (!key || value === undefined) {
100
- console.error("Usage: vellum config set <key> <value>");
101
- process.exit(1);
102
- }
103
- const raw = loadRawConfig();
104
- let parsed: unknown = value;
105
- try {
106
- parsed = JSON.parse(value);
107
- } catch {
108
- // keep as string
109
- }
110
- setNestedValue(raw, key, parsed);
111
- saveRawConfig(raw);
112
- syncConfigToLockfile();
113
- console.log(`Set ${key} = ${JSON.stringify(parsed)}`);
114
- break;
115
- }
116
-
117
- case "get": {
118
- const key = args[1];
119
- if (!key) {
120
- console.error("Usage: vellum config get <key>");
121
- process.exit(1);
122
- }
123
- const raw = loadRawConfig();
124
- const val = getNestedValue(raw, key);
125
- if (val === undefined) {
126
- console.log("(not set)");
127
- } else {
128
- console.log(
129
- typeof val === "object" ? JSON.stringify(val, null, 2) : String(val),
130
- );
131
- }
132
- break;
133
- }
134
-
135
- case "list": {
136
- const raw = loadRawConfig();
137
- if (Object.keys(raw).length === 0) {
138
- console.log("No configuration set");
139
- } else {
140
- console.log(JSON.stringify(raw, null, 2));
141
- }
142
- break;
143
- }
144
-
145
- case "validate-allowlist": {
146
- try {
147
- const errors = validateAllowlistFile();
148
- if (errors === null) {
149
- console.log("No secret-allowlist.json file found");
150
- return;
151
- }
152
- if (errors.length === 0) {
153
- console.log("All patterns in secret-allowlist.json are valid");
154
- return;
155
- }
156
- console.error(
157
- `Found ${errors.length} invalid pattern(s) in secret-allowlist.json:`,
158
- );
159
- for (const e of errors) {
160
- console.error(` [${e.index}] "${e.pattern}": ${e.message}`);
161
- }
162
- process.exit(1);
163
- } catch (err) {
164
- console.error(
165
- `Failed to read secret-allowlist.json: ${(err as Error).message}`,
166
- );
167
- process.exit(1);
168
- }
169
- break;
170
- }
171
-
172
- default: {
173
- console.error(`Unknown config subcommand: ${subcommand}`);
174
- printUsage();
175
- process.exit(1);
176
- }
177
- }
178
- }