@outputai/cli 0.1.13-next.f537949.0 → 0.2.1-dev.82acd68.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/bin/run.js +4 -2
- package/dist/api/generated/api.d.ts +160 -7
- package/dist/api/generated/api.js +33 -1
- package/dist/api/http_client.js +24 -19
- package/dist/assets/docker/docker-compose-dev.yml +6 -10
- package/dist/commands/credentials/set.d.ts +16 -0
- package/dist/commands/credentials/set.js +125 -0
- package/dist/commands/credentials/set.spec.d.ts +1 -0
- package/dist/commands/credentials/set.spec.js +160 -0
- package/dist/commands/dev/index.js +12 -1
- package/dist/commands/fix.js +1 -1
- package/dist/commands/fix.spec.js +2 -2
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.js +5 -1
- package/dist/commands/init.spec.js +10 -5
- package/dist/commands/migrate.d.ts +11 -0
- package/dist/commands/migrate.js +40 -0
- package/dist/commands/update.js +1 -1
- package/dist/commands/update.spec.js +2 -2
- package/dist/commands/workflow/plan.js +6 -2
- package/dist/commands/workflow/plan.spec.js +3 -2
- package/dist/commands/workflow/run.d.ts +1 -1
- package/dist/commands/workflow/run.js +8 -5
- package/dist/commands/workflow/run.spec.js +3 -3
- package/dist/commands/workflow/runs/list.d.ts +1 -0
- package/dist/commands/workflow/runs/list.js +7 -0
- package/dist/commands/workflow/start.d.ts +1 -1
- package/dist/commands/workflow/start.js +8 -5
- package/dist/commands/workflow/start.spec.js +1 -1
- package/dist/config.d.ts +11 -38
- package/dist/config.js +34 -42
- package/dist/config.spec.d.ts +1 -0
- package/dist/config.spec.js +129 -0
- package/dist/generated/framework_version.json +1 -1
- package/dist/hooks/init.d.ts +4 -0
- package/dist/hooks/init.js +17 -1
- package/dist/hooks/init.spec.js +79 -5
- package/dist/services/claude_client.d.ts +14 -2
- package/dist/services/claude_client.integration.test.js +2 -2
- package/dist/services/claude_client.js +38 -5
- package/dist/services/claude_client.spec.js +3 -3
- package/dist/services/coding_agents.js +5 -1
- package/dist/services/coding_agents.spec.js +19 -6
- package/dist/services/credentials_configurator.js +1 -1
- package/dist/services/docker.js +5 -2
- package/dist/services/docker.spec.js +74 -3
- package/dist/services/env_configurator.js +1 -1
- package/dist/services/env_configurator.spec.js +12 -12
- package/dist/services/messages.js +2 -1
- package/dist/services/project_scaffold.d.ts +1 -1
- package/dist/services/project_scaffold.js +17 -2
- package/dist/services/project_scaffold.spec.js +6 -6
- package/dist/services/workflow_builder.d.ts +1 -1
- package/dist/services/workflow_builder.js +6 -2
- package/dist/services/workflow_builder.spec.js +3 -2
- package/dist/services/workflow_runs.d.ts +1 -0
- package/dist/services/workflow_runs.js +3 -0
- package/dist/templates/project/.env.example.template +17 -0
- package/dist/utils/credentials_loader.d.ts +1 -0
- package/dist/utils/credentials_loader.js +18 -0
- package/dist/utils/credentials_loader.spec.d.ts +1 -0
- package/dist/utils/credentials_loader.spec.js +84 -0
- package/dist/utils/env_loader.js +1 -2
- package/dist/utils/error_handler.js +10 -8
- package/dist/utils/interactive.d.ts +2 -0
- package/dist/utils/interactive.js +5 -0
- package/dist/utils/interactive.spec.d.ts +1 -0
- package/dist/utils/interactive.spec.js +40 -0
- package/dist/utils/prompt.d.ts +17 -0
- package/dist/utils/prompt.js +20 -0
- package/dist/utils/prompt.spec.d.ts +1 -0
- package/dist/utils/prompt.spec.js +70 -0
- package/dist/utils/proxy.d.ts +9 -0
- package/dist/utils/proxy.js +24 -0
- package/dist/utils/proxy.spec.d.ts +1 -0
- package/dist/utils/proxy.spec.js +48 -0
- package/dist/utils/validation.d.ts +13 -0
- package/dist/utils/validation.js +31 -0
- package/dist/utils/validation.spec.js +47 -1
- package/dist/views/dev.js +3 -3
- package/dist/views/workflow/list.js +10 -8
- package/package.json +10 -9
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
3
|
+
import { confirm } from '@inquirer/prompts';
|
|
4
|
+
import * as credentialsService from '#services/credentials_service.js';
|
|
5
|
+
import CredentialsSet from './set.js';
|
|
6
|
+
vi.mock('#services/credentials_service.js');
|
|
7
|
+
vi.mock('@inquirer/prompts', () => ({
|
|
8
|
+
confirm: vi.fn()
|
|
9
|
+
}));
|
|
10
|
+
vi.mock('js-yaml', () => ({
|
|
11
|
+
load: vi.fn((yaml) => {
|
|
12
|
+
if (yaml.includes('__PRIMITIVE_AT_X_Y__')) {
|
|
13
|
+
return { x: { y: 'FOO' } };
|
|
14
|
+
}
|
|
15
|
+
if (yaml.includes('__OBJECT_AT_X_Y__')) {
|
|
16
|
+
return { x: { y: { z: 'FOO' } } };
|
|
17
|
+
}
|
|
18
|
+
if (yaml.includes('sk-existing')) {
|
|
19
|
+
return { anthropic: { api_key: 'sk-existing' } };
|
|
20
|
+
}
|
|
21
|
+
return {};
|
|
22
|
+
}),
|
|
23
|
+
dump: vi.fn((obj) => JSON.stringify(obj))
|
|
24
|
+
}));
|
|
25
|
+
describe('credentials set command', () => {
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
vi.clearAllMocks();
|
|
28
|
+
vi.mocked(credentialsService.credentialsExist).mockReturnValue(true);
|
|
29
|
+
vi.mocked(credentialsService.decryptCredentials).mockReturnValue('anthropic:\n api_key: sk-existing\n');
|
|
30
|
+
vi.mocked(credentialsService.writeEncrypted).mockImplementation(() => { });
|
|
31
|
+
vi.mocked(confirm).mockResolvedValue(true);
|
|
32
|
+
});
|
|
33
|
+
afterEach(() => {
|
|
34
|
+
vi.restoreAllMocks();
|
|
35
|
+
});
|
|
36
|
+
const createTestCommand = (parsedArgs = {}, flags = {}) => {
|
|
37
|
+
const cmd = new CredentialsSet([], {});
|
|
38
|
+
cmd.log = vi.fn();
|
|
39
|
+
cmd.warn = vi.fn();
|
|
40
|
+
cmd.error = vi.fn((msg) => {
|
|
41
|
+
throw new Error(msg);
|
|
42
|
+
});
|
|
43
|
+
Object.defineProperty(cmd, 'parse', {
|
|
44
|
+
value: vi.fn().mockResolvedValue({
|
|
45
|
+
args: { path: 'anthropic.api_key', value: 'sk-new-key', ...parsedArgs },
|
|
46
|
+
flags: { environment: undefined, workflow: undefined, yes: false, ...flags }
|
|
47
|
+
}),
|
|
48
|
+
configurable: true
|
|
49
|
+
});
|
|
50
|
+
return cmd;
|
|
51
|
+
};
|
|
52
|
+
describe('command structure', () => {
|
|
53
|
+
it('should have correct description', () => {
|
|
54
|
+
expect(CredentialsSet.description).toContain('credential value');
|
|
55
|
+
});
|
|
56
|
+
it('should have required path and value arguments', () => {
|
|
57
|
+
expect(CredentialsSet.args.path).toBeDefined();
|
|
58
|
+
expect(CredentialsSet.args.path.required).toBe(true);
|
|
59
|
+
expect(CredentialsSet.args.value).toBeDefined();
|
|
60
|
+
expect(CredentialsSet.args.value.required).toBe(true);
|
|
61
|
+
});
|
|
62
|
+
it('should have environment, workflow, and yes flags', () => {
|
|
63
|
+
expect(CredentialsSet.flags.environment).toBeDefined();
|
|
64
|
+
expect(CredentialsSet.flags.workflow).toBeDefined();
|
|
65
|
+
expect(CredentialsSet.flags.yes).toBeDefined();
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
describe('command execution', () => {
|
|
69
|
+
it('should decrypt, update, and re-encrypt credentials', async () => {
|
|
70
|
+
const cmd = createTestCommand();
|
|
71
|
+
await cmd.run();
|
|
72
|
+
expect(credentialsService.decryptCredentials).toHaveBeenCalledWith(undefined, undefined);
|
|
73
|
+
expect(credentialsService.writeEncrypted).toHaveBeenCalledWith(undefined, expect.any(String), undefined);
|
|
74
|
+
expect(confirm).not.toHaveBeenCalled();
|
|
75
|
+
expect(cmd.log).toHaveBeenCalledWith('Set anthropic.api_key');
|
|
76
|
+
});
|
|
77
|
+
it('should create nested keys that do not exist', async () => {
|
|
78
|
+
vi.mocked(credentialsService.decryptCredentials).mockReturnValue('');
|
|
79
|
+
const cmd = createTestCommand({ path: 'new.nested.key', value: 'my-value' });
|
|
80
|
+
await cmd.run();
|
|
81
|
+
expect(credentialsService.writeEncrypted).toHaveBeenCalledTimes(1);
|
|
82
|
+
expect(confirm).not.toHaveBeenCalled();
|
|
83
|
+
expect(cmd.log).toHaveBeenCalledWith('Set new.nested.key');
|
|
84
|
+
});
|
|
85
|
+
it('should pass environment flag to service functions', async () => {
|
|
86
|
+
const cmd = createTestCommand({}, { environment: 'production' });
|
|
87
|
+
await cmd.run();
|
|
88
|
+
expect(credentialsService.credentialsExist).toHaveBeenCalledWith('production', undefined);
|
|
89
|
+
expect(credentialsService.decryptCredentials).toHaveBeenCalledWith('production', undefined);
|
|
90
|
+
expect(credentialsService.writeEncrypted).toHaveBeenCalledWith('production', expect.any(String), undefined);
|
|
91
|
+
});
|
|
92
|
+
it('should pass workflow flag to service functions', async () => {
|
|
93
|
+
const cmd = createTestCommand({}, { workflow: 'my_workflow' });
|
|
94
|
+
await cmd.run();
|
|
95
|
+
expect(credentialsService.credentialsExist).toHaveBeenCalledWith(undefined, 'my_workflow');
|
|
96
|
+
expect(credentialsService.decryptCredentials).toHaveBeenCalledWith(undefined, 'my_workflow');
|
|
97
|
+
expect(credentialsService.writeEncrypted).toHaveBeenCalledWith(undefined, expect.any(String), 'my_workflow');
|
|
98
|
+
});
|
|
99
|
+
it('should error when both environment and workflow are specified', async () => {
|
|
100
|
+
const cmd = createTestCommand({}, { environment: 'production', workflow: 'my_workflow' });
|
|
101
|
+
await expect(cmd.run()).rejects.toThrow('Cannot specify both');
|
|
102
|
+
});
|
|
103
|
+
it('should error when credentials file does not exist', async () => {
|
|
104
|
+
vi.mocked(credentialsService.credentialsExist).mockReturnValue(false);
|
|
105
|
+
vi.mocked(credentialsService.resolveCredentialsPath).mockReturnValue('/project/config/credentials.yml.enc');
|
|
106
|
+
const cmd = createTestCommand();
|
|
107
|
+
await expect(cmd.run()).rejects.toThrow('No credentials file found');
|
|
108
|
+
});
|
|
109
|
+
it('should surface a friendly error when decryption fails', async () => {
|
|
110
|
+
vi.mocked(credentialsService.decryptCredentials).mockImplementation(() => {
|
|
111
|
+
throw new Error('Invalid key');
|
|
112
|
+
});
|
|
113
|
+
const cmd = createTestCommand();
|
|
114
|
+
await expect(cmd.run()).rejects.toThrow('Failed to update credentials: Invalid key');
|
|
115
|
+
});
|
|
116
|
+
it('should surface a friendly error when encryption fails', async () => {
|
|
117
|
+
vi.mocked(credentialsService.writeEncrypted).mockImplementation(() => {
|
|
118
|
+
throw new Error('Permission denied');
|
|
119
|
+
});
|
|
120
|
+
const cmd = createTestCommand();
|
|
121
|
+
await expect(cmd.run()).rejects.toThrow('Failed to update credentials: Permission denied');
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
describe('shape-change confirmation', () => {
|
|
125
|
+
it('should prompt before converting a primitive value into an object', async () => {
|
|
126
|
+
vi.mocked(credentialsService.decryptCredentials).mockReturnValue('__PRIMITIVE_AT_X_Y__');
|
|
127
|
+
const cmd = createTestCommand({ path: 'x.y.z', value: 'BAR' });
|
|
128
|
+
await cmd.run();
|
|
129
|
+
expect(cmd.warn).toHaveBeenCalledWith(expect.stringContaining('convert "x.y" from a value into an object'));
|
|
130
|
+
expect(confirm).toHaveBeenCalledTimes(1);
|
|
131
|
+
expect(credentialsService.writeEncrypted).toHaveBeenCalledTimes(1);
|
|
132
|
+
expect(cmd.log).toHaveBeenCalledWith('Set x.y.z');
|
|
133
|
+
});
|
|
134
|
+
it('should prompt before replacing an object with a primitive value', async () => {
|
|
135
|
+
vi.mocked(credentialsService.decryptCredentials).mockReturnValue('__OBJECT_AT_X_Y__');
|
|
136
|
+
const cmd = createTestCommand({ path: 'x.y', value: 'BAR' });
|
|
137
|
+
await cmd.run();
|
|
138
|
+
expect(cmd.warn).toHaveBeenCalledWith(expect.stringContaining('replace the existing object'));
|
|
139
|
+
expect(confirm).toHaveBeenCalledTimes(1);
|
|
140
|
+
expect(credentialsService.writeEncrypted).toHaveBeenCalledTimes(1);
|
|
141
|
+
});
|
|
142
|
+
it('should abort without writing when the user declines the prompt', async () => {
|
|
143
|
+
vi.mocked(credentialsService.decryptCredentials).mockReturnValue('__PRIMITIVE_AT_X_Y__');
|
|
144
|
+
vi.mocked(confirm).mockResolvedValue(false);
|
|
145
|
+
const cmd = createTestCommand({ path: 'x.y.z', value: 'BAR' });
|
|
146
|
+
await cmd.run();
|
|
147
|
+
expect(credentialsService.writeEncrypted).not.toHaveBeenCalled();
|
|
148
|
+
expect(cmd.log).toHaveBeenCalledWith('Aborted.');
|
|
149
|
+
expect(cmd.log).not.toHaveBeenCalledWith('Set x.y.z');
|
|
150
|
+
});
|
|
151
|
+
it('should skip the prompt when --yes is passed', async () => {
|
|
152
|
+
vi.mocked(credentialsService.decryptCredentials).mockReturnValue('__PRIMITIVE_AT_X_Y__');
|
|
153
|
+
const cmd = createTestCommand({ path: 'x.y.z', value: 'BAR' }, { yes: true });
|
|
154
|
+
await cmd.run();
|
|
155
|
+
expect(confirm).not.toHaveBeenCalled();
|
|
156
|
+
expect(credentialsService.writeEncrypted).toHaveBeenCalledTimes(1);
|
|
157
|
+
expect(cmd.log).toHaveBeenCalledWith('Set x.y.z');
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
});
|
|
@@ -7,8 +7,16 @@ import { validateDockerEnvironment, startDockerCompose, startDockerComposeDetach
|
|
|
7
7
|
import { getErrorMessage } from '#utils/error_utils.js';
|
|
8
8
|
import { ensureClaudePlugin } from '#services/coding_agents.js';
|
|
9
9
|
import { DevApp } from '#views/dev.js';
|
|
10
|
+
import { config } from '#config.js';
|
|
10
11
|
export default class Dev extends Command {
|
|
11
|
-
static description =
|
|
12
|
+
static description = [
|
|
13
|
+
'Start Output development services (auto-restarts worker on file changes)',
|
|
14
|
+
'',
|
|
15
|
+
'To run a second dev stack concurrently, override host ports in .env:',
|
|
16
|
+
'',
|
|
17
|
+
' OUTPUT_API_HOST_PORT=3002',
|
|
18
|
+
' OUTPUT_TEMPORAL_UI_HOST_PORT=8081'
|
|
19
|
+
].join('\n');
|
|
12
20
|
static examples = [
|
|
13
21
|
'<%= config.bin %> <%= command.id %>',
|
|
14
22
|
'<%= config.bin %> <%= command.id %> --compose-file ./custom-docker-compose.yml',
|
|
@@ -38,6 +46,8 @@ export default class Dev extends Command {
|
|
|
38
46
|
// Ensure Claude plugin is configured (fire-and-forget, silent)
|
|
39
47
|
ensureClaudePlugin(process.cwd(), { silent: true }).catch(() => { });
|
|
40
48
|
validateDockerEnvironment();
|
|
49
|
+
// Eagerly resolve ports so InvalidPortError surfaces before Ink mounts.
|
|
50
|
+
void config.ports;
|
|
41
51
|
const dockerComposePath = flags['compose-file'] ?
|
|
42
52
|
path.resolve(process.cwd(), flags['compose-file']) :
|
|
43
53
|
getDefaultDockerComposePath();
|
|
@@ -48,6 +58,7 @@ export default class Dev extends Command {
|
|
|
48
58
|
throw new DockerComposeConfigNotFoundError(dockerComposePath);
|
|
49
59
|
}
|
|
50
60
|
this.log('\n🚀 Starting Output development services...\n');
|
|
61
|
+
this.log(`Docker project name: ${config.dockerServiceName}\n`);
|
|
51
62
|
if (flags['compose-file']) {
|
|
52
63
|
this.log(`Using custom docker-compose file: ${flags['compose-file']}\n`);
|
|
53
64
|
}
|
package/dist/commands/fix.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Command } from '@oclif/core';
|
|
2
|
-
import { confirm } from '
|
|
2
|
+
import { confirm } from '#utils/prompt.js';
|
|
3
3
|
import { applyFix, planFix } from '#services/fix_package.js';
|
|
4
4
|
import { getErrorMessage } from '#utils/error_utils.js';
|
|
5
5
|
const Ansi = {
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
3
3
|
import Fix from './fix.js';
|
|
4
4
|
import * as fixService from '#services/fix_package.js';
|
|
5
|
-
import { confirm } from '
|
|
5
|
+
import { confirm } from '#utils/prompt.js';
|
|
6
6
|
vi.mock('#services/fix_package.js', () => ({
|
|
7
7
|
planFix: vi.fn(),
|
|
8
8
|
applyFix: vi.fn()
|
|
9
9
|
}));
|
|
10
|
-
vi.mock('
|
|
10
|
+
vi.mock('#utils/prompt.js', () => ({
|
|
11
11
|
confirm: vi.fn()
|
|
12
12
|
}));
|
|
13
13
|
const basePlan = () => ({
|
package/dist/commands/init.d.ts
CHANGED
package/dist/commands/init.js
CHANGED
|
@@ -18,12 +18,16 @@ export default class Init extends Command {
|
|
|
18
18
|
'skip-env': Flags.boolean({
|
|
19
19
|
description: 'Skip interactive environment variable configuration',
|
|
20
20
|
default: false
|
|
21
|
+
}),
|
|
22
|
+
'skip-git': Flags.boolean({
|
|
23
|
+
description: 'Skip git repository initialization',
|
|
24
|
+
default: false
|
|
21
25
|
})
|
|
22
26
|
};
|
|
23
27
|
async run() {
|
|
24
28
|
try {
|
|
25
29
|
const { args, flags } = await this.parse(Init);
|
|
26
|
-
await runInit(flags['skip-env'], args.folderName);
|
|
30
|
+
await runInit(flags['skip-env'], flags['skip-git'], args.folderName);
|
|
27
31
|
}
|
|
28
32
|
catch (error) {
|
|
29
33
|
if (error instanceof UserCancelledError) {
|
|
@@ -37,7 +37,7 @@ describe('init command', () => {
|
|
|
37
37
|
Object.defineProperty(cmd, 'parse', {
|
|
38
38
|
value: vi.fn().mockResolvedValue({
|
|
39
39
|
args: { folderName: undefined, ...parsedArgs },
|
|
40
|
-
flags: { 'skip-env': false, ...flags }
|
|
40
|
+
flags: { 'skip-env': false, 'skip-git': false, ...flags }
|
|
41
41
|
}),
|
|
42
42
|
configurable: true
|
|
43
43
|
});
|
|
@@ -52,10 +52,10 @@ describe('init command', () => {
|
|
|
52
52
|
expect(cmd.run).toBeDefined();
|
|
53
53
|
expect(typeof cmd.run).toBe('function');
|
|
54
54
|
});
|
|
55
|
-
it('should call runInit with skip-env flag and folder name', async () => {
|
|
55
|
+
it('should call runInit with skip-env flag, skip-git flag, and folder name', async () => {
|
|
56
56
|
const cmd = createTestCommand();
|
|
57
57
|
await cmd.run();
|
|
58
|
-
expect(projectScaffold.runInit).toHaveBeenCalledWith(false, undefined);
|
|
58
|
+
expect(projectScaffold.runInit).toHaveBeenCalledWith(false, false, undefined);
|
|
59
59
|
});
|
|
60
60
|
it('should handle UserCancelledError', async () => {
|
|
61
61
|
const error = new UserCancelledError();
|
|
@@ -89,12 +89,17 @@ describe('init command', () => {
|
|
|
89
89
|
it('should pass skip-env flag to runInit when --skip-env is provided', async () => {
|
|
90
90
|
const cmd = createTestCommand([], { 'skip-env': true });
|
|
91
91
|
await cmd.run();
|
|
92
|
-
expect(projectScaffold.runInit).toHaveBeenCalledWith(true, undefined);
|
|
92
|
+
expect(projectScaffold.runInit).toHaveBeenCalledWith(true, false, undefined);
|
|
93
|
+
});
|
|
94
|
+
it('should pass skip-git flag to runInit when --skip-git is provided', async () => {
|
|
95
|
+
const cmd = createTestCommand([], { 'skip-git': true });
|
|
96
|
+
await cmd.run();
|
|
97
|
+
expect(projectScaffold.runInit).toHaveBeenCalledWith(false, true, undefined);
|
|
93
98
|
});
|
|
94
99
|
it('should pass folder name to runInit when provided', async () => {
|
|
95
100
|
const cmd = createTestCommand([], {}, { folderName: 'my-project' });
|
|
96
101
|
await cmd.run();
|
|
97
|
-
expect(projectScaffold.runInit).toHaveBeenCalledWith(false, 'my-project');
|
|
102
|
+
expect(projectScaffold.runInit).toHaveBeenCalledWith(false, false, 'my-project');
|
|
98
103
|
});
|
|
99
104
|
});
|
|
100
105
|
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class Migrate extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
from: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
+
to: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
notes: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
};
|
|
10
|
+
run(): Promise<void>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Command, Flags } from '@oclif/core';
|
|
2
|
+
import { ensureOutputAISystem } from '#services/coding_agents.js';
|
|
3
|
+
import { invokeMigrate } from '#services/claude_client.js';
|
|
4
|
+
export default class Migrate extends Command {
|
|
5
|
+
static description = 'Upgrade a project between versions of the Output framework. ' +
|
|
6
|
+
'Fetches the matching migration guide from docs.output.ai and applies it.';
|
|
7
|
+
static examples = [
|
|
8
|
+
'<%= config.bin %> <%= command.id %>',
|
|
9
|
+
'<%= config.bin %> <%= command.id %> --to 0.2.0',
|
|
10
|
+
'<%= config.bin %> <%= command.id %> --from 0.1.12 --to 0.2.0',
|
|
11
|
+
'<%= config.bin %> <%= command.id %> --to 0.2.0 --notes "skip the http changes, we don\'t use that package"'
|
|
12
|
+
];
|
|
13
|
+
static flags = {
|
|
14
|
+
from: Flags.string({
|
|
15
|
+
description: 'Version to migrate from. Defaults to the framework version in your package.json.',
|
|
16
|
+
required: false
|
|
17
|
+
}),
|
|
18
|
+
to: Flags.string({
|
|
19
|
+
description: 'Version to migrate to. Defaults to the latest published version.',
|
|
20
|
+
required: false
|
|
21
|
+
}),
|
|
22
|
+
notes: Flags.string({
|
|
23
|
+
char: 'n',
|
|
24
|
+
description: 'Extra guidance passed through to the migration agent.',
|
|
25
|
+
required: false
|
|
26
|
+
})
|
|
27
|
+
};
|
|
28
|
+
async run() {
|
|
29
|
+
const { flags } = await this.parse(Migrate);
|
|
30
|
+
const projectRoot = process.cwd();
|
|
31
|
+
this.log('Checking .outputai directory structure...');
|
|
32
|
+
await ensureOutputAISystem(projectRoot);
|
|
33
|
+
this.log('\nInvoking the /output-migrate skill...');
|
|
34
|
+
this.log('This may take a moment...\n');
|
|
35
|
+
const summary = await invokeMigrate(flags.from ?? '', flags.to ?? '', flags.notes);
|
|
36
|
+
this.log('\n=========');
|
|
37
|
+
this.log(summary);
|
|
38
|
+
this.log('=========\n');
|
|
39
|
+
}
|
|
40
|
+
}
|
package/dist/commands/update.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Command, Flags } from '@oclif/core';
|
|
2
|
-
import { confirm } from '
|
|
2
|
+
import { confirm } from '#utils/prompt.js';
|
|
3
3
|
import { fetchLatestVersion, getGlobalInstalledVersion, getLocalInstalledVersion, updateGlobal, updateLocal, isOutdated } from '#services/npm_update_service.js';
|
|
4
4
|
import { ensureClaudePlugin } from '#services/coding_agents.js';
|
|
5
5
|
import { getErrorMessage } from '#utils/error_utils.js';
|
|
@@ -3,7 +3,7 @@ import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
|
3
3
|
import Update from './update.js';
|
|
4
4
|
import { fetchLatestVersion, getGlobalInstalledVersion, getLocalInstalledVersion, updateGlobal, updateLocal, isOutdated } from '#services/npm_update_service.js';
|
|
5
5
|
import { ensureClaudePlugin } from '#services/coding_agents.js';
|
|
6
|
-
import { confirm } from '
|
|
6
|
+
import { confirm } from '#utils/prompt.js';
|
|
7
7
|
vi.mock('#services/npm_update_service.js', () => ({
|
|
8
8
|
fetchLatestVersion: vi.fn(),
|
|
9
9
|
getGlobalInstalledVersion: vi.fn(),
|
|
@@ -15,7 +15,7 @@ vi.mock('#services/npm_update_service.js', () => ({
|
|
|
15
15
|
vi.mock('#services/coding_agents.js', () => ({
|
|
16
16
|
ensureClaudePlugin: vi.fn()
|
|
17
17
|
}));
|
|
18
|
-
vi.mock('
|
|
18
|
+
vi.mock('#utils/prompt.js', () => ({
|
|
19
19
|
confirm: vi.fn()
|
|
20
20
|
}));
|
|
21
21
|
describe('update command', () => {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Command, Flags, ux } from '@oclif/core';
|
|
2
|
-
import { input } from '
|
|
2
|
+
import { input } from '#utils/prompt.js';
|
|
3
|
+
import { isInteractive } from '#utils/interactive.js';
|
|
3
4
|
import { generatePlanName, updateAgentTemplates, writePlanFile } from '#services/workflow_planner.js';
|
|
4
5
|
import { ensureOutputAISystem } from '#services/coding_agents.js';
|
|
5
6
|
import { invokePlanWorkflow, PLAN_COMMAND_OPTIONS, replyToClaude } from '#services/claude_client.js';
|
|
@@ -45,6 +46,9 @@ export default class WorkflowPlan extends Command {
|
|
|
45
46
|
this.log('=========');
|
|
46
47
|
this.log(originalPlanContent);
|
|
47
48
|
this.log('=========');
|
|
49
|
+
if (!isInteractive()) {
|
|
50
|
+
return originalPlanContent;
|
|
51
|
+
}
|
|
48
52
|
const modifications = await input({
|
|
49
53
|
message: ux.colorize('gray', `Reply or type ${acceptKey} to accept the plan as is: `),
|
|
50
54
|
validate: (value) => value.length >= 10 || value === acceptKey
|
|
@@ -56,7 +60,7 @@ export default class WorkflowPlan extends Command {
|
|
|
56
60
|
return this.planModificationLoop(modifiedPlanContent);
|
|
57
61
|
}
|
|
58
62
|
async planGenerationLoop(promptDescription, planName, projectRoot) {
|
|
59
|
-
this.log('\nInvoking the /
|
|
63
|
+
this.log('\nInvoking the /output-plan-workflow command...');
|
|
60
64
|
this.log('This may take a moment...\n');
|
|
61
65
|
const planContent = await invokePlanWorkflow(promptDescription);
|
|
62
66
|
const modifiedPlanContent = await this.planModificationLoop(planContent);
|
|
@@ -3,11 +3,12 @@ import WorkflowPlan from './plan.js';
|
|
|
3
3
|
import { generatePlanName, writePlanFile, updateAgentTemplates } from '#services/workflow_planner.js';
|
|
4
4
|
import { ensureOutputAISystem } from '#services/coding_agents.js';
|
|
5
5
|
import { invokePlanWorkflow, replyToClaude, ClaudeInvocationError } from '#services/claude_client.js';
|
|
6
|
-
import { input } from '
|
|
6
|
+
import { input } from '#utils/prompt.js';
|
|
7
7
|
vi.mock('#services/workflow_planner.js');
|
|
8
8
|
vi.mock('#services/coding_agents.js');
|
|
9
9
|
vi.mock('#services/claude_client.js');
|
|
10
|
-
vi.mock('
|
|
10
|
+
vi.mock('#utils/prompt.js');
|
|
11
|
+
vi.mock('#utils/interactive.js', () => ({ isInteractive: () => true }));
|
|
11
12
|
describe('WorkflowPlan Command', () => {
|
|
12
13
|
const createCommand = () => {
|
|
13
14
|
const cmd = new WorkflowPlan([], {});
|
|
@@ -8,7 +8,7 @@ export default class WorkflowRun extends Command {
|
|
|
8
8
|
};
|
|
9
9
|
static flags: {
|
|
10
10
|
input: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
-
|
|
11
|
+
catalog: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
12
|
format: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
13
|
};
|
|
14
14
|
run(): Promise<void>;
|
|
@@ -35,7 +35,7 @@ export default class WorkflowRun extends Command {
|
|
|
35
35
|
'<%= config.bin %> <%= command.id %> simple my_scenario --format json',
|
|
36
36
|
'<%= config.bin %> <%= command.id %> simple --input \'{"values":[1,2,3]}\'',
|
|
37
37
|
'<%= config.bin %> <%= command.id %> simple --input input.json',
|
|
38
|
-
'<%= config.bin %> <%= command.id %> simple --input \'{"key":"value"}\' --
|
|
38
|
+
'<%= config.bin %> <%= command.id %> simple --input \'{"key":"value"}\' --catalog my-catalog'
|
|
39
39
|
];
|
|
40
40
|
static args = {
|
|
41
41
|
workflowName: Args.string({
|
|
@@ -53,9 +53,12 @@ export default class WorkflowRun extends Command {
|
|
|
53
53
|
description: 'Workflow input as JSON string or file path (overrides scenario)',
|
|
54
54
|
required: false
|
|
55
55
|
}),
|
|
56
|
-
|
|
57
|
-
char: '
|
|
58
|
-
|
|
56
|
+
catalog: Flags.string({
|
|
57
|
+
char: 'c',
|
|
58
|
+
aliases: ['task-queue'],
|
|
59
|
+
charAliases: ['q'],
|
|
60
|
+
deprecateAliases: true,
|
|
61
|
+
description: 'Catalog name for workflow execution (defaults to OUTPUT_CATALOG_ID)',
|
|
59
62
|
env: 'OUTPUT_CATALOG_ID'
|
|
60
63
|
}),
|
|
61
64
|
format: Flags.string({
|
|
@@ -73,7 +76,7 @@ export default class WorkflowRun extends Command {
|
|
|
73
76
|
body: {
|
|
74
77
|
workflowName: args.workflowName,
|
|
75
78
|
input,
|
|
76
|
-
|
|
79
|
+
catalog: flags.catalog
|
|
77
80
|
},
|
|
78
81
|
options: { config: { timeout: 600000 } },
|
|
79
82
|
log: msg => this.log(msg)
|
|
@@ -27,7 +27,7 @@ describe('workflow run command', () => {
|
|
|
27
27
|
expect(WorkflowRun.args).toHaveProperty('workflowName');
|
|
28
28
|
expect(WorkflowRun.flags).toHaveProperty('input');
|
|
29
29
|
expect(WorkflowRun.flags).toHaveProperty('format');
|
|
30
|
-
expect(WorkflowRun.flags).toHaveProperty('
|
|
30
|
+
expect(WorkflowRun.flags).toHaveProperty('catalog');
|
|
31
31
|
});
|
|
32
32
|
it('should have correct flag configuration', async () => {
|
|
33
33
|
const WorkflowRun = (await import('./run.js')).default;
|
|
@@ -53,7 +53,7 @@ describe('workflow run command', () => {
|
|
|
53
53
|
});
|
|
54
54
|
cmd.parse = vi.fn().mockResolvedValue({
|
|
55
55
|
args: { workflowName: 'my_workflow', scenario: undefined },
|
|
56
|
-
flags: { input: undefined,
|
|
56
|
+
flags: { input: undefined, catalog: undefined, format: 'text' }
|
|
57
57
|
});
|
|
58
58
|
return { cmd, postWorkflowRun: vi.mocked(postWorkflowRun), resolveInput: vi.mocked(resolveInput) };
|
|
59
59
|
};
|
|
@@ -67,7 +67,7 @@ describe('workflow run command', () => {
|
|
|
67
67
|
});
|
|
68
68
|
await cmd.run();
|
|
69
69
|
expect(postWorkflowRun).toHaveBeenCalledTimes(1);
|
|
70
|
-
expect(postWorkflowRun).toHaveBeenCalledWith({ workflowName: 'my_workflow', input: { key: 'value' },
|
|
70
|
+
expect(postWorkflowRun).toHaveBeenCalledWith({ workflowName: 'my_workflow', input: { key: 'value' }, catalog: undefined }, expect.objectContaining({ config: { timeout: 600000 } }));
|
|
71
71
|
expect(cmd.log).toHaveBeenCalledWith('Executing workflow: my_workflow...');
|
|
72
72
|
expect(cmd.log).toHaveBeenCalledWith(expect.stringMatching(/\n/));
|
|
73
73
|
});
|
|
@@ -6,6 +6,7 @@ export default class WorkflowRunsList extends Command {
|
|
|
6
6
|
workflowName: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
7
7
|
};
|
|
8
8
|
static flags: {
|
|
9
|
+
catalog: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
10
|
limit: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
11
|
format: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
12
|
};
|
|
@@ -55,6 +55,7 @@ export default class WorkflowRunsList extends Command {
|
|
|
55
55
|
'<%= config.bin %> <%= command.id %>',
|
|
56
56
|
'<%= config.bin %> <%= command.id %> simple',
|
|
57
57
|
'<%= config.bin %> <%= command.id %> simple --limit 10',
|
|
58
|
+
'<%= config.bin %> <%= command.id %> --catalog my-catalog',
|
|
58
59
|
'<%= config.bin %> <%= command.id %> --format json',
|
|
59
60
|
'<%= config.bin %> <%= command.id %> --format table'
|
|
60
61
|
];
|
|
@@ -65,6 +66,11 @@ export default class WorkflowRunsList extends Command {
|
|
|
65
66
|
})
|
|
66
67
|
};
|
|
67
68
|
static flags = {
|
|
69
|
+
catalog: Flags.string({
|
|
70
|
+
char: 'c',
|
|
71
|
+
description: 'Filter runs by catalog (defaults to OUTPUT_CATALOG_ID)',
|
|
72
|
+
env: 'OUTPUT_CATALOG_ID'
|
|
73
|
+
}),
|
|
68
74
|
limit: Flags.integer({
|
|
69
75
|
char: 'l',
|
|
70
76
|
description: 'Maximum number of runs to return',
|
|
@@ -81,6 +87,7 @@ export default class WorkflowRunsList extends Command {
|
|
|
81
87
|
const { args, flags } = await this.parse(WorkflowRunsList);
|
|
82
88
|
const { runs, count } = await fetchWorkflowRuns({
|
|
83
89
|
workflowType: args.workflowName,
|
|
90
|
+
catalog: flags.catalog,
|
|
84
91
|
limit: flags.limit
|
|
85
92
|
});
|
|
86
93
|
if (runs.length === 0) {
|
|
@@ -8,7 +8,7 @@ export default class WorkflowStart extends Command {
|
|
|
8
8
|
};
|
|
9
9
|
static flags: {
|
|
10
10
|
input: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
-
|
|
11
|
+
catalog: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
12
|
};
|
|
13
13
|
run(): Promise<void>;
|
|
14
14
|
catch(error: Error): Promise<void>;
|
|
@@ -8,7 +8,7 @@ export default class WorkflowStart extends Command {
|
|
|
8
8
|
'<%= config.bin %> <%= command.id %> simple basic_input',
|
|
9
9
|
'<%= config.bin %> <%= command.id %> simple --input \'{"values":[1,2,3]}\'',
|
|
10
10
|
'<%= config.bin %> <%= command.id %> simple --input input.json',
|
|
11
|
-
'<%= config.bin %> <%= command.id %> simple --input \'{"key":"value"}\' --
|
|
11
|
+
'<%= config.bin %> <%= command.id %> simple --input \'{"key":"value"}\' --catalog my-catalog'
|
|
12
12
|
];
|
|
13
13
|
static args = {
|
|
14
14
|
workflowName: Args.string({
|
|
@@ -26,9 +26,12 @@ export default class WorkflowStart extends Command {
|
|
|
26
26
|
description: 'Workflow input as JSON string or file path (overrides scenario)',
|
|
27
27
|
required: false
|
|
28
28
|
}),
|
|
29
|
-
|
|
30
|
-
char: '
|
|
31
|
-
|
|
29
|
+
catalog: Flags.string({
|
|
30
|
+
char: 'c',
|
|
31
|
+
aliases: ['task-queue'],
|
|
32
|
+
charAliases: ['q'],
|
|
33
|
+
deprecateAliases: true,
|
|
34
|
+
description: 'Catalog name for workflow execution (defaults to OUTPUT_CATALOG_ID)',
|
|
32
35
|
env: 'OUTPUT_CATALOG_ID'
|
|
33
36
|
})
|
|
34
37
|
};
|
|
@@ -39,7 +42,7 @@ export default class WorkflowStart extends Command {
|
|
|
39
42
|
const response = await postWorkflowStart({
|
|
40
43
|
workflowName: args.workflowName,
|
|
41
44
|
input,
|
|
42
|
-
|
|
45
|
+
catalog: flags.catalog
|
|
43
46
|
});
|
|
44
47
|
if (!response || !response.data) {
|
|
45
48
|
this.error('API returned invalid response', { exit: 1 });
|
|
@@ -14,7 +14,7 @@ describe('workflow start command', () => {
|
|
|
14
14
|
expect(WorkflowStart.description).toContain('Start a workflow');
|
|
15
15
|
expect(WorkflowStart.args).toHaveProperty('workflowName');
|
|
16
16
|
expect(WorkflowStart.flags).toHaveProperty('input');
|
|
17
|
-
expect(WorkflowStart.flags).toHaveProperty('
|
|
17
|
+
expect(WorkflowStart.flags).toHaveProperty('catalog');
|
|
18
18
|
});
|
|
19
19
|
it('should have correct flag configuration', async () => {
|
|
20
20
|
const WorkflowStart = (await import('./start.js')).default;
|
package/dist/config.d.ts
CHANGED
|
@@ -1,44 +1,17 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* CLI configuration
|
|
3
|
-
*/
|
|
4
1
|
export declare const config: {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
* Set via OUTPUT_API_AUTH_TOKEN environment variable
|
|
13
|
-
*/
|
|
14
|
-
apiToken: string | undefined;
|
|
15
|
-
/**
|
|
16
|
-
* Default timeout for API requests (in milliseconds)
|
|
17
|
-
*/
|
|
2
|
+
readonly apiUrl: string;
|
|
3
|
+
readonly ports: {
|
|
4
|
+
temporalUi: number;
|
|
5
|
+
api: number;
|
|
6
|
+
};
|
|
7
|
+
readonly temporalUiUrl: string;
|
|
8
|
+
readonly apiToken: string | undefined;
|
|
18
9
|
requestTimeout: number;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
*/
|
|
23
|
-
dockerServiceName: string;
|
|
24
|
-
/**
|
|
25
|
-
* Set the debug mode
|
|
26
|
-
*/
|
|
27
|
-
debugMode: boolean;
|
|
28
|
-
/**
|
|
29
|
-
* Where the env vars are stored, defaults to `.env`
|
|
30
|
-
*/
|
|
31
|
-
envFile: string;
|
|
32
|
-
/**
|
|
33
|
-
* Agent configuration directory name
|
|
34
|
-
*/
|
|
10
|
+
readonly dockerServiceName: string;
|
|
11
|
+
readonly debugMode: boolean;
|
|
12
|
+
readonly envFile: string;
|
|
35
13
|
agentConfigDir: string;
|
|
36
|
-
|
|
37
|
-
* S3 configuration for remote trace storage
|
|
38
|
-
* Set via OUTPUT_TRACE_REMOTE_S3_BUCKET, OUTPUT_AWS_REGION,
|
|
39
|
-
* OUTPUT_AWS_ACCESS_KEY_ID, and OUTPUT_AWS_SECRET_ACCESS_KEY environment variables
|
|
40
|
-
*/
|
|
41
|
-
s3: {
|
|
14
|
+
readonly s3: {
|
|
42
15
|
bucket: string | undefined;
|
|
43
16
|
region: string | undefined;
|
|
44
17
|
accessKeyId: string | undefined;
|