@studio-foundation/cli 0.3.0-beta.1 → 0.3.0-beta.6
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 +9 -5
- package/ARCHITECTURE.md +0 -40
- package/src/commands/api.ts +0 -126
- package/src/commands/config.ts +0 -433
- package/src/commands/init.ts +0 -879
- package/src/commands/install.ts +0 -23
- package/src/commands/integrations.ts +0 -332
- package/src/commands/list.ts +0 -197
- package/src/commands/logs.ts +0 -119
- package/src/commands/ollama.ts +0 -168
- package/src/commands/project.ts +0 -167
- package/src/commands/registry/audit.ts +0 -87
- package/src/commands/registry/index.ts +0 -63
- package/src/commands/registry/install.ts +0 -222
- package/src/commands/registry/publish.ts +0 -112
- package/src/commands/registry/remove.ts +0 -89
- package/src/commands/registry/search.ts +0 -93
- package/src/commands/registry/sync.ts +0 -24
- package/src/commands/registry/update.ts +0 -63
- package/src/commands/replay.ts +0 -559
- package/src/commands/run.ts +0 -454
- package/src/commands/status.ts +0 -163
- package/src/commands/template/index.ts +0 -59
- package/src/commands/template/validate.ts +0 -175
- package/src/commands/templates.ts +0 -99
- package/src/commands/tools.ts +0 -227
- package/src/commands/users.ts +0 -127
- package/src/commands/validate.ts +0 -46
- package/src/config.ts +0 -101
- package/src/index.ts +0 -201
- package/src/models-cache.ts +0 -127
- package/src/output/file-changes.ts +0 -97
- package/src/output/formatter.ts +0 -85
- package/src/output/formatters.ts +0 -303
- package/src/output/logger.ts +0 -18
- package/src/output/parallel-progress.ts +0 -103
- package/src/output/progress.ts +0 -414
- package/src/provider-validator.ts +0 -109
- package/src/registry/cache.ts +0 -46
- package/src/registry/client.ts +0 -84
- package/src/registry/lockfile.ts +0 -68
- package/src/registry/resolver.ts +0 -113
- package/src/registry/types.ts +0 -66
- package/src/run-logger.ts +0 -66
- package/src/run-store-factory.ts +0 -30
- package/src/studio-dir.ts +0 -28
- package/src/utils/input-wizard.ts +0 -73
- package/src/utils/placeholders.ts +0 -10
- package/tests/__stubs__/studio-runner.ts +0 -104
- package/tests/commands/api.test.ts +0 -110
- package/tests/commands/config.test.ts +0 -221
- package/tests/commands/init.test.ts +0 -919
- package/tests/commands/install.test.ts +0 -52
- package/tests/commands/integrations.test.ts +0 -158
- package/tests/commands/ollama.test.ts +0 -139
- package/tests/commands/project.test.ts +0 -179
- package/tests/commands/registry/audit.test.ts +0 -56
- package/tests/commands/registry/install.test.ts +0 -200
- package/tests/commands/registry/publish.test.ts +0 -56
- package/tests/commands/registry/remove.test.ts +0 -103
- package/tests/commands/registry/search.test.ts +0 -44
- package/tests/commands/registry/sync.test.ts +0 -37
- package/tests/commands/registry/update.test.ts +0 -62
- package/tests/commands/replay.test.ts +0 -283
- package/tests/commands/template/validate.test.ts +0 -150
- package/tests/commands/templates.test.ts +0 -42
- package/tests/commands/tools.test.ts +0 -106
- package/tests/config.test.ts +0 -142
- package/tests/formatter.test.ts +0 -158
- package/tests/integration/sigint.test.ts +0 -188
- package/tests/models-cache.test.ts +0 -250
- package/tests/output/file-changes.test.ts +0 -178
- package/tests/output/formatters.test.ts +0 -448
- package/tests/output/progress-spinner.test.ts +0 -232
- package/tests/output/progress-timer.test.ts +0 -230
- package/tests/provider-validator.test.ts +0 -182
- package/tests/registry/cache.test.ts +0 -66
- package/tests/registry/client.test.ts +0 -70
- package/tests/registry/lockfile.test.ts +0 -87
- package/tests/registry/resolver.test.ts +0 -122
- package/tests/run-logger-events.test.ts +0 -326
- package/tests/run-logger.test.ts +0 -64
- package/tests/run-store-factory.test.ts +0 -51
- package/tests/studio-dir.test.ts +0 -31
- package/tests/utils/input-wizard.test.ts +0 -153
- package/tests/utils/placeholders.test.ts +0 -36
- package/tsconfig.json +0 -24
- package/vitest.config.ts +0 -20
|
@@ -1,221 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
import { mkdir, rm, readFile } from 'node:fs/promises';
|
|
3
|
-
import { resolve } from 'node:path';
|
|
4
|
-
import * as yaml from 'js-yaml';
|
|
5
|
-
import {
|
|
6
|
-
getConfigValue,
|
|
7
|
-
setConfigValue,
|
|
8
|
-
maskSecrets,
|
|
9
|
-
PROVIDERS,
|
|
10
|
-
validateApiKeyForProvider,
|
|
11
|
-
addProviderConfig,
|
|
12
|
-
isProviderConfigured,
|
|
13
|
-
} from '../../src/commands/config.js';
|
|
14
|
-
|
|
15
|
-
const TMP = resolve(import.meta.dirname, '.tmp-config-cmd-test');
|
|
16
|
-
const STUDIO_DIR = resolve(TMP, '.studio');
|
|
17
|
-
|
|
18
|
-
beforeEach(async () => { await mkdir(STUDIO_DIR, { recursive: true }); });
|
|
19
|
-
afterEach(async () => { await rm(TMP, { recursive: true, force: true }); });
|
|
20
|
-
|
|
21
|
-
describe('getConfigValue', () => {
|
|
22
|
-
it('gets a nested value by dotted path', () => {
|
|
23
|
-
const config = { defaults: { model: 'claude-haiku' } };
|
|
24
|
-
expect(getConfigValue(config, 'defaults.model')).toBe('claude-haiku');
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it('returns undefined for missing path', () => {
|
|
28
|
-
expect(getConfigValue({}, 'defaults.model')).toBeUndefined();
|
|
29
|
-
});
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
describe('setConfigValue', () => {
|
|
33
|
-
it('sets a nested value by dotted path', () => {
|
|
34
|
-
const config: Record<string, unknown> = {};
|
|
35
|
-
setConfigValue(config, 'defaults.model', 'claude-sonnet');
|
|
36
|
-
expect((config as any).defaults.model).toBe('claude-sonnet');
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it('merges without destroying sibling keys', () => {
|
|
40
|
-
const config = { defaults: { provider: 'anthropic', model: 'old' } };
|
|
41
|
-
setConfigValue(config, 'defaults.model', 'new');
|
|
42
|
-
expect((config as any).defaults.provider).toBe('anthropic');
|
|
43
|
-
expect((config as any).defaults.model).toBe('new');
|
|
44
|
-
});
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
describe('maskSecrets', () => {
|
|
48
|
-
it('masks apiKey values', () => {
|
|
49
|
-
const config = { providers: { anthropic: { apiKey: 'sk-ant-longkey' } } };
|
|
50
|
-
const masked = maskSecrets(config);
|
|
51
|
-
expect((masked as any).providers.anthropic.apiKey).toMatch(/\*\*\*/);
|
|
52
|
-
expect((masked as any).providers.anthropic.apiKey).not.toContain('longkey');
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it('preserves non-secret values', () => {
|
|
56
|
-
const config = { defaults: { model: 'claude-haiku' } };
|
|
57
|
-
expect((maskSecrets(config) as any).defaults.model).toBe('claude-haiku');
|
|
58
|
-
});
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
describe('PROVIDERS', () => {
|
|
62
|
-
it('includes anthropic, openai, google, ollama', () => {
|
|
63
|
-
const ids = PROVIDERS.map((p) => p.id);
|
|
64
|
-
expect(ids).toContain('anthropic');
|
|
65
|
-
expect(ids).toContain('openai');
|
|
66
|
-
expect(ids).toContain('google');
|
|
67
|
-
expect(ids).toContain('ollama');
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it('each provider has id, label, and defaultModel', () => {
|
|
71
|
-
for (const p of PROVIDERS) {
|
|
72
|
-
expect(typeof p.id).toBe('string');
|
|
73
|
-
expect(typeof p.label).toBe('string');
|
|
74
|
-
expect(typeof p.defaultModel).toBe('string');
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
describe('validateApiKeyForProvider', () => {
|
|
80
|
-
it('accepts a valid Anthropic key (sk-ant-...)', () => {
|
|
81
|
-
expect(validateApiKeyForProvider('anthropic', 'sk-ant-api03-abc123')).toBe(true);
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it('rejects an Anthropic key with wrong prefix', () => {
|
|
85
|
-
const result = validateApiKeyForProvider('anthropic', 'sk-wrong');
|
|
86
|
-
expect(typeof result).toBe('string');
|
|
87
|
-
expect(result).toContain('sk-ant-');
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it('accepts a valid OpenAI key (sk-...)', () => {
|
|
91
|
-
expect(validateApiKeyForProvider('openai', 'sk-proj-abc123')).toBe(true);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it('rejects an OpenAI key with wrong prefix', () => {
|
|
95
|
-
const result = validateApiKeyForProvider('openai', 'wrong-key');
|
|
96
|
-
expect(typeof result).toBe('string');
|
|
97
|
-
expect(result).toContain('sk-');
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
it('rejects an Anthropic-prefixed key when provider is openai', () => {
|
|
101
|
-
const result = validateApiKeyForProvider('openai', 'sk-ant-api03-abc');
|
|
102
|
-
expect(typeof result).toBe('string');
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
it('accepts a valid Google key (AIza...)', () => {
|
|
106
|
-
expect(validateApiKeyForProvider('google', 'AIzaSyABC123')).toBe(true);
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it('rejects a Google key with wrong prefix', () => {
|
|
110
|
-
const result = validateApiKeyForProvider('google', 'wrong-key');
|
|
111
|
-
expect(typeof result).toBe('string');
|
|
112
|
-
expect(result).toContain('AIza');
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
it('accepts any value for ollama provider (no validation)', () => {
|
|
116
|
-
expect(validateApiKeyForProvider('ollama', 'http://localhost:11434')).toBe(true);
|
|
117
|
-
expect(validateApiKeyForProvider('ollama', '')).toBe(true);
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
it('accepts any value for unknown providers', () => {
|
|
121
|
-
expect(validateApiKeyForProvider('future-provider', 'any-key')).toBe(true);
|
|
122
|
-
});
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
describe('addProviderConfig', () => {
|
|
126
|
-
const CONFIG_FILE = resolve(STUDIO_DIR, 'config.yaml');
|
|
127
|
-
|
|
128
|
-
it('writes provider apiKey to config.yaml', async () => {
|
|
129
|
-
await addProviderConfig(CONFIG_FILE, 'anthropic', 'sk-ant-key', false);
|
|
130
|
-
|
|
131
|
-
const raw = await readFile(CONFIG_FILE, 'utf-8');
|
|
132
|
-
const parsed = yaml.load(raw) as Record<string, unknown>;
|
|
133
|
-
const providers = parsed.providers as Record<string, { apiKey: string }>;
|
|
134
|
-
expect(providers.anthropic.apiKey).toBe('sk-ant-key');
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
it('sets defaults when setDefault=true', async () => {
|
|
138
|
-
await addProviderConfig(CONFIG_FILE, 'anthropic', 'sk-ant-key', true);
|
|
139
|
-
|
|
140
|
-
const raw = await readFile(CONFIG_FILE, 'utf-8');
|
|
141
|
-
const parsed = yaml.load(raw) as Record<string, unknown>;
|
|
142
|
-
const defaults = parsed.defaults as { provider: string; model: string };
|
|
143
|
-
expect(defaults.provider).toBe('anthropic');
|
|
144
|
-
expect(defaults.model).toBe('claude-sonnet-4-20250514');
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
it('does not touch defaults when setDefault=false', async () => {
|
|
148
|
-
await addProviderConfig(CONFIG_FILE, 'anthropic', 'sk-ant-key', false);
|
|
149
|
-
|
|
150
|
-
const raw = await readFile(CONFIG_FILE, 'utf-8');
|
|
151
|
-
const parsed = yaml.load(raw) as Record<string, unknown>;
|
|
152
|
-
expect(parsed.defaults).toBeUndefined();
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
it('preserves existing provider when adding a second', async () => {
|
|
156
|
-
await addProviderConfig(CONFIG_FILE, 'anthropic', 'sk-ant-key', false);
|
|
157
|
-
await addProviderConfig(CONFIG_FILE, 'openai', 'sk-openai-key', false);
|
|
158
|
-
|
|
159
|
-
const raw = await readFile(CONFIG_FILE, 'utf-8');
|
|
160
|
-
const parsed = yaml.load(raw) as Record<string, unknown>;
|
|
161
|
-
const providers = parsed.providers as Record<string, { apiKey: string }>;
|
|
162
|
-
expect(providers.anthropic.apiKey).toBe('sk-ant-key');
|
|
163
|
-
expect(providers.openai.apiKey).toBe('sk-openai-key');
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
it('overwrites existing provider key', async () => {
|
|
167
|
-
await addProviderConfig(CONFIG_FILE, 'anthropic', 'sk-ant-first', false);
|
|
168
|
-
await addProviderConfig(CONFIG_FILE, 'anthropic', 'sk-ant-second', false);
|
|
169
|
-
|
|
170
|
-
const raw = await readFile(CONFIG_FILE, 'utf-8');
|
|
171
|
-
const parsed = yaml.load(raw) as Record<string, unknown>;
|
|
172
|
-
const providers = parsed.providers as Record<string, { apiKey: string }>;
|
|
173
|
-
expect(providers.anthropic.apiKey).toBe('sk-ant-second');
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
it('creates config.yaml if it does not exist', async () => {
|
|
177
|
-
// STUDIO_DIR exists (from beforeEach) but config.yaml does not
|
|
178
|
-
await addProviderConfig(CONFIG_FILE, 'openai', 'sk-new-key', false);
|
|
179
|
-
|
|
180
|
-
const raw = await readFile(CONFIG_FILE, 'utf-8');
|
|
181
|
-
const parsed = yaml.load(raw) as Record<string, unknown>;
|
|
182
|
-
const providers = parsed.providers as Record<string, { apiKey: string }>;
|
|
183
|
-
expect(providers.openai.apiKey).toBe('sk-new-key');
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
it('uses correct defaultModel for openai', async () => {
|
|
187
|
-
await addProviderConfig(CONFIG_FILE, 'openai', 'sk-openai-key', true);
|
|
188
|
-
|
|
189
|
-
const raw = await readFile(CONFIG_FILE, 'utf-8');
|
|
190
|
-
const parsed = yaml.load(raw) as Record<string, unknown>;
|
|
191
|
-
const defaults = parsed.defaults as { model: string };
|
|
192
|
-
expect(defaults.model).toBe('gpt-4o');
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
it('uses claude-sonnet fallback for unknown provider', async () => {
|
|
196
|
-
await addProviderConfig(CONFIG_FILE, 'unknown-provider', 'some-key', true);
|
|
197
|
-
|
|
198
|
-
const raw = await readFile(CONFIG_FILE, 'utf-8');
|
|
199
|
-
const parsed = yaml.load(raw) as Record<string, unknown>;
|
|
200
|
-
const defaults = parsed.defaults as { model: string };
|
|
201
|
-
expect(defaults.model).toBe('claude-sonnet-4-20250514');
|
|
202
|
-
});
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
describe('isProviderConfigured', () => {
|
|
206
|
-
const CONFIG_FILE = resolve(STUDIO_DIR, 'config.yaml');
|
|
207
|
-
|
|
208
|
-
it('returns false when config.yaml does not exist', async () => {
|
|
209
|
-
expect(await isProviderConfigured(CONFIG_FILE, 'anthropic')).toBe(false);
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
it('returns false when provider not in config', async () => {
|
|
213
|
-
await addProviderConfig(CONFIG_FILE, 'openai', 'sk-key', false);
|
|
214
|
-
expect(await isProviderConfigured(CONFIG_FILE, 'anthropic')).toBe(false);
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
it('returns true when provider is in config', async () => {
|
|
218
|
-
await addProviderConfig(CONFIG_FILE, 'anthropic', 'sk-ant-key', false);
|
|
219
|
-
expect(await isProviderConfigured(CONFIG_FILE, 'anthropic')).toBe(true);
|
|
220
|
-
});
|
|
221
|
-
});
|