@amodalai/amodal 0.3.89 → 0.3.91

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.
Files changed (75) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/dist/src/commands/audit.d.ts +1 -3
  3. package/dist/src/commands/audit.d.ts.map +1 -1
  4. package/dist/src/commands/audit.js +4 -53
  5. package/dist/src/commands/audit.js.map +1 -1
  6. package/dist/src/commands/build-manifest-types.js +1 -1
  7. package/dist/src/commands/build-tools.d.ts +3 -10
  8. package/dist/src/commands/build-tools.d.ts.map +1 -1
  9. package/dist/src/commands/build-tools.js +5 -118
  10. package/dist/src/commands/build-tools.js.map +1 -1
  11. package/dist/src/commands/build.d.ts +23 -5
  12. package/dist/src/commands/build.d.ts.map +1 -1
  13. package/dist/src/commands/build.js +53 -34
  14. package/dist/src/commands/build.js.map +1 -1
  15. package/dist/src/commands/chat.d.ts +1 -1
  16. package/dist/src/commands/chat.js +5 -5
  17. package/dist/src/commands/chat.js.map +1 -1
  18. package/dist/src/commands/deploy.d.ts +1 -1
  19. package/dist/src/commands/deploy.d.ts.map +1 -1
  20. package/dist/src/commands/deploy.js +3 -61
  21. package/dist/src/commands/deploy.js.map +1 -1
  22. package/dist/src/commands/deployments.d.ts.map +1 -1
  23. package/dist/src/commands/deployments.js +3 -36
  24. package/dist/src/commands/deployments.js.map +1 -1
  25. package/dist/src/commands/dev.d.ts.map +1 -1
  26. package/dist/src/commands/dev.js +7 -10
  27. package/dist/src/commands/dev.js.map +1 -1
  28. package/dist/src/commands/experiment.d.ts +1 -3
  29. package/dist/src/commands/experiment.d.ts.map +1 -1
  30. package/dist/src/commands/experiment.js +4 -102
  31. package/dist/src/commands/experiment.js.map +1 -1
  32. package/dist/src/commands/promote.d.ts.map +1 -1
  33. package/dist/src/commands/promote.js +3 -21
  34. package/dist/src/commands/promote.js.map +1 -1
  35. package/dist/src/commands/rollback.d.ts.map +1 -1
  36. package/dist/src/commands/rollback.js +3 -24
  37. package/dist/src/commands/rollback.js.map +1 -1
  38. package/dist/src/commands/secrets.d.ts.map +1 -1
  39. package/dist/src/commands/secrets.js +2 -102
  40. package/dist/src/commands/secrets.js.map +1 -1
  41. package/dist/src/commands/serve.d.ts +2 -11
  42. package/dist/src/commands/serve.d.ts.map +1 -1
  43. package/dist/src/commands/serve.js +44 -87
  44. package/dist/src/commands/serve.js.map +1 -1
  45. package/dist/src/commands/status.d.ts.map +1 -1
  46. package/dist/src/commands/status.js +3 -49
  47. package/dist/src/commands/status.js.map +1 -1
  48. package/dist/tsconfig.tsbuildinfo +1 -1
  49. package/package.json +7 -8
  50. package/src/commands/audit.ts +4 -71
  51. package/src/commands/build-manifest-types.ts +1 -1
  52. package/src/commands/build-tools.ts +5 -142
  53. package/src/commands/build.test.ts +14 -9
  54. package/src/commands/build.ts +73 -33
  55. package/src/commands/chat.ts +5 -5
  56. package/src/commands/deploy.test.ts +2 -13
  57. package/src/commands/deploy.ts +5 -67
  58. package/src/commands/deployments.ts +3 -39
  59. package/src/commands/dev.ts +7 -10
  60. package/src/commands/experiment.ts +4 -110
  61. package/src/commands/promote.ts +3 -21
  62. package/src/commands/rollback.ts +3 -24
  63. package/src/commands/secrets.test.ts +12 -134
  64. package/src/commands/secrets.ts +2 -116
  65. package/src/commands/serve.ts +46 -93
  66. package/src/commands/status.ts +3 -51
  67. package/src/e2e-commands.test.ts +18 -17
  68. package/dist/src/shared/platform-client.d.ts +0 -123
  69. package/dist/src/shared/platform-client.d.ts.map +0 -1
  70. package/dist/src/shared/platform-client.js +0 -280
  71. package/dist/src/shared/platform-client.js.map +0 -1
  72. package/src/commands/audit.test.ts +0 -92
  73. package/src/commands/experiment.test.ts +0 -125
  74. package/src/shared/platform-client.test.ts +0 -106
  75. package/src/shared/platform-client.ts +0 -367
@@ -6,10 +6,7 @@
6
6
 
7
7
  import type {CommandModule} from 'yargs';
8
8
  import {findRepoRoot} from '../shared/repo-discovery.js';
9
- import {PlatformClient} from '../shared/platform-client.js';
10
9
  import {runValidate} from './validate.js';
11
- import {createRepoTarball} from '../shared/tarball.js';
12
- import {readProjectLink} from './link.js';
13
10
 
14
11
  export interface DeployOptions {
15
12
  cwd?: string;
@@ -19,7 +16,7 @@ export interface DeployOptions {
19
16
  }
20
17
 
21
18
  /**
22
- * Deploy to the platform: validate → tarball → trigger build → poll.
19
+ * Validate a repo before cloud deployment.
23
20
  *
24
21
  * Returns 0 on success, 1 on error.
25
22
  */
@@ -48,69 +45,10 @@ export async function runDeploy(options: DeployOptions = {}): Promise<number> {
48
45
  return 0;
49
46
  }
50
47
 
51
- // Create platform client
52
- let client: PlatformClient;
53
- try {
54
- client = await PlatformClient.create();
55
- } catch (err) {
56
- const msg = err instanceof Error ? err.message : String(err);
57
- process.stderr.write(`[deploy] ${msg}\n`);
58
- return 1;
59
- }
60
-
61
- const projectLink = await readProjectLink();
62
- const appId = projectLink?.appId;
63
-
64
- if (!appId) {
65
- process.stderr.write('[deploy] No app linked. Run `amodal deploy link` first.\n');
66
- return 1;
67
- }
68
-
69
- // Create tarball
70
- process.stderr.write('[deploy] Creating tarball...\n');
71
- const tarballPath = await createRepoTarball(repoPath);
72
-
73
- try {
74
- // Trigger remote build
75
- process.stderr.write('[deploy] Triggering build...\n');
76
- const buildResult = await client.triggerRemoteBuild(appId, environment, tarballPath, options.message);
77
- const buildId = buildResult.buildId;
78
-
79
- process.stderr.write(`[deploy] Build ${buildId} accepted. Waiting for completion...\n`);
80
-
81
- // Poll for completion
82
- const maxWaitMs = 5 * 60 * 1000; // 5 minutes
83
- const pollIntervalMs = 3000;
84
- const startTime = Date.now();
85
-
86
- while (Date.now() - startTime < maxWaitMs) {
87
- await new Promise((r) => setTimeout(r, pollIntervalMs));
88
-
89
- const status = await client.getBuildStatus(buildId);
90
-
91
- if (status.status === 'complete') {
92
- process.stderr.write(`[deploy] Deployed ${status.deployId} to ${status.environment ?? environment}\n`);
93
- return 0;
94
- }
95
-
96
- if (status.status === 'error') {
97
- process.stderr.write(`[deploy] Build failed: ${status.error ?? 'unknown error'}\n`);
98
- return 1;
99
- }
100
-
101
- // Still building — continue polling
102
- }
103
-
104
- process.stderr.write(`[deploy] Build timed out after 5 minutes. Build ${buildId} may still be running.\n`);
105
- return 1;
106
- } catch (err) {
107
- const msg = err instanceof Error ? err.message : String(err);
108
- process.stderr.write(`[deploy] Deploy failed: ${msg}\n`);
109
- return 1;
110
- } finally {
111
- const {unlinkSync} = await import('node:fs');
112
- try { unlinkSync(tarballPath); } catch { /* best-effort */ }
113
- }
48
+ process.stderr.write(
49
+ '[deploy] Cloud deployment is not included in the OSS CLI. Use `amodal deploy build` to create a local snapshot, then deploy it with your hosted control-plane tooling.\n',
50
+ );
51
+ return 1;
114
52
  }
115
53
 
116
54
  export const deployCommand: CommandModule = {
@@ -5,7 +5,6 @@
5
5
  */
6
6
 
7
7
  import type {CommandModule} from 'yargs';
8
- import {PlatformClient} from '../shared/platform-client.js';
9
8
 
10
9
  export interface DeploymentsOptions {
11
10
  env?: string;
@@ -17,44 +16,9 @@ export interface DeploymentsOptions {
17
16
  * List deployment history.
18
17
  */
19
18
  export async function runDeployments(options: DeploymentsOptions = {}): Promise<number> {
20
- let client: PlatformClient;
21
- try {
22
- client = new PlatformClient();
23
- } catch (err) {
24
- const msg = err instanceof Error ? err.message : String(err);
25
- process.stderr.write(`[deployments] ${msg}\n`);
26
- return 1;
27
- }
28
-
29
- try {
30
- const deployments = await client.listDeployments({
31
- environment: options.env,
32
- limit: options.limit ?? 10,
33
- });
34
-
35
- if (options.json) {
36
- process.stdout.write(JSON.stringify(deployments, null, 2) + '\n');
37
- return 0;
38
- }
39
-
40
- if (deployments.length === 0) {
41
- process.stderr.write('[deployments] No deployments found.\n');
42
- return 0;
43
- }
44
-
45
- for (const d of deployments) {
46
- const active = d.isActive ? ' [ACTIVE]' : '';
47
- const msg = d.message ? ` — ${d.message}` : '';
48
- const sha = d.commitSha ? ` (${d.commitSha.slice(0, 7)})` : '';
49
- process.stdout.write(`${d.id} ${d.environment}${active} ${d.createdBy ?? d.source}${sha} ${d.createdAt}${msg}\n`);
50
- }
51
-
52
- return 0;
53
- } catch (err) {
54
- const msg = err instanceof Error ? err.message : String(err);
55
- process.stderr.write(`[deployments] Failed: ${msg}\n`);
56
- return 1;
57
- }
19
+ void options;
20
+ process.stderr.write('[deployments] Hosted deployment history is not included in the OSS CLI.\n');
21
+ return 1;
58
22
  }
59
23
 
60
24
  export const deploymentsCommand: CommandModule = {
@@ -203,13 +203,11 @@ export function formatLineForDev(line: string): string | null {
203
203
  return null;
204
204
  }
205
205
  case 'tool_log': {
206
- // Tools call ctx.log(...) for noteworthy progress (e.g.
207
- // install_template emits "Cloned whodatdev/template-X into
208
- // agent repo (N connection packages installed)"). When fired
206
+ // Tools call ctx.log(...) for noteworthy progress. When fired
209
207
  // during an intent run the callId starts with `intent_`, so
210
- // these lines pass the quiet filter but as raw JSON they
211
- // bury the useful message inside callId/session noise. Strip
212
- // to a clean nested bullet.
208
+ // these lines pass the quiet filter, but as raw JSON they bury
209
+ // the useful message inside callId/session noise. Strip to a
210
+ // clean nested bullet.
213
211
  const msg = str('message');
214
212
  if (!msg) return null;
215
213
  return ` · ${msg}`;
@@ -753,9 +751,8 @@ Or add it to your agent's .env file:
753
751
  // Two flows watched by the same poller:
754
752
  //
755
753
  // 1. AUTO-BOOT — runtime didn't start at CLI launch (no manifest
756
- // yet). Once amodal.json lands (commit_setup, the user-button
757
- // commit-setup endpoint, or init-repo's skip-onboarding
758
- // write), boot the runtime in place. Studio's runtime URL
754
+ // yet). Once amodal.json lands, boot the runtime in place.
755
+ // Studio's runtime URL
759
756
  // probe picks it up on the next tick.
760
757
  //
761
758
  // 2. AUTO-RESTART — runtime is already up but amodal.json has
@@ -766,7 +763,7 @@ Or add it to your agent's .env file:
766
763
  // bundle in memory and the new packages/config never load.
767
764
  //
768
765
  // 500ms debounce after detecting a change — lets the writer
769
- // (commit_setup's atomic rename, init-repo's full write) settle
766
+ // (commit_setup's atomic rename or a direct file write) settle
770
767
  // before loadRepo tries to read.
771
768
  // -------------------------------------------------------------------
772
769
 
@@ -5,128 +5,22 @@
5
5
  */
6
6
 
7
7
  import type {CommandModule} from 'yargs';
8
- import {resolvePlatformConfig} from '../shared/platform-client.js';
9
8
 
10
9
  export interface ExperimentOptions {
11
10
  action: 'create' | 'deploy' | 'watch' | 'list';
12
11
  name?: string;
13
12
  id?: string;
14
- platformUrl?: string;
15
- platformApiKey?: string;
16
13
  controlConfig?: string;
17
14
  variantConfig?: string;
18
15
  }
19
16
 
20
17
  /**
21
- * Manage model experiments via the platform API.
18
+ * Hosted experiment management is not available from the OSS CLI.
22
19
  */
23
20
  export async function runExperimentCommand(options: ExperimentOptions): Promise<void> {
24
- let platformUrl: string;
25
- let apiKey: string;
26
- try {
27
- const config = await resolvePlatformConfig({
28
- url: options.platformUrl,
29
- apiKey: options.platformApiKey,
30
- });
31
- platformUrl = config.url;
32
- apiKey = config.apiKey;
33
- } catch (err) {
34
- const msg = err instanceof Error ? err.message : String(err);
35
- process.stderr.write(`[experiment] ${msg}\n`);
36
- process.exit(1);
37
- }
38
-
39
- const headers = {
40
- 'Content-Type': 'application/json',
41
- 'Authorization': `Bearer ${apiKey}`,
42
- };
43
-
44
- switch (options.action) {
45
- case 'list': {
46
- const res = await fetch(`${platformUrl}/api/experiments`, {headers});
47
- if (!res.ok) {
48
- process.stderr.write(`[experiment] HTTP ${res.status}\n`);
49
- return;
50
- }
51
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- platform response
52
- const data = await res.json() as {experiments: Array<Record<string, unknown>>};
53
- if (data.experiments.length === 0) {
54
- process.stdout.write('No experiments found.\n');
55
- return;
56
- }
57
- for (const exp of data.experiments) {
58
- process.stdout.write(`${exp['id']} ${exp['name']} [${exp['status']}]\n`);
59
- }
60
- break;
61
- }
62
-
63
- case 'create': {
64
- if (!options.name) {
65
- process.stderr.write('[experiment] --name is required for create\n');
66
- return;
67
- }
68
- const controlConfig: Record<string, unknown> = options.controlConfig
69
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- CLI JSON input
70
- ? JSON.parse(options.controlConfig) as Record<string, unknown>
71
- : {};
72
- const variantConfig: Record<string, unknown> = options.variantConfig
73
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- CLI JSON input
74
- ? JSON.parse(options.variantConfig) as Record<string, unknown>
75
- : {};
76
-
77
- const res = await fetch(`${platformUrl}/api/experiments`, {
78
- method: 'POST',
79
- headers,
80
- body: JSON.stringify({name: options.name, controlConfig, variantConfig}),
81
- });
82
- if (!res.ok) {
83
- process.stderr.write(`[experiment] HTTP ${res.status}\n`);
84
- return;
85
- }
86
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- platform response
87
- const {id} = await res.json() as {id: string};
88
- process.stdout.write(`Created experiment: ${id}\n`);
89
- break;
90
- }
91
-
92
- case 'deploy': {
93
- if (!options.id) {
94
- process.stderr.write('[experiment] --id is required for deploy\n');
95
- return;
96
- }
97
- const res = await fetch(`${platformUrl}/api/experiments/${options.id}`, {
98
- method: 'PUT',
99
- headers,
100
- body: JSON.stringify({status: 'deployed'}),
101
- });
102
- if (!res.ok) {
103
- process.stderr.write(`[experiment] HTTP ${res.status}\n`);
104
- return;
105
- }
106
- process.stdout.write(`Deployed experiment: ${options.id}\n`);
107
- break;
108
- }
109
-
110
- case 'watch': {
111
- if (!options.id) {
112
- process.stderr.write('[experiment] --id is required for watch\n');
113
- return;
114
- }
115
- const res = await fetch(`${platformUrl}/api/experiments/${options.id}`, {headers});
116
- if (!res.ok) {
117
- process.stderr.write(`[experiment] HTTP ${res.status}\n`);
118
- return;
119
- }
120
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- platform response
121
- const exp = await res.json() as Record<string, unknown>;
122
- process.stdout.write(JSON.stringify(exp, null, 2) + '\n');
123
- break;
124
- }
125
-
126
- default:
127
- process.stderr.write(`[experiment] Unknown action: ${options.action}\n`);
128
- break;
129
- }
21
+ void options;
22
+ process.stderr.write('[experiment] Hosted experiment management is not included in the OSS CLI.\n');
23
+ process.exit(1);
130
24
  }
131
25
 
132
26
  export const experimentCommand: CommandModule = {
@@ -5,7 +5,6 @@
5
5
  */
6
6
 
7
7
  import type {CommandModule} from 'yargs';
8
- import {PlatformClient} from '../shared/platform-client.js';
9
8
 
10
9
  export interface PromoteOptions {
11
10
  fromEnv: string;
@@ -16,26 +15,9 @@ export interface PromoteOptions {
16
15
  * Promote a deployment from one environment to another.
17
16
  */
18
17
  export async function runPromote(options: PromoteOptions): Promise<number> {
19
- let client: PlatformClient;
20
- try {
21
- client = new PlatformClient();
22
- } catch (err) {
23
- const msg = err instanceof Error ? err.message : String(err);
24
- process.stderr.write(`[promote] ${msg}\n`);
25
- return 1;
26
- }
27
-
28
- const toEnv = options.toEnv ?? 'production';
29
-
30
- try {
31
- const result = await client.promote(options.fromEnv, toEnv);
32
- process.stderr.write(`[promote] Promoted from ${options.fromEnv} to ${toEnv}: ${result.id}\n`);
33
- return 0;
34
- } catch (err) {
35
- const msg = err instanceof Error ? err.message : String(err);
36
- process.stderr.write(`[promote] Failed: ${msg}\n`);
37
- return 1;
38
- }
18
+ void options;
19
+ process.stderr.write('[promote] Hosted deployment promotion is not included in the OSS CLI.\n');
20
+ return 1;
39
21
  }
40
22
 
41
23
  export const promoteCommand: CommandModule = {
@@ -5,7 +5,6 @@
5
5
  */
6
6
 
7
7
  import type {CommandModule} from 'yargs';
8
- import {PlatformClient} from '../shared/platform-client.js';
9
8
 
10
9
  export interface RollbackOptions {
11
10
  deployId?: string;
@@ -17,29 +16,9 @@ export interface RollbackOptions {
17
16
  * Rollback to a previous deployment.
18
17
  */
19
18
  export async function runRollback(options: RollbackOptions = {}): Promise<number> {
20
- let client: PlatformClient;
21
- try {
22
- client = new PlatformClient();
23
- } catch (err) {
24
- const msg = err instanceof Error ? err.message : String(err);
25
- process.stderr.write(`[rollback] ${msg}\n`);
26
- return 1;
27
- }
28
-
29
- const environment = options.env ?? 'production';
30
-
31
- try {
32
- const result = await client.rollback({
33
- deployId: options.deployId,
34
- environment,
35
- });
36
- process.stderr.write(`[rollback] Rolled back to ${result.id} in ${result.environment}\n`);
37
- return 0;
38
- } catch (err) {
39
- const msg = err instanceof Error ? err.message : String(err);
40
- process.stderr.write(`[rollback] Failed: ${msg}\n`);
41
- return 1;
42
- }
19
+ void options;
20
+ process.stderr.write('[rollback] Hosted deployment rollback is not included in the OSS CLI.\n');
21
+ return 1;
43
22
  }
44
23
 
45
24
  export const rollbackCommand: CommandModule = {
@@ -4,22 +4,15 @@
4
4
  * SPDX-License-Identifier: MIT
5
5
  */
6
6
 
7
- import {describe, it, expect, vi, beforeEach, afterEach} from 'vitest';
7
+ import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest';
8
8
 
9
9
  describe('runSecrets', () => {
10
10
  let stderrOutput: string;
11
11
  let stdoutOutput: string;
12
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
13
- let fetchSpy: any;
14
- const originalEnv = process.env;
12
+ let fetchSpy: ReturnType<typeof vi.spyOn>;
15
13
 
16
14
  beforeEach(() => {
17
15
  vi.clearAllMocks();
18
- process.env = {
19
- ...originalEnv,
20
- PLATFORM_API_URL: 'https://api.example.com',
21
- PLATFORM_API_KEY: 'test-key',
22
- };
23
16
  stderrOutput = '';
24
17
  stdoutOutput = '';
25
18
  vi.spyOn(process.stderr, 'write').mockImplementation((chunk) => {
@@ -34,135 +27,20 @@ describe('runSecrets', () => {
34
27
  });
35
28
 
36
29
  afterEach(() => {
37
- process.env = originalEnv;
38
- fetchSpy.mockRestore();
30
+ vi.restoreAllMocks();
39
31
  });
40
32
 
41
- it('should set a secret', async () => {
42
- fetchSpy.mockResolvedValue(new Response('{}', {status: 200}));
43
-
44
- const {runSecrets} = await import('./secrets.js');
45
- const result = await runSecrets({subcommand: 'set', key: 'DB_URL', value: 'postgres://...'});
46
- expect(result).toBe(0);
47
- expect(stderrOutput).toContain('set successfully');
48
- });
49
-
50
- it('should return 1 when key missing for set', async () => {
51
- const {runSecrets} = await import('./secrets.js');
52
- const result = await runSecrets({subcommand: 'set'});
53
- expect(result).toBe(1);
54
- expect(stderrOutput).toContain('Missing key');
55
- });
56
-
57
- it('should return 1 when value missing for set', async () => {
58
- const {runSecrets} = await import('./secrets.js');
59
- const result = await runSecrets({subcommand: 'set', key: 'DB_URL'});
60
- expect(result).toBe(1);
61
- expect(stderrOutput).toContain('Missing value');
62
- });
63
-
64
- it('should list secrets', async () => {
65
- fetchSpy.mockResolvedValue(new Response(JSON.stringify([
66
- {key: 'DB_URL'},
67
- {key: 'API_KEY'},
68
- ]), {status: 200}));
69
-
70
- const {runSecrets} = await import('./secrets.js');
71
- const result = await runSecrets({subcommand: 'list'});
72
- expect(result).toBe(0);
73
- expect(stdoutOutput).toContain('DB_URL');
74
- expect(stdoutOutput).toContain('API_KEY');
75
- });
76
-
77
- it('should list secrets as JSON', async () => {
78
- fetchSpy.mockResolvedValue(new Response(JSON.stringify([{key: 'DB_URL'}]), {status: 200}));
79
-
80
- const {runSecrets} = await import('./secrets.js');
81
- const result = await runSecrets({subcommand: 'list', json: true});
82
- expect(result).toBe(0);
83
- const parsed = JSON.parse(stdoutOutput);
84
- expect(parsed).toHaveLength(1);
85
- });
86
-
87
- it('should handle empty secrets list', async () => {
88
- fetchSpy.mockResolvedValue(new Response('[]', {status: 200}));
89
-
33
+ it.each([
34
+ {subcommand: 'set' as const, key: 'DB_URL', value: 'postgres://example'},
35
+ {subcommand: 'list' as const},
36
+ {subcommand: 'delete' as const, key: 'DB_URL'},
37
+ ])('fails closed for hosted secret management in OSS: $subcommand', async (options) => {
90
38
  const {runSecrets} = await import('./secrets.js');
91
- const result = await runSecrets({subcommand: 'list'});
92
- expect(result).toBe(0);
93
- expect(stderrOutput).toContain('No secrets configured');
94
- });
95
-
96
- it('should delete a secret', async () => {
97
- fetchSpy.mockResolvedValue(new Response('{}', {status: 200}));
39
+ const result = await runSecrets(options);
98
40
 
99
- const {runSecrets} = await import('./secrets.js');
100
- const result = await runSecrets({subcommand: 'delete', key: 'DB_URL'});
101
- expect(result).toBe(0);
102
- expect(stderrOutput).toContain('deleted');
103
- });
104
-
105
- it('should return 1 when key missing for delete', async () => {
106
- const {runSecrets} = await import('./secrets.js');
107
- const result = await runSecrets({subcommand: 'delete'});
108
41
  expect(result).toBe(1);
109
- expect(stderrOutput).toContain('Missing key');
110
- });
111
-
112
- it('should return 1 when platform not configured', async () => {
113
- delete process.env['PLATFORM_API_URL'];
114
- delete process.env['PLATFORM_API_KEY'];
115
-
116
- const {runSecrets} = await import('./secrets.js');
117
- const result = await runSecrets({subcommand: 'list'});
118
- expect(result).toBe(1);
119
- });
120
-
121
- it('should handle auth failure', async () => {
122
- fetchSpy.mockResolvedValue(new Response('Unauthorized', {status: 401}));
123
-
124
- const {runSecrets} = await import('./secrets.js');
125
- const result = await runSecrets({subcommand: 'list'});
126
- expect(result).toBe(1);
127
- expect(stderrOutput).toContain('401');
128
- });
129
-
130
- it('should handle network error', async () => {
131
- fetchSpy.mockRejectedValue(new Error('ECONNREFUSED'));
132
-
133
- const {runSecrets} = await import('./secrets.js');
134
- const result = await runSecrets({subcommand: 'set', key: 'K', value: 'V'});
135
- expect(result).toBe(1);
136
- expect(stderrOutput).toContain('ECONNREFUSED');
137
- });
138
-
139
- it('should handle set server error', async () => {
140
- fetchSpy.mockResolvedValue(new Response('Internal Error', {status: 500}));
141
-
142
- const {runSecrets} = await import('./secrets.js');
143
- const result = await runSecrets({subcommand: 'set', key: 'K', value: 'V'});
144
- expect(result).toBe(1);
145
- expect(stderrOutput).toContain('500');
146
- });
147
-
148
- it('should show count for listed secrets', async () => {
149
- fetchSpy.mockResolvedValue(new Response(JSON.stringify([
150
- {key: 'A'},
151
- {key: 'B'},
152
- {key: 'C'},
153
- ]), {status: 200}));
154
-
155
- const {runSecrets} = await import('./secrets.js');
156
- await runSecrets({subcommand: 'list'});
157
- expect(stderrOutput).toContain('3 secrets');
158
- });
159
-
160
- it('should use singular for single secret', async () => {
161
- fetchSpy.mockResolvedValue(new Response(JSON.stringify([{key: 'ONLY'}]), {status: 200}));
162
-
163
- const {runSecrets} = await import('./secrets.js');
164
- await runSecrets({subcommand: 'list'});
165
- expect(stderrOutput).toContain('1 secret configured');
166
- expect(stderrOutput).not.toContain('1 secrets');
42
+ expect(stderrOutput).toContain('Hosted secret management is not included in the OSS CLI');
43
+ expect(stdoutOutput).toBe('');
44
+ expect(fetchSpy).not.toHaveBeenCalled();
167
45
  });
168
46
  });